// comments.jsx — shared commenting layer for the Method 8 IA wireframes canvas.
// Freeform world-space pins + threads, persisted to Supabase with realtime.
// CSS scoped under .cm-. Loaded after design-canvas.jsx, before wireframes/app.jsx.
// Exposes window.CommentsRoot and window.CommentPins.

(function () {
  const { useState, useEffect, useRef, useCallback, useContext, createContext } = React;

  // ---- one-time CSS ----
  if (!document.getElementById('cm-styles')) {
    const s = document.createElement('style');
    s.id = 'cm-styles';
    s.textContent = [
      ".cm-toolbar{position:fixed;left:16px;bottom:16px;z-index:120;display:flex;align-items:center;gap:8px;background:#fff;border:1px solid #e4e7ec;border-radius:12px;padding:6px 8px;box-shadow:0 4px 16px rgba(16,24,40,.12);font-family:'Inter',system-ui,sans-serif;font-size:13px;color:#101828}",
      ".cm-btn{display:flex;align-items:center;gap:6px;border:1px solid transparent;background:#f2f4f7;color:#344054;border-radius:8px;padding:6px 10px;font:inherit;font-weight:500;cursor:pointer;transition:background .12s,color .12s}",
      ".cm-btn:hover{background:#e4e7ec}",
      ".cm-btn.cm-on{background:#2e90fa;color:#fff}",
      ".cm-sep{width:1px;height:22px;background:#e4e7ec;margin:0 2px}",
      ".cm-name{border:1px solid #e4e7ec;border-radius:8px;padding:5px 8px;font:inherit;width:118px;color:#101828}",
      ".cm-dot{width:8px;height:8px;border-radius:4px;background:#d0d5dd}",
      ".cm-dot.cm-live{background:#17b26a}",
      ".cm-count{color:#667085;font-variant-numeric:tabular-nums}",
      ".cm-capture{position:fixed;inset:0;z-index:110;cursor:crosshair;background:rgba(46,144,250,.04)}",
      ".cm-pinwrap{position:absolute;width:0;height:0}",
      ".cm-pin{position:absolute;transform:translate(-50%,-100%) scale(var(--dc-inv-zoom,1));transform-origin:bottom center;cursor:pointer;filter:drop-shadow(0 2px 4px rgba(16,24,40,.25))}",
      ".cm-pin svg{display:block}",
      ".cm-pin .cm-num{position:absolute;top:4px;left:50%;transform:translateX(-50%);color:#fff;font:600 12px 'Inter',sans-serif}",
      ".cm-pin.cm-resolved{opacity:.55}",
      ".cm-pop{position:fixed;z-index:130;width:300px;max-height:62vh;overflow:auto;background:#fff;border:1px solid #e4e7ec;border-radius:12px;box-shadow:0 12px 40px rgba(16,24,40,.18);font-family:'Inter',system-ui,sans-serif;color:#101828}",
      ".cm-pop-hd{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid #f2f4f7;position:sticky;top:0;background:#fff}",
      ".cm-pop-ttl{font-weight:600;font-size:13px}",
      ".cm-x{border:none;background:transparent;cursor:pointer;color:#98a2b3;font-size:18px;line-height:1;padding:0 2px}",
      ".cm-msg{padding:10px 12px;border-bottom:1px solid #f2f4f7}",
      ".cm-msg-hd{display:flex;align-items:center;gap:6px;margin-bottom:3px}",
      ".cm-who{font-weight:600;font-size:12.5px}",
      ".cm-when{color:#98a2b3;font-size:11px}",
      ".cm-body{font-size:13px;line-height:1.45;white-space:pre-wrap;word-break:break-word}",
      ".cm-actions{display:flex;gap:10px;margin-top:6px}",
      ".cm-link{border:none;background:transparent;color:#2e90fa;cursor:pointer;font:500 12px 'Inter',sans-serif;padding:0}",
      ".cm-link.cm-danger{color:#f04438}",
      ".cm-compose{padding:10px 12px;display:flex;flex-direction:column;gap:8px}",
      ".cm-input{border:1px solid #e4e7ec;border-radius:8px;padding:8px;font:inherit;font-size:13px;resize:vertical;min-height:54px;color:#101828;box-sizing:border-box;width:100%}",
      ".cm-input.cm-name-in{min-height:0}",
      ".cm-row{display:flex;gap:8px;align-items:center}",
      ".cm-primary{background:#2e90fa;color:#fff;border:none;border-radius:8px;padding:7px 12px;font:600 13px 'Inter',sans-serif;cursor:pointer}",
      ".cm-primary:disabled{opacity:.5;cursor:default}",
      ".cm-err{color:#f04438;font-size:12px}",
    ].join('\n');
    document.head.appendChild(s);
  }

  // ---- identity ----
  const AUTHOR_KEY = 'm8-canvas-author';
  const getAuthor = () => { try { return localStorage.getItem(AUTHOR_KEY) || ''; } catch (e) { return ''; } };
  const setAuthorLS = (n) => { try { localStorage.setItem(AUTHOR_KEY, n); } catch (e) {} };

  const relTime = (iso) => {
    const d = (Date.now() - new Date(iso).getTime()) / 1000;
    if (d < 60) return 'just now';
    if (d < 3600) return Math.floor(d / 60) + 'm';
    if (d < 86400) return Math.floor(d / 3600) + 'h';
    return Math.floor(d / 86400) + 'd';
  };

  // ---- supabase client ----
  function makeClient() {
    const cfg = window.__CM_SUPABASE;
    if (!cfg || !window.supabase) return null;
    return window.supabase.createClient(cfg.url, cfg.anonKey, { realtime: { params: { eventsPerSecond: 5 } } });
  }

  // ---- data hook ----
  function useComments(canvasId) {
    const [rows, setRows] = useState([]);
    const [connected, setConnected] = useState(false);
    const sbRef = useRef(null);

    useEffect(() => {
      const sb = makeClient();
      sbRef.current = sb;
      if (!sb) return undefined;
      let alive = true;
      sb.from('design_comments').select('*').eq('canvas_id', canvasId)
        .order('created_at', { ascending: true })
        .then(function (res) { if (alive && res.data) setRows(res.data); });

      const merge = (row) => setRows((r) => (r.some((x) => x.id === row.id) ? r.map((x) => (x.id === row.id ? row : x)) : r.concat([row])));
      const drop = (id) => setRows((r) => r.filter((x) => x.id !== id));

      const flt = 'canvas_id=eq.' + canvasId;
      const ch = sb.channel('design_comments:' + canvasId)
        .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'design_comments', filter: flt }, (p) => merge(p.new))
        .on('postgres_changes', { event: 'UPDATE', schema: 'public', table: 'design_comments', filter: flt }, (p) => merge(p.new))
        .on('postgres_changes', { event: 'DELETE', schema: 'public', table: 'design_comments', filter: flt }, (p) => drop(p.old.id))
        .subscribe((status) => setConnected(status === 'SUBSCRIBED'));

      return () => { alive = false; sb.removeChannel(ch); };
    }, [canvasId]);

    const insert = useCallback(async (payload) => {
      const sb = sbRef.current;
      if (!sb) throw new Error('offline');
      const res = await sb.from('design_comments').insert(Object.assign({ canvas_id: canvasId }, payload)).select().single();
      if (res.error) throw res.error;
      const data = res.data;
      setRows((r) => (r.some((x) => x.id === data.id) ? r : r.concat([data])));
      return data;
    }, [canvasId]);

    const addPin = useCallback((x, y, author, body) => insert({ x: x, y: y, author: author, body: body }), [insert]);
    const addReply = useCallback((parentId, author, body) => insert({ parent_id: parentId, author: author, body: body }), [insert]);
    const setResolved = useCallback(async (id, resolved) => {
      const sb = sbRef.current; if (!sb) return;
      setRows((r) => r.map((x) => (x.id === id ? Object.assign({}, x, { resolved: resolved }) : x)));
      await sb.from('design_comments').update({ resolved: resolved }).eq('id', id);
    }, []);
    const remove = useCallback(async (id) => {
      const sb = sbRef.current; if (!sb) return;
      setRows((r) => r.filter((x) => x.id !== id && x.parent_id !== id));
      await sb.from('design_comments').delete().eq('id', id);
    }, []);

    return { rows: rows, connected: connected, addPin: addPin, addReply: addReply, setResolved: setResolved, remove: remove };
  }

  const CMCtx = createContext(null);
  const useCM = () => useContext(CMCtx);

  // ---- pins rendered inside the canvas world (commentSlot) ----
  function CommentPins() {
    const cm = useCM();
    if (!cm) return null;
    const pins = cm.rows.filter((r) => !r.parent_id && (cm.showResolved || !r.resolved));
    const Tear = (fill) => (
      React.createElement('svg', { width: 28, height: 34, viewBox: '0 0 28 34' },
        React.createElement('path', { d: 'M14 0C6.3 0 0 6 0 13.4 0 22 14 34 14 34s14-12 14-20.6C28 6 21.7 0 14 0z', fill: fill }))
    );
    return (
      React.createElement(React.Fragment, null,
        pins.map((p, i) => (
          React.createElement('div', { key: p.id, className: 'cm-pinwrap', style: { left: p.x, top: p.y } },
            React.createElement('div', {
              className: 'cm-pin' + (p.resolved ? ' cm-resolved' : ''),
              'data-cm-pin': p.id,
              onClick: (e) => { e.stopPropagation(); cm.setDraft(null); cm.setActiveId(p.id); },
            },
              Tear(p.resolved ? '#98a2b3' : '#2e90fa'),
              React.createElement('span', { className: 'cm-num' }, p.resolved ? '✓' : (i + 1))))
        )),
        cm.draft ? React.createElement('div', { className: 'cm-pinwrap', style: { left: cm.draft.x, top: cm.draft.y } },
          React.createElement('div', { className: 'cm-pin' }, Tear('#f79009'))) : null)
    );
  }

  // ---- toolbar (fixed) ----
  function CommentToolbar() {
    const cm = useCM();
    const open = cm.rows.filter((r) => !r.parent_id && !r.resolved).length;
    return (
      <div className="cm-toolbar" onPointerDown={(e) => e.stopPropagation()}>
        <button className={'cm-btn' + (cm.mode === 'placing' ? ' cm-on' : '')} onClick={cm.beginPlace} title="Drop a comment pin">
          <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
          Comment
        </button>
        <button className={'cm-btn' + (cm.showResolved ? ' cm-on' : '')} onClick={() => cm.setShowResolved((v) => !v)}>
          {cm.showResolved ? 'Hide resolved' : 'Show resolved'}
        </button>
        <span className="cm-count">{open} open</span>
        <div className="cm-sep" />
        <input className="cm-name" placeholder="Your name" value={cm.author} onChange={(e) => cm.setAuthor(e.target.value)} />
        <span className={'cm-dot' + (cm.connected ? ' cm-live' : '')} title={cm.connected ? 'Live' : 'Offline'} />
      </div>
    );
  }

  // ---- draft composer (new pin) ----
  function DraftCompose() {
    const cm = useCM();
    const [name, setName] = useState(cm.author);
    const [body, setBody] = useState('');
    const [err, setErr] = useState('');
    const [busy, setBusy] = useState(false);
    const submit = async () => {
      if (!name.trim()) { setErr('Enter your name'); return; }
      if (!body.trim()) return;
      setBusy(true); setErr('');
      try {
        cm.setAuthor(name.trim());
        await cm.addPin(cm.draft.x, cm.draft.y, name.trim(), body.trim());
        cm.setDraft(null);
      } catch (e) { setErr('Could not save — check connection'); setBusy(false); }
    };
    return (
      <>
        <div className="cm-pop-hd"><span className="cm-pop-ttl">New comment</span>
          <button className="cm-x" onClick={() => cm.setDraft(null)}>×</button></div>
        <div className="cm-compose">
          {!cm.author && <input className="cm-input cm-name-in" placeholder="Your name" value={name} onChange={(e) => setName(e.target.value)} />}
          <textarea className="cm-input" autoFocus placeholder="Add a comment…" value={body} onChange={(e) => setBody(e.target.value)} />
          {err && <span className="cm-err">{err}</span>}
          <div className="cm-row" style={{ justifyContent: 'flex-end' }}>
            <button className="cm-primary" disabled={busy || !body.trim()} onClick={submit}>Comment</button>
          </div>
        </div>
      </>
    );
  }

  // ---- thread (existing pin) ----
  function Thread(props) {
    const root = props.root;
    const cm = useCM();
    const replies = cm.rows.filter((r) => r.parent_id === root.id).sort((a, b) => new Date(a.created_at) - new Date(b.created_at));
    const [name, setName] = useState(cm.author);
    const [body, setBody] = useState('');
    const [err, setErr] = useState('');
    const mine = (m) => cm.author && m.author === cm.author;
    const submit = async () => {
      if (!name.trim()) { setErr('Enter your name'); return; }
      if (!body.trim()) return;
      setErr('');
      try { cm.setAuthor(name.trim()); await cm.addReply(root.id, name.trim(), body.trim()); setBody(''); }
      catch (e) { setErr('Could not save'); }
    };
    const Msg = (mp) => {
      const m = mp.m;
      return (
        <div className="cm-msg">
          <div className="cm-msg-hd"><span className="cm-who">{m.author}</span><span className="cm-when">{relTime(m.created_at)}</span></div>
          <div className="cm-body">{m.body}</div>
          {mine(m) && <div className="cm-actions"><button className="cm-link cm-danger" onClick={() => cm.remove(m.id)}>Delete</button></div>}
        </div>
      );
    };
    return (
      <>
        <div className="cm-pop-hd">
          <span className="cm-pop-ttl">{root.resolved ? 'Resolved' : 'Comment'}</span>
          <span style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
            <button className="cm-link" onClick={() => cm.setResolved(root.id, !root.resolved)}>{root.resolved ? 'Reopen' : 'Resolve'}</button>
            <button className="cm-x" onClick={() => cm.setActiveId(null)}>×</button>
          </span>
        </div>
        <Msg m={root} />
        {replies.map((r) => <Msg key={r.id} m={r} />)}
        <div className="cm-compose">
          {!cm.author && <input className="cm-input cm-name-in" placeholder="Your name" value={name} onChange={(e) => setName(e.target.value)} />}
          <textarea className="cm-input" placeholder="Reply…" value={body} onChange={(e) => setBody(e.target.value)} />
          {err && <span className="cm-err">{err}</span>}
          <div className="cm-row" style={{ justifyContent: 'flex-end' }}>
            <button className="cm-primary" disabled={!body.trim()} onClick={submit}>Reply</button>
          </div>
        </div>
      </>
    );
  }

  // ---- popover host (portaled, anchored to pin or draft) ----
  function CommentPopover() {
    const cm = useCM();
    const [pos, setPos] = useState(null);
    const active = cm.activeId ? cm.rows.find((r) => r.id === cm.activeId && !r.parent_id) : null;
    useEffect(() => {
      if (!active && !cm.draft) { setPos(null); return undefined; }
      let raf;
      const tick = () => {
        if (cm.draft) {
          setPos({ left: Math.min(cm.draft.cx + 16, window.innerWidth - 316), top: Math.min(cm.draft.cy, window.innerHeight - 230) });
        } else {
          const el = document.querySelector('[data-cm-pin="' + cm.activeId + '"]');
          if (el) { const r = el.getBoundingClientRect(); setPos({ left: Math.min(r.right + 10, window.innerWidth - 316), top: Math.min(r.top, window.innerHeight - 250) }); }
        }
        raf = requestAnimationFrame(tick);
      };
      tick();
      return () => cancelAnimationFrame(raf);
    }, [active, cm.draft, cm.activeId]);

    if (!pos) return null;
    return ReactDOM.createPortal(
      <div className="cm-pop" style={{ left: pos.left, top: pos.top }} onPointerDown={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}>
        {cm.draft ? <DraftCompose /> : (active ? <Thread root={active} /> : null)}
      </div>,
      document.body
    );
  }

  // ---- root: provides context, capture overlay, toolbar, popover ----
  function CommentsRoot(props) {
    const data = useComments(props.canvasId || 'default');
    const [mode, setMode] = useState(null);
    const [activeId, setActiveId] = useState(null);
    const [draft, setDraft] = useState(null);
    const [showResolved, setShowResolved] = useState(false);
    const [author, setAuthorState] = useState(getAuthor());
    const setAuthor = useCallback((n) => { setAuthorState(n); setAuthorLS(n); }, []);

    useEffect(() => {
      const k = (e) => { if (e.key === 'Escape') { setMode(null); setDraft(null); setActiveId(null); } };
      window.addEventListener('keydown', k);
      return () => window.removeEventListener('keydown', k);
    }, []);

    const beginPlace = () => { setActiveId(null); setDraft(null); setMode((m) => (m === 'placing' ? null : 'placing')); };
    const onCapture = (e) => {
      const api = window.DCApi;
      const w = api && api.screenToWorld ? api.screenToWorld(e.clientX, e.clientY) : null;
      setMode(null);
      if (!w) return;
      setDraft({ x: w.x, y: w.y, cx: e.clientX, cy: e.clientY });
    };

    const ctx = Object.assign({}, data, {
      canvasId: props.canvasId, mode: mode, setMode: setMode, beginPlace: beginPlace,
      activeId: activeId, setActiveId: setActiveId, draft: draft, setDraft: setDraft,
      showResolved: showResolved, setShowResolved: setShowResolved, author: author, setAuthor: setAuthor,
    });

    return (
      <CMCtx.Provider value={ctx}>
        {props.children}
        {mode === 'placing' && <div className="cm-capture" onClick={onCapture} />}
        <CommentToolbar />
        <CommentPopover />
      </CMCtx.Provider>
    );
  }

  Object.assign(window, { CommentsRoot: CommentsRoot, CommentPins: CommentPins });
})();
