import runDbClient_default from "../../../data/db/runDbClient.js";
import { getLogger as getLogger$1 } from "../../../logger.js";
import { requireProjectPermission } from "../../../middleware/projectAccess.js";
import { continueImprovement, triggerImprovement } from "../../run/services/ImprovementService.js";
import { OpenAPIHono, z } from "@hono/zod-openapi";
import { EvalSummaryResponseSchema, ImprovementBranchParamsSchema, ImprovementConversationResponseSchema, ImprovementDiffQuerySchema, ImprovementDiffResponseSchema, ImprovementListResponseSchema, ImprovementMergeRequestSchema, ImprovementMergeResponseSchema, ImprovementRevertRequestSchema, ImprovementSuccessMessageSchema, ImprovementTriggerRequestSchema, ImprovementTriggerResponseSchema, TenantProjectParamsSchema, areBranchesSchemaCompatible, commonGetErrorResponses, createApiError, createCoPilotRun, deleteBranch, deleteCoPilotRunsByBranchName, doltCheckout, doltDiff, doltGetBranchNamespace, doltHashOf, doltPreviewMergeConflicts, doltPreviewMergeConflictsSummary, evaluatePassCriteria, generateId, getDatasetById, getEvaluatorsByIds, getFeedbackByIds, getInProcessFetch, getMessagesByConversation, getProjectMainResolvedRef, listCoPilotRuns, listCoPilotRunsByBranchName, listDatasetRuns, listEvaluationResultsByRun, listEvaluationRunsByJobConfigId, listScheduledTriggerInvocationsByTriggerId, manageFkColumnLinks, managePkMap, revertImprovementRows, syncSchemaFromMain } from "@inkeep/agents-core";
import { createProtectedRoute } from "@inkeep/agents-core/middleware";

