import React, { useEffect, useState } from "react";
import { MenuBarExtra, open, showHUD, Icon } from "@raycast/api";
import { execFileSync, execSync } from "child_process";
import fs from "fs";
import path from "path";
import os from "os";

interface ThreadItem {
  id: string;
  title: string;
  displayTitle: string;
  cleanTitle: string;
  rawPrompt: string;
  cwd: string;
  git_branch: string | null;
  model: string | null;
  preview: string;
  updated_at: number;
  created_at: number;
  tokens_used: number;
  subagents: number;
  rollout_path?: string;
  projectName: string;
  projectId?: string;
  isRunning: boolean;
  isRecent: boolean;
}

interface ProjectMeta {
  id: string;
  name: string;
  rootPaths?: string[];
}

function cleanTitleText(raw: string | undefined): string {
  if (!raw) return "Untitled Thread";
  let text = raw.trim();
  if (text.includes("## My request:")) {
    text = text.split("## My request:")[1].trim();
  }
  const lines = text
    .split("\n")
    .map((l) => l.trim())
    .filter(
      (l) =>
        l &&
        !l.startsWith("# Files mentioned") &&
        !l.startsWith("Distinguish instructions") &&
        !l.startsWith("## codex-clipboard-")
    );
  const firstLine = lines[0] || "Untitled Thread";
  return firstLine.length > 60 ? firstLine.substring(0, 60) + "…" : firstLine;
}

function formatRelativeTime(epochSec: number): string {
  if (!epochSec) return "";
  const now = Math.floor(Date.now() / 1000);
  const diff = Math.max(0, now - epochSec);
  if (diff < 45) return "now";
  if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
  if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
  const days = Math.floor(diff / 86400);
  if (days === 1) return "1d ago";
  return `${days}d ago`;
}

function isThreadActive(rolloutPath: string | undefined, updatedAtSec: number): boolean {
  const nowMs = Date.now();
  if (rolloutPath && fs.existsSync(rolloutPath)) {
    try {
      const stat = fs.statSync(rolloutPath);
      const ageMs = nowMs - stat.mtimeMs;
      if (ageMs > 180_000) return false;

      const bufferSize = Math.min(stat.size, 4096);
      if (bufferSize > 0) {
        const fd = fs.openSync(rolloutPath, "r");
        const buf = Buffer.alloc(bufferSize);
        fs.readSync(fd, buf, 0, bufferSize, Math.max(0, stat.size - bufferSize));
        fs.closeSync(fd);

        const text = buf.toString("utf8");
        const lines = text.trim().split("\n").filter(Boolean);
        if (lines.length > 0) {
          const lastLine = lines[lines.length - 1];
          try {
            const parsed = JSON.parse(lastLine);
            const payloadType = parsed.payload?.type;
            if (payloadType === "task_complete" || payloadType === "turn_aborted") {
              return false;
            }
          } catch {}
        }
      }
      return ageMs < 120_000;
    } catch {}
  }
  const dbAgeMs = nowMs - updatedAtSec * 1000;
  return dbAgeMs < 25_000 && dbAgeMs >= 0;
}

function getGeneratedTitlesMap(home: string): Map<string, string> {
  const titles = new Map<string, string>();

  // 1. Parse ~/.codex/session_index.jsonl
  try {
    const sessionIndexPath = path.join(home, ".codex/session_index.jsonl");
    if (fs.existsSync(sessionIndexPath)) {
      const content = fs.readFileSync(sessionIndexPath, "utf8");
      const lines = content.trim().split("\n");
      for (const line of lines) {
        if (!line) continue;
        try {
          const item = JSON.parse(line);
          if (item.id && typeof item.thread_name === "string") {
            const trimmed = item.thread_name.trim();
            if (trimmed) titles.set(item.id, trimmed);
          }
        } catch {}
      }
    }
  } catch {}

  // 2. Query any sqlite databases in ~/.codex/sqlite/ or ~/.codex/ that have local_thread_catalog
  const candidateDirs = [path.join(home, ".codex/sqlite"), path.join(home, ".codex")];
  for (const dir of candidateDirs) {
    if (!fs.existsSync(dir)) continue;
    try {
      const entries = fs.readdirSync(dir);
      for (const entry of entries) {
        if (entry.endsWith(".db") || entry.endsWith(".sqlite")) {
          const fullPath = path.join(dir, entry);
          try {
            const hasTable = execFileSync(
              "sqlite3",
              [fullPath, "SELECT 1 FROM sqlite_master WHERE type='table' AND name='local_thread_catalog';"],
              { encoding: "utf8", timeout: 1500 }
            ).trim();
            if (hasTable === "1") {
              const stdout = execFileSync(
                "sqlite3",
                ["-json", fullPath, "SELECT thread_id, display_title FROM local_thread_catalog WHERE display_title IS NOT NULL AND display_title != '';"],
                { encoding: "utf8", timeout: 2500 }
              );
              const rows = JSON.parse(stdout);
              for (const r of rows) {
                if (r.thread_id && typeof r.display_title === "string") {
                  const trimmed = r.display_title.trim();
                  if (trimmed) titles.set(r.thread_id, trimmed);
                }
              }
            }
          } catch {}
        }
      }
    } catch {}
  }

  // 3. Global state fallback
  try {
    const globalStatePath = path.join(home, ".codex/.codex-global-state.json");
    if (fs.existsSync(globalStatePath)) {
      const raw = fs.readFileSync(globalStatePath, "utf8");
      const gs = JSON.parse(raw);
      if (gs["thread-titles"]?.titles) {
        for (const [id, val] of Object.entries(gs["thread-titles"].titles)) {
          const titleStr = typeof val === "string" ? val : (val as any)?.title;
          if (titleStr && typeof titleStr === "string" && titleStr.trim()) {
            titles.set(id, titleStr.trim());
          }
        }
      }
    }
  } catch {}

  return titles;
}

