/**
 * 跨平台 skill 安装脚本（Node / tsx）。
 *
 * 将本仓库安装为多个 AI 客户端的 skill / 命令 / 规则：
 *   - codebuddy / workbuddy / gemini：以目录链接（junction / symlink）方式装到 ~/.{client}/skills/<name>
 *   - claude：生成 Claude Code 自定义命令文件（项目级 .claude/commands/baskreport.md，去掉 frontmatter）
 *   - codex：生成 Codex CLI 项目指令文件（项目级 AGENTS.md）
 *   - cursor：生成 Cursor Rule 文件（项目级 .cursor/rules/basksoft-ai.mdc，复用 description 做自动触发）
 *
 * 用法:
 *   tsx scripts/install-skill.ts
 *   tsx scripts/install-skill.ts --build
 *   tsx scripts/install-skill.ts --clients codebuddy
 *   tsx scripts/install-skill.ts --clients all
 *   tsx scripts/install-skill.ts --clients claude,cursor
 *   tsx scripts/install-skill.ts --uninstall
 *   tsx scripts/install-skill.ts --uninstall --clients codebuddy
 *
 * 说明：claude / codex / cursor / generic 采用"项目级"落盘（写入仓库内文件），
 * 跟随 git 版本管理，且不影响用户级目录。执行层（Node CLI）多方完全共用，无需任何改动。
 * codebuddy / workbuddy / gemini 采用目录链接方式（个人级）。
 */
import { execSync } from "node:child_process";
import * as fs from "node:fs";
import * as path from "node:path";
import * as os from "node:os";
import { fileURLToPath } from "node:url";
import { runUninstall } from "../src/install-skill.js";

interface Options {
  build: boolean;
  clients: Client[];
  uninstall: boolean;
}

// 支持的所有客户端；"all" 展开为全部
const KNOWN_CLIENTS = ["codebuddy", "workbuddy", "gemini", "codex", "claude", "cursor", "generic"] as const;
type Client = (typeof KNOWN_CLIENTS)[number];

function expandClients(raw: string[]): Client[] {
  const set = new Set<Client>();
  for (const c of raw) {
    if (c === "all") {
      KNOWN_CLIENTS.forEach((k) => set.add(k));
    } else if ((KNOWN_CLIENTS as readonly string[]).includes(c)) {
      set.add(c as Client);
    } else {
      throw new Error(
        `未知客户端: ${c}。支持: ${KNOWN_CLIENTS.join(", ")} 或 all`
      );
    }
  }
  return [...set];
}

function parseArgs(argv: string[]): Options {
  const options: Options = { build: false, clients: ["codebuddy", "workbuddy"], uninstall: false };
  for (let i = 0; i < argv.length; i++) {
    const arg = argv[i];
    if (!arg) continue;
    if (arg === "--build") {
      options.build = true;
    } else if (arg === "--uninstall") {
      options.uninstall = true;
    } else if (arg === "--clients") {
      const val = argv[++i];
      if (!val) throw new Error("--clients 需要一个逗号分隔的客户端列表");
      options.clients = expandClients(
        val.split(",").map((c) => c.trim()).filter(Boolean)
      );
    } else if (arg.startsWith("--clients=")) {
      options.clients = expandClients(
        arg.slice("--clients=".length).split(",").map((c) => c.trim()).filter(Boolean)
      );
    } else {
      throw new Error(`未知参数: ${arg}`);
    }
  }
  return options;
}

function isLink(targetPath: string): boolean {
  try {
    // lstat 对 Windows junction 也会返回 isSymbolicLink()=true，比 readlinkSync 更可靠
    return fs.lstatSync(targetPath).isSymbolicLink();
  } catch {
    return false;
  }
}

/**
 * 读取 SKILL.md，分离 frontmatter 与正文。
 * 返回 { description, body }。body 为去掉 frontmatter 后的正文（含前导换行裁剪）。
 */
function readSkillMeta(skillMd: string): { description: string; body: string } {
  const raw = fs.readFileSync(skillMd, "utf8");
  const fm = raw.match(/^---\s*\n([\s\S]*?)\n---\s*\n?/);
  let description = "";
  let body = raw;
  if (fm) {
    const fmText = fm[1] ?? "";
    const m = fmText.match(/^\s*description:\s*([\s\S]*?)(?:\n\w|$)/m);
    if (m) description = (m[1] ?? "").trim();
    body = raw.slice((fm[0] ?? "").length);
  }
  return { description, body: body.replace(/^\n+/, "") };
}

