// Komonichi — dynamic Brain Map with pan + zoom canvas.
const { useKomonichi, actions } = window.KomonichiState;

const TONES = {
  ink:       'var(--ink)',
  persimmon: 'var(--persimmon)',
  moss:      'var(--moss)',
  indigo:    'var(--indigo)',
  gold:      'var(--gold)',
};

const MIN_ZOOM = 0.4, MAX_ZOOM = 2.4;

// Quotes shown when releasing an area of life. Picked at random.
const RELEASE_QUOTES = [
  { text: 'To let go is not to forget, but to let what was once a weight rest where it belongs.', attr: '— after Ryōkan' },
  { text: 'The cherry blossom is loved because it falls.', attr: '— Japanese proverb' },
  { text: 'Subtract until what remains can be carried in two hands.', attr: '— a quiet teacher' },
  { text: 'Mono no aware — the gentle sadness in the passing of things.', attr: '— 物の哀れ' },
  { text: 'When you can no longer go forward, sit. When you can no longer hold, release.', attr: '— after Dōgen' },
];

function BrainMap() {
  const [state] = useKomonichi();
  const [adding, setAdding] = React.useState(false);
  const [showList, setShowList] = React.useState(false);
  const [name, setName] = React.useState('');
  const [tone, setTone] = React.useState('ink');
  const [note, setNote] = React.useState('');
  const [hovered, setHovered] = React.useState(null);
  const [draggingNode, setDraggingNode] = React.useState(null);
  const [pan, setPan] = React.useState({ x: 0, y: 0 });
  const [zoom, setZoom] = React.useState(1);
  const [panning, setPanning] = React.useState(false);
  const surfaceRef = React.useRef(null);
  const panStartRef = React.useRef(null);
  const sizeRef = React.useRef({ w: 0, h: 0 });

  const areas = state.areas;

  // surface size — we treat areas as fractions of an inner 1000x680 "world"
  const WORLD_W = 1000, WORLD_H = 680;

  React.useLayoutEffect(() => {
    const el = surfaceRef.current;
    if (!el) return;
    const r = el.getBoundingClientRect();
    sizeRef.current = { w: r.width, h: r.height };
  });

  // Bound the pan so the world can never drift entirely off-screen. When the
  // world (at the current zoom) is larger than the viewport, the user can pan
  // by at most "world overhang / 2 + a small slack" in each direction. When
  // the world is smaller than the viewport, pan is constrained to a small
  // slack only — there's nothing to pan toward.
  const clampPan = React.useCallback((p, zoomVal) => {
    const { w, h } = sizeRef.current;
    if (!w || !h) return p;
    const slack = 0.18;  // fraction of viewport: how far past the strict bound the user can drift
    const maxX = Math.max(0, (WORLD_W * zoomVal - w) / 2) + w * slack;
    const maxY = Math.max(0, (WORLD_H * zoomVal - h) / 2) + h * slack;
    return {
      x: Math.max(-maxX, Math.min(maxX, p.x)),
      y: Math.max(-maxY, Math.min(maxY, p.y)),
    };
  }, []);

  const submit = (e) => {
    e.preventDefault();
    if (!name.trim()) return;
    const angle = Math.random() * Math.PI * 2;
    const r = 0.18 + Math.random() * 0.22;
    actions.addArea({
      name, tone, note,
      x: 0.5 + Math.cos(angle) * r,
      y: 0.5 + Math.sin(angle) * r * 0.85,
    });
    setName(''); setNote(''); setAdding(false);
  };

  // node drag — convert client coords to world fraction accounting for pan/zoom.
  // Pointer events unify mouse + touch + pen, so the same code works on phones.
  React.useEffect(() => {
    if (!draggingNode) return;
    const move = (e) => {
      const rect = surfaceRef.current?.getBoundingClientRect();
      if (!rect) return;
      const vx = e.clientX - rect.left;
      const vy = e.clientY - rect.top;
      const worldX = (vx - rect.width / 2 - pan.x) / zoom + WORLD_W / 2;
      const worldY = (vy - rect.height / 2 - pan.y) / zoom + WORLD_H / 2;
      const fx = Math.max(0.04, Math.min(0.96, worldX / WORLD_W));
      const fy = Math.max(0.04, Math.min(0.96, worldY / WORLD_H));
      actions.moveArea(draggingNode, fx, fy);
    };
    const up = () => setDraggingNode(null);
    window.addEventListener('pointermove', move);
    window.addEventListener('pointerup', up);
    window.addEventListener('pointercancel', up);
    return () => {
      window.removeEventListener('pointermove', move);
      window.removeEventListener('pointerup', up);
      window.removeEventListener('pointercancel', up);
    };
  }, [draggingNode, pan, zoom]);

  // canvas pan
  const onSurfaceDown = (e) => {
    // Pointer events: button is 0 for primary mouse and for touch contacts.
    // Skip secondary/right-click only.
    if (e.button > 0) return;
    setPanning(true);
    panStartRef.current = { x: e.clientX - pan.x, y: e.clientY - pan.y };
  };
  React.useEffect(() => {
    if (!panning) return;
    const move = (e) => {
      const s = panStartRef.current; if (!s) return;
      setPan(clampPan({ x: e.clientX - s.x, y: e.clientY - s.y }, zoom));
    };
    const up = () => setPanning(false);
    window.addEventListener('pointermove', move);
    window.addEventListener('pointerup', up);
    window.addEventListener('pointercancel', up);
    return () => {
      window.removeEventListener('pointermove', move);
      window.removeEventListener('pointerup', up);
      window.removeEventListener('pointercancel', up);
    };
  }, [panning, zoom, clampPan]);

  // zoom on wheel — anchored to cursor. Attached as a native non-passive listener
  // so we can preventDefault and stop the page from scrolling.
  React.useEffect(() => {
    const el = surfaceRef.current;
    if (!el) return;
    const handler = (e) => {
      e.preventDefault();
      const rect = el.getBoundingClientRect();
      const delta = -e.deltaY * 0.0015;
      setZoom(prevZoom => {
        const next = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, prevZoom * (1 + delta)));
        if (next === prevZoom) return prevZoom;
        const cx = e.clientX - rect.left - rect.width / 2;
        const cy = e.clientY - rect.top - rect.height / 2;
        const k = next / prevZoom;
        setPan(p => clampPan({ x: cx - (cx - p.x) * k, y: cy - (cy - p.y) * k }, next));
        return next;
      });
    };
    el.addEventListener('wheel', handler, { passive: false });
    return () => el.removeEventListener('wheel', handler);
  }, []);

  const resetView = () => { setPan({ x: 0, y: 0 }); setZoom(1); };
  const zoomIn  = () => setZoom(z => {
    const next = Math.min(MAX_ZOOM, z * 1.2);
    setPan(p => clampPan(p, next));
    return next;
  });
  const zoomOut = () => setZoom(z => {
    const next = Math.max(MIN_ZOOM, z / 1.2);
    setPan(p => clampPan(p, next));
    return next;
  });

  // edges
  const edges = React.useMemo(() => {
    const out = [];
    for (let i = 0; i < areas.length; i++) {
      const a = areas[i];
      const dists = areas
        .map((b, j) => ({ b, j, d: Math.hypot(a.x - b.x, a.y - b.y) }))
        .filter(x => x.j !== i)
        .sort((p, q) => p.d - q.d)
        .slice(0, 2);
      for (const { b, j } of dists) {
        const key = i < j ? `${i}-${j}` : `${j}-${i}`;
        if (!out.find(e => e.key === key)) out.push({ key, a, b });
      }
    }
    return out;
  }, [areas]);

  const worldStyle = {
    position: 'absolute',
    left: '50%', top: '50%',
    width: WORLD_W, height: WORLD_H,
    transform: `translate(-50%, -50%) translate(${pan.x}px, ${pan.y}px) scale(${zoom})`,
    transformOrigin: 'center center',
    transition: (panning || draggingNode) ? 'none' : 'transform 0.25s cubic-bezier(.2,.7,.3,1)',
  };

  return (
    <div className="card">
      <div className="cap">
        <div>
          <div className="label">a living circuit</div>
          <h3 className="title"><span className="em">brain</span> map</h3>
        </div>
        <div className="brain-controls" style={{ display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' }}>
          <button onClick={() => setShowList(true)} className="chip chip-button" title="view all areas">
            {areas.length} areas
          </button>
          <span className="chip" style={{ fontFamily: 'var(--mono)' }}>{Math.round(zoom * 100)}%</span>
          <button className="btn ghost tiny" onClick={zoomOut} title="zoom out">−</button>
          <button className="btn ghost tiny" onClick={zoomIn} title="zoom in">+</button>
          <button className="btn ghost tiny" onClick={resetView} title="reset view">◯</button>
          <button className="btn ghost tiny" onClick={() => setAdding(a => !a)}>+ node</button>
        </div>
      </div>

      {adding && (
        <form onSubmit={submit} className="fade-in" style={{ padding: '4px 0 14px', borderBottom: '1px dashed var(--paper-line)', marginBottom: 14, display: 'grid', gap: 10 }}>
          <input className="f" autoFocus placeholder="name of this area of life…" value={name} onChange={e => setName(e.target.value)} />
          <input className="f" placeholder="a brief note (optional)" value={note} onChange={e => setNote(e.target.value)} />
          <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
            <div className="label">tone</div>
            {Object.entries(TONES).map(([k, v]) => (
              <button type="button" key={k} onClick={() => setTone(k)}
                style={{ width: 18, height: 18, borderRadius: '50%', background: v, border: tone === k ? '2px solid var(--ink)' : '1px solid var(--paper-line)', padding: 0, cursor: 'pointer' }} />
            ))}
            <div style={{ flex: 1 }}></div>
            <button type="button" className="btn ghost tiny" onClick={() => setAdding(false)}>cancel</button>
            <button type="submit" className="btn warm tiny">place</button>
          </div>
        </form>
      )}

      <div
        ref={surfaceRef}
        onPointerDown={onSurfaceDown}
        style={{
          position: 'relative', width: '100%', height: 520,
          background: 'radial-gradient(circle at 50% 50%, rgba(44,38,32,0.035), transparent 70%)',
          borderRadius: 2, overflow: 'hidden',
          cursor: panning ? 'grabbing' : (draggingNode ? 'grabbing' : 'grab'),
          // stop the page from scrolling when the user drags on the canvas
          touchAction: 'none',
        }}>
        {/* world */}
        <div style={worldStyle}>
          {/* edges */}
          <svg viewBox={`0 0 ${WORLD_W} ${WORLD_H}`} preserveAspectRatio="none"
            style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', pointerEvents: 'none' }}>
            {edges.map(({ key, a, b }) => (
              <line key={key}
                x1={a.x * WORLD_W} y1={a.y * WORLD_H}
                x2={b.x * WORLD_W} y2={b.y * WORLD_H}
                stroke="var(--paper-line)" strokeWidth="1.2" strokeDasharray="3 5" opacity="0.75" />
            ))}
            {edges.slice(0, 3).map(({ key, a, b }, i) => (
              <circle key={'p' + key} r="3" fill="var(--persimmon)" opacity="0.5">
                <animateMotion dur={`${6 + i * 1.4}s`} repeatCount="indefinite"
                  path={`M ${a.x * WORLD_W} ${a.y * WORLD_H} L ${b.x * WORLD_W} ${b.y * WORLD_H}`} />
              </circle>
            ))}
          </svg>

          {/* central enso */}
          <div className="breathe" style={{
            position: 'absolute', left: '50%', top: '50%',
            transform: 'translate(-50%, -50%)',
            width: 120, height: 120, borderRadius: '50%',
            border: '1px dashed var(--paper-line)', opacity: 0.45,
          }}></div>

          {/* nodes */}
          {areas.map(a => {
            const color = TONES[a.tone] || 'var(--ink)';
            const isHovered = hovered === a.id;
            return (
              <div
                key={a.id}
                onMouseEnter={() => setHovered(a.id)}
                onMouseLeave={() => setHovered(null)}
                onPointerDown={(e) => { e.stopPropagation(); e.preventDefault(); setDraggingNode(a.id); }}
                style={{
                  position: 'absolute',
                  left: a.x * WORLD_W, top: a.y * WORLD_H,
                  transform: 'translate(-50%, -50%)',
                  cursor: draggingNode === a.id ? 'grabbing' : 'grab',
                  userSelect: 'none',
                }}>
                <div style={{
                  width: isHovered ? 84 : 78, height: isHovered ? 84 : 78,
                  borderRadius: '50%',
                  border: `1px solid ${color}`,
                  background: 'rgba(255,254,250,0.85)',
                  display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
                  boxShadow: 'inset 3px -4px 0 -2px var(--paper), inset -6px 5px 0 -3px var(--paper-deep), 0 1px 3px rgba(44,38,32,0.05)',
                  transition: 'all 0.4s ease',
                }}>
                  <div className="display" style={{ fontSize: 14, color: color, lineHeight: 1, textAlign: 'center', padding: '0 6px' }}>{a.name}</div>
                </div>
                {isHovered && (
                  <div style={{
                    position: 'absolute', top: '105%', left: '50%', transform: 'translateX(-50%)',
                    background: 'var(--ink)', color: 'var(--paper)', padding: '6px 10px',
                    whiteSpace: 'nowrap', fontSize: 11, fontFamily: 'var(--mono)', letterSpacing: '0.06em',
                    pointerEvents: 'auto', marginTop: 6, display: 'flex', gap: 8, alignItems: 'center',
                  }}>
                    <span style={{ opacity: 0.7 }}>{a.note || '—'}</span>
                    <a href={'#tasks?area=' + a.id}
                       onClick={(e) => e.stopPropagation()}
                       style={{ color: 'var(--paper)', textDecoration: 'underline', fontSize: 10, opacity: 0.8 }}>tasks</a>
                    <button onClick={async (e) => {
                      e.stopPropagation();
                      const ok = await window.KomonichiConfirm.ask({
                        eyebrow: 'release a chapter',
                        title: `remove “${a.name}”?`,
                        body: 'Are you really sure that you want to remove this chapter from your life? Tasks linked to it will become orphaned but kept.',
                        confirmLabel: 'remove it',
                        cancelLabel: 'keep it',
                        tone: 'warm',
                      });
                      if (ok) actions.removeArea(a.id);
                    }} style={{ background: 'none', border: '1px solid var(--paper)', color: 'var(--paper)', padding: '1px 5px', fontSize: 10, cursor: 'pointer' }}>×</button>
                  </div>
                )}
              </div>
            );
          })}
        </div>
      </div>

      <div className="label" style={{ marginTop: 12, fontStyle: 'italic', opacity: 0.7 }}>
        drag canvas to pan · scroll to zoom · drag a node to arrange · hover for note
      </div>

      {showList && (
        <AreasListModal
          areas={areas}
          onClose={() => setShowList(false)}
        />
      )}
    </div>
  );
}

function AreasListModal({ areas, onClose }) {
  async function release(a) {
    const q = RELEASE_QUOTES[Math.floor(Math.random() * RELEASE_QUOTES.length)];
    const ok = await window.KomonichiConfirm.ask({
      eyebrow: 'release a chapter',
      title: `let go of “${a.name}”?`,
      body: `${q.text}\n\n${q.attr}\n\nThis area, and the responsibility it represented, will be released. Tasks linked to it remain — they become orphaned (visible under the "orphaned" filter on the Tasks page) so the record of the work isn't lost.`,
      confirmLabel: 'release it',
      cancelLabel: 'not yet',
      tone: 'warm',
    });
    if (ok) await actions.removeArea(a.id);
    // modal stays open — user explicitly closes via × or backdrop click
  }
  // close on Esc as well
  React.useEffect(() => {
    function onKey(e) { if (e.key === 'Escape') onClose(); }
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);

  // Portal to <body> — escapes any ancestor transform / animation that would
  // otherwise constrain a position:fixed element to the shell's bounds.
  return ReactDOM.createPortal(
    <div className="areas-modal-overlay" onClick={onClose}>
      <div className="areas-modal-card card" onClick={(e) => e.stopPropagation()}>
        <button className="areas-modal-close" onClick={onClose} aria-label="close">×</button>
        <div className="areas-modal-header">
          <div className="label">the chapters you tend</div>
          <h2 className="display" style={{ fontSize: 44, lineHeight: 1.05, margin: '8px 0 0' }}>
            <span style={{ color: 'var(--persimmon)', fontStyle: 'italic' }}>areas</span> of life
          </h2>
          <div className="haiku" style={{ marginTop: 14, fontSize: 14, maxWidth: 560 }}>
            Six is the limit, kept by intention. To take on a new chapter, an old one must first be released. Linked tasks remain — they become orphaned, not deleted, so nothing of the work is lost.
          </div>
        </div>
        <div className="areas-modal-body">
          {areas.length === 0 ? (
            <div className="haiku" style={{ fontSize: 14, padding: '24px 0' }}>
              No chapters yet. Place a node on the map.
            </div>
          ) : (
            areas.map((a, i) => (
              <div key={a.id} className="areas-modal-row">
                <span className="areas-modal-dot" style={{ background: TONES[a.tone] || 'var(--ink)' }} />
                <div>
                  <div className="display" style={{ fontSize: 20, color: 'var(--ink)' }}>{a.name}</div>
                  {a.note ? <div className="label" style={{ marginTop: 4, opacity: 0.75 }}>{a.note}</div> : null}
                </div>
                <button className="btn ghost" onClick={() => release(a)}>release</button>
              </div>
            ))
          )}
        </div>
        <div className="areas-modal-footer label">
          {areas.length}/6 chapters · click outside or press esc to close
        </div>
      </div>
    </div>,
    document.body
  );
}

window.BrainMap = BrainMap;
