// src/actions/lintlint.ts
import { spawnSync as spawnSync2 } from "child_process";
import {
  existsSync as existsSync4,
  readFileSync as readFileSync3,
  writeFileSync as writeFileSync2
} from "fs";
import PATH3 from "path";
import chalk4 from "chalk";
import { findUp } from "find-up";

// src/lib/packageManagerRuntime.ts
import { spawnSync } from "child_process";
import fs from "fs";
import chalk from "chalk";
var DISPLAY_NAMES = {
  bun: "Bun",
  npm: "npm",
  pnpm: "pnpm",
  yarn: "Yarn"
};
var MAC_INSTALL_COMMANDS = {
  bun: "brew install oven-sh/bun/bun",
  npm: "brew install node",
  pnpm: "brew install pnpm",
  yarn: "brew install yarn"
};
function currentPlatform() {
  if (process.platform === "darwin" || process.platform === "linux" || process.platform === "win32") {
    return process.platform;
  }
  return "unknown";
}
function askYesNo(question) {
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
    return false;
  }
  process.stdout.write(question);
  const buffer = Buffer.alloc(1024);
  const bytesRead = fs.readSync(process.stdin.fd, buffer, 0, buffer.length, null);
  const answer = buffer.toString("utf8", 0, bytesRead).trim().toLowerCase();
  return answer === "y" || answer === "yes";
}
function isCommandInstalled(command) {
  const result = spawnSync(command, ["--version"], {
    encoding: "utf8",
    shell: false,
    stdio: "ignore"
  });
  return !result.error && result.status === 0;
}
function isPackageManagerInstalled(pm) {
  return isCommandInstalled(pm);
}
function getPackageManagerInstallPlan(pm, platform = currentPlatform()) {
  const displayName = DISPLAY_NAMES[pm];
  if (platform === "darwin") {
    const command = MAC_INSTALL_COMMANDS[pm];
    return {
      command,
      manualInstructions: [
        `Install ${displayName} with: ${command}`
      ],
      packageManager: pm,
      platform,
      supported: true
    };
  }
  if (platform === "linux") {
    return {
      manualInstructions: [
        `Automatic installation for ${displayName} is not implemented on Linux yet.`,
        `Install ${displayName} manually and re-run the command.`
      ],
      packageManager: pm,
      platform,
      supported: false
    };
  }
  if (platform === "win32") {
    return {
      manualInstructions: [
        `Automatic installation for ${displayName} is not implemented on Windows yet.`,
        `Install ${displayName} manually and re-run the command.`
      ],
      packageManager: pm,
      platform,
      supported: false
    };
  }
  return {
    manualInstructions: [
      `Automatic installation for ${displayName} is not implemented on ${platform} yet.`,
      `Install ${displayName} manually and re-run the command.`
    ],
    packageManager: pm,
    platform,
    supported: false
  };
}
function printManualInstructions(plan, context) {
  console.error(chalk.yellow(`${DISPLAY_NAMES[plan.packageManager]} is required for "${context.operation}".`));
  for (const line of plan.manualInstructions) {
    console.error(chalk.gray(`  ${line}`));
  }
}
function installWithHomebrew(pm, cwd) {
  const command = MAC_INSTALL_COMMANDS[pm];
  const result = spawnSync("brew", command.replace("brew ", "").split(" "), {
    cwd,
    encoding: "utf8",
    shell: false,
    stdio: "inherit"
  });
  return result.status === 0;
}
function ensurePackageManagerInstalled(pm, context) {
  if (isPackageManagerInstalled(pm)) return true;
  const plan = getPackageManagerInstallPlan(pm, context.platform ?? currentPlatform());
  const displayName = DISPLAY_NAMES[pm];
  console.error(chalk.yellow(`${context.operation} requires ${displayName}, but it is not installed.`));
  if (!plan.supported) {
    printManualInstructions(plan, context);
    return false;
  }
  if (!isCommandInstalled("brew")) {
    console.error(chalk.yellow("Homebrew is not installed, so automatic installation is unavailable."));
    console.error(chalk.gray("  Install Homebrew from https://brew.sh and then run:"));
    console.error(chalk.gray(`  ${plan.command}`));
    return false;
  }
  const confirmed = askYesNo(chalk.cyan(`Install ${displayName} with Homebrew now? [y/N] `));
  if (!confirmed) {
    console.error(chalk.gray(`Skipping install. Run "${plan.command}" manually and then retry.`));
    return false;
  }
  console.error(chalk.cyan(`Installing ${displayName} via Homebrew...
`));
  if (!installWithHomebrew(pm, context.cwd)) {
    console.error(chalk.red(`Homebrew failed to install ${displayName}.`));
    console.error(chalk.gray(`Run "${plan.command}" manually and then retry.`));
    return false;
  }
  if (!isPackageManagerInstalled(pm)) {
    console.error(chalk.red(`${displayName} still does not appear to be installed after Homebrew completed.`));
    console.error(chalk.gray(`Run "${plan.command}" manually and then retry.`));
    return false;
  }
  console.error(chalk.green(`${displayName} installed successfully.`));
  return true;
}
function failureResult() {
  return {
    output: ["", ""],
    pid: 0,
    signal: null,
    status: 1,
    stderr: "",
    stdout: ""
  };
}
function spawnPackageManagerSync(pm, args, options) {
  if (!ensurePackageManagerInstalled(pm, options.context)) {
    return failureResult();
  }
  return spawnSync(pm, args, { encoding: "utf8", ...options });
}

