// Ch.06 Daily stack map: story player (default) + explore mode

const MAP_RED = "#c0392b";
const LOGO_BASE = () => window.RESOURCE_LOGO_BASE || "assets/tools";
const PLAYER_STEP_MS = 2500;
const TILE_RADIUS = 52;
const HUB_TILE_RADIUS = 62;

function nodeHitRadius(node) {
  return node.hub ? HUB_TILE_RADIUS : TILE_RADIUS;
}

function edgeAnchor(node, towardId) {
  const other = window.getResourceMapNode?.(towardId);
  if (!other) return { x: node.x, y: node.y };
  const r = nodeHitRadius(node);
  const dx = other.x - node.x;
  const dy = other.y - node.y;
  const len = Math.hypot(dx, dy) || 1;
  const ux = dx / len;
  const uy = dy / len;
  const scale = r + 4;
  return { x: node.x + ux * scale, y: node.y + uy * scale };
}

function edgePath(fromNode, toNode) {
  const a = edgeAnchor(fromNode, toNode.id);
  const b = edgeAnchor(toNode, fromNode.id);
  const mx = (a.x + b.x) / 2;
  return `M ${a.x} ${a.y} C ${mx} ${a.y}, ${mx} ${b.y}, ${b.x} ${b.y}`;
}

function nodePositionPct(node, vb) {
  return {
    left: `${(node.x / vb.w) * 100}%`,
    top: `${(node.y / vb.h) * 100}%`,
  };
}

function MapToolBadge({ toolId, size = 28, pseudoLabel }) {
  const tool = window.getResourceTool?.(toolId);
  const label = pseudoLabel || tool?.name || "?";
  if (tool?.logo) {
    return (
      <img
        src={`${LOGO_BASE()}/${tool.logo}`}
        alt=""
        width={size}
        height={size}
        className="toolbelt-tile-logo-img"
      />
    );
  }
  return (
    <span
      className="toolbelt-tile-logo-fallback"
      style={{
        width: size,
        height: size,
        background: `color-mix(in srgb, ${tool?.color || MAP_RED} 22%, rgba(255,255,255,0.08))`,
        borderColor: `color-mix(in srgb, ${tool?.color || MAP_RED} 40%, transparent)`,
        color: tool?.color || "var(--ink-2)",
        fontSize: Math.max(14, size * 0.38),
      }}
      aria-hidden="true"
    >
      {label.charAt(0)}
    </span>
  );
}

function LogoTile({ node, state, accent, onClick, onMouseEnter }) {
  const tool = window.getResourceTool?.(node.toolId);
  const name = window.nodeDisplayName(node);
  const logoSize = node.hub ? 52 : 44;
  const pos = nodePositionPct(node, window.RESOURCE_MAP_VB);

  return (
    <button
      type="button"
      className={`toolbelt-logo-tile${node.hub ? " is-hub" : ""} is-${state}`}
      style={{ left: pos.left, top: pos.top, "--tile-accent": accent || tool?.color || MAP_RED }}
      onClick={onClick}
      onMouseEnter={onMouseEnter}
      aria-label={name}
    >
      <MapToolBadge toolId={node.toolId} size={logoSize} pseudoLabel={node.pseudo ? name : undefined} />
      <span className="toolbelt-logo-tile-name">{name}</span>
      {state === "active" && (
        <span className="toolbelt-logo-tile-chip caption">{node.hub ? "Vault hub" : "Active"}</span>
      )}
    </button>
  );
}

