// Modules = reusable building blocks, each a sub-project with its own Forgejo
// repo + spec (brief) + roadmap. Agents improve a module the same way they work
// any project. This screen: Library (cards) / Graph (deps) / Matrix (project×module).
// Reuses globals from board.jsx (Icon, mdToHtml) and pages.jsx (RepoFiles,
// RoadmapModal, BriefModal). brief/roadmap/files hit the shared project endpoints.

const MOD_CAT_ORDER = ['foundation', 'content', 'support', 'revenue', 'insight'];
const MOD_STATUS = { planned: 'var(--c-text-3)', building: 'var(--c-status-running)', done: 'var(--c-status-done)' };

// ---- dependency layering (column = dependency depth) ----
function moduleLayers(modules) {
  const by = Object.fromEntries(modules.map(m => [m.id, m]));
  const memo = {};
  const depth = (id, seen = new Set()) => {
    if (id in memo) return memo[id];
    if (seen.has(id)) return 0; // cycle guard
    seen.add(id);
    const m = by[id];
    const deps = (m && m.deps) || [];
    const d = deps.length ? 1 + Math.max(...deps.map(x => (by[x] ? depth(x, seen) : -1))) : 0;
    return (memo[id] = d);
  };
  modules.forEach(m => depth(m.id));
  const layers = [];
  modules.forEach(m => { const d = memo[m.id]; (layers[d] = layers[d] || []).push(m); });
  return layers;
}

function ModuleGraph({ modules }) {
  const layers = moduleLayers(modules);
  const NW = 160, NH = 48, VGAP = 20, COLW = 230, PADX = 24, PADY = 28;
  const pos = {};
  layers.forEach((col, li) => (col || []).forEach((m, ri) => {
    pos[m.id] = { x: PADX + li * COLW, y: PADY + ri * (NH + VGAP), m };
  }));
  const W = PADX * 2 + (layers.length - 1) * COLW + NW;
  const maxRows = Math.max(1, ...layers.map(c => (c || []).length));
  const H = PADY * 2 + maxRows * (NH + VGAP) - VGAP;

  const edges = [];
  modules.forEach(m => ((m.deps || []).forEach(dep => {
    if (pos[dep] && pos[m.id]) edges.push({ from: pos[dep], to: pos[m.id], key: `${dep}-${m.id}` });
  })));

  return (
    <div className="panel" style={{ overflow: 'auto' }}>
      <svg viewBox={`0 0 ${W} ${H}`} style={{ width: '100%', minWidth: W, height: 'auto', display: 'block' }}>
        {edges.map(e => {
          const x1 = e.from.x + NW, y1 = e.from.y + NH / 2;
          const x2 = e.to.x, y2 = e.to.y + NH / 2;
          const mx = (x1 + x2) / 2;
          return <path key={e.key} d={`M ${x1} ${y1} C ${mx} ${y1}, ${mx} ${y2}, ${x2} ${y2}`}
            fill="none" stroke="var(--c-line-strong)" strokeWidth="1.5" />;
        })}
        {Object.values(pos).map(({ x, y, m }) => (
          <g key={m.id}>
            <rect x={x} y={y} width={NW} height={NH} rx="9" fill="var(--c-surface-2)" stroke="var(--c-line)" strokeWidth="1" />
            <rect x={x + 9} y={y + 9} width="30" height="30" rx="7" fill={m.icon_bg || 'var(--c-accent)'} />
            <text x={x + 24} y={y + 29} textAnchor="middle" fontSize="15" fontWeight="700" fill="white"
              fontFamily="var(--font-mono)">{m.icon || m.name[0]}</text>
            <text x={x + 48} y={y + 21} fontSize="12.5" fontWeight="600" fill="var(--c-text)" fontFamily="var(--font-sans)">{m.name}</text>
            <text x={x + 48} y={y + 36} fontSize="9.5" fill="var(--c-text-3)" fontFamily="var(--font-mono)">{m.category}</text>
          </g>
        ))}
      </svg>
    </div>
  );
}