// src/pm/workspaces.ts
import { existsSync, readFileSync } from "fs";
import PATH from "path";
import { globSync } from "glob";
function readJsonFile(path) {
  if (!existsSync(path)) return void 0;
  try {
    return JSON.parse(readFileSync(path, "utf8"));
  } catch {
    return void 0;
  }
}
function readRootPackageJson(cwd = process.cwd()) {
  return readJsonFile(PATH.resolve(cwd, "package.json"));
}
function detectPackageManagerFromPackageJson(cwd = process.cwd()) {
  const packageManager = readRootPackageJson(cwd)?.packageManager;
  if (!packageManager) return void 0;
  return packageManager.split("@")[0];
}

// src/pm/detectPackageManager.ts
import { existsSync as existsSync2 } from "fs";
var knownPackageManagers = /* @__PURE__ */ new Set(["bun", "npm", "pnpm", "yarn"]);
function detectPackageManager() {
  const packageManager = detectPackageManagerFromPackageJson();
  if (packageManager && knownPackageManagers.has(packageManager)) {
    return packageManager;
  }
  if (existsSync2("pnpm-lock.yaml") || existsSync2("pnpm-workspace.yaml")) return "pnpm";
  if (existsSync2("bun.lock") || existsSync2("bun.lockb")) return "bun";
  if (existsSync2("yarn.lock") || existsSync2(".yarnrc.yml")) return "yarn";
  if (existsSync2("package-lock.json") || existsSync2("npm-shrinkwrap.json")) return "npm";
  return "yarn";
}

// src/lib/runInstall.ts
import chalk2 from "chalk";
function runInstall(cwd) {
  const pm = detectPackageManager();
  console.log(chalk2.gray(`Running ${pm} install...`));
  const result = spawnPackageManagerSync(pm, ["install"], {
    context: { cwd, operation: `Run ${pm} install` },
    cwd,
    encoding: "utf8",
    stdio: "inherit"
  });
  if (result.status !== 0) {
    console.warn(chalk2.yellow(`${pm} install failed`));
    return false;
  }
  console.log(chalk2.green("Dependencies installed"));
  return true;
}

