import { execFile } from "node:child_process";
import fs from "node:fs/promises";
import path from "node:path";
import { promisify } from "node:util";
import { and, desc, eq, inArray, isNull } from "drizzle-orm";
import { executionWorkspaces, issues, projects, projectWorkspaces } from "@paperclipai/db";
import { parseProjectExecutionWorkspacePolicy } from "./execution-workspace-policy.js";
import { listCurrentRuntimeServicesForExecutionWorkspaces, listCurrentRuntimeServicesForProjectWorkspaces, } from "./workspace-runtime-read-model.js";
const execFileAsync = promisify(execFile);
const TERMINAL_ISSUE_STATUSES = new Set(["done", "cancelled"]);
function isRecord(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value);
}
function readNullableString(value) {
    if (typeof value !== "string")
        return null;
    const trimmed = value.trim();
    return trimmed.length > 0 ? trimmed : null;
}
function cloneRecord(value) {
    if (!isRecord(value))
        return null;
    return { ...value };
}
function readDesiredState(value) {
    return value === "running" || value === "stopped" || value === "manual" ? value : null;
}
function readServiceStates(value) {
    if (!isRecord(value))
        return null;
    const entries = Object.entries(value).filter(([, state]) => state === "running" || state === "stopped" || state === "manual");
    return entries.length > 0
        ? Object.fromEntries(entries)
        : null;
}
async function pathExists(value) {
    if (!value)
        return false;
    try {
        await fs.access(value);
        return true;
    }
    catch {
        return false;
    }
}
async function runGit(args, cwd) {
    return await execFileAsync("git", ["-C", cwd, ...args], { cwd });
}
async function inspectGitCloseReadiness(workspace) {
    const warnings = [];
    const workspacePath = readNullableString(workspace.providerRef) ?? readNullableString(workspace.cwd);
    const createdByRuntime = workspace.metadata?.createdByRuntime === true;
    const expectsGitInspection = workspace.providerType === "git_worktree" ||
        Boolean(workspace.repoUrl || workspace.baseRef || workspace.branchName || workspacePath);
    if (!expectsGitInspection) {
        return { git: null, warnings };
    }
    if (!workspacePath) {
        warnings.push("Workspace has no local path, so Paperclip cannot inspect git status before close.");
        return { git: null, warnings };
    }
    if (!(await pathExists(workspacePath))) {
        warnings.push(`Workspace path "${workspacePath}" does not exist, so Paperclip cannot inspect git status before close.`);
        return {
            git: {
                repoRoot: null,
                workspacePath,
                branchName: workspace.branchName,
                baseRef: workspace.baseRef,
                hasDirtyTrackedFiles: false,
                hasUntrackedFiles: false,
                dirtyEntryCount: 0,
                untrackedEntryCount: 0,
                aheadCount: null,
                behindCount: null,
                isMergedIntoBase: null,
                createdByRuntime,
            },
            warnings,
        };
    }
    let repoRoot = null;
    try {
        repoRoot = (await runGit(["rev-parse", "--show-toplevel"], workspacePath)).stdout.trim() || null;
    }
    catch (error) {
        warnings.push(`Could not inspect git status for "${workspacePath}": ${error instanceof Error ? error.message : String(error)}`);
    }
    let branchName = workspace.branchName;
    if (repoRoot && !branchName) {
        try {
            branchName = (await runGit(["rev-parse", "--abbrev-ref", "HEAD"], workspacePath)).stdout.trim() || null;
        }
        catch {
            branchName = workspace.branchName;
        }
    }
    let dirtyEntryCount = 0;
    let untrackedEntryCount = 0;
    if (repoRoot) {
        try {
            const statusOutput = (await runGit(["status", "--porcelain=v1", "--untracked-files=all"], workspacePath)).stdout;
            for (const line of statusOutput.split(/\r?\n/)) {
                if (!line)
                    continue;
                if (line.startsWith("??")) {
                    untrackedEntryCount += 1;
                    continue;
                }
                dirtyEntryCount += 1;
            }
        }
        catch (error) {
            warnings.push(`Could not read git working tree status for "${workspacePath}": ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    let aheadCount = null;
    let behindCount = null;
    let isMergedIntoBase = null;
    const baseRef = workspace.baseRef;
    if (repoRoot && baseRef) {
        try {
            const counts = (await runGit(["rev-list", "--left-right", "--count", `${baseRef}...HEAD`], workspacePath)).stdout.trim();
            const [behindRaw, aheadRaw] = counts.split(/\s+/);
            behindCount = behindRaw ? Number.parseInt(behindRaw, 10) : 0;
            aheadCount = aheadRaw ? Number.parseInt(aheadRaw, 10) : 0;
        }
        catch (error) {
            warnings.push(`Could not compare this workspace against ${baseRef}: ${error instanceof Error ? error.message : String(error)}`);
        }
        try {
            await runGit(["merge-base", "--is-ancestor", "HEAD", baseRef], workspacePath);
            isMergedIntoBase = true;
        }
        catch (error) {
            const code = typeof error === "object" && error && "code" in error ? error.code : null;
            if (code === 1)
                isMergedIntoBase = false;
            else {
                warnings.push(`Could not determine whether this workspace is merged into ${baseRef}: ${error instanceof Error ? error.message : String(error)}`);
            }
        }
    }
    return {
        git: {
            repoRoot,
            workspacePath,
            branchName,
            baseRef,
            hasDirtyTrackedFiles: dirtyEntryCount > 0,
            hasUntrackedFiles: untrackedEntryCount > 0,
            dirtyEntryCount,
            untrackedEntryCount,
            aheadCount,
            behindCount,
            isMergedIntoBase,
            createdByRuntime,
        },
        warnings,
    };
}
export function readExecutionWorkspaceConfig(metadata) {
    const raw = isRecord(metadata?.config) ? metadata.config : null;
    if (!raw)
        return null;
    const config = {
        environmentId: readNullableString(raw.environmentId),
        provisionCommand: readNullableString(raw.provisionCommand),
        teardownCommand: readNullableString(raw.teardownCommand),
        cleanupCommand: readNullableString(raw.cleanupCommand),
        workspaceRuntime: cloneRecord(raw.workspaceRuntime),
        desiredState: readDesiredState(raw.desiredState),
        serviceStates: readServiceStates(raw.serviceStates),
    };
    const hasConfig = Object.values(config).some((value) => {
        if (value === null)
            return false;
        if (typeof value === "object")
            return Object.keys(value).length > 0;
        return true;
    });
    return hasConfig ? config : null;
}
export function mergeExecutionWorkspaceConfig(metadata, patch) {
    const nextMetadata = isRecord(metadata) ? { ...metadata } : {};
    const current = readExecutionWorkspaceConfig(metadata) ?? {
        environmentId: null,
        provisionCommand: null,
        teardownCommand: null,
        cleanupCommand: null,
        workspaceRuntime: null,
        desiredState: null,
        serviceStates: null,
    };
    if (patch === null) {
        delete nextMetadata.config;
        return Object.keys(nextMetadata).length > 0 ? nextMetadata : null;
    }
    const nextConfig = {
        environmentId: patch.environmentId !== undefined ? readNullableString(patch.environmentId) : current.environmentId,
        provisionCommand: patch.provisionCommand !== undefined ? readNullableString(patch.provisionCommand) : current.provisionCommand,
        teardownCommand: patch.teardownCommand !== undefined ? readNullableString(patch.teardownCommand) : current.teardownCommand,
        cleanupCommand: patch.cleanupCommand !== undefined ? readNullableString(patch.cleanupCommand) : current.cleanupCommand,
        workspaceRuntime: patch.workspaceRuntime !== undefined ? cloneRecord(patch.workspaceRuntime) : current.workspaceRuntime,
        desiredState: patch.desiredState !== undefined
            ? readDesiredState(patch.desiredState)
            : current.desiredState,
        serviceStates: patch.serviceStates !== undefined ? readServiceStates(patch.serviceStates) : current.serviceStates,
    };
    const hasConfig = Object.values(nextConfig).some((value) => {
        if (value === null)
            return false;
        if (typeof value === "object")
            return Object.keys(value).length > 0;
        return true;
    });
    if (hasConfig) {
        nextMetadata.config = {
            environmentId: nextConfig.environmentId,
            provisionCommand: nextConfig.provisionCommand,
            teardownCommand: nextConfig.teardownCommand,
            cleanupCommand: nextConfig.cleanupCommand,
            workspaceRuntime: nextConfig.workspaceRuntime,
            desiredState: nextConfig.desiredState,
            serviceStates: nextConfig.serviceStates ?? null,
        };
    }
    else {
        delete nextMetadata.config;
    }
    return Object.keys(nextMetadata).length > 0 ? nextMetadata : null;
}
function toRuntimeService(row) {
    return {
        id: row.id,
        companyId: row.companyId,
        projectId: row.projectId ?? null,
        projectWorkspaceId: row.projectWorkspaceId ?? null,
        executionWorkspaceId: row.executionWorkspaceId ?? null,
        issueId: row.issueId ?? null,
        scopeType: row.scopeType,
        scopeId: row.scopeId ?? null,
        serviceName: row.serviceName,
        status: row.status,
        lifecycle: row.lifecycle,
        reuseKey: row.reuseKey ?? null,
        command: row.command ?? null,
        cwd: row.cwd ?? null,
        port: row.port ?? null,
        url: row.url ?? null,
        provider: row.provider,
        providerRef: row.providerRef ?? null,
        ownerAgentId: row.ownerAgentId ?? null,
        startedByRunId: row.startedByRunId ?? null,
        lastUsedAt: row.lastUsedAt,
        startedAt: row.startedAt,
        stoppedAt: row.stoppedAt ?? null,
        stopPolicy: row.stopPolicy ?? null,
        healthStatus: row.healthStatus,
        createdAt: row.createdAt,
        updatedAt: row.updatedAt,
    };
}
function toExecutionWorkspace(row, runtimeServices = []) {
    return {
        id: row.id,
        companyId: row.companyId,
        projectId: row.projectId,
        projectWorkspaceId: row.projectWorkspaceId ?? null,
        sourceIssueId: row.sourceIssueId ?? null,
        mode: row.mode,
        strategyType: row.strategyType,
        name: row.name,
        status: row.status,
        cwd: row.cwd ?? null,
        repoUrl: row.repoUrl ?? null,
        baseRef: row.baseRef ?? null,
        branchName: row.branchName ?? null,
        providerType: row.providerType,
        providerRef: row.providerRef ?? null,
        derivedFromExecutionWorkspaceId: row.derivedFromExecutionWorkspaceId ?? null,
        lastUsedAt: row.lastUsedAt,
        openedAt: row.openedAt,
        closedAt: row.closedAt ?? null,
        cleanupEligibleAt: row.cleanupEligibleAt ?? null,
        cleanupReason: row.cleanupReason ?? null,
        config: readExecutionWorkspaceConfig(row.metadata ?? null),
        metadata: row.metadata ?? null,
        runtimeServices,
        createdAt: row.createdAt,
        updatedAt: row.updatedAt,
    };
}
function toExecutionWorkspaceSummary(row) {
    return {
        id: row.id,
        name: row.name,
        mode: row.mode,
        status: row.status,
        cwd: row.cwd ?? null,
        branchName: row.branchName ?? null,
        projectWorkspaceId: row.projectWorkspaceId ?? null,
        lastUsedAt: row.lastUsedAt,
    };
}
function usesInheritedProjectRuntimeServices(row) {
    if (row.mode !== "shared_workspace" || !row.projectWorkspaceId)
        return false;
    return !readExecutionWorkspaceConfig(row.metadata ?? null)?.workspaceRuntime;
}
async function loadEffectiveRuntimeServicesByExecutionWorkspace(db, companyId, rows) {
    const executionRuntimeServices = await listCurrentRuntimeServicesForExecutionWorkspaces(db, companyId, rows.map((row) => row.id));
    const projectWorkspaceIds = rows
        .filter((row) => usesInheritedProjectRuntimeServices(row))
        .map((row) => row.projectWorkspaceId)
        .filter((value) => Boolean(value));
    const projectRuntimeServices = await listCurrentRuntimeServicesForProjectWorkspaces(db, companyId, [...new Set(projectWorkspaceIds)]);
    return new Map(rows.map((row) => [
        row.id,
        usesInheritedProjectRuntimeServices(row)
            ? (projectRuntimeServices.get(row.projectWorkspaceId) ?? [])
            : (executionRuntimeServices.get(row.id) ?? []),
    ]));
}
export function executionWorkspaceService(db) {
    function buildListConditions(companyId, filters) {
        const conditions = [eq(executionWorkspaces.companyId, companyId)];
        if (filters?.projectId)
            conditions.push(eq(executionWorkspaces.projectId, filters.projectId));
        if (filters?.projectWorkspaceId) {
            conditions.push(eq(executionWorkspaces.projectWorkspaceId, filters.projectWorkspaceId));
        }
        if (filters?.issueId)
            conditions.push(eq(executionWorkspaces.sourceIssueId, filters.issueId));
        if (filters?.status) {
            const statuses = filters.status.split(",").map((value) => value.trim()).filter(Boolean);
            if (statuses.length === 1)
                conditions.push(eq(executionWorkspaces.status, statuses[0]));
            else if (statuses.length > 1)
                conditions.push(inArray(executionWorkspaces.status, statuses));
        }
        if (filters?.reuseEligible) {
            conditions.push(inArray(executionWorkspaces.status, ["active", "idle", "in_review"]));
            conditions.push(isNull(executionWorkspaces.closedAt));
            conditions.push(inArray(executionWorkspaces.mode, ["isolated_workspace", "operator_branch", "adapter_managed", "cloud_sandbox"]));
        }
        return conditions;
    }
    return {
        list: async (companyId, filters) => {
            const conditions = buildListConditions(companyId, filters);
            const rows = await db
                .select()
                .from(executionWorkspaces)
                .where(and(...conditions))
                .orderBy(desc(executionWorkspaces.lastUsedAt), desc(executionWorkspaces.createdAt));
            const runtimeServicesByWorkspaceId = await loadEffectiveRuntimeServicesByExecutionWorkspace(db, companyId, rows);
            return rows.map((row) => toExecutionWorkspace(row, (runtimeServicesByWorkspaceId.get(row.id) ?? []).map(toRuntimeService)));
        },
        listSummaries: async (companyId, filters) => {
            const conditions = buildListConditions(companyId, filters);
            const rows = await db
                .select({
                id: executionWorkspaces.id,
                name: executionWorkspaces.name,
                mode: executionWorkspaces.mode,
                status: executionWorkspaces.status,
                cwd: executionWorkspaces.cwd,
                branchName: executionWorkspaces.branchName,
                projectWorkspaceId: executionWorkspaces.projectWorkspaceId,
                lastUsedAt: executionWorkspaces.lastUsedAt,
            })
                .from(executionWorkspaces)
                .where(and(...conditions))
                .orderBy(desc(executionWorkspaces.lastUsedAt), desc(executionWorkspaces.createdAt));
            return rows.map((row) => toExecutionWorkspaceSummary(row));
        },
        getById: async (id) => {
            const row = await db
                .select()
                .from(executionWorkspaces)
                .where(eq(executionWorkspaces.id, id))
                .then((rows) => rows[0] ?? null);
            if (!row)
                return null;
            const runtimeServicesByWorkspaceId = await loadEffectiveRuntimeServicesByExecutionWorkspace(db, row.companyId, [row]);
            return toExecutionWorkspace(row, (runtimeServicesByWorkspaceId.get(row.id) ?? []).map(toRuntimeService));
        },
        getCloseReadiness: async (id) => {
            const workspace = await db
                .select()
                .from(executionWorkspaces)
                .where(eq(executionWorkspaces.id, id))
                .then((rows) => rows[0] ?? null);
            if (!workspace)
                return null;
            const runtimeServicesByWorkspaceId = await loadEffectiveRuntimeServicesByExecutionWorkspace(db, workspace.companyId, [workspace]);
            const runtimeServices = (runtimeServicesByWorkspaceId.get(workspace.id) ?? []).map(toRuntimeService);
            const linkedIssues = await db
                .select({
                id: issues.id,
                identifier: issues.identifier,
                title: issues.title,
                status: issues.status,
            })
                .from(issues)
                .where(and(eq(issues.companyId, workspace.companyId), eq(issues.executionWorkspaceId, workspace.id)));
            const projectWorkspace = workspace.projectWorkspaceId
                ? await db
                    .select({
                    id: projectWorkspaces.id,
                    cwd: projectWorkspaces.cwd,
                    cleanupCommand: projectWorkspaces.cleanupCommand,
                    isPrimary: projectWorkspaces.isPrimary,
                })
                    .from(projectWorkspaces)
                    .where(and(eq(projectWorkspaces.companyId, workspace.companyId), eq(projectWorkspaces.id, workspace.projectWorkspaceId)))
                    .then((rows) => rows[0] ?? null)
                : null;
            const primaryProjectWorkspace = workspace.projectId
                ? await db
                    .select({
                    id: projectWorkspaces.id,
                })
                    .from(projectWorkspaces)
                    .where(and(eq(projectWorkspaces.companyId, workspace.companyId), eq(projectWorkspaces.projectId, workspace.projectId), eq(projectWorkspaces.isPrimary, true)))
                    .then((rows) => rows[0] ?? null)
                : null;
            const projectPolicy = workspace.projectId
                ? await db
                    .select({
                    executionWorkspacePolicy: projects.executionWorkspacePolicy,
                })
                    .from(projects)
                    .where(and(eq(projects.id, workspace.projectId), eq(projects.companyId, workspace.companyId)))
                    .then((rows) => parseProjectExecutionWorkspacePolicy(rows[0]?.executionWorkspacePolicy))
                : null;
            const executionWorkspace = toExecutionWorkspace(workspace, runtimeServices);
            const config = readExecutionWorkspaceConfig(workspace.metadata ?? null);
            const { git, warnings: gitWarnings } = await inspectGitCloseReadiness(executionWorkspace);
            const warnings = [...gitWarnings];
            const blockingReasons = [];
            const isSharedWorkspace = executionWorkspace.mode === "shared_workspace";
            const workspacePath = readNullableString(executionWorkspace.providerRef) ?? readNullableString(executionWorkspace.cwd);
            const resolvedWorkspacePath = workspacePath ? path.resolve(workspacePath) : null;
            const resolvedPrimaryWorkspacePath = projectWorkspace?.cwd ? path.resolve(projectWorkspace.cwd) : null;
            const isProjectPrimaryWorkspace = workspace.projectWorkspaceId != null
                && workspace.projectWorkspaceId === primaryProjectWorkspace?.id
                && resolvedWorkspacePath != null
                && resolvedPrimaryWorkspacePath != null
                && resolvedWorkspacePath === resolvedPrimaryWorkspacePath;
            const linkedIssueSummaries = linkedIssues.map((issue) => ({
                ...issue,
                isTerminal: TERMINAL_ISSUE_STATUSES.has(issue.status),
            }));
            const blockingIssues = linkedIssueSummaries.filter((issue) => !issue.isTerminal);
            if (blockingIssues.length > 0) {
                const linkedIssueMessage = blockingIssues.length === 1
                    ? "This workspace is still linked to an open issue."
                    : `This workspace is still linked to ${blockingIssues.length} open issues.`;
                if (isSharedWorkspace) {
                    warnings.push(`${linkedIssueMessage} Archiving it will detach this shared workspace session from those issues, but keep the underlying project workspace available.`);
                }
                else {
                    blockingReasons.push(linkedIssueMessage);
                }
            }
            if (isSharedWorkspace) {
                warnings.push("This shared workspace session points at project workspace infrastructure. Archiving it only removes the session record.");
            }
            if (runtimeServices.some((service) => service.status !== "stopped")) {
                warnings.push(runtimeServices.length === 1
                    ? "Closing this workspace will stop 1 attached runtime service."
                    : `Closing this workspace will stop ${runtimeServices.length} attached runtime services.`);
            }
            if (git?.hasDirtyTrackedFiles) {
                warnings.push(git.dirtyEntryCount === 1
                    ? "The workspace has 1 modified tracked file."
                    : `The workspace has ${git.dirtyEntryCount} modified tracked files.`);
            }
            if (git?.hasUntrackedFiles) {
                warnings.push(git.untrackedEntryCount === 1
                    ? "The workspace has 1 untracked file."
                    : `The workspace has ${git.untrackedEntryCount} untracked files.`);
            }
            if (git?.aheadCount && git.aheadCount > 0 && git.isMergedIntoBase === false) {
                warnings.push(git.aheadCount === 1
                    ? `This workspace is 1 commit ahead of ${git.baseRef ?? "the base ref"} and is not merged.`
                    : `This workspace is ${git.aheadCount} commits ahead of ${git.baseRef ?? "the base ref"} and is not merged.`);
            }
            if (git?.behindCount && git.behindCount > 0) {
                warnings.push(git.behindCount === 1
                    ? `This workspace is 1 commit behind ${git.baseRef ?? "the base ref"}.`
                    : `This workspace is ${git.behindCount} commits behind ${git.baseRef ?? "the base ref"}.`);
            }
            const plannedActions = [
                {
                    kind: "archive_record",
                    label: "Archive workspace record",
                    description: "Keep the execution workspace history and issue linkage, but remove it from active workspace lists.",
                    command: null,
                },
            ];
            if (runtimeServices.some((service) => service.status !== "stopped")) {
                plannedActions.push({
                    kind: "stop_runtime_services",
                    label: runtimeServices.length === 1 ? "Stop attached runtime service" : "Stop attached runtime services",
                    description: runtimeServices.length === 1
                        ? `${runtimeServices[0]?.serviceName ?? "A runtime service"} will be stopped before cleanup.`
                        : `${runtimeServices.length} runtime services will be stopped before cleanup.`,
                    command: null,
                });
            }
            const configuredCleanupCommands = [
                {
                    kind: "cleanup_command",
                    label: "Run workspace cleanup command",
                    description: "Workspace-specific cleanup runs before teardown.",
                    command: config?.cleanupCommand ?? null,
                },
                {
                    kind: "cleanup_command",
                    label: "Run project workspace cleanup command",
                    description: "Project workspace cleanup runs before execution workspace teardown.",
                    command: projectWorkspace?.cleanupCommand ?? null,
                },
            ];
            for (const action of configuredCleanupCommands) {
                if (!action.command)
                    continue;
                plannedActions.push(action);
            }
            const teardownCommand = config?.teardownCommand ?? projectPolicy?.workspaceStrategy?.teardownCommand ?? null;
            if (teardownCommand) {
                plannedActions.push({
                    kind: "teardown_command",
                    label: "Run teardown command",
                    description: "Teardown runs after cleanup commands during workspace close.",
                    command: teardownCommand,
                });
            }
            if (executionWorkspace.providerType === "git_worktree" && workspacePath) {
                plannedActions.push({
                    kind: "git_worktree_remove",
                    label: "Remove git worktree",
                    description: `Paperclip will run git worktree cleanup for ${workspacePath}.`,
                    command: `git worktree remove --force ${workspacePath}`,
                });
            }
            if (git?.createdByRuntime && executionWorkspace.branchName) {
                plannedActions.push({
                    kind: "git_branch_delete",
                    label: "Delete runtime-created branch",
                    description: "Paperclip will try to delete the runtime-created branch after removing the worktree.",
                    command: `git branch -d ${executionWorkspace.branchName}`,
                });
            }
            if (executionWorkspace.providerType === "local_fs" && git?.createdByRuntime && workspacePath) {
                const resolvedWorkspacePath = path.resolve(workspacePath);
                const resolvedProjectWorkspacePath = projectWorkspace?.cwd ? path.resolve(projectWorkspace.cwd) : null;
                const containsProjectWorkspace = resolvedProjectWorkspacePath
                    ? (resolvedWorkspacePath === resolvedProjectWorkspacePath ||
                        resolvedProjectWorkspacePath.startsWith(`${resolvedWorkspacePath}${path.sep}`))
                    : false;
                if (containsProjectWorkspace) {
                    warnings.push(`Paperclip will archive this workspace but keep "${workspacePath}" because it contains the project workspace.`);
                }
                else {
                    plannedActions.push({
                        kind: "remove_local_directory",
                        label: "Remove runtime-created directory",
                        description: `Paperclip will remove the runtime-created directory at ${workspacePath}.`,
                        command: `rm -rf ${workspacePath}`,
                    });
                }
            }
            const state = blockingReasons.length > 0
                ? "blocked"
                : warnings.length > 0
                    ? "ready_with_warnings"
                    : "ready";
            return {
                workspaceId: workspace.id,
                state,
                blockingReasons,
                warnings,
                linkedIssues: linkedIssueSummaries,
                plannedActions,
                isDestructiveCloseAllowed: blockingReasons.length === 0,
                isSharedWorkspace,
                isProjectPrimaryWorkspace,
                git,
                runtimeServices,
            };
        },
        create: async (data) => {
            const row = await db
                .insert(executionWorkspaces)
                .values(data)
                .returning()
                .then((rows) => rows[0] ?? null);
            return row ? toExecutionWorkspace(row) : null;
        },
        update: async (id, patch) => {
            const row = await db
                .update(executionWorkspaces)
                .set({ ...patch, updatedAt: new Date() })
                .where(eq(executionWorkspaces.id, id))
                .returning()
                .then((rows) => rows[0] ?? null);
            return row ? toExecutionWorkspace(row) : null;
        },
        clearEnvironmentSelection: async (companyId, environmentId) => {
            return db.transaction(async (tx) => {
                const rows = await tx
                    .select({
                    id: executionWorkspaces.id,
                    metadata: executionWorkspaces.metadata,
                })
                    .from(executionWorkspaces)
                    .where(eq(executionWorkspaces.companyId, companyId));
                let cleared = 0;
                const updatedAt = new Date();
                for (const row of rows) {
                    const metadata = row.metadata ?? null;
                    const config = readExecutionWorkspaceConfig(metadata);
                    if (config?.environmentId !== environmentId)
                        continue;
                    await tx
                        .update(executionWorkspaces)
                        .set({
                        metadata: mergeExecutionWorkspaceConfig(metadata, { environmentId: null }),
                        updatedAt,
                    })
                        .where(eq(executionWorkspaces.id, row.id));
                    cleared += 1;
                }
                return cleared;
            });
        },
    };
}
export { toExecutionWorkspace };
//# sourceMappingURL=execution-workspaces.js.map