/* project-todos.jsx — redesigned "Todos" panel for the Project ▸ Planning tab.
   ────────────────────────────────────────────────────────────────────────────
   DROP-IN REPLACEMENT for the <Panel title="Todos">…</Panel> block (and its
   TodoCluster rows) inside ProjectPlanningExec in project-planning.jsx.

   It renders its own <Panel>, so you replace the whole Todos Panel with a single
   <ProjectTodosPanel … /> and delete the now-unused TodoCluster component.

   Design goals (from the redesign brief):
     • Title + status are the prominent elements. Status is a colored pill you
       click to change. "Done" is just a status — the old checkbox is gone.
     • Milestone + cluster are quiet chips that open a picker on click.
     • Remarks / "after" are tucked behind chips, shown only when present.
     • Clicking the empty area of a card opens a slide-in edit drawer; the chips,
       pill and title handle their own clicks (they stopPropagation).
     • Group by Cluster (default) or Milestone, toggled in the panel header.
     • Header actions: "+ Cluster" and "+ Todo".

   ── Props (all map 1:1 onto mutators that already exist in ProjectPlanningExec) ──
     categories      active.todoPlan.categories  → [{ id, name, items:[…] }]
                       item: { id, title, status, done, remarks, after, milestoneId, category }
     milestones      sectionMs                   → [{ id, title, completed }]
     statuses        window.AC.PROJECT_TODO_STATUSES   (string[])
     onRenameCluster (catId, name)        → renameCat(catId, name)
     onAddCluster    ()                   → addCluster()
     onRemoveCluster (catId)              → removeCluster(catId)
     onAddTodo       (catId)              → addTodo(catId)
     onPatchTodo     (catId, todoId, patch) → patchTodo(catId, todoId, patch)
                       NB: when patching status, also set done:(status==='Done')
     onMoveTodo      (fromCatId, todoId, toCatId) → moveTodo(fromCatId, todoId, toCatId)
     onRemoveTodo    (catId, todoId)      → removeTodo(catId, todoId)

   Requires: window.Panel, window.Icon (already in ui.jsx / icons.jsx) and
   project-todos.css loaded after app.css.
   ──────────────────────────────────────────────────────────────────────────── */