function ToolExplainer({ nodeId, onJumpToCard, onClose }) {
  const node = window.getResourceMapNode(nodeId);
  if (!node) return null;
  const tool = window.getResourceTool?.(node.toolId);
  const regions = window.RESOURCE_REGIONS || [];
  const region = regions.find((r) => r.id === node.region);
  const color = tool?.color || region?.color || MAP_RED;
  const name = window.nodeDisplayName(node);
  const edges = window.edgesForNode?.(nodeId) || [];
  const outbound = edges.filter((e) => e.from === nodeId);
  const inbound = edges.filter((e) => e.to === nodeId);

  return (
    <div className="toolbelt-explainer grid-12">
      <div className="toolbelt-explainer-head">
        <div className="toolbelt-explainer-logo">
          <MapToolBadge toolId={node.toolId} size={48} pseudoLabel={node.pseudo ? name : undefined} />
        </div>
        <div>
          <span className="caption" style={{ color }}>{region?.label}</span>
          <h4 className="toolbelt-explainer-title">{name}</h4>
        </div>
        <button type="button" className="toolbelt-explainer-close" onClick={onClose} aria-label="Close">×</button>
      </div>
      <div style={{ gridColumn: "span 6" }}>
        <p className="toolbelt-explainer-blurb">
          {tool?.blurb || "Part of the daily stack that feeds the vault and compounds over time."}
        </p>
        <button
          type="button"
          className="toolbelt-explainer-jump"
          onClick={() => onJumpToCard(node.toolId, node.id)}
        >
          Jump to card below ↓
        </button>
      </div>
      <div style={{ gridColumn: "span 6" }}>
        <span className="caption" style={{ color }}>Connections</span>
        <ul className="toolbelt-explainer-flows">
          {outbound.map((e) => {
            const to = window.getResourceMapNode(e.to);
            return (
              <li key={e.id}>
                <span className="toolbelt-explainer-dir" style={{ color }}>→</span>
                <div>
                  <strong>{window.nodeDisplayName(to)}</strong>
                  <span>{e.label} · {e.cadence}</span>
                </div>
              </li>
            );
          })}
          {inbound.map((e) => {
            const from = window.getResourceMapNode(e.from);
            return (
              <li key={e.id}>
                <span className="toolbelt-explainer-dir" style={{ color }}>←</span>
                <div>
                  <strong>{window.nodeDisplayName(from)}</strong>
                  <span>{e.label} · {e.cadence}</span>
                </div>
              </li>
            );
          })}
        </ul>
      </div>
    </div>
  );
}

