/**
 * commands/statusline.mjs — C22 statusline primitive.
 *
 * Emits a single-line status summary suitable for embedding in a shell prompt
 * or external statusline tool. Reads shared .aperant/state.json and the live
 * team-status/ directory via the CoordinationStore seam (R8).
 *
 * Modes:
 *   --json         Emit a structured envelope { status, command, line, data, updated_at }.
 *   --ansi         Prefer ANSI; NO_COLOR still wins (https://no-color.org/).
 *   --plain        Force plain text (no ANSI).
 *   (default)      TTY → ANSI; non-TTY → plain. NO_COLOR forces plain.
 *
 * Gracefully handles missing .aperant/ — returns a muted "not initialized" line
 * so the caller can still render a prompt segment.
 */

import { existsSync, readFileSync } from 'node:fs'
import { join, resolve } from 'node:path'
import { createCoordinationStore } from '../coordination/store.mjs'
import { formatUpdateChips, readUpdateCache } from '../install/update-chips.mjs'
import { statusLine, supportsColor } from '../output-style/index.mjs'
import { parseFlags } from '../util/args.mjs'
import { err, ok, rawText } from '../util/result.mjs'

/**
 * Normalize `active_tasks` from state.json across the legacy (single object)
 * and modern (map) shapes. Mirrors commands/state.mjs lines 34-41.
 */
function normalizeActiveTasks(state) {
	if (state?.active_tasks && typeof state.active_tasks === 'object') return state.active_tasks
	if (state?.active_task) {
		const id = state.active_task.id || 'legacy-task'
		return { [id]: state.active_task }
	}
	return {}
}

/**
 * C54 — build the install-status chip(s) from active_tasks. Returns a
 * {chips, summary} pair:
 *   chips: array of wrap()-ed strings ready to prefix onto the statusline
 *   summary: { running, failed, oldest_ecosystem } for --json callers
 *
 * Behavior:
 *   - Single running task: `installing [<ecosystem>] <N>s`
 *   - Multi running task: `installing [N tasks]`
 *   - Any failed task: red `install failed` chip (persists until closed)
 */
function buildInstallChips(activeTasks, { color }) {
	const ANSI_RED = '\x1b[31m'
	const ANSI_YELLOW = '\x1b[33m'
	const ANSI_RESET = '\x1b[0m'
	const wrap = (ansi, text) => (color ? `${ansi}${text}${ANSI_RESET}` : text)

	const running = []
	const failed = []
	for (const t of Object.values(activeTasks)) {
		if (!t || typeof t !== 'object') continue
		if (t.install_status === 'running' || t.install_status === 'queued') running.push(t)
		if (t.install_status === 'failed') failed.push(t)
	}

	const chips = []
	const summary = {
		running: running.length,
		failed: failed.length,
		oldest_ecosystem: null,
	}

	if (running.length === 1) {
		const t = running[0]
		const eco =
			Array.isArray(t.install_ecosystems) && t.install_ecosystems.length > 0
				? t.install_ecosystems[0]
				: 'install'
		summary.oldest_ecosystem = eco
		let elapsed = 0
		if (t.install_started_at) {
			const started = Date.parse(t.install_started_at)
			if (!Number.isNaN(started)) elapsed = Math.max(0, Math.round((Date.now() - started) / 1000))
		}
		chips.push(wrap(ANSI_YELLOW, `installing [${eco}] ${elapsed}s`))
	} else if (running.length > 1) {
		const oldest = running.reduce((a, b) => {
			const ta = Date.parse(a.install_started_at || '') || Infinity
			const tb = Date.parse(b.install_started_at || '') || Infinity
			return ta <= tb ? a : b
		})
		if (Array.isArray(oldest.install_ecosystems) && oldest.install_ecosystems.length > 0) {
			summary.oldest_ecosystem = oldest.install_ecosystems[0]
		}
		chips.push(wrap(ANSI_YELLOW, `installing [${running.length} tasks]`))
	}
	if (failed.length > 0) {
		chips.push(wrap(ANSI_RED, 'install failed'))
	}
	return { chips, summary }
}

/**
 * Derive a `current_phase` to display — prefer the most-recently-updated
 * active task's lifecycle_phase. Falls back to null when nothing is active.
 */
