/**
 * release-notes/ship-autodraft.mjs — apt-ship Section 2.6 helper.
 *
 * Deterministic, unit-testable shape: given a feature-registry diff +
 * personas envelope + persona_filter mode, decide whether the PR body
 * should grow a `Release note: …` line OR ship should block.
 *
 * This module is intentionally pure — the apt-ship SKILL.md body shells
 * `apt-tools features-audit . --diff-since <base> --json` and feeds the
 * envelope here. Keeping the decision pure makes TD-4 trivial: fixture in,
 * decision out, no I/O.
 */

import { buildPersonaTierMap, classifyFragment } from './persona-filter.mjs'

/**
 * @typedef {Object} FeatureDiffEntry
 * @property {string} feature_id
 * @property {string} [area]
 * @property {string[]} [personas]
 */

/**
 * @typedef {Object} AutodraftInput
 * @property {FeatureDiffEntry[]} featureDiff
 * @property {{ personas?: Array<{ ui_json?: unknown }> } | null} personasEnvelope
 * @property {'primary' | 'primary+secondary' | 'all'} personaFilter
 * @property {boolean} requirePrField
 */

/**
 * @typedef {{
 *   status: 'draft',
 *   line: string,
 *   persona: string,
 *   matchedFeatures: string[],
 * } | {
 *   status: 'block',
 *   reason: 'no-feature-match' | 'no-persona-signal',
 *   message: string,
 * } | {
 *   status: 'warn',
 *   reason: 'no-feature-match' | 'no-persona-signal',
 *   message: string,
 * }} AutodraftResult
 */

const BLOCK_MESSAGE =
	'[APT] No release-note signal found — author manual note in PR body or label PR no-notes'

/**
 * Compute the autodraft decision.
 *
 * @param {AutodraftInput} input
 * @returns {AutodraftResult}
 */
export function decideAutodraft(input) {
	const tierMap = buildPersonaTierMap(input.personasEnvelope)

	if (!Array.isArray(input.featureDiff) || input.featureDiff.length === 0) {
		return failClassify(input, 'no-feature-match')
	}

	// Walk the diff; the first feature whose persona resolves into the
	// filter mode wins. (Multi-persona dispatch is intentionally out of
	// scope here — host LLM-side narrative can refine per persona; the
	// CLI just picks a clean signal so ship is decisive.)
	for (const feature of input.featureDiff) {
		if (!Array.isArray(feature.personas) || feature.personas.length === 0) continue
		for (const personaTag of feature.personas) {
			const decision = classifyFragment(personaTag, tierMap, input.personaFilter)
			if (decision.bucket === 'body' && decision.resolvedTier) {
				return {
					status: 'draft',
					line: `Release note: ${feature.feature_id} — ships for ${personaTag}.`,
					persona: personaTag,
					matchedFeatures: [feature.feature_id],
				}
			}
		}
	}

	// No persona signal anywhere — features exist but none carry a tag
	// that the roster + filter accept.
	return failClassify(input, 'no-persona-signal')
}

/**
 * Translate a failure reason into the right envelope shape based on
 * `require_pr_field`. require=true → block; require=false → warn (skill
 * proceeds without the line).
 *
 * @param {AutodraftInput} input
 * @param {'no-feature-match' | 'no-persona-signal'} reason
 * @returns {AutodraftResult}
 */
function failClassify(input, reason) {
	if (input.requirePrField) {
		return { status: 'block', reason, message: BLOCK_MESSAGE }
	}
	return {
		status: 'warn',
		reason,
		message: `[APT] auto-draft skipped (${reason}) — release_notes.require_pr_field is false; proceeding without a Release note line.`,
	}
}
