import React, { useEffect, useState, useMemo } from "react";
import {
  List,
  ActionPanel,
  Action,
  Icon,
  Color,
  showToast,
  Toast,
  showHUD,
  Clipboard,
  open
} from "@raycast/api";
import { execFileSync, execSync } from "child_process";
import fs from "fs";
import path from "path";
import os from "os";

export 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;
}

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

export 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 > 80 ? firstLine.substring(0, 80) + "…" : firstLine;
}

export 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 "just now";
  if (diff < 90) return "1m ago";
  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 "yesterday";
  if (days < 7) return `${days}d ago`;
  return new Date(epochSec * 1000).toLocaleDateString(undefined, {
    month: "short",
    day: "numeric"
  });
}

export function formatTokens(count: number): string {
  if (!count) return "0";
  if (count >= 1_000_000) return `${(count / 1_000_000).toFixed(1)}M`;
  if (count >= 1_000) return `${(count / 1_000).toFixed(1)}k`;
  return count.toString();
}

export 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;
}

export 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;
}

export 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) {
    console.error("Failed to read .codex-global-state.json:", 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 120;
  `;

  let rawRows: any[] = [];
  try {
    const stdout = execFileSync("sqlite3", ["-json", dbPath, query], {
      encoding: "utf8",
      timeout: 4000
    });
    rawRows = JSON.parse(stdout);
  } catch (err) {
    console.error("Failed to query SQLite state_5.sqlite:", 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 Command() {
  const [data, setData] = useState<{ threads: ThreadItem[]; projects: ProjectMeta[] }>({
    threads: [],
    projects: []
  });
  const [isLoading, setIsLoading] = useState(true);
  const [selectedProjectId, setSelectedProjectId] = useState<string>("all");
  const [isShowingDetail, setIsShowingDetail] = useState(false);

  const refreshData = () => {
    try {
      const res = loadCodexData();
      setData(res);
    } catch (err: any) {
      showToast({
        style: Toast.Style.Failure,
        title: "Failed to load Codex data",
        message: err.message
      });
    } finally {
      setIsLoading(false);
    }
  };

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

  const filteredThreads = useMemo(() => {
    if (selectedProjectId === "all") {
      return data.threads;
    }
    if (selectedProjectId === "running") {
      return data.threads.filter((t) => t.isRunning);
    }
    return data.threads.filter(
      (t) => t.projectId === selectedProjectId || t.projectName === selectedProjectId
    );
  }, [data.threads, selectedProjectId]);

  const runningCount = useMemo(
    () => data.threads.filter((t) => t.isRunning).length,
    [data.threads]
  );

  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) {
      showToast({
        style: Toast.Style.Failure,
        title: "Failed to open thread in Codex",
        message: err.message
      });
    }
  };

  const openInEditor = (folderPath: string, editor: "cursor" | "zed" | "finder" | "ghostty") => {
    if (!folderPath || !fs.existsSync(folderPath)) {
      showToast({
        style: Toast.Style.Failure,
        title: "Folder does not exist",
        message: folderPath
      });
      return;
    }

    try {
      if (editor === "cursor") {
        execSync(`cursor "${folderPath}"`);
        showHUD("Opened in Cursor");
      } else if (editor === "zed") {
        execSync(`zed "${folderPath}"`);
        showHUD("Opened in Zed");
      } else if (editor === "ghostty") {
        execSync(`open -a Ghostty "${folderPath}"`);
        showHUD("Opened in Ghostty");
      } else {
        open(folderPath);
        showHUD("Opened in Finder");
      }
    } catch (err: any) {
      showToast({
        style: Toast.Style.Failure,
        title: `Failed to open in ${editor}`,
        message: err.message
      });
    }
  };

  return (
    <List
      isLoading={isLoading}
      isShowingDetail={isShowingDetail}
      searchBarPlaceholder="Search active agents, chats, branches, or prompts…"
      searchBarAccessory={
        <List.Dropdown
          tooltip="Filter by Project or Status"
          value={selectedProjectId}
          onChange={(val) => setSelectedProjectId(val)}
        >
          <List.Dropdown.Item
            title={`All Chats (${data.threads.length})`}
            value="all"
            icon={Icon.List}
          />
          {runningCount > 0 && (
            <List.Dropdown.Item
              title={`⚡️ Running Agents (${runningCount})`}
              value="running"
              icon={{ source: Icon.Bolt, tintColor: Color.Green }}
            />
          )}
          <List.Dropdown.Section title="Projects">
            {data.projects.map((p) => {
              const count = data.threads.filter(
                (t) => t.projectId === p.id || t.projectName === p.name
              ).length;
              return (
                <List.Dropdown.Item
                  key={p.id}
                  title={`${p.name} (${count})`}
                  value={p.id}
                  icon={Icon.Folder}
                />
              );
            })}
          </List.Dropdown.Section>
        </List.Dropdown>
      }
    >
      {filteredThreads.map((thread) => {
        const timeAgo = formatRelativeTime(thread.updated_at);
        const badges: List.Item.Accessory[] = [];

        if (thread.isRunning) {
          badges.push({
            tag: { value: "Running", color: Color.Green },
            icon: { source: Icon.Bolt, tintColor: Color.Green },
            tooltip: "Agent is actively working"
          });
        } else if (thread.isRecent) {
          badges.push({
            tag: { value: "Recent", color: Color.Blue },
            tooltip: "Updated within the last hour"
          });
        }

        if (thread.subagents > 0) {
          badges.push({
            text: `${thread.subagents} sub-agent${thread.subagents > 1 ? "s" : ""}`,
            icon: Icon.Person,
            tooltip: `${thread.subagents} child agent runs spawned`
          });
        }

        if (thread.git_branch) {
          badges.push({
            text: thread.git_branch,
            icon: Icon.Tag,
            tooltip: `Branch: ${thread.git_branch}`
          });
        }

        badges.push({
          text: timeAgo,
          tooltip: `Updated ${new Date(thread.updated_at * 1000).toLocaleString()}`
        });

        const iconConfig = thread.isRunning
          ? { source: Icon.Bolt, tintColor: Color.Green }
          : thread.isRecent
          ? { source: Icon.Bubble, tintColor: Color.Blue }
          : { source: Icon.Bubble, tintColor: Color.SecondaryText };

        const detailMarkdown = `
# ${thread.displayTitle}

${
  thread.isRunning
    ? "> ⚡️ **Agent is currently active and processing**\n\n"
    : thread.isRecent
    ? "> 🟢 **Recently updated session**\n\n"
    : ""
}
### Session Overview
* **Project:** \`${thread.projectName}\`
* **Directory:** \`${thread.cwd || "None"}\`
* **Git Branch:** \`${thread.git_branch || "None"}\`
* **Model:** \`${thread.model || "Default"}\`
* **Tokens Used:** \`${formatTokens(thread.tokens_used)}\`
* **Sub-agents:** \`${thread.subagents}\`
* **Thread ID:** \`${thread.id}\`
* **Last Active:** ${new Date(thread.updated_at * 1000).toLocaleString()}

---

### Initial Prompt / Context
\`\`\`text
${thread.rawPrompt || thread.preview || "No prompt preview available"}
\`\`\`
`;

        return (
          <List.Item
            key={thread.id}
            id={thread.id}
            title={thread.displayTitle}
            subtitle={isShowingDetail ? undefined : thread.projectName}
            icon={iconConfig}
            accessories={isShowingDetail ? undefined : badges}
            detail={
              <List.Item.Detail
                markdown={detailMarkdown}
                metadata={
                  <List.Item.Detail.Metadata>
                    <List.Item.Detail.Metadata.Label
                      title="Status"
                      text={thread.isRunning ? "⚡️ Running" : thread.isRecent ? "🟢 Recent" : "⚪️ Idle"}
                    />
                    <List.Item.Detail.Metadata.Label
                      title="Project"
                      text={thread.projectName}
                    />
                    {thread.git_branch && (
                      <List.Item.Detail.Metadata.Label
                        title="Git Branch"
                        text={thread.git_branch}
                        icon={Icon.Tag}
                      />
                    )}
                    <List.Item.Detail.Metadata.Label
                      title="Model"
                      text={thread.model || "Default"}
                    />
                    <List.Item.Detail.Metadata.Label
                      title="Tokens Used"
                      text={formatTokens(thread.tokens_used)}
                    />
                    {thread.subagents > 0 && (
                      <List.Item.Detail.Metadata.Label
                        title="Sub-agents"
                        text={thread.subagents.toString()}
                      />
                    )}
                    <List.Item.Detail.Metadata.Separator />
                    <List.Item.Detail.Metadata.Label
                      title="Last Updated"
                      text={new Date(thread.updated_at * 1000).toLocaleString()}
                    />
                    <List.Item.Detail.Metadata.Label
                      title="Created"
                      text={new Date(thread.created_at * 1000).toLocaleString()}
                    />
                    <List.Item.Detail.Metadata.Label
                      title="Thread ID"
                      text={thread.id}
                    />
                  </List.Item.Detail.Metadata>
                }
              />
            }
            actions={
              <ActionPanel>
                <ActionPanel.Section title="Codex Navigation">
                  <Action
                    title="Switch to in Codex"
                    icon={Icon.ArrowRightCircle}
                    onAction={() => activateInCodex(thread)}
                  />
                  <Action
                    title={isShowingDetail ? "Hide Details" : "Show Details"}
                    icon={Icon.Sidebar}
                    shortcut={{ modifiers: ["cmd"], key: "d" }}
                    onAction={() => setIsShowingDetail(!isShowingDetail)}
                  />
                  <Action
                    title="Reload Data"
                    icon={Icon.ArrowClockwise}
                    shortcut={{ modifiers: ["cmd"], key: "r" }}
                    onAction={refreshData}
                  />
                </ActionPanel.Section>

                {thread.cwd && (
                  <ActionPanel.Section title="Workspace">
                    <Action
                      title="Open in Cursor"
                      icon={Icon.Code}
                      shortcut={{ modifiers: ["cmd"], key: "e" }}
                      onAction={() => openInEditor(thread.cwd, "cursor")}
                    />
                    <Action
                      title="Open in Zed"
                      icon={Icon.Terminal}
                      shortcut={{ modifiers: ["cmd", "shift"], key: "z" }}
                      onAction={() => openInEditor(thread.cwd, "zed")}
                    />
                    <Action
                      title="Open in Ghostty"
                      icon={Icon.Terminal}
                      shortcut={{ modifiers: ["cmd", "shift"], key: "t" }}
                      onAction={() => openInEditor(thread.cwd, "ghostty")}
                    />
                    <Action
                      title="Reveal in Finder"
                      icon={Icon.Finder}
                      shortcut={{ modifiers: ["cmd", "shift"], key: "f" }}
                      onAction={() => openInEditor(thread.cwd, "finder")}
                    />
                  </ActionPanel.Section>
                )}

                <ActionPanel.Section title="Copy Info">
                  <Action
                    title="Copy Title"
                    icon={Icon.CopyClipboard}
                    shortcut={{ modifiers: ["cmd"], key: "c" }}
                    onAction={async () => {
                      await Clipboard.copy(thread.displayTitle);
                      showHUD("Copied title to clipboard");
                    }}
                  />
                  <Action
                    title="Copy Codex Deep Link"
                    icon={Icon.Link}
                    shortcut={{ modifiers: ["cmd", "shift"], key: "c" }}
                    onAction={async () => {
                      await Clipboard.copy(`codex://threads/${thread.id}`);
                      showHUD("Copied deep link to clipboard");
                    }}
                  />
                  <Action
                    title="Copy Thread ID"
                    icon={Icon.CopyClipboard}
                    shortcut={{ modifiers: ["cmd", "opt"], key: "c" }}
                    onAction={async () => {
                      await Clipboard.copy(thread.id);
                      showHUD("Copied Thread ID");
                    }}
                  />
                  <Action
                    title="Copy Full Prompt"
                    icon={Icon.Document}
                    shortcut={{ modifiers: ["cmd", "opt"], key: "p" }}
                    onAction={async () => {
                      await Clipboard.copy(thread.rawPrompt);
                      showHUD("Copied prompt to clipboard");
                    }}
                  />
                  {thread.rollout_path && (
                    <Action
                      title="Reveal Rollout Log in Finder"
                      icon={Icon.Finder}
                      onAction={() => {
                        try {
                          execSync(`open -R "${thread.rollout_path}"`);
                          showHUD("Revealed rollout log in Finder");
                        } catch (err: any) {
                          showToast({
                            style: Toast.Style.Failure,
                            title: "Failed to reveal log",
                            message: err.message
                          });
                        }
                      }}
                    />
                  )}
                </ActionPanel.Section>
              </ActionPanel>
            }
          />
        );
      })}
    </List>
  );
}
