// src/actions/deplint/checkPackage/legacyPeerCheck.ts
import fs4 from "node:fs";
import chalk from "chalk";
import sortPackageJson from "sort-package-json";

// src/actions/deplint/isTerminalPackage.ts
import fs from "node:fs";
var EXPORT_META_KEYS = /* @__PURE__ */ new Set([".", "./package.json", "./README.md"]);
function hasBinSetup(bin) {
  if (typeof bin === "string") return bin.length > 0;
  if (typeof bin !== "object" || bin === null || Array.isArray(bin)) return false;
  return Object.keys(bin).length > 0;
}
function isCliOnlyExports(exports) {
  if (exports === void 0) return true;
  if (typeof exports === "string") return true;
  if (typeof exports !== "object" || exports === null || Array.isArray(exports)) return false;
  return Object.keys(exports).every((k) => EXPORT_META_KEYS.has(k));
}
function hasLibrarySurface(pkg) {
  return pkg.main !== void 0 || pkg.module !== void 0 || !isCliOnlyExports(pkg.exports);
}
function withTerminalOverride(classification, isTerminalOverride) {
  if (isTerminalOverride === void 0) return classification;
  if (isTerminalOverride) {
    return {
      isTerminalPackage: true,
      kind: classification.kind === "terminal[private]" ? "terminal[private]" : "terminal[cli]"
    };
  }
  return {
    isTerminalPackage: false,
    kind: classification.kind === "library/cli" || classification.kind === "terminal[cli]" ? "library/cli" : "library"
  };
}
function classifyDeplintPackage(location, isTerminalOverride) {
  try {
    const pkg = JSON.parse(
      fs.readFileSync(`${location}/package.json`, "utf8")
    );
    if (pkg.private === true) {
      return withTerminalOverride({
        isTerminalPackage: true,
        kind: "terminal[private]"
      }, isTerminalOverride);
    }
    if (pkg.xy?.commands?.deplint?.terminal === true) {
      return withTerminalOverride({
        isTerminalPackage: true,
        kind: "terminal[cli]"
      }, isTerminalOverride);
    }
    const hasBin = hasBinSetup(pkg.bin);
    if (hasBin && hasLibrarySurface(pkg)) {
      return withTerminalOverride({
        isTerminalPackage: false,
        kind: "library/cli"
      }, isTerminalOverride);
    }
    if (hasBin) {
      return withTerminalOverride({
        isTerminalPackage: true,
        kind: "terminal[cli]"
      }, isTerminalOverride);
    }
    return withTerminalOverride({
      isTerminalPackage: false,
      kind: "library"
    }, isTerminalOverride);
  } catch {
    return withTerminalOverride({
      isTerminalPackage: false,
      kind: "library"
    }, isTerminalOverride);
  }
}
function isTerminalPackage(location) {
  return classifyDeplintPackage(location).isTerminalPackage;
}

// src/actions/deplint/checkPackage/getUnsatisfiedPeerDependencies.ts
import fs3 from "node:fs";
import semver from "semver";

// src/actions/deplint/getRequiredPeerDependencies.ts
import fs2 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 (fs2.existsSync(candidate)) return candidate;
    const parent = path.dirname(dir);
    if (parent === dir) return void 0;
    dir = parent;
  }
}