/**
 * 为 claude 生成自定义命令文件（.claude/commands/baskreport.md）。
 * 命令文件不读 frontmatter，直接用 SKILL.md 正文（已去 frontmatter）。
 * 顶部加一行精简说明，提示用户用 /baskreport 触发。
 */
function genClaudeCommand(skillRoot: string, body: string): void {
  const dir = path.join(skillRoot, ".claude", "commands");
  fs.mkdirSync(dir, { recursive: true });
  const out = path.join(dir, "baskreport.md");
  const header = [
    "<!--",
    "此文件由 scripts/install-skill.ts 自动生成；如需修改请改 SKILL.md 后重跑脚本。",
    "在 Claude Code 中用 /baskreport 调用（Claude Code 命令不会像 CodeBuddy 那样按 description 自动触发，需显式 /调用）。",
    "-->",
    "",
  ].join("\n");
  fs.writeFileSync(out, header + body, "utf8");
  console.log(`  已生成: ${out}`);
}

/**
 * 为 cursor 生成 Rule 文件（.cursor/rules/basksoft-ai.mdc）。
 * 复用 SKILL.md 的 description 作为 rule 的 description；alwaysApply:false + description
 * 使 Cursor 在匹配语义时自动把该规则注入上下文（≈ CodeBuddy 的自动触发）。
 */
function genCursorRule(skillRoot: string, description: string, body: string): void {
  const dir = path.join(skillRoot, ".cursor", "rules");
  fs.mkdirSync(dir, { recursive: true });
  const out = path.join(dir, "basksoft-ai.mdc");
  const fm = [
    "---",
    `description: ${description}`,
    "alwaysApply: false",
    "globs:",
    "  - '**/*.json'",
    "  - '**/*.xml'",
    "---",
    "",
  ].join("\n");
  const header = [
    "<!--",
    "此文件由 scripts/install-skill.ts 自动生成；如需修改请改 SKILL.md 后重跑脚本。",
    "-->",
    "",
  ].join("\n");
  fs.writeFileSync(out, fm + header + body, "utf8");
  console.log(`  已生成: ${out}`);
}

/**
 * 为 generic（未配置的第三方 agent）生成通用 skill 说明文件（项目级 baskreport.md）。
 *
 * 第三方 agent 没有统一的能力目录规范，故不采用目录链接，而是把 SKILL.md 正文 + 使用指引
 * 落到项目内一份独立的说明文件中，交由使用者按第三方 agent 自己的规范加载：
 *   - 复制为系统提示 / 附加上下文 / MCP 工具描述等；
 *   - 文件本身就是 SKILL.md 的可分发快照，agent 只需读它 + 能执行 CLI 即可使用。
 */
function genGenericSkill(skillRoot: string, body: string): void {
  fs.mkdirSync(skillRoot, { recursive: true }); // 目标目录可能不存在（--project-root 指向新目录）
  const out = path.join(skillRoot, "baskreport.md");
  const header = [
    "<!--",
    "此文件由 scripts/install-skill.ts --clients generic 自动生成；如需修改请改 SKILL.md 后重跑。",
    "面向未配置的第三方 agent（如 Gemini CLI、Copilot、自研 agent 等）。",
    "用法：把本文件作为该 agent 的 skill / 附加上下文 / 系统提示加载，",
    "并确保 agent 能执行 CLI（全局 `baskreport` 或 `npx baskreport-ai-report-generator`）。",
    "-->",
    "",
  ].join("\n");
  fs.writeFileSync(out, header + body, "utf8");
  console.log(`  已生成: ${out}`);
}

/**
 * 为 codex（OpenAI Codex CLI）生成项目级指令文件 AGENTS.md。
 * Codex CLI 官方支持项目根 AGENTS.md 作为自动加载的项目指令文件（类似 CLAUDE.md/.cursorrules）；
 * 无统一用户级 skills 目录规范，故采用项目级 AGENTS.md 作为最稳妥的装载方式。
 */
