/**
 * commands/consistency.mjs — `apt-tools consistency <list|check>` (C15).
 *
 * Thin CLI surface over checkConsistency + registry.listRules. Both
 * subcommands emit a structured JSON envelope via util/io.mjs:output.
 * `check` exits 1 when the envelope.status is 'fail'.
 *
 *   apt-tools consistency list [project-dir]
 *     → { status: 'ok', command: 'consistency-list', count, rules: [...] }
 *
 *   apt-tools consistency check <project-dir> --task-id <id>
 *     [--stage plan|execute|verify|review|ship]    (default: ship)
 *     [--rules r1,r3]                              (subset)
 *     [--format human|json]                        (default: json)
 */

import { existsSync } from 'node:fs'
import { join, resolve } from 'node:path'

import { checkConsistency } from '../consistency/check.mjs'
import { listRules } from '../consistency/registry.mjs'
// Eagerly register the built-in rules so list/check see them.
import '../consistency/rules/index.mjs'
import { createCoordinationStore } from '../coordination/store.mjs'
import { parseFlags } from '../util/args.mjs'
import { err, exitWith, ok, rawText, UsageError } from '../util/result.mjs'

const VALID_STAGES = new Set(['plan', 'execute', 'verify', 'review', 'ship'])

/**
 * Sentinel error class for helper-level usage failures inside cmdConsistency.
 * The dispatcher catches it and converts it into the standard err() envelope.
 */
class ConsistencyUsageError extends UsageError {
	constructor(message) {
		super(message)
		this.name = 'ConsistencyUsageError'
	}
}

export async function cmdConsistency(subcommand, projectDir, extraArgs) {
	if (!subcommand) {
		return err('Usage: apt-tools consistency <list|check> [project-dir] [flags]')
	}
	try {
		switch (subcommand) {
			case 'list':
				return cmdConsistencyList(projectDir, extraArgs ?? [])
			case 'check':
				return await cmdConsistencyCheck(projectDir, extraArgs ?? [])
			default:
				return err(`Unknown consistency subcommand: ${subcommand}. Expected: list, check`)
		}
	} catch (e) {
		if (e instanceof ConsistencyUsageError) return err(e.message)
		throw e
	}
}

function cmdConsistencyList(_projectDir, _extraArgs) {
	const rules = listRules().map((r) => ({
		id: r.id,
		severity: r.severity,
		stages: r.stages,
		description: r.description,
	}))
	return ok({
		status: 'ok',
		command: 'consistency-list',
		count: rules.length,
		rules,
	})
}

async function cmdConsistencyCheck(projectDir, extraArgs) {
	if (!projectDir) {
		throw new ConsistencyUsageError(
			'Usage: apt-tools consistency check <project-dir> --task-id <id>',
		)
	}
	const flags = parseFlags(extraArgs)
	const taskId = flags.get('task-id')
	if (!taskId || typeof taskId !== 'string')
		throw new ConsistencyUsageError('Required: --task-id <id>')
	const stageRaw = flags.get('stage')
	const stage = typeof stageRaw === 'string' ? stageRaw : 'ship'
	if (!VALID_STAGES.has(stage)) {
		throw new ConsistencyUsageError(
			`Invalid --stage: ${stage}. Expected one of: ${Array.from(VALID_STAGES).join(', ')}`,
		)
	}
	const rulesRaw = flags.get('rules')
	const ruleIds =
		typeof rulesRaw === 'string'
			? rulesRaw
					.split(',')
					.map((s) => s.trim())
					.filter(Boolean)
			: undefined
	const formatRaw = flags.get('format')
	const format = formatRaw === 'human' ? 'human' : 'json'

	const absRoot = resolve(projectDir)
	const store = createCoordinationStore(absRoot)
	const sharedRoot = store.resolveSharedRoot()
	const taskDir = join(sharedRoot, '.aperant', 'tasks', taskId)
	if (!existsSync(taskDir)) throw new ConsistencyUsageError(`task not found: ${taskId}`)

	const envelope = await checkConsistency({ taskDir, stage, ruleIds })
	const payload = {
		status: envelope.status,
		command: 'consistency-check',
		task_id: taskId,
		stage: envelope.stage,
		errors: envelope.errors,
		warnings: envelope.warnings,
		rules: envelope.rules,
	}

	if (format === 'human') {
		// Human format emits raw text. Builds the human-readable string in
		// renderHuman() then routes through wrap()'s rawText shape so the
		// exit code is carried out-of-band (no process.exit() inside the cmd).
		const text = renderHuman(payload)
		return rawText(text, envelope.status === 'fail' ? 1 : 0)
	}
	return exitWith(payload, envelope.status === 'fail' ? 1 : 0)
}

function renderHuman(env) {
	const lines = []
	lines.push(
		`consistency-check  task=${env.task_id}  stage=${env.stage}  status=${env.status}  errors=${env.errors}  warnings=${env.warnings}`,
	)
	lines.push('')
	const header = padRow(['RULE', 'SEVERITY', 'STATUS', 'REASON'])
	lines.push(header)
	lines.push('-'.repeat(header.length))
	for (const r of env.rules) {
		lines.push(padRow([r.id, r.severity, r.status, truncate(r.reason, 60)]))
		if (r.status === 'fail' && Array.isArray(r.drift) && r.drift.length > 0) {
			for (const d of r.drift) {
				lines.push(`    - ${truncate(JSON.stringify(d), 120)}`)
			}
		}
	}
	return `${lines.join('\n')}\n`
}

function padRow(cells) {
	const widths = [28, 10, 8, 64]
	return cells.map((c, i) => String(c).padEnd(widths[i] ?? 10)).join(' ')
}

function truncate(s, n) {
	if (typeof s !== 'string') s = String(s ?? '')
	return s.length > n ? `${s.slice(0, n - 1)}…` : s
}
