// src/perf/integrity.ts
function isMissing(value) {
  return value === null || value === void 0;
}
function checkRecordIntegrity(records, resolveJourney) {
  const violations = [];
  for (const [recordIndex, record] of records.entries()) {
    if (record.pass !== true) continue;
    const journey = resolveJourney(record);
    if (journey === null) continue;
    for (const field of journey.requiredFields) {
      if (isMissing(record[field])) {
        violations.push({
          recordIndex,
          journeyId: journey.id,
          field,
          reason: "null-required-field",
          detail: `required field '${field}' is ${record[field] === null ? "null" : "undefined"} on a passing '${journey.id}' record`
        });
      }
    }
    for (const field of journey.phaseFields ?? []) {
      if (isMissing(record[field])) {
        violations.push({
          recordIndex,
          journeyId: journey.id,
          field,
          reason: "null-required-field",
          detail: `phase field '${field}' is ${record[field] === null ? "null" : "undefined"} on a passing '${journey.id}' record`
        });
      }
    }
    for (const { field, min } of journey.minimums ?? []) {
      const value = record[field];
      if (isMissing(value)) continue;
      if (typeof value !== "number" || Number.isNaN(value)) {
        violations.push({
          recordIndex,
          journeyId: journey.id,
          field,
          reason: "below-minimum",
          detail: `field '${field}' has non-numeric value ${JSON.stringify(value)} on a passing '${journey.id}' record (minimum ${min})`
        });
        continue;
      }
      if (value < min) {
        violations.push({
          recordIndex,
          journeyId: journey.id,
          field,
          reason: "below-minimum",
          detail: `field '${field}' is ${value}, below minimum ${min} on a passing '${journey.id}' record`
        });
      }
    }
  }
  return { succeeded: violations.length === 0, violations };
}
function assertRecordIntegrity(records, resolveJourney) {
  const result = checkRecordIntegrity(records, resolveJourney);
  if (result.succeeded) return;
  const lines = result.violations.map(
    (v) => `  [record ${v.recordIndex}] ${v.journeyId}.${v.field} (${v.reason}): ${v.detail}`
  );
  throw new Error(
    `Record integrity check failed with ${result.violations.length} violation(s):
${lines.join("\n")}`
  );
}

// src/perf/journey.ts
function scenarioKey(journeyId, axes) {
  const parts = Object.keys(axes).sort().map((dim) => `${dim}=${axes[dim]}`);
  return [journeyId, ...parts].join("|");
}
function expandMatrix(journeys, axes, filter) {
  const dims = Object.keys(axes).sort();
  let combos = [{}];
  for (const dim of dims) {
    const values = axes[dim];
    const next = [];
    for (const combo of combos) {
      for (const value of values) {
        next.push({ ...combo, [dim]: value });
      }
    }
    combos = next;
  }
  const scenarios = [];
  for (const journey of journeys) {
    for (const combo of combos) {
      if (filter && !filter(journey.id, combo)) continue;
      scenarios.push({ key: scenarioKey(journey.id, combo), journey, axes: combo });
    }
  }
  return scenarios;
}

// src/perf/ratchet.ts
function nearestRank(sorted, p) {
  if (sorted.length === 0) throw new Error("nearestRank requires at least one sample");
  const rank = Math.max(1, Math.ceil(p / 100 * sorted.length));
  return sorted[rank - 1];
}
function summarizeRecords(records, keyOf, metricFields) {
  const samples = /* @__PURE__ */ new Map();
  for (const record of records) {
    const key = keyOf(record);
    if (key === null) continue;
    let byField = samples.get(key);
    if (!byField) {
      byField = /* @__PURE__ */ new Map();
      samples.set(key, byField);
    }
    for (const field of metricFields) {
      const value = record[field];
      if (typeof value !== "number" || Number.isNaN(value)) continue;
      let values = byField.get(field);
      if (!values) {
        values = [];
        byField.set(field, values);
      }
      values.push(value);
    }
  }
  const scenarios = {};
  for (const [key, byField] of samples) {
    const stats = {};
    for (const [field, values] of byField) {
      if (values.length === 0) continue;
      const sorted = [...values].sort((a, b) => a - b);
      stats[field] = {
        p50: nearestRank(sorted, 50),
        p90: nearestRank(sorted, 90),
        n: sorted.length
      };
    }
    scenarios[key] = stats;
  }
  return { version: 1, scenarios };
}
function pctOver(currentValue, baselineValue) {
  if (baselineValue === 0) {
    return currentValue === 0 ? 0 : Number.POSITIVE_INFINITY;
  }
  return (currentValue - baselineValue) / baselineValue * 100;
}
function gatePerf(current, baseline, options) {
  const tolerancePct = options?.tolerancePct ?? 10;
  const minSamples = options?.minSamples ?? 3;
  const regressions = [];
  const improvements = [];
  const missing = /* @__PURE__ */ new Set();
  for (const [scenarioKey2, baselineStats] of Object.entries(baseline.scenarios)) {
    const currentStats = current.scenarios[scenarioKey2];
    if (!currentStats) {
      missing.add(scenarioKey2);
      continue;
    }
    for (const [field, baselineStat] of Object.entries(baselineStats)) {
      const currentStat = currentStats[field];
      if (!currentStat || currentStat.n < minSamples) {
        missing.add(scenarioKey2);
        continue;
      }
      const overBy = {
        p50Pct: pctOver(currentStat.p50, baselineStat.p50),
        p90Pct: pctOver(currentStat.p90, baselineStat.p90)
      };
      const entry = {
        scenarioKey: scenarioKey2,
        field,
        baseline: baselineStat,
        current: currentStat,
        overBy
      };
      if (overBy.p50Pct > tolerancePct || overBy.p90Pct > tolerancePct) {
        regressions.push(entry);
      } else if (overBy.p50Pct < 0 && overBy.p90Pct < 0) {
        improvements.push(entry);
      }
    }
  }
  const newScenarios = Object.keys(current.scenarios).filter((key) => !(key in baseline.scenarios));
  return {
    succeeded: regressions.length === 0,
    regressions,
    improvements,
    missingScenarios: [...missing],
    newScenarios
  };
}

export {
  checkRecordIntegrity,
  assertRecordIntegrity,
  scenarioKey,
  expandMatrix,
  summarizeRecords,
  gatePerf
};
//# sourceMappingURL=chunk-STGVSCDH.js.map