import { clearParseCache } from "../files.js";
import { renderPlanFromDb, renderReplanFromDb } from "../markdown-renderer.js";
import {
	hasCompleteAdversarialReview,
	hasStructuredPlanningMeeting,
} from "../plan-quality.js";
import {
	deleteTask,
	getSlice,
	getSliceTasks,
	getTask,
	insertReplanHistory,
	insertTask,
	transaction,
	upsertSlicePlanning,
	upsertTaskPlanning,
} from "../sf-db.js";
import { invalidateStateCache } from "../state.js";
import { isClosedStatus } from "../status-guards.js";
import {
	isNonEmptyString,
	normalizePlanningText,
	normalizePlanningTextArray,
} from "../validation.js";
import { logWarning } from "../workflow-logger.js";
import { runPostMutationHook } from "./tool-helpers.js";

function validateParams(params) {
	if (!isNonEmptyString(params?.milestoneId))
		throw new Error("milestoneId is required");
	if (!isNonEmptyString(params?.sliceId))
		throw new Error("sliceId is required");
	if (!isNonEmptyString(params?.blockerTaskId))
		throw new Error("blockerTaskId is required");
	if (!isNonEmptyString(params?.blockerDescription))
		throw new Error("blockerDescription is required");
	if (!isNonEmptyString(params?.whatChanged))
		throw new Error("whatChanged is required");
	if (!Array.isArray(params.updatedTasks)) {
		throw new Error("updatedTasks must be an array");
	}
	if (!Array.isArray(params.removedTaskIds)) {
		throw new Error("removedTaskIds must be an array");
	}
	// Validate each updated task
	for (let i = 0; i < params.updatedTasks.length; i++) {
		const t = params.updatedTasks[i];
		if (!t || typeof t !== "object")
			throw new Error(`updatedTasks[${i}] must be an object`);
		if (!isNonEmptyString(t.taskId))
			throw new Error(`updatedTasks[${i}].taskId is required`);
		if (!isNonEmptyString(t.title))
			throw new Error(`updatedTasks[${i}].title is required`);
	}
	return {
		...params,
		milestoneId: normalizePlanningText(params.milestoneId, "milestoneId"),
		sliceId: normalizePlanningText(params.sliceId, "sliceId"),
		blockerTaskId: normalizePlanningText(params.blockerTaskId, "blockerTaskId"),
		blockerDescription: normalizePlanningText(
			params.blockerDescription,
			"blockerDescription",
		),
		whatChanged: normalizePlanningText(params.whatChanged, "whatChanged"),
		goal:
			params.goal === undefined
				? undefined
				: normalizePlanningText(params.goal, "goal"),
		successCriteria:
			params.successCriteria === undefined
				? undefined
				: normalizePlanningText(params.successCriteria, "successCriteria"),
		proofLevel:
			params.proofLevel === undefined
				? undefined
				: normalizePlanningText(params.proofLevel, "proofLevel"),
		integrationClosure:
			params.integrationClosure === undefined
				? undefined
				: normalizePlanningText(
						params.integrationClosure,
						"integrationClosure",
					),
		observabilityImpact:
			params.observabilityImpact === undefined
				? undefined
				: normalizePlanningText(
						params.observabilityImpact,
						"observabilityImpact",
					),
		removedTaskIds: normalizePlanningTextArray(
			params.removedTaskIds,
			"removedTaskIds",
		),
		updatedTasks: params.updatedTasks.map((task, index) => ({
			...task,
			taskId: normalizePlanningText(
				task.taskId,
				`updatedTasks[${index}].taskId`,
			),
			title: normalizePlanningText(task.title, `updatedTasks[${index}].title`),
			description:
				task.description === undefined
					? ""
					: normalizePlanningText(
							task.description,
							`updatedTasks[${index}].description`,
						),
			estimate:
				task.estimate === undefined
					? ""
					: normalizePlanningText(
							task.estimate,
							`updatedTasks[${index}].estimate`,
						),
			files: task.files
				? normalizePlanningTextArray(task.files, `updatedTasks[${index}].files`)
				: [],
			verify:
				task.verify === undefined
					? ""
					: normalizePlanningText(task.verify, `updatedTasks[${index}].verify`),
			inputs: task.inputs
				? normalizePlanningTextArray(
						task.inputs,
						`updatedTasks[${index}].inputs`,
					)
				: [],
			expectedOutput: task.expectedOutput
				? normalizePlanningTextArray(
						task.expectedOutput,
						`updatedTasks[${index}].expectedOutput`,
					)
				: [],
		})),
	};
}
export async function handleReplanSlice(rawParams, basePath) {
	// ── Validate ──────────────────────────────────────────────────────
	let params;
	try {
		params = validateParams(rawParams);
	} catch (err) {
		return { error: `validation failed: ${err.message}` };
	}
	// ── Guards + DB writes inside a single transaction (prevents TOCTOU) ───
	// Guards must be inside the transaction so the state they check cannot
	// change between the read and the write (#2723).
	let guardError = null;
	let existingTaskIds = new Set();
	try {
		transaction(() => {
			// Verify parent slice exists and is not closed
			const parentSlice = getSlice(params.milestoneId, params.sliceId);
			if (!parentSlice) {
				guardError = `missing parent slice: ${params.milestoneId}/${params.sliceId}`;
				return;
			}
			if (isClosedStatus(parentSlice.status)) {
				guardError = `cannot replan a closed slice: ${params.sliceId} (status: ${parentSlice.status})`;
				return;
			}
			// Verify blocker task exists and is complete
			const blockerTask = getTask(
				params.milestoneId,
				params.sliceId,
				params.blockerTaskId,
			);
			if (!blockerTask) {
				guardError = `blockerTaskId not found: ${params.milestoneId}/${params.sliceId}/${params.blockerTaskId}`;
				return;
			}
			if (!isClosedStatus(blockerTask.status)) {
				guardError = `blockerTaskId ${params.blockerTaskId} is not complete (status: ${blockerTask.status}) — the blocker task must be finished before a replan is triggered`;
				return;
			}
			// Structural enforcement — reject modifications/removal of completed tasks
			const existingTasks = getSliceTasks(params.milestoneId, params.sliceId);
			const completedTaskIds = new Set();
			for (const task of existingTasks) {
				if (isClosedStatus(task.status)) {
					completedTaskIds.add(task.id);
				}
			}
			for (const updatedTask of params.updatedTasks) {
				if (completedTaskIds.has(updatedTask.taskId)) {
					guardError = `cannot modify completed task ${updatedTask.taskId}`;
					return;
				}
			}
			for (const removedId of params.removedTaskIds) {
				if (completedTaskIds.has(removedId)) {
					guardError = `cannot remove completed task ${removedId}`;
					return;
				}
			}
			existingTaskIds = new Set(existingTasks.map((t) => t.id));
			// Record replan history
			insertReplanHistory({
				milestoneId: params.milestoneId,
				sliceId: params.sliceId,
				taskId: params.blockerTaskId,
				summary: params.whatChanged,
			});
			upsertSlicePlanning(params.milestoneId, params.sliceId, {
				goal: params.goal,
				successCriteria: params.successCriteria,
				proofLevel: params.proofLevel,
				integrationClosure: params.integrationClosure,
				observabilityImpact: params.observabilityImpact,
				adversarialReview: hasCompleteAdversarialReview(
					params.adversarialReview,
				)
					? {
							partner: params.adversarialReview.partner.trim(),
							combatant: params.adversarialReview.combatant.trim(),
							architect: params.adversarialReview.architect.trim(),
						}
					: undefined,
				planningMeeting: hasStructuredPlanningMeeting(params.planningMeeting)
					? {
							trigger: params.planningMeeting.trigger.trim(),
							pm: params.planningMeeting.pm.trim(),
							researcher: params.planningMeeting.researcher.trim(),
							partner: params.planningMeeting.partner.trim(),
							combatant: params.planningMeeting.combatant.trim(),
							architect: params.planningMeeting.architect.trim(),
							moderator: params.planningMeeting.moderator.trim(),
							recommendedRoute: params.planningMeeting.recommendedRoute,
							confidenceSummary:
								params.planningMeeting.confidenceSummary.trim(),
						}
					: undefined,
			});
			// Apply task updates (upsert existing, insert new)
			for (const updatedTask of params.updatedTasks) {
				if (existingTaskIds.has(updatedTask.taskId)) {
					// Update existing task's planning fields
					upsertTaskPlanning(
						params.milestoneId,
						params.sliceId,
						updatedTask.taskId,
						{
							title: updatedTask.title,
							description: updatedTask.description || "",
							estimate: updatedTask.estimate || "",
							files: updatedTask.files || [],
							verify: updatedTask.verify || "",
							inputs: updatedTask.inputs || [],
							expectedOutput: updatedTask.expectedOutput || [],
							fullPlanMd: updatedTask.fullPlanMd,
						},
					);
				} else {
					// Insert new task then set planning fields
					insertTask({
						id: updatedTask.taskId,
						sliceId: params.sliceId,
						milestoneId: params.milestoneId,
						title: updatedTask.title,
						status: "pending",
					});
					upsertTaskPlanning(
						params.milestoneId,
						params.sliceId,
						updatedTask.taskId,
						{
							title: updatedTask.title,
							description: updatedTask.description || "",
							estimate: updatedTask.estimate || "",
							files: updatedTask.files || [],
							verify: updatedTask.verify || "",
							inputs: updatedTask.inputs || [],
							expectedOutput: updatedTask.expectedOutput || [],
							fullPlanMd: updatedTask.fullPlanMd,
						},
					);
				}
			}
			// Delete removed tasks
			for (const removedId of params.removedTaskIds) {
				deleteTask(params.milestoneId, params.sliceId, removedId);
			}
		});
	} catch (err) {
		return { error: `db write failed: ${err.message}` };
	}
	if (guardError) {
		return { error: guardError };
	}
	// ── Render artifacts ──────────────────────────────────────────────
	try {
		const renderResult = await renderPlanFromDb(
			basePath,
			params.milestoneId,
			params.sliceId,
		);
		const replanResult = await renderReplanFromDb(
			basePath,
			params.milestoneId,
			params.sliceId,
			{
				blockerTaskId: params.blockerTaskId,
				blockerDescription: params.blockerDescription,
				whatChanged: params.whatChanged,
			},
		);
		// ── Invalidate caches ─────────────────────────────────────────
		invalidateStateCache();
		clearParseCache();
		// ── Post-mutation hook: projections, manifest, event log ─────
		await runPostMutationHook(
			basePath,
			params.milestoneId,
			{
				cmd: "replan-slice",
				params: {
					milestoneId: params.milestoneId,
					sliceId: params.sliceId,
					blockerTaskId: params.blockerTaskId,
				},
				ts: new Date().toISOString(),
				actor: "agent",
				actor_name: params.actorName,
				trigger_reason: params.triggerReason,
			},
			{ caller: "replan-slice" },
		);
		return {
			milestoneId: params.milestoneId,
			sliceId: params.sliceId,
			replanPath: replanResult.replanPath,
			planPath: renderResult.planPath,
		};
	} catch (err) {
		return { error: `render failed: ${err.message}` };
	}
}
