// Application version comparison + upstream version lookup for the Phase 5
// catalog. The comparison and Graph/winget mapping are pure and unit-tested; the
// network lookups (`fetchLatestVersion`) require live sources and are marked
// needs-live-verification.

// Compare two dotted version strings numerically with lexical fallback for
// non-numeric segments. Returns -1 (a<b), 0 (equal), or 1 (a>b).
export function compareVersions(a = '', b = '') {
  const norm = (v) => String(v).trim().replace(/^v/i, '').split(/[.\-+]/).filter(Boolean);
  const pa = norm(a);
  const pb = norm(b);
  const len = Math.max(pa.length, pb.length);
  for (let i = 0; i < len; i++) {
    const sa = pa[i] ?? '0';
    const sb = pb[i] ?? '0';
    const na = Number(sa);
    const nb = Number(sb);
    const bothNumeric = Number.isFinite(na) && Number.isFinite(nb) && /^\d+$/.test(sa) && /^\d+$/.test(sb);
    let cmp;
    if (bothNumeric) cmp = na === nb ? 0 : (na < nb ? -1 : 1);
    else cmp = sa === sb ? 0 : (sa < sb ? -1 : 1);
    if (cmp !== 0) return cmp;
  }
  return 0;
}

// Decide whether a catalog entry has a newer upstream version than what is
// currently published.
export function evaluateUpdateState({ currentVersion, latestKnownVersion } = {}) {
  const current = currentVersion || '';
  const latest = latestKnownVersion || '';
  if (!latest) return { updateAvailable: false, current, latest, reason: 'no upstream version known' };
  if (!current) return { updateAvailable: true, current, latest, reason: 'no current version recorded' };
  const cmp = compareVersions(latest, current);
  return {
    updateAvailable: cmp > 0,
    current,
    latest,
    reason: cmp > 0 ? 'newer upstream version available' : 'up to date'
  };
}

// Pure mapping from a Graph mobileApp (win32LobApp) to catalog fields, used when
// adopting an existing tenant app into the catalog.
export function mapGraphAppToCatalog(graphApp = {}) {
  return {
    name: graphApp.displayName || graphApp.name || graphApp.id || 'Imported app',
    vendor: graphApp.publisher || '',
    publisher: graphApp.publisher || '',
    currentVersion: graphApp.displayVersion || graphApp.version || '',
    currentGraphAppId: graphApp.id || ''
  };
}

// Look up the latest upstream version for a catalog entry. `manual` sources are
// operator-driven; `url` expects JSON with a `version` field; `winget` queries a
// community winget index (configurable ref). Network-dependent — verify live.
export async function fetchLatestVersion({ versionSource = 'manual', versionSourceRef = '' } = {}) {
  if (versionSource === 'manual' || !versionSourceRef) {
    return { version: '', source: 'manual', message: 'Manual source; set the latest version by hand.' };
  }
  if (versionSource === 'url') {
    const res = await fetch(versionSourceRef, { headers: { Accept: 'application/json' } });
    if (!res.ok) throw new Error(`Version URL returned ${res.status}`);
    const data = await res.json().catch(() => ({}));
    const version = data.version || data.latestVersion || data.tag_name || '';
    return { version: String(version).replace(/^v/i, ''), source: 'url', message: version ? '' : 'No version field found in URL response.' };
  }
  if (versionSource === 'winget') {
    // Community winget index (e.g. https://api.winget.run/v2/packages/<id>).
    const res = await fetch(versionSourceRef, { headers: { Accept: 'application/json' } });
    if (!res.ok) throw new Error(`winget source returned ${res.status}`);
    const data = await res.json().catch(() => ({}));
    const version = data?.Package?.Latest?.PackageVersion || data?.latestVersion || data?.version || '';
    return { version: String(version), source: 'winget', message: version ? '' : 'No version found in winget response.' };
  }
  throw new Error(`Unknown version source "${versionSource}"`);
}
