import { scanCodeForEnvVars } from './chunk-C5ZGZA3T.js';
import { gracefulExit } from './chunk-XQSWW35O.js';
import { define } from './chunk-WIL4LMMA.js';
import { checkForSchemaErrors, checkForNoEnvFiles } from './chunk-4MCNC4YY.js';
import { loadVarlockEnvGraph } from './chunk-5SI4EGRL.js';
import { FileBasedDataSource, ansis_default } from './chunk-LTYLEVHC.js';
import { __name } from './chunk-IBAHCH6O.js';
import fs from 'fs/promises';
import path from 'path';

// src/cli/helpers/audit-diff.ts
function diffSchemaAndCodeKeys(schemaKeys, codeKeys) {
  const schemaSet = new Set(schemaKeys);
  const codeSet = new Set(codeKeys);
  const missingInSchema = [...codeSet].filter((k) => !schemaSet.has(k)).sort((a, b) => a.localeCompare(b));
  const unusedInSchema = [...schemaSet].filter((k) => !codeSet.has(k)).sort((a, b) => a.localeCompare(b));
  return {
    missingInSchema,
    unusedInSchema
  };
}
__name(diffSchemaAndCodeKeys, "diffSchemaAndCodeKeys");

// src/cli/commands/audit.command.ts
var commandSpec = define({
  name: "audit",
  description: "Audit code env var usage against your .env.schema",
  args: {
    path: {
      type: "string",
      short: "p",
      description: "Path to a specific .env file or directory to use as the schema entry point"
    },
    ignore: {
      type: "string",
      short: "i",
      multiple: true,
      description: "Directory to exclude from code scanning (can be specified multiple times)"
    }
  },
  examples: `
Scans your source code for environment variable references and compares them
to keys defined in your varlock schema.

Examples:
  varlock audit                          # Audit current project
  varlock audit --path .env.prod         # Audit using a specific env entry point
  varlock audit ./src ./lib              # Only scan specific directories
  varlock audit --ignore vendor          # Exclude a directory from scanning
  varlock audit -i vendor -i generated   # Exclude multiple directories
`.trim()
});
function formatReference(cwd, ref) {
  const relPath = path.relative(cwd, ref.filePath);
  return `${relPath}:${ref.lineNumber}:${ref.columnNumber}`;
}
__name(formatReference, "formatReference");
async function getScanRootFromEntryPath(providedEntryPath) {
  const resolved = path.resolve(providedEntryPath);
  try {
    const entryStat = await fs.stat(resolved);
    if (entryStat.isDirectory()) return resolved;
  } catch {
  }
  if (providedEntryPath.endsWith("/") || providedEntryPath.endsWith(path.sep)) {
    return resolved;
  }
  return path.dirname(resolved);
}
__name(getScanRootFromEntryPath, "getScanRootFromEntryPath");
function collectStringArgs(input, out) {
  if (Array.isArray(input)) {
    for (const entry of input) collectStringArgs(entry, out);
    return;
  }
  if (typeof input !== "string") return;
  const normalized = input.trim().replace(/^\.\//, "").replace(/[/\\]+$/, "");
  if (!normalized) return;
  out.push(normalized);
}
__name(collectStringArgs, "collectStringArgs");
function getInternallyReferencedKeys(envGraph) {
  const referenced = /* @__PURE__ */ new Set();
  const adjList = envGraph.graphAdjacencyList;
  if (adjList) {
    for (const itemKey in adjList) {
      for (const dep of adjList[itemKey]) {
        referenced.add(dep);
      }
    }
  }
  for (const source of envGraph.sortedDataSources ?? []) {
    for (const dec of source.rootDecorators ?? []) {
      for (const dep of dec.decValueResolver?.deps ?? []) {
        referenced.add(dep);
      }
    }
  }
  return referenced;
}
__name(getInternallyReferencedKeys, "getInternallyReferencedKeys");
async function getCustomAuditIgnorePaths(envGraph) {
  const rootDecFns = typeof envGraph?.getRootDecFns === "function" ? envGraph.getRootDecFns("auditIgnorePaths") : [];
  const mergedPaths = [];
  for (const dec of rootDecFns || []) {
    const resolved = await dec.resolve();
    collectStringArgs(resolved?.arr, mergedPaths);
  }
  return [...new Set(mergedPaths)];
}
__name(getCustomAuditIgnorePaths, "getCustomAuditIgnorePaths");
var commandFn = /* @__PURE__ */ __name(async (ctx) => {
  const providedEntryPath = ctx.values.path;
  const cliIgnoreDirs = ctx.values.ignore ?? [];
  const scanTargets = (ctx.positionals ?? []).slice(ctx.commandPath?.length ?? 0);
  const envGraph = await loadVarlockEnvGraph({
    entryFilePaths: providedEntryPath ? [providedEntryPath] : void 0
  });
  checkForSchemaErrors(envGraph);
  checkForNoEnvFiles(envGraph);
  const schemaScanRoot = (() => {
    if (providedEntryPath) {
      return void 0;
    }
    const rootSource = envGraph.rootDataSource;
    if (rootSource instanceof FileBasedDataSource) {
      return path.dirname(rootSource.fullPath);
    }
    return envGraph.basePath ?? process.cwd();
  })();
  const finalScanRoot = providedEntryPath ? await getScanRootFromEntryPath(providedEntryPath) : schemaScanRoot ?? process.cwd();
  const customIgnoredPaths = await getCustomAuditIgnorePaths(envGraph);
  const allIgnoredPaths = [...customIgnoredPaths, ...cliIgnoreDirs];
  if (allIgnoredPaths.length > 0) {
    console.log(`\u2139\uFE0F Skipping ignored paths: ${allIgnoredPaths.join(", ")}`);
  }
  let scanResult;
  if (scanTargets.length > 0) {
    const mergedRefs = [];
    let totalFilesScanned = 0;
    for (const target of scanTargets) {
      const resolvedTarget = path.resolve(finalScanRoot, target);
      const result = await scanCodeForEnvVars(
        { cwd: resolvedTarget },
        allIgnoredPaths
      );
      mergedRefs.push(...result.references);
      totalFilesScanned += result.scannedFilesCount;
    }
    const uniqueKeys = [...new Set(mergedRefs.map((r) => r.key))].sort((a, b) => a.localeCompare(b));
    scanResult = { keys: uniqueKeys, references: mergedRefs, scannedFilesCount: totalFilesScanned };
  } else {
    scanResult = await scanCodeForEnvVars(
      { cwd: finalScanRoot },
      allIgnoredPaths
    );
  }
  const schemaKeys = Object.keys(envGraph.configSchema);
  const diff = diffSchemaAndCodeKeys(schemaKeys, scanResult.keys);
  const internallyReferenced = getInternallyReferencedKeys(envGraph);
  const unusedInSchema = [];
  for (const key of diff.unusedInSchema) {
    if (internallyReferenced.has(key)) continue;
    const item = envGraph.configSchema[key];
    const itemDecorators = item?.decorators;
    const isIgnored = typeof item?.getDec === "function" && item.getDec("auditIgnore") === true || itemDecorators?.auditIgnore === true;
    if (isIgnored) continue;
    unusedInSchema.push(key);
  }
  if (diff.missingInSchema.length === 0 && unusedInSchema.length === 0) {
    console.log(ansis_default.green(`\u2705 Schema and code references are in sync. (scanned ${scanResult.scannedFilesCount} file${scanResult.scannedFilesCount === 1 ? "" : "s"})`));
    gracefulExit(0);
    return;
  }
  console.error(ansis_default.red("\n\u{1F6A8} Schema/code mismatch detected:\n"));
  if (diff.missingInSchema.length > 0) {
    console.error(ansis_default.red(`Missing in schema (${diff.missingInSchema.length}):`));
    for (const key of diff.missingInSchema) {
      const refs = scanResult.references.filter((r) => r.key === key).slice(0, 3);
      const refPreview = refs.map((r) => formatReference(finalScanRoot, r)).join(", ");
      console.error(`  - ${ansis_default.bold(key)}${refPreview ? ansis_default.dim(` (seen at ${refPreview})`) : ""}`);
    }
    console.error("");
  }
  if (unusedInSchema.length > 0) {
    console.error(ansis_default.yellow(`Unused in schema (${unusedInSchema.length}):`));
    for (const key of unusedInSchema) {
      console.error(`  - ${ansis_default.bold(key)}`);
    }
    console.error(ansis_default.dim("(Hint: If this is used by an external tool, add # @auditIgnore to the item)"));
    console.error("");
  }
  gracefulExit(1);
}, "commandFn");

export { commandFn, commandSpec };
//# sourceMappingURL=chunk-N3LH752M.js.map
//# sourceMappingURL=chunk-N3LH752M.js.map