// src/actions/deplint/checkPackage/getUnsatisfiedPeerDependencies.ts
function readDepPackageJson(location, dep) {
  const depPkgPath = findDepPackageJson(location, dep);
  if (!(depPkgPath != null && depPkgPath.length > 0)) return void 0;
  try {
    return JSON.parse(fs3.readFileSync(depPkgPath, "utf8"));
  } catch {
    return void 0;
  }
}
function readPeerDeps(location, dep) {
  const pkg = readDepPackageJson(location, dep);
  if (!pkg) return [];
  const peers = pkg.peerDependencies;
  return Object.keys(peers ?? {});
}
function readPeerVersionRange(location, dep, peer) {
  const pkg = readDepPackageJson(location, dep);
  if (!pkg) return void 0;
  const peers = pkg.peerDependencies;
  return peers?.[peer];
}
function readInstalledVersion(location, dep) {
  const pkg = readDepPackageJson(location, dep);
  if (!pkg) return void 0;
  return pkg.version;
}
function isPeerOptionalInAll(location, peer, sources) {
  for (const source of sources) {
    const pkg = readDepPackageJson(location, source.requiredBy);
    if (!pkg) continue;
    const meta = pkg.peerDependenciesMeta?.[peer];
    if (!(meta?.optional ?? false)) return false;
  }
  return true;
}
var MAX_TRANSITIVE_DEPTH = 2;
function collectPeerSources(location, dependencies, devDependencies, peerDependencies = []) {
  const peerSources = /* @__PURE__ */ new Map();
  const visited = /* @__PURE__ */ new Set();
  function addPeerSource(peer, source) {
    const sources = peerSources.get(peer) ?? [];
    sources.push(source);
    peerSources.set(peer, sources);
  }
  function collectFromDeps(deps, isRuntime, chain) {
    for (const dep of deps) {
      const visitKey = `${dep}:${chain.join(">")}`;
      if (visited.has(visitKey)) continue;
      visited.add(visitKey);
      for (const peer of readPeerDeps(location, dep)) {
        addPeerSource(peer, {
          chain: chain.length > 0 ? [...chain, dep] : void 0,
          isRuntimeDep: isRuntime,
          requiredBy: dep
        });
      }
      if (chain.length < MAX_TRANSITIVE_DEPTH) {
        const depPkg = readDepPackageJson(location, dep);
        if (depPkg) {
          const transitiveDeps = Object.keys(depPkg.dependencies ?? {});
          collectFromDeps(transitiveDeps, isRuntime, [...chain, dep]);
        }
      }
    }
  }
  const runtimeRoots = [.../* @__PURE__ */ new Set([...dependencies, ...peerDependencies])];
  collectFromDeps(runtimeRoots, true, []);
  const devOnly = devDependencies.filter((d) => !runtimeRoots.includes(d));
  collectFromDeps(devOnly, false, []);
  return peerSources;
}
function resolveVersionRange(location, sources, peer) {
  for (const source of sources) {
    const range = readPeerVersionRange(location, source.requiredBy, peer);
    if (range != null && range.length > 0) return range;
  }
  return "*";
}
function checkVersionSatisfaction(location, peer, requestedRange) {
  const installedVersion = readInstalledVersion(location, peer);
  if (!(installedVersion != null && installedVersion.length > 0)) return { satisfied: true };
  if (requestedRange === "*") return { installedVersion, satisfied: true };
  if (requestedRange.startsWith("workspace:")) return { installedVersion, satisfied: true };
  try {
    const isSatisfied = semver.satisfies(installedVersion, requestedRange, { includePrerelease: true });
    return { installedVersion, satisfied: isSatisfied };
  } catch {
    return { installedVersion, satisfied: true };
  }
}
function findDeclaredPeerVersionMismatches(name, location, peerDependencies, peerDependencyVersions, exclude) {
  const results = [];
  for (const peer of peerDependencies) {
    if ((exclude?.has(peer) ?? false) || peer === name) continue;
    const declaredRange = peerDependencyVersions[peer];
    if (!declaredRange || declaredRange === "*") continue;
    const { installedVersion, satisfied: isSatisfied } = checkVersionSatisfaction(location, peer, declaredRange);
    if (!isSatisfied) {
      results.push({
        hasRuntimeSource: true,
        installedVersion,
        peer,
        reason: "version-mismatch",
        sources: [{ isRuntimeDep: true, requiredBy: name }],
        versionRange: declaredRange
      });
    }
  }
  return results;
}
function classifyPeer(peer, sources, ctx) {
  const {
    location,
    depsSet,
    devDepsSet,
    peerDepsSet,
    isTerminal,
    forceDevs,
    forcePeerOptional
  } = ctx;
  if (forceDevs.has(peer)) return void 0;
  const isPeerForced = forcePeerOptional?.has(peer) ?? false;
  const runtimeSources = sources.filter((s) => s.isRuntimeDep);
  const hasRuntimeSource = runtimeSources.length > 0 && (isPeerForced || !isPeerOptionalInAll(location, peer, runtimeSources));
  if (!depsSet.has(peer) && !devDepsSet.has(peer)) {
    return {
      hasRuntimeSource,
      peer,
      reason: "missing",
      sources,
      versionRange: resolveVersionRange(location, sources, peer)
    };
  }
  const directRuntimeSources = sources.filter((s) => s.isRuntimeDep && !s.chain);
  const shouldForwardRuntime = directRuntimeSources.length > 0 && (isPeerForced || !isPeerOptionalInAll(location, peer, directRuntimeSources));
  if (shouldForwardRuntime && !isTerminal && !depsSet.has(peer) && !peerDepsSet.has(peer)) {
    return {
      hasRuntimeSource,
      peer,
      reason: "under-exposed",
      sources,
      versionRange: resolveVersionRange(location, sources, peer)
    };
  }
  const versionRange = resolveVersionRange(location, sources, peer);
  const { installedVersion, satisfied: isSatisfied } = checkVersionSatisfaction(location, peer, versionRange);
  if (isSatisfied) return void 0;
  return {
    hasRuntimeSource,
    installedVersion,
    mismatchedSources: findMismatchedSources(location, sources, peer, installedVersion),
    peer,
    reason: "version-mismatch",
    sources,
    versionRange
  };
}
function findUnsatisfiedPeers({ name, location }, {
  dependencies,
  devDependencies,
  peerDependencies,
  peerDependencyVersions
}, exclude, isTerminal, forcePeerOptional, forceDevs) {
  const peerSources = collectPeerSources(location, dependencies, devDependencies, peerDependencies);
  const ctx = {
    depsSet: new Set(dependencies),
    devDepsSet: new Set(devDependencies),
    forceDevs: forceDevs ?? /* @__PURE__ */ new Set(),
    forcePeerOptional,
    isTerminal: isTerminal ?? isTerminalPackage(location),
    location,
    peerDepsSet: new Set(peerDependencies)
  };
  const unsatisfied = [];
  for (const [peer, sources] of peerSources) {
    if ((exclude?.has(peer) ?? false) || peer === name) continue;
    if (!(forcePeerOptional?.has(peer) ?? false) && isPeerOptionalInAll(location, peer, sources)) continue;
    const finding = classifyPeer(peer, sources, ctx);
    if (finding) unsatisfied.push(finding);
  }
  const declaredMismatches = findDeclaredPeerVersionMismatches(name, location, peerDependencies, peerDependencyVersions, exclude).filter((m) => !(forceDevs?.has(m.peer) ?? false));
  return [
    ...unsatisfied,
    ...declaredMismatches
  ];
}
function findMismatchedSources(location, sources, peer, installedVersion) {
  if (!(installedVersion != null && installedVersion.length > 0)) return [];
  return sources.filter((source) => {
    const range = readPeerVersionRange(location, source.requiredBy, peer);
    if (!(range != null && range.length > 0) || range === "*" || range.startsWith("workspace:")) return false;
    try {
      return !semver.satisfies(installedVersion, range, { includePrerelease: true });
    } catch {
      return false;
    }
  });
}
function resolvePeerTargetSections(hasRuntimeSource, isTerminal) {
  if (!hasRuntimeSource) return ["devDependencies"];
  if (isTerminal) return ["dependencies"];
  return ["peerDependencies", "devDependencies"];
}