// src/actions/lint-init.ts
import {
  existsSync as existsSync3,
  readFileSync as readFileSync2,
  unlinkSync,
  writeFileSync
} from "fs";
import { createRequire } from "module";
import PATH2 from "path";
import { createInterface } from "readline";
import chalk3 from "chalk";
import { globSync as globSync2 } from "glob";
var require2 = createRequire(import.meta.url);
var toolchainPkgPath = require2.resolve("@xylabs/toolchain/package.json");
var toolchainVersion = JSON.parse(readFileSync2(toolchainPkgPath, "utf8")).version;
function hasReactDependency(packageJsonPath) {
  try {
    const content = readFileSync2(packageJsonPath, "utf8");
    const pkg = JSON.parse(content);
    const deps = pkg.dependencies;
    const devDeps = pkg.devDependencies;
    const peerDeps = pkg.peerDependencies;
    return deps?.react != null || devDeps?.react != null || peerDeps?.react != null;
  } catch {
    return false;
  }
}
function detectReactInMonorepo(cwd, verbose) {
  const rootPkgPath = PATH2.resolve(cwd, "package.json");
  if (hasReactDependency(rootPkgPath)) {
    if (verbose) console.log(chalk3.gray("  React detected in package.json"));
    return true;
  }
  const packageJsonPaths = globSync2("packages/**/package.json", {
    cwd,
    ignore: ["**/node_modules/**"]
  });
  for (const relPath of packageJsonPaths) {
    const fullPath = PATH2.resolve(cwd, relPath);
    if (hasReactDependency(fullPath)) {
      if (verbose) console.log(chalk3.gray(`  React detected in ${relPath}`));
      return true;
    }
  }
  return false;
}