function AssemblyMatrix({ data, onToggle, busyCell }) {
  const { projects, modules, links } = data;
  const linkOf = (pid, mid) => links.find(l => l.project_id === pid && l.module_id === mid);
  if (!projects.length) return <div className="empty" style={{ margin: 16 }}>no projects yet</div>;
  return (
    <div className="dtbl-wrap">
      <table className="dtbl matrix-tbl">
        <thead>
          <tr>
            <th style={{ textAlign: 'left' }}>Project \ Module</th>
            {modules.map(m => (
              <th key={m.id} title={m.name} style={{ textAlign: 'center' }}>
                <span className="mtx-modhead">
                  <span className="mtx-modicon" style={{ background: m.icon_bg }}>{m.icon || m.name[0]}</span>
                  {m.name}
                </span>
              </th>
            ))}
          </tr>
        </thead>
        <tbody>
          {projects.map(p => (
            <tr key={p.id}>
              <td style={{ whiteSpace: 'nowrap' }}>
                <span style={{ display: 'inline-flex', alignItems: 'center', gap: 7 }}>
                  <Swatch color={p.color} size={9} />{p.name}
                </span>
              </td>
              {modules.map(m => {
                const link = linkOf(p.id, m.id);
                const busy = busyCell === `${p.id}:${m.id}`;
                return (
                  <td key={m.id} style={{ textAlign: 'center' }}>
                    <button type="button" className={`mtx-cell ${link ? 'on' : ''}`} disabled={busy}
                      title={link ? `${link.status} — click to remove` : 'add module to project'}
                      onClick={() => onToggle(p.id, m.id, link)}>
                      {busy ? '·' : link
                        ? <span className="mtx-dot" style={{ background: MOD_STATUS[link.status] || 'var(--c-text-3)' }} />
                        : <Icon name="plus" size={12} />}
                    </button>
                  </td>
                );
              })}
            </tr>
          ))}
        </tbody>
      </table>
      <div className="mtx-legend">
        {Object.entries(MOD_STATUS).map(([k, c]) => (
          <span key={k}><span className="mtx-dot" style={{ background: c }} />{k}</span>
        ))}
      </div>
    </div>
  );
}

function ImproveModal({ module, onSubmit, onClose }) {
  const [text, setText] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  async function submit() {
    if (!text.trim()) return;
    setBusy(true);
    try { await onSubmit(text.trim()); onClose(); } finally { setBusy(false); }
  }
  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="panel" style={{ width: 600, maxWidth: '94vw' }} onClick={e => e.stopPropagation()}>
        <div className="panel-head">
          <span className="panel-title"><Icon name="cpu" size={13} /> Improve {module.name}</span>
          <button className="icon-btn" type="button" onClick={onClose}><Icon name="close" size={14} /></button>
        </div>
        <div className="panel-body" style={{ padding: '16px 18px' }}>
          <p style={{ fontSize: 12, color: 'var(--c-text-3)', margin: '0 0 10px' }}>
            codex-agent works the module's own repo (<span className="chip-mono">{module.repo}</span>). The spec is sent as context automatically.
          </p>
          <textarea className="input" style={{ minHeight: 150, resize: 'vertical', lineHeight: 1.6, fontSize: 13, width: '100%' }}
            autoFocus value={text} spellCheck={false}
            placeholder="What to build or change in this module — e.g. 'scaffold the data model and the checkout endpoint per the spec'."
            onChange={e => setText(e.target.value)} />
        </div>
        <div className="panel-head" style={{ justifyContent: 'flex-end', gap: 8, borderTop: '1px solid var(--c-line)', borderBottom: 'none' }}>
          <button className="btn ghost" type="button" onClick={onClose}>Cancel</button>
          <button className="btn primary" type="button" disabled={busy || !text.trim()} onClick={submit}>
            <Icon name="play" size={12} />{busy ? 'Dispatching…' : 'Dispatch to agent'}
          </button>
        </div>
      </div>
    </div>
  );
}

