// === Mind-map screen — physics-driven floating nodes, multi-level ===
const { useState: msUseState, useEffect: msUseEffect, useRef: msUseRef, useMemo: msUseMemo } = React;

const DEFAULT_MAP_CATEGORIES = [
  { id: 'coding',   label: 'CODING',          color: '#00f0ff', glyph: '⌨', sub: [
    { id: 'coding-ide',  label: 'IN-IDE',     color: '#00f0ff', toolIds: ['codeforge','pairwise'] },
    { id: 'coding-cli',  label: 'CLI / AGENT',color: '#3affb6', toolIds: ['shellsmith'] },
    { id: 'coding-rev',  label: 'PR REVIEW',  color: '#a78bff', toolIds: ['devstream'] },
  ]},
  { id: 'meetings', label: 'MEETING NOTES',   color: '#a78bff', glyph: '◈', sub: [
    { id: 'meet-live',   label: 'LIVE',       color: '#a78bff', toolIds: ['transcribr'] },
    { id: 'meet-async',  label: 'ASYNC',      color: '#ff2bd6', toolIds: ['minutemate'] },
    { id: 'meet-local',  label: 'ON-DEVICE',  color: '#c6ff3d', toolIds: ['scribed'] },
  ]},
  { id: 'local',    label: 'LOCAL · PRIVATE', color: '#c6ff3d', glyph: '⬢', sub: [
    { id: 'local-runner',label: 'RUNNERS',    color: '#c6ff3d', toolIds: ['hearthlm'] },
    { id: 'local-models',label: 'OPEN MODELS',color: '#3affb6', toolIds: ['orbit7b'] },
    { id: 'local-host',  label: 'SELF-HOST',  color: '#a78bff', toolIds: ['privacypod'] },
  ]},
  { id: 'video',    label: 'VIDEO',           color: '#ff2bd6', glyph: '▶', sub: [
    { id: 'video-gen',   label: 'GENERATIVE', color: '#ff2bd6', toolIds: ['reelweaver'] },
    { id: 'video-avatar',label: 'AVATAR',     color: '#00f0ff', toolIds: ['frameforge'] },
    { id: 'video-edit',  label: 'AUTO-EDIT',  color: '#a78bff', toolIds: ['cuttr'] },
  ]},
  { id: 'legal',    label: 'LEGAL',           color: '#ffb547', glyph: '§', sub: [
    { id: 'legal-rev',   label: 'DOC REVIEW', color: '#ffb547', toolIds: ['lexreader'] },
    { id: 'legal-redline',label:'CONTRACTS',  color: '#ff2bd6', toolIds: ['briefly'] },
  ]},
  { id: 'chat',     label: 'GENERALIST',      color: '#3affb6', glyph: '◆', sub: [
    { id: 'chat-pro',    label: 'POWER USER', color: '#00f0ff', toolIds: ['aurora','sable'] },
    { id: 'chat-multi',  label: 'MULTIMODAL', color: '#3affb6', toolIds: ['pollen'] },
  ]},
];

function getMapCategories() {
  return window.MAP_CATEGORIES?.length ? window.MAP_CATEGORIES : DEFAULT_MAP_CATEGORIES;
}

function toolsForSub(sub) {
  return sub.toolIds.map(id => window.TOOLS.find(t => t.id === id)).filter(Boolean);
}

function buildLevel(path, categories = getMapCategories()) {
  if (path.length === 0) {
    return {
      level: 0,
      root: { id: '__root', label: `${window.MAP_TOTAL_TOOLS || window.TOOLS.length} TOOLS`, color: '#00f0ff', glyph: '⌬', kind: 'root' },
      children: categories.map(c => ({ id: c.id, label: c.label, color: c.color, glyph: c.glyph, kind: 'cat', meta: c })),
    };
  }
  if (path.length === 1) {
    const cat = categories.find(c => c.id === path[0]);
    if (!cat) return buildLevel([], categories);
    const onlySub = cat.sub.length === 1 ? cat.sub[0] : null;
    if (onlySub) {
      const tools = toolsForSub(onlySub);
      return {
        level: 1,
        root: { id: cat.id, label: cat.label, color: cat.color, glyph: cat.glyph, kind: 'cat' },
        children: tools.map(t => ({ id: t.id, label: t.name, color: t.color, glyph: t.emoji, kind: 'tool', meta: t })),
      };
    }
    return {
      level: 1,
      root: { id: cat.id, label: cat.label, color: cat.color, glyph: cat.glyph, kind: 'cat' },
      children: cat.sub.map(s => ({ id: s.id, label: s.label, color: s.color, glyph: '◇', kind: 'sub', meta: s })),
    };
  }
  const cat = categories.find(c => c.id === path[0]);
  if (!cat) return buildLevel([], categories);
  const sub = cat.sub.find(s => s.id === path[1]);
  if (!sub) return buildLevel([cat.id], categories);
  const tools = toolsForSub(sub);
  return {
    level: 2,
    root: { id: sub.id, label: sub.label, color: sub.color, glyph: '◇', kind: 'sub' },
    children: tools.map(t => ({ id: t.id, label: t.name, color: t.color, glyph: t.emoji, kind: 'tool', meta: t })),
  };
}

