/**
 * @internal
 * consistency/rules/r4-finding-propagation.mjs — R4 (C15).
 *
 * Every unresolved (status !== 'fixed') critical/major finding in review.json
 * must have a matching entry in qa_signoff.json findings (by title substring
 * or file equality).
 *
 * Severity: error. Stages: review, ship.
 * Skip when: review.json or qa_signoff.json absent.
 */

import { registerRule } from '../registry.mjs'

const BLOCKING = new Set(['critical', 'major'])

const rule = {
	id: 'r4-finding-propagation',
	severity: 'error',
	stages: ['review', 'ship'],
	description: 'Unresolved review findings are cited in qa_signoff.',
	async check({ parsed }) {
		if (!parsed?.review?.present || !parsed?.qa?.present) {
			return {
				status: 'skip',
				drift: [],
				reason: 'review.json or qa_signoff.json not present',
			}
		}
		const reviewFindings = parsed.review.findings ?? []
		const qaFindings = parsed.qa.findings ?? []
		const unresolved = reviewFindings.filter(
			(f) => BLOCKING.has(f.severity) && f.status !== 'fixed',
		)
		const drift = []
		for (const rf of unresolved) {
			const matched = qaFindings.some((qf) => {
				if (rf.file && qf.file && rf.file === qf.file) return true
				if (rf.title && qf.title?.includes(rf.title)) return true
				if (rf.title && qf.title && rf.title.includes(qf.title)) return true
				return false
			})
			if (!matched) {
				drift.push({
					review_finding_id: rf.id,
					title: rf.title,
					severity: rf.severity,
					reason: 'not cited in qa_signoff',
				})
			}
		}
		if (drift.length > 0) {
			return {
				status: 'fail',
				drift,
				reason: `${drift.length} unresolved review finding(s) missing from qa_signoff`,
			}
		}
		return {
			status: 'pass',
			drift: [],
			reason: `${unresolved.length} unresolved review finding(s) cited in qa_signoff`,
		}
	},
}

registerRule(rule)
export default rule