function genAgentsMd(skillRoot: string, body: string): void {
  fs.mkdirSync(skillRoot, { recursive: true });
  const out = path.join(skillRoot, "AGENTS.md");
  const header = [
    "<!--",
    "此文件由 scripts/install-skill.ts --clients codex 自动生成；如需修改请改 SKILL.md 后重跑。",
    "OpenAI Codex CLI 会自动加载项目根的 AGENTS.md；其中包含 BaskReport 报表生成技能说明。",
    "-->",
    "",
  ].join("\n");
  fs.writeFileSync(out, header + body, "utf8");
  console.log(`  已生成: ${out}`);
}

function main(): void {
  const opts = parseArgs(process.argv.slice(2));

  // 仓库根目录 = scripts 的上一级（ESM 下用 import.meta.url 推导 __dirname）
  const skillRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");

  // 卸载模式：委托给可编译版 runUninstall（与 baskreport uninstall 共用同一套安全删除逻辑）
  if (opts.uninstall) {
    runUninstall({ clients: opts.clients, build: false, projectRoot: skillRoot }, skillRoot);
    return;
  }

  const skillMd = path.join(skillRoot, "SKILL.md");
  if (!fs.existsSync(skillMd)) {
    throw new Error(`未找到 SKILL.md，请确认脚本位于 <repo>/scripts/ 目录下。skillRoot=${skillRoot}`);
  }

  const skillName = path.basename(skillRoot);
  console.log(`Skill 名称: ${skillName}`);
  console.log(`仓库路径:   ${skillRoot}`);
  console.log(`目标客户端: ${opts.clients.join(", ")}`);

  // 构建前置
  if (opts.build) {
    console.log("\n[1/3] 构建产物 (npm install && npm run build)...");
    execSync("npm install && npm run build", { cwd: skillRoot, stdio: "inherit" });
  } else {
    console.log("\n[1/3] 跳过构建 (使用 --build 可前置构建)");
  }

  // 读取 SKILL.md 的 description 与正文（供 claude / cursor 复用）
  const { description, body } = readSkillMeta(skillMd);

  // 安装
  console.log("[2/3] 创建客户端链接 / 生成命令与规则...");
  const homeDir = os.homedir();

  for (const client of opts.clients) {
    if (client === "claude") {
      // 项目级命令文件（跟随 git，落盘到仓库）
      genClaudeCommand(skillRoot, body);
      continue;
    }
    if (client === "cursor") {
      // 项目级 rule 文件（跟随 git，落盘到仓库）
      genCursorRule(skillRoot, description, body);
      continue;
    }
    if (client === "generic") {
      // 项目级通用 skill 说明文件（供第三方 agent 加载）
      genGenericSkill(skillRoot, body);
      continue;
    }
    if (client === "codex") {
      // 项目级 AGENTS.md（Codex CLI 自动加载）
      genAgentsMd(skillRoot, body);
      continue;
    }

    // codebuddy / workbuddy / gemini：用户级目录链接方式（~/.{client}/skills）
    const skillsDir = path.join(homeDir, `.${client}`, "skills");
    const linkPath = path.join(skillsDir, skillName);

    fs.mkdirSync(skillsDir, { recursive: true });

    // 已存在：仅移除链接，绝不删除真实目录
    if (fs.existsSync(linkPath)) {
      if (isLink(linkPath)) {
        console.log(`  移除旧链接: ${linkPath}`);
        fs.rmSync(linkPath, { force: true, recursive: true });
      } else {
        throw new Error(
          `目标已存在且为真实目录，已中止以免误删数据: ${linkPath}\n请手动处理后再运行脚本。`
        );
      }
    }

    // 创建链接（Windows 用 junction）
    const type = process.platform === "win32" ? "junction" : "dir";
    fs.symlinkSync(skillRoot, linkPath, type);
    console.log(`  已创建: ${linkPath} -> ${skillRoot}`);
  }

  console.log("\n[3/3] 完成。");
  console.log(
    `  - codebuddy / workbuddy / gemini：在对应客户端重新加载即可使用 skill '${skillName}'。`
  );
  console.log(
    `  - claude：在项目内用 /baskreport 触发（命令文件位于 .claude/commands/baskreport.md）。`
  );
  console.log(
    `  - codex：打开项目后 Codex CLI 会自动加载 AGENTS.md（含 BaskReport 技能说明）。`
  );
  console.log(
    `  - cursor：打开项目后，提及 BaskReport 报表需求时会自动加载 .cursor/rules/basksoft-ai.mdc。`
  );
  console.log(
    `  - generic：把项目内 baskreport.md 交给第三方 agent 加载（详见该文件头部指引）。`
  );
}

main();