function ModuleDetail({ module, tasks, onBack, pushToast }) {
  const [tab, setTab] = React.useState('spec');
  const [kb, setKb] = React.useState(null);
  const [editBrief, setEditBrief] = React.useState(false);
  const [roadmapItem, setRoadmapItem] = React.useState(null); // {} = new
  const [improve, setImprove] = React.useState(false);

  const loadKb = React.useCallback(() => {
    window.ORC_API.getKb(module.id).then(setKb).catch(() => setKb({ brief: '', items: [] }));
  }, [module.id]);
  React.useEffect(() => { loadKb(); }, [loadKb]);

  const mtasks = (tasks || []).filter(t => t.project === module.id);
  const badge = (col) => col === 'review' ? 'review' : col === 'done' ? 'live' : col === 'failed' ? 'idle' : col === 'running' ? 'update' : 'idle';

  async function saveBrief(text) {
    await window.ORC_API.updateBrief(module.id, text);
    loadKb();
    pushToast({ tone: 'ok', title: 'Spec saved', msg: module.name });
  }
  async function submitRoadmap(body) {
    if (roadmapItem && roadmapItem.id) await window.ORC_API.updateRoadmap(roadmapItem.id, body);
    else await window.ORC_API.createRoadmap(module.id, body);
    setRoadmapItem(null); loadKb();
  }
  async function generate() {
    pushToast({ tone: 'review', title: 'Generating roadmap', msg: module.name });
    try { await window.ORC_API.generateRoadmap(module.id); setTimeout(loadKb, 1500); }
    catch (e) { pushToast({ tone: 'fail', title: 'Generate failed', msg: e.message || 'error' }); }
  }
  async function dispatchImprove(prompt) {
    const t = await window.ORC_API.improveModule(module.id, prompt);
    pushToast({ tone: 'ok', title: 'Dispatched', msg: `${t.id} → ${module.name}` });
  }

  const items = (kb && kb.items) || [];

  return (
    <div className="page">
      <div className="page-head">
        <div style={{ width: 44, height: 44, borderRadius: 10, background: module.icon_bg || module.color,
          display: 'grid', placeItems: 'center', color: 'white', fontFamily: 'var(--font-mono)', fontWeight: 700, fontSize: 18,
          boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.25)' }}>{module.icon || module.name[0]}</div>
        <div>
          <h1 className="page-title">{module.name}</h1>
          <p className="page-sub" style={{ fontFamily: 'var(--font-mono)' }}>{module.repo} · {module.default_branch || 'main'} · {module.category}</p>
        </div>
        <div style={{ marginLeft: 'auto', display: 'flex', gap: 6 }}>
          <button className="btn ghost" type="button" onClick={onBack}><Icon name="arrowRight" size={12} style={{ transform: 'rotate(180deg)' }} />All modules</button>
          <button className="btn primary" type="button" onClick={() => setImprove(true)}><Icon name="cpu" size={12} />Improve</button>
        </div>
      </div>

      {(module.deps && module.deps.length > 0) && (
        <div style={{ display: 'flex', gap: 6, alignItems: 'center', marginBottom: 14, flexWrap: 'wrap' }}>
          <span style={{ fontSize: 11, color: 'var(--c-text-3)' }}>depends on</span>
          {module.deps.map(d => <span key={d} className="chip chip-mono">{d}</span>)}
        </div>
      )}

      <div className="detail-tabs" style={{ marginBottom: 14 }}>
        {[
          { id: 'spec', label: 'Spec', icon: 'book' },
          { id: 'roadmap', label: 'Roadmap', icon: 'steps' },
          { id: 'files', label: 'Files', icon: 'folder' },
          { id: 'tasks', label: `Tasks (${mtasks.length})`, icon: 'log' },
        ].map(t => (
          <button key={t.id} className={`detail-tab ${tab === t.id ? 'active' : ''}`} type="button" onClick={() => setTab(t.id)}>
            <Icon name={t.icon} size={12} />{t.label}
          </button>
        ))}
      </div>

      {tab === 'spec' && (
        <div className="panel">
          <div className="panel-head">
            <Icon name="book" size={14} /><span className="panel-title">Spec</span>
            <button className="btn sm" style={{ marginLeft: 'auto' }} type="button" onClick={() => setEditBrief(true)}><Icon name="edit" size={11} />Edit</button>
          </div>
          <div className="panel-body" style={{ padding: '16px 18px' }}>
            {kb === null ? <div className="empty">loading…</div>
              : (kb.brief || '').trim()
                ? <div className="md-body" dangerouslySetInnerHTML={{ __html: mdToHtml(kb.brief) }} />
                : <div className="empty">no spec yet — click Edit to write the module concept</div>}
          </div>
        </div>
      )}

      {tab === 'roadmap' && (
        <div className="panel">
          <div className="panel-head">
            <Icon name="steps" size={14} /><span className="panel-title">Roadmap</span>
            <div style={{ marginLeft: 'auto', display: 'flex', gap: 6 }}>
              <button className="btn sm ghost" type="button" onClick={generate} disabled={kb && kb.generating}><Icon name="spark" size={11} />{kb && kb.generating ? 'Generating…' : 'Generate'}</button>
              <button className="btn sm" type="button" onClick={() => setRoadmapItem({})}><Icon name="plus" size={11} />Add</button>
            </div>
          </div>
          <div className="panel-body">
            {items.length === 0 ? <div className="empty" style={{ padding: '24px 0' }}>no milestones yet</div>
              : items.map(it => (
                <div className="row" key={it.id} style={{ gridTemplateColumns: 'auto 1fr auto auto', cursor: 'pointer' }} onClick={() => setRoadmapItem(it)}>
                  <span className={`badge ${it.status === 'done' ? 'live' : it.status === 'active' ? 'update' : 'idle'}`}>{it.status}</span>
                  <div className="row-name"><div><div>{it.title}</div>{it.detail && <div style={{ fontSize: 11, color: 'var(--c-text-3)' }}>{it.detail}</div>}</div></div>
                  <span className="chip-mono" style={{ color: 'var(--c-text-3)' }}>{it.estimate}</span>
                  <Icon name="arrowRight" size={12} />
                </div>
              ))}
          </div>
        </div>
      )}

      {tab === 'files' && <div className="panel"><div className="panel-body" style={{ padding: 0 }}><RepoFiles project={module} /></div></div>}

      {tab === 'tasks' && (
        <div className="panel">
          <div className="panel-head"><Icon name="log" size={14} /><span className="panel-title">Tasks</span><span className="chip-mono" style={{ marginLeft: 'auto', color: 'var(--c-text-3)' }}>{mtasks.length}</span></div>
          <div className="panel-body">
            {mtasks.length === 0 ? <div className="empty" style={{ padding: '24px 0' }}>no tasks — hit Improve to dispatch one</div>
              : [...mtasks].reverse().map(t => (
                <div className="row" key={t.id} style={{ gridTemplateColumns: '1fr auto auto' }}>
                  <div className="row-name"><div><div>{t.title}</div><div style={{ fontFamily: 'var(--font-mono)', fontSize: 10.5, color: 'var(--c-text-3)' }}>{t.id}{t.agent ? ` · ${t.agent}` : ''}</div></div></div>
                  <PriorityBadge priority={t.priority} />
                  <span className={`badge ${badge(t.column)}`}>{t.column}</span>
                </div>
              ))}
          </div>
        </div>
      )}

      {editBrief && <BriefModal value={(kb && kb.brief) || ''} onSave={saveBrief} onClose={() => setEditBrief(false)} />}
      {roadmapItem !== null && <RoadmapModal item={roadmapItem && roadmapItem.id ? roadmapItem : null} onSubmit={submitRoadmap} onClose={() => setRoadmapItem(null)} />}
      {improve && <ImproveModal module={module} onSubmit={dispatchImprove} onClose={() => setImprove(false)} />}
    </div>
  );
}

function ModulesScreen({ tasks, pushToast }) {
  const [view, setView] = React.useState('library'); // library | graph | matrix
  const [modules, setModules] = React.useState(null);
  const [openId, setOpenId] = React.useState(null);
  const [assembly, setAssembly] = React.useState(null);
  const [busyCell, setBusyCell] = React.useState(null);

  const loadModules = React.useCallback(() => {
    window.ORC_API.getModules().then(setModules).catch(() => setModules([]));
  }, []);
  React.useEffect(() => { loadModules(); }, [loadModules]);
  React.useEffect(() => {
    if (view === 'matrix') window.ORC_API.getAssembly().then(setAssembly).catch(() => setAssembly({ projects: [], modules: [], links: [] }));
  }, [view]);

  async function toggleCell(pid, mid, link) {
    setBusyCell(`${pid}:${mid}`);
    try {
      if (link) await window.ORC_API.unsetProjectModule(pid, mid);
      else await window.ORC_API.setProjectModule(pid, { module_id: mid });
      const a = await window.ORC_API.getAssembly(); setAssembly(a);
    } catch (e) { pushToast({ tone: 'fail', title: 'Failed', msg: e.message || 'error' }); }
    finally { setBusyCell(null); }
  }

  if (openId && modules) {
    const m = modules.find(x => x.id === openId);
    if (m) return <ModuleDetail module={m} tasks={tasks} onBack={() => { setOpenId(null); loadModules(); }} pushToast={pushToast} />;
  }

  const byCat = {};
  (modules || []).forEach(m => { (byCat[m.category] || (byCat[m.category] = [])).push(m); });
  const cats = Object.keys(byCat).sort((a, b) => {
    const ia = MOD_CAT_ORDER.indexOf(a), ib = MOD_CAT_ORDER.indexOf(b);
    return (ia < 0 ? 99 : ia) - (ib < 0 ? 99 : ib);
  });
  const counts = (modules || []).reduce((acc, m) => { acc[m.id] = (tasks || []).filter(t => t.project === m.id).length; return acc; }, {});

  return (
    <div className="page">
      <div className="page-head">
        <div>
          <h1 className="page-title">Modules</h1>
          <p className="page-sub">Reusable building blocks · each a sub-project with its own repo, spec and roadmap</p>
        </div>
        <div style={{ marginLeft: 'auto' }}>
          <div className="seg">
            {[{ id: 'library', label: 'Library' }, { id: 'graph', label: 'Graph' }, { id: 'matrix', label: 'Matrix' }].map(v => (
              <button key={v.id} type="button" className={view === v.id ? 'active' : ''} onClick={() => setView(v.id)}>{v.label}</button>
            ))}
          </div>
        </div>
      </div>

      {modules === null ? <div className="empty" style={{ margin: 16 }}>loading…</div>
        : view === 'library' ? (
          cats.length === 0 ? <div className="empty" style={{ margin: 16 }}>no modules</div>
            : cats.map(cat => (
              <div key={cat} style={{ marginBottom: 22 }}>
                <div className="section-title" style={{ marginBottom: 10 }}>{cat}</div>
                <div className="modules-grid">
                  {byCat[cat].map(m => (
                    <article className="module-card" key={m.id} style={{ cursor: 'pointer' }} onClick={() => setOpenId(m.id)}>
                      <div className="module-head">
                        <div className="module-icon" style={{ background: m.icon_bg }}>{m.icon || m.name[0]}</div>
                        <div style={{ minWidth: 0 }}>
                          <div className="module-name">{m.name}</div>
                          <div className="module-id">modules/{m.id}</div>
                        </div>
                        <div className="module-status">
                          {counts[m.id] > 0 && <span className="badge update">{counts[m.id]} task{counts[m.id] > 1 ? 's' : ''}</span>}
                        </div>
                      </div>
                      <p className="module-desc">{firstLine(m.brief) || 'no spec yet'}</p>
                      <div className="module-meta">
                        <span>category <b>{m.category}</b></span>
                        {(m.deps && m.deps.length > 0) && <span>deps <b>{m.deps.join(', ')}</b></span>}
                      </div>
                      <div className="module-actions">
                        <button className="btn" type="button" onClick={(e) => { e.stopPropagation(); setOpenId(m.id); }}><Icon name="arrowRight" size={12} />Open</button>
                      </div>
                    </article>
                  ))}
                </div>
              </div>
            ))
        ) : view === 'graph' ? <ModuleGraph modules={modules} />
          : assembly === null ? <div className="empty" style={{ margin: 16 }}>loading…</div>
            : <AssemblyMatrix data={assembly} onToggle={toggleCell} busyCell={busyCell} />}
    </div>
  );
}

// first non-empty, non-heading line of a markdown brief (for card description)
function firstLine(md) {
  if (!md) return '';
  for (const l of md.split('\n')) {
    const t = l.trim();
    if (t && !t.startsWith('#')) return t.length > 140 ? t.slice(0, 140) + '…' : t;
  }
  return '';
}

window.ModulesScreen = ModulesScreen;
