// Views — Pipeline (default home), Sessions, Console, Drawer.
// Wired to real API via window.LHData (data.jsx).

const { useState, useMemo, useEffect, useRef, useCallback } = React;
const { PROJECTS, fetchIntake, fetchTasks, fetchSessions, fetchTmuxSessions, fetchAllQueue, usePolling, patchSession } = window.LHData;

// ---- Shared helpers ----
function StageTag(props) {
  const labels = {
    idea: "Idea", dispatched: "Dispatched", working: "Working", done: "Done", blocked: "Blocked",
    pending: "Pending", claimed: "Claimed", sent: "Sent", cancelled: "Cancelled",
    idle: "Idle", silent: "Silent", stuck: "Stuck",
  };
  return <span className={"stage stage-" + props.s}>{labels[props.s] || props.s}</span>;
}
function Row(props) { return <div className="row" onClick={props.onClick}>{props.children}</div>; }

// project short-name lookup
function projShort(id) {
  const p = PROJECTS.find(p => p.id === id);
  return p?.short || (id || "").replace(/^\d+_/, "");
}

// Format timestamps for display
function timeAgo(dateStr) {
  if (!dateStr) return "";
  const d = new Date(dateStr);
  if (isNaN(d.getTime())) return dateStr; // already formatted
  const now = Date.now();
  const diff = now - d.getTime();
  if (diff < 0) return "just now";
  const secs = Math.floor(diff / 1000);
  if (secs < 60) return secs <= 5 ? "just now" : `${secs}s ago`;
  const mins = Math.floor(secs / 60);
  if (mins < 60) return `${mins}m ago`;
  const hours = Math.floor(mins / 60);
  if (hours < 24) return `${hours}h ago`;
  const days = Math.floor(hours / 24);
  return `${days}d ago`;
}

window.StageTag = StageTag;
window.Row = Row;

// =============== Sidebar / shell ===============
function Sidebar({ route, setRoute, filterProj, onProjectClick, intake, tasks, sessions, queue }) {
  const intakeList = intake || [];
  const tasksList = tasks || [];
  const sessionsList = sessions || [];
  const queueList = queue || [];

  const intakeOpen = intakeList.filter(i => i.stage === "idea").length;
  const tasksLive = tasksList.filter(t => ["dispatched","working","blocked"].includes(t.stage)).length;
  const queuePending = queueList.filter(q => q.status === "pending" || q.status === "sent").length;
  const sesLive = sessionsList.filter(s => s.health === "working").length;

  const items = [
    { id: "pipeline",  label: "Pipeline",  icon: <I.Task/>,    count: tasksLive },
    { id: "intake",    label: "Intake",    icon: <I.Inbox/>,   count: intakeOpen },
    { id: "tasks",     label: "Tasks",     icon: <I.Task/>,    count: tasksLive },
    { id: "queue",     label: "Queue",     icon: <I.Queue/>,   count: queuePending },
    { id: "sessions",  label: "Sessions",  icon: <I.Sessions/>,count: sesLive },
    { id: "console",   label: "Console",   icon: <I.Console/>, count: null },
  ];

  return (
    <nav className="nav">
      <div className="brand">
        <div className="brand-mark"></div>
        <div>
          <div className="brand-name">Lighthouse</div>
          <div className="brand-sub">always-on agent</div>
        </div>
      </div>

      <div className="nav-section">
        <div className="nav-section-label">Workspace</div>
        {items.map(it => (
          <button key={it.id}
            className={"nav-item" + (route === it.id ? " active" : "")}
            onClick={() => setRoute(it.id)}>
            <span className="ico">{it.icon}</span>
            <span>{it.label}</span>
            {it.count !== null && <span className="count">{it.count}</span>}
          </button>
        ))}
      </div>

      <div className="nav-section">
        <div className="nav-section-label">Projects</div>
        {PROJECTS.map(p => (
          <button key={p.id}
            className={"nav-item" + (filterProj === p.id && route === "pipeline" ? " active" : "")}
            onClick={() => onProjectClick && onProjectClick(p.id)}
            title={`Pipeline - filter: ${p.id}`}>
            <span className="ico" style={{
              width: 8, height: 8, borderRadius: 2,
              background: `oklch(0.65 0.12 ${p.color})`
            }}></span>
            <span style={{fontFamily: "var(--mono)", fontSize: 11.5}}>{p.short}</span>
          </button>
        ))}
      </div>

      <div className="nav-foot">
        <span className="health-dot"></span>
        <div>
          <div style={{fontSize: 11.5}}>All systems online</div>
          <div className="meta">db - monitor - stop-hook</div>
        </div>
      </div>
    </nav>
  );
}

