import { env } from "../../../env.js";
import runDbClient_default from "../../../data/db/runDbClient.js";
import { getLogger as getLogger$1 } from "../../../logger.js";
import manageDbClient_default from "../../../data/db/manageDbClient.js";
import { requireProjectPermission } from "../../../middleware/projectAccess.js";
import { openapiRegisterPutPatchRoutesForLegacy } from "../../../utils/openapiDualRoute.js";
import { requireEntitlement } from "../../../middleware/requireEntitlement.js";
import { requirePermission } from "../../../middleware/requirePermission.js";
import { validateTriggerPermissions } from "./triggerHelpers.js";
import { OpenAPIHono } from "@hono/zod-openapi";
import { ErrorResponseSchema, FullProjectDefinitionSchema, FullProjectSelectResponse, FullProjectSelectWithRelationIdsResponse, QUOTA_RESOURCE_TYPES, TenantParamsSchema, TenantProjectParamsSchema, cascadeDeleteByProject, checkoutBranch, commonGetErrorResponses, countProjectsInRuntime, createApiError, createFullProjectServerSide, createProjectMetadataAndBranch, deleteFullProject, deleteProjectAndBranches, doltCheckout, getFullProject, getFullProjectWithRelationIds, getProjectMainBranchName, getProjectMetadata, listTriggers, removeProjectFromSpiceDb, syncProjectToSpiceDb, throwIfUniqueConstraintError, updateFullProjectServerSide, withEntitlementLock } from "@inkeep/agents-core";
import { createProtectedRoute, registerAuthzMeta } from "@inkeep/agents-core/middleware";
import { HTTPException } from "hono/http-exception";

