#!/usr/bin/env node
/**
 * fix-button-has-type.js
 *
 * Bulk-fix the react-doctor/button-has-type warning by adding
 * `type="button"` to every `<button>` JSX element that doesn't
 * already have a `type` attribute. Skips elements that already
 * declare `type="submit"`, `type="reset"`, `type="button"`, or
 * the corresponding JSX expressions.
 *
 * Why default to "button": A bare `<button>` in HTML defaults to
 * `type="submit"`, which accidentally submits the nearest form.
 * The vast majority of buttons in admin UIs are click handlers
 * (not form submitters), so "button" is the safer default. When
 * a button IS a form submit (e.g. inside `<form onSubmit>`), the
 * form handler typically uses its own submit semantics, but
 * authors can always override by adding `type="submit"` explicitly.
 *
 * Idempotent: re-running this script is a no-op once applied.
 */
const fs = require("node:fs");
const path = require("node:path");
const { execSync } = require("node:child_process");

// Find all .tsx and .jsx files in src/ that are tracked by git
function listFiles() {
  const out = execSync(
    "git ls-files 'src/**/*.tsx' 'src/**/*.jsx' 'src/**/*.ts'",
    { encoding: "utf8" }
  );
  return out.split("\n").filter(Boolean);
}

const FILES = listFiles();
let totalFixed = 0;
let totalSkipped = 0;

// Match <button ... > opening tag (or self-closing) that doesn't have type=
// Supports multi-line tags.
// We do this in a stateful loop to properly skip <button> tags that DO have type=
function fixFile(file) {
  const original = fs.readFileSync(file, "utf8");
  let out = "";
  let i = 0;
  let changed = false;

  while (i < original.length) {
    // Look for "<button" (case-sensitive, lowercase to match JSX conventions)
    const buttonIdx = original.indexOf("<button", i);
    if (buttonIdx === -1) {
      out += original.slice(i);
      break;
    }
    // Copy everything up to the match
    out += original.slice(i, buttonIdx);
    i = buttonIdx;

    // Find the closing > of this tag (handle quoted attrs and > inside strings)
    let j = buttonIdx + "<button".length;
    let inSingle = false;
    let inDouble = false;
    let inBrace = 0;
    let tagEnd = -1;
    let isSelfClosing = false;
    while (j < original.length) {
      const c = original[j];
      if (inSingle) {
        if (c === "'" && original[j - 1] !== "\\") inSingle = false;
      } else if (inDouble) {
        if (c === '"' && original[j - 1] !== "\\") inDouble = false;
      } else if (inBrace > 0) {
        if (c === "{") inBrace++;
        else if (c === "}") inBrace--;
      } else {
        if (c === "'") inSingle = true;
        else if (c === '"') inDouble = true;
        else if (c === "{") inBrace++;
        else if (c === ">") {
          tagEnd = j;
          isSelfClosing = original[j - 1] === "/";
          break;
        }
      }
      j++;
    }

    if (tagEnd === -1) {
      // Unterminated tag — leave as-is
      out += original.slice(i);
      break;
    }

    const tagContent = original.slice(buttonIdx, tagEnd + 1);
    // Already has type= ? skip
    if (/\stype\s*=/.test(tagContent)) {
      out += tagContent;
      i = tagEnd + 1;
      totalSkipped++;
      continue;
    }

    // Insert type="button" right after "<button"
    const insertAt = buttonIdx + "<button".length;
    const newTag =
      original.slice(buttonIdx, insertAt) +
      ' type="button"' +
      original.slice(insertAt, tagEnd + 1);

    out += newTag;
    i = tagEnd + 1;
    changed = true;
    totalFixed++;
  }

  if (changed) {
    fs.writeFileSync(file, out);
  }
}

for (const f of FILES) {
  try {
    fixFile(f);
  } catch (err) {
    console.error(`error processing ${f}: ${err.message}`);
  }
}

console.log(`Fixed ${totalFixed} <button> elements; skipped ${totalSkipped} already-typed.`);