(function () {
  const { useState, useRef, useEffect, useLayoutEffect } = React;

  /* Status → color system. Tweak these hexes to taste; they sit in the slate/
     blue brand and intentionally echo the app's pill palette. */
  const STATUS_META = {
    'To do':          { fg: '#64748b', bg: '#f1f4f9', bd: '#d4dbe6', dot: '#94a3b8' },
    'In progress':    { fg: '#1e429f', bg: '#eaf0fb', bd: '#c5d6f5', dot: '#2856c4' },
    'Partially done': { fg: '#b45309', bg: '#fbf1e3', bd: '#f0d9b8', dot: '#d08a3a' },
    'Missing info':   { fg: '#0e7490', bg: '#e7f2f5', bd: '#c2dee5', dot: '#0e9bb8' },
    'Blocked':        { fg: '#c0392b', bg: '#fcecea', bd: '#f0c7c1', dot: '#d65648' },
    'Done':           { fg: '#15803d', bg: '#e7f5ec', bd: '#bfe3cc', dot: '#1f9a64' },
  };
  const META = (s) => STATUS_META[s] || STATUS_META['To do'];
  const isDoneStatus = (s) => s === 'Done';
  const statusChoices = (status, statuses) => {
    const configured = statuses && statuses.length ? statuses : Object.keys(STATUS_META);
    return configured.includes(status) ? configured : [status, ...configured];
  };
  const categoryLabel = (value) => window.AC.projectTodoCategoryLabel(value);

  /* ───────────────────────── popover (portal into <body>) ───────────────────────── */
  function Popover({ trigger, align, children }) {
    const [open, setOpen] = useState(false);
    const [pos, setPos] = useState(null);
    const wrapRef = useRef(null);
    const menuRef = useRef(null);

    const place = () => {
      const el = wrapRef.current; if (!el) return;
      const r = el.getBoundingClientRect();
      setPos({ left: r.left, right: window.innerWidth - r.right, top: r.bottom + 5, anchorTop: r.top });
    };
    useLayoutEffect(() => { if (open) place(); }, [open]);
    useLayoutEffect(() => {
      if (!open || !menuRef.current || !pos) return;
      const mr = menuRef.current.getBoundingClientRect();
      if (pos.top + mr.height > window.innerHeight - 8 && pos.anchorTop - 5 - mr.height > 8) {
        menuRef.current.style.top = (pos.anchorTop - 5 - mr.height) + 'px';
      }
    }, [open, pos]);
    useEffect(() => {
      if (!open) return;
      const off = (e) => {
        if (wrapRef.current && wrapRef.current.contains(e.target)) return;
        if (menuRef.current && menuRef.current.contains(e.target)) return;
        setOpen(false);
      };
      const onKey = (e) => { if (e.key === 'Escape') setOpen(false); };
      const reflow = () => place();
      document.addEventListener('pointerdown', off, true);
      document.addEventListener('keydown', onKey);
      window.addEventListener('scroll', reflow, true);
      window.addEventListener('resize', reflow);
      return () => {
        document.removeEventListener('pointerdown', off, true);
        document.removeEventListener('keydown', onKey);
        window.removeEventListener('scroll', reflow, true);
        window.removeEventListener('resize', reflow);
      };
    }, [open]);

    const style = align === 'right'
      ? { top: pos && pos.top, right: pos && pos.right, left: 'auto' }
      : { top: pos && pos.top, left: pos && pos.left, right: 'auto' };
    const menu = (open && pos) ? ReactDOM.createPortal(
      <div ref={menuRef} className="ptd-pop" style={style} onClick={(e) => e.stopPropagation()}>
        {typeof children === 'function' ? children({ close: () => setOpen(false) }) : children}
      </div>, document.body) : null;

    return (
      <span className="ptd-pop-anchor" ref={wrapRef} style={{ display: 'inline-flex' }}>
        {trigger({ open, toggle: (e) => { if (e) e.stopPropagation(); setOpen((o) => !o); } })}
        {menu}
      </span>
    );
  }

  /* ───────────────────────── status pill + picker ───────────────────────── */
  function StatusPill({ status, statuses, sm, onChange }) {
    const m = META(status);
    const options = statusChoices(status, statuses);
    return (
      <Popover align="left" trigger={({ toggle }) => (
        <button type="button" className={'ptd-status' + (sm ? ' sm' : '')} onClick={toggle}
          style={{ background: m.bg, color: m.fg, borderColor: m.bd }} title="Change status">
          <span className="ptd-sdot" style={{ background: m.dot }} />{status}
          <Icon name="chevD" size={11} className="ptd-scar" />
        </button>
      )}>
        {({ close }) => (
          <React.Fragment>
            <div className="ptd-pl">Status</div>
            {options.map((s) => (
              <button key={s} type="button" className={'ptd-pi' + (s === status ? ' on' : '')}
                onClick={() => { onChange(s); close(); }}>
                <span className="ptd-pdot" style={{ background: META(s).dot }} />{s}
                <Icon name="check" size={14} className="ptd-pcheck" />
              </button>
            ))}
          </React.Fragment>
        )}
      </Popover>
    );
  }

  /* ───────────────────────── quiet chips ───────────────────────── */
  function MilestoneChip({ milestoneId, milestones, align, onChange }) {
    const cur = milestones.find((m) => m.id === milestoneId);
    const name = cur ? cur.title : '';
    return (
      <Popover align={align} trigger={({ toggle }) => (
        <button type="button" className={'ptd-qchip' + (name ? '' : ' empty')} onClick={toggle}
          title={name ? 'Milestone: ' + name : 'Assign milestone'}>
          <span className={'ptd-qg dia' + (name ? '' : ' empty')} />
          <span className="ptd-qtxt">{name || 'Milestone'}</span>
        </button>
      )}>
        {({ close }) => (
          <React.Fragment>
            <div className="ptd-pl">Milestone</div>
            <button type="button" className={'ptd-pi' + (!milestoneId ? ' on' : '')} onClick={() => { onChange(''); close(); }}>
              <span className="ptd-pg dia" />No milestone<Icon name="check" size={14} className="ptd-pcheck" />
            </button>
            {milestoneId && !cur && (
              <button type="button" className="ptd-pi on" onClick={() => close()}>
                <span className="ptd-pg dia" style={{ background: 'var(--neg)' }} />Missing milestone<Icon name="check" size={14} className="ptd-pcheck" />
              </button>
            )}
            {milestones.length > 0 && <div className="ptd-psep" />}
            {milestones.map((m) => (
              <button key={m.id} type="button" className={'ptd-pi' + (m.id === milestoneId ? ' on' : '')} onClick={() => { onChange(m.id); close(); }}>
                <span className="ptd-pg dia" style={{ background: 'var(--primary)' }} />{m.title}
                <Icon name="check" size={14} className="ptd-pcheck" />
              </button>
            ))}
          </React.Fragment>
        )}
      </Popover>
    );
  }

  function ClusterChip({ categoryId, categories, align, onMove }) {
    const cur = categories.find((c) => c.id === categoryId);
    return (
      <Popover align={align} trigger={({ toggle }) => (
        <button type="button" className="ptd-qchip" onClick={toggle} title={'Cluster: ' + (cur ? cur.name : '—')}>
          <span className="ptd-qg" /><span className="ptd-qtxt">{cur ? cur.name : '—'}</span>
        </button>
      )}>
        {({ close }) => (
          <React.Fragment>
            <div className="ptd-pl">Move to cluster</div>
            {categories.map((c) => (
              <button key={c.id} type="button" className={'ptd-pi' + (c.id === categoryId ? ' on' : '')} onClick={() => { onMove(c.id); close(); }}>
                <span className="ptd-pg" />{c.name}<Icon name="check" size={14} className="ptd-pcheck" />
              </button>
            ))}
          </React.Fragment>
        )}
      </Popover>
    );
  }

  /* ───────────────────────── inline title editor ───────────────────────── */
  function InlineTitle({ value, autoFocus, onCommit }) {
    const [editing, setEditing] = useState(!!autoFocus);
    const [val, setVal] = useState(value);
    const ref = useRef(null);
    useEffect(() => { setVal(value); }, [value]);
    useEffect(() => { if (editing && ref.current) { ref.current.focus(); ref.current.select(); } }, [editing]);
    const commit = () => { setEditing(false); if (val.trim() !== value) onCommit(val.trim()); };
    if (editing) {
      return <input ref={ref} className="ptd-ti" value={val} onClick={(e) => e.stopPropagation()}
        onChange={(e) => setVal(e.target.value)} onBlur={commit}
        onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); commit(); } if (e.key === 'Escape') { setVal(value); setEditing(false); } }} />;
    }
    return <span className="ptd-titext" onClick={(e) => { e.stopPropagation(); setEditing(true); }}>
      {value || <span className="ptd-faint">Untitled todo</span>}
    </span>;
  }

  /* ───────────────────────── the two-line card ───────────────────────── */
  function TodoCard({ project, todo, catId, categories, milestones, statuses, onAction, onOpen, onPatch, onMove }) {
    const done = isDoneStatus(todo.status);
    const after = todo.after;
    const [docOver, setDocOver] = useState(false);
    const docTarget = { todoId: todo.id };
    const docs = window.projectDocumentsFor ? window.projectDocumentsFor(project, docTarget) : [];
    const dropProps = window.projectDocumentDropProps ? window.projectDocumentDropProps(project, onAction, docTarget, setDocOver) : {};
    return (
      <div className={'ptd-card' + (done ? ' done' : '') + (docOver ? ' doc-over' : '')} tabIndex={0} role="button"
        {...dropProps}
        onClick={() => onOpen()} onKeyDown={(e) => { if (e.key === 'Enter') onOpen(); }}>
        <div className="ptd-card-top">
          <span className="ptd-ttl" onClick={(e) => e.stopPropagation()}>
            <InlineTitle value={todo.title} autoFocus={todo._isNew} onCommit={(v) => onPatch({ title: v })} />
          </span>
          <span className="ptd-status-wrap" onClick={(e) => e.stopPropagation()}>
            <StatusPill status={todo.status} statuses={statuses} onChange={(s) => onPatch({ status: s, done: isDoneStatus(s) })} />
          </span>
        </div>
        <div className="ptd-card-meta" onClick={(e) => e.stopPropagation()}>
          <span className="ptd-qchip" title={'Category: ' + categoryLabel(todo.category)}>
            <Icon name="tag" size={12} />
            <span className="ptd-qtxt">{categoryLabel(todo.category)}</span>
          </span>
          <span className="ptd-vsep" />
          <MilestoneChip milestoneId={todo.milestoneId} milestones={milestones} onChange={(v) => onPatch({ milestoneId: v })} />
          <span className="ptd-vsep" />
          <ClusterChip categoryId={catId} categories={categories} onMove={(toId) => onMove(toId)} />
          {todo.remarks ? (
            <button type="button" className="ptd-note" title="Has remarks — click card to edit" onClick={(e) => { e.stopPropagation(); onOpen(); }}>
              <Icon name="doc" size={12} /><span>{todo.remarks}</span>
            </button>
          ) : null}
          {after ? (
            <button type="button" className="ptd-note" title={'After: ' + after} onClick={(e) => { e.stopPropagation(); onOpen(); }}>
              <Icon name="link" size={12} /><span>After {after}</span>
            </button>
          ) : null}
          {docs.map((doc) => (
            <button key={doc.id} type="button" className="ptd-note" title={doc.fileName} onClick={(e) => { e.stopPropagation(); onAction('openProjectDocument', { projectId: project.id, documentId: doc.id }); }}>
              <Icon name="doc" size={12} /><span>{doc.fileName}</span>
            </button>
          ))}
        </div>
      </div>
    );
  }

  /* ───────────────────────── group section ───────────────────────── */
  function Group({ project, group, groupBy, categories, milestones, statuses, onAction,
                   onRenameCluster, onRemoveCluster, onAddTodo, onPatchTodo, onMoveTodo, onOpen }) {
    const [open, setOpen] = useState(true);
    const [renaming, setRenaming] = useState(false);
    const items = group.items;
    const doneCount = items.filter((i) => isDoneStatus(i.status)).length;
    const pct = items.length ? doneCount / items.length : 0;
    const allDone = items.length > 0 && doneCount === items.length;
    const isCluster = groupBy === 'cluster';
    const canDeleteCluster = isCluster && group.catId && items.length === 0;

    return (
      <div className="ptd-group">
        <div className="ptd-ghead" onClick={() => setOpen((o) => !o)}>
          <Icon name="chevR" size={13} className={'ptd-chev' + (open ? ' open' : '')} />
          {isCluster && group.catId ? (
            <strong className="ptd-gname" onClick={(e) => e.stopPropagation()}>
              {renaming
                ? <InlineTitle value={group.name} autoFocus onCommit={(v) => { setRenaming(false); onRenameCluster(group.catId, v); }} />
                : <span className="ptd-titext" onDoubleClick={() => setRenaming(true)}>{group.name || <span className="ptd-faint">Untitled cluster</span>}</span>}
            </strong>
          ) : (
            <span className={'ptd-gname' + (group.ghost ? ' ghosted' : '')}>{group.name}</span>
          )}
          <span className="ptd-gcount">{doneCount}/{items.length}</span>
          <span className="ptd-gbar"><span style={{ width: (pct * 100) + '%', background: allDone ? 'var(--pos)' : 'var(--primary)' }} /></span>
          <span className="ptd-gspace" />
          <span className="ptd-gtools" onClick={(e) => e.stopPropagation()}>
            {isCluster && group.catId && (
              <React.Fragment>
                <button className="icon-btn" title="Rename cluster" onClick={() => setRenaming(true)}><Icon name="edit" size={14} /></button>
                <button className="icon-btn" title={canDeleteCluster ? 'Delete empty cluster' : 'Only empty clusters can be deleted'} disabled={!canDeleteCluster} onClick={() => onRemoveCluster(group.catId)}><Icon name="trash" size={14} /></button>
              </React.Fragment>
            )}
            {isCluster && group.catId && (
              <button className="icon-btn" title="Add todo" onClick={() => { if (!open) setOpen(true); onAddTodo(group.catId); }}><Icon name="plus" size={15} /></button>
            )}
          </span>
        </div>
        {open && (
          <div className="ptd-cards">
            {items.map((t) => (
              <TodoCard key={t.id} project={project} todo={t} catId={t._catId} categories={categories} milestones={milestones} statuses={statuses} onAction={onAction}
                onOpen={() => onOpen(t._catId, t.id)}
                onPatch={(patch) => onPatchTodo(t._catId, t.id, patch)}
                onMove={(toId) => onMoveTodo(t._catId, t.id, toId)} />
            ))}
            {items.length === 0 && <div className="ptd-empty">No todos here yet.</div>}
            {isCluster && group.catId && (
              <button type="button" className="ptd-add" onClick={() => onAddTodo(group.catId)}><Icon name="plus" size={14} />Add todo</button>
            )}
          </div>
        )}
      </div>
    );
  }

  /* ───────────────────────── the panel ───────────────────────── */
  function ProjectTodosPanel({ project, categories, milestones, statuses, onAction,
                               onRenameCluster, onAddCluster, onRemoveCluster,
                               onAddTodo, onPatchTodo, onMoveTodo, onMoveTodoStatus, onRemoveTodo }) {
    const [groupBy, setGroupBy] = useState('cluster');
    const [editing, setEditing] = useState(null);    // { catId, todoId }
    const cats = categories || [];
    const ms = milestones || [];
    const sts = statuses || Object.keys(STATUS_META);

    // flatten with provenance so we can regroup by milestone
    const flat = [];
    cats.forEach((c) => (c.items || []).forEach((it) => flat.push({ ...it, _catId: c.id })));
    const total = flat.length;
    const done = flat.filter((i) => isDoneStatus(i.status)).length;

    let groups;
    if (groupBy === 'milestone') {
      groups = ms.map((m) => ({ key: m.id, name: m.title, items: flat.filter((t) => t.milestoneId === m.id) }));
      const none = flat.filter((t) => !t.milestoneId || !ms.some((m) => m.id === t.milestoneId));
      if (none.length) groups.push({ key: '_none', name: 'No milestone', ghost: true, items: none });
    } else {
      groups = cats.map((c) => ({ key: c.id, catId: c.id, name: c.name, items: flat.filter((t) => t._catId === c.id) }));
    }

    const firstCatId = cats[0] && cats[0].id;
    const groupWord = groupBy === 'cluster' ? 'cluster' : 'milestone';
    const TodoEditDrawer = window.ProjectTodoEditDrawer;

    const header = (
      <div className="ptd-header-actions" style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
        <div className="ptd-groupby">
          <span className="ptd-gl">Group</span>
          <div className="segmented">
            <button className={groupBy === 'cluster' ? 'on' : ''} onClick={() => setGroupBy('cluster')}>
              <span className="ptd-glyph" style={{ marginRight: 5 }} />Cluster
            </button>
            <button className={groupBy === 'milestone' ? 'on' : ''} onClick={() => setGroupBy('milestone')}>
              <span className="ptd-glyph dia" style={{ marginRight: 5 }} />Milestone
            </button>
          </div>
        </div>
        <button className="btn sm ghost" onClick={onAddCluster}><Icon name="plus" size={14} />Cluster</button>
        <button className="btn sm" onClick={() => firstCatId && onAddTodo(firstCatId)} disabled={!firstCatId}><Icon name="plus" size={14} />Todo</button>
      </div>
    );

    return (
      <Panel title="Todos" sub={`${done}/${total} done · grouped by ${groupWord}`} actions={header}>
        <div className="ptd">
          {groups.map((g) => (
            <Group key={g.key} project={project} group={g} groupBy={groupBy} categories={cats} milestones={ms} statuses={sts} onAction={onAction}
              onRenameCluster={onRenameCluster} onRemoveCluster={onRemoveCluster}
              onAddTodo={onAddTodo} onPatchTodo={onPatchTodo} onMoveTodo={onMoveTodo}
              onOpen={(catId, todoId) => setEditing({ catId, todoId })} />
          ))}
          {cats.length === 0 && (
            <div className="ptd-empty" style={{ padding: '10px 4px' }}>
              No clusters yet. <button type="button" className="ptd-add" style={{ display: 'inline-flex', width: 'auto', padding: '2px 6px' }} onClick={onAddCluster}><Icon name="plus" size={13} />Add a cluster</button>
            </div>
          )}
        </div>
        {TodoEditDrawer && (
          <TodoEditDrawer project={project} target={editing} categories={cats} milestones={ms} statuses={sts} onAction={onAction}
            onClose={() => setEditing(null)} onSave={onPatchTodo} onMove={onMoveTodoStatus || onMoveTodo} onRemove={onRemoveTodo} />
        )}
      </Panel>
    );
  }

  Object.assign(window, { ProjectTodosPanel, PROJECT_TODO_STATUS_META: STATUS_META });
})();
