/**
 * @internal
 * gate/context.mjs — GateContext builder (C2).
 *
 * Gates receive a shared, cheap-to-build context object so they don't have to
 * re-derive paths, task records, and config. The context carries:
 *
 *   {
 *     projectRoot:  string,          // absolute caller cwd (may be worktree)
 *     sharedRoot:   string,          // main repo root via CoordinationStore
 *     taskId:       string,
 *     taskDir:      string,          // <sharedRoot>/.aperant/tasks/<taskId>/
 *     taskRecord:   object | null,   // state.active_tasks[taskId] (or null)
 *     config:       object,          // .aperant/config.json (empty {} if none)
 *     profile:      'lite' | 'full-pipeline'
 *                                    // derived from taskRecord.track:
 *                                    //   QUICK | DEBUG    → 'lite'
 *                                    //   STANDARD | DEEP  → 'full-pipeline'
 *                                    //   null taskRecord  → 'full-pipeline'
 *                                    //                       (fail-closed)
 *   }
 *
 * The builder is side-effect-free — it never writes state, never mutates the
 * task record, never touches the event log.
 */

import { existsSync, readFileSync } from 'node:fs'
import { join, resolve } from 'node:path'
import { createCoordinationStore } from '../coordination/store.mjs'

const SAFE_TASK_ID_PATTERN = /^[a-zA-Z0-9_-]+$/

const KNOWN_TRACKS = new Set(['QUICK', 'STANDARD', 'DEEP', 'DEBUG'])

function readJsonIfExists(path) {
	if (!existsSync(path)) return null
	try {
		return JSON.parse(readFileSync(path, 'utf-8'))
	} catch {
		return null
	}
}

/**
 * Build a GateContext for the given task. Throws if the task directory does
 * not exist — callers should catch and surface the "task not found" error as
 * a CLI-level failure envelope.
 *
 * @internal
 */
export function buildGateContext(projectRoot, taskId) {
	if (!taskId || typeof taskId !== 'string') {
		throw new Error('buildGateContext: taskId is required')
	}
	if (!SAFE_TASK_ID_PATTERN.test(taskId)) {
		const err = new Error(`invalid task id: ${taskId}`)
		err.code = 'ETASKINVALID'
		throw err
	}
	const absRoot = resolve(projectRoot)
	const store = createCoordinationStore(absRoot)
	const sharedRoot = store.resolveSharedRoot()
	const sharedTaskDir = join(sharedRoot, '.aperant', 'tasks', taskId)
	const localTaskDir = join(absRoot, '.aperant', 'tasks', taskId)
	const taskDir = existsSync(sharedTaskDir)
		? sharedTaskDir
		: sharedRoot !== absRoot && existsSync(localTaskDir)
			? localTaskDir
			: null
	if (!taskDir) {
		const err = new Error(`task not found: ${taskId}`)
		err.code = 'ETASKNOTFOUND'
		throw err
	}

	const state = store.readState()
	const localState =
		sharedRoot !== absRoot ? readJsonIfExists(join(absRoot, '.aperant', 'state.json')) : null
	const taskRecord = state?.active_tasks?.[taskId] ?? localState?.active_tasks?.[taskId] ?? null

	let config = {}
	const configPath = join(sharedRoot, '.aperant', 'config.json')
	config = readJsonIfExists(configPath) ?? {}
	if (
		sharedRoot !== absRoot &&
		Object.keys(config).length === 0 &&
		existsSync(join(absRoot, '.aperant', 'config.json'))
	) {
		config = readJsonIfExists(join(absRoot, '.aperant', 'config.json')) ?? {}
	}

	if (taskRecord?.track && !KNOWN_TRACKS.has(taskRecord.track)) {
		process.stderr.write(
			`[apt:gate] WARNING: unrecognized track "${taskRecord.track}" — defaulting to full-pipeline. ` +
				`Expected one of: ${[...KNOWN_TRACKS].join(', ')}.\n`,
		)
	}

	const profile =
		taskRecord?.track === 'QUICK' || taskRecord?.track === 'DEBUG' ? 'lite' : 'full-pipeline'

	return {
		projectRoot: absRoot,
		sharedRoot,
		taskId,
		taskDir,
		taskRecord,
		config,
		profile,
	}
}