function pickCurrentPhase(activeTasks) {
	const entries = Object.values(activeTasks)
	if (entries.length === 0) return null
	const sorted = entries
		.filter((e) => e && typeof e === 'object')
		.sort((a, b) => {
			const ta = Date.parse(a.updated_at || a.created_at || 0) || 0
			const tb = Date.parse(b.updated_at || b.created_at || 0) || 0
			return tb - ta
		})
	return sorted[0]?.lifecycle_phase || null
}

/**
 * Resolve the effective color flag. NO_COLOR always wins — even over an
 * explicit `--ansi`. Otherwise: --plain forces off, --ansi forces on,
 * default falls through to supportsColor().
 */
function resolveColor({ forceAnsi, forcePlain, env, stream }) {
	if (env.NO_COLOR !== undefined && env.NO_COLOR !== '') return false
	if (forcePlain) return false
	if (forceAnsi) return true
	return supportsColor({ stream, env })
}

export function cmdStatusline(projectDir, extraArgs = []) {
	if (!projectDir) return err('Usage: apt-tools statusline <project-dir> [--json|--ansi|--plain]')
	const targetDir = resolve(projectDir)
	const flags = parseFlags(extraArgs)
	const asJson = flags.has('json')
	const forceAnsi = flags.has('ansi')
	const forcePlain = flags.has('plain')

	const color = resolveColor({
		forceAnsi,
		forcePlain,
		env: process.env,
		stream: process.stdout,
	})

	const aperantDir = join(targetDir, '.aperant')
	const statePath = join(aperantDir, 'state.json')

	// Read SessionStart worker cache once — chips prepend onto every render
	// path (initialized or not).
	const updateCache = readUpdateCache()
	const chips = formatUpdateChips(updateCache, { color })
	const chipPrefix = chips.length > 0 ? `${chips.join(' ')} | ` : ''

	if (!existsSync(aperantDir)) {
		const baseLine = statusLine({}, { color })
		const line = chipPrefix + baseLine
		if (asJson) {
			return ok({
				status: 'not-initialized',
				command: 'statusline',
				projectDir: targetDir,
				line,
				data: {
					active_task_count: 0,
					completed_count: 0,
					last_activity: null,
					team_count: 0,
					current_phase: null,
				},
				update_status: updateCache,
				updated_at: new Date().toISOString(),
			})
		}
		return rawText(line + '\n', 0)
	}

	let state = null
	if (existsSync(statePath)) {
		try {
			state = JSON.parse(readFileSync(statePath, 'utf-8'))
		} catch {
			state = null
		}
	}

	const activeTasks = normalizeActiveTasks(state)
	const active_task_count = Object.keys(activeTasks).length
	const completed_count = Array.isArray(state?.completed_tasks) ? state.completed_tasks.length : 0
	const last_activity = state?.last_activity || null
	const current_phase = pickCurrentPhase(activeTasks)

	// Live-agent count via CoordinationStore — R4 liveness TTL filters stale
	// entries automatically so a freshly-rebooted host doesn't show 47 ghosts.
	let team_count = 0
	try {
		const store = createCoordinationStore(targetDir)
		const records = store.listTeamStatus({ includeStale: false })
		team_count = Array.isArray(records) ? records.length : 0
	} catch {
		team_count = 0
	}

	// C54 — install chip(s) prefix BEFORE the existing chipPrefix so install
	// activity is the first thing the user sees.
	const installChipsResult = buildInstallChips(activeTasks, { color })
	const installChipPrefix =
		installChipsResult.chips.length > 0 ? `${installChipsResult.chips.join(' ')} • ` : ''

	const baseLine = statusLine(
		{ active_tasks: active_task_count, current_phase, last_activity, team_count },
		{ color },
	)
	const line = installChipPrefix + chipPrefix + baseLine

	if (asJson) {
		return ok({
			status: 'ok',
			command: 'statusline',
			projectDir: targetDir,
			line,
			data: {
				active_task_count,
				completed_count,
				last_activity,
				team_count,
				current_phase,
				install_status_summary: installChipsResult.summary,
			},
			update_status: updateCache,
			updated_at: new Date().toISOString(),
		})
	}

	return rawText(line + '\n', 0)
}
