#!/usr/bin/env node

import { execFileSync } from "node:child_process";
import { promises as fs } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import os from "node:os";

const REPO_URL = "https://github.com/obra/superpowers.git";
const VENDOR_DIRS = ["skills", "hooks"];
const DEPRECATED_RE = /^---[\s\S]*?description:\s*"Deprecated[\s\S]*?^---/m;
const SAFE_REF = /^[a-zA-Z0-9._\-/]+$/;

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const pluginRoot = path.resolve(__dirname, "..");
const versionFile = path.join(pluginRoot, ".superpowers-version.json");

function git(args, opts = {}) {
  return execFileSync("git", args, { encoding: "utf-8", ...opts }).trim();
}

async function main() {
  const ref = process.argv[2];
  if (!ref) {
    console.error("Usage: sync-superpowers.mjs <git-ref>");
    console.error("  git-ref: a tag (v5.0.5) or commit SHA");
    process.exit(1);
  }
  if (!SAFE_REF.test(ref)) {
    console.error(`Invalid ref: "${ref}". Only alphanumeric, dots, dashes, slashes allowed.`);
    process.exit(1);
  }

  const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "superpowers-"));

  try {
    console.log(`Cloning obra/superpowers at ${ref}...`);
    git(["clone", "--no-checkout", REPO_URL, tmpDir]);
    git(["checkout", ref], { cwd: tmpDir });

    const commit = git(["rev-parse", "HEAD"], { cwd: tmpDir });

    let upstreamVersion = null;
    try {
      const manifest = JSON.parse(
        await fs.readFile(
          path.join(tmpDir, ".cursor-plugin", "plugin.json"),
          "utf-8"
        )
      );
      upstreamVersion = manifest.version;
    } catch {
      // no manifest
    }

    console.log(`Resolved: commit=${commit.slice(0, 12)}, ref=${ref}, version=${upstreamVersion}`);

    // Remove previously vendored files
    const currentVersion = JSON.parse(await fs.readFile(versionFile, "utf-8"));
    if (currentVersion.vendoredFiles?.length) {
      console.log(
        `Removing ${currentVersion.vendoredFiles.length} previously vendored files...`
      );
      for (const f of currentVersion.vendoredFiles) {
        const abs = path.resolve(pluginRoot, f);
        if (!abs.startsWith(pluginRoot + path.sep)) continue;
        await fs.rm(abs, { force: true });
      }
      for (const dir of VENDOR_DIRS) {
        await removeEmptyDirs(path.join(pluginRoot, dir));
      }
    }

    // Copy vendor directories
    const vendoredFiles = [];
    for (const dir of VENDOR_DIRS) {
      const src = path.join(tmpDir, dir);
      const dest = path.join(pluginRoot, dir);
      try {
        await fs.access(src);
      } catch {
        console.log(`  Skipping ${dir}/ (not found in source)`);
        continue;
      }
      const files = await copyDir(src, dest);
      vendoredFiles.push(...files.map((f) => path.relative(pluginRoot, f)));
    }

    // Remove deprecated files (e.g. upstream stubs that say "Deprecated" in frontmatter)
    const removed = [];
    for (const relPath of [...vendoredFiles]) {
      if (!relPath.endsWith(".md")) continue;
      const abs = path.join(pluginRoot, relPath);
      const content = await fs.readFile(abs, "utf-8");
      if (DEPRECATED_RE.test(content)) {
        await fs.rm(abs, { force: true });
        vendoredFiles.splice(vendoredFiles.indexOf(relPath), 1);
        removed.push(relPath);
      }
    }
    if (removed.length) {
      console.log(`  Removed ${removed.length} deprecated file(s): ${removed.join(", ")}`);
      for (const dir of VENDOR_DIRS) {
        await removeEmptyDirs(path.join(pluginRoot, dir));
      }
    }

    // Write version manifest
    const newVersion = {
      source: REPO_URL,
      commit,
      ref,
      version: upstreamVersion,
      syncedAt: new Date().toISOString(),
      vendoredFiles: vendoredFiles.sort(),
    };
    await fs.writeFile(versionFile, JSON.stringify(newVersion, null, 2) + "\n");

    console.log("\n--- Sync complete ---");
    console.log(`  Pinned to: ${ref} (commit ${commit.slice(0, 12)}, v${upstreamVersion})`);
    console.log(`  Files vendored: ${vendoredFiles.length}`);
    console.log(`  Version manifest: .superpowers-version.json`);
    console.log("\nNext steps:");
    console.log("  1. Review changes: git diff");
    console.log("  2. Commit: git add plugins/humand-tech && git commit");
  } finally {
    await fs.rm(tmpDir, { recursive: true, force: true });
  }
}

async function copyDir(src, dest) {
  const copied = [];
  const entries = await fs.readdir(src, { withFileTypes: true });
  await fs.mkdir(dest, { recursive: true });
  for (const entry of entries) {
    const srcPath = path.join(src, entry.name);
    const destPath = path.join(dest, entry.name);
    if (entry.isDirectory()) {
      copied.push(...(await copyDir(srcPath, destPath)));
    } else {
      await fs.copyFile(srcPath, destPath);
      const stat = await fs.stat(srcPath);
      if (stat.mode & 0o111) {
        await fs.chmod(destPath, stat.mode);
      }
      copied.push(destPath);
    }
  }
  return copied;
}

async function removeEmptyDirs(dir) {
  try {
    const entries = await fs.readdir(dir, { withFileTypes: true });
    for (const entry of entries) {
      if (entry.isDirectory()) {
        await removeEmptyDirs(path.join(dir, entry.name));
      }
    }
    const remaining = await fs.readdir(dir);
    if (remaining.length === 0) {
      await fs.rmdir(dir);
    }
  } catch {
    // directory doesn't exist
  }
}

main().catch((err) => {
  console.error("Sync failed:", err.message);
  process.exit(1);
});
