import { isValidManageTable, managePkMap } from "./pk-map.js";
import { manageFkDeps } from "./fk-map.js";
import { sql } from "drizzle-orm";

//#region src/dolt/resolve-conflicts.ts
var ResolutionValidationError = class extends Error {
	constructor(message) {
		super(message);
		this.name = "ResolutionValidationError";
	}
};
function toSqlLiteral(val) {
	if (val === null || val === void 0) return "NULL";
	if (typeof val === "object") return `'${JSON.stringify(val).replace(/'/g, "''")}'`;
	return `'${String(val).replace(/'/g, "''")}'`;
}
function classifyOperation(conflictRow, rowDefaultPick, hasColumnOverrides) {
	const ourDiffType = conflictRow.our_diff_type;
	const theirDiffType = conflictRow.their_diff_type;
	if (rowDefaultPick === "theirs" && !hasColumnOverrides) {
		if (theirDiffType === "removed") return "delete";
		if (ourDiffType === "removed") return "insert";
		return "update";
	}
	if (theirDiffType === "removed" || ourDiffType === "removed") {
		if (rowDefaultPick === "ours") return "skip";
		if (theirDiffType === "removed") return "delete";
		if (ourDiffType === "removed") return "insert";
	}
	return "update";
}
function computeTableInsertOrder(fkDeps) {
	const order = /* @__PURE__ */ new Map();
	const visited = /* @__PURE__ */ new Set();
	let counter = 0;
	function visit(table) {
		if (visited.has(table)) return;
		visited.add(table);
		for (const dep of fkDeps[table] ?? []) visit(dep);
		order.set(table, counter++);
	}
	for (const table of Object.keys(fkDeps)) visit(table);
	return order;
}
const PHASE_ORDER = {
	delete: 0,
	update: 1,
	insert: 2,
	skip: 3
};
const tableOrder = computeTableInsertOrder(manageFkDeps);
function sortByFkDependencyOrder(classified) {
	return [...classified].sort((a, b) => {
		const phaseA = PHASE_ORDER[a.operation];
		const phaseB = PHASE_ORDER[b.operation];
		if (phaseA !== phaseB) return phaseA - phaseB;
		const orderA = tableOrder.get(a.resolution.table) ?? 0;
		const orderB = tableOrder.get(b.resolution.table) ?? 0;
		if (a.operation === "delete") return orderB - orderA;
		if (a.operation === "insert") return orderA - orderB;
		return 0;
	});
}
const applyResolutions = (db) => async (resolutions) => {
	const affectedTables = /* @__PURE__ */ new Set();
	const classified = [];
	for (const resolution of resolutions) {
		if (!isValidManageTable(resolution.table)) throw new ResolutionValidationError(`Invalid table name: ${resolution.table}`);
		affectedTables.add(resolution.table);
		const pkColumns = managePkMap[resolution.table];
		if (!pkColumns) throw new ResolutionValidationError(`No PK columns found for table: ${resolution.table}`);
		const hasColumnOverrides = resolution.columns && Object.keys(resolution.columns).length > 0;
		if (resolution.rowDefaultPick === "ours" && !hasColumnOverrides) {
			classified.push({
				resolution,
				conflictRow: {},
				pkColumns,
				operation: "skip"
			});
			continue;
		}
		const conflictRow = await readConflictRow(db, resolution.table, resolution.primaryKey, pkColumns);
		if (!conflictRow) throw new ResolutionValidationError(`No conflict found for table ${resolution.table} with PK ${JSON.stringify(resolution.primaryKey)}`);
		if (hasColumnOverrides) {
			const ourDiffType = conflictRow.our_diff_type;
			const theirDiffType = conflictRow.their_diff_type;
			if (Object.values(resolution.columns ?? {}).some((pick) => pick !== resolution.rowDefaultPick) && (ourDiffType === "removed" || theirDiffType === "removed")) {
				const removedSide = ourDiffType === "removed" ? "ours" : "theirs";
				throw new ResolutionValidationError(`Cannot apply column overrides for table ${resolution.table} (PK ${JSON.stringify(resolution.primaryKey)}): ${removedSide} side deleted the row`);
			}
		}
		const operation = classifyOperation(conflictRow, resolution.rowDefaultPick, hasColumnOverrides ?? false);
		classified.push({
			resolution,
			conflictRow,
			pkColumns,
			operation
		});
	}
	const sorted = sortByFkDependencyOrder(classified);
	for (const { resolution, conflictRow, pkColumns, operation } of sorted) {
		if (operation === "skip") continue;
		const pkWhere = buildPkWhere(pkColumns, resolution.primaryKey);
		switch (operation) {
			case "delete":
				await db.execute(sql.raw(`DELETE FROM "${resolution.table}" WHERE ${pkWhere}`));
				break;
			case "insert": {
				const columns = getColumnNames(conflictRow, pkColumns);
				const allCols = [...pkColumns, ...columns];
				const values = allCols.map((col) => {
					return toSqlLiteral(pkColumns.includes(col) ? resolution.primaryKey[col] : conflictRow[`their_${col}`]);
				});
				await db.execute(sql.raw(`INSERT INTO "${resolution.table}" (${allCols.map((c) => `"${c}"`).join(", ")}) VALUES (${values.join(", ")})`));
				break;
			}
			case "update": {
				const setClauses = getColumnNames(conflictRow, pkColumns).map((col) => {
					return `"${col}" = ${toSqlLiteral(conflictRow[`${(resolution.columns?.[col] ?? resolution.rowDefaultPick) === "theirs" ? "their_" : "our_"}${col}`])}`;
				});
				await db.execute(sql.raw(`UPDATE "${resolution.table}" SET ${setClauses.join(", ")} WHERE ${pkWhere}`));
				break;
			}
		}
	}
	for (const table of affectedTables) await db.execute(sql.raw(`SELECT DOLT_CONFLICTS_RESOLVE('--ours', '${table}')`));
};
async function readConflictRow(db, table, primaryKey, pkColumns) {
	const whereClause = pkColumns.map((col) => {
		const val = primaryKey[col];
		if (val === void 0) throw new ResolutionValidationError(`Missing PK column ${col} for table ${table}`);
		const escapedVal = val.replace(/'/g, "''");
		return `(base_${col} = '${escapedVal}' OR (base_${col} IS NULL AND (our_${col} = '${escapedVal}' OR their_${col} = '${escapedVal}')))`;
	}).join(" AND ");
	return (await db.execute(sql.raw(`SELECT * FROM dolt_conflicts_${table} WHERE ${whereClause} LIMIT 1`))).rows[0] ?? null;
}
function buildPkWhere(pkColumns, primaryKey) {
	return pkColumns.map((col) => {
		return `"${col}" = '${primaryKey[col]?.replace(/'/g, "''")}'`;
	}).join(" AND ");
}
function getColumnNames(conflictRow, pkColumns) {
	const theirPrefix = "their_";
	const skipColumns = new Set([
		"our_diff_type",
		"their_diff_type",
		"base_diff_type",
		...pkColumns.map((c) => `base_${c}`),
		...pkColumns.map((c) => `our_${c}`),
		...pkColumns.map((c) => `their_${c}`)
	]);
	const columns = [];
	for (const key of Object.keys(conflictRow)) if (key.startsWith(theirPrefix) && !skipColumns.has(key)) columns.push(key.slice(6));
	return columns;
}

//#endregion
export { ResolutionValidationError, applyResolutions, computeTableInsertOrder, sortByFkDependencyOrder };