// src/actions/lintlint.ts
function parseRuleValue(value) {
  if (typeof value === "string") {
    return { level: value };
  }
  if (typeof value === "number") {
    return { level: String(value) };
  }
  if (Array.isArray(value) && value.length > 0) {
    return {
      level: String(value[0]),
      options: value.length > 1 ? value.slice(1) : void 0
    };
  }
  return void 0;
}
function normalizeLevel(level) {
  if (level === "0" || level === "off") return "off";
  if (level === "1" || level === "warn") return "warn";
  if (level === "2" || level === "error") return "error";
  return level;
}
function rulesMatch(a, b) {
  if (normalizeLevel(a.level) !== normalizeLevel(b.level)) return false;
  return JSON.stringify(a.options) === JSON.stringify(b.options);
}
function formatRule(entry) {
  if (entry.options) {
    return JSON.stringify([entry.level, ...entry.options]);
  }
  return JSON.stringify([entry.level]);
}
function mergeRulesFromBlocks(blocks) {
  const merged = /* @__PURE__ */ new Map();
  for (const block of blocks) {
    if (!block.rules) continue;
    for (const [name, value] of Object.entries(block.rules)) {
      const parsed = parseRuleValue(value);
      if (parsed) merged.set(name, parsed);
    }
  }
  return merged;
}
function detectSharedPackage(source) {
  if (source.includes("@xylabs/eslint-config-react-flat")) return "@xylabs/eslint-config-react-flat";
  if (source.includes("@xylabs/eslint-config-flat")) return "@xylabs/eslint-config-flat";
  return void 0;
}
function extractLocalRuleBlocks(source) {
  const blocks = [];
  const ruleBlockRegex = /\{\s*(?:files\s*:\s*\[.*?\]\s*,\s*)?rules\s*:\s*\{([^}]*(?:\{[^}]*\}[^}]*)*)\}/g;
  let match;
  while ((match = ruleBlockRegex.exec(source)) !== null) {
    blocks.push(match[1]);
  }
  return blocks;
}
function extractRulesFromSourceBlocks(blocks) {
  const rules = /* @__PURE__ */ new Map();
  for (const block of blocks) {
    const ruleRegex = /['"]([^'"]+)['"]\s*:\s*(\[[\s\S]*?\](?=\s*,|\s*$))/gm;
    let match;
    while ((match = ruleRegex.exec(block)) !== null) {
      rules.set(match[1], match[2]);
    }
  }
  return rules;
}
function extractConfigBlocks(sharedModule) {
  const config = sharedModule.config ?? sharedModule.default;
  if (Array.isArray(config)) return config;
  return [];
}
async function resolveSharedConfig(configDir, sharedPkg) {
  try {
    const sharedModule = await import(sharedPkg);
    return extractConfigBlocks(sharedModule);
  } catch {
    const distPath = PATH3.resolve(configDir, "node_modules", sharedPkg, "dist", "node", "index.mjs");
    try {
      const sharedModule = await import(distPath);
      return extractConfigBlocks(sharedModule);
    } catch {
      const neutralPath = PATH3.resolve(configDir, "node_modules", sharedPkg, "dist", "neutral", "index.mjs");
      const sharedModule = await import(neutralPath);
      return extractConfigBlocks(sharedModule);
    }
  }
}
async function loadSharedRules(configDir, sharedPkg, verbose) {
  const sharedBlocks = await resolveSharedConfig(configDir, sharedPkg);
  const sharedRules = mergeRulesFromBlocks(sharedBlocks);
  if (verbose) {
    console.log(chalk4.gray(`Shared config defines ${sharedRules.size} rules`));
  }
  if (sharedRules.size === 0) {
    console.error(chalk4.red("Could not load rules from shared config. Is it installed and built?"));
    return void 0;
  }
  return sharedRules;
}
async function loadLocalRules(eslintConfigPath, source, verbose) {
  const localModule = await import(eslintConfigPath);
  const localConfig = localModule.default ?? localModule;
  const localBlocks = Array.isArray(localConfig) ? localConfig : [localConfig];
  const resolved = mergeRulesFromBlocks(localBlocks);
  const localRuleBlocks = extractLocalRuleBlocks(source);
  const explicit = extractRulesFromSourceBlocks(localRuleBlocks);
  if (verbose) {
    console.log(chalk4.gray(`Local config has ${explicit.size} explicit rule setting(s)`));
  }
  return { explicit, resolved };
}
function compareRules(explicitRuleNames, allResolvedRules, sharedRules) {
  const redundant = [];
  const overrides = [];
  const additions = [];
  for (const ruleName of explicitRuleNames.keys()) {
    const resolvedEntry = allResolvedRules.get(ruleName);
    const sharedEntry = sharedRules.get(ruleName);
    if (!resolvedEntry) continue;
    if (!sharedEntry) {
      additions.push({ local: resolvedEntry, rule: ruleName });
    } else if (rulesMatch(resolvedEntry, sharedEntry)) {
      redundant.push({
        local: resolvedEntry,
        rule: ruleName,
        shared: sharedEntry
      });
    } else {
      overrides.push({
        local: resolvedEntry,
        rule: ruleName,
        shared: sharedEntry
      });
    }
  }
  return {
    additions,
    overrides,
    redundant
  };
}
function reportResults({
  additions,
  overrides,
  redundant
}, verbose) {
  if (redundant.length > 0) {
    console.log(chalk4.yellow(`
${redundant.length} redundant rule(s) (same as shared config \u2014 can be removed):`));
    for (const { rule, local } of redundant) {
      console.log(chalk4.yellow(`  ${rule}: ${formatRule(local)}`));
    }
  }
  if (overrides.length > 0) {
    console.log(chalk4.cyan(`
${overrides.length} rule override(s) (different from shared config):`));
    for (const {
      rule,
      local,
      shared
    } of overrides) {
      console.log(chalk4.cyan(`  ${rule}:`));
      console.log(chalk4.gray(`    shared: ${formatRule(shared)}`));
      console.log(chalk4.white(`    local:  ${formatRule(local)}`));
    }
  }
  if (additions.length > 0 && verbose) {
    console.log(chalk4.gray(`
${additions.length} local addition(s) (not in shared config):`));
    for (const { rule, local } of additions) {
      console.log(chalk4.gray(`  ${rule}: ${formatRule(local)}`));
    }
  }
  if (redundant.length === 0 && overrides.length === 0) {
    console.log(chalk4.green("No redundant or overridden rules found"));
  }
}
function fixRedundantRules(eslintConfigPath, redundant) {
  let updated = readFileSync3(eslintConfigPath, "utf8");
  const original = updated;
  for (const { rule } of redundant) {
    const escaped = rule.replaceAll("/", String.raw`\/`);
    const pattern = new RegExp(String.raw`[ \t]*['"]${escaped}['"]\s*:\s*\[[^\]]*\],?[ \t]*\n?`, "g");
    updated = updated.replace(pattern, "");
  }
  updated = updated.replaceAll(/\{\s*rules\s*:\s*\{\s*\}\s*,?\s*\}\s*,?/g, "");
  updated = updated.replaceAll(/\n{3,}/g, "\n\n");
  if (updated !== original) {
    writeFileSync2(eslintConfigPath, updated, "utf8");
    console.log(chalk4.green(`
Fixed: removed ${redundant.length} redundant rule(s)`));
  }
}
function formatConfigFile(eslintConfigPath) {
  spawnSync2("eslint", ["--fix", eslintConfigPath], { stdio: "inherit" });
}
function fixConfigMismatch(eslintConfigPath, configDir, source, sharedPkg, expectedPkg) {
  const updated = source.replaceAll(sharedPkg, expectedPkg);
  writeFileSync2(eslintConfigPath, updated, "utf8");
  console.log(chalk4.green(`Fixed: replaced ${sharedPkg} with ${expectedPkg}`));
  const packageJsonPath = PATH3.resolve(configDir, "package.json");
  if (!existsSync4(packageJsonPath)) return;
  const content = readFileSync3(packageJsonPath, "utf8");
  const pkg = JSON.parse(content);
  const devDeps = pkg.devDependencies ?? {};
  const oldVersion = devDeps[sharedPkg];
  if (!oldVersion) return;
  delete devDeps[sharedPkg];
  devDeps[expectedPkg] = oldVersion;
  const sorted = Object.fromEntries(Object.entries(devDeps).toSorted(([a], [b]) => a.localeCompare(b)));
  pkg.devDependencies = sorted;
  writeFileSync2(packageJsonPath, `${JSON.stringify(pkg, null, 2)}
`, "utf8");
  console.log(chalk4.green(`Updated package.json: ${sharedPkg} \u2192 ${expectedPkg}`));
  runInstall(configDir);
}
function checkConfigVariant(eslintConfigPath, configDir, source, sharedPkg, fix, verbose) {
  const hasReact = detectReactInMonorepo(configDir, verbose);
  const expectedPkg = hasReact ? "@xylabs/eslint-config-react-flat" : "@xylabs/eslint-config-flat";
  if (sharedPkg === expectedPkg) return false;
  console.log(chalk4.yellow(`
Config mismatch: using ${sharedPkg} but ${hasReact ? "React packages detected" : "no React packages found"}`));
  console.log(chalk4.yellow(`  Expected: ${expectedPkg}`));
  if (fix) {
    fixConfigMismatch(eslintConfigPath, configDir, source, sharedPkg, expectedPkg);
  }
  return true;
}
async function lintlint({ fix, verbose } = {}) {
  const eslintConfigPath = await findUp(["eslint.config.ts", "eslint.config.mjs"]);
  if (!eslintConfigPath) {
    console.error(chalk4.red("No eslint.config.ts found"));
    return 1;
  }
  const configDir = PATH3.dirname(eslintConfigPath);
  if (verbose) {
    console.log(chalk4.gray(`Found config: ${eslintConfigPath}`));
  }
  const source = readFileSync3(eslintConfigPath, "utf8");
  const sharedPkg = detectSharedPackage(source);
  if (!sharedPkg) {
    console.log(chalk4.yellow("No @xylabs/eslint-config-flat or @xylabs/eslint-config-react-flat imports found"));
    return 0;
  }
  if (verbose) {
    console.log(chalk4.gray(`Shared package: ${sharedPkg}`));
  }
  const hasMismatch = checkConfigVariant(eslintConfigPath, configDir, source, sharedPkg, !!fix, !!verbose);
  const currentSource = hasMismatch && fix ? readFileSync3(eslintConfigPath, "utf8") : source;
  const sharedRules = await loadSharedRules(configDir, sharedPkg, !!verbose);
  if (!sharedRules) return 1;
  const { explicit, resolved } = await loadLocalRules(eslintConfigPath, currentSource, !!verbose);
  const results = compareRules(explicit, resolved, sharedRules);
  reportResults(results, !!verbose);
  if (results.redundant.length > 0 && fix) {
    fixRedundantRules(eslintConfigPath, results.redundant);
  }
  const didFix = fix && (hasMismatch || results.redundant.length > 0);
  if (didFix) {
    formatConfigFile(eslintConfigPath);
  }
  const hasUnfixedMismatch = hasMismatch && !fix;
  const hasUnfixedRedundant = results.redundant.length > 0 && !fix;
  return hasUnfixedMismatch || hasUnfixedRedundant ? 1 : 0;
}
export {
  lintlint
};
//# sourceMappingURL=lintlint.mjs.map