/**
 * 선택 도구 — 스킬이 있으면 더 잘 작동하는 외부 도구들.
 *
 * 스킬 자체는 이것들 없이도 돈다. 다만 레퍼런스 조사에서 갤러리가 봇 차단(403)을
 * 걸거나 SPA 라 본문을 못 읽을 때, 텍스트 페처만으로는 방법이 없다.
 * 실측에서 톤 레퍼런스 하나를 얻으려고 네 사이트를 돌고 전부 실패했다.
 * headed 브라우저가 있으면 첫 번째에 끝난다.
 *
 * 그래서 **필수 의존성이 아니라 선택 의존성**이다. 설치는 사용자가 고른다.
 */
import { execFile } from "node:child_process";
import { promisify } from "node:util";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";

const exec = promisify(execFile);

export interface OptionalTool {
  id: string;
  label: string;
  /** 이게 없으면 무엇을 못 하는가 — 고르는 사람이 판단할 수 있게 */
  why: string;
  /** 설치 크기 어림값. 수백 MB 를 말없이 받게 하지 않는다 */
  size: string;
  detect(): Promise<boolean>;
  install(): Promise<void>;
  /** 설치 후 사람이 직접 해야 하는 것이 있으면 여기에 */
  followUp?: string;
}

/** npm 전역 루트. Windows 경로에 공백이 있어도 안전하게 얻는다 */
async function npmRoot(): Promise<string | null> {
  try {
    const { stdout } = await exec("npm", ["root", "-g"], { shell: true });
    return stdout.trim() || null;
  } catch {
    return null;
  }
}

async function exists(p: string): Promise<boolean> {
  try {
    await fs.access(p);
    return true;
  } catch {
    return false;
  }
}

/**
 * Playwright — 레퍼런스 조사와 프리플라이트 실렌더 검사에 쓴다.
 *
 * 라이브러리만 있고 브라우저 바이너리가 없으면 실행 시점에 죽는다.
 * 둘 다 있어야 설치된 것으로 친다.
 */
const playwright: OptionalTool = {
  id: "playwright",
  label: "Playwright (headed 브라우저)",
  why: "봇 차단(403)·SPA 갤러리를 직접 열어 스크린샷과 실측값을 받는다. 5단계 실렌더 검사에도 쓴다",
  size: "약 180MB (크로미움 포함)",

  async detect() {
    const root = await npmRoot();
    if (!root || !(await exists(path.join(root, "playwright")))) return false;
    // 브라우저 바이너리까지 확인한다 — 라이브러리만 있으면 첫 실행에서 실패한다
    const cacheDir =
      process.platform === "win32"
        ? path.join(os.homedir(), "AppData", "Local", "ms-playwright")
        : process.platform === "darwin"
          ? path.join(os.homedir(), "Library", "Caches", "ms-playwright")
          : path.join(os.homedir(), ".cache", "ms-playwright");
    try {
      const entries = await fs.readdir(cacheDir);
      return entries.some((e) => e.startsWith("chromium"));
    } catch {
      return false;
    }
  },

  async install() {
    await exec("npm", ["install", "-g", "playwright"], { shell: true, maxBuffer: 1 << 24 });
    await exec("npx", ["--yes", "playwright", "install", "chromium"], {
      shell: true,
      maxBuffer: 1 << 24,
    });
  },
};

/**
 * Playwright MCP — Claude Code 가 브라우저를 직접 조작하게 한다.
 *
 * 설치는 해주지만 **등록은 하지 않는다.** MCP 서버 등록은 사용자의 에이전트 설정을
 * 건드리는 일이고, 설정 파일 위치와 형식이 도구마다 다르다. 명령만 알려준다.
 */
const playwrightMcp: OptionalTool = {
  id: "playwright-mcp",
  label: "Playwright MCP 서버",
  why: "에이전트가 브라우저를 직접 조작한다. 스크립트를 매번 쓰지 않아도 된다",
  size: "약 2MB",
  followUp: "claude mcp add playwright -- npx @playwright/mcp@latest",

  async detect() {
    const root = await npmRoot();
    if (!root) return false;
    return exists(path.join(root, "@playwright", "mcp"));
  },

  async install() {
    await exec("npm", ["install", "-g", "@playwright/mcp"], { shell: true, maxBuffer: 1 << 24 });
  },
};

export const OPTIONAL_TOOLS: OptionalTool[] = [playwright, playwrightMcp];

export interface ToolStatus {
  tool: OptionalTool;
  installed: boolean;
}

export async function checkTools(): Promise<ToolStatus[]> {
  return Promise.all(
    OPTIONAL_TOOLS.map(async (tool) => ({ tool, installed: await tool.detect() })),
  );
}

export function getTool(id: string): OptionalTool | undefined {
  return OPTIONAL_TOOLS.find((t) => t.id === id);
}