// =============== Topbar ===============
// /next-go is dispatched per-project from Sessions Health (one Send button
// per project group). The old global trigger button used to live here but
// was removed once per-project triggers shipped.
function Topbar({ route, nextGoStatus }) {
  const labels = {
    pipeline: ["Workspace", "Pipeline"],
    intake:   ["Workspace", "Intake"],
    tasks:    ["Workspace", "Tasks"],
    queue:    ["Workspace", "Queue"],
    sessions: ["Workspace", "Sessions"],
    console:  ["Workspace", "Console"],
  }[route] || ["Workspace", route];

  return (
    <div className="topbar">
      <div className="crumbs">
        <span>{labels[0]}</span>
        <span className="sep">/</span>
        <span className="here">{labels[1]}</span>
      </div>
      <div className="topbar-spacer"></div>
      <div className="search">
        <I.Search/>
        <input placeholder="Find intake, task, session..." />
        <span className="kbd">&#8984;K</span>
      </div>
      {nextGoStatus && <span style={{fontFamily:"var(--mono)", fontSize:11, color:"var(--text-3)"}}>{nextGoStatus}</span>}
    </div>
  );
}

// =============== PreviewRow ===============
function PreviewRow({ id, title, meta, stage, onClick }) {
  return (
    <div className="prev-row" onClick={onClick}>
      <div className="prev-row-main">
        <div className="prev-row-title">{title}</div>
        <div className="prev-row-meta">
          {meta}
        </div>
      </div>
      {stage && <StageTag s={stage}/>}
    </div>
  );
}