function MindMap({ onOpenTool, onBack, path: pathProp, setPath: setPathProp }) {
  const [pathLocal, setPathLocal] = msUseState([]);
  const path = pathProp !== undefined ? pathProp : pathLocal;
  const setPath = setPathProp || setPathLocal;
  const [mapCategories, setMapCategories] = msUseState(getMapCategories);
  const [hover, setHover] = msUseState(null);
  const containerRef = msUseRef(null);
  const [size, setSize] = msUseState({ w: 1200, h: 700 });

  msUseEffect(() => {
    const measure = () => {
      const el = containerRef.current;
      if (el) { const r = el.getBoundingClientRect(); setSize({ w: r.width, h: r.height }); }
    };
    measure();
    window.addEventListener('resize', measure);
    return () => window.removeEventListener('resize', measure);
  }, []);

  msUseEffect(() => {
    let cancelled = false;
    fetch('/api/map')
      .then(r => r.ok ? r.json() : null)
      .then(data => {
        if (cancelled || !data?.categories?.length || !data?.tools?.length) return;
        window.MAP_CATEGORIES = data.categories;
        window.MAP_TOTAL_TOOLS = data.totalTools;
        window.TOOLS = data.tools;
        window.TOOL_DETAILS = {
          ...window.TOOL_DETAILS,
          ...Object.fromEntries(Object.entries(data.details || {}).map(([slug, detail]) => [slug, {
            bestFor: detail.best_for,
            notFor: detail.not_for,
            take: detail.editorial_take,
            pros: detail.pros,
            cons: detail.cons,
          }]))
        };
        setMapCategories(data.categories);
      })
      .catch(() => {});
    return () => { cancelled = true; };
  }, []);

  const level = msUseMemo(() => buildLevel(path, mapCategories), [path, mapCategories]);

  // Persistent physics state: id -> {x, y, vx, vy, r}
  const nodesRef = msUseRef({});
  // Track which node IDs are currently in the active level (so we know what to render + simulate)
  const activeIdsRef = msUseRef(new Set());
  const [, force] = msUseState(0);

  // When level changes, transition smoothly:
  // - The new root node may have existed as a child in the previous level (clicked-into) — preserve its position
  // - Any existing node with the same id keeps its position+velocity
  // - New child nodes are seeded near the new root with a slight outward burst
  msUseEffect(() => {
    const cx = size.w / 2, cy = size.h / 2;
    const nodes = nodesRef.current;
    const newIds = new Set();

    // Root: prefer existing position (e.g. the category we just clicked); else center
    const rootId = '__root_' + (path.join('>') || 'top');
    // Use a stable per-level rootId so simulation handles it as the root for that level
    const prevRootId = Object.keys(nodes).find(k => k.startsWith('__root_') && nodes[k].__isRoot);
    let rootSeed = nodes[level.root.id];
    if (!rootSeed && prevRootId && nodes[prevRootId]) {
      // First time at this level — but the new root's id (e.g. 'coding') may already exist as a child node
      rootSeed = null;
    }
    if (nodes[level.root.id]) {
      // Was a child in the previous level — promote to root in place
      nodes[level.root.id].__isRoot = true;
      nodes[level.root.id].r = 80;
    } else {
      // No previous instance — seed at center
      nodes[level.root.id] = { x: cx, y: cy, vx: 0, vy: 0, r: 80, __isRoot: true };
    }
    // Demote prior root if different
    Object.keys(nodes).forEach(k => {
      if (k !== level.root.id && nodes[k].__isRoot) nodes[k].__isRoot = false;
    });
    newIds.add(level.root.id);

    // Children
    const root = nodes[level.root.id];
    level.children.forEach((c, i) => {
      newIds.add(c.id);
      if (!nodes[c.id]) {
        // Seed near root with outward direction
        const ang = (i / Math.max(1, level.children.length)) * Math.PI * 2 - Math.PI / 2;
        const burst = 20 + Math.random() * 30;
        nodes[c.id] = {
          x: root.x + Math.cos(ang) * burst,
          y: root.y + Math.sin(ang) * burst,
          vx: Math.cos(ang) * 200,
          vy: Math.sin(ang) * 200,
          r: c.kind === 'tool' ? 62 : 66,
        };
      } else {
        // Update radius for current level
        nodes[c.id].r = c.kind === 'tool' ? 62 : 66;
      }
    });

    // (Bottom BACK node removed — root node serves as back when path > 0)

    activeIdsRef.current = newIds;

    // Don't delete dropped nodes immediately — let them float out (handled in step)
    force(n => n + 1);
  }, [level, size]);

  // Animation loop
  msUseEffect(() => {
    let raf;
    const step = () => {
      const cx = size.w / 2, cy = size.h / 2;
      const minDim = Math.min(size.w, size.h);
      const ringR = minDim * 0.30;
      const nodes = nodesRef.current;
      const allIds = Object.keys(nodes);
      const active = activeIdsRef.current;
      const tStep = 1 / 60;
      const damping = 0.88;

      // Collect active children for equal-spacing
      const childIds = [...active].filter(id => id !== '__back' && !nodes[id].__isRoot);
      const childCount = childIds.length;

      allIds.forEach((id) => {
        const n = nodes[id];
        if (!n) return;
        let fx = 0, fy = 0;
        const isActive = active.has(id);

        if (!isActive) {
          // Fade out: pull off-screen and decay
          n.opacity = (n.opacity == null ? 1 : n.opacity) * 0.85;
          if (n.opacity < 0.02) { delete nodes[id]; return; }
          // Drift outward gently
          const dx = n.x - cx, dy = n.y - cy;
          const d = Math.max(0.01, Math.hypot(dx, dy));
          fx += (dx / d) * 200;
          fy += (dy / d) * 200;
        } else {
          n.opacity = Math.min(1, (n.opacity == null ? 0 : n.opacity) + 0.08);
        }

        if (id === level.root.id) {
          // Root pulled to center
          fx += (cx - n.x) * 8;
          fy += (cy - n.y) * 8;
        } else if (id === '__back') {
          const tx = cx;
          const ty = cy + ringR + 80;
          fx += (tx - n.x) * 5;
          fy += (ty - n.y) * 5;
        } else if (isActive) {
          const root = nodes[level.root.id];
          if (!root) return;
          const dx = n.x - root.x, dy = n.y - root.y;
          const dist = Math.max(0.01, Math.hypot(dx, dy));
          // Spring to ring radius
          const radial = (dist - ringR) * 4;
          fx -= (dx / dist) * radial;
          fy -= (dy / dist) * radial;

          // Equal angular spacing (only when 3+ children)
          if (childCount > 2) {
            const idx = childIds.indexOf(id);
            if (idx !== -1) {
              // For each other child, compute angular force pushing toward equal spacing
              const myAng = Math.atan2(dy, dx);
              let angTorque = 0;
              childIds.forEach((oid) => {
                if (oid === id) return;
                const o = nodes[oid];
                if (!o) return;
                const odx = o.x - root.x, ody = o.y - root.y;
                const oAng = Math.atan2(ody, odx);
                let diff = myAng - oAng;
                while (diff > Math.PI) diff -= Math.PI * 2;
                while (diff < -Math.PI) diff += Math.PI * 2;
                const idealGap = (Math.PI * 2) / childCount;
                const sign = diff >= 0 ? 1 : -1;
                const absDiff = Math.abs(diff);
                // If too close, push apart; if too far... ignore (other neighbor handles it)
                if (absDiff < idealGap) {
                  angTorque += sign * (idealGap - absDiff) * 1.5;
                }
              });
              // Convert angular torque to tangential force
              const tx = -dy / dist, ty = dx / dist;
              fx += tx * angTorque * 60;
              fy += ty * angTorque * 60;
            }
          }

          // Soft breathing
          const t = performance.now() * 0.0005;
          fx += Math.cos(t + (n.x + n.y) * 0.005) * 6;
          fy += Math.sin(t + (n.x - n.y) * 0.005) * 6;

          // Repulsion among siblings (prevents overlap)
          allIds.forEach((jd) => {
            if (jd === id) return;
            const m = nodes[jd];
            if (!m || !active.has(jd)) return;
            const ddx = n.x - m.x, ddy = n.y - m.y;
            const d2 = ddx * ddx + ddy * ddy;
            const minD = (n.r + m.r) + 30;
            if (d2 < minD * minD) {
              const d = Math.max(0.01, Math.sqrt(d2));
              const f = (minD - d) * 22;
              fx += (ddx / d) * f;
              fy += (ddy / d) * f;
            }
          });
        }

        // Walls — keep entire node (incl. label aura) inside
        if (isActive) {
          const margin = n.r + 36;
          if (n.x < margin) fx += (margin - n.x) * 18;
          if (n.x > size.w - margin) fx += (size.w - margin - n.x) * 18;
          if (n.y < margin) fy += (margin - n.y) * 18;
          if (n.y > size.h - margin) fy += (size.h - margin - n.y) * 18;
        }

        n.vx = (n.vx + fx * tStep) * damping;
        n.vy = (n.vy + fy * tStep) * damping;
        // Clamp velocity
        const vmax = 600;
        const vm = Math.hypot(n.vx, n.vy);
        if (vm > vmax) { n.vx *= vmax / vm; n.vy *= vmax / vm; }
        n.x += n.vx * tStep;
        n.y += n.vy * tStep;
      });

      force(n => (n + 1) % 1000000);
      raf = requestAnimationFrame(step);
    };
    raf = requestAnimationFrame(step);
    return () => cancelAnimationFrame(raf);
  }, [size, level]);

  const dive = (child) => {
    if (child.kind === 'cat') setPath([child.id]);
    else if (child.kind === 'sub') setPath([path[0], child.id]);
    else if (child.kind === 'tool' && child.meta) {
      const slug = child.meta.slug || child.meta.id;
      window.parent.location.href = `/tool/${encodeURIComponent(slug)}`;
    }
  };
  const goBack = () => {
    if (path.length === 0) return;
    setPath(path.slice(0, -1));
  };

  const root = nodesRef.current[level.root.id];

  const breadcrumb = ['CATALOG', ...path.map((p, idx) => {
    if (idx === 0) return mapCategories.find(c => c.id === p)?.label || p;
    const cat = mapCategories.find(c => c.id === path[0]);
    return cat?.sub.find(s => s.id === p)?.label || p;
  })].join(' / ');

  return (
    <div className="mindmap page-enter">
      <NeonGrid hue="#00f0ff" intensity={0.7} />
      <div className="mindmap__head">
        <button className="btn btn--ghost" onClick={onBack}>⟵ HOME</button>
        <div className="mindmap__head-center">
          <Chip color="#a78bff">CATALOG MAP</Chip>
          <h1 className="mindmap__title"><GlitchText>The Map</GlitchText></h1>
          <p className="mindmap__sub">{breadcrumb}</p>
        </div>
        <Chip color="#c6ff3d" size="sm">LEVEL {level.level}</Chip>
      </div>

      <div className="mindmap__stage" ref={containerRef}>
        <svg className="mindmap__svg" width={size.w} height={size.h}>
          <defs>
            <radialGradient id="core-glow" cx="50%" cy="50%" r="50%">
              <stop offset="0%" stopColor={level.root.color} stopOpacity="0.4" />
              <stop offset="100%" stopColor={level.root.color} stopOpacity="0" />
            </radialGradient>
          </defs>
          {root && <circle cx={root.x} cy={root.y} r={140} fill="url(#core-glow)" opacity={root.opacity ?? 1} />}
          {root && level.children.map(c => {
            const n = nodesRef.current[c.id];
            if (!n) return null;
            const isHover = hover === c.id;
            const op = Math.min(root.opacity ?? 1, n.opacity ?? 1);
            return (
              <line
                key={`l-${c.id}`}
                x1={root.x} y1={root.y} x2={n.x} y2={n.y}
                stroke={c.color}
                strokeWidth={isHover ? 2 : 1}
                strokeOpacity={(isHover ? 1 : 0.5) * op}
                strokeDasharray={isHover ? '0' : '3 6'}
                style={{ filter: isHover ? `drop-shadow(0 0 6px ${c.color})` : 'none' }}
              />
            );
          })}
        </svg>

        {/* Render every node currently in nodesRef (active + fading) */}
        {Object.keys(nodesRef.current).map(id => {
          const n = nodesRef.current[id];
          if (!n) return null;
          const op = n.opacity ?? 1;

          // Find descriptor
          let desc = null;
          let isRoot = n.__isRoot;
          if (isRoot) {
            desc = { ...level.root, kind: 'root' };
          } else {
            desc = level.children.find(c => c.id === id);
            if (!desc) {
              // Fading out — we still need to render. Look up in CATEGORIES / TOOLS for label.
              const cat = mapCategories.find(c => c.id === id);
              if (cat) desc = { id, label: cat.label, color: cat.color, glyph: cat.glyph, kind: 'cat', meta: cat };
              else {
                // sub?
                for (const c of mapCategories) {
                  const s = c.sub.find(s => s.id === id);
                  if (s) { desc = { id, label: s.label, color: s.color, glyph: '◇', kind: 'sub', meta: s }; break; }
                }
              }
              if (!desc) {
                const tool = window.TOOLS.find(t => t.id === id);
                if (tool) desc = { id, label: tool.name, color: tool.color, glyph: tool.emoji, kind: 'tool', meta: tool };
              }
              if (!desc) return null;
            }
          }

          const isHover = hover === id;
          const handleClick = () => {
            if (isRoot) {
              if (path.length > 0) goBack();
            } else {
              dive(desc);
            }
          };

          let countLine = null;
          if (desc.kind === 'cat' && desc.meta) {
            countLine = `${desc.meta.sub.reduce((a, s) => a + s.toolIds.length, 0)} TOOLS`;
          } else if (desc.kind === 'sub' && desc.meta) {
            countLine = `${desc.meta.toolIds.length} TOOLS`;
          } else if (desc.kind === 'tool' && desc.meta) {
            countLine = desc.meta.price;
          } else if (desc.kind === 'root') {
            countLine = path.length > 0 ? '⟵ CLICK TO GO BACK' : 'CATALOG ROOT';
          }

          return (
            <div
              key={id}
              className={`mm-node mm-node--${desc.kind} ${isHover ? 'is-hover' : ''}`}
              style={{
                left: n.x, top: n.y,
                '--node-color': desc.color,
                width: n.r * 2, height: n.r * 2,
                marginLeft: -n.r, marginTop: -n.r,
                opacity: op,
                pointerEvents: op < 0.4 ? 'none' : 'auto',
              }}
              onClick={handleClick}
              onMouseEnter={() => setHover(id)}
              onMouseLeave={() => setHover(null)}
            >
              <div className="mm-node__ring"></div>
              {(desc.kind === 'cat' || desc.kind === 'root') && <div className="mm-node__ring mm-node__ring--2"></div>}
              {/* Curved label along the top arc */}
              <svg className="mm-node__arc" viewBox={`0 0 ${n.r * 2} ${n.r * 2}`} width={n.r * 2} height={n.r * 2}>
                <defs>
                  <path
                    id={`arc-${id}`}
                    d={`M ${n.r - (n.r - 30)},${n.r + 4} A ${n.r - 30},${n.r - 30} 0 0 1 ${n.r + (n.r - 30)},${n.r + 4}`}
                    fill="none"
                  />
                </defs>
                <text className={`mm-node__arc-text mm-node__arc-text--${desc.kind}`} fill={desc.kind === 'tool' ? '#f3f4ff' : desc.color}>
                  <textPath href={`#arc-${id}`} startOffset="50%" textAnchor="middle">{desc.label}</textPath>
                </text>
              </svg>
              <div className="mm-node__glyph">{desc.glyph}</div>
              {countLine && <div className="mm-node__count">{countLine}</div>}
            </div>
          );
        })}

        {hover && (() => {
          const c = level.children.find(x => x.id === hover);
          if (!c || c.kind !== 'tool') return null;
          const t = c.meta;
          const d = window.TOOL_DETAILS[t.id] || {};
          return (
            <div className="map-preview" style={{ borderColor: t.color, boxShadow: `0 0 24px ${t.color}55` }}>
              <div className="map-preview__head" style={{ color: t.color }}>{t.name}</div>
              <div className="map-preview__tag">{t.tag} · {t.price}</div>
              {d.bestFor && <div className="map-preview__line"><span>BEST FOR</span> {d.bestFor}</div>}
              <div className="map-preview__hint">CLICK TO OPEN ⟶</div>
            </div>
          );
        })()}

        <div className="mindmap__legend">
          <div className="mindmap__legend-head">LEGEND</div>
          <div className="mindmap__legend-row"><span className="dot" style={{background:'var(--cyan)'}}></span> CATEGORY</div>
          <div className="mindmap__legend-row"><span className="dot" style={{background:'var(--violet)'}}></span> SUB-CATEGORY</div>
          <div className="mindmap__legend-row"><span className="dot" style={{background:'var(--lime)'}}></span> TOOL · CLICK TO OPEN</div>
          <div className="mindmap__legend-row"><span className="dot" style={{background:'var(--magenta)'}}></span> BACK</div>
        </div>

        <div className="mindmap__stats">
          <div><b>{level.children.length}</b> nodes in view</div>
          <div><b>{path.length}</b> levels deep</div>
          <div><b>{window.TOOLS.length}</b> tools total</div>
          <div><b>FLOATING</b> physics on</div>
        </div>
      </div>
    </div>
  );
}

window.MindMap = MindMap;
