/**
 * Task Frontmatter - schema-backed task metadata
 *
 * Purpose: add structured fields to task records for risk assessment,
 * mutation scope declaration, verification requirements, plan approval, and
 * task lifecycle status while keeping scheduler status as a separate view field.
 *
 * Consumer: plan-v2 task creation, UOK gate runner, parallel orchestrator,
 * sf-db row mapping, and task state machine.
 */

export const RISK_LEVELS = ["none", "low", "medium", "high", "critical"];

export const MUTATION_SCOPES = [
	"none",
	"docs-only",
	"config",
	"test-only",
	"isolated",
	"bounded",
	"cross-cutting",
	"systemic",
];

export const VERIFICATION_TYPES = [
	"none",
	"self-check",
	"review",
	"test",
	"integration",
	"manual-qa",
];

export const PLAN_APPROVAL_STATES = [
	"not-required",
	"pending",
	"approved",
	"rejected",
	"auto-approved",
];

export const TASK_STATUSES = [
	"todo",
	"running",
	"verifying",
	"reviewing",
	"done",
	"blocked",
	"paused",
	"failed",
	"cancelled",
	"retrying",
];

export const SCHEDULER_STATUSES = [
	"queued",
	"due",
	"claimed",
	"dispatched",
	"consumed",
	"expired",
];

const TASK_STATUS_ALIASES = {
	complete: "done",
	completed: "done",
	in_progress: "running",
	"manual-attention": "reviewing",
	manual_attention: "reviewing",
	pending: "todo",
	review: "reviewing",
};

const SCHEDULER_STATUS_ALIASES = {
	completed: "consumed",
	done: "consumed",
	pending: "queued",
};

export const DEFAULT_TASK_FRONTMATTER = {
	risk: "low",
	mutationScope: "isolated",
	verification: "self-check",
	planApproval: "not-required",
	taskStatus: "todo",
	schedulerStatus: "queued",
	estimatedEffort: null,
	keyFiles: [],
	dependencies: [],
	blocksParallel: false,
	requiresUserInput: false,
	autoRetry: true,
	maxRetries: 2,
	frontmatterVersion: 1,
};

export function normalizeTaskStatus(value) {
	if (typeof value !== "string" || value.trim() === "") return "todo";
	const status = value.trim().toLowerCase();
	if (TASK_STATUSES.includes(status)) return status;
	return TASK_STATUS_ALIASES[status] ?? null;
}

export function normalizeSchedulerStatus(value) {
	if (typeof value !== "string" || value.trim() === "") return "queued";
	const status = value.trim().toLowerCase();
	if (SCHEDULER_STATUSES.includes(status)) return status;
	return SCHEDULER_STATUS_ALIASES[status] ?? null;
}

function normalizeArray(value) {
	if (Array.isArray(value)) return value.filter((v) => typeof v === "string");
	if (typeof value !== "string" || value.trim() === "") return [];
	try {
		const parsed = JSON.parse(value);
		if (Array.isArray(parsed))
			return parsed.filter((v) => typeof v === "string");
		return [];
	} catch {
		return value
			.split(",")
			.map((v) => v.trim())
			.filter(Boolean);
	}
}

function normalizeBoolean(value) {
	if (value === true || value === 1) return true;
	if (value === false || value === 0 || value == null) return false;
	if (typeof value === "string") {
		const normalized = value.trim().toLowerCase();
		if (["1", "true", "yes", "y"].includes(normalized)) return true;
		if (["0", "false", "no", "n", ""].includes(normalized)) return false;
	}
	return Boolean(value);
}

function validateChoice(field, value, allowed, normalized, errors) {
	if (value === undefined || value === null || value === "") return;
	if (allowed.includes(value)) {
		normalized[field] = value;
		return;
	}
	errors.push(
		`Invalid ${field} "${value}". Must be one of: ${allowed.join(", ")}`,
	);
}

export function validateTaskFrontmatter(frontmatter = {}) {
	const errors = [];
	const normalized = {
		...DEFAULT_TASK_FRONTMATTER,
		keyFiles: [],
		dependencies: [],
	};

	validateChoice("risk", frontmatter.risk, RISK_LEVELS, normalized, errors);
	validateChoice(
		"mutationScope",
		frontmatter.mutationScope,
		MUTATION_SCOPES,
		normalized,
		errors,
	);
	validateChoice(
		"verification",
		frontmatter.verification,
		VERIFICATION_TYPES,
		normalized,
		errors,
	);
	validateChoice(
		"planApproval",
		frontmatter.planApproval,
		PLAN_APPROVAL_STATES,
		normalized,
		errors,
	);

	if (frontmatter.taskStatus !== undefined) {
		const status = normalizeTaskStatus(frontmatter.taskStatus);
		if (status) {
			normalized.taskStatus = status;
		} else {
			errors.push(
				`Invalid taskStatus "${frontmatter.taskStatus}". Must be one of: ${TASK_STATUSES.join(", ")}`,
			);
		}
	}

	if (frontmatter.schedulerStatus !== undefined) {
		const status = normalizeSchedulerStatus(frontmatter.schedulerStatus);
		if (status) {
			normalized.schedulerStatus = status;
		} else {
			errors.push(
				`Invalid schedulerStatus "${frontmatter.schedulerStatus}". Must be one of: ${SCHEDULER_STATUSES.join(", ")}`,
			);
		}
	}

	if (frontmatter.estimatedEffort !== undefined) {
		const effort = Number(frontmatter.estimatedEffort);
		if (!Number.isNaN(effort) && effort >= 0) {
			normalized.estimatedEffort = effort;
		} else if (frontmatter.estimatedEffort !== null) {
			errors.push(
				`Invalid estimatedEffort "${frontmatter.estimatedEffort}". Must be a non-negative number or null.`,
			);
		}
	}

	if (frontmatter.keyFiles !== undefined) {
		normalized.keyFiles = normalizeArray(frontmatter.keyFiles);
	}
	if (frontmatter.dependencies !== undefined) {
		normalized.dependencies = normalizeArray(frontmatter.dependencies);
	}

	for (const field of ["blocksParallel", "requiresUserInput", "autoRetry"]) {
		if (frontmatter[field] !== undefined) {
			normalized[field] = normalizeBoolean(frontmatter[field]);
		}
	}

	if (frontmatter.maxRetries !== undefined) {
		const retries = Number(frontmatter.maxRetries);
		if (Number.isInteger(retries) && retries >= 0 && retries <= 10) {
			normalized.maxRetries = retries;
		} else {
			errors.push(
				`Invalid maxRetries "${frontmatter.maxRetries}". Must be an integer 0-10.`,
			);
		}
	}

	if (frontmatter.frontmatterVersion !== undefined) {
		const ver = Number(frontmatter.frontmatterVersion);
		if (Number.isInteger(ver) && ver >= 1) {
			normalized.frontmatterVersion = ver;
		} else {
			errors.push(
				`Invalid frontmatterVersion "${frontmatter.frontmatterVersion}". Must be a positive integer.`,
			);
		}
	}

	return {
		valid: errors.length === 0,
		errors,
		normalized,
	};
}