function StoryPlayer({ activeStory, onSelectTool }) {
  const [stepIndex, setStepIndex] = React.useState(0);
  const [playing, setPlaying] = React.useState(false);

  const VB = window.RESOURCE_MAP_VB || { w: 1600, h: 900 };
  const viewBox = window.viewBoxString?.(VB) || `0 0 ${VB.w} ${VB.h}`;
  const story = window.getResourceMapStory?.(activeStory);
  const steps = window.stepsForStory?.(activeStory) || [];
  const storyEdges = window.edgesForStory?.(activeStory) || [];
  const storyNodeIds = story?.stepNodeIds || [];
  const storyNodes = storyNodeIds.map((id) => window.getResourceMapNode(id)).filter(Boolean);
  const storyRegions = window.regionsForStory?.(activeStory) || [];
  const regionColor = (id) => (window.RESOURCE_REGIONS || []).find((r) => r.id === id)?.color || MAP_RED;

  const nodeIndexById = React.useMemo(() => {
    const m = new Map();
    storyNodeIds.forEach((id, i) => m.set(id, i));
    return m;
  }, [storyNodeIds]);

  React.useEffect(() => {
    setStepIndex(0);
    setPlaying(false);
  }, [activeStory]);

  React.useEffect(() => {
    if (!playing || steps.length < 2) return undefined;
    const timer = setInterval(() => {
      setStepIndex((i) => (i >= steps.length - 1 ? 0 : i + 1));
    }, PLAYER_STEP_MS);
    return () => clearInterval(timer);
  }, [playing, steps.length]);

  React.useEffect(() => {
    const logos = new Set();
    storyNodes.forEach((n) => {
      const t = window.getResourceTool?.(n.toolId);
      if (t?.logo) logos.add(`${LOGO_BASE()}/${t.logo}`);
    });
    logos.forEach((src) => { const img = new Image(); img.src = src; });
  }, [activeStory, storyNodes]);

  const currentStep = steps[stepIndex] || steps[0];
  const activeNodeId = currentStep?.nodeId;
  const flowingEdgeId = currentStep?.edgeId;

  const nodeState = (nodeId) => {
    const idx = nodeIndexById.get(nodeId);
    if (idx == null) return "hidden";
    if (idx < stepIndex) return "past";
    if (idx === stepIndex) return "active";
    return "future";
  };

  const edgeState = (edge) => {
    const toIdx = nodeIndexById.get(edge.to);
    const fromIdx = nodeIndexById.get(edge.from);
    if (toIdx == null || fromIdx == null) return "hidden";
    if (Math.max(toIdx, fromIdx) > stepIndex) return "hidden";
    if (edge.id === flowingEdgeId && stepIndex > 0) return "flowing";
    return "past";
  };

  const goPrev = () => {
    setPlaying(false);
    setStepIndex((i) => Math.max(0, i - 1));
  };

  const goNext = () => {
    setPlaying(false);
    setStepIndex((i) => Math.min(steps.length - 1, i + 1));
  };

  const togglePlay = () => setPlaying((p) => !p);

  const progressLabel = steps.length
    ? `${String(stepIndex + 1).padStart(2, "0")} / ${String(steps.length).padStart(2, "0")} · ${currentStep?.caption || ""}`
    : "";

  return (
    <div className="toolbelt-player">
      <div className="toolbelt-player-controls">
        <button type="button" className="toolbelt-player-btn" onClick={goPrev} disabled={stepIndex === 0} aria-label="Previous step">
          ←
        </button>
        <button type="button" className={`toolbelt-player-btn toolbelt-player-btn--play${playing ? " is-playing" : ""}`} onClick={togglePlay} aria-label={playing ? "Pause" : "Play"}>
          {playing ? "❚❚" : "▶"}
        </button>
        <button type="button" className="toolbelt-player-btn" onClick={goNext} disabled={stepIndex >= steps.length - 1} aria-label="Next step">
          →
        </button>
        <span className="toolbelt-player-progress caption">{progressLabel}</span>
      </div>

      <div className="toolbelt-player-canvas">
        <svg viewBox={viewBox} preserveAspectRatio="xMidYMid meet" className="toolbelt-player-svg" aria-hidden="true">
          <defs>
            <pattern id="toolbeltPlayerDots" width="28" height="28" patternUnits="userSpaceOnUse">
              <circle cx="2" cy="2" r="0.8" fill="rgba(255,255,255,0.035)" />
            </pattern>
          </defs>
          <rect x="0" y="0" width={VB.w} height={VB.h} fill="url(#toolbeltPlayerDots)" />
          {storyRegions.map((r) => (
            <ellipse
              key={r.id}
              cx={r.cx}
              cy={r.cy}
              rx={r.rx}
              ry={r.ry}
              fill={r.color}
              opacity={0.045}
              pointerEvents="none"
            />
          ))}
          <g className="toolbelt-player-edges">
            {storyEdges.map((edge) => {
              const fromN = window.getResourceMapNode(edge.from);
              const toN = window.getResourceMapNode(edge.to);
              if (!fromN || !toN) return null;
              const state = edgeState(edge);
              if (state === "hidden") return null;
              const d = edgePath(fromN, toN);
              return (
                <path
                  key={edge.id}
                  d={d}
                  fill="none"
                  stroke={regionColor(fromN.region)}
                  strokeWidth={state === "flowing" ? 2.5 : 2}
                  strokeOpacity={state === "flowing" ? 0.95 : 0.45}
                  strokeLinecap="round"
                  className={`toolbelt-edge-path is-${state}`}
                />
              );
            })}
          </g>
        </svg>

        <div className="toolbelt-player-tiles">
          {storyNodes.map((node) => {
            const state = nodeState(node.id);
            if (state === "hidden") return null;
            const tool = window.getResourceTool?.(node.toolId);
            return (
              <LogoTile
                key={node.id}
                node={node}
                state={state}
                accent={tool?.color || regionColor(node.region)}
                onClick={() => onSelectTool(node.toolId, node.id)}
              />
            );
          })}
        </div>
      </div>

      <div className="toolbelt-player-caption">
        <span className="caption" style={{ color: MAP_RED }}>{story?.label}</span>
        <p className="toolbelt-player-caption-text">
          {stepIndex === 0 ? story?.copy : currentStep?.caption}
        </p>
      </div>
    </div>
  );
}

