import { doltHashOf } from "./commit.js";
import { SCHEMA_SOURCE_BRANCH, ensureSchemaSync, getSchemaDiff, syncSchemaFromMain } from "./schema-sync.js";
import { doltBranch, doltCheckout, doltDeleteBranch, doltGetBranchNamespace, doltListBranches } from "./branch.js";
import { sql } from "drizzle-orm";

//#region src/dolt/branches-api.ts
const MAIN_BRANCH_SUFFIX = "main";
var invalidBranchParamsError = class extends Error {
	constructor(message) {
		super(message);
		this.name = "invalidBranchParamsError";
	}
};
/**
* Get the tenant-scoped main branch name
*/
const getTenantMainBranch = (tenantId) => `${tenantId}_${MAIN_BRANCH_SUFFIX}`;
/**
* Check if a branch name (without tenant/project prefix) is a protected branch
*/
const isProtectedBranchName = (branchName) => {
	return branchName === MAIN_BRANCH_SUFFIX;
};
const getTempBranchSuffix = (prefix) => {
	return `temp-${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;
};
const isTempBranchName = (fullName) => {
	return /temp-[a-z-]+_\d+_[a-z0-9]+$/.test(fullName);
};
/**
* Checkout a branch with optional schema synchronization from main.
*
* This function:
* 1. Checks out the specified branch
* 2. If syncSchema is true (default), checks for schema differences from main
* 3. If differences exist, merges schema from main into the branch
*
* @param db - Database client
* @returns Function that takes checkout params and returns checkout result
*/
const checkoutBranch = (db) => async (params) => {
	const { branchName, syncSchema = true, autoCommitPending = false } = params;
	const branch = (await doltListBranches(db)()).find((b) => b.name === branchName);
	if (!branch) throw new Error(`Branch '${branchName}' not found`);
	await doltCheckout(db)({ branch: branchName });
	let schemaSyncResult = {
		synced: false,
		hadDifferences: false
	};
	if (syncSchema && branchName !== SCHEMA_SOURCE_BRANCH) schemaSyncResult = await ensureSchemaSync(db)({
		autoSync: true,
		autoCommitPending
	});
	return {
		branchName,
		hash: (await doltListBranches(db)()).find((b) => b.name === branchName)?.hash ?? branch.hash,
		schemaSync: {
			performed: schemaSyncResult.synced,
			hadDifferences: schemaSyncResult.hadDifferences,
			error: schemaSyncResult.error,
			mergeCommitHash: schemaSyncResult.mergeCommitHash
		}
	};
};
const syncSchemaOnBranch = (db) => async (branchName) => {
	if ((await getSchemaDiff(db)(branchName)).length > 0) {
		await doltCheckout(db)({ branch: branchName });
		const syncResult = await syncSchemaFromMain(db)({ autoCommitPending: true });
		if (syncResult.error && !syncResult.synced) throw new Error(`Failed to sync schema on source branch '${branchName}': ${syncResult.error}`);
	}
};
/**
* Create a new branch with optional schema synchronization.
*
* This function:
* 1. Optionally syncs schema on the source branch from main (default: true)
* 2. Creates a new branch from the source branch
*
* By syncing schema on the source branch first, we ensure the new branch
* starts with the latest schema, avoiding schema conflicts later.
* If you want to create a branch from a commit hash, use the fromCommit parameter.
*/
const createBranch = (db) => async (params) => {
	const { tenantId, projectId, name, fromBranch, fromCommit, syncSchemaOnSource = true } = params;
	if (fromBranch && fromCommit) throw new invalidBranchParamsError("Cannot specify both fromBranch and fromCommit");
	if (!name || name.trim() === "") throw new invalidBranchParamsError("Branch name cannot be empty");
	const fullName = doltGetBranchNamespace({
		tenantId,
		projectId,
		branchName: name
	})();
	if ((await doltListBranches(db)()).some((b) => b.name === fullName)) throw new invalidBranchParamsError(`Branch '${name}' already exists`);
	if (fromCommit) {
		await doltBranch(db)({
			name: fullName,
			startPoint: fromCommit
		});
		if (syncSchemaOnSource) await syncSchemaOnBranch(db)(fullName);
	} else {
		let fromFullBranchName;
		if (fromBranch) fromFullBranchName = doltGetBranchNamespace({
			tenantId,
			projectId,
			branchName: fromBranch
		})();
		else fromFullBranchName = doltGetBranchNamespace({
			tenantId,
			projectId,
			branchName: MAIN_BRANCH_SUFFIX
		})();
		if (syncSchemaOnSource && fromFullBranchName !== SCHEMA_SOURCE_BRANCH) await syncSchemaOnBranch(db)(fromFullBranchName);
		await doltBranch(db)({
			name: fullName,
			startPoint: fromFullBranchName
		});
	}
	return {
		baseName: name,
		fullName,
		hash: await doltHashOf(db)({ revision: fullName })
	};
};
/**
* Delete a branch
*/
const deleteBranch = (db) => async (params) => {
	const { tenantId, projectId, branchName, force } = params;
	if (isProtectedBranchName(branchName)) throw new invalidBranchParamsError(`Cannot delete protected branch '${branchName}'`);
	const fullName = doltGetBranchNamespace({
		tenantId,
		projectId,
		branchName
	})();
	if (!(await doltListBranches(db)()).some((b) => b.name === fullName)) throw new invalidBranchParamsError(`Branch '${branchName}' not found`);
	await doltDeleteBranch(db)({
		name: fullName,
		force
	});
};
/**
* Get a single branch
*/
const getBranch = (db) => async (params) => {
	const { tenantId, projectId, name } = params;
	const fullName = doltGetBranchNamespace({
		tenantId,
		projectId,
		branchName: name
	})();
	const branch = (await doltListBranches(db)()).find((b) => b.name === fullName);
	if (!branch) return null;
	return {
		baseName: name,
		fullName,
		hash: branch.hash
	};
};
/**
* List all branches for a project
* Returns only project-specific branches (e.g., {tenantId}_{projectId}_main, {tenantId}_{projectId}_feature-x)
* Does NOT include the tenant_main branch as that's an organizational branch, not a project development branch
*/
const listBranches = (db) => async (params) => {
	const { tenantId, projectId } = params;
	const allBranches = await doltListBranches(db)();
	const prefix = `${tenantId}_${projectId}_`;
	return allBranches.filter((b) => b.name.startsWith(prefix)).map((b) => ({
		baseName: b.name.substring(prefix.length),
		fullName: b.name,
		hash: b.hash
	}));
};
const listBranchesForAgent = (db) => async (params) => {
	const { tenantId, projectId, agentId } = params;
	const allBranches = await listBranches(db)({
		tenantId,
		projectId
	});
	const branches = [];
	for (const branch of allBranches) try {
		const result = await db.execute(sql.raw(`SELECT id FROM agent AS OF '${branch.fullName}' WHERE id = '${agentId}'`));
		console.log(result);
		if (result.rows.length > 0) branches.push(branch);
	} catch (error) {
		console.debug(`Failed to query agent ${agentId} on branch ${branch.fullName}:`, error);
	}
	return branches;
};

//#endregion
export { MAIN_BRANCH_SUFFIX, checkoutBranch, createBranch, deleteBranch, getBranch, getTempBranchSuffix, getTenantMainBranch, invalidBranchParamsError, isProtectedBranchName, isTempBranchName, listBranches, listBranchesForAgent, syncSchemaOnBranch };