#!/usr/bin/env node
// Minimal safe release script.
// Usage: node scripts/release.js [major|minor|patch|<version>]
const { execSync } = require("child_process");
const fs = require("fs");
const path = require("path");

const root = path.resolve(__dirname, "..");
const pkgPath = path.join(root, "package.json");

function run(cmd, opts = {}) {
  return execSync(cmd, { stdio: "inherit", cwd: root, ...opts });
}
function runOutput(cmd) {
  return execSync(cmd, { cwd: root }).toString().trim();
}
function bumpSemver(current, spec) {
  if (["major","minor","patch"].includes(spec)) {
    const [maj, min, patch] = current.split(".").map(n=>parseInt(n,10));
    if (spec==="major") return `${maj+1}.0.0`;
    if (spec==="minor") return `${maj}.${min+1}.0`;
    return `${maj}.${min}.${patch+1}`;
  }
  if (!/^\d+\.\d+\.\d+$/.test(spec)) throw new Error("Invalid version spec");
  return spec;
}

(async () => {
  const arg = process.argv[2] || "patch";
  const pkgRaw = fs.readFileSync(pkgPath, "utf8");
  const pkg = JSON.parse(pkgRaw);
  const oldVersion = pkg.version;
  const newVersion = bumpSemver(oldVersion, arg);
  let committed = false;
  let tagged = false;
  let pushedTags = false;
  try {
    // refuse to run if there are unstaged/uncommitted changes
    const status = runOutput("git status --porcelain");
    if (status) throw new Error("Repository has uncommitted changes; please commit or stash before releasing.");

    console.log("Running tests...");
    run("npm run test:ci");

    console.log("Building...");
    run("npm run build");

    // write new version
    pkg.version = newVersion;
    fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
    console.log(`Bumped version: ${oldVersion} -> ${newVersion}`);

    // commit
    run(`git add ${pkgPath}`);
    run(`git commit -m "chore(release): v${newVersion} - bump from v${oldVersion}"`);
    committed = true;

    // ensure tag doesn't already exist locally
    let localTagExists = false;
    try {
      runOutput(`git rev-parse --verify refs/tags/v${newVersion}`);
      localTagExists = true;
    } catch (_) {
      localTagExists = false;
    }
    if (localTagExists) throw new Error(`Tag v${newVersion} already exists locally — aborting to avoid overwrite.`);

    // ensure tag doesn't exist on remote
    const remoteTagInfo = (() => {
      try { return runOutput(`git ls-remote --tags origin v${newVersion}`); } catch (_) { return ""; }
    })();
    if (remoteTagInfo) throw new Error(`Tag v${newVersion} already exists on remote — aborting to avoid overwrite.`);

    // tag
    run(`git tag -a v${newVersion} -m "Release v${newVersion}"`);
    tagged = true;

    // push commit and tags
    run("git push");
    run("git push --tags");
    pushedTags = true;

    // publish
    console.log("Publishing to npm...");
    const publishCmd = pkg.name && pkg.name.startsWith("@") ? "npm publish --access public" : "npm publish";
    run(publishCmd);

    console.log(`Release v${newVersion} succeeded.`);
    process.exit(0);
  } catch (err) {
    console.error("Release failed:", err.message || err);
    try {
      // delete local tag
      if (tagged) {
        try { run(`git tag -d v${newVersion}`); } catch {}
        if (pushedTags) {
          try { run(`git push origin :refs/tags/v${newVersion}`); } catch {}
        }
      }
      // undo commit if made
      if (committed) {
        try { run("git reset --hard HEAD~1"); } catch {
          // fallback: restore package.json content
          fs.writeFileSync(pkgPath, pkgRaw, "utf8");
        }
      } else {
        // restore package.json
        fs.writeFileSync(pkgPath, pkgRaw, "utf8");
      }
    } catch (rbErr) {
      console.error("Rollback error:", rbErr.message || rbErr);
    }
    process.exit(1);
  }
})();
