import { getLogger } from "../utils/logger.js";
import { createApiError, getDatabaseErrorLogContext } from "../utils/error.js";
import { doltAddAndCommit } from "./commit.js";
import { managePkMap } from "./pk-map.js";
import { ResolutionValidationError, applyResolutions } from "./resolve-conflicts.js";
import { doltCheckout } from "./branch.js";
import { sql } from "drizzle-orm";

//#region src/dolt/merge.ts
const logger = getLogger("dolt-merge");
const TIMESTAMP_COLUMNS = new Set(["created_at", "updated_at"]);
var MergeConflictError = class extends Error {
	constructor(message, conflictCount, fromBranch, toBranch) {
		super(message);
		this.conflictCount = conflictCount;
		this.fromBranch = fromBranch;
		this.toBranch = toBranch;
		this.name = "MergeConflictError";
	}
};
function extractConflictCount(row) {
	if (typeof row.conflicts === "number" || typeof row.conflicts === "string") return Number(row.conflicts);
	const doltMerge$1 = row.dolt_merge;
	if (Array.isArray(doltMerge$1)) return Number(doltMerge$1[2] ?? 0);
	throw new Error(`Unexpected DOLT_MERGE result format: ${JSON.stringify(row)}`);
}
function isTimestampOnlyConflictRow(row, pkColumns) {
	if (row.our_diff_type !== "modified" || row.their_diff_type !== "modified") return false;
	const pkSet = new Set(pkColumns);
	for (const key of Object.keys(row)) {
		if (!key.startsWith("base_")) continue;
		const col = key.slice(5);
		if (col === "diff_type" || pkSet.has(col) || TIMESTAMP_COLUMNS.has(col)) continue;
		if (String(row[`base_${col}`] ?? "") !== String(row[`our_${col}`] ?? "") || String(row[`base_${col}`] ?? "") !== String(row[`their_${col}`] ?? "")) return false;
	}
	return true;
}
function buildTimestampAutoResolution(tableName, row, pkColumns) {
	const primaryKey = {};
	for (const col of pkColumns) primaryKey[col] = String(row[`base_${col}`] ?? row[`our_${col}`] ?? row[`their_${col}`]);
	return {
		table: tableName,
		primaryKey,
		rowDefaultPick: (row.our_updated_at ? new Date(String(row.our_updated_at)) : /* @__PURE__ */ new Date(0)) >= (row.their_updated_at ? new Date(String(row.their_updated_at)) : /* @__PURE__ */ new Date(0)) ? "ours" : "theirs"
	};
}
/**
* Merge a branch into the currently checked out branch.
*
* Runs inside an explicit transaction so that conflicts and
* constraint-violations are surfaced to the caller instead of being
* auto-rolled-back by Dolt's AUTOCOMMIT mode.
*
* If conflicts arise and `resolutions` are provided, they are applied
* and the merge is committed. If conflicts arise without resolutions
* (or with insufficient resolutions), the transaction is rolled back
* and a `MergeConflictError` is thrown.
*/
const doltMerge = (db) => async (params) => {
	logger.info({
		fromBranch: params.fromBranch,
		toBranch: params.toBranch
	}, "Merging branch");
	await doltCheckout(db)({ branch: params.toBranch });
	const toHead = (await db.execute(sql`SELECT HASHOF('HEAD') as hash`)).rows[0]?.hash;
	const args = [`'${params.fromBranch.replace(/'/g, "''")}'`];
	if (params.noFastForward) args.push("'--no-ff'");
	if (params.message) {
		const cleanedMessage = params.message.replace(/'/g, "''");
		args.push("'-m'", `'${cleanedMessage}'`);
	}
	const cleanedAuthor = params.author?.name?.replace(/'/g, "''");
	const cleanedEmail = params.author?.email?.replace(/'/g, "''");
	if (params.author) args.push("'--author'", `'${cleanedAuthor} <${cleanedEmail}>'`);
	await db.execute(sql.raw("START TRANSACTION"));
	let txFinalized = false;
	try {
		let result;
		try {
			result = await db.execute(sql.raw(`SELECT DOLT_MERGE(${args.join(", ")})`));
			logger.info({ result }, "DOLT_MERGE result");
		} catch (error) {
			logger.error({
				error,
				...getDatabaseErrorLogContext(error),
				fromBranch: params.fromBranch,
				toBranch: params.toBranch
			}, "Error merging branch");
			throw error;
		}
		const conflicts = extractConflictCount(result.rows[0] ?? {});
		if (Number.isFinite(conflicts) && conflicts > 0) {
			const userResolutions = params.resolutions ?? [];
			const autoResolutions = [];
			const conflictTables = await doltConflicts(db)();
			let manualConflicts = 0;
			for (const ct of conflictTables) {
				const tableConflicts = await doltTableConflicts(db)({ tableName: ct.table });
				const pkColumns = managePkMap[ct.table] ?? [];
				for (const row of tableConflicts) if (isTimestampOnlyConflictRow(row, pkColumns)) autoResolutions.push(buildTimestampAutoResolution(ct.table, row, pkColumns));
				else manualConflicts++;
			}
			if (manualConflicts > 0 && userResolutions.length < manualConflicts) throw new MergeConflictError(manualConflicts > 0 && userResolutions.length === 0 ? "Merge has conflicts but no resolutions were provided." : `Resolutions provided (${userResolutions.length}) do not cover all conflicts (${manualConflicts}). All conflicts must be resolved.`, manualConflicts, params.fromBranch, params.toBranch);
			const allResolutions = [...autoResolutions, ...userResolutions];
			try {
				await applyResolutions(db)(allResolutions);
			} catch (error) {
				if (error instanceof ResolutionValidationError) throw createApiError({
					code: "bad_request",
					message: `Invalid resolution: ${error.message}`
				});
				throw error;
			}
			await doltAddAndCommit(db)({
				message: params.message ? `${params.message} (with conflict resolution)` : `Merge ${params.fromBranch} into ${params.toBranch} (with conflict resolution)`,
				author: params.author
			});
			txFinalized = true;
			return {
				status: "success",
				from: params.fromBranch,
				to: params.toBranch,
				toHead,
				hasConflicts: true
			};
		}
		await db.execute(sql.raw("COMMIT"));
		txFinalized = true;
		return {
			status: "success",
			from: params.fromBranch,
			to: params.toBranch,
			toHead,
			hasConflicts: false
		};
	} finally {
		if (!txFinalized) try {
			await db.execute(sql.raw("ROLLBACK"));
		} catch (rollbackError) {
			logger.error({
				error: rollbackError,
				...getDatabaseErrorLogContext(rollbackError)
			}, "Failed to rollback transaction");
		}
	}
};
/**
* Abort a merge
*/
const doltAbortMerge = (db) => async () => {
	await db.execute(sql.raw(`SELECT DOLT_MERGE('--abort')`));
};
/**
* Get merge status
*/
const doltMergeStatus = (db) => async () => {
	const status = (await db.execute(sql`SELECT * FROM DOLT_MERGE_STATUS`)).rows[0];
	if (!status || !status.is_merging) return { isMerging: false };
	return {
		isMerging: status.is_merging,
		source: status.source,
		target: status.target,
		unmergedTables: status.unmerged_tables ? status.unmerged_tables.split(",") : []
	};
};
/**
* Get list of tables with conflicts
*/
const doltConflicts = (db) => async () => {
	return (await db.execute(sql`SELECT * FROM dolt_conflicts`)).rows;
};
/**
* Get detailed conflicts for a specific table
*/
const doltTableConflicts = (db) => async (params) => {
	return (await db.execute(sql.raw(`SELECT * FROM dolt_conflicts_${params.tableName}`))).rows;
};
/**
* Get schema conflicts
*/
const doltSchemaConflicts = (db) => async () => {
	return (await db.execute(sql`SELECT * FROM dolt_schema_conflicts`)).rows;
};
/**
* Preview merge conflicts without modifying the database (dry-run).
* Returns a summary of which tables have conflicts.
*/
const doltPreviewMergeConflictsSummary = (db) => async (params) => {
	const escapedBaseBranch = params.baseBranch.replace(/'/g, "''");
	const escapedMergeBranch = params.mergeBranch.replace(/'/g, "''");
	return (await db.execute(sql.raw(`SELECT * FROM DOLT_PREVIEW_MERGE_CONFLICTS_SUMMARY('${escapedBaseBranch}', '${escapedMergeBranch}')`))).rows.map((row) => ({
		table: row.table.replace("public.", ""),
		numDataConflicts: Number(row.num_data_conflicts ?? 0),
		numSchemaConflicts: Number(row.num_schema_conflicts ?? 0)
	}));
};
/**
* Preview detailed merge conflicts for a specific table without modifying the database (dry-run).
* Returns the same column shape as dolt_conflicts_$table.
*/
const doltPreviewMergeConflicts = (db) => async (params) => {
	const escapedBaseBranch = params.baseBranch.replace(/'/g, "''");
	const escapedMergeBranch = params.mergeBranch.replace(/'/g, "''");
	const escapedTableName = params.tableName.replace(/'/g, "''");
	return (await db.execute(sql.raw(`SELECT * FROM DOLT_PREVIEW_MERGE_CONFLICTS('${escapedBaseBranch}', '${escapedMergeBranch}', '${escapedTableName}')`))).rows;
};
/**
* Resolve conflicts for a table using a strategy
*/
const doltResolveConflicts = (db) => async (params) => {
	await db.execute(sql`SET dolt_allow_commit_conflicts = 1`);
	await db.execute(sql.raw(`SELECT DOLT_CONFLICTS_RESOLVE('--${params.strategy}', '${params.tableName}')`));
};

//#endregion
export { MergeConflictError, doltAbortMerge, doltConflicts, doltMerge, doltMergeStatus, doltPreviewMergeConflicts, doltPreviewMergeConflictsSummary, doltResolveConflicts, doltSchemaConflicts, doltTableConflicts };