// src/lib/updo/resolveVersions.ts
import semver from "semver";

// src/lib/match.ts
import { minimatch } from "minimatch";
function makeMatcher(pattern, options = {}) {
  const patterns = Array.isArray(pattern) ? pattern : [pattern];
  return (input) => patterns.some((p) => minimatch(input, p, options));
}

// src/lib/updo/resolveVersions.ts
function parseExclude(pattern) {
  const sep = pattern.indexOf("@", 1);
  if (sep > 0 && sep < pattern.length - 1) {
    const name = pattern.slice(0, sep);
    const version = pattern.slice(sep + 1);
    return { matcher: makeMatcher(name), version };
  }
  return { matcher: makeMatcher(pattern), version: void 0 };
}
function isExcluded(name, version, rules) {
  for (const rule of rules) {
    if (!rule.matcher(name)) continue;
    if (rule.version === void 0) return true;
    if (rule.version === version) return true;
    try {
      if (semver.satisfies(version, rule.version)) return true;
    } catch {
    }
  }
  return false;
}
function buildExcludeRules(patterns) {
  return patterns.map(parseExclude);
}
function isVersionAllowed(name, version, info, filter, rules) {
  if (isExcluded(name, version, rules)) return true;
  const published = info.publishTimes?.[version];
  if (!(published != null && published.length > 0)) return filter.ignoreMissingTime;
  const publishedMs = Date.parse(published);
  if (Number.isNaN(publishedMs)) return filter.ignoreMissingTime;
  return filter.now - publishedMs >= filter.minutes * 6e4;
}
function filterRegistryInfo(name, info, filter, rules) {
  const allowedVersions = info.versions.filter((v) => isVersionAllowed(name, v, info, filter, rules));
  const allowedSet = new Set(allowedVersions);
  const allowedDistTags = {};
  for (const [tag, version] of Object.entries(info.distTags)) {
    if (allowedSet.has(version)) allowedDistTags[tag] = version;
  }
  return {
    distTags: allowedDistTags,
    publishTimes: info.publishTimes,
    versions: allowedVersions
  };
}
function isDeclaredRangeSatisfiable(declaredRanges, allowedStable) {
  for (const v of allowedStable) {
    for (const range of declaredRanges) {
      try {
        if (semver.satisfies(v, range)) return true;
      } catch {
      }
    }
  }
  return false;
}
function pickDowngrade(allowedStable, anchor) {
  const below = allowedStable.filter((v) => semver.lt(v, anchor));
  return highest(below);
}
function pickPrefix(declaredRanges) {
  if (declaredRanges.some((r) => r.startsWith("^"))) return "^";
  if (declaredRanges.some((r) => r.startsWith("~"))) return "~";
  return "";
}
function lowestAnchor(declaredRanges) {
  let lowest;
  for (const range of declaredRanges) {
    try {
      const min = semver.minVersion(range);
      if (!min) continue;
      if (!lowest || semver.lt(min, lowest)) lowest = min;
    } catch {
    }
  }
  return lowest;
}
function highest(versions) {
  if (versions.length === 0) return void 0;
  return versions.toSorted(semver.rcompare)[0];
}
function isStable(version) {
  return semver.prerelease(version) === null;
}
function format(prefix, version) {
  if (!(version != null && version.length > 0)) return void 0;
  return `${prefix}${version}`;
}
function pickBuckets(stableVersions, anchor) {
  const above = stableVersions.filter((v) => semver.gt(v, anchor));
  const sameMajor = above.filter((v) => semver.major(v) === anchor.major);
  const samePatch = sameMajor.filter((v) => semver.minor(v) === anchor.minor);
  const minorAbove = sameMajor.filter((v) => semver.minor(v) > anchor.minor);
  const majorAbove = above.filter((v) => semver.major(v) > anchor.major);
  return {
    major: highest(majorAbove),
    minor: highest(minorAbove),
    patch: highest(samePatch)
  };
}
function pickPrerelease(distTags, anchor) {
  let best;
  for (const tag of ["next", "rc", "beta", "alpha"]) {
    const v = distTags[tag];
    if (!v) continue;
    if (semver.prerelease(v) === null) continue;
    if (!semver.gt(v, anchor)) continue;
    if (!(best != null && best.length > 0) || semver.gt(v, best)) best = v;
  }
  return best;
}
function differentFromCurrent(value, current) {
  if (!(value != null && value.length > 0)) return void 0;
  return value === current ? void 0 : value;
}
function unresolvedDep(name, current, declaredRanges, preferredRangePrefix, workspaces) {
  return {
    current,
    declaredRanges,
    downgrade: void 0,
    major: void 0,
    minor: void 0,
    name,
    next: void 0,
    patch: void 0,
    preferredRangePrefix,
    updateAvailable: false,
    workspaces
  };
}
function normalizedMixedPatch(patch, declaredRanges, preferredRangePrefix, anchor) {
  if (patch != null && patch.length > 0 || declaredRanges.length <= 1) return patch;
  const isAllSameAnchor = declaredRanges.every((range) => {
    const v = semver.minVersion(range);
    return v !== null && v.version === anchor.version;
  });
  if (!isAllSameAnchor) return patch;
  const normalized = `${preferredRangePrefix}${anchor.version}`;
  return declaredRanges.some((r) => r !== normalized) ? normalized : patch;
}
function resolveOne(name, group, info, options, excludeRules) {
  const nonPeer = group.filter((d) => d.depType !== "peerDependencies");
  const sourceForRange = nonPeer.length > 0 ? nonPeer : group;
  const declaredRanges = [...new Set(sourceForRange.map((d) => d.range))];
  const workspaces = [...new Set(group.map((d) => d.workspaceName))];
  const current = declaredRanges.length === 1 ? declaredRanges[0] : "mixed";
  const preferredRangePrefix = pickPrefix(declaredRanges);
  const anchor = lowestAnchor(declaredRanges);
  if (info === void 0 || anchor === void 0) {
    return unresolvedDep(name, current, declaredRanges, preferredRangePrefix, workspaces);
  }
  const effectiveInfo = options.minReleaseAge ? filterRegistryInfo(name, info, options.minReleaseAge, excludeRules) : info;
  const stable = effectiveInfo.versions.filter((v) => semver.valid(v) !== null && isStable(v));
  const downgrade = options.minReleaseAge && !isDeclaredRangeSatisfiable(declaredRanges, stable) ? format(preferredRangePrefix, pickDowngrade(stable, anchor)) : void 0;
  const buckets = pickBuckets(stable, anchor);
  const patchRaw = format(preferredRangePrefix, buckets.patch);
  const minorRaw = format(preferredRangePrefix, buckets.minor);
  const majorRaw = format(preferredRangePrefix, buckets.major);
  const nextRaw = options.shouldIncludeNext ?? false ? format(preferredRangePrefix, pickPrerelease(effectiveInfo.distTags, anchor)) : void 0;
  const patch = normalizedMixedPatch(differentFromCurrent(patchRaw, current), declaredRanges, preferredRangePrefix, anchor);
  const minor = differentFromCurrent(minorRaw, current);
  const major = differentFromCurrent(majorRaw, current);
  const next = differentFromCurrent(nextRaw, current);
  const availableVersion = patch ?? minor ?? major ?? next ?? downgrade;
  const isUpdateAvailable = availableVersion !== void 0 && availableVersion.length > 0;
  return {
    current,
    declaredRanges,
    downgrade,
    major,
    minor,
    name,
    next,
    patch,
    preferredRangePrefix,
    updateAvailable: isUpdateAvailable,
    workspaces
  };
}
function resolveVersions(deps, registry, options = {}) {
  const grouped = /* @__PURE__ */ new Map();
  for (const dep of deps) {
    const existing = grouped.get(dep.name);
    if (existing) existing.push(dep);
    else grouped.set(dep.name, [dep]);
  }
  const excludeRules = options.minReleaseAge ? buildExcludeRules(options.minReleaseAge.exclude) : [];
  const resolved = [];
  for (const [name, group] of grouped) {
    resolved.push(resolveOne(name, group, registry.get(name), options, excludeRules));
  }
  return resolved.toSorted((a, b) => {
    if (a.updateAvailable !== b.updateAvailable) return a.updateAvailable ? -1 : 1;
    return a.name.localeCompare(b.name);
  });
}
export {
  resolveVersions
};
//# sourceMappingURL=resolveVersions.mjs.map
