// src/actions/deplint/checkPackage/getUnusedDevDependencies.ts
import chalk2 from "chalk";

// src/actions/deplint/getCliReferencedPackagesFromFiles.ts
import fs3 from "node:fs";
import path3 from "node:path";
import ts from "typescript";

// src/actions/deplint/getBasePackageName.ts
function getBasePackageName(importName) {
  const importNameScrubbed = importName.replaceAll('"', "").trim();
  if (importNameScrubbed.startsWith("@")) {
    const parts = importNameScrubbed.split("/");
    return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : importNameScrubbed;
  }
  return importNameScrubbed.split("/")[0];
}

// src/actions/deplint/getScriptReferencedPackages.ts
import fs2 from "node:fs";
import path2 from "node:path";

// src/actions/deplint/getRequiredPeerDependencies.ts
import fs from "node:fs";
import path from "node:path";
function findDepPackageJson(location, dep) {
  let dir = location;
  while (true) {
    const candidate = path.join(dir, "node_modules", dep, "package.json");
    if (fs.existsSync(candidate)) return candidate;
    const parent = path.dirname(dir);
    if (parent === dir) return void 0;
    dir = parent;
  }
}
function getRequiredPeerDependencies(location, allDeps) {
  const required = /* @__PURE__ */ new Set();
  for (const dep of allDeps) {
    const depPkgPath = findDepPackageJson(location, dep);
    if (!depPkgPath) continue;
    try {
      const raw = fs.readFileSync(depPkgPath, "utf8");
      const pkg = JSON.parse(raw);
      const peerDeps = pkg.peerDependencies;
      const meta = pkg.peerDependenciesMeta;
      if (peerDeps) {
        for (const peer of Object.keys(peerDeps)) {
          if (meta?.[peer]?.optional) continue;
          required.add(peer);
        }
      }
    } catch {
    }
  }
  return required;
}