function StackExplorer({ activeStory, onSelectTool }) {
  const [hovered, setHovered] = React.useState(null);
  const [selected, setSelected] = React.useState(null);
  const hoverRaf = React.useRef(null);

  const VB = window.RESOURCE_MAP_VB || { w: 1600, h: 900 };
  const viewBox = window.viewBoxString?.(VB) || `0 0 ${VB.w} ${VB.h}`;
  const regions = window.RESOURCE_REGIONS || [];
  const nodes = window.RESOURCE_MAP_NODES || [];
  const allEdges = window.RESOURCE_MAP_EDGES || [];

  const storyEdgeIdSet = React.useMemo(() => {
    const story = window.getResourceMapStory?.(activeStory);
    if (!story?.edgeIds) return null;
    return new Set(story.edgeIds);
  }, [activeStory]);

  const effectiveFocus = selected || hovered;

  const highlightIds = React.useMemo(() => {
    if (!effectiveFocus) return null;
    return window.connectedNodeIds(effectiveFocus, allEdges);
  }, [effectiveFocus, allEdges]);

  const regionColor = (id) => regions.find((r) => r.id === id)?.color || MAP_RED;

  const setHoveredDebounced = (id) => {
    if (selected) return;
    if (hoverRaf.current) cancelAnimationFrame(hoverRaf.current);
    hoverRaf.current = requestAnimationFrame(() => setHovered(id));
  };

  const clearHover = () => {
    if (hoverRaf.current) cancelAnimationFrame(hoverRaf.current);
    if (!selected) setHovered(null);
  };

  const nodeState = (nodeId) => {
    if (!highlightIds) return "visible";
    return highlightIds.has(nodeId) ? "visible" : "dim";
  };

  const edgeHi = (edge) => {
    if (!highlightIds) return storyEdgeIdSet ? storyEdgeIdSet.has(edge.id) : false;
    return highlightIds.has(edge.from) && highlightIds.has(edge.to);
  };

  const edgeVis = (edge) => {
    if (highlightIds) return edgeHi(edge);
    if (storyEdgeIdSet) return storyEdgeIdSet.has(edge.id);
    return true;
  };

  return (
    <div className="toolbelt-explorer">
      <div className="toolbelt-map-legend">
        {regions.map((r) => (
          <span key={r.id} className="toolbelt-map-legend-item" style={{ "--region-color": r.color }}>
            <span className="toolbelt-map-legend-dot" />
            {r.label}
          </span>
        ))}
        <span className="toolbelt-map-legend-hint caption">Hover to trace · click for detail</span>
      </div>

      <div className="toolbelt-player-canvas" onMouseLeave={clearHover}>
        <svg viewBox={viewBox} preserveAspectRatio="xMidYMid meet" className="toolbelt-player-svg" aria-hidden="true">
          <defs>
            <pattern id="toolbeltExploreDots" width="28" height="28" patternUnits="userSpaceOnUse">
              <circle cx="2" cy="2" r="0.8" fill="rgba(255,255,255,0.035)" />
            </pattern>
          </defs>
          <rect x="0" y="0" width={VB.w} height={VB.h} fill="url(#toolbeltExploreDots)" />
          {regions.map((r) => (
            <ellipse
              key={r.id}
              cx={r.cx}
              cy={r.cy}
              rx={r.rx}
              ry={r.ry}
              fill={r.color}
              opacity={effectiveFocus ? 0.03 : 0.055}
              pointerEvents="none"
            />
          ))}
          <g className="toolbelt-explorer-edges">
            {allEdges.map((edge) => {
              const fromN = window.getResourceMapNode(edge.from);
              const toN = window.getResourceMapNode(edge.to);
              if (!fromN || !toN) return null;
              const hi = edgeHi(edge);
              const vis = edgeVis(edge);
              const d = edgePath(fromN, toN);
              const mid = { x: (fromN.x + toN.x) / 2, y: (fromN.y + toN.y) / 2 };
              return (
                <g key={edge.id} pointerEvents="none">
                  <path
                    d={d}
                    fill="none"
                    stroke={regionColor(fromN.region)}
                    strokeWidth={hi ? 2.5 : 1.5}
                    strokeOpacity={hi ? 0.9 : vis ? 0.35 : 0.08}
                    strokeDasharray={edge.weak && !hi ? "5 6" : undefined}
                    strokeLinecap="round"
                    className={`toolbelt-edge-path${hi ? " is-hi" : ""}`}
                  />
                  {hi && effectiveFocus && (
                    <g transform={`translate(${mid.x}, ${mid.y})`}>
                      <rect x={-68} y={-12} width={136} height={24} rx={6} className="toolbelt-map-edge-label-bg" />
                      <text textAnchor="middle" dy={4} className="toolbelt-map-edge-label">{edge.label}</text>
                    </g>
                  )}
                </g>
              );
            })}
          </g>
        </svg>

        <div className="toolbelt-player-tiles">
          {nodes.map((node) => {
            const state = nodeState(node.id);
            const active = selected === node.id || hovered === node.id;
            const tool = window.getResourceTool?.(node.toolId);
            const tileState = active ? "active" : state === "dim" ? "future" : "visible";
            return (
              <LogoTile
                key={node.id}
                node={node}
                state={tileState}
                accent={tool?.color || regionColor(node.region)}
                onClick={() => {
                  setSelected((prev) => (prev === node.id ? null : node.id));
                  setHovered(null);
                }}
                onMouseEnter={() => setHoveredDebounced(node.id)}
              />
            );
          })}
        </div>
      </div>

      <div className={`toolbelt-explainer-panel${selected ? " is-open" : ""}`}>
        {selected && (
          <ToolExplainer
            nodeId={selected}
            onJumpToCard={onSelectTool}
            onClose={() => setSelected(null)}
          />
        )}
      </div>
    </div>
  );
}