// src/actions/deplint/checkPackage/legacyPeerCheck.ts
function formatChain(sources) {
  const chainsWithDepth = sources.filter((s) => (s.chain && s.chain.length > 0) ?? false);
  if (chainsWithDepth.length === 0) return "";
  const first = chainsWithDepth[0];
  return ` (via ${chalk.dim(first.chain.join(" \u2192 "))})`;
}
function logUnsatisfiedPeer(name, unsatisfied) {
  const {
    peer,
    sources,
    reason,
    hasRuntimeSource,
    installedVersion,
    versionRange
  } = unsatisfied;
  const chainInfo = formatChain(sources);
  if (reason === "version-mismatch") {
    const requiredByList = sources.map((s) => s.requiredBy);
    console.log(
      `[${chalk.blue(name)}] Peer dependency version mismatch ${chalk.red(peer)} \u2014 installed ${chalk.yellow(installedVersion ?? "unknown")} does not satisfy ${chalk.cyan(versionRange)} (required by ${chalk.cyan(requiredByList.join(", "))})` + chainInfo
    );
  } else if (reason === "under-exposed") {
    const requiredByList = sources.filter((s) => s.isRuntimeDep).map((s) => s.requiredBy);
    console.log(
      `[${chalk.blue(name)}] Under-exposed peer dependency ${chalk.red(peer)} (required by ${chalk.cyan(requiredByList.join(", "))})` + chainInfo + " \u2014 present in devDependencies but must also be in dependencies or peerDependencies for consumers"
    );
  } else if (hasRuntimeSource) {
    const requiredByList = sources.filter((s) => s.isRuntimeDep).map((s) => s.requiredBy);
    console.log(
      `[${chalk.blue(name)}] Unsatisfied peer dependency ${chalk.red(peer)} (required by ${chalk.cyan(requiredByList.join(", "))})` + chainInfo + " \u2014 must be in dependencies or peerDependencies"
    );
  } else {
    const requiredByList = sources.map((s) => s.requiredBy);
    console.log(
      `[${chalk.blue(name)}] Unsatisfied peer dependency ${chalk.red(peer)} (required by devDependency ${chalk.cyan(requiredByList.join(", "))})` + chainInfo + " \u2014 must be in dependencies, devDependencies, or peerDependencies"
    );
  }
}
function getUnsatisfiedPeerDependencies(workspace, packageParams, exclude) {
  const unsatisfied = findUnsatisfiedPeers(workspace, packageParams, exclude);
  for (const entry of unsatisfied) {
    logUnsatisfiedPeer(workspace.name, entry);
  }
  if (unsatisfied.length > 0) {
    console.log(`  ${chalk.yellow(`${workspace.location}/package.json`)}
`);
  }
  return {
    count: unsatisfied.length,
    unsatisfiedPeers: new Set(unsatisfied.map((u) => u.peer))
  };
}
function shouldFixVersionMismatch(workspaceName, entry, pkg) {
  const {
    installedVersion,
    peer,
    versionRange
  } = entry;
  if (!(installedVersion != null && installedVersion.length > 0)) {
    logUnsatisfiedPeer(workspaceName, entry);
    console.log(`  ${chalk.dim("(version mismatch \u2014 could not resolve installed version)")}`);
    return false;
  }
  const match = /^(\d+)\.(\d+)/.exec(installedVersion);
  if (!match) {
    logUnsatisfiedPeer(workspaceName, entry);
    console.log(`  ${chalk.dim("(version mismatch \u2014 could not resolve installed version)")}`);
    return false;
  }
  const expectedVersion = versionRange.startsWith("^") ? `^${match[1]}` : `~${match[1]}.${match[2]}`;
  if (pkg.peerDependencies !== void 0 && Object.hasOwn(pkg.peerDependencies, peer) && !pkg.peerDependencies[peer].startsWith("workspace:")) {
    pkg.peerDependencies[peer] = expectedVersion;
  }
  if (pkg.devDependencies !== void 0 && Object.hasOwn(pkg.devDependencies, peer) && !pkg.devDependencies[peer].startsWith("workspace:")) {
    pkg.devDependencies[peer] = expectedVersion;
  }
  console.log(
    `[${chalk.blue(workspaceName)}] Fixed: updated ${chalk.green(peer)} version to ${chalk.cyan(expectedVersion)} (was ${chalk.yellow(versionRange)})`
  );
  return true;
}
function fixUnsatisfiedPeerDependencies(workspace, packageParams, exclude, isTerminal) {
  const isTerminalResolved = isTerminal ?? isTerminalPackage(workspace.location);
  const unsatisfied = findUnsatisfiedPeers(workspace, packageParams, exclude, isTerminalResolved);
  if (unsatisfied.length === 0) return { addedPeers: /* @__PURE__ */ new Set(), count: 0 };
  const packageJsonPath = `${workspace.location}/package.json`;
  const pkg = JSON.parse(fs4.readFileSync(packageJsonPath, "utf8"));
  const addedPeers = /* @__PURE__ */ new Set();
  let fixedCount = 0;
  for (const entry of unsatisfied) {
    const {
      hasRuntimeSource,
      peer,
      reason,
      sources,
      versionRange
    } = entry;
    if (reason === "version-mismatch") {
      if (shouldFixVersionMismatch(workspace.name, entry, pkg)) fixedCount++;
      continue;
    }
    const sections = reason === "under-exposed" ? ["peerDependencies"] : resolvePeerTargetSections(hasRuntimeSource, isTerminalResolved);
    for (const section of sections) {
      pkg[section] ??= {};
      pkg[section][peer] = versionRange;
    }
    addedPeers.add(peer);
    const requiredByList = sources.map((s) => s.requiredBy).join(", ");
    console.log(
      `[${chalk.blue(workspace.name)}] Fixed: added ${chalk.green(peer)}@${versionRange} to ${chalk.cyan(sections.join(" + "))} (required by ${requiredByList})`
    );
    fixedCount++;
  }
  if (fixedCount > 0) {
    const sorted = sortPackageJson(JSON.stringify(pkg, null, 2));
    fs4.writeFileSync(packageJsonPath, `${sorted}
`);
  }
  return { addedPeers, count: unsatisfied.length };
}
export {
  fixUnsatisfiedPeerDependencies,
  getUnsatisfiedPeerDependencies
};
//# sourceMappingURL=legacyPeerCheck.mjs.map