//#region src/domains/manage/routes/projectFull.ts
const logger = getLogger$1("projectFull");
const app = new OpenAPIHono();
const requireProjectUpsertPermission = async (c, next) => {
	const tenantId = c.get("tenantId");
	const projectId = c.req.param("projectId");
	if (!tenantId || !projectId) throw createApiError({
		code: "bad_request",
		message: "Missing tenantId or projectId"
	});
	const exists = await getProjectMetadata(runDbClient_default)({
		tenantId,
		projectId
	});
	c.set("isProjectCreate", !exists);
	return exists ? requireProjectPermission("edit")(c, next) : requirePermission({ project: ["create"] })(c, next);
};
registerAuthzMeta(requireProjectUpsertPermission, { description: "Dynamic: requires organization admin for new projects, or project edit permission for existing projects." });
app.openapi(createProtectedRoute({
	method: "post",
	path: "/project-full",
	summary: "Create Full Project",
	operationId: "create-full-project",
	tags: ["Projects"],
	description: "Create a complete project with all Agents, Sub Agents, tools, and relationships from JSON definition",
	permission: requirePermission({ project: ["create"] }),
	entitlement: requireEntitlement({
		resourceType: QUOTA_RESOURCE_TYPES.PROJECT,
		countFn: (tenantId) => countProjectsInRuntime(runDbClient_default)({ tenantId }),
		label: "Project"
	}),
	request: {
		params: TenantParamsSchema,
		body: { content: { "application/json": { schema: FullProjectDefinitionSchema } } }
	},
	responses: {
		201: {
			description: "Full project created successfully",
			content: { "application/json": { schema: FullProjectSelectResponse } }
		},
		409: {
			description: "Project already exists",
			content: { "application/json": { schema: ErrorResponseSchema } }
		},
		...commonGetErrorResponses
	}
}), async (c) => {
	const configDb = c.get("db");
	const userId = c.get("userId");
	const { tenantId } = c.req.valid("param");
	const projectData = c.req.valid("json");
	const validatedProjectData = FullProjectDefinitionSchema.parse(projectData);
	try {
		const createdProject = await runDbClient_default.transaction(async (runTx) => {
			return await configDb.transaction(async (configTx) => {
				await createProjectMetadataAndBranch(runTx, configTx)({
					tenantId,
					projectId: validatedProjectData.id,
					createdBy: userId
				});
				logger.info({
					tenantId,
					projectId: validatedProjectData.id
				}, "Created project with branch, now populating config");
				const projectMainBranch = getProjectMainBranchName(tenantId, validatedProjectData.id);
				await checkoutBranch(configTx)({
					branchName: projectMainBranch,
					autoCommitPending: true
				});
				const newResolvedRef = {
					type: "branch",
					name: projectMainBranch,
					hash: ""
				};
				c.set("resolvedRef", newResolvedRef);
				logger.debug({ projectMainBranch }, "Checked out project branch for config writes");
				const project = await createFullProjectServerSide(configTx)({
					scopes: {
						tenantId,
						projectId: validatedProjectData.id
					},
					projectData: validatedProjectData
				});
				if (userId) await syncProjectToSpiceDb({
					tenantId,
					projectId: validatedProjectData.id,
					creatorUserId: userId
				});
				else logger.warn({
					tenantId,
					projectId: validatedProjectData.id
				}, "Skipping SpiceDB sync — no userId available");
				return project;
			});
		});
		return c.json({ data: createdProject }, 201);
	} catch (error) {
		logger.error({ error }, "Error creating project");
		throwIfUniqueConstraintError(error, `Project with ID '${projectData.id}' already exists`);
		const isGrpcError = error?.metadata !== void 0 && typeof error?.code === "number";
		if (error?.message?.includes("SpiceDB") || isGrpcError) {
			logger.error({
				error,
				tenantId,
				projectId: validatedProjectData.id,
				userId
			}, "Failed to sync project to SpiceDB — database transactions rolled back");
			throw createApiError({
				code: "internal_server_error",
				message: "Failed to set up project authorization. No changes were made to the database."
			});
		}
		throw error;
	}
});
app.openapi(createProtectedRoute({
	method: "get",
	path: "/project-full/{projectId}",
	summary: "Get Full Project",
	operationId: "get-full-project",
	tags: ["Projects"],
	description: "Retrieve a complete project definition with all Agents, Sub Agents, tools, and relationships",
	permission: requireProjectPermission("view"),
	request: { params: TenantProjectParamsSchema },
	responses: {
		200: {
			description: "Full project found",
			content: { "application/json": { schema: FullProjectSelectResponse } }
		},
		...commonGetErrorResponses
	}
}), async (c) => {
	const { tenantId, projectId } = c.req.valid("param");
	const db = c.get("db");
	try {
		const project = await getFullProject(db)({ scopes: {
			tenantId,
			projectId
		} });
		if (!project) throw createApiError({
			code: "not_found",
			message: "Project not found"
		});
		return c.json({ data: project });
	} catch (error) {
		if (error instanceof HTTPException) throw error;
		if (error instanceof Error && error.message.includes("not found")) throw createApiError({
			code: "not_found",
			message: "Project not found"
		});
		throw createApiError({
			code: "internal_server_error",
			message: "Failed to retrieve project"
		});
	}
});
app.openapi(createProtectedRoute({
	method: "get",
	path: "/project-full/{projectId}/with-relation-ids",
	summary: "Get Full Project with Relation IDs",
	operationId: "get-full-project-with-relation-ids",
	tags: ["Projects"],
	description: "Retrieve a complete project definition with all Agents, Sub Agents, tools, and relationships",
	permission: requireProjectPermission("view"),
	request: { params: TenantProjectParamsSchema },
	responses: {
		200: {
			description: "Full project found",
			content: { "application/json": { schema: FullProjectSelectWithRelationIdsResponse } }
		},
		...commonGetErrorResponses
	}
}), async (c) => {
	const { tenantId, projectId } = c.req.valid("param");
	const db = c.get("db");
	try {
		const project = await getFullProjectWithRelationIds(db)({ scopes: {
			tenantId,
			projectId
		} });
		if (!project) throw createApiError({
			code: "not_found",
			message: "Project not found"
		});
		return c.json({ data: project });
	} catch (error) {
		if (error instanceof HTTPException) throw error;
		if (error instanceof Error && error.message.includes("not found")) throw createApiError({
			code: "not_found",
			message: "Project not found"
		});
		throw createApiError({
			code: "internal_server_error",
			message: "Failed to retrieve project"
		});
	}
});
const updateFullProjectRouteConfig = {
	path: "/project-full/{projectId}",
	summary: "Update Full Project",
	tags: ["Projects"],
	description: "Update or create a complete project with all Agents, Sub Agents, tools, and relationships from JSON definition",
	permission: requireProjectUpsertPermission,
	request: {
		params: TenantProjectParamsSchema,
		body: { content: { "application/json": { schema: FullProjectDefinitionSchema } } }
	},
	responses: {
		200: {
			description: "Full project updated successfully",
			content: { "application/json": { schema: FullProjectSelectResponse } }
		},
		201: {
			description: "Full project created successfully",
			content: { "application/json": { schema: FullProjectSelectResponse } }
		},
		...commonGetErrorResponses
	}
};
const updateFullProjectHandler = async (c) => {
	const { tenantId, projectId } = c.req.valid("param");
	const projectData = c.req.valid("json");
	const configDb = c.get("db");
	const userId = c.get("userId");
	const isCreate = c.get("isProjectCreate") ?? false;
	try {
		const validatedProjectData = FullProjectDefinitionSchema.parse(projectData);
		if (projectId !== validatedProjectData.id) throw createApiError({
			code: "bad_request",
			message: `Project ID mismatch: expected ${projectId}, got ${validatedProjectData.id}`
		});
		const existingWebhookTriggersByAgent = /* @__PURE__ */ new Map();
		if (!isCreate) {
			const agents = Object.keys(validatedProjectData.agents || {});
			const webhookResults = await Promise.all(agents.map(async (agentId) => ({
				agentId,
				triggers: await listTriggers(configDb)({ scopes: {
					tenantId,
					projectId,
					agentId
				} })
			})));
			for (const { agentId, triggers } of webhookResults) existingWebhookTriggersByAgent.set(agentId, triggers);
		}
		const callerId = userId ?? "";
		const tenantRole = c.get("tenantRole");
		if (!tenantRole) throw createApiError({
			code: "unauthorized",
			message: "Missing tenant role"
		});
		for (const [agentId, agentData] of Object.entries(validatedProjectData.agents)) {
			if (!agentData.triggers) continue;
			const existingTriggers = existingWebhookTriggersByAgent.get(agentId) || [];
			const existingById = new Map(existingTriggers.map((t) => [t.id, t]));
			for (const [triggerId, triggerData] of Object.entries(agentData.triggers)) await validateTriggerPermissions({
				triggerData,
				existing: existingById.get(triggerId),
				callerId,
				tenantId,
				projectId,
				tenantRole
			});
		}
		const updatedProject = isCreate ? await withEntitlementLock(runDbClient_default, tenantId, QUOTA_RESOURCE_TYPES.PROJECT, async (limit, _tx) => {
			if (limit !== null) {
				const current = await countProjectsInRuntime(runDbClient_default)({ tenantId });
				if (current >= limit) throw createApiError({
					code: "payment_required",
					message: `Project limit reached (${current}/${limit})`,
					instance: c.req.path,
					extensions: {
						resourceType: QUOTA_RESOURCE_TYPES.PROJECT,
						current,
						limit
					}
				});
			}
			return runDbClient_default.transaction(async (runTx) => {
				return await configDb.transaction(async (configTx) => {
					await createProjectMetadataAndBranch(runTx, configTx)({
						tenantId,
						projectId,
						createdBy: userId
					});
					logger.info({
						tenantId,
						projectId
					}, "Created project with branch (upsert)");
					const projectMainBranch = getProjectMainBranchName(tenantId, projectId);
					await checkoutBranch(configTx)({
						branchName: projectMainBranch,
						autoCommitPending: true
					});
					const project = await createFullProjectServerSide(configTx)({
						scopes: {
							tenantId,
							projectId
						},
						projectData: validatedProjectData
					});
					if (userId) await syncProjectToSpiceDb({
						tenantId,
						projectId,
						creatorUserId: userId
					});
					else logger.warn({
						tenantId,
						projectId
					}, "Skipping SpiceDB sync — no userId available");
					return project;
				});
			});
		}) : await updateFullProjectServerSide(configDb)({
			scopes: {
				tenantId,
				projectId
			},
			projectData: validatedProjectData
		});
		return c.json({ data: updatedProject }, isCreate ? 201 : 200);
	} catch (error) {
		if (error instanceof HTTPException) throw error;
		if (error instanceof Error && error.message.includes("ID mismatch")) throw createApiError({
			code: "bad_request",
			message: error.message
		});
		if (isCreate) {
			const isGrpcError = error?.metadata !== void 0 && typeof error?.code === "number";
			if (error?.message?.includes("SpiceDB") || isGrpcError) {
				logger.error({
					error,
					tenantId,
					projectId,
					userId
				}, "Failed to sync project to SpiceDB — database transactions rolled back");
				throw createApiError({
					code: "internal_server_error",
					message: "Failed to set up project authorization. No changes were made to the database."
				});
			}
		}
		throw createApiError({
			code: "internal_server_error",
			message: env.ENVIRONMENT === "development" && error instanceof Error ? error.message : "Failed to update project"
		});
	}
};
openapiRegisterPutPatchRoutesForLegacy(app, updateFullProjectRouteConfig, updateFullProjectHandler, {
	operationId: "update-full-project",
	canonical: "put"
});
app.openapi(createProtectedRoute({
	method: "delete",
	path: "/project-full/{projectId}",
	summary: "Delete Full Project",
	operationId: "delete-full-project",
	tags: ["Projects"],
	description: "Delete a complete project and cascade to all related entities (Agents, Sub Agents, tools, relationships)",
	permission: requirePermission({ project: ["delete"] }),
	request: { params: TenantProjectParamsSchema },
	responses: {
		204: { description: "Project deleted successfully" },
		...commonGetErrorResponses
	}
}), async (c) => {
	const { tenantId, projectId } = c.req.valid("param");
	const configDb = c.get("db");
	const resolvedRef = c.get("resolvedRef");
	const expectedMainBranch = `${tenantId}_${projectId}_main`;
	if (resolvedRef?.name !== expectedMainBranch) throw createApiError({
		code: "bad_request",
		message: "Project deletion must be performed from the main branch"
	});
	try {
		await cascadeDeleteByProject(runDbClient_default)({ scopes: {
			tenantId,
			projectId
		} });
		await deleteFullProject(configDb)({ scopes: {
			tenantId,
			projectId
		} });
		await doltCheckout(configDb)({ branch: "main" });
		if (!await deleteProjectAndBranches(runDbClient_default, manageDbClient_default)({
			tenantId,
			projectId
		})) throw createApiError({
			code: "not_found",
			message: "Project not found"
		});
		try {
			await removeProjectFromSpiceDb({
				tenantId,
				projectId
			});
			logger.info({
				tenantId,
				projectId
			}, "Removed project from SpiceDB");
		} catch (error) {
			logger.warn({
				error,
				tenantId,
				projectId
			}, "Failed to remove project from SpiceDB - orphaned auth relationships may remain");
		}
		return c.body(null, 204);
	} catch (error) {
		if (error instanceof HTTPException) throw error;
		if (error instanceof Error && error.message.includes("not found")) throw createApiError({
			code: "not_found",
			message: "Project not found"
		});
		throw createApiError({
			code: "internal_server_error",
			message: "Failed to delete project"
		});
	}
});
var projectFull_default = app;

//#endregion
export { projectFull_default as default };