function ToolbeltMapDesktop({ activeStory, onStoryChange, onSelectTool }) {
  const [mode, setMode] = React.useState(activeStory === "full" ? "explore" : "player");
  const stories = window.RESOURCE_MAP_STORIES || [];
  const guidedStories = stories.filter((s) => s.id !== "full");
  const defaultStory = (guidedStories[0] && guidedStories[0].id) || "reading";

  // Remember the last guided story so Explore keeps its pill and returns to it.
  const lastGuidedRef = React.useRef(activeStory === "full" ? defaultStory : activeStory);
  React.useEffect(() => {
    if (activeStory !== "full") lastGuidedRef.current = activeStory;
  }, [activeStory]);

  React.useEffect(() => {
    setMode(activeStory === "full" ? "explore" : "player");
  }, [activeStory]);

  const handleStoryChange = (id) => {
    onStoryChange(id);
    if (mode === "explore") setMode("player");
  };

  const handleModeToggle = () => {
    if (mode === "player") {
      setMode("explore");
      onStoryChange("full");
    } else {
      setMode("player");
      if (activeStory === "full") onStoryChange(lastGuidedRef.current);
    }
  };

  // While exploring, keep the pill on the story you'd return to.
  const pillActive = mode === "explore" ? lastGuidedRef.current : activeStory;

  return (
    <div className="toolbelt-map-desktop hide-mobile">
      <div className="toolbelt-map-toolbar">
        <div className="toolbelt-map-story-row">
          <GlassSegmented
            className="glass-seg-resources glass-seg-resources--map"
            items={guidedStories.map((s, i) => ({
              id: s.id,
              num: String(i + 1).padStart(2, "0"),
              label: s.label,
              sub: s.sub,
              color: MAP_RED,
            }))}
            active={pillActive}
            onChange={handleStoryChange}
            ariaLabel="Daily stack stories"
          />
        </div>
        <button type="button" className="toolbelt-mode-toggle" onClick={handleModeToggle}>
          {mode === "player" ? "Explore full stack" : "Back to story"}
        </button>
      </div>

      <div className="toolbelt-map-layout">
        <div className="toolbelt-flow-diagram-wrap" id="resources-map">
          {mode === "player" ? (
            <StoryPlayer
              activeStory={activeStory === "full" ? "reading" : activeStory}
              onSelectTool={onSelectTool}
            />
          ) : (
            <StackExplorer
              activeStory={activeStory}
              onSelectTool={onSelectTool}
            />
          )}
        </div>

        {mode === "explore" && (
          <aside className="toolbelt-map-copy">
            <span className="caption" style={{ color: MAP_RED }}>
              {window.getResourceMapStory(activeStory)?.label || "Full stack"}
            </span>
            <p className="toolbelt-map-copy-body">
              {window.getResourceMapStory(activeStory)?.copy}
            </p>
            <span className="caption toolbelt-map-copy-hint">
              Hover a logo to trace connections. Click for detail.
            </span>
          </aside>
        )}
      </div>
    </div>
  );
}

