// src/adapters/contextMode.ts
import { existsSync, readFileSync, writeFileSync, lstatSync, rmSync, symlinkSync, readdirSync, mkdirSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import type { Adapter, RepairResult, AbsoluteTotals } from "./Adapter.ts";

export const CTX_TOOL_PREFIX = "mcp__plugin_context-mode_context-mode__";
export const CTX_MATCHER = "mcp__plugin_context-mode_context-mode__ctx.*";
export const CTX_CMD_RE = /<command-name>\s*(context-mode:)?(ctx[-_a-z]*|context-mode)\s*(<\/command-name>|\s+)/;
export const CTX_SKILL_LINE_RE = /^\s*-\s*\/?(context-mode:)?(ctx[-_a-z]*|context-mode)(\s|:|$).*$\n?/gm;

const INSTALLED_PLUGINS_PATH = join(homedir(), ".claude", "plugins", "installed_plugins.json");
const CACHE_ROOT = join(homedir(), ".claude", "plugins", "cache", "context-mode", "context-mode");
const HISTORY_PATH = join(homedir(), ".fewtok", "ctx-mode-versions.json");
const FT_DIR = join(homedir(), ".fewtok");

interface InstalledPluginEntry {
  scope: string;
  installPath: string;
  version: string;
  lastUpdated: string;
  gitCommitSha: string;
}
interface InstalledPluginsFile {
  version: number;
  plugins: Record<string, InstalledPluginEntry[]>;
  enabledPlugins?: Record<string, boolean>;
}

function readInstalledPlugins(): InstalledPluginsFile | null {
  try {
    const raw = readFileSync(INSTALLED_PLUGINS_PATH, "utf8");
    return JSON.parse(raw) as InstalledPluginsFile;
  } catch {
    return null;
  }
}

function isInstalled(): boolean {
  try {
    const file = readInstalledPlugins();
    if (!file) return false;
    if (file.enabledPlugins?.["context-mode@context-mode"] !== true) return false;
    const entries = file.plugins["context-mode@context-mode"];
    return Array.isArray(entries) && entries.length > 0;
  } catch {
    return false;
  }
}

function getCurrentVersion(): string | null {
  try {
    const file = readInstalledPlugins();
    if (!file) return null;
    const entries = file.plugins["context-mode@context-mode"];
    if (!Array.isArray(entries) || entries.length === 0) return null;
    return entries[0]!.version ?? null;
  } catch {
    return null;
  }
}

function readHistory(): string[] {
  try {
    const raw = readFileSync(HISTORY_PATH, "utf8");
    const parsed = JSON.parse(raw);
    if (parsed && Array.isArray(parsed.versions)) return parsed.versions as string[];
    if (Array.isArray(parsed)) return parsed as string[];  // backwards compat
    return [];
  } catch {
    return [];
  }
}

function writeHistory(versions: string[]): void {
  try {
    mkdirSync(FT_DIR, { recursive: true });
    writeFileSync(HISTORY_PATH, JSON.stringify({ versions }, null, 2), "utf8");
  } catch {
    // ignore write failures
  }
}

function listCacheVersions(): string[] {
  try {
    if (!existsSync(CACHE_ROOT)) return [];
    return readdirSync(CACHE_ROOT, { withFileTypes: true })
      .filter((d) => d.isDirectory() || d.isSymbolicLink())
      .map((d) => d.name);
  } catch {
    return [];
  }
}

function repair(): RepairResult {
  const current = getCurrentVersion();
  const result: RepairResult = {
    performed: false,
    current,
    symlinked: [],
    skipped: [],
    errors: [],
  };

  if (!current) return result;

  // Update history with current version
  const history = readHistory();
  if (!history.includes(current)) {
    history.unshift(current);
    writeHistory(history);
  }

  // Symlink stale cache dirs to current version dir
  const currentPath = join(CACHE_ROOT, current);
  if (!existsSync(currentPath)) return result;

  const onDisk = new Set(listCacheVersions());
  const allKnown = new Set<string>([...history, ...onDisk]);
  for (const name of allKnown) {
    if (name === current) continue;
    const verPath = join(CACHE_ROOT, name);
    try {
      if (!existsSync(verPath)) {
        // History-tracked but deleted by Claude Code — recreate as symlink
        symlinkSync(currentPath, verPath);
        result.symlinked.push(name);
        result.performed = true;
        continue;
      }
      const stat = lstatSync(verPath);
      if (stat.isSymbolicLink()) {
        result.skipped.push(name);
        continue;
      }
      if (stat.isDirectory()) {
        if (!history.includes(name) && !onDisk.has(name)) {
          result.skipped.push(name);
          continue;
        }
        rmSync(verPath, { recursive: true, force: true });
        symlinkSync(currentPath, verPath);
        result.symlinked.push(name);
        result.performed = true;
      }
    } catch (err) {
      result.errors.push({ name, msg: String(err) });
    }
  }

  return result;
}

function readSavings(): AbsoluteTotals | null {
  try {
    const file = readInstalledPlugins();
    if (!file) return null;
    const entries = file.plugins["context-mode@context-mode"];
    if (!Array.isArray(entries) || entries.length === 0) return null;
    const installPath = entries[0]!.installPath;
    if (!installPath) return null;
    const statsPath = join(installPath, "stats.json");
    if (!existsSync(statsPath)) return null;
    const raw = readFileSync(statsPath, "utf8");
    const data = JSON.parse(raw);
    // Expect keys: saved_bytes, saved_tokens, calls
    if (
      typeof data.saved_bytes !== "number" &&
      typeof data.saved_tokens !== "number" &&
      typeof data.calls !== "number"
    ) {
      return null;
    }
    return {
      bytesIn: typeof data.saved_bytes === "number" ? data.saved_bytes : 0,
      bytesOut: 0,
      tokensIn: typeof data.saved_tokens === "number" ? data.saved_tokens : 0,
      tokensOut: 0,
      hits: typeof data.calls === "number" ? data.calls : 0,
    };
  } catch {
    return null;
  }
}

export const contextModeAdapter: Adapter = {
  id: "context-mode",
  displayLabel: "CTXMD",
  isInstalled,
  getCurrentVersion,
  repair,
  readSavings,
};
