/**
 * auto/unit-lanes.js — Planning/Execute lane classification for R090.
 *
 * Splits autonomous units into two conceptual lanes so the planner can
 * work on milestone N while the executor works on milestone N-1.
 *
 * Exports:
 *   LANE_PLANNING       — string constant "planning"
 *   LANE_EXECUTE        — string constant "execute"
 *   classifyUnitLane()  — pure function: unitType → lane
 *   areLanesEnabled()   — reads SF_LANES env; default true
 *   getPlanningLaneCapacity() — reads SF_LANES_PLANNING_N; default 1
 *   getExecuteLaneCapacity()  — reads SF_LANES_EXECUTE_N; default 1
 *   selectLaneAwareUnits()    — pick up to cap units per lane from a candidate list
 */

export const LANE_PLANNING = "planning";
export const LANE_EXECUTE = "execute";

/**
 * Explicit membership map for the well-known planning-lane unit types.
 * Anything not listed here is classified by fallback rules, then defaults
 * to the execute lane.
 */
const PLANNING_TYPES = new Set([
	"plan-milestone",
	"plan-slice",
	"research-slice",
	"assess-slice",
	"synthesize-slice",
	"reassess-roadmap",
	"validate-milestone",
]);

/**
 * Classify a unit type into its lane.
 *
 * Planning lane:
 *   - Any type in PLANNING_TYPES (explicit set)
 *   - Any type ending in "-research"
 *   - Any type starting with "plan-" or "research-"
 *
 * Execute lane (default):
 *   - execute-task, complete-task, complete-slice, complete-milestone
 *   - repair-* (any type starting with "repair-")
 *   - Anything else not matched above
 *
 * @param {string} unitType
 * @returns {"planning" | "execute"}
 */
export function classifyUnitLane(unitType) {
	if (typeof unitType !== "string" || unitType.length === 0) {
		return LANE_EXECUTE;
	}

	// Explicit set check (fast path for common types)
	if (PLANNING_TYPES.has(unitType)) {
		return LANE_PLANNING;
	}

	// Suffix rule: ends with "-research"
	if (unitType.endsWith("-research")) {
		return LANE_PLANNING;
	}

	// Prefix rules
	if (unitType.startsWith("plan-") || unitType.startsWith("research-")) {
		return LANE_PLANNING;
	}

	// Default: execute lane
	return LANE_EXECUTE;
}

/**
 * Return true when lane splitting is active.
 *
 * Default: ON (lanes enabled).
 * Set SF_LANES=0 or SF_LANES=false to disable (back-compat one-at-a-time mode).
 *
 * @param {NodeJS.ProcessEnv} [env]
 * @returns {boolean}
 */
export function areLanesEnabled(env = process.env) {
	const val = env.SF_LANES;
	if (val === "0" || val === "false") return false;
	return true;
}

/**
 * How many planning-lane units may be dispatched per tick.
 *
 * Default: 1. Override with SF_LANES_PLANNING_N.
 *
 * @param {NodeJS.ProcessEnv} [env]
 * @returns {number}
 */
export function getPlanningLaneCapacity(env = process.env) {
	const raw = env.SF_LANES_PLANNING_N;
	if (raw !== undefined && raw !== "") {
		const n = Number.parseInt(raw, 10);
		if (Number.isFinite(n) && n > 0) return n;
	}
	return 1;
}

/**
 * How many execute-lane units may be dispatched per tick.
 *
 * Default: 1. Override with SF_LANES_EXECUTE_N.
 *
 * @param {NodeJS.ProcessEnv} [env]
 * @returns {number}
 */
export function getExecuteLaneCapacity(env = process.env) {
	const raw = env.SF_LANES_EXECUTE_N;
	if (raw !== undefined && raw !== "") {
		const n = Number.parseInt(raw, 10);
		if (Number.isFinite(n) && n > 0) return n;
	}
	return 1;
}

/**
 * Walk a list of candidate units and select up to planningCap planning-lane
 * units and up to executeCap execute-lane units.
 *
 * The walk continues past filled slots: a saturated execute slot does NOT
 * stop the scan — subsequent planning candidates can still fill the planning
 * slot (and vice-versa).
 *
 * @param {Array<{unitType: string, unitId?: string, [k: string]: unknown}>} candidates
 *   Ordered list of ready units (front = highest priority).
 * @param {number} planningCap  Maximum planning-lane dispatches per tick.
 * @param {number} executeCap   Maximum execute-lane dispatches per tick.
 * @returns {Array<{unitType: string, unitId?: string, [k: string]: unknown}>}
 *   The selected subset, in the order they were encountered.
 */
export function selectLaneAwareUnits(candidates, planningCap, executeCap) {
	let planningRemaining = planningCap;
	let executeRemaining = executeCap;
	const selected = [];

	for (const candidate of candidates) {
		if (planningRemaining <= 0 && executeRemaining <= 0) break;

		const lane = classifyUnitLane(candidate.unitType);
		if (lane === LANE_PLANNING && planningRemaining > 0) {
			selected.push(candidate);
			planningRemaining--;
		} else if (lane === LANE_EXECUTE && executeRemaining > 0) {
			selected.push(candidate);
			executeRemaining--;
		}
		// else: this lane is saturated — keep scanning for the other lane
	}

	return selected;
}
