function parseSemver(value) {
  const match = String(value || "").trim().match(/^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/);
  if (!match) return null;
  return {
    major: Number(match[1]),
    minor: Number(match[2]),
    patch: Number(match[3]),
    raw: `${Number(match[1])}.${Number(match[2])}.${Number(match[3])}`
  };
}

function compareSemver(left, right) {
  const a = parseSemver(left);
  const b = parseSemver(right);
  if (!a && !b) return 0;
  if (!a) return -1;
  if (!b) return 1;
  for (const key of ["major", "minor", "patch"]) {
    if (a[key] > b[key]) return 1;
    if (a[key] < b[key]) return -1;
  }
  return 0;
}

function isMajorJump(current, target) {
  const from = parseSemver(current);
  const to = parseSemver(target);
  return Boolean(from && to && from.major !== to.major);
}

function versionInRange(version, from, to) {
  return compareSemver(version, from) > 0 && compareSemver(version, to) <= 0;
}

function normalizeVersionEntry(entry, fallback = {}) {
  if (typeof entry === "string") return { version: entry, ...fallback };
  if (!entry || typeof entry !== "object") return null;
  const version = entry.version || entry.name || entry.tag;
  if (!parseSemver(version)) return null;
  return { ...fallback, ...entry, version: parseSemver(version).raw };
}

function normalizeVersions(entries, fallback = {}) {
  return (Array.isArray(entries) ? entries : [])
    .map((entry) => normalizeVersionEntry(entry, fallback))
    .filter(Boolean)
    .sort((a, b) => compareSemver(a.version, b.version));
}

function compatibleFromAllows(entry, current) {
  if (!entry?.compatible_from) return false;
  return compareSemver(current, entry.compatible_from) >= 0;
}

function isBridgeTarget(entry, current) {
  const from = parseSemver(current);
  const to = parseSemver(entry?.version);
  if (!from || !to || from.major === to.major) return false;
  if (entry.compatibility_bridge === true || entry.migration_kind === "compatibility_bridge") {
    return compatibleFromAllows(entry, current) || !entry.compatible_from;
  }
  if (to.major === from.major + 1 && to.minor === 0 && to.patch === 0) {
    return compatibleFromAllows(entry, current) || entry.compatible_from === undefined;
  }
  return false;
}

function findSafeTarget(currentVersion, versionEntries) {
  const current = parseSemver(currentVersion);
  const versions = normalizeVersions(versionEntries);
  if (!current) {
    return {
      target: null,
      blocked: true,
      reason: "Current version is not valid semver."
    };
  }
  const newer = versions.filter((entry) => compareSemver(entry.version, current.raw) > 0);
  if (!newer.length) {
    return { target: null, blocked: false, reason: "Already up to date." };
  }
  const latest = newer[newer.length - 1];
  const sameMajor = newer.filter((entry) => parseSemver(entry.version)?.major === current.major);
  if (sameMajor.length) {
    return { target: sameMajor[sameMajor.length - 1], latest, blocked: false };
  }
  const bridge = newer.find((entry) => isBridgeTarget(entry, current.raw));
  if (bridge) {
    return {
      target: bridge,
      latest,
      blocked: false,
      warning: `Latest ${latest.version} crosses a major boundary; targeting compatibility bridge ${bridge.version}.`
    };
  }
  return {
    target: null,
    latest,
    blocked: true,
    reason: `Latest ${latest.version} crosses a major boundary and no compatible bridge target was found.`
  };
}

function collectChangelogRange(currentVersion, targetVersion, changelogEntries) {
  if (!targetVersion) return [];
  return normalizeVersions(changelogEntries)
    .filter((entry) => versionInRange(entry.version, currentVersion, targetVersion))
    .sort((a, b) => compareSemver(b.version, a.version));
}

module.exports = {
  parseSemver,
  compareSemver,
  isMajorJump,
  normalizeVersions,
  findSafeTarget,
  collectChangelogRange
};
