/**
 * @internal
 * rollup.mjs — milestone-status rollup helper (C33 §In-scope #6).
 *
 * Milestone status is a DERIVED field — always recomputed from phase statuses.
 * The JSON field (when present) is a cache; every writer re-derives.
 *
 * Truth table (spec AC9):
 *   - phases all [completed | skipped]            → 'completed'
 *   - any phase 'blocked'                         → 'blocked'
 *   - any phase 'in-progress'                     → 'in-progress'
 *   - any completed/skipped mixed with planned    → 'in-progress'
 *   - phases all 'planned'                        → 'planned'
 *   - phases = []                                 → null
 */

import { loadRoadmapForScope, saveRoadmapForScope } from './io.mjs'

/**
 * Pure — compute the rollup status for a milestone object.
 *
 * @param {object} milestone  expects `phases: Array<{status}>`
 * @returns {'completed'|'in-progress'|'planned'|'blocked'|null}
 */
export function rollupMilestoneStatus(milestone) {
	if (!milestone || !Array.isArray(milestone.phases) || milestone.phases.length === 0) return null
	const statuses = milestone.phases.map((p) => p?.status || 'planned')
	if (statuses.some((s) => s === 'blocked')) return 'blocked'
	if (statuses.some((s) => s === 'in-progress')) return 'in-progress'
	const allDone = statuses.every((s) => s === 'completed' || s === 'skipped')
	if (allDone) return 'completed'
	const anyDone = statuses.some((s) => s === 'completed' || s === 'skipped')
	const anyPlanned = statuses.some((s) => s === 'planned')
	if (anyDone && anyPlanned) return 'in-progress'
	// Only remaining legitimate case: all planned.
	return 'planned'
}

/**
 * Apply the rollup to a milestone inside a roadmap. Returns a summary:
 *
 *   - before: the current stored status (null when none)
 *   - after:  the computed status
 *   - changed: true iff we would persist a new value
 *
 * Does NOT write; callers decide.
 */
export function applyRollup(roadmap, milestoneId) {
	if (!roadmap || !Array.isArray(roadmap.milestones)) {
		return { before: null, after: null, changed: false, milestone_id: milestoneId }
	}
	const milestone = roadmap.milestones.find((m) => m && m.id === milestoneId)
	if (!milestone) {
		return { before: null, after: null, changed: false, milestone_id: milestoneId }
	}
	const before = milestone.status ?? null
	const after = rollupMilestoneStatus(milestone)
	// Only persist when the computed value differs from what's stored.
	// Writing `null` back when nothing was stored is a no-op.
	const changed = after !== before && !(before == null && after == null)
	if (changed) {
		milestone.status = after
	}
	return { before, after, changed, milestone_id: milestoneId }
}

/**
 * Convenience writer — load → rollup → save. Used by `apt-tools roadmap rollup`
 * and by `applyLifecyclePhaseSideEffect` after a phase transition.
 *
 * Returns `{before, after, changed, committed, milestone_id}`. `committed`
 * mirrors `changed` today; it exists so future callers can distinguish
 * in-memory mutation from on-disk persistence.
 */
export function writeRollup(projectDir, scope, milestoneId) {
	const roadmap = loadRoadmapForScope(projectDir, scope)
	if (!roadmap) {
		return {
			before: null,
			after: null,
			changed: false,
			committed: false,
			milestone_id: milestoneId,
		}
	}
	const summary = applyRollup(roadmap, milestoneId)
	if (summary.changed) {
		saveRoadmapForScope(projectDir, scope, roadmap)
	}
	return { ...summary, committed: summary.changed }
}