//#region src/domains/manage/routes/improvements.ts
const logger = getLogger$1("improvements");
const app = new OpenAPIHono();
const EVAL_INFRASTRUCTURE_TABLES = new Set([
	"evaluation_job_config",
	"evaluation_job_config_evaluator_relations",
	"evaluation_run_config",
	"evaluation_run_config_evaluation_suite_config_relations",
	"evaluation_suite_config",
	"evaluation_suite_config_evaluator_relations",
	"dataset_run_config",
	"dataset_run_config_agent_relations",
	"public.evaluation_job_config",
	"public.evaluation_job_config_evaluator_relations",
	"public.evaluation_run_config",
	"public.evaluation_run_config_evaluation_suite_config_relations",
	"public.evaluation_suite_config",
	"public.evaluation_suite_config_evaluator_relations",
	"public.dataset_run_config",
	"public.dataset_run_config_agent_relations",
	"projects",
	"public.projects"
]);
app.openapi(createProtectedRoute({
	method: "get",
	path: "/",
	summary: "List Improvements",
	description: "List all improvement proposals for a project. Returns branches matching the improvement naming convention.",
	operationId: "list-improvements",
	tags: ["Improvements"],
	permission: requireProjectPermission("view"),
	request: { params: TenantProjectParamsSchema },
	responses: {
		200: {
			description: "List of improvement proposals",
			content: { "application/json": { schema: ImprovementListResponseSchema } }
		},
		...commonGetErrorResponses
	}
}), async (c) => {
	const { tenantId, projectId } = c.req.valid("param");
	const runs = await listCoPilotRuns(runDbClient_default)({ scopes: {
		tenantId,
		projectId
	} });
	const projectMainRef = `${tenantId}_${projectId}_main`;
	const seen = /* @__PURE__ */ new Set();
	const data = [];
	for (const run of runs) {
		const name = run.ref?.name;
		if (!name || name === projectMainRef || seen.has(name)) continue;
		seen.add(name);
		data.push({
			branchName: name,
			status: run.status,
			updatedAt: run.updatedAt
		});
	}
	return c.json({ data });
});
app.openapi(createProtectedRoute({
	method: "post",
	path: "/trigger",
	summary: "Trigger Improvement Run",
	description: "Create an improvement branch and trigger the improvement agent via the chat API. Returns immediately while the agent runs in the background.",
	operationId: "trigger-improvement",
	tags: ["Improvements"],
	permission: requireProjectPermission("edit"),
	request: {
		params: TenantProjectParamsSchema,
		body: { content: { "application/json": { schema: ImprovementTriggerRequestSchema } } }
	},
	responses: {
		200: {
			description: "Improvement triggered — agent runs in the background",
			content: { "application/json": { schema: ImprovementTriggerResponseSchema } }
		},
		...commonGetErrorResponses
	}
}), async (c) => {
	const { tenantId, projectId } = c.req.valid("param");
	const { feedbackIds, additionalContext } = c.req.valid("json");
	const feedbackItems = await getFeedbackByIds(runDbClient_default)({
		scopes: {
			tenantId,
			projectId
		},
		feedbackIds
	});
	const missingIds = feedbackIds.filter((id) => !feedbackItems.some((f) => f.id === id));
	if (missingIds.length > 0) throw createApiError({
		code: "bad_request",
		message: `Feedback not found: ${missingIds.join(", ")}`
	});
	const agentIds = new Set(feedbackItems.map((f) => f.agentId).filter(Boolean));
	if (agentIds.size > 1) throw createApiError({
		code: "bad_request",
		message: `All feedback must belong to the same agent. Found feedback from multiple agents: ${[...agentIds].join(", ")}`
	});
	const agentId = [...agentIds][0];
	if (!agentId) throw createApiError({
		code: "bad_request",
		message: "Could not derive target agentId from feedback"
	});
	const db = c.get("db");
	const userId = c.get("userId");
	if (!userId) throw createApiError({
		code: "unauthorized",
		message: "userId is required"
	});
	const result = await triggerImprovement({
		tenantId,
		projectId,
		agentId,
		feedbackIds,
		additionalContext,
		userId,
		forwardedCookie: c.req.header("x-forwarded-cookie") || c.req.header("cookie") || void 0,
		db
	});
	return c.json(result);
});
app.openapi(createProtectedRoute({
	method: "post",
	path: "/copilot-runs",
	summary: "Create CoPilot Run",
	description: "Track an interactive copilot session. Called on the first user message in a copilot chat.",
	operationId: "create-copilot-run",
	tags: ["Improvements"],
	permission: requireProjectPermission("edit"),
	request: {
		params: TenantProjectParamsSchema,
		body: { content: { "application/json": { schema: z.object({ conversationId: z.string().describe("The conversation ID from the copilot chat") }) } } }
	},
	responses: {
		200: {
			description: "CoPilot run tracked",
			content: { "application/json": { schema: z.object({
				id: z.string(),
				conversationId: z.string()
			}) } }
		},
		...commonGetErrorResponses
	}
}), async (c) => {
	const { tenantId, projectId } = c.req.valid("param");
	const { conversationId } = c.req.valid("json");
	const mainRef = await getProjectMainResolvedRef(c.get("db"))(tenantId, projectId);
	const run = await createCoPilotRun(runDbClient_default)({
		tenantId,
		projectId,
		id: generateId(),
		ref: mainRef,
		conversationId,
		status: "running"
	});
	return c.json({
		id: run.id,
		conversationId: run.conversationId
	});
});
app.openapi(createProtectedRoute({
	method: "post",
	path: "/{branchName}/continue",
	summary: "Continue Improvement",
	description: "Send a follow-up message to an improvement branch. Creates a new conversation and appends it to the existing copilot run.",
	operationId: "continue-improvement",
	tags: ["Improvements"],
	permission: requireProjectPermission("edit"),
	request: {
		params: ImprovementBranchParamsSchema,
		body: { content: { "application/json": { schema: z.object({ message: z.string().min(1).describe("The follow-up instruction for the agent") }) } } }
	},
	responses: {
		200: {
			description: "Continuation triggered — new conversation created",
			content: { "application/json": { schema: z.object({ conversationId: z.string() }) } }
		},
		...commonGetErrorResponses
	}
}), async (c) => {
	const { tenantId, projectId, branchName } = c.req.valid("param");
	const { message } = c.req.valid("json");
	const userId = c.get("userId");
	if (!userId) throw createApiError({
		code: "unauthorized",
		message: "userId is required"
	});
	const db = c.get("db");
	const forwardedCookie = c.req.header("x-forwarded-cookie") || c.req.header("cookie") || void 0;
	const result = await continueImprovement({
		tenantId,
		projectId,
		branchName: decodeURIComponent(branchName),
		message,
		userId,
		forwardedCookie,
		db
	});
	return c.json(result);
});
app.openapi(createProtectedRoute({
	method: "get",
	path: "/{branchName}/diff",
	summary: "Get Improvement Diff",
	description: "Get the diff between an improvement branch and a target branch (defaults to main), showing what changes would be applied on merge. Delegates conflict detection and hashes to the generic branches merge preview endpoint, and enriches with per-row table data and foreign-key metadata.",
	operationId: "get-improvement-diff",
	tags: ["Improvements"],
	permission: requireProjectPermission("view"),
	request: {
		params: ImprovementBranchParamsSchema,
		query: ImprovementDiffQuerySchema
	},
	responses: {
		200: {
			description: "Diff between improvement branch and target branch",
			content: { "application/json": { schema: ImprovementDiffResponseSchema } }
		},
		...commonGetErrorResponses
	}
}), async (c) => {
	const { tenantId, projectId, branchName } = c.req.valid("param");
	const { targetBranch: targetBranchParam } = c.req.valid("query");
	const db = c.get("db");
	const decodedBranchName = decodeURIComponent(branchName);
	const targetBranch = targetBranchParam ?? "main";
	if (decodedBranchName === "main") throw createApiError({
		code: "bad_request",
		message: "Cannot operate on main branch"
	});
	const sourceFullName = doltGetBranchNamespace({
		tenantId,
		projectId,
		branchName: decodedBranchName
	})();
	const targetFullName = doltGetBranchNamespace({
		tenantId,
		projectId,
		branchName: targetBranch
	})();
	const previewUrl = `${new URL(c.req.url).origin}/manage/tenants/${encodeURIComponent(tenantId)}/projects/${encodeURIComponent(projectId)}/branches/merge/preview`;
	const forwardedHeaders = new Headers(c.req.raw.headers);
	forwardedHeaders.set("Content-Type", "application/json");
	forwardedHeaders.delete("content-length");
	const previewResponse = await getInProcessFetch()(previewUrl, {
		method: "POST",
		headers: forwardedHeaders,
		body: JSON.stringify({
			sourceBranch: decodedBranchName,
			targetBranch
		})
	});
	if (!previewResponse.ok) {
		const errorBody = await previewResponse.json().catch(() => null);
		logger.warn({
			sourceFullName,
			targetFullName,
			status: previewResponse.status,
			message: errorBody?.error?.message
		}, "Merge preview failed for improvement diff");
		return c.json({
			branchName: decodedBranchName,
			targetBranch,
			hasConflicts: false,
			conflicts: [],
			summary: [],
			tables: {}
		});
	}
	const preview = await previewResponse.json();
	const summary = preview.data.diffSummary.filter((row) => !EVAL_INFRASTRUCTURE_TABLES.has(row.table)).map((row) => ({
		tableName: row.table,
		diffType: row.diffType,
		dataChange: row.dataChange,
		schemaChange: row.schemaChange
	}));
	const conflicts = preview.data.conflicts.filter((c$1) => !EVAL_INFRASTRUCTURE_TABLES.has(c$1.table));
	const tables = {};
	for (const s of summary) {
		if (!s.dataChange) continue;
		const rawTableName = s.tableName.replace(/^public\./, "");
		try {
			const rows = await doltDiff(db)({
				fromRevision: targetFullName,
				toRevision: sourceFullName,
				tableName: rawTableName
			});
			tables[s.tableName] = rows;
		} catch (err) {
			logger.warn({
				tableName: s.tableName,
				rawTableName,
				err
			}, "Failed to get diff for table");
		}
	}
	const changedTables = new Set(summary.map((s) => s.tableName.replace(/^public\./, "")));
	const relevantFkLinks = manageFkColumnLinks.filter((link) => changedTables.has(link.childTable) || changedTables.has(link.parentTable));
	return c.json({
		branchName: decodedBranchName,
		targetBranch,
		sourceHash: preview.data.sourceHash,
		targetHash: preview.data.targetHash,
		hasConflicts: conflicts.length > 0,
		conflicts,
		summary,
		tables,
		fkLinks: relevantFkLinks,
		pkMap: Object.fromEntries([...changedTables].filter((t) => managePkMap[t]).map((t) => [t, managePkMap[t]]))
	});
});
app.openapi(createProtectedRoute({
	method: "post",
	path: "/{branchName}/merge",
	summary: "Merge Improvement",
	description: "Approve and merge an improvement branch into a target branch (defaults to main). Delegates the actual merge to the generic branches merge endpoint for locking and concurrency handling, then runs improvement-specific cleanup on success. If conflicts exist, returns 409 with conflict details. Re-submit with resolutions to resolve.",
	operationId: "merge-improvement",
	tags: ["Improvements"],
	permission: requireProjectPermission("edit"),
	request: {
		params: ImprovementBranchParamsSchema,
		body: {
			content: { "application/json": { schema: ImprovementMergeRequestSchema } },
			required: false
		}
	},
	responses: {
		200: {
			description: "Improvement merged successfully",
			content: { "application/json": { schema: ImprovementMergeResponseSchema } }
		},
		409: { description: "Merge conflicts detected — resolve and retry" },
		...commonGetErrorResponses
	}
}), async (c) => {
	const { tenantId, projectId, branchName } = c.req.valid("param");
	const db = c.get("db");
	const decodedBranchName = decodeURIComponent(branchName);
	if (decodedBranchName === "main") throw createApiError({
		code: "bad_request",
		message: "Cannot operate on main branch"
	});
	const body = c.req.valid("json");
	const resolutions = body?.resolutions;
	const targetBranch = body?.targetBranch ?? "main";
	if (targetBranch === decodedBranchName) throw createApiError({
		code: "bad_request",
		message: "Source and target branch must differ"
	});
	const sourceFullName = doltGetBranchNamespace({
		tenantId,
		projectId,
		branchName: decodedBranchName
	})();
	const targetFullName = doltGetBranchNamespace({
		tenantId,
		projectId,
		branchName: targetBranch
	})();
	const schemaCompat = await areBranchesSchemaCompatible(db)(sourceFullName, targetFullName);
	if (!schemaCompat.compatible) {
		if (schemaCompat.branchADifferences.length > 0) {
			await doltCheckout(db)({ branch: sourceFullName });
			const syncResult = await syncSchemaFromMain(db)({ autoCommitPending: true });
			if (syncResult.error && !syncResult.synced) logger.warn({ error: syncResult.error }, "Schema sync failed on improvement branch");
		}
		if (schemaCompat.branchBDifferences.length > 0) {
			await doltCheckout(db)({ branch: targetFullName });
			const syncResult = await syncSchemaFromMain(db)({ autoCommitPending: true });
			if (syncResult.error && !syncResult.synced) logger.warn({ error: syncResult.error }, "Schema sync failed on target branch");
		}
	}
	const [sourceHash, targetHash] = await Promise.all([doltHashOf(db)({ revision: sourceFullName }), doltHashOf(db)({ revision: targetFullName })]);
	const mergeUrl = `${new URL(c.req.url).origin}/manage/tenants/${encodeURIComponent(tenantId)}/projects/${encodeURIComponent(projectId)}/branches/merge`;
	const forwardedHeaders = new Headers(c.req.raw.headers);
	forwardedHeaders.set("Content-Type", "application/json");
	forwardedHeaders.delete("content-length");
	const mergeResponse = await getInProcessFetch()(mergeUrl, {
		method: "POST",
		headers: forwardedHeaders,
		body: JSON.stringify({
			sourceBranch: decodedBranchName,
			targetBranch,
			sourceHash,
			targetHash,
			message: `Merge improvement "${decodedBranchName}" into ${targetBranch}`,
			...resolutions ? { resolutions } : {}
		})
	});
	if (mergeResponse.status === 409) {
		const conflicts = await buildImprovementConflictItems(db, targetFullName, sourceFullName).catch((err) => {
			logger.warn({
				err,
				sourceBranch: decodedBranchName,
				targetBranch
			}, "Failed to build conflict items for 409 merge response");
			return [];
		});
		if (conflicts.length > 0) throw createApiError({
			code: "conflict",
			message: `Merge has ${conflicts.length} conflict(s) that need resolution.`,
			extensions: { conflicts }
		});
		throw createApiError({
			code: "conflict",
			message: (await mergeResponse.json().catch(() => null))?.error?.message ?? "Merge conflict"
		});
	}
	if (!mergeResponse.ok) {
		const message = (await mergeResponse.json().catch(() => null))?.error?.message ?? `Merge failed with status ${mergeResponse.status}`;
		if (mergeResponse.status === 400) throw createApiError({
			code: "bad_request",
			message
		});
		if (mergeResponse.status === 404) throw createApiError({
			code: "not_found",
			message
		});
		throw createApiError({
			code: "internal_server_error",
			message
		});
	}
	const mergeResult = await mergeResponse.json();
	await deleteBranch(db)({
		tenantId,
		projectId,
		branchName: decodedBranchName,
		force: true
	}).catch((err) => {
		logger.warn({
			err,
			branchName: decodedBranchName
		}, "Failed to delete branch after merge");
	});
	await deleteCoPilotRunsByBranchName(runDbClient_default)({
		scopes: {
			tenantId,
			projectId
		},
		branchName: decodedBranchName
	}).catch((err) => {
		logger.warn({
			err,
			branchName: decodedBranchName
		}, "Failed to delete copilot runs after merge");
	});
	return c.json({
		success: true,
		message: `Improvement branch "${decodedBranchName}" merged into ${targetBranch}`,
		mergeCommitHash: mergeResult.data.mergeCommitHash,
		sourceBranch: decodedBranchName,
		targetBranch
	});
});
app.openapi(createProtectedRoute({
	method: "post",
	path: "/{branchName}/revert",
	summary: "Revert Changes on Improvement Branch",
	description: "Revert specific changes on the improvement branch before merging, allowing selective merge. Baseline values for modified/removed rows are read from the target branch (defaults to main).",
	operationId: "revert-improvement",
	tags: ["Improvements"],
	permission: requireProjectPermission("edit"),
	request: {
		params: ImprovementBranchParamsSchema,
		body: { content: { "application/json": { schema: ImprovementRevertRequestSchema } } }
	},
	responses: {
		200: {
			description: "Rows reverted on the improvement branch",
			content: { "application/json": { schema: ImprovementSuccessMessageSchema } }
		},
		...commonGetErrorResponses
	}
}), async (c) => {
	const { tenantId, projectId, branchName } = c.req.valid("param");
	const db = c.get("db");
	const decodedBranchName = decodeURIComponent(branchName);
	if (decodedBranchName === "main") throw createApiError({
		code: "bad_request",
		message: "Cannot operate on main branch"
	});
	const { rows, targetBranch: targetBranchParam } = c.req.valid("json");
	const targetBranch = targetBranchParam ?? "main";
	if (targetBranch === decodedBranchName) throw createApiError({
		code: "bad_request",
		message: "Source and target branch must differ"
	});
	if (rows.length === 0) return c.json({
		success: true,
		message: "No rows to revert"
	});
	const branchFullName = doltGetBranchNamespace({
		tenantId,
		projectId,
		branchName: decodedBranchName
	})();
	const targetFullName = doltGetBranchNamespace({
		tenantId,
		projectId,
		branchName: targetBranch
	})();
	if (!(await revertImprovementRows(db)({
		sourceBranchFullName: branchFullName,
		targetBranchFullName: targetFullName,
		rows,
		commitMessage: `Revert ${rows.length} excluded row(s) before merge into ${targetBranch}`
	})).committed) logger.info({
		tenantId,
		projectId,
		branchName: decodedBranchName,
		targetBranch,
		rowCount: rows.length
	}, "No changes to commit (rows may already match target)");
	logger.info({
		tenantId,
		projectId,
		branchName: decodedBranchName,
		targetBranch,
		rowCount: rows.length
	}, "Reverted excluded rows on improvement branch");
	return c.json({
		success: true,
		message: `Reverted ${rows.length} row(s) on branch "${decodedBranchName}"`
	});
});
app.openapi(createProtectedRoute({
	method: "post",
	path: "/{branchName}/reject",
	summary: "Reject Improvement",
	description: "Reject an improvement proposal and delete the branch.",
	operationId: "reject-improvement",
	tags: ["Improvements"],
	permission: requireProjectPermission("edit"),
	request: { params: ImprovementBranchParamsSchema },
	responses: {
		200: {
			description: "Improvement rejected and branch deleted",
			content: { "application/json": { schema: ImprovementSuccessMessageSchema } }
		},
		...commonGetErrorResponses
	}
}), async (c) => {
	const { tenantId, projectId, branchName } = c.req.valid("param");
	const db = c.get("db");
	const decodedBranchName = decodeURIComponent(branchName);
	if (decodedBranchName === "main") throw createApiError({
		code: "bad_request",
		message: "Cannot operate on main branch"
	});
	await deleteBranch(db)({
		tenantId,
		projectId,
		branchName: decodedBranchName,
		force: true
	});
	await deleteCoPilotRunsByBranchName(runDbClient_default)({
		scopes: {
			tenantId,
			projectId
		},
		branchName: decodedBranchName
	}).catch((err) => {
		logger.warn({
			err,
			branchName: decodedBranchName
		}, "Failed to delete copilot runs after reject");
	});
	return c.json({
		success: true,
		message: `Improvement branch "${decodedBranchName}" rejected and deleted`
	});
});
app.openapi(createProtectedRoute({
	method: "get",
	path: "/{branchName}/conversation",
	summary: "Get Improvement Conversation",
	description: "Get the improvement agent conversation trace for a given improvement branch.",
	operationId: "get-improvement-conversation",
	tags: ["Improvements"],
	permission: requireProjectPermission("view"),
	request: { params: ImprovementBranchParamsSchema },
	responses: {
		200: {
			description: "Conversation messages from the improvement agent run",
			content: { "application/json": { schema: ImprovementConversationResponseSchema } }
		},
		...commonGetErrorResponses
	}
}), async (c) => {
	const { tenantId, projectId, branchName } = c.req.valid("param");
	const decodedBranchName = decodeURIComponent(branchName);
	const runs = await listCoPilotRunsByBranchName(runDbClient_default)({
		scopes: {
			tenantId,
			projectId
		},
		branchName: decodedBranchName
	});
	if (runs.length === 0) return c.json({
		conversationIds: [],
		status: void 0,
		feedbackItems: void 0
	});
	const oldest = runs[0];
	const newest = runs[runs.length - 1];
	const feedbackIds = oldest.feedbackIds ?? [];
	const feedbackItems = feedbackIds.length > 0 ? await getFeedbackByIds(runDbClient_default)({
		scopes: {
			tenantId,
			projectId
		},
		feedbackIds
	}).catch((err) => {
		logger.warn({
			err,
			feedbackIds,
			tenantId,
			projectId,
			branchName: decodedBranchName
		}, "Failed to fetch feedback items for improvement");
		return [];
	}) : [];
	return c.json({
		conversationIds: runs.map((r) => r.conversationId),
		status: newest.status,
		feedbackItems: feedbackItems.length > 0 ? feedbackItems.map((f) => ({
			id: f.id,
			type: f.type,
			details: f.details,
			createdAt: f.createdAt
		})) : void 0
	});
});
app.openapi(createProtectedRoute({
	method: "get",
	path: "/{branchName}/eval-summary",
	summary: "Get Improvement Eval Summary",
	description: "Get structured evaluation and dataset run data for an improvement branch.",
	operationId: "get-improvement-eval-summary",
	tags: ["Improvements"],
	permission: requireProjectPermission("view"),
	request: { params: ImprovementBranchParamsSchema },
	responses: {
		200: {
			description: "Evaluation summary for the improvement branch",
			content: { "application/json": { schema: EvalSummaryResponseSchema } }
		},
		...commonGetErrorResponses
	}
}), async (c) => {
	const { tenantId, projectId, branchName } = c.req.valid("param");
	const db = c.get("db");
	const decodedBranchName = decodeURIComponent(branchName);
	if (decodedBranchName === "main") throw createApiError({
		code: "bad_request",
		message: "Cannot operate on main branch"
	});
	const allRuns = await listDatasetRuns(runDbClient_default)({ scopes: {
		tenantId,
		projectId
	} });
	const improvementBranchFullName = doltGetBranchNamespace({
		tenantId,
		projectId,
		branchName: decodedBranchName
	})();
	const branchRuns = allRuns.filter((run) => {
		if (!run.ref) return false;
		return (run.ref.name ?? "") === improvementBranchFullName;
	});
	await doltCheckout(db)({ branch: improvementBranchFullName });
	const runData = await Promise.all(branchRuns.map(async (run) => {
		const [invocations, dataset] = await Promise.all([listScheduledTriggerInvocationsByTriggerId(runDbClient_default)({
			scopes: {
				tenantId,
				projectId
			},
			scheduledTriggerId: run.id
		}), getDatasetById(db)({ scopes: {
			tenantId,
			projectId,
			datasetId: run.datasetId
		} }).catch(() => null)]);
		const statusCounts = {
			total: invocations.length,
			completed: invocations.filter((i) => i.status === "completed").length,
			failed: invocations.filter((i) => i.status === "failed").length,
			pending: invocations.filter((i) => i.status === "pending").length,
			running: invocations.filter((i) => i.status === "running").length
		};
		let allResults = [];
		const inputMap = /* @__PURE__ */ new Map();
		if (run.evaluationJobConfigId) {
			const jobRuns = await listEvaluationRunsByJobConfigId(runDbClient_default)({
				scopes: {
					tenantId,
					projectId
				},
				evaluationJobConfigId: run.evaluationJobConfigId
			});
			allResults = (await Promise.all(jobRuns.map((jr) => listEvaluationResultsByRun(runDbClient_default)({ scopes: {
				tenantId,
				projectId,
				evaluationRunId: jr.id
			} })))).flat();
			const uniqueConvIds = [...new Set(allResults.map((r) => r.conversationId))];
			await Promise.all(uniqueConvIds.map(async (conversationId) => {
				try {
					const firstUser = [...await getMessagesByConversation(runDbClient_default)({
						scopes: {
							tenantId,
							projectId
						},
						conversationId,
						pagination: {
							page: 1,
							limit: 10
						}
					})].reverse().find((m) => m.role === "user");
					if (firstUser?.content) {
						const text = typeof firstUser.content === "string" ? firstUser.content : firstUser.content.text || "";
						if (text) inputMap.set(conversationId, text);
					}
				} catch {}
			}));
		}
		return {
			run,
			dataset,
			statusCounts,
			allResults,
			inputMap
		};
	}));
	const allEvaluatorIds = /* @__PURE__ */ new Set();
	for (const entry of runData) for (const result of entry.allResults) allEvaluatorIds.add(result.evaluatorId);
	const evaluators = allEvaluatorIds.size > 0 ? await getEvaluatorsByIds(db)({
		scopes: {
			tenantId,
			projectId
		},
		evaluatorIds: Array.from(allEvaluatorIds)
	}) : [];
	const evaluatorMap = new Map(evaluators.map((e) => [e.id, e]));
	const datasetRuns = runData.map(({ run, dataset, statusCounts, allResults, inputMap }) => {
		const evaluationResults = allResults.map((result) => {
			const evaluator = evaluatorMap.get(result.evaluatorId);
			const evaluatorName = evaluator?.name ?? result.evaluatorId;
			let passed = "pending";
			if (result.output) {
				const outputData = result.output?.output ?? {};
				passed = evaluatePassCriteria(evaluator?.passCriteria ?? null, outputData).status;
			}
			return {
				id: result.id,
				evaluatorId: result.evaluatorId,
				evaluatorName,
				conversationId: result.conversationId,
				input: inputMap.get(result.conversationId) ?? null,
				output: result.output,
				passed,
				createdAt: result.createdAt
			};
		});
		return {
			id: run.id,
			datasetId: run.datasetId,
			datasetName: dataset?.name ?? run.datasetId,
			datasetCreatedAt: dataset?.createdAt ?? null,
			runConfigName: run.datasetRunConfigId ?? null,
			createdAt: run.createdAt,
			phase: "unknown",
			ref: run.ref,
			items: statusCounts,
			evaluationJobConfigId: run.evaluationJobConfigId ?? null,
			evaluationResults
		};
	});
	datasetRuns.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
	const datasetRunsByDataset = /* @__PURE__ */ new Map();
	for (const run of datasetRuns) {
		const existing = datasetRunsByDataset.get(run.datasetId) ?? [];
		existing.push(run);
		datasetRunsByDataset.set(run.datasetId, existing);
	}
	for (const runs of datasetRunsByDataset.values()) if (new Set(runs.map((r) => r.ref?.hash).filter(Boolean)).size <= 1) for (const r of runs) r.phase = "post_change";
	else {
		const earliestHash = [...runs].sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime())[0]?.ref?.hash;
		for (const r of runs) r.phase = r.ref?.hash === earliestHash ? "baseline" : "post_change";
	}
	return c.json({ datasetRuns });
});
const TIMESTAMP_COLUMNS = new Set(["created_at", "updated_at"]);
function extractPrefixedValues(row, prefix, pkColumns) {
	const result = {};
	const skipSuffixes = new Set(["diff_type", ...pkColumns]);
	for (const [key, value] of Object.entries(row)) {
		if (!key.startsWith(prefix)) continue;
		const suffix = key.slice(prefix.length);
		if (skipSuffixes.has(suffix)) continue;
		result[suffix] = value;
	}
	return result;
}
function isTimestampOnlyDiff(base, ours, theirs, row) {
	if (row.our_diff_type !== "modified" || row.their_diff_type !== "modified") return false;
	for (const key of Object.keys(base)) {
		if (TIMESTAMP_COLUMNS.has(key)) continue;
		if (String(base[key] ?? "") !== String(ours[key] ?? "") || String(base[key] ?? "") !== String(theirs[key] ?? "")) return false;
	}
	return true;
}
async function buildImprovementConflictItems(db, baseBranch, mergeBranch) {
	const tablesWithConflicts = (await doltPreviewMergeConflictsSummary(db)({
		baseBranch,
		mergeBranch
	})).filter((t) => t.numDataConflicts > 0);
	const conflicts = [];
	for (const ct of tablesWithConflicts) {
		const rows = await doltPreviewMergeConflicts(db)({
			baseBranch,
			mergeBranch,
			tableName: ct.table
		});
		const pkColumns = managePkMap[ct.table] ?? [];
		for (const row of rows) {
			const fullPk = {};
			for (const col of pkColumns) fullPk[col] = String(row[`base_${col}`] ?? row[`our_${col}`] ?? row[`their_${col}`]);
			const base = extractPrefixedValues(row, "base_", pkColumns);
			const ours = extractPrefixedValues(row, "our_", pkColumns);
			const theirs = extractPrefixedValues(row, "their_", pkColumns);
			if (isTimestampOnlyDiff(base, ours, theirs, row)) continue;
			conflicts.push({
				table: ct.table,
				primaryKey: fullPk,
				ourDiffType: String(row.our_diff_type ?? "modified"),
				theirDiffType: String(row.their_diff_type ?? "modified"),
				base: row.base_diff_type === "added" ? null : base,
				ours: row.our_diff_type === "removed" ? null : ours,
				theirs: row.their_diff_type === "removed" ? null : theirs
			});
		}
	}
	return conflicts;
}
var improvements_default = app;

//#endregion
export { improvements_default as default };