#!/usr/bin/env node
/**
 * Convert Zod 3 chained format calls to the Zod 4 top-level API.
 * Idempotent: re-running on an already-converted file is a no-op.
 *
 * Before: z.string().email()
 * After:  z.email()
 *
 * Mirrors https://zod.dev/v4/changelog and the react-doctor rule
 * `react-doctor/zod-v4-prefer-top-level-string-formats`.
 */
const fs = require("node:fs");
const path = require("node:path");

const SIMPLE_METHODS = [
  "email",
  "url",
  "uuid",
  "cuid",
  "cuid2",
  "nanoid",
  "ulid",
  "emoji",
  "jwt",
];

const SIMPLE_REGEX = new RegExp(
  String.raw`\b(z|zod|schema)(\.string\(\))\.(${SIMPLE_METHODS.join("|")})\(`,
  "g",
);

const ISO_REGEX = new RegExp(
  String.raw`\b(z|zod|schema)(\.string\(\))\.(${"datetime|date|time|ip|ipv4|ipv6|cidr|cidrv4|cidrv6"})\(`,
  "g",
);

function patch(src) {
  let out = src;

  // email/url/uuid/etc — drop the .string() wrapper
  out = out.replace(SIMPLE_REGEX, (_, z, _str, method) => `${z}.${method}(`);

  // iso.* — wrap the leaf call in .iso.<format>(...)
  out = out.replace(ISO_REGEX, (_, z, _str, method) => `${z}.iso.${method}(`);

  return out === src ? null : out;
}

function processFile(filePath) {
  const abs = path.resolve(filePath);
  if (!fs.existsSync(abs)) {
    console.error(`skip (not found): ${abs}`);
    return 0;
  }
  const original = fs.readFileSync(abs, "utf8");
  if (!/from\s+["']zod["']/.test(original)) {
    return 0;
  }
  const patched = patch(original);
  if (patched) {
    fs.writeFileSync(abs, patched, "utf8");
    console.log(`patched: ${abs}`);
    return 1;
  }
  return 0;
}

const files = process.argv.slice(2);
if (files.length === 0) {
  console.error("usage: node scripts/fix-zod-format.js <file>...");
  process.exit(2);
}
let n = 0;
for (const f of files) {
  n += processFile(f);
}
console.log(`done — ${n} files patched`);
