/**
 * @internal
 * consistency/parse-review.mjs — normalize review.json findings (C15).
 *
 * Accepts both flat (`findings: [{severity: 'critical'|'warning'|...}]`)
 * and grouped (`findings: {critical: [...], major: [...]}`) shapes.
 * Also honors `issues: [...]` as a legacy alias for flat findings.
 *
 * Canonical severities retained: `critical`, `major`.
 * `warning` -> `major`. `suggestion|nitpick|note|info` are dropped.
 *
 * Status normalizes to `fixed | deferred | open` (default `open`).
 * Both `fixed: true` and `resolved: true` are treated as `status: 'fixed'`
 * (the reviewer skill writes `fixed: true`; `resolved: true` is legacy).
 *
 * Output: {findings: [{id, severity, status, title, file}]}
 */

const SEVERITY_MAP = {
	critical: 'critical',
	blocker: 'critical',
	major: 'major',
	warning: 'major',
	warn: 'major',
}

const DROPPED_SEVERITIES = new Set(['suggestion', 'nitpick', 'note', 'info', 'minor'])

const STATUS_MAP = {
	fixed: 'fixed',
	resolved: 'fixed',
	closed: 'fixed',
	deferred: 'deferred',
	open: 'open',
	unresolved: 'open',
}

/**
 * @param {object | null | undefined} review
 */
export function parseReview(review) {
	if (!review || typeof review !== 'object') {
		return { findings: [] }
	}

	const raw = collectRaw(review)
	const findings = []
	for (let i = 0; i < raw.length; i++) {
		const f = raw[i]
		if (!f || typeof f !== 'object') continue
		const sev = normalizeSeverity(f.severity)
		if (!sev) continue
		findings.push({
			id: typeof f.id === 'string' ? f.id : typeof f.id === 'number' ? String(f.id) : `rf-${i + 1}`,
			severity: sev,
			status: normalizeStatus(f),
			title: typeof f.title === 'string' ? f.title : typeof f.message === 'string' ? f.message : '',
			file: typeof f.file === 'string' ? f.file : typeof f.path === 'string' ? f.path : '',
		})
	}
	return { findings }
}

function collectRaw(review) {
	const out = []
	const findings = review.findings
	if (Array.isArray(findings)) {
		for (const f of findings) out.push(f)
	} else if (findings && typeof findings === 'object') {
		for (const [groupKey, group] of Object.entries(findings)) {
			if (!Array.isArray(group)) continue
			for (const f of group) {
				if (!f || typeof f !== 'object') continue
				// Inherit group severity when the item doesn't carry one.
				out.push({ severity: f.severity ?? groupKey, ...f })
			}
		}
	}
	if (Array.isArray(review.issues)) {
		for (const f of review.issues) out.push(f)
	}
	return out
}

function normalizeSeverity(sev) {
	if (typeof sev !== 'string') return null
	const key = sev.toLowerCase().trim()
	if (DROPPED_SEVERITIES.has(key)) return null
	return SEVERITY_MAP[key] ?? null
}

function normalizeStatus(f) {
	if (f.fixed === true) return 'fixed'
	if (f.resolved === true) return 'fixed'
	if (typeof f.status === 'string') {
		const s = f.status.toLowerCase().trim()
		if (STATUS_MAP[s]) return STATUS_MAP[s]
		// BUG-009: reviewer-skill writes `deferred-per-spec`, `deferred-followup`,
		// etc. for findings that are intentionally not being fixed in the
		// current scope. Collapse the variant prefix back to canonical
		// `deferred` so downstream gates (review-clean, ship) treat them
		// the same as a bare `status: "deferred"`.
		if (s.startsWith('deferred-')) return 'deferred'
	}
	return 'open'
}
