/**
 * Regression tests for the per-call `model` override on the subagent tool.
 *
 * Validates:
 * - `model` is declared on SubagentParams, TaskItem, and ChainItem schemas.
 * - `buildSubagentProcessArgs` uses the override when provided, and falls
 *   back to `agent.model` otherwise.
 * - `runSingleAgent` and `runSingleAgentInCmuxSplit` accept a trailing
 *   `modelOverride` parameter.
 * - The three dispatch modes (single, parallel, chain) pass the override
 *   through to the runner functions.
 *
 * These tests are structural (source-grep) because the runner spawns a
 * child process that is hard to fake in a unit test; the existing
 * subagent-model-dispatch.test.ts uses the same pattern.
 */

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { test } from 'vitest';
import { fileURLToPath } from "node:url";

const __dirname = dirname(fileURLToPath(import.meta.url));
const subagentSrc = readFileSync(join(__dirname, "..", "index.ts"), "utf-8");

test("SubagentParams declares optional model override field", () => {
	const paramsStart = subagentSrc.indexOf(
		"const SubagentParams = Type.Object({",
	);
	const paramsEnd = subagentSrc.indexOf("});", paramsStart);
	const paramsBlock = subagentSrc.slice(paramsStart, paramsEnd);
	assert.match(paramsBlock, /model:\s*Type\.Optional\(\s*Type\.String/);
});

test("TaskItem declares optional model override field", () => {
	const itemStart = subagentSrc.indexOf("const TaskItem = Type.Object({");
	const itemEnd = subagentSrc.indexOf("});", itemStart);
	const itemBlock = subagentSrc.slice(itemStart, itemEnd);
	assert.match(itemBlock, /model:\s*Type\.Optional\(\s*Type\.String/);
});

test("ChainItem declares optional model override field", () => {
	const itemStart = subagentSrc.indexOf("const ChainItem = Type.Object({");
	const itemEnd = subagentSrc.indexOf("});", itemStart);
	const itemBlock = subagentSrc.slice(itemStart, itemEnd);
	assert.match(itemBlock, /model:\s*Type\.Optional\(\s*Type\.String/);
});

test("buildSubagentProcessArgs prefers modelOverride over agent.model", () => {
	const fnStart = subagentSrc.indexOf("function buildSubagentProcessArgs(");
	const fnEnd = subagentSrc.indexOf(
		"}",
		subagentSrc.indexOf("return args;", fnStart),
	);
	const fn = subagentSrc.slice(fnStart, fnEnd);

	assert.match(
		fn,
		/modelOverride\?\s*:\s*string/,
		"signature should accept modelOverride",
	);
	assert.match(
		fn,
		/const\s+modelToUse\s*=\s*modelOverride\s*\?\?\s*agent\.model/,
		"should coalesce override first, then agent.model",
	);
});

test("runSingleAgent accepts trailing modelOverride parameter", () => {
	const fnStart = subagentSrc.indexOf("async function runSingleAgent(");
	const fnEnd = subagentSrc.indexOf("): Promise<SingleResult>", fnStart);
	const signature = subagentSrc.slice(fnStart, fnEnd);
	assert.match(signature, /modelOverride\?\s*:\s*string/);
});

test("runSingleAgentInCmuxSplit accepts trailing modelOverride parameter", () => {
	const fnStart = subagentSrc.indexOf(
		"async function runSingleAgentInCmuxSplit(",
	);
	const fnEnd = subagentSrc.indexOf("): Promise<SingleResult>", fnStart);
	const signature = subagentSrc.slice(fnStart, fnEnd);
	assert.match(signature, /modelOverride\?\s*:\s*string/);
});

test("single-mode dispatch forwards params.model to the runner", () => {
	// The single-mode runSingleAgent call lives after `if (params.agent && params.task)`.
	const singleStart = subagentSrc.indexOf("if (params.agent && params.task) {");
	const singleEnd = subagentSrc.indexOf("let outputText", singleStart);
	const block = subagentSrc.slice(singleStart, singleEnd);
	// Both the cmux and non-cmux branches should pass params.model as the trailing arg.
	const occurrences = (block.match(/params\.model,/g) ?? []).length;
	assert.ok(
		occurrences >= 2,
		`expected params.model to be passed in both cmux+non-cmux single paths; found ${occurrences}`,
	);
});

test("parallel-mode dispatch coalesces per-task model with params.model fallback", () => {
	const parallelStart = subagentSrc.indexOf(
		"if (params.tasks && params.tasks.length > 0) {",
	);
	// End at the "if (params.agent && params.task)" block — everything before that
	// is the parallel dispatch path.
	const parallelEnd = subagentSrc.indexOf(
		"if (params.agent && params.task) {",
		parallelStart,
	);
	const block = subagentSrc.slice(parallelStart, parallelEnd);
	assert.match(
		block,
		/const\s+taskModelOverride\s*=\s*t\.model\s*\?\?\s*params\.model/,
		"per-task override must coalesce with batch-level params.model",
	);
	const occurrences = (block.match(/taskModelOverride/g) ?? []).length;
	assert.ok(
		occurrences >= 3,
		`expected taskModelOverride to be declared once and passed into both cmux+non-cmux branches; found ${occurrences}`,
	);
});

test("chain-mode dispatch coalesces per-step model with params.model fallback", () => {
	const chainStart = subagentSrc.indexOf(
		"if (params.chain && params.chain.length > 0) {",
	);
	const chainEnd = subagentSrc.indexOf("return {", chainStart);
	const block = subagentSrc.slice(chainStart, chainEnd);
	assert.match(
		block,
		/step\.model\s*\?\?\s*params\.model/,
		"chain step override must coalesce with batch-level params.model",
	);
});
