export function normalizeSemver(input) {
  const parsed = parseSemver(input);
  return formatSemver(parsed);
}

export function isExactSemver(input) {
  try {
    parseSemver(input);
    return true;
  } catch {
    return false;
  }
}

export function parseSemver(input) {
  const raw = String(input || "").trim().replace(/^v/i, "");
  const match = raw.match(/^(\d+)(?:\.(\d+))?(?:\.(\d+))?$/);
  if (!match) {
    throw new Error(`Invalid semver "${input}"`);
  }

  return {
    major: Number.parseInt(match[1], 10),
    minor: Number.parseInt(match[2] || "0", 10),
    patch: Number.parseInt(match[3] || "0", 10),
  };
}

export function compareSemver(left, right) {
  const a = typeof left === "string" ? parseSemver(left) : left;
  const b = typeof right === "string" ? parseSemver(right) : right;

  if (a.major !== b.major) return a.major - b.major;
  if (a.minor !== b.minor) return a.minor - b.minor;
  return a.patch - b.patch;
}

export function formatSemver(version) {
  const parsed = typeof version === "string" ? parseSemver(version) : version;
  return `${parsed.major}.${parsed.minor}.${parsed.patch}`;
}

export function satisfiesSemver(version, spec) {
  const normalizedSpec = String(spec || "").trim();
  if (!normalizedSpec) return true;
  const parsedVersion = typeof version === "string" ? parseSemver(version) : version;

  for (const alternative of normalizedSpec.split("||").map((part) => part.trim()).filter(Boolean)) {
    if (satisfiesSemverAlternative(parsedVersion, alternative)) {
      return true;
    }
  }

  return false;
}

export function deriveSemverMinimum(spec) {
  const normalizedSpec = String(spec || "").trim();
  if (!normalizedSpec) return null;

  const alternatives = normalizedSpec
    .split("||")
    .map((part) => part.trim())
    .filter(Boolean)
    .map((part) => deriveAlternativeMinimum(part))
    .filter(Boolean);

  if (alternatives.length === 0) return null;
  alternatives.sort(compareSemver);
  return formatSemver(alternatives[0]);
}

function satisfiesSemverAlternative(version, spec) {
  const comparators = expandSpecToComparators(spec);
  return comparators.every((comparator) => compareWithComparator(version, comparator));
}

function deriveAlternativeMinimum(spec) {
  const comparators = expandSpecToComparators(spec);
  if (comparators.length === 0) return null;

  let lowerBound = null;
  for (const comparator of comparators) {
    if (comparator.operator === "=" || comparator.operator === ">=") {
      lowerBound = maxSemver(lowerBound, comparator.version);
      continue;
    }
    if (comparator.operator === ">") {
      lowerBound = maxSemver(lowerBound, incrementPatch(comparator.version));
    }
  }

  const candidate = lowerBound || { major: 0, minor: 0, patch: 0 };
  return comparators.every((comparator) => compareWithComparator(candidate, comparator))
    ? candidate
    : null;
}

function expandSpecToComparators(spec) {
  const trimmed = String(spec || "").trim();
  if (!trimmed) return [];

  const parts = trimmed.split(/\s+/).filter(Boolean);
  const comparators = [];
  for (const part of parts) {
    comparators.push(...expandComparator(part));
  }
  return comparators;
}

function expandComparator(part) {
  const token = String(part || "").trim();
  if (!token) return [];

  const simple = token.match(/^(<=|>=|<|>|=)?\s*(.+)$/);
  if (!simple) {
    throw new Error(`Unsupported semver comparator "${token}"`);
  }

  const operator = simple[1] || "";
  const rhs = simple[2];
  if (operator) {
    return [{ operator: operator === "" ? "=" : operator, version: parseSemver(rhs) }];
  }

  if (rhs.startsWith("^")) {
    const base = parseSemver(rhs.slice(1));
    return [
      { operator: ">=", version: base },
      { operator: "<", version: nextCaretUpperBound(base) },
    ];
  }

  if (rhs.startsWith("~")) {
    const base = parseSemver(rhs.slice(1));
    return [
      { operator: ">=", version: base },
      { operator: "<", version: { major: base.major, minor: base.minor + 1, patch: 0 } },
    ];
  }

  if (/^\d+\.(?:x|\*)$/i.test(rhs)) {
    const [major] = rhs.split(".");
    const base = { major: Number.parseInt(major, 10), minor: 0, patch: 0 };
    return [
      { operator: ">=", version: base },
      { operator: "<", version: { major: base.major + 1, minor: 0, patch: 0 } },
    ];
  }

  if (/^\d+\.\d+\.(?:x|\*)$/i.test(rhs)) {
    const [major, minor] = rhs.split(".");
    const base = {
      major: Number.parseInt(major, 10),
      minor: Number.parseInt(minor, 10),
      patch: 0,
    };
    return [
      { operator: ">=", version: base },
      { operator: "<", version: { major: base.major, minor: base.minor + 1, patch: 0 } },
    ];
  }

  if (/^\d+$/i.test(rhs)) {
    const major = Number.parseInt(rhs, 10);
    return [
      { operator: ">=", version: { major, minor: 0, patch: 0 } },
      { operator: "<", version: { major: major + 1, minor: 0, patch: 0 } },
    ];
  }

  if (/^\d+\.\d+$/i.test(rhs)) {
    const [major, minor] = rhs.split(".").map((value) => Number.parseInt(value, 10));
    return [
      { operator: ">=", version: { major, minor, patch: 0 } },
      { operator: "<", version: { major, minor: minor + 1, patch: 0 } },
    ];
  }

  return [{ operator: "=", version: parseSemver(rhs) }];
}

function compareWithComparator(version, comparator) {
  const comparison = compareSemver(version, comparator.version);
  switch (comparator.operator) {
    case "=":
      return comparison === 0;
    case ">":
      return comparison > 0;
    case ">=":
      return comparison >= 0;
    case "<":
      return comparison < 0;
    case "<=":
      return comparison <= 0;
    default:
      throw new Error(`Unsupported semver operator "${comparator.operator}"`);
  }
}

function nextCaretUpperBound(base) {
  if (base.major > 0) {
    return { major: base.major + 1, minor: 0, patch: 0 };
  }
  if (base.minor > 0) {
    return { major: 0, minor: base.minor + 1, patch: 0 };
  }
  return { major: 0, minor: 0, patch: base.patch + 1 };
}

function incrementPatch(version) {
  return {
    major: version.major,
    minor: version.minor,
    patch: version.patch + 1,
  };
}

function maxSemver(left, right) {
  if (!left) return right;
  return compareSemver(left, right) >= 0 ? left : right;
}