export function taskFrontmatterFromRecord(task = {}, overrides = {}) {
	const rawFrontmatter = {
		risk: task.risk,
		mutationScope: task.mutation_scope ?? task.mutationScope,
		verification:
			task.verification_type ?? task.verificationType ?? task.verification,
		planApproval: task.plan_approval ?? task.planApproval,
		taskStatus: task.task_status ?? task.taskStatus ?? task.status,
		schedulerStatus: task.scheduler_status ?? task.schedulerStatus,
		estimatedEffort: task.estimated_effort ?? task.estimatedEffort,
		keyFiles:
			task.frontmatter_key_files ??
			task.frontmatterKeyFiles ??
			task.files ??
			task.key_files ??
			task.keyFiles ??
			[],
		dependencies:
			task.dependencies ??
			task.depends_on ??
			task.dependsOn ??
			task.depends ??
			[],
		blocksParallel: task.blocks_parallel ?? task.blocksParallel,
		requiresUserInput: task.requires_user_input ?? task.requiresUserInput,
		autoRetry: task.auto_retry ?? task.autoRetry,
		maxRetries: task.max_retries ?? task.maxRetries,
		frontmatterVersion: task.frontmatter_version ?? task.frontmatterVersion,
		...overrides,
	};

	return validateTaskFrontmatter(rawFrontmatter);
}

export function buildTaskRecord(task = {}, overrides = {}) {
	const validation = taskFrontmatterFromRecord(task, overrides);
	return {
		...task,
		frontmatter: validation.normalized,
		frontmatterValid: validation.valid,
		frontmatterErrors: validation.errors,
	};
}

export function withTaskFrontmatter(task = {}, overrides = {}) {
	return buildTaskRecord(task, overrides);
}

export function canRunInParallel(taskA, taskB) {
	if (
		!taskA ||
		!taskB ||
		typeof taskA !== "object" ||
		typeof taskB !== "object"
	) {
		return { canParallel: false, reason: "Invalid task input" };
	}
	const fmA = taskA.frontmatter ?? buildTaskRecord(taskA).frontmatter;
	const fmB = taskB.frontmatter ?? buildTaskRecord(taskB).frontmatter;

	if (fmA.blocksParallel || fmB.blocksParallel) {
		return {
			canParallel: false,
			reason: "One or both tasks block parallel execution",
		};
	}

	if (fmA.mutationScope === "systemic" || fmB.mutationScope === "systemic") {
		return {
			canParallel: false,
			reason: "One or both tasks have systemic mutation scope",
		};
	}

	const highRisk = ["high", "critical"];
	if (highRisk.includes(fmA.risk) && highRisk.includes(fmB.risk)) {
		return { canParallel: false, reason: "Both tasks are high/critical risk" };
	}

	if (fmA.keyFiles.length > 0 && fmB.keyFiles.length > 0) {
		const filesB = new Set(fmB.keyFiles);
		const overlap = fmA.keyFiles.filter((file) => filesB.has(file));
		if (overlap.length > 0) {
			return {
				canParallel: false,
				reason: `File overlap: ${overlap.join(", ")}`,
			};
		}
	}

	return { canParallel: true };
}

export function canTasksRunInParallel(taskA, taskB) {
	return canRunInParallel(taskA, taskB);
}

export function computeTaskPriority(task) {
	const fm = task.frontmatter ?? buildTaskRecord(task).frontmatter;
	let score = 50;

	const riskScores = { none: 0, low: 5, medium: 15, high: 30, critical: 50 };
	score += riskScores[fm.risk] ?? 0;

	const scopeScores = {
		none: 0,
		"docs-only": 2,
		config: 5,
		"test-only": 3,
		isolated: 5,
		bounded: 10,
		"cross-cutting": 25,
		systemic: 40,
	};
	score += scopeScores[fm.mutationScope] ?? 0;

	if (fm.blocksParallel) score += 20;
	if (fm.requiresUserInput) score += 10;
	if (fm.planApproval === "pending") score += 10;

	return Math.min(100, score);
}

export function scoreTaskFrontmatterPriority(task) {
	return computeTaskPriority(task);
}
