import { existsSync } from "node:fs";
import { homedir } from "node:os";
import { resolve } from "node:path";
import { parseOptionalEnumArray } from "./validation.js";

export type AnalyticsSource = "codex" | "copilot" | "claude" | "cursor" | "antigravity" | "hook_log";
export type ClientSurface = "cli" | "desktop" | "vscode" | "vscode_insiders" | "code" | "unknown";
export type ClaudeStorageKind = "claude_code" | "claude_desktop";
export interface SourceRootDescriptor { path: string; claudeStorage?: ClaudeStorageKind; }

export const ALL_SOURCES: readonly AnalyticsSource[] = ["codex", "copilot", "claude", "cursor", "antigravity", "hook_log"];
// NOTE: this must stay in sync with ALL_SOURCES/SESSION_SOURCES. A source missing here is
// silently skipped by `analytics_scan` (the read tools below still default to including it),
// which is exactly the bug that made Claude Code analytics look empty for a while. When adding
// a new source, add it here too, even if it feels redundant.
export const DEFAULT_SCAN_SOURCES: readonly AnalyticsSource[] = ["codex", "copilot", "claude", "cursor", "antigravity", "hook_log"];
export const SESSION_SOURCES: readonly AnalyticsSource[] = ["codex", "copilot", "claude", "cursor", "antigravity"];
export const CLIENT_SURFACES: readonly ClientSurface[] = ["cli", "desktop", "vscode", "vscode_insiders", "code", "unknown"];
export const SESSION_KINDS = ["main", "subagent", "task", "unknown"] as const;

export function parseSources(value: unknown): AnalyticsSource[] | undefined {
  return parseOptionalEnumArray(value, ALL_SOURCES, "sources");
}

export function resolveCodexRoots(): string[] {
  const codexHome = resolveCodexHome();
  return [resolve(codexHome, "sessions"), resolve(codexHome, "archived_sessions")];
}

export function resolveCodexHome(): string {
  return resolve(homedir(), ".codex");
}

export function resolveCodexSessionIndexPath(): string {
  return resolve(resolveCodexHome(), "session_index.jsonl");
}

export function resolveCopilotRoots(): string[] {
  if (process.platform === "win32") {
    const appData = process.env.APPDATA ?? resolve(homedir(), "AppData", "Roaming");
    return [
      resolve(appData, "Code", "User", "globalStorage"),
      resolve(appData, "Code", "User", "workspaceStorage"),
      resolve(appData, "Code - Insiders", "User", "globalStorage"),
      resolve(appData, "Code - Insiders", "User", "workspaceStorage")
    ];
  }
  return [
    resolve(homedir(), ".config", "Code", "User", "globalStorage"),
    resolve(homedir(), ".config", "Code", "User", "workspaceStorage"),
    resolve(homedir(), ".config", "Code - Insiders", "User", "globalStorage"),
    resolve(homedir(), ".config", "Code - Insiders", "User", "workspaceStorage")
  ];
}

export function resolveClaudeRoots(): string[] {
  // Deliberately NOT the broad `~/.claude` root: real Claude Code CLI session transcripts only
  // ever live under `~/.claude/projects/**/*.jsonl` (including `.../subagents/*.jsonl`). The
  // broad root also contains `plugins/cache/**/node_modules/**` (vendored npm dependencies
  // bundled with installed plugins), settings/telemetry/shell-snapshot files, etc. -- none of
  // which are session data, and recursing into them wastes time and (for large pretty-printed
  // JSON files, e.g. inside node_modules) floods the scan with thousands of spurious
  // INVALID_JSON_LINE_SKIPPED/UNSUPPORTED_FORMAT warnings per file.
  return [resolve(homedir(), ".claude", "projects")];
}

export function resolveClaudeRootDescriptors(): SourceRootDescriptor[] {
  return [{ path: resolve(homedir(), ".claude", "projects"), claudeStorage: "claude_code" }];
}

export function resolveAntigravityRoots(): string[] {
  // Antigravity (Google's Antigravity IDE/CLI, built on the Gemini stack) always keeps its config
  // and local state directly under the user's home directory on every OS -- unlike Copilot/Cursor,
  // which branch into per-OS AppData/Roaming/Library locations, there is no platform branching
  // here. This mirrors the install-side convention already used elsewhere in this repo for
  // `.gemini/antigravity/skills`, `.gemini/config/hooks.json`, `.gemini/agents`. There are two
  // separate roots because the IDE and CLI variants are distinct products/paths per public docs.
  return [resolve(homedir(), ".gemini", "antigravity"), resolve(homedir(), ".gemini", "antigravity-cli")];
}

export function resolveCursorRoots(): string[] {
  // Cursor session state lives in a per-root `state.vscdb` SQLite file: one under the global
  // storage root, and one under each workspace's storage root. Unlike resolveCopilotRoots /
  // resolveClaudeRoots (which lump macOS and Linux together), Cursor's roots are split three
  // ways here since the macOS path does not follow the XDG-style `.config` layout used on Linux.
  if (process.platform === "win32") {
    const appData = process.env.APPDATA ?? resolve(homedir(), "AppData", "Roaming");
    return [resolve(appData, "Cursor", "User", "globalStorage"), resolve(appData, "Cursor", "User", "workspaceStorage")];
  }
  if (process.platform === "darwin") {
    return [
      resolve(homedir(), "Library", "Application Support", "Cursor", "User", "globalStorage"),
      resolve(homedir(), "Library", "Application Support", "Cursor", "User", "workspaceStorage")
    ];
  }
  return [
    resolve(homedir(), ".config", "Cursor", "User", "globalStorage"),
    resolve(homedir(), ".config", "Cursor", "User", "workspaceStorage")
  ];
}

export function resolveSourceRoots(source: AnalyticsSource, hookLogPath: string): string[] {
  return resolveSourceRootDescriptors(source, hookLogPath).map((descriptor) => descriptor.path);
}

export function resolveSourceRootDescriptors(source: AnalyticsSource, hookLogPath: string): SourceRootDescriptor[] {
  switch (source) {
    case "codex":
      return resolveCodexRoots().map((path) => ({ path }));
    case "copilot":
      return resolveCopilotRoots().map((path) => ({ path }));
    case "claude":
      return resolveClaudeRootDescriptors();
    case "cursor":
      return resolveCursorRoots().map((path) => ({ path }));
    case "antigravity":
      return resolveAntigravityRoots().map((path) => ({ path }));
    case "hook_log":
      return [{ path: hookLogPath }];
  }
}

export function sourceExists(source: AnalyticsSource, hookLogPath: string): boolean {
  const roots = resolveSourceRoots(source, hookLogPath);
  return roots.some((entry) => existsSync(entry));
}
