export function normalizeFailureDetail(detail) {
  if (!detail || typeof detail !== "object") return null;

  const kind = normalizeNonEmptyString(detail.kind);
  const key = normalizeNonEmptyString(detail.key);
  const title = normalizeNonEmptyString(detail.title);
  if (!kind || !key || !title) return null;

  const normalized = {
    kind,
    key,
    title,
    count: normalizePositiveInteger(detail.count) || 1,
  };

  const groupPath = normalizeStringArray(detail.groupPath);
  if (groupPath.length > 0) normalized.groupPath = groupPath;

  const checkName = normalizeNonEmptyString(detail.checkName);
  if (checkName) normalized.checkName = checkName;

  const suitePath = normalizeStringArray(detail.suitePath);
  if (suitePath.length > 0) normalized.suitePath = suitePath;

  const phase = normalizeNonEmptyString(detail.phase);
  if (phase) normalized.phase = phase;

  const message = normalizeNonEmptyString(detail.message);
  if (message) normalized.message = message;

  if (detail.expected !== undefined) normalized.expected = cloneJsonValue(detail.expected);
  if (detail.actual !== undefined) normalized.actual = cloneJsonValue(detail.actual);

  const traceId = normalizeNonEmptyString(detail.traceId);
  if (traceId) normalized.traceId = traceId;

  const request = normalizeRecord(detail.request);
  if (request) normalized.request = request;

  const response = normalizeRecord(detail.response);
  if (response) normalized.response = response;

  const location = normalizeRecord(detail.location);
  if (location) normalized.location = location;

  const stack = normalizeNonEmptyString(detail.stack);
  if (stack) normalized.stack = stack;

  return normalized;
}

export function mergeFailureDetails(details) {
  const mergedByKey = new Map();

  for (const detail of Array.isArray(details) ? details : []) {
    const normalized = normalizeFailureDetail(detail);
    if (!normalized) continue;
    const dedupeKey = `${normalized.kind}::${normalized.key}`;
    const existing = mergedByKey.get(dedupeKey);
    if (!existing) {
      mergedByKey.set(dedupeKey, { ...normalized });
      continue;
    }
    existing.count += normalized.count;
    if (!existing.message && normalized.message) {
      existing.message = normalized.message;
    }
  }

  return [...mergedByKey.values()].sort(
    (left, right) =>
      left.kind.localeCompare(right.kind) || left.key.localeCompare(right.key)
  );
}

export function collectFailureDetailsFromRuntimeArtifacts(artifacts) {
  const details = [];

  for (const artifact of Array.isArray(artifacts) ? artifacts : []) {
    if (artifact?.kind !== "testkit.failure-details") continue;
    const phase = normalizeNonEmptyString(artifact?.data?.phase);
    for (const detail of Array.isArray(artifact?.data?.failures) ? artifact.data.failures : []) {
      details.push({
        ...detail,
        phase: detail?.phase || phase || null,
      });
    }
  }

  return mergeFailureDetails(details);
}

function normalizeStringArray(value) {
  if (!Array.isArray(value)) return [];
  return value
    .map((entry) => normalizeNonEmptyString(entry))
    .filter(Boolean);
}

function normalizeNonEmptyString(value) {
  if (typeof value !== "string") return null;
  const normalized = value.trim();
  return normalized.length > 0 ? normalized : null;
}

function normalizePositiveInteger(value) {
  if (!Number.isInteger(value) || value <= 0) return null;
  return value;
}

function normalizeRecord(value) {
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
  return cloneJsonValue(value);
}

function cloneJsonValue(value) {
  try {
    return JSON.parse(JSON.stringify(value));
  } catch {
    return String(value);
  }
}