function loadCodexData(): { threads: ThreadItem[]; projects: ProjectMeta[] } {
  const home = os.homedir();
  const globalStatePath = path.join(home, ".codex/.codex-global-state.json");
  const dbPath = path.join(home, ".codex/state_5.sqlite");

  let localProjectsMap: Record<string, ProjectMeta> = {};
  let projectOrder: string[] = [];
  let assignments: Record<string, { projectId?: string }> = {};

  try {
    if (fs.existsSync(globalStatePath)) {
      const raw = fs.readFileSync(globalStatePath, "utf8");
      const globalState = JSON.parse(raw);
      localProjectsMap = globalState["local-projects"] || {};
      projectOrder = globalState["project-order"] || [];
      assignments = globalState["thread-project-assignments"] || {};
    }
  } catch (err) {}

  const orderedProjects: ProjectMeta[] = [];
  const addedPids = new Set<string>();
  for (const pid of projectOrder) {
    if (localProjectsMap[pid]) {
      orderedProjects.push(localProjectsMap[pid]);
      addedPids.add(pid);
    }
  }
  for (const [pid, p] of Object.entries(localProjectsMap)) {
    if (!addedPids.has(pid)) {
      orderedProjects.push(p);
    }
  }

  if (!fs.existsSync(dbPath)) {
    return { threads: [], projects: orderedProjects };
  }

  const titlesMap = getGeneratedTitlesMap(home);

  const query = `
    SELECT 
      t.id, 
      t.title,
      t.first_user_message,
      t.cwd, 
      t.git_branch, 
      t.model, 
      t.preview, 
      t.updated_at, 
      t.created_at, 
      t.tokens_used,
      t.rollout_path,
      (SELECT COUNT(*) FROM thread_spawn_edges e WHERE e.parent_thread_id = t.id) as subagents
    FROM threads t
    WHERE t.archived = 0
    ORDER BY t.updated_at DESC
    LIMIT 60;
  `;

  let rawRows: any[] = [];
  try {
    const stdout = execFileSync("sqlite3", ["-json", dbPath, query], {
      encoding: "utf8",
      timeout: 3000
    });
    rawRows = JSON.parse(stdout);
  } catch (err) {}

  const nowSec = Math.floor(Date.now() / 1000);

  const threads: ThreadItem[] = rawRows.map((r) => {
    let projName = "Other";
    let projId: string | undefined = undefined;

    const assigned = assignments[r.id];
    if (assigned && assigned.projectId && localProjectsMap[assigned.projectId]) {
      projName = localProjectsMap[assigned.projectId].name;
      projId = assigned.projectId;
    } else if (r.cwd) {
      const matched = orderedProjects.find(
        (p) => p.rootPaths && p.rootPaths.some((root) => r.cwd.startsWith(root))
      );
      if (matched) {
        projName = matched.name;
        projId = matched.id;
      } else {
        projName = path.basename(r.cwd);
      }
    }

    const isRunning = isThreadActive(r.rollout_path, r.updated_at);
    const isRecent = !isRunning && nowSec - r.updated_at < 3600;

    const generated = titlesMap.get(r.id);
    const rawPrompt = r.first_user_message || r.title || r.preview || "";
    const fallback = cleanTitleText(r.title || r.first_user_message || r.preview);
    const displayTitle = generated || fallback;

    return {
      id: r.id,
      title: r.title || "",
      displayTitle,
      cleanTitle: displayTitle,
      rawPrompt,
      cwd: r.cwd || "",
      git_branch: r.git_branch || null,
      model: r.model || null,
      preview: r.preview || "",
      updated_at: r.updated_at || 0,
      created_at: r.created_at || 0,
      tokens_used: r.tokens_used || 0,
      subagents: Number(r.subagents) || 0,
      rollout_path: r.rollout_path || undefined,
      projectName: projName,
      projectId: projId,
      isRunning,
      isRecent
    };
  });

  return { threads, projects: orderedProjects };
}