// =============== ProjectFilter ===============
function ProjectFilter({ value, onChange, intake, tasks, sessions, queue }) {
  const [open, setOpen] = useState(false);
  const ref = useRef(null);

  useEffect(() => {
    if (!open) return;
    const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener("mousedown", onDoc);
    return () => document.removeEventListener("mousedown", onDoc);
  }, [open]);

  const intakeList = intake || [];
  const tasksList = tasks || [];
  const queueList = queue || [];
  const sessionsList = sessions || [];

  const counts = useMemo(() => {
    const sessProj = Object.fromEntries(sessionsList.map(s => [s.id, s.project]));
    const c = { __all: 0 };
    const bump = (proj) => { if (!proj) return; c[proj] = (c[proj] || 0) + 1; c.__all++; };
    intakeList.filter(i => i.stage === "idea").forEach(i => bump(i.project));
    tasksList.filter(t => ["dispatched","working","blocked"].includes(t.stage)).forEach(t => bump(t.project));
    queueList.filter(q => q.status === "pending" || q.status === "sent").forEach(q => bump(sessProj[q.session_id]));
    return c;
  }, [intakeList, tasksList, queueList, sessionsList]);

  const current = PROJECTS.find(p => p.id === value);
  const label = value === "__all" || !value ? "All projects" : (current?.short || value);

  return (
    <div className="proj-filter" ref={ref}>
      <button className="btn btn-ghost" onClick={() => setOpen(o => !o)} aria-expanded={open}>
        <span className="proj-filter-label">Project:</span>
        <span className="proj-filter-value">{label}</span>
        <I.Chev/>
      </button>
      {open && (
        <div className="proj-filter-menu" role="listbox">
          <button
            className={"proj-filter-item" + ((!value || value === "__all") ? " active" : "")}
            onClick={() => { onChange("__all"); setOpen(false); }}
          >
            <span className="proj-filter-dot" style={{background: "var(--text-3)"}}></span>
            <span className="proj-filter-name">All projects</span>
            <span className="proj-filter-count">{counts.__all}</span>
          </button>
          <div className="proj-filter-sep"></div>
          {PROJECTS.map(p => (
            <button
              key={p.id}
              className={"proj-filter-item" + (value === p.id ? " active" : "")}
              onClick={() => { onChange(p.id); setOpen(false); }}
            >
              <span className="proj-filter-dot" style={{background: `oklch(0.78 0.13 ${p.color})`}}></span>
              <span className="proj-filter-name">{p.short}</span>
              <span className="proj-filter-id">{p.id}</span>
              <span className="proj-filter-count">{counts[p.id] || 0}</span>
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

// =============== QuickCapture ===============
function QuickCapture({ filterProj, project, setProject, draft, setDraft, onSubmit, onClose }) {
  const [localProj, setLocalProj] = useState(() =>
    (filterProj && filterProj !== "__all") ? filterProj : project
  );
  useEffect(() => {
    if (filterProj && filterProj !== "__all") setLocalProj(filterProj);
  }, [filterProj]);

  const PickFromExtras = window.LHViewsExtra?.ProjectPick;
  const onPickChange = (id) => {
    setLocalProj(id);
    setProject && setProject(id);
  };

  const submit = () => {
    if (!draft.trim()) return;
    if (setProject) setProject(localProj);
    setTimeout(() => onSubmit && onSubmit(), 0);
  };

  const taRef = useRef(null);
  useEffect(() => {
    const el = taRef.current;
    if (!el) return;
    if (el.scrollHeight > el.clientHeight) {
      el.style.height = Math.min(el.scrollHeight, 320) + "px";
    }
  }, [draft]);

  return (
    <div className="quick-capture">
      <textarea
        ref={taRef}
        className="quick-capture-input"
        rows={1}
        autoFocus
        value={draft}
        placeholder={
          (filterProj && filterProj !== "__all")
            ? `Capture into ${filterProj} -- Enter to add, Esc to cancel`
            : `Capture into ${localProj} -- Enter to add, Esc to cancel`
        }
        onChange={e => setDraft(e.target.value)}
        onKeyDown={e => {
          if (e.key === "Enter" && !e.shiftKey && !e.isComposing && draft.trim()) {
            e.preventDefault();
            submit();
          } else if (e.key === "Escape") {
            e.preventDefault();
            onClose && onClose();
          }
        }}
      />
      <div className="quick-capture-foot">
        <span className="quick-capture-target" title="Project this idea will land in">&rarr; {localProj}</span>
        <div style={{display:"flex", gap:6}}>
          {onClose && (
            <button className="btn btn-ghost btn-sm" onClick={onClose}>Cancel</button>
          )}
          <button
            className="btn btn-accent btn-sm"
            onClick={submit}
            disabled={!draft.trim()}
          >Add <span className="kbd">&crarr;</span></button>
        </div>
      </div>
    </div>
  );
}

// =============== Pipeline columns (with real data) ===============
function IntakeColumn({ onSelect, filterProj, setRoute, selectedId, focusKey, project, setProject, draft, setDraft, onSubmit, intake }) {
  const allIntake = intake || [];
  let open = allIntake.filter(i => i.stage === "idea");
  if (filterProj && filterProj !== "__all") open = open.filter(i => i.project === filterProj);
  const shown = open.slice(0, 4);
  const [captureOpen, setCaptureOpen] = useState(false);
  const wrappedSubmit = onSubmit ? () => {
    onSubmit();
    setCaptureOpen(false);
  } : null;
  return (
    <div className="col col-overview" data-col="intake">
      <div className="col-head col-head-clickable" onClick={() => {
        if (focusKey && focusKey !== "intake") { onSelect && onSelect(null); }
        else { setRoute && setRoute("intake"); }
      }} title={focusKey && focusKey !== "intake" ? "Expand pipeline" : "Open Intake page"}>
        <span className="col-title">Intake - ideas</span>
        <span className="col-count">{open.length}</span>
      </div>
      <div className="col-body">
        {shown.length === 0 && <div className="empty">no open ideas</div>}
        {shown.map(i => (
          <PreviewRow key={i.id}
            active={selectedId === i.id}
            onClick={() => onSelect(i)}
            title={(i.raw_text || "").replace(/^##\s*/, "")}
            meta={<>
              <span className="proj-tag">{projShort(i.project)}</span>
              <span className="dim">{timeAgo(i.created_at)}</span>
              {i.analysis && <span className="dim">- analysed</span>}
            </>}
          />
        ))}
      </div>
      {onSubmit && !captureOpen && (
        <button className="col-add" onClick={() => setCaptureOpen(true)}>
          <I.Plus/> Add intake
        </button>
      )}
      {onSubmit && captureOpen && (
        <QuickCapture
          filterProj={filterProj}
          project={project}
          setProject={setProject}
          draft={draft}
          setDraft={setDraft}
          onSubmit={wrappedSubmit}
          onClose={() => setCaptureOpen(false)}
        />
      )}
    </div>
  );
}

function TasksColumn({ onSelect, filterProj, setRoute, selectedId, focusKey, tasks }) {
  const allTasks = tasks || [];
  let live = allTasks.filter(t => ["dispatched","working","blocked"].includes(t.stage));
  if (filterProj && filterProj !== "__all") live = live.filter(t => t.project === filterProj);
  const shown = live.slice(0, 4);
  const more = live.length - shown.length;
  return (
    <div className="col col-overview" data-col="tasks">
      <div className="col-head col-head-clickable" onClick={() => {
        if (focusKey && focusKey !== "tasks") { onSelect && onSelect(null); }
        else { setRoute && setRoute("tasks"); }
      }} title={focusKey && focusKey !== "tasks" ? "Expand pipeline" : "Open Tasks page"}>
        <span className="col-title">Tasks - live</span>
        <span className="col-count">{live.length}</span>
      </div>
      <div className="col-body">
        {shown.length === 0 && <div className="empty">no live tasks</div>}
        {shown.map(t => (
          <PreviewRow key={t.id}
            active={selectedId === t.id}
            onClick={() => onSelect(t)}
            stage={t.stage}
            title={t.title}
            meta={<>
              <span className="proj-tag">{projShort(t.project)}</span>
              {t.priority && <span className="dim warn">{t.priority}</span>}
              <span className="dim">{timeAgo(t.updated_at)}</span>
            </>}
          />
        ))}
      </div>
      {more > 0 && setRoute && (
        <button className="col-more" onClick={() => setRoute("tasks")}>+ {more} more in Tasks &rarr;</button>
      )}
    </div>
  );
}

function QueueColumn({ onSelect, filterProj, setRoute, selectedId, focusKey, queue, sessions }) {
  const allQueue = queue || [];
  const sessionsList = sessions || [];
  const sessionsById = Object.fromEntries(sessionsList.map(s => [s.id, s]));
  let live = allQueue.filter(q => q.status === "pending" || q.status === "sent");
  if (filterProj && filterProj !== "__all") {
    live = live.filter(q => sessionsById[q.session_id]?.project === filterProj);
  }
  const shown = live.slice(0, 4);
  const more = live.length - shown.length;
  return (
    <div className="col col-overview" data-col="queue">
      <div className="col-head col-head-clickable" onClick={() => {
        if (focusKey && focusKey !== "queue") { onSelect && onSelect(null); }
        else { setRoute && setRoute("queue"); }
      }} title={focusKey && focusKey !== "queue" ? "Expand pipeline" : "Open Queue page"}>
        <span className="col-title">Queue - dispatched</span>
        <span className="col-count">{live.length}</span>
      </div>
      <div className="col-body">
        {shown.length === 0 && <div className="empty">queue empty</div>}
        {shown.map(q => {
          const s = sessionsById[q.session_id];
          return (
            <PreviewRow key={q.id}
              active={selectedId === q.id}
              onClick={() => onSelect(q)}
              stage={q.status === "sent" ? "working" : "dispatched"}
              title={q.text}
              meta={<>
                <span className="proj-tag">&rarr; {s?.tmux_name ?? q.session_id}</span>
                <span className="dim">pos {q.position}</span>
                <span className="dim">{timeAgo(q.created_at)}</span>
              </>}
            />
          );
        })}
      </div>
      {more > 0 && setRoute && (
        <button className="col-more" onClick={() => setRoute("queue")}>+ {more} more in Queue &rarr;</button>
      )}
    </div>
  );
}

// =============== Pipeline (home) ===============
function PipelineView({ onSelect, selectedId, project, setProject, draft, setDraft, onSubmit, filterProj, setFilterProj, setRoute, intake, tasks, sessions, queue }) {
  const fp = filterProj ?? "__all";
  const setFp = setFilterProj ?? (() => {});
  const focusKey = selectedId
    ? (selectedId.startsWith("int_") ? "intake"
      : selectedId.startsWith("tsk_") ? "tasks"
      : selectedId.startsWith("qi_")  ? "queue"
      : null)
    : null;
  return (
    <div className="page">
      <div className="page-head">
        <div>
          <div className="page-title">Pipeline</div>
          <div className="page-sub">capture &rarr; idea &rarr; task &rarr; queue - overview only -- drill into a tab for full lists</div>
        </div>
        <div className="page-actions">
          <ProjectFilter value={fp} onChange={setFp} intake={intake} tasks={tasks} sessions={sessions} queue={queue}/>
        </div>
      </div>
      <div className={"pipeline pipeline-overview" + (focusKey ? " has-focus" : "")} data-focus={focusKey || ""}>
        <IntakeColumn onSelect={onSelect} filterProj={fp} setRoute={setRoute} selectedId={selectedId} focusKey={focusKey}
                      project={project} setProject={setProject} draft={draft} setDraft={setDraft} onSubmit={onSubmit} intake={intake}/>
        <TasksColumn onSelect={onSelect} filterProj={fp} setRoute={setRoute} selectedId={selectedId} focusKey={focusKey} tasks={tasks}/>
        <QueueColumn onSelect={onSelect} filterProj={fp} setRoute={setRoute} selectedId={selectedId} focusKey={focusKey} queue={queue} sessions={sessions}/>
      </div>
    </div>
  );
}

// =============== Sessions ===============
function SessionsView({ onSelect, onOpenConsole, filterProj, setFilterProj, sessions, tmuxSessions }) {
  const sessionsList = sessions || [];
  const tmuxList = tmuxSessions || [];

  // Merge: SQLite sessions are primary, add tmux-only sessions at end
  const mergedSessions = useMemo(() => {
    const sqliteNames = new Set(sessionsList.map(s => s.tmux_name));
    const tmuxOnly = tmuxList
      .filter(t => !sqliteNames.has(t.name))
      .map(t => ({
        id: t.name,
        tmux_name: t.name,
        project: t.name.replace(/-\d+$/, ""),
        health: "idle",
        role: null,
        last_active: null,
        updated_at: null,
        displayName: t.displayName || null,
      }));
    return [...sessionsList, ...tmuxOnly];
  }, [sessionsList, tmuxList]);

  // dmByProject: { project: session_id }
  const [dmByProject, setDmByProject] = useState({});

  // Initialize DM from session data
  useEffect(() => {
    const seed = {};
    for (const s of mergedSessions) {
      if (s.role === "dispatch-manager") seed[s.project] = s.id;
    }
    setDmByProject(prev => {
      // Only set if empty (don't override user selections)
      const merged = { ...seed };
      for (const [k, v] of Object.entries(prev)) {
        if (v) merged[k] = v;
      }
      return merged;
    });
  }, [mergedSessions]);

  const [collapsedProjects, setCollapsedProjects] = useState(() => {
    try { return JSON.parse(localStorage.getItem("lh.sessions.collapsed") || "{}"); }
    catch { return {}; }
  });
  useEffect(() => {
    try { localStorage.setItem("lh.sessions.collapsed", JSON.stringify(collapsedProjects)); } catch {}
  }, [collapsedProjects]);
  const toggleCollapse = (proj) => {
    setCollapsedProjects(prev => ({ ...prev, [proj]: !prev[proj] }));
  };

  const groups = useMemo(() => {
    const map = new Map();
    for (const s of mergedSessions) {
      if (filterProj && filterProj !== "__all" && s.project !== filterProj) continue;
      if (!map.has(s.project)) map.set(s.project, []);
      map.get(s.project).push(s);
    }
    return [...map.entries()];
  }, [filterProj, mergedSessions]);

  const setDM = async (project, sessionId, e) => {
    e.stopPropagation();
    setDmByProject(prev => ({ ...prev, [project]: sessionId }));
    try {
      await patchSession(sessionId, { role: "dispatch-manager" });
    } catch (err) {
      console.warn("Failed to set DM:", err);
    }
  };

  return (
    <div className="page">
      <div className="page-head">
        <div>
          <div className="page-title">Sessions</div>
          <div className="page-sub">tmux x project - live health - designated dispatch managers receive <span className="kbd-inline">/next-go</span></div>
        </div>
        <div className="page-actions">
          {setFilterProj && <ProjectFilter value={filterProj} onChange={setFilterProj} sessions={mergedSessions}/>}
          <button className="btn btn-ghost"><I.Plus/> Spawn session</button>
        </div>
      </div>
      <div className="sessions-grid">
        {groups.map(([proj, list]) => {
          const dmId = dmByProject[proj];
          const dmSession = list.find(s => s.id === dmId);
          const isCollapsed = !!collapsedProjects[proj];
          return (
            <div className={"proj-group" + (isCollapsed ? " collapsed" : "")} key={proj}>
              <div className="proj-group-head" onClick={() => toggleCollapse(proj)}>
                <button
                  className="proj-group-chevron"
                  aria-label={isCollapsed ? "Expand" : "Collapse"}
                  onClick={(e) => { e.stopPropagation(); toggleCollapse(proj); }}
                >
                  <svg width="10" height="10" viewBox="0 0 10 10" fill="none">
                    <path d="M3 2L7 5L3 8" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
                  </svg>
                </button>
                <span className="proj-group-name">{proj}</span>
                <span className="proj-group-count">{list.length}</span>
                <div className="proj-group-actions" onClick={(e) => e.stopPropagation()}>
                  <div className="dm-target">
                    <span className="dm-target-label">/next-go &rarr;</span>
                    {dmSession
                      ? <span className="dm-target-name mono">{dmSession.tmux_name}</span>
                      : <span className="dm-target-empty">no DM set</span>}
                  </div>
                  <button className="btn btn-accent btn-sm" disabled={!dmSession}>
                    <I.Play/> Send
                  </button>
                </div>
              </div>
              {!isCollapsed && list.slice().sort((a, b) => {
                if (a.id === dmId) return -1;
                if (b.id === dmId) return 1;
                return 0;
              }).map(s => {
                const isDM = s.id === dmId;
                return (
                  <div className={"ses-row" + (isDM ? " ses-row-dm" : "")} key={s.id} onClick={() => onSelect(s)}>
                    <div>
                      <div className="ses-name">
                        {isDM && <span className="dm-badge" title="Dispatch manager -- receives /next-go">DM</span>}
                        {s.tmux_name}
                      </div>
                      {s.displayName && <div className="ses-display">"{s.displayName}"</div>}
                    </div>
                    <span className="ses-id">{s.id}</span>
                    <span className="ses-health">
                      <StageTag s={s.health}/>
                    </span>
                    <div className="ses-actions">
                      <span className="ses-when">{timeAgo(s.last_active)}</span>
                      {!isDM ? (
                        <button
                          className="set-dm-btn"
                          title="Set as dispatch manager -- receives /next-go"
                          onClick={(e) => setDM(proj, s.id, e)}
                        >set DM</button>
                      ) : (
                        <span className="set-dm-slot" aria-hidden="true"></span>
                      )}
                      <button
                        className="icon-btn"
                        title="Open in Console"
                        onClick={(e) => { e.stopPropagation(); onOpenConsole && onOpenConsole(s.id); }}
                      ><I.Console/></button>
                      <button className="icon-btn" onClick={(e) => e.stopPropagation()}><I.More/></button>
                    </div>
                  </div>
                );
              })}
            </div>
          );
        })}
      </div>
    </div>
  );
}

// =============== Drawer ===============
function Drawer({ item, onClose, onSelect }) {
  if (!item) return <div className="drawer"></div>;
  const isTask = item.id?.startsWith("tsk_");
  const isIntake = item.id?.startsWith("int_");
  const isSession = item.id?.startsWith("ses_");
  const isQueue = item.id?.startsWith("qi_");

  return (
    <div className="drawer open">
      <div className="drawer-inner">
        <div className="drawer-head">
          <div>
            <div className="drawer-id">{item.id}</div>
            <div className="drawer-title">
              {isIntake && (item.raw_text || "").replace(/^##\s*/,"")}
              {isTask && item.title}
              {isSession && (item.displayName || item.tmux_name)}
              {isQueue && item.text}
            </div>
          </div>
          <button className="icon-btn" onClick={onClose}><I.X/></button>
        </div>

        <div className="kv">
          {item.project && <><div className="k">Project</div><div className="v mono">{item.project}</div></>}
          {item.stage && <><div className="k">Stage</div><div className="v"><StageTag s={item.stage}/></div></>}
          {item.health && <><div className="k">Health</div><div className="v"><span className={"health-dot " + item.health} style={{display:"inline-block", marginRight:6}}></span>{item.health}</div></>}
          {item.tmux_name && <><div className="k">Tmux</div><div className="v mono">{item.tmux_name}</div></>}
          {item.role && <><div className="k">Role</div><div className="v">{item.role}</div></>}
          {item.intake_id && <><div className="k">From</div><div className="v mono" style={{color:"var(--accent)"}}>{item.intake_id}</div></>}
          {item.session_id && <><div className="k">Session</div><div className="v mono">{item.session_id}</div></>}
          {item.branch && <><div className="k">Branch</div><div className="v mono">{item.branch}</div></>}
          {item.worktree && <><div className="k">Worktree</div><div className="v mono" style={{fontSize:10.5}}>{item.worktree}</div></>}
          {item.depends_on && <><div className="k">Depends on</div><div className="v mono">{item.depends_on}</div></>}
          {item.priority && <><div className="k">Priority</div><div className="v">{item.priority}</div></>}
          {item.note_path && <><div className="k">Note</div><div className="v mono" style={{fontSize:10.5}}>{item.note_path}</div></>}
          {item.created_at && <><div className="k">Created</div><div className="v">{timeAgo(item.created_at)}</div></>}
          {item.updated_at && <><div className="k">Updated</div><div className="v">{timeAgo(item.updated_at)}</div></>}
        </div>

        {isIntake && item.raw_text && (
          <>
            <div style={{fontFamily:"var(--mono)", fontSize:10, color:"var(--text-3)", textTransform:"uppercase", letterSpacing:"0.12em", margin:"22px 0 8px"}}>Raw text</div>
            <div style={{background:"var(--bg)", border:"1px solid var(--line-soft)", borderRadius:6, padding:"10px 12px", fontFamily:"var(--mono)", fontSize:11.5, color:"var(--text-2)", whiteSpace:"pre-wrap"}}>
              {item.raw_text}
            </div>
          </>
        )}
        {item.analysis && (
          <>
            <div style={{fontFamily:"var(--mono)", fontSize:10, color:"var(--text-3)", textTransform:"uppercase", letterSpacing:"0.12em", margin:"18px 0 8px"}}>Analysis</div>
            <div style={{fontSize:12, color:"var(--text-2)"}}>{item.analysis}</div>
          </>
        )}

        <div style={{fontFamily:"var(--mono)", fontSize:10, color:"var(--text-3)", textTransform:"uppercase", letterSpacing:"0.12em", margin:"22px 0 8px"}}>Activity</div>
        <div className="empty">No activity log yet</div>
      </div>
    </div>
  );
}

// =============== Console (per-session with real xterm.js) ===============
function ConsoleView({ sessionId, setSessionId, sessions, tmuxSessions }) {
  const sessionsList = sessions || [];
  const tmuxList = tmuxSessions || [];

  // Merge sessions same as SessionsView
  const mergedSessions = useMemo(() => {
    const sqliteNames = new Set(sessionsList.map(s => s.tmux_name));
    const tmuxOnly = tmuxList
      .filter(t => !sqliteNames.has(t.name))
      .map(t => ({
        id: t.name,
        tmux_name: t.name,
        project: t.name.replace(/-\d+$/, ""),
        health: "idle",
        role: null,
        last_active: null,
        displayName: t.displayName || null,
      }));
    return [...sessionsList, ...tmuxOnly];
  }, [sessionsList, tmuxList]);

  const active = mergedSessions.find(s => s.id === sessionId) || mergedSessions.find(s => s.health === "working") || mergedSessions[0];
  const sid = active?.id;
  const tmuxName = active?.tmux_name;

  // collapsed project groups
  const [collapsedGroups, setCollapsedGroups] = useState(() => {
    try { return JSON.parse(localStorage.getItem("lh.sessions.collapsed") || "{}"); }
    catch { return {}; }
  });
  useEffect(() => {
    try { localStorage.setItem("lh.sessions.collapsed", JSON.stringify(collapsedGroups)); } catch {}
  }, [collapsedGroups]);
  const toggleGroup = (proj) => setCollapsedGroups(p => ({ ...p, [proj]: !p[proj] }));

  // xterm refs
  const termRef = useRef(null);
  const termContainerRef = useRef(null);
  const wsRef = useRef(null);
  const fitAddonRef = useRef(null);
  const [inputText, setInputText] = useState("");

  // Initialize terminal once
  useEffect(() => {
    if (!termContainerRef.current || typeof Terminal === "undefined") return;

    const term = new Terminal({
      fontFamily: "'JetBrains Mono', 'SF Mono', Menlo, monospace",
      fontSize: 12,
      theme: {
        background: '#1e1e2e',
        foreground: '#cdd6f4',
        cursor: '#f5e0dc',
        selectionBackground: '#585b70',
      },
      cursorBlink: true,
      scrollback: 5000,
      convertEol: true,
    });

    const fitAddon = new FitAddon.FitAddon();
    term.loadAddon(fitAddon);
    term.open(termContainerRef.current);

    // Slight delay to ensure container has dimensions
    setTimeout(() => {
      try { fitAddon.fit(); } catch {}
    }, 50);

    termRef.current = term;
    fitAddonRef.current = fitAddon;

    const resizeObserver = new ResizeObserver(() => {
      try { fitAddon.fit(); } catch {}
    });
    resizeObserver.observe(termContainerRef.current);

    return () => {
      resizeObserver.disconnect();
      term.dispose();
      termRef.current = null;
    };
  }, []);

  // Connect WebSocket when session changes
  useEffect(() => {
    if (!tmuxName || !termRef.current) return;

    const term = termRef.current;
    term.clear();
    term.writeln(`\x1b[33m--- connecting to ${tmuxName} ---\x1b[0m`);

    // Close existing ws
    if (wsRef.current) {
      wsRef.current.close();
      wsRef.current = null;
    }

    const proto = location.protocol === "https:" ? "wss:" : "ws:";
    const ws = new WebSocket(`${proto}//${location.host}/ws/term?session=${encodeURIComponent(tmuxName)}`);
    wsRef.current = ws;

    ws.onopen = () => {
      term.writeln(`\x1b[32m--- connected ---\x1b[0m`);
      // Fit after connect
      try { fitAddonRef.current?.fit(); } catch {}
    };

    ws.onmessage = (ev) => {
      if (typeof ev.data === "string") {
        term.write(ev.data);
      } else if (ev.data instanceof Blob) {
        ev.data.text().then(text => term.write(text));
      }
    };

    ws.onerror = () => {
      term.writeln(`\x1b[31m--- connection error ---\x1b[0m`);
    };

    ws.onclose = () => {
      term.writeln(`\x1b[33m--- disconnected ---\x1b[0m`);
    };

    // Handle terminal input (user typing in xterm)
    const disposeOnData = term.onData((data) => {
      if (ws.readyState === WebSocket.OPEN) {
        ws.send(JSON.stringify({ type: "data", data }));
      }
    });

    return () => {
      disposeOnData.dispose();
      ws.close();
      wsRef.current = null;
    };
  }, [tmuxName]);

  const sendInput = () => {
    if (!inputText.trim() || !wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) return;
    wsRef.current.send(JSON.stringify({ type: "data", data: inputText + "\n" }));
    setInputText("");
  };

  // Handle scroll in alt-screen
  const handleWheel = useCallback((e) => {
    if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) return;
    const lines = Math.ceil(Math.abs(e.deltaY) / 20);
    const direction = e.deltaY < 0 ? "up" : "down";
    wsRef.current.send(JSON.stringify({ type: "tmux-scroll", direction, lines }));
  }, []);

  return (
    <div className="page">
      <div className="page-head">
        <div>
          <div className="page-title">Console</div>
          <div className="page-sub">per-session - tmux capture-pane - live</div>
        </div>
      </div>
      <div className="console-shell">
        <div className="console-sidebar">
          <div className="console-sidebar-label">Sessions</div>
          {(() => {
            const groups = [];
            const idx = new Map();
            for (const s of mergedSessions) {
              if (!idx.has(s.project)) { idx.set(s.project, groups.length); groups.push([s.project, []]); }
              groups[idx.get(s.project)][1].push(s);
            }
            return groups.map(([proj, list]) => {
              const collapsed = !!collapsedGroups[proj];
              return (
                <div className="console-grp" key={proj}>
                  <button
                    className={"console-grp-head" + (collapsed ? " collapsed" : "")}
                    onClick={() => toggleGroup(proj)}
                  >
                    <svg className="console-grp-chev" width="9" height="9" viewBox="0 0 10 10" fill="none">
                      <path d="M3 2L7 5L3 8" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
                    </svg>
                    <span className="console-grp-name">{proj}</span>
                    <span className="console-grp-count">{list.length}</span>
                  </button>
                  {!collapsed && list.slice().sort((a, b) => {
                    if (a.role === "dispatch-manager") return -1;
                    if (b.role === "dispatch-manager") return 1;
                    return 0;
                  }).map(s => (
                    <button key={s.id}
                      className={"console-ses" + (s.id === sid ? " active" : "")}
                      onClick={() => setSessionId && setSessionId(s.id)}>
                      <span className={"health-dot " + (s.health || "idle")}></span>
                      <div className="console-ses-main">
                        <div className="console-ses-name">{s.tmux_name}</div>
                        <div className="console-ses-meta">{s.role === "dispatch-manager" ? "DM - " : ""}{s.health}</div>
                      </div>
                    </button>
                  ))}
                </div>
              );
            });
          })()}
        </div>
        <div className="console-pane">
          {!active && <div className="empty" style={{flex:1, display:"flex", alignItems:"center", justifyContent:"center"}}>no session selected</div>}
          {active && (
            <>
              <div className="xterm-container" ref={termContainerRef} onWheel={handleWheel}></div>
              <div className="console-input-bar">
                <input
                  value={inputText}
                  onChange={e => setInputText(e.target.value)}
                  onKeyDown={e => { if (e.key === "Enter") { e.preventDefault(); sendInput(); } }}
                  placeholder={`Send to ${tmuxName || "session"}...`}
                />
                <button className="btn btn-accent btn-sm" onClick={sendInput}>Send</button>
              </div>
            </>
          )}
        </div>
      </div>
    </div>
  );
}

window.LHViews = { Sidebar, Topbar, PipelineView, SessionsView, Drawer, ConsoleView, StageTag, Row, ProjectFilter };
window.StageTag = StageTag;
window.Row = Row;
window.ProjectFilter = ProjectFilter;
