// LearningMap.jsx — hero: constellation mind-map of domains → topics

function MapScreen({ openTopic, openDomain, onCapture, go, openMaterial, profile }) {
  const stageRef = React.useRef(null);
  const [pos, setPos] = React.useState(() => {
    // Balanced radial layout: domains evenly around the centre, each domain's
    // topics fanned outward from it. resolve() then keeps them from overlapping
    // and re-balances dynamically as labels are dragged.
    const o = { __center: [50, 50] };
    const nD = DOMAINS.length || 1;
    DOMAINS.forEach((d, di) => {
      const a = di / nD * Math.PI * 2 - Math.PI / 2;
      const dx = 50 + Math.cos(a) * 30,dy = 50 + Math.sin(a) * 28;
      o[d.id] = [dx, dy];
      const ts = topicsOf(d.id);
      const n = ts.length || 1;
      ts.forEach((t, ti) => {
        const a2 = a + (ti - (n - 1) / 2) * 0.46;
        const r = 15 + ti % 2 * 5;
        o[t.id] = [Math.max(6, Math.min(94, dx + Math.cos(a2) * r)), Math.max(7, Math.min(93, dy + Math.sin(a2) * r * 0.92))];
      });
    });
    return o;
  });
  // Push overlapping nodes apart (measured from the live DOM) so labels never touch.
  // pinnedId (the node being dragged) and the centre stay put; everything else gives way.
  const resolve = (np, pinned) => {
    const stage = stageRef.current;
    if (!stage) return np;
    const rect = stage.getBoundingClientRect();
    const W = rect.width,H = rect.height;
    if (!W || !H) return np;
    const sizes = {};
    stage.querySelectorAll('[data-nid]').forEach((n) => {
      const r = n.getBoundingClientRect();
      sizes[n.dataset.nid] = { hw: r.width / 2 + 7, hh: r.height / 2 + 7 };
    });
    const ids = Object.keys(np).filter((k) => sizes[k]);
    if (!ids.length) return np;
    const P = {};ids.forEach((k) => P[k] = [np[k][0] / 100 * W, np[k][1] / 100 * H]);
    const fixed = new Set(['__center']);if (pinned) (Array.isArray(pinned) ? pinned : [pinned]).forEach((p) => fixed.add(p));
    for (let it = 0; it < 36; it++) {
      for (let a = 0; a < ids.length; a++) for (let b = a + 1; b < ids.length; b++) {
        const i = ids[a],j = ids[b];
        const dx = P[j][0] - P[i][0],dy = P[j][1] - P[i][1];
        const ox = sizes[i].hw + sizes[j].hw - Math.abs(dx);
        const oy = sizes[i].hh + sizes[j].hh - Math.abs(dy);
        if (ox > 0 && oy > 0) {
          let px = 0,py = 0;
          if (ox < oy) px = (dx < 0 ? -1 : 1) * ox;else py = (dy < 0 ? -1 : 1) * oy;
          const fi = fixed.has(i),fj = fixed.has(j);
          if (fi && fj) continue;
          if (fi) {P[j][0] += px;P[j][1] += py;} else
          if (fj) {P[i][0] -= px;P[i][1] -= py;} else
          {P[i][0] -= px / 2;P[i][1] -= py / 2;P[j][0] += px / 2;P[j][1] += py / 2;}
        }
      }
    }
    const out = { ...np };
    ids.forEach((k) => {
      const hwp = sizes[k].hw / W * 100,hhp = sizes[k].hh / H * 100;
      out[k] = [Math.max(hwp, Math.min(100 - hwp, P[k][0] / W * 100)), Math.max(hhp, Math.min(100 - hhp, P[k][1] / H * 100))];
    });
    out.__center = np.__center;
    return out;
  };
  // On mount (and after fonts settle) spread overlapping nodes apart and pull any
  // label that pokes past the border back inside — so nothing overlaps or overflows.
  React.useEffect(() => {
    let n = 0,id;
    const tick = () => {setPos((p) => resolve(p, null));if (++n < 9) id = setTimeout(tick, 30);};
    id = setTimeout(tick, 40);
    return () => clearTimeout(id);
  }, []);
  // drag-or-click: moves a node; if it didn't move, treat as a click
  const startDrag = (id, fn) => (e) => {
    e.preventDefault();e.stopPropagation();
    const rect = stageRef.current.getBoundingClientRect();
    const sx = e.clientX,sy = e.clientY;
    const isDomain = DOMAINS.some((d) => d.id === id);
    const groupIds = isDomain ? [id, ...topicsOf(id).map((t) => t.id)] : [id];
    const starts = {};groupIds.forEach((k) => starts[k] = pos[k]);
    let moved = false;
    const cx = (v) => Math.max(4, Math.min(96, v));
    const cy = (v) => Math.max(5, Math.min(95, v));
    const move = (ev) => {
      if (Math.abs(ev.clientX - sx) > 3 || Math.abs(ev.clientY - sy) > 3) moved = true;
      const dx = (ev.clientX - sx) / rect.width * 100;
      const dy = (ev.clientY - sy) / rect.height * 100;
      setPos((p) => {
        const np = { ...p };
        groupIds.forEach((k) => {np[k] = [cx(starts[k][0] + dx), cy(starts[k][1] + dy)];});
        return resolve(np, groupIds);
      });
    };
    const up = () => {document.removeEventListener('pointermove', move);document.removeEventListener('pointerup', up);if (!moved) fn(id);};
    document.addEventListener('pointermove', move);document.addEventListener('pointerup', up);
  };
  const [openLeaf, setOpenLeaf] = React.useState(null);
  const [highlight, setHighlight] = React.useState(null);
  const dim = (cat) => highlight && highlight !== cat ? 0.3 : 1;
  const [connectOpen, setConnectOpen] = React.useState(false);
  const [connected, setConnected] = React.useState(['notion']);
  // build connector lines from live positions
  const lines = [];
  const ctr = pos.__center;
  DOMAINS.forEach((d) => {
    const dp = pos[d.id];
    lines.push({ x1: ctr[0], y1: ctr[1], x2: dp[0], y2: dp[1], strong: true });
    topicsOf(d.id).forEach((t) => {const tp = pos[t.id];lines.push({ x1: dp[0], y1: dp[1], x2: tp[0], y2: tp[1], strong: false, tid: t.id });});
  });
  const totalTopics = TOPICS.length;

  // --- Time layers: when did the user add each topic? ----------------------
  // Topics carry no date in the data, so derive a stable "days ago" from the id
  // (FNV-1a hash). Each topic then falls into one learning layer: this month,
  // last month, or earlier — letting the user peel the map back through time.
  const hashStr = (s) => { let h = 2166136261; for (let i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = Math.imul(h, 16777619); } return h >>> 0; };
  const topicDays = React.useCallback((id) => hashStr(id) % 118, []); // deterministic "days since added"
  const layerOf = React.useCallback((id) => { const d = topicDays(id); return d <= 30 ? 'this' : d <= 60 ? 'last' : 'earlier'; }, [topicDays]);
  const LAYERS = [
    { id: 'all', label: 'All time' },
    { id: 'this', label: 'This month' },
    { id: 'last', label: 'Last month' },
    { id: 'earlier', label: 'Earlier' }];

  const [layer, setLayer] = React.useState('all');
  const layerCount = React.useCallback((lid) => lid === 'all' ? TOPICS.length : TOPICS.filter((t) => layerOf(t.id) === lid).length, [layerOf]);
  const inLayer = (id) => layer === 'all' || layerOf(id) === layer;
  const newCount = layerCount('this');
  // Newest topics this month, soonest-added first — shown as a quick recap strip.
  const newTopics = React.useMemo(() => TOPICS.filter((t) => layerOf(t.id) === 'this').sort((a, b) => topicDays(a.id) - topicDays(b.id)), [layerOf, topicDays]);

  // Resize the map from any corner (CSS resize only offers bottom-right).
  const [mapSize, setMapSize] = React.useState(null);
  // Re-fit whenever the map is resized so labels never overlap or overflow the new border.
  React.useEffect(() => {
    if (!mapSize) return;
    let n = 0,id;
    const tick = () => {setPos((p) => resolve(p, null));if (++n < 5) id = setTimeout(tick, 25);};
    id = setTimeout(tick, 20);
    return () => clearTimeout(id);
  }, [mapSize]);
  const startResize = (corner) => (e) => {
    e.preventDefault();e.stopPropagation();
    const rect = stageRef.current.getBoundingClientRect();
    const sx = e.clientX,sy = e.clientY,sw = rect.width,sh = rect.height;
    const maxW = stageRef.current.parentElement && stageRef.current.parentElement.clientWidth || 1400;
    const move = (ev) => {
      const dx = ev.clientX - sx,dy = ev.clientY - sy;
      let w = sw,h = sh;
      if (corner.includes('e')) w = sw + dx;
      if (corner.includes('w')) w = sw - dx;
      if (corner.includes('s')) h = sh + dy;
      if (corner.includes('n')) h = sh - dy;
      setMapSize({ w: Math.max(440, Math.min(maxW, w)), h: Math.max(360, Math.min(1100, h)) });
    };
    const up = () => {document.removeEventListener('pointermove', move);document.removeEventListener('pointerup', up);};
    document.addEventListener('pointermove', move);document.addEventListener('pointerup', up);
  };

  return (
    <div className="content-pad wide fade-up">
      <div className="page-head">
        <div>
          <p className="eyebrow">Overview</p>
          <h1>{(() => { const first = ((profile && profile.name) || '').trim().split(/\s+/)[0]; return first ? first + (/(s|z)$/i.test(first) ? '’' : '’s') + ' learning map' : 'Your learning map'; })()}</h1>
          <p className="sub">{DOMAINS.length} domains · {totalTopics} topics · <span style={{ color: 'var(--positive)', fontWeight: 600 }}>{newCount} new this month</span></p>
        </div>
        <div className="actions" style={{ display: 'flex', gap: 10 }}>
          <button className="btn" onClick={() => setConnectOpen(true)}><Icon name="link" size={16} /> Connect notes</button>
          <button className="btn primary" onClick={onCapture}><Icon name="plus" size={16} /> New Idea</button>
        </div>
      </div>

      {/* Time layers — peel the map back through the months you've been learning */}
      <div className="map-layers">
        <div className="segmented" role="tablist" aria-label="Learning timeline layers">
          {LAYERS.map((L) =>
          <button key={L.id} role="tab" aria-selected={layer === L.id} className={layer === L.id ? 'on' : ''} onClick={() => setLayer(L.id)}>
              {L.id === 'this' && <span className="layer-dot" />}{L.label}<span className="layer-n">{layerCount(L.id)}</span>
            </button>
          )}
        </div>
        {layer === 'this' && newTopics.length > 0 &&
        <div className="new-strip">
            <span className="new-strip-label"><Icon name="sparkles" size={13} /> New this month</span>
            <div className="new-strip-chips">
              {newTopics.map((t) => { const d = domainById(t.domain); return (
                <button key={t.id} className="new-chip" onClick={() => setOpenLeaf(t.id)}>
                    <span className="accent" style={{ background: d.accent }} /> {t.name}
                  </button>); })}
            </div>
          </div>
        }
      </div>

      <div className="map-legend in-stage">
        {[['center', 'Map', 'sw circle'], ['domain', 'Learning domain · progress', 'sw'], ['topic', 'Topic — tap for materials', 'sw leaf']].map(([cat, label, sw]) =>
        <button key={cat} className={'li' + (highlight === cat ? ' on' : '')} onClick={() => setHighlight((h) => h === cat ? null : cat)}>
            <span className={sw} /> {label}
          </button>
        )}
      </div>
      {/* desktop constellation */}
      <div className="map-stage" ref={stageRef} style={{ width: mapSize ? mapSize.w : '100%', height: mapSize ? mapSize.h : 940, resize: 'none' }}>
        <svg className="map-lines" viewBox="0 0 100 100" preserveAspectRatio="none" style={{ height: '100%' }}>
          {lines.map((l, i) => {
          const faded = l.tid && layer !== 'all' && layerOf(l.tid) !== layer;
          return (
          <line key={i} x1={l.x1} y1={l.y1} x2={l.x2} y2={l.y2}
          stroke={l.strong ? 'var(--ink-4)' : 'var(--ink-4)'} strokeWidth={l.strong ? 1.4 : 1}
          strokeDasharray={l.strong ? 'none' : '2.5 2.5'} vectorEffect="non-scaling-stroke" opacity={faded ? 0.08 : l.strong ? 0.7 : 0.6} style={{ transition: 'opacity .25s' }} />);
          })}
        </svg>

        {/* center */}
        <div className="map-node mn-center" data-nid="__center" style={{ left: pos.__center[0] + '%', top: pos.__center[1] + '%', cursor: 'grab', touchAction: 'none', opacity: dim('center'), transition: 'opacity .2s', background: '#ffffff', color: '#33363E', border: '3px solid var(--line-strong)' }} onPointerDown={startDrag('__center', () => {})}>
          <Icon name="brain" size={26} />
          <span className="nm" style={{ color: '#33363E' }}>My learning</span>
          <span className="mt" style={{ color: '#6B7280' }}>{DOMAINS.length} domains</span>
        </div>

        {/* domains */}
        {DOMAINS.map((d) => {
          const ts = topicsOf(d.id);
          const prog = Math.round(ts.reduce((s, t) => s + t.progress, 0) / ts.length);
          return (
            <div key={d.id} className="map-node mn-domain" data-nid={d.id} style={{ left: pos[d.id][0] + '%', top: pos[d.id][1] + '%', cursor: 'grab', touchAction: 'none', opacity: dim('domain'), transition: 'opacity .2s' }}
            onPointerDown={startDrag(d.id, openDomain)} role="button">
              <div className="nm"><span className="accent" style={{ background: d.accent }} /> {d.name}</div>
              <div className="progress"><i style={{ width: prog + '%', background: d.accent }} /></div>
              <div className="mt">{ts.reduce((s, t) => s + t.notes, 0)} notes · {prog}%</div>
            </div>);

        })}

        {/* topic leaves */}
        {TOPICS.map((t) => {
          const d = domainById(t.domain);
          const isNew = layerOf(t.id) === 'this';
          const off = layer !== 'all' && !inLayer(t.id);
          const op = off ? 0.1 : dim('topic');
          return (
            <div key={t.id} className={'map-node mn-leaf' + (isNew ? ' is-new' : '')} data-nid={t.id} style={{ left: pos[t.id][0] + '%', top: pos[t.id][1] + '%', cursor: off ? 'default' : 'grab', touchAction: 'none', opacity: op, pointerEvents: off ? 'none' : 'auto', transition: 'opacity .25s' }}
            onPointerDown={off ? undefined : startDrag(t.id, setOpenLeaf)} role="button">
              <span className="accent" style={{ background: d.accent }} /> {t.name}
            </div>);

        })}

        {/* topic materials popover — clicking a topic shows all its learning materials */}
        {openLeaf && (() => {
          const tp = topicById(openLeaf);if (!tp) return null;
          const dd = domainById(tp.domain);
          const mats = materialsOf(openLeaf);
          const p = pos[openLeaf] || [50, 50];
          const above = p[1] > 56;
          const tx = p[0] > 72 ? '-86%' : p[0] < 28 ? '-14%' : '-50%';
          return (
            <React.Fragment>
              <div onPointerDown={(e) => {e.stopPropagation();setOpenLeaf(null);}}
              style={{ position: 'absolute', inset: 0, zIndex: 8 }} />
              <div className="card" onPointerDown={(e) => e.stopPropagation()}
              style={{ position: 'absolute', zIndex: 9, left: p[0] + '%', top: p[1] + '%',
                transform: `translate(${tx}, ${above ? 'calc(-100% - 16px)' : '16px'})`,
                width: 272, maxWidth: '80%', padding: 14, boxShadow: 'var(--shadow-card)' }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 11 }}>
                  <span style={{ width: 9, height: 9, borderRadius: 99, background: dd.accent, flex: 'none' }} />
                  <span style={{ fontSize: 14.5, fontWeight: 600, letterSpacing: '-0.02em', flex: 1, minWidth: 0,
                    overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{tp.name}</span>
                  <span className="count-pill">{mats.length}</span>
                  <button className="icon-btn" style={{ width: 26, height: 26, border: 'none', flex: 'none' }} onClick={() => setOpenLeaf(null)}><Icon name="x" size={15} /></button>
                </div>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>
                  {mats.length === 0 ?
                  <div style={{ fontSize: 12.5, color: 'var(--ink-3)', padding: '4px 2px' }}>No materials yet.</div> :
                  mats.map((m) =>
                  <button key={m.id} className="card tight hover" onClick={() => {setOpenLeaf(null);openMaterial && openMaterial(m.id);}}
                  style={{ display: 'flex', alignItems: 'center', gap: 10, textAlign: 'left', padding: 8, borderRadius: 'var(--r-sm)' }}>
                        <MatTile type={m.type} size={30} />
                        <div style={{ flex: 1, minWidth: 0 }}>
                          <div style={{ fontSize: 12.5, fontWeight: 600, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{m.title}</div>
                          <div className="mono" style={{ fontSize: 10.5, color: 'var(--ink-3)', marginTop: 1 }}>{MAT_TYPE[m.type].label}{m.cards ? ` · ${m.cards} cards` : ''}</div>
                        </div>
                        <Icon name="chevron-right" size={15} style={{ color: 'var(--ink-4)', flex: 'none' }} />
                      </button>
                  )}
                </div>
                <button className="btn ghost block sm" style={{ marginTop: 11 }} onClick={() => {setOpenLeaf(null);openTopic(openLeaf);}}>
                  Open topic page <Icon name="arrow-right" size={14} />
                </button>
              </div>
            </React.Fragment>);
        })()}

        {/* resize handles — drag any corner to size the map */}
        {['nw', 'ne', 'sw', 'se'].map((c) =>
        <div key={c} onPointerDown={startResize(c)} title="Drag to resize the map"
        style={{ position: 'absolute', width: 18, height: 18, zIndex: 6, display: 'grid', placeItems: 'center',
          [c[0] === 'n' ? 'top' : 'bottom']: -9, [c[1] === 'w' ? 'left' : 'right']: -9,
          cursor: c === 'nw' || c === 'se' ? 'nwse-resize' : 'nesw-resize', touchAction: 'none' }}>
            <span style={{ width: 10, height: 10, background: 'var(--paper)', border: '1.5px solid var(--line-strong)',
            borderRadius: 3, transform: 'rotate(45deg)', boxShadow: 'var(--shadow-sm)' }} />
          </div>
        )}
      </div>

      {/* mobile fallback: domain list */}
      <div className="map-list">
        {DOMAINS.map((d) => {
          const ts = topicsOf(d.id);
          const prog = Math.round(ts.reduce((s, t) => s + t.progress, 0) / ts.length);
          return (
            <div key={d.id} className="card pad" style={{ display: 'flex', flexDirection: 'column', gap: 13 }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                <span style={{ width: 12, height: 12, borderRadius: 99, background: d.accent }} />
                <span style={{ fontSize: 17, fontWeight: 600, letterSpacing: '-0.02em' }}>{d.name}</span>
                <span className="mono" style={{ marginLeft: 'auto', fontSize: 12, color: 'var(--ink-3)' }}>{prog}%</span>
              </div>
              <ProgressBar value={prog} />
              <div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
                {ts.map((t) =>
                <button key={t.id} onClick={() => openTopic(t.id)} style={{ display: 'flex', alignItems: 'center', gap: 10,
                  padding: '11px 4px', border: 'none', background: 'transparent', cursor: 'pointer', textAlign: 'left',
                  borderTop: '1px solid var(--line-2)' }}>
                    <span style={{ width: 7, height: 7, borderRadius: 99, background: d.accent, flex: 'none' }} />
                    <span style={{ fontSize: 14.5, fontWeight: 500, flex: 1 }}>{t.name}</span>
                    <span className="mono" style={{ fontSize: 11.5, color: 'var(--ink-3)' }}>{t.notes} notes</span>
                    <Icon name="chevron-right" size={16} style={{ color: 'var(--ink-4)' }} />
                  </button>
                )}
              </div>
            </div>);

        })}
      </div>

      <div className="map-legend" style={{ display: 'none' }}>
        <span className="li"><span className="sw circle" /> You</span>
        <span className="li"><span className="sw" /> Learning domain · progress</span>
        <span className="li"><span className="sw leaf" /> Topic — tap to open</span>
      </div>

      {connectOpen &&
      <div className="scrim" onMouseDown={(e) => {if (e.target === e.currentTarget) setConnectOpen(false);}}>
          <div className="modal" style={{ maxWidth: 480 }}>
            <div className="mhead">
              <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                <span className="itile" style={{ width: 32, height: 32, borderRadius: 9, background: 'var(--surface-2)', color: 'var(--ink-2)' }}><Icon name="link" size={17} /></span>
                <span style={{ fontWeight: 600, fontSize: 16 }}>Connect a notes app</span>
              </div>
              <button className="icon-btn" style={{ width: 32, height: 32, border: 'none' }} onClick={() => setConnectOpen(false)}><Icon name="x" size={18} /></button>
            </div>
            <div className="mbody" style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
              {[{ id: 'notion', name: 'Notion', mark: 'N' }, { id: 'obsidian', name: 'Obsidian', mark: 'O' }, { id: 'gdocs', name: 'Google Docs', mark: 'G' }, { id: 'apple', name: 'Apple Notes', mark: 'A' }].map((s) => {
              const on = connected.includes(s.id);
              return (
                <div key={s.id} className="conn-row" style={{ padding: '12px 14px' }}>
                    <span className="conn-logo">{s.mark}</span>
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div style={{ fontSize: 14.5, fontWeight: 600 }}>{s.name}</div>
                      <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginTop: 3 }}>
                        {on && <span style={{ width: 7, height: 7, borderRadius: 99, background: 'var(--positive)' }} />}
                        <span className="mono" style={{ fontSize: 11.5, color: 'var(--ink-3)' }}>{on ? 'Connected' : 'Not connected'}</span>
                      </div>
                    </div>
                    {on ?
                  <button className="btn sm" onClick={() => setConnected((c) => c.filter((x) => x !== s.id))}>Remove</button> :
                  <button className="btn sm blue" onClick={() => setConnected((c) => [...c, s.id])}>Connect</button>}
                  </div>);

            })}
            </div>
          </div>
        </div>
      }
    </div>);

}
window.MapScreen = MapScreen;