export default function MenuBarCommand() {
  const [data, setData] = useState<{ threads: ThreadItem[]; projects: ProjectMeta[] }>({
    threads: [],
    projects: []
  });

  const refreshData = () => {
    try {
      const res = loadCodexData();
      setData(res);
    } catch {}
  };

  useEffect(() => {
    refreshData();
    const interval = setInterval(refreshData, 3000);
    return () => clearInterval(interval);
  }, []);

  const runningThreads = data.threads.filter((t) => t.isRunning);
  const recentThreads = data.threads.filter((t) => !t.isRunning).slice(0, 10);

  const activateInCodex = async (thread: ThreadItem) => {
    try {
      const url = `codex://threads/${thread.id}`;
      execSync(`open -a "/Applications/ChatGPT.app" "${url}" || open "${url}"`);
      execSync(`osascript -e 'tell application id "com.openai.codex" to activate' 2>/dev/null || osascript -e 'tell application "ChatGPT" to activate'`);
      await showHUD(`Switched to "${thread.displayTitle}" in Codex`);
    } catch (err: any) {}
  };

  const openApp = () => {
    try {
      execSync(`osascript -e 'tell application id "com.openai.codex" to activate' 2>/dev/null || osascript -e 'tell application "ChatGPT" to activate'`);
    } catch {}
  };

  const menuTitle =
    runningThreads.length > 0
      ? `⚡️ ${runningThreads.length}`
      : undefined;

  return (
    <MenuBarExtra
      icon="🤖"
      title={menuTitle}
      tooltip={
        runningThreads.length > 0
          ? `${runningThreads.length} Codex agent(s) running`
          : "Codex Agents"
      }
    >
      {runningThreads.length > 0 && (
        <MenuBarExtra.Section title={`Active Agents (${runningThreads.length})`}>
          {runningThreads.map((t) => (
            <MenuBarExtra.Item
              key={t.id}
              title={`⚡️ [${t.projectName}] ${t.displayTitle}`}
              subtitle={formatRelativeTime(t.updated_at)}
              onAction={() => activateInCodex(t)}
            />
          ))}
        </MenuBarExtra.Section>
      )}

      <MenuBarExtra.Section title="Recent Sessions">
        {recentThreads.map((t) => (
          <MenuBarExtra.Item
            key={t.id}
            title={`[${t.projectName}] ${t.displayTitle}`}
            subtitle={formatRelativeTime(t.updated_at)}
            onAction={() => activateInCodex(t)}
          />
        ))}
      </MenuBarExtra.Section>

      <MenuBarExtra.Section title="Projects">
        {data.projects.map((p) => {
          const projectThreads = data.threads.filter(
            (t) => t.projectId === p.id || t.projectName === p.name
          );
          if (projectThreads.length === 0) return null;
          return (
            <MenuBarExtra.Submenu
              key={p.id}
              title={`${p.name} (${projectThreads.length})`}
              icon={Icon.Folder}
            >
              {projectThreads.slice(0, 8).map((t) => (
                <MenuBarExtra.Item
                  key={t.id}
                  title={`${t.isRunning ? "⚡️ " : ""}${t.displayTitle}`}
                  subtitle={formatRelativeTime(t.updated_at)}
                  onAction={() => activateInCodex(t)}
                />
              ))}
            </MenuBarExtra.Submenu>
          );
        })}
      </MenuBarExtra.Section>

      <MenuBarExtra.Section>
        <MenuBarExtra.Item
          title="Bring Codex to Front"
          icon={Icon.Window}
          onAction={openApp}
        />
        <MenuBarExtra.Item
          title="Refresh Sessions"
          icon={Icon.ArrowClockwise}
          onAction={refreshData}
        />
      </MenuBarExtra.Section>
    </MenuBarExtra>
  );
}