// src/actions/deplint/getScriptReferencedPackages.ts
function getBinNames(location, dep) {
  const depPkgPath = findDepPackageJson(location, dep);
  if (!depPkgPath) return [];
  try {
    const raw = fs2.readFileSync(depPkgPath, "utf8");
    const pkg = JSON.parse(raw);
    if (!pkg.bin) return [];
    if (typeof pkg.bin === "string") return [pkg.name?.split("/").pop() ?? dep];
    return Object.keys(pkg.bin);
  } catch {
    return [];
  }
}
function tokenizeScript(script) {
  return script.split(/[&|;$()"`\s]+/).map((t) => t.trim()).filter(Boolean);
}
function getScriptReferencedPackages(location, allDeps) {
  const pkgPath = path2.join(location, "package.json");
  let scripts;
  try {
    const raw = fs2.readFileSync(pkgPath, "utf8");
    const pkg = JSON.parse(raw);
    scripts = pkg.scripts ?? {};
  } catch {
    return /* @__PURE__ */ new Set();
  }
  const scriptText = Object.values(scripts).join(" ");
  const tokens = new Set(tokenizeScript(scriptText));
  const binToPackage = /* @__PURE__ */ new Map();
  for (const dep of allDeps) {
    const bins = getBinNames(location, dep);
    for (const bin of bins) {
      binToPackage.set(bin, dep);
    }
  }
  const referenced = /* @__PURE__ */ new Set();
  for (const token of tokens) {
    const baseName = getBasePackageName(token);
    if (allDeps.includes(baseName)) {
      referenced.add(baseName);
    }
    const pkg = binToPackage.get(token);
    if (pkg) {
      referenced.add(pkg);
    }
  }
  return referenced;
}

// src/actions/deplint/getCliReferencedPackagesFromFiles.ts
var shellCommandFunctions = /* @__PURE__ */ new Set(["execSync", "exec"]);
var directExecFunctions = /* @__PURE__ */ new Set(["spawn", "spawnSync", "execFile", "execFileSync"]);
var allExecFunctions = /* @__PURE__ */ new Set([...shellCommandFunctions, ...directExecFunctions]);
function getCommandTokensFromFile(filePath) {
  const tokens = /* @__PURE__ */ new Set();
  let sourceCode;
  try {
    sourceCode = fs3.readFileSync(filePath, "utf8");
  } catch {
    return tokens;
  }
  const isMjsFile = filePath.endsWith(".mjs");
  const sourceFile = ts.createSourceFile(
    path3.basename(filePath),
    sourceCode,
    ts.ScriptTarget.Latest,
    true,
    isMjsFile ? ts.ScriptKind.JS : void 0
  );
  function visit(node) {
    if (ts.isCallExpression(node) && node.arguments.length > 0) {
      const fnName = getFunctionName(node.expression);
      if (fnName && allExecFunctions.has(fnName)) {
        const firstArg = node.arguments[0];
        if (ts.isStringLiteral(firstArg) || ts.isNoSubstitutionTemplateLiteral(firstArg)) {
          const value = firstArg.text;
          if (shellCommandFunctions.has(fnName)) {
            for (const token of tokenizeScript(value)) {
              tokens.add(token);
            }
          } else {
            tokens.add(value);
          }
        } else if (ts.isTemplateExpression(firstArg)) {
          const head = firstArg.head.text;
          if (head) {
            for (const token of tokenizeScript(head)) {
              tokens.add(token);
            }
          }
        }
      }
    }
    ts.forEachChild(node, visit);
  }
  visit(sourceFile);
  return tokens;
}
function getFunctionName(expr) {
  if (ts.isIdentifier(expr)) {
    return expr.text;
  }
  if (ts.isPropertyAccessExpression(expr) && ts.isIdentifier(expr.name)) {
    return expr.name.text;
  }
  return void 0;
}
function getCliReferencedPackagesFromFiles(allFiles, location, allDeps) {
  const allTokens = /* @__PURE__ */ new Set();
  for (const file of allFiles) {
    for (const token of getCommandTokensFromFile(file)) {
      allTokens.add(token);
    }
  }
  if (allTokens.size === 0) return /* @__PURE__ */ new Set();
  const binToPackage = /* @__PURE__ */ new Map();
  for (const dep of allDeps) {
    for (const bin of getBinNames(location, dep)) {
      binToPackage.set(bin, dep);
    }
  }
  const referenced = /* @__PURE__ */ new Set();
  for (const token of allTokens) {
    const baseName = getBasePackageName(token);
    if (allDeps.includes(baseName)) {
      referenced.add(baseName);
    }
    const pkg = binToPackage.get(token);
    if (pkg) {
      referenced.add(pkg);
    }
  }
  return referenced;
}

// src/actions/deplint/implicitDevDependencies.ts
import fs4 from "node:fs";
var hasFileWithExtension = (files, extensions) => files.some((f) => extensions.some((ext) => f.endsWith(ext)));
var tsExtensions = [".ts", ".tsx", ".mts", ".cts"];
var hasTypescriptFiles = ({ allFiles }) => hasFileWithExtension(allFiles, tsExtensions);
var decoratorPattern = /^\s*@[a-zA-Z]\w*/m;
var hasDecorators = ({ allFiles }) => allFiles.filter((f) => tsExtensions.some((ext) => f.endsWith(ext))).some((file) => {
  try {
    const content = fs4.readFileSync(file, "utf8");
    return decoratorPattern.test(content);
  } catch {
    return false;
  }
});
var hasDependencyRequiringTslib = ({ location, allDependencies }) => allDependencies.some((dep) => {
  const pkgPath = findDepPackageJson(location, dep);
  if (!pkgPath) return false;
  try {
    const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf8"));
    const peers = pkg.peerDependencies;
    return peers !== void 0 && "tslib" in peers;
  } catch {
    return false;
  }
});
var importPlugins = /* @__PURE__ */ new Set(["eslint-plugin-import-x", "eslint-plugin-import"]);
function hasImportPlugin({ location, allDependencies }) {
  if (allDependencies.some((d) => importPlugins.has(d))) return true;
  for (const dep of allDependencies) {
    const pkgPath = findDepPackageJson(location, dep);
    if (!pkgPath) continue;
    try {
      const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf8"));
      const transitiveDeps = [
        ...Object.keys(pkg.dependencies ?? {}),
        ...Object.keys(pkg.peerDependencies ?? {})
      ];
      if (transitiveDeps.some((d) => importPlugins.has(d))) return true;
    } catch {
    }
  }
  return false;
}
var hasVitest = ({ allDependencies }) => allDependencies.includes("vitest");
var rules = [
  {
    package: "typescript",
    isNeeded: hasTypescriptFiles
  },
  {
    package: "eslint-import-resolver-typescript",
    isNeeded: (context) => hasTypescriptFiles(context) && context.allDependencies.includes("eslint") && hasImportPlugin(context)
  },
  {
    package: "tslib",
    isNeeded: (context) => hasDecorators(context) || hasDependencyRequiringTslib(context)
  },
  {
    package: "@vitest/coverage-v8",
    isNeeded: hasVitest
  },
  {
    package: "@xylabs/toolchain",
    isNeeded: hasTypescriptFiles
  }
];
function getImplicitDevDependencies(context) {
  const implicit = /* @__PURE__ */ new Set();
  for (const rule of rules) {
    if (rule.isNeeded(context)) {
      implicit.add(rule.package);
    }
  }
  return implicit;
}

// src/actions/deplint/packageJsonEditor.ts
import fs5 from "node:fs";
import chalk from "chalk";
import semver from "semver";
import sortPackageJson from "sort-package-json";
function readPackageJson(location) {
  return JSON.parse(fs5.readFileSync(`${location}/package.json`, "utf8"));
}
function writePackageJson(location, pkg) {
  const sorted = sortPackageJson(JSON.stringify(pkg, null, 2));
  fs5.writeFileSync(`${location}/package.json`, `${sorted}
`);
}
function removeDep(pkg, dep, section) {
  const sectionObj = pkg[section];
  if (!sectionObj?.[dep]) return;
  delete sectionObj[dep];
  if (Object.keys(sectionObj).length === 0) {
    delete pkg[section];
  }
  if (section === "peerDependencies") {
    removePeerMeta(pkg, dep);
  }
}
function removePeerMeta(pkg, dep) {
  const meta = pkg.peerDependenciesMeta;
  if (!meta?.[dep]) return;
  delete meta[dep];
  if (Object.keys(meta).length === 0) {
    delete pkg.peerDependenciesMeta;
  }
}
function logFix(workspace, action, dep, detail) {
  const detailStr = detail ? ` (${detail})` : "";
  console.log(`[${chalk.blue(workspace.name)}] Fixed: ${action} ${chalk.green(dep)}${detailStr}`);
}

// src/actions/deplint/tsScriptsAliases.ts
var VARIANT_MAP = {
  "@xylabs/toolchain": "@xylabs/toolchain",
  "@xylabs/ts-scripts-react-yarn3": "@xylabs/ts-scripts-react-pnpm",
  "@xylabs/ts-scripts-react-pnpm": "@xylabs/ts-scripts-react-yarn3"
};
function isUsedViaTsScriptsVariant(dep, imports) {
  const variant = VARIANT_MAP[dep];
  return variant !== void 0 && imports.includes(variant);
}

// src/actions/deplint/checkPackage/getUnusedDevDependencies.ts
var allExternalImports = ({
  externalAllImports,
  externalDistImports,
  externalDistTypeImports
}) => {
  return /* @__PURE__ */ new Set([
    ...externalAllImports,
    ...externalDistImports,
    ...externalDistTypeImports
  ]);
};
function isDevDepUsed(dep, allImports, implicitDeps, requiredPeers, scriptRefs, cliRefs) {
  if (implicitDeps.has(dep)) return true;
  if (requiredPeers.has(dep)) return true;
  if (scriptRefs.has(dep)) return true;
  if (cliRefs.has(dep)) return true;
  if (dep.startsWith("@types/")) {
    const baseName = dep.replace(/^@types\//, "");
    return allImports.has(baseName) || allImports.has(dep) || implicitDeps.has(baseName);
  }
  if (allImports.has(dep)) return true;
  return isUsedViaTsScriptsVariant(dep, [...allImports]);
}
function findUnusedDevDeps(location, dependencies, devDependencies, peerDependencies, sourceParams, fileContext, exclude) {
  const allImports = allExternalImports(sourceParams);
  const allDeps = [...dependencies, ...devDependencies, ...peerDependencies];
  const implicitDeps = getImplicitDevDependencies({
    ...fileContext,
    allDependencies: allDeps,
    location
  });
  const requiredPeers = getRequiredPeerDependencies(location, allDeps);
  const scriptRefs = getScriptReferencedPackages(location, allDeps);
  const cliRefs = getCliReferencedPackagesFromFiles(fileContext.allFiles, location, allDeps);
  const unused = [];
  for (const dep of devDependencies) {
    if (exclude?.has(dep)) continue;
    if (dependencies.includes(dep) || peerDependencies.includes(dep)) continue;
    if (!isDevDepUsed(dep, allImports, implicitDeps, requiredPeers, scriptRefs, cliRefs)) {
      unused.push(dep);
    }
  }
  return unused;
}
function getUnusedDevDependencies({ name, location }, {
  devDependencies,
  dependencies,
  peerDependencies
}, sourceParams, fileContext, exclude) {
  const unused = findUnusedDevDeps(location, dependencies, devDependencies, peerDependencies, sourceParams, fileContext, exclude);
  for (const dep of unused) {
    console.log(`[${chalk2.blue(name)}] Unused devDependency in package.json: ${chalk2.red(dep)}`);
  }
  if (unused.length > 0) {
    console.log(`  ${chalk2.yellow(`${location}/package.json`)}
`);
  }
  return unused.length;
}
function fixUnusedDevDependencies(workspace, {
  devDependencies,
  dependencies,
  peerDependencies
}, sourceParams, fileContext, exclude) {
  const unused = findUnusedDevDeps(workspace.location, dependencies, devDependencies, peerDependencies, sourceParams, fileContext, exclude);
  if (unused.length === 0) return 0;
  const pkg = readPackageJson(workspace.location);
  for (const dep of unused) {
    removeDep(pkg, dep, "devDependencies");
    logFix(workspace, "removed from devDependencies", dep);
  }
  writePackageJson(workspace.location, pkg);
  return unused.length;
}
export {
  findUnusedDevDeps,
  fixUnusedDevDependencies,
  getUnusedDevDependencies
};
//# sourceMappingURL=getUnusedDevDependencies.mjs.map