function ToolbeltMapMobile({ activeStory, onStoryChange, onSelectTool }) {
  const stories = window.RESOURCE_MAP_STORIES || [];
  const guidedStories = stories.filter((s) => s.id !== "full");
  const story = guidedStories.find((s) => s.id === activeStory) || guidedStories[0];
  const nodes = (story.stepNodeIds || window.RESOURCE_MAP_NODES.map((n) => n.id))
    .map((id) => window.getResourceMapNode(id))
    .filter(Boolean);
  const edges = window.edgesForStory?.(story.id) || [];

  const edgeLabelFor = (nodeId, idx) => {
    if (idx === 0) return "Start";
    const prev = nodes[idx - 1];
    const edge = edges.find((e) => e.to === nodeId && e.from === prev?.id)
      || edges.find((e) => e.to === nodeId);
    return edge?.label || "flows to";
  };

  return (
    <div className="toolbelt-map-mobile show-mobile" id="resources-map">
      <div className="toolbelt-map-story-row">
        <GlassSegmented
          className="glass-seg-resources"
          items={guidedStories.map((s, i) => ({
            id: s.id,
            num: String(i + 1).padStart(2, "0"),
            label: s.label,
            sub: s.sub,
            color: MAP_RED,
          }))}
          active={story.id}
          onChange={onStoryChange}
          ariaLabel="Daily stack stories"
        />
      </div>
      <p className="toolbelt-map-mobile-copy">{story.copy}</p>
      <div className="toolbelt-map-mobile-steps">
        {nodes.map((node, i) => {
          const tool = window.getResourceTool?.(node.toolId);
          const name = node.label || tool?.name;
          return (
            <React.Fragment key={node.id}>
              {i > 0 && (
                <div className="toolbelt-map-mobile-arrow">
                  <span className="caption">{edgeLabelFor(node.id, i)}</span>
                  <span>↓</span>
                </div>
              )}
              <button
                type="button"
                className="toolbelt-map-mobile-step"
                onClick={() => onSelectTool(node.toolId, node.id)}
              >
                <MapToolBadge toolId={node.toolId} size={32} pseudoLabel={node.pseudo ? name : undefined} />
                <span className="toolbelt-map-mobile-step-name">{name}</span>
              </button>
            </React.Fragment>
          );
        })}
      </div>
    </div>
  );
}

function ToolbeltMap({ activeStory, onStoryChange, onSelectTool }) {
  return (
    <section className="toolbelt-map-section" aria-label="Daily stack map">
      <div className="page toolbelt-map-header-wrap">
        <div className="toolbelt-map-header">
          <span className="caption" style={{ color: MAP_RED }}>Daily stack map</span>
          <h3 className="kicker" style={{ margin: "8px 0 0" }}>The system, not the apps</h3>
          <p className="body-text" style={{ margin: "10px 0 0", maxWidth: "54ch", color: "var(--ink-2)", fontSize: 15 }}>
            The principle travels even if the tools don't: keep one source of truth, and let AI compound on it daily. Everything worth remembering flows into a single vault. These are the apps I happen to route through it.
          </p>
        </div>
      </div>
      <div className="toolbelt-map-canvas-bleed">
        <div className="page">
          <ToolbeltMapDesktop
            activeStory={activeStory}
            onStoryChange={onStoryChange}
            onSelectTool={onSelectTool}
          />
        </div>
      </div>
      <div className="page">
        <ToolbeltMapMobile
          activeStory={activeStory}
          onStoryChange={onStoryChange}
          onSelectTool={onSelectTool}
        />
      </div>
    </section>
  );
}

window.ToolbeltMap = ToolbeltMap;
