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

// 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;
  }
}

// src/actions/deplint/isTerminalPackage.ts
import fs2 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 exportsLookCliOnly(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 || !exportsLookCliOnly(pkg.exports);
}
function withTerminalOverride(classification, terminalOverride) {
  if (terminalOverride === void 0) return classification;
  if (terminalOverride) {
    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, terminalOverride) {
  try {
    const pkg = JSON.parse(
      fs2.readFileSync(`${location}/package.json`, "utf8")
    );
    if (pkg.private === true) {
      return withTerminalOverride({
        isTerminalPackage: true,
        kind: "terminal[private]"
      }, terminalOverride);
    }
    if (pkg.xy?.commands?.deplint?.terminal === true) {
      return withTerminalOverride({
        isTerminalPackage: true,
        kind: "terminal[cli]"
      }, terminalOverride);
    }
    const hasBin = hasBinSetup(pkg.bin);
    if (hasBin && hasLibrarySurface(pkg)) {
      return withTerminalOverride({
        isTerminalPackage: false,
        kind: "library/cli"
      }, terminalOverride);
    }
    if (hasBin) {
      return withTerminalOverride({
        isTerminalPackage: true,
        kind: "terminal[cli]"
      }, terminalOverride);
    }
    return withTerminalOverride({
      isTerminalPackage: false,
      kind: "library"
    }, terminalOverride);
  } catch {
    return withTerminalOverride({
      isTerminalPackage: false,
      kind: "library"
    }, terminalOverride);
  }
}
function isTerminalPackage(location) {
  return classifyDeplintPackage(location).isTerminalPackage;
}

// src/actions/deplint/checkPackage/getUnsatisfiedPeerDependencies.ts
function readDepPackageJson(location, dep) {
  const depPkgPath = findDepPackageJson(location, dep);
  if (!depPkgPath) 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) 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 getRequiredTransitivePeers(location, {
  dependencies,
  devDependencies,
  peerDependencies
}) {
  const peerSources = collectPeerSources(location, dependencies, devDependencies, peerDependencies);
  return new Set(peerSources.keys());
}
function getRequiredNonOptionalTransitivePeers(location, {
  dependencies,
  devDependencies,
  peerDependencies
}) {
  const peerSources = collectPeerSources(location, dependencies, devDependencies, peerDependencies);
  const result = /* @__PURE__ */ new Set();
  for (const [peer, sources] of peerSources) {
    if (!isPeerOptionalInAll(location, peer, sources)) result.add(peer);
  }
  return result;
}
function getRuntimeRootedNonOptionalTransitivePeers(location, {
  dependencies,
  devDependencies,
  peerDependencies
}) {
  const peerSources = collectPeerSources(location, dependencies, devDependencies, peerDependencies);
  const result = /* @__PURE__ */ new Set();
  for (const [peer, sources] of peerSources) {
    const runtimeSources = sources.filter((s) => s.isRuntimeDep);
    if (runtimeSources.length === 0) continue;
    if (isPeerOptionalInAll(location, peer, runtimeSources)) continue;
    result.add(peer);
  }
  return result;
}
function resolveVersionRange(location, sources, peer) {
  for (const source of sources) {
    const range = readPeerVersionRange(location, source.requiredBy, peer);
    if (range) return range;
  }
  return "*";
}
function checkVersionSatisfaction(location, peer, requestedRange) {
  const installedVersion = readInstalledVersion(location, peer);
  if (!installedVersion) return { satisfied: true };
  if (requestedRange === "*") return { installedVersion, satisfied: true };
  if (requestedRange.startsWith("workspace:")) return { installedVersion, satisfied: true };
  try {
    const satisfied = semver.satisfies(installedVersion, requestedRange, { includePrerelease: true });
    return { installedVersion, satisfied };
  } catch {
    return { installedVersion, satisfied: true };
  }
}
function findDeclaredPeerVersionMismatches(name, location, peerDependencies, peerDependencyVersions, exclude) {
  const results = [];
  for (const peer of peerDependencies) {
    if (exclude?.has(peer) || peer === name) continue;
    const declaredRange = peerDependencyVersions[peer];
    if (!declaredRange || declaredRange === "*") continue;
    const { installedVersion, satisfied } = checkVersionSatisfaction(location, peer, declaredRange);
    if (!satisfied) {
      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 } = checkVersionSatisfaction(location, peer, versionRange);
  if (satisfied) 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) || peer === name) continue;
    if (!forcePeerOptional?.has(peer) && 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));
  return [
    ...unsatisfied,
    ...declaredMismatches
  ];
}
function findMismatchedSources(location, sources, peer, installedVersion) {
  if (!installedVersion) return [];
  return sources.filter((source) => {
    const range = readPeerVersionRange(location, source.requiredBy, peer);
    if (!range || 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"];
}
export {
  findUnsatisfiedPeers,
  getRequiredNonOptionalTransitivePeers,
  getRequiredTransitivePeers,
  getRuntimeRootedNonOptionalTransitivePeers,
  isPeerOptionalInAll,
  resolvePeerTargetSections
};
//# sourceMappingURL=getUnsatisfiedPeerDependencies.mjs.map
