// Cockpit CORE — one call gives the operator full situational awareness of the local business:
// live state (channels, unread, control-plane) + the capability catalog (what the terminal can DO).
// Pure read. This is what makes the Dew terminal a cockpit, not just a tool list.

import type { DatabaseSync } from "node:sqlite";
import { selectedWorkspaceId } from "./sync/workspace.ts";

/// Grouped catalog of what the MCP/terminal can operate — the "you can run the whole business" map.
export const CATALOG = {
  "Read the inbox": ["list_chats", "get_messages", "get_media", "search_messages", "inbox_conversations", "inbox_thread", "channels_list", "connected_accounts"],
  "Run the inbox": ["send_message", "new_conversation", "mark_read", "mark_all_read", "archive_conversation", "mute_conversation", "delete_message", "unread_total"],
  "CRM": ["crm_contacts", "crm_companies", "crm_deals", "crm_person", "crm_pending_mutations"],
  "Calendar": ["calendar_events", "calendar_create_event"],
  "Mail (local, no OAuth)": ["mail_accounts", "mail_list", "mail_read", "mail_draft", "mail_send"],
  "Reminders (local)": ["reminder_lists", "reminders_list", "reminder_create"],
  "Contacts (local)": ["contacts_list", "contacts_search"],
  "Notes (local)": ["notes_list", "notes_read", "note_create"],
  "Messages/iMessage (local, read)": ["imessage_chats", "imessage_messages", "imessage_search"],
  "Cloud sync (web)": ["sync_preview"],
  "Identity (one person across channels)": ["suggest_merges", "merge_contacts", "unmerge", "link_identity", "identities_for"],
  "Control plane (brain)": ["ops_board", "ops_project", "ops_order", "ops_answer", "ops_resume", "ops_upsert_project"],
  "Control plane (worker)": ["ops_register", "ops_heartbeat", "ops_report", "ops_inbox", "ops_ack", "ops_ask"],
};

export function cockpit(db: DatabaseSync) {
  const ws = selectedWorkspaceId(db);
  const channels = db.prepare(
    `SELECT channel, COUNT(*) AS conversations, COALESCE(SUM(unread),0) AS unread
     FROM unified_conversation WHERE workspace_id=? AND deleted_at IS NULL AND archived_at IS NULL
     GROUP BY channel ORDER BY conversations DESC`).all(ws);
  const unread = db.prepare(
    `SELECT COALESCE(SUM(unread),0) AS total, COUNT(*) AS chats FROM unified_conversation
     WHERE workspace_id=? AND deleted_at IS NULL AND archived_at IS NULL AND unread>0`).get(ws) as any;

  // Control plane snapshot (ops tables may not exist if P0 never ran — guard).
  let control: any = { projects: 0, tabs_live: 0, tabs_down: 0, open_asks: 0, by_project: [] };
  try {
    const nowMs = Date.now();
    const projects = db.prepare(`SELECT id, slug, name, status FROM project ORDER BY priority ASC`).all() as any[];
    const byProject = projects.map((p) => {
      const tabs = db.prepare(`SELECT interval_sec, heartbeat_at FROM tab WHERE current_project_id=?`).all(p.id) as any[];
      const live = tabs.filter((t) => t.heartbeat_at && (nowMs - Date.parse(t.heartbeat_at)) <= 2 * (t.interval_sec || 600) * 1000).length;
      const asks = (db.prepare(`SELECT COUNT(*) AS n FROM ask WHERE project_id=? AND status='open'`).get(p.id) as any)?.n ?? 0;
      return { slug: p.slug, name: p.name, status: p.status, tabs: tabs.length, tabs_live: live, open_asks: asks };
    });
    control = {
      projects: projects.length,
      tabs_live: byProject.reduce((s, p) => s + p.tabs_live, 0),
      tabs_down: byProject.reduce((s, p) => s + (p.tabs - p.tabs_live), 0),
      open_asks: byProject.reduce((s, p) => s + p.open_asks, 0),
      by_project: byProject,
    };
  } catch { /* ops schema absent */ }

  // Calendar snapshot (next meeting + a few upcoming) from the EventKit mirror. Guard: table may be
  // absent / empty until the user connects their calendar.
  let calendar: any = { connected: false, next: null, upcoming: [] };
  try {
    const nowMs = Date.now();
    const rows = db.prepare(
      `SELECT title, start_ms, calendar, all_day FROM calendar_event
       WHERE workspace_id=? AND end_ms >= ? ORDER BY start_ms ASC LIMIT 8`).all(ws, nowMs) as any[];
    const fmt = (ms: number) => new Date(ms).toISOString();
    const mins = (ms: number) => Math.round((ms - nowMs) / 60000);
    const upcoming = rows.map((r) => ({ title: r.title, start: fmt(r.start_ms), in_min: mins(r.start_ms), calendar: r.calendar, all_day: !!r.all_day }));
    const total = (db.prepare(`SELECT COUNT(*) AS n FROM calendar_event WHERE workspace_id=?`).get(ws) as any)?.n ?? 0;
    // "next meeting" = the next TIMED event (skip all-day holidays/birthdays).
    const next = upcoming.find((e) => !e.all_day) ?? null;
    calendar = { connected: total > 0, next, upcoming: upcoming.slice(0, 4) };
  } catch { /* calendar mirror absent */ }

  // Headline: lead with the next meeting if there is one.
  const nextBit = calendar.next
    ? ` · next: ${String(calendar.next.title).slice(0, 28)}${calendar.next.in_min >= 0 ? ` in ${calendar.next.in_min < 90 ? calendar.next.in_min + "m" : Math.round(calendar.next.in_min / 60) + "h"}` : ""}`
    : "";

  return {
    headline: `Local business cockpit · ${channels.length} channels · ${unread.total} unread (${unread.chats} chats) · ${control.projects} projects, ${control.tabs_live} tabs live, ${control.open_asks} open asks${nextBit}`,
    channels,
    unread: { total: unread.total, chats: unread.chats },
    calendar,
    control_plane: control,
    capabilities: CATALOG,
    how_to: "Read with list_chats/get_messages (+get_media for screenshots/voice). Act with send_message (sends directly; draft_only:true to hold). Manage with archive/mute/mark_all_read. Orchestrate tabs with ops_board → ops_order. Full guide: ~/dewx/state/COCKPIT.md",
  };
}
