import { activityLog, agents, approvals, automations, calendarEvents, calendarSources, goals, heartbeatRuns, issues, projects, } from "@rudderhq/db";
import { SECRET_PROVIDERS } from "@rudderhq/shared";
import { and, asc, desc, eq, gt, inArray, isNull, lt, sql } from "drizzle-orm";
import { asBoolean, asNumber, parseObject } from "../agent-runtimes/utils.js";
import { conflict, forbidden, notFound, unprocessable } from "../errors.js";
import { secretService } from "./secrets.js";
const PROJECTED_HEARTBEAT_DURATION_MS = 15 * 60 * 1000;
const PROJECTED_HEARTBEAT_MAX_PER_AGENT = 96;
const GOOGLE_CALENDAR_OAUTH_SECRET_NAME = "google_calendar_oauth_credentials";
const GOOGLE_CALENDAR_REQUIRED_ENV = ["GOOGLE_CALENDAR_CLIENT_ID", "GOOGLE_CALENDAR_CLIENT_SECRET"];
const GOOGLE_CALENDAR_ACCEPTED_ENV_ALIASES = ["GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET"];
function sanitizeSource(row) {
    const rawCursor = row.syncCursorJson ?? null;
    const cursor = rawCursor && typeof rawCursor === "object"
        ? {
            ...rawCursor,
            accessToken: typeof rawCursor.accessToken === "string" ? "[redacted]" : undefined,
            refreshToken: typeof rawCursor.refreshToken === "string" ? "[redacted]" : undefined,
        }
        : null;
    return {
        ...row,
        type: row.type,
        ownerType: row.ownerType,
        visibilityDefault: row.visibilityDefault,
        status: row.status,
        syncCursorJson: cursor,
    };
}
function csvIncludes(filters, value) {
    return !filters || filters.length === 0 || filters.includes(value);
}
function parseSyncCursor(value) {
    if (!value)
        return {};
    return value;
}
function envGoogleCredentials() {
    const clientId = process.env.GOOGLE_CALENDAR_CLIENT_ID?.trim() || process.env.GOOGLE_CLIENT_ID?.trim() || "";
    const clientSecret = process.env.GOOGLE_CALENDAR_CLIENT_SECRET?.trim() || process.env.GOOGLE_CLIENT_SECRET?.trim() || "";
    return clientId && clientSecret ? { clientId, clientSecret, managedByEnv: true } : null;
}
function parseStoredGoogleCredentials(value) {
    try {
        const parsed = JSON.parse(value);
        const clientId = typeof parsed.clientId === "string" ? parsed.clientId.trim() : "";
        const clientSecret = typeof parsed.clientSecret === "string" ? parsed.clientSecret.trim() : "";
        return clientId && clientSecret ? { clientId, clientSecret } : null;
    }
    catch {
        return null;
    }
}
function serializeGoogleCredentials(credentials) {
    return JSON.stringify({
        clientId: credentials.clientId,
        clientSecret: credentials.clientSecret,
    });
}
function defaultSecretProvider() {
    const configured = process.env.RUDDER_SECRETS_PROVIDER;
    return (configured && SECRET_PROVIDERS.includes(configured)
        ? configured
        : "local_encrypted");
}
function googleCalendarIdForListItem(item) {
    return item.primary ? "primary" : item.id?.trim() || null;
}
function sourceHasGoogleToken(source) {
    const cursor = parseSyncCursor(source?.syncCursorJson);
    return typeof cursor.accessToken === "string" || typeof cursor.refreshToken === "string";
}
function parseHeartbeatPolicy(agent) {
    const runtimeConfig = parseObject(agent.runtimeConfig);
    const heartbeat = parseObject(runtimeConfig.heartbeat);
    return {
        enabled: asBoolean(heartbeat.enabled, true),
        intervalSec: Math.max(0, asNumber(heartbeat.intervalSec, 0)),
    };
}
function eventSummary(event) {
    return {
        title: event.title,
        eventKind: event.eventKind,
        eventStatus: event.eventStatus,
        startAt: event.startAt.toISOString(),
        endAt: event.endAt.toISOString(),
        ownerAgentId: event.ownerAgentId,
        issueId: event.issueId,
    };
}
function createEventValues(orgId, input, actor) {
    return {
        orgId,
        sourceId: input.sourceId ?? null,
        eventKind: input.eventKind,
        eventStatus: input.eventStatus,
        ownerType: input.ownerType,
        ownerUserId: input.ownerUserId ?? null,
        ownerAgentId: input.ownerAgentId ?? null,
        title: input.title,
        description: input.description ?? null,
        startAt: input.startAt,
        endAt: input.endAt,
        timezone: input.timezone,
        allDay: input.allDay,
        visibility: input.visibility,
        issueId: input.issueId ?? null,
        projectId: input.projectId ?? null,
        goalId: input.goalId ?? null,
        approvalId: input.approvalId ?? null,
        heartbeatRunId: input.heartbeatRunId ?? null,
        activityId: input.activityId ?? null,
        sourceMode: input.sourceMode,
        externalProvider: input.externalProvider ?? null,
        externalCalendarId: input.externalCalendarId ?? null,
        externalEventId: input.externalEventId ?? null,
        externalEtag: input.externalEtag ?? null,
        externalUpdatedAt: input.externalUpdatedAt ?? null,
        createdByUserId: actor?.userId ?? null,
        updatedByUserId: actor?.userId ?? null,
    };
}
function updateEventValues(input, actor) {
    const values = {
        updatedByUserId: actor?.userId ?? null,
    };
    if (input.sourceId !== undefined)
        values.sourceId = input.sourceId ?? null;
    if (input.eventKind !== undefined)
        values.eventKind = input.eventKind;
    if (input.eventStatus !== undefined)
        values.eventStatus = input.eventStatus;
    if (input.ownerType !== undefined)
        values.ownerType = input.ownerType;
    if (input.ownerUserId !== undefined)
        values.ownerUserId = input.ownerUserId ?? null;
    if (input.ownerAgentId !== undefined)
        values.ownerAgentId = input.ownerAgentId ?? null;
    if (input.title !== undefined)
        values.title = input.title;
    if (input.description !== undefined)
        values.description = input.description ?? null;
    if (input.startAt !== undefined)
        values.startAt = input.startAt;
    if (input.endAt !== undefined)
        values.endAt = input.endAt;
    if (input.timezone !== undefined)
        values.timezone = input.timezone;
    if (input.allDay !== undefined)
        values.allDay = input.allDay;
    if (input.visibility !== undefined)
        values.visibility = input.visibility;
    if (input.issueId !== undefined)
        values.issueId = input.issueId ?? null;
    if (input.projectId !== undefined)
        values.projectId = input.projectId ?? null;
    if (input.goalId !== undefined)
        values.goalId = input.goalId ?? null;
    if (input.approvalId !== undefined)
        values.approvalId = input.approvalId ?? null;
    if (input.heartbeatRunId !== undefined)
        values.heartbeatRunId = input.heartbeatRunId ?? null;
    if (input.activityId !== undefined)
        values.activityId = input.activityId ?? null;
    if (input.sourceMode !== undefined)
        values.sourceMode = input.sourceMode;
    if (input.externalProvider !== undefined)
        values.externalProvider = input.externalProvider ?? null;
    if (input.externalCalendarId !== undefined)
        values.externalCalendarId = input.externalCalendarId ?? null;
    if (input.externalEventId !== undefined)
        values.externalEventId = input.externalEventId ?? null;
    if (input.externalEtag !== undefined)
        values.externalEtag = input.externalEtag ?? null;
    if (input.externalUpdatedAt !== undefined)
        values.externalUpdatedAt = input.externalUpdatedAt ?? null;
    return values;
}
function mergeEventInput(existing, input) {
    return {
        sourceId: input.sourceId === undefined ? existing.sourceId : input.sourceId,
        eventKind: (input.eventKind ?? existing.eventKind),
        eventStatus: (input.eventStatus ?? existing.eventStatus),
        ownerType: (input.ownerType ?? existing.ownerType),
        ownerUserId: input.ownerUserId === undefined ? existing.ownerUserId : input.ownerUserId,
        ownerAgentId: input.ownerAgentId === undefined ? existing.ownerAgentId : input.ownerAgentId,
        title: input.title ?? existing.title,
        description: input.description === undefined ? existing.description : input.description,
        startAt: input.startAt ?? existing.startAt,
        endAt: input.endAt ?? existing.endAt,
        timezone: input.timezone ?? existing.timezone,
        allDay: input.allDay ?? existing.allDay,
        visibility: (input.visibility ?? existing.visibility),
        issueId: input.issueId === undefined ? existing.issueId : input.issueId,
        projectId: input.projectId === undefined ? existing.projectId : input.projectId,
        goalId: input.goalId === undefined ? existing.goalId : input.goalId,
        approvalId: input.approvalId === undefined ? existing.approvalId : input.approvalId,
        heartbeatRunId: input.heartbeatRunId === undefined ? existing.heartbeatRunId : input.heartbeatRunId,
        activityId: input.activityId === undefined ? existing.activityId : input.activityId,
        sourceMode: (input.sourceMode ?? existing.sourceMode),
        externalProvider: input.externalProvider === undefined ? existing.externalProvider : input.externalProvider,
        externalCalendarId: input.externalCalendarId === undefined ? existing.externalCalendarId : input.externalCalendarId,
        externalEventId: input.externalEventId === undefined ? existing.externalEventId : input.externalEventId,
        externalEtag: input.externalEtag === undefined ? existing.externalEtag : input.externalEtag,
        externalUpdatedAt: input.externalUpdatedAt === undefined ? existing.externalUpdatedAt : input.externalUpdatedAt,
    };
}
export function calendarService(db) {
    const issueIdAsText = sql `${issues.id}::text`;
    const contextIssueId = sql `${heartbeatRuns.contextSnapshot} ->> 'issueId'`;
    const secrets = secretService(db);
    async function storedGoogleCredentials(orgId) {
        const secret = await secrets.getByName(orgId, GOOGLE_CALENDAR_OAUTH_SECRET_NAME);
        if (!secret)
            return null;
        const value = await secrets.resolveSecretValue(orgId, secret.id, "latest");
        const credentials = parseStoredGoogleCredentials(value);
        return credentials ? { ...credentials, managedByEnv: false } : null;
    }
    async function googleCredentials(orgId) {
        return envGoogleCredentials() ?? await storedGoogleCredentials(orgId);
    }
    async function googleOAuthConfig(orgId, redirectUri) {
        const credentials = await googleCredentials(orgId);
        return {
            clientId: credentials?.clientId ?? "",
            clientSecretConfigured: !!credentials,
            managedByEnv: credentials?.managedByEnv ?? false,
            redirectUri,
            requiredEnv: [...GOOGLE_CALENDAR_REQUIRED_ENV],
            acceptedAliases: [...GOOGLE_CALENDAR_ACCEPTED_ENV_ALIASES],
        };
    }
    async function updateGoogleOAuthConfig(orgId, input, redirectUri, actor) {
        if (envGoogleCredentials()) {
            throw unprocessable("Google Calendar OAuth is managed by server environment variables");
        }
        const existingSecret = await secrets.getByName(orgId, GOOGLE_CALENDAR_OAUTH_SECRET_NAME);
        if (input.clear) {
            if (existingSecret)
                await secrets.remove(existingSecret.id);
            return googleOAuthConfig(orgId, redirectUri);
        }
        const existingCredentials = existingSecret
            ? parseStoredGoogleCredentials(await secrets.resolveSecretValue(orgId, existingSecret.id, "latest"))
            : null;
        const clientId = input.clientId?.trim() || existingCredentials?.clientId || "";
        const clientSecret = input.clientSecret?.trim() || existingCredentials?.clientSecret || "";
        if (!clientId)
            throw unprocessable("Google Calendar client ID is required");
        if (!clientSecret)
            throw unprocessable("Google Calendar client secret is required");
        const value = serializeGoogleCredentials({ clientId, clientSecret });
        if (existingSecret) {
            await secrets.rotate(existingSecret.id, { value }, { userId: actor?.userId ?? "board", agentId: null });
        }
        else {
            await secrets.create(orgId, {
                name: GOOGLE_CALENDAR_OAUTH_SECRET_NAME,
                provider: defaultSecretProvider(),
                value,
                description: "Google Calendar OAuth client credentials used for read-only calendar import.",
            }, { userId: actor?.userId ?? "board", agentId: null });
        }
        return googleOAuthConfig(orgId, redirectUri);
    }
    async function assertSourceOrg(orgId, sourceId) {
        if (!sourceId)
            return null;
        const source = await db
            .select()
            .from(calendarSources)
            .where(and(eq(calendarSources.id, sourceId), eq(calendarSources.orgId, orgId)))
            .then((rows) => rows[0] ?? null);
        if (!source)
            throw notFound("Calendar source not found");
        return source;
    }
    async function assertAgentOrg(orgId, agentId) {
        if (!agentId)
            return null;
        const agent = await db
            .select({ id: agents.id, orgId: agents.orgId, status: agents.status })
            .from(agents)
            .where(eq(agents.id, agentId))
            .then((rows) => rows[0] ?? null);
        if (!agent)
            throw notFound("Agent not found");
        if (agent.orgId !== orgId)
            throw unprocessable("Agent must belong to same organization");
        if (agent.status === "terminated")
            throw conflict("Cannot create calendar blocks for terminated agents");
        return agent;
    }
    async function assertIssueOrg(orgId, issueId) {
        if (!issueId)
            return null;
        const issue = await db
            .select({ id: issues.id, orgId: issues.orgId, hiddenAt: issues.hiddenAt })
            .from(issues)
            .where(eq(issues.id, issueId))
            .then((rows) => rows[0] ?? null);
        if (!issue || issue.hiddenAt)
            throw notFound("Issue not found");
        if (issue.orgId !== orgId)
            throw unprocessable("Issue must belong to same organization");
        return issue;
    }
    async function assertProjectOrg(orgId, projectId) {
        if (!projectId)
            return null;
        const project = await db
            .select({ id: projects.id, orgId: projects.orgId })
            .from(projects)
            .where(eq(projects.id, projectId))
            .then((rows) => rows[0] ?? null);
        if (!project)
            throw notFound("Project not found");
        if (project.orgId !== orgId)
            throw unprocessable("Project must belong to same organization");
        return project;
    }
    async function assertGoalOrg(orgId, goalId) {
        if (!goalId)
            return null;
        const goal = await db
            .select({ id: goals.id, orgId: goals.orgId })
            .from(goals)
            .where(eq(goals.id, goalId))
            .then((rows) => rows[0] ?? null);
        if (!goal)
            throw notFound("Goal not found");
        if (goal.orgId !== orgId)
            throw unprocessable("Goal must belong to same organization");
        return goal;
    }
    async function assertApprovalOrg(orgId, approvalId) {
        if (!approvalId)
            return null;
        const approval = await db
            .select({ id: approvals.id, orgId: approvals.orgId })
            .from(approvals)
            .where(eq(approvals.id, approvalId))
            .then((rows) => rows[0] ?? null);
        if (!approval)
            throw notFound("Approval not found");
        if (approval.orgId !== orgId)
            throw unprocessable("Approval must belong to same organization");
        return approval;
    }
    async function assertRunOrg(orgId, runId) {
        if (!runId)
            return null;
        const run = await db
            .select({ id: heartbeatRuns.id, orgId: heartbeatRuns.orgId })
            .from(heartbeatRuns)
            .where(eq(heartbeatRuns.id, runId))
            .then((rows) => rows[0] ?? null);
        if (!run)
            throw notFound("Agent run not found");
        if (run.orgId !== orgId)
            throw unprocessable("Agent run must belong to same organization");
        return run;
    }
    async function assertActivityOrg(orgId, activityId) {
        if (!activityId)
            return null;
        const activity = await db
            .select({ id: activityLog.id, orgId: activityLog.orgId })
            .from(activityLog)
            .where(eq(activityLog.id, activityId))
            .then((rows) => rows[0] ?? null);
        if (!activity)
            throw notFound("Activity event not found");
        if (activity.orgId !== orgId)
            throw unprocessable("Activity event must belong to same organization");
        return activity;
    }
    async function assertCalendarEventShape(orgId, input) {
        if (input.eventKind === "agent_work_block") {
            if (input.ownerAgentId === null)
                throw unprocessable("Agent work blocks require an agent");
            if (input.ownerType && input.ownerType !== "agent")
                throw unprocessable("Agent work blocks must be owned by an agent");
        }
        if (input.eventKind === "human_event" && input.ownerType && input.ownerType !== "user") {
            throw unprocessable("Human calendar events must be owned by a user");
        }
        if (input.sourceMode && input.sourceMode !== "manual" && input.sourceMode !== "imported") {
            throw forbidden("Derived calendar events are read-only");
        }
        await Promise.all([
            assertSourceOrg(orgId, input.sourceId),
            assertAgentOrg(orgId, input.ownerAgentId),
            assertIssueOrg(orgId, input.issueId),
            assertProjectOrg(orgId, input.projectId),
            assertGoalOrg(orgId, input.goalId),
            assertApprovalOrg(orgId, input.approvalId),
            assertRunOrg(orgId, input.heartbeatRunId),
            assertActivityOrg(orgId, input.activityId),
        ]);
    }
    function mapPersistedEvent(row) {
        return {
            ...row.event,
            eventKind: row.event.eventKind,
            eventStatus: row.event.eventStatus,
            ownerType: row.event.ownerType,
            visibility: row.event.visibility,
            sourceMode: row.event.sourceMode,
            source: row.sourceId
                ? {
                    id: row.sourceId,
                    type: row.sourceType,
                    name: row.sourceName ?? "Calendar",
                    visibilityDefault: (row.sourceVisibilityDefault ?? "full"),
                    externalProvider: row.sourceExternalProvider,
                }
                : null,
            agent: row.event.ownerAgentId && row.agentName
                ? {
                    id: row.event.ownerAgentId,
                    name: row.agentName,
                    role: row.agentRole ?? "general",
                    title: row.agentTitle,
                    urlKey: row.agentUrlKey,
                }
                : null,
            issue: row.event.issueId && row.issueTitle
                ? {
                    id: row.event.issueId,
                    identifier: row.issueIdentifier,
                    title: row.issueTitle,
                    status: row.issueStatus ?? "todo",
                    priority: row.issuePriority ?? "medium",
                }
                : null,
            automation: row.automationId && row.automationTitle
                ? {
                    id: row.automationId,
                    title: row.automationTitle,
                }
                : null,
        };
    }
    async function listPersistedEvents(orgId, filters) {
        const conditions = [
            eq(calendarEvents.orgId, orgId),
            isNull(calendarEvents.deletedAt),
            lt(calendarEvents.startAt, filters.end),
            gt(calendarEvents.endAt, filters.start),
        ];
        if (filters.agentIds?.length) {
            conditions.push(inArray(calendarEvents.ownerAgentId, filters.agentIds));
        }
        if (filters.sourceIds?.length) {
            conditions.push(inArray(calendarEvents.sourceId, filters.sourceIds));
        }
        if (filters.eventKinds?.length) {
            conditions.push(inArray(calendarEvents.eventKind, filters.eventKinds));
        }
        if (filters.statuses?.length) {
            conditions.push(inArray(calendarEvents.eventStatus, filters.statuses));
        }
        const rows = await db
            .select({
            event: calendarEvents,
            sourceId: calendarSources.id,
            sourceType: calendarSources.type,
            sourceName: calendarSources.name,
            sourceVisibilityDefault: calendarSources.visibilityDefault,
            sourceExternalProvider: calendarSources.externalProvider,
            agentName: agents.name,
            agentRole: agents.role,
            agentTitle: agents.title,
            agentUrlKey: agents.workspaceKey,
            issueIdentifier: issues.identifier,
            issueTitle: issues.title,
            issueStatus: issues.status,
            issuePriority: issues.priority,
            automationId: automations.id,
            automationTitle: automations.title,
        })
            .from(calendarEvents)
            .leftJoin(calendarSources, eq(calendarEvents.sourceId, calendarSources.id))
            .leftJoin(agents, eq(calendarEvents.ownerAgentId, agents.id))
            .leftJoin(issues, eq(calendarEvents.issueId, issues.id))
            .leftJoin(automations, and(eq(issues.originKind, "automation_execution"), sql `${issues.originId} = cast(${automations.id} as text)`, eq(automations.orgId, issues.orgId)))
            .where(and(...conditions))
            .orderBy(asc(calendarEvents.startAt), asc(calendarEvents.title));
        return rows.map(mapPersistedEvent);
    }
    async function listRunIssueFallbacks(orgId, runIds) {
        if (runIds.length === 0)
            return new Map();
        const rows = await db
            .selectDistinctOn([activityLog.runId], {
            runId: activityLog.runId,
            activityId: activityLog.id,
            issueId: issues.id,
            identifier: issues.identifier,
            title: issues.title,
            status: issues.status,
            priority: issues.priority,
            automationId: automations.id,
            automationTitle: automations.title,
        })
            .from(activityLog)
            .innerJoin(issues, eq(activityLog.entityId, issueIdAsText))
            .leftJoin(automations, and(eq(issues.originKind, "automation_execution"), sql `${issues.originId} = cast(${automations.id} as text)`, eq(automations.orgId, issues.orgId)))
            .where(and(eq(activityLog.orgId, orgId), inArray(activityLog.runId, runIds), eq(activityLog.entityType, "issue"), isNull(issues.hiddenAt)))
            .orderBy(activityLog.runId, desc(activityLog.createdAt));
        return new Map(rows
            .filter((row) => row.runId)
            .map((row) => [
            row.runId,
            {
                activityId: row.activityId,
                issue: {
                    id: row.issueId,
                    identifier: row.identifier,
                    title: row.title,
                    status: row.status,
                    priority: row.priority,
                },
                automation: row.automationId && row.automationTitle
                    ? {
                        id: row.automationId,
                        title: row.automationTitle,
                    }
                    : null,
            },
        ]));
    }
    async function listDerivedRunEvents(orgId, filters) {
        if (filters.sourceIds?.length)
            return [];
        if (!csvIncludes(filters.eventKinds, "agent_work_block"))
            return [];
        const conditions = [
            eq(heartbeatRuns.orgId, orgId),
            sql `coalesce(${heartbeatRuns.startedAt}, ${heartbeatRuns.createdAt}) < ${filters.end.toISOString()}::timestamptz`,
            sql `coalesce(${heartbeatRuns.finishedAt}, now()) > ${filters.start.toISOString()}::timestamptz`,
        ];
        if (filters.agentIds?.length) {
            conditions.push(inArray(heartbeatRuns.agentId, filters.agentIds));
        }
        if (filters.runId) {
            conditions.push(eq(heartbeatRuns.id, filters.runId));
        }
        const runRows = await db
            .select({
            id: heartbeatRuns.id,
            orgId: heartbeatRuns.orgId,
            agentId: heartbeatRuns.agentId,
            status: heartbeatRuns.status,
            startedAt: heartbeatRuns.startedAt,
            finishedAt: heartbeatRuns.finishedAt,
            createdAt: heartbeatRuns.createdAt,
            updatedAt: heartbeatRuns.updatedAt,
            invocationSource: heartbeatRuns.invocationSource,
            triggerDetail: heartbeatRuns.triggerDetail,
            contextSnapshot: heartbeatRuns.contextSnapshot,
            agentName: agents.name,
            agentRole: agents.role,
            agentTitle: agents.title,
            agentUrlKey: agents.workspaceKey,
            issueId: issues.id,
            issueIdentifier: issues.identifier,
            issueTitle: issues.title,
            issueStatus: issues.status,
            issuePriority: issues.priority,
            automationId: automations.id,
            automationTitle: automations.title,
        })
            .from(heartbeatRuns)
            .innerJoin(agents, eq(heartbeatRuns.agentId, agents.id))
            .leftJoin(issues, and(eq(issueIdAsText, contextIssueId), isNull(issues.hiddenAt)))
            .leftJoin(automations, and(eq(issues.originKind, "automation_execution"), sql `${issues.originId} = cast(${automations.id} as text)`, eq(automations.orgId, issues.orgId)))
            .where(and(...conditions))
            .orderBy(asc(sql `coalesce(${heartbeatRuns.startedAt}, ${heartbeatRuns.createdAt})`));
        const fallbackByRunId = await listRunIssueFallbacks(orgId, runRows.map((row) => row.id));
        const now = new Date();
        return runRows.flatMap((row) => {
            const startAt = row.startedAt ?? row.createdAt;
            const endAt = row.finishedAt ?? now;
            const eventStatus = row.status === "queued" || row.status === "running"
                ? "in_progress"
                : "actual";
            if (!csvIncludes(filters.statuses, eventStatus))
                return [];
            const fallback = fallbackByRunId.get(row.id);
            const issue = row.issueId
                ? {
                    id: row.issueId,
                    identifier: row.issueIdentifier,
                    title: row.issueTitle ?? "Untitled issue",
                    status: row.issueStatus ?? "todo",
                    priority: row.issuePriority ?? "medium",
                }
                : fallback?.issue ?? null;
            const automation = row.automationId && row.automationTitle
                ? {
                    id: row.automationId,
                    title: row.automationTitle,
                }
                : fallback?.automation ?? null;
            const title = issue ? `${row.agentName} · ${issue.title}` : `${row.agentName} · Agent run`;
            return [{
                    id: `run:${row.id}`,
                    orgId: row.orgId,
                    sourceId: null,
                    eventKind: "agent_work_block",
                    eventStatus,
                    ownerType: "agent",
                    ownerUserId: null,
                    ownerAgentId: row.agentId,
                    title,
                    description: row.triggerDetail ? `Run trigger: ${row.triggerDetail}` : null,
                    startAt,
                    endAt,
                    timezone: "UTC",
                    allDay: false,
                    visibility: "full",
                    issueId: issue?.id ?? null,
                    projectId: null,
                    goalId: null,
                    approvalId: null,
                    heartbeatRunId: row.id,
                    activityId: fallback?.activityId ?? null,
                    sourceMode: "derived",
                    externalProvider: null,
                    externalCalendarId: null,
                    externalEventId: null,
                    externalEtag: null,
                    externalUpdatedAt: null,
                    createdByUserId: null,
                    updatedByUserId: null,
                    createdAt: row.createdAt,
                    updatedAt: row.updatedAt,
                    deletedAt: null,
                    source: {
                        id: "derived:agent-work",
                        type: "agent_work",
                        name: "Agent work history",
                        visibilityDefault: "full",
                        externalProvider: null,
                    },
                    agent: {
                        id: row.agentId,
                        name: row.agentName,
                        role: row.agentRole,
                        title: row.agentTitle,
                        urlKey: row.agentUrlKey,
                    },
                    issue,
                    automation,
                }];
        });
    }
    async function listProjectedHeartbeatEvents(orgId, filters) {
        if (filters.sourceIds?.length)
            return [];
        if (!csvIncludes(filters.eventKinds, "agent_work_block"))
            return [];
        if (!csvIncludes(filters.statuses, "projected"))
            return [];
        const now = new Date();
        const projectionStart = new Date(Math.max(filters.start.getTime(), now.getTime()));
        if (filters.end.getTime() <= projectionStart.getTime())
            return [];
        const conditions = [
            eq(agents.orgId, orgId),
            sql `${agents.status} not in ('paused', 'terminated', 'pending_approval')`,
        ];
        if (filters.agentIds?.length) {
            conditions.push(inArray(agents.id, filters.agentIds));
        }
        const rows = await db
            .select()
            .from(agents)
            .where(and(...conditions))
            .orderBy(asc(agents.name));
        const projected = [];
        for (const row of rows) {
            const policy = parseHeartbeatPolicy(row);
            if (!policy.enabled || policy.intervalSec <= 0)
                continue;
            const intervalMs = policy.intervalSec * 1000;
            const baselineMs = new Date(row.lastHeartbeatAt ?? row.createdAt).getTime();
            let nextMs = baselineMs + intervalMs;
            if (nextMs < projectionStart.getTime()) {
                const elapsed = projectionStart.getTime() - baselineMs;
                nextMs = baselineMs + Math.ceil(elapsed / intervalMs) * intervalMs;
            }
            let count = 0;
            while (nextMs < filters.end.getTime() && count < PROJECTED_HEARTBEAT_MAX_PER_AGENT) {
                const startAt = new Date(nextMs);
                const endAt = new Date(Math.min(nextMs + PROJECTED_HEARTBEAT_DURATION_MS, filters.end.getTime()));
                projected.push({
                    id: `projected-heartbeat:${row.id}:${startAt.toISOString()}`,
                    orgId: row.orgId,
                    sourceId: null,
                    eventKind: "agent_work_block",
                    eventStatus: "projected",
                    ownerType: "agent",
                    ownerUserId: null,
                    ownerAgentId: row.id,
                    title: `${row.name} · Projected heartbeat`,
                    description: `Projected from this agent's ${policy.intervalSec}s timer heartbeat. This does not schedule or guarantee execution.`,
                    startAt,
                    endAt,
                    timezone: "UTC",
                    allDay: false,
                    visibility: "full",
                    issueId: null,
                    projectId: null,
                    goalId: null,
                    approvalId: null,
                    heartbeatRunId: null,
                    activityId: null,
                    sourceMode: "derived",
                    externalProvider: null,
                    externalCalendarId: null,
                    externalEventId: null,
                    externalEtag: null,
                    externalUpdatedAt: null,
                    createdByUserId: null,
                    updatedByUserId: null,
                    createdAt: row.createdAt,
                    updatedAt: row.updatedAt,
                    deletedAt: null,
                    source: {
                        id: "derived:projected-heartbeats",
                        type: "system",
                        name: "Projected heartbeats",
                        visibilityDefault: "full",
                        externalProvider: null,
                    },
                    agent: {
                        id: row.id,
                        name: row.name,
                        role: row.role,
                        title: row.title,
                        urlKey: row.workspaceKey,
                    },
                    issue: null,
                });
                nextMs += intervalMs;
                count += 1;
            }
        }
        return projected;
    }
    async function getPersistedEvent(orgId, id) {
        const rows = await db
            .select({
            event: calendarEvents,
            sourceId: calendarSources.id,
            sourceType: calendarSources.type,
            sourceName: calendarSources.name,
            sourceVisibilityDefault: calendarSources.visibilityDefault,
            sourceExternalProvider: calendarSources.externalProvider,
            agentName: agents.name,
            agentRole: agents.role,
            agentTitle: agents.title,
            agentUrlKey: agents.workspaceKey,
            issueIdentifier: issues.identifier,
            issueTitle: issues.title,
            issueStatus: issues.status,
            issuePriority: issues.priority,
            automationId: automations.id,
            automationTitle: automations.title,
        })
            .from(calendarEvents)
            .leftJoin(calendarSources, eq(calendarEvents.sourceId, calendarSources.id))
            .leftJoin(agents, eq(calendarEvents.ownerAgentId, agents.id))
            .leftJoin(issues, eq(calendarEvents.issueId, issues.id))
            .leftJoin(automations, and(eq(issues.originKind, "automation_execution"), sql `${issues.originId} = cast(${automations.id} as text)`, eq(automations.orgId, issues.orgId)))
            .where(and(eq(calendarEvents.id, id), eq(calendarEvents.orgId, orgId), isNull(calendarEvents.deletedAt)));
        return rows[0] ? mapPersistedEvent(rows[0]) : null;
    }
    async function writableEvent(orgId, id) {
        const event = await db
            .select()
            .from(calendarEvents)
            .where(and(eq(calendarEvents.id, id), eq(calendarEvents.orgId, orgId), isNull(calendarEvents.deletedAt)))
            .then((rows) => rows[0] ?? null);
        if (!event)
            throw notFound("Calendar event not found");
        if (event.sourceMode !== "manual") {
            throw conflict("Imported and derived calendar events are read-only");
        }
        if (event.eventKind !== "human_event") {
            throw conflict("Only My Calendar events can be edited");
        }
        return event;
    }
    async function getOrCreateGoogleSource(orgId, actor, status = "disconnected") {
        const existing = await db
            .select()
            .from(calendarSources)
            .where(and(eq(calendarSources.orgId, orgId), eq(calendarSources.type, "google_calendar"), eq(calendarSources.externalProvider, "google_calendar"), eq(calendarSources.externalCalendarId, "primary")))
            .then((rows) => rows[0] ?? null);
        if (existing)
            return existing;
        const [created] = await db
            .insert(calendarSources)
            .values({
            orgId,
            type: "google_calendar",
            name: "Google Calendar",
            ownerType: "user",
            ownerUserId: actor?.userId ?? "board",
            externalProvider: "google_calendar",
            externalCalendarId: "primary",
            visibilityDefault: "full",
            status,
        })
            .returning();
        return created;
    }
    async function listGoogleSourceRows(orgId) {
        const rows = await db
            .select()
            .from(calendarSources)
            .where(and(eq(calendarSources.orgId, orgId), eq(calendarSources.type, "google_calendar"), eq(calendarSources.externalProvider, "google_calendar")));
        return rows.sort((a, b) => {
            const aPrimary = a.externalCalendarId === "primary" ? 0 : 1;
            const bPrimary = b.externalCalendarId === "primary" ? 0 : 1;
            if (aPrimary !== bPrimary)
                return aPrimary - bPrimary;
            return a.name.localeCompare(b.name);
        });
    }
    async function findGoogleCredentialSource(orgId, preferred) {
        if (preferred && preferred.type === "google_calendar" && sourceHasGoogleToken(preferred))
            return preferred;
        const sources = await listGoogleSourceRows(orgId);
        return sources.find(sourceHasGoogleToken) ?? preferred ?? sources[0] ?? null;
    }
    async function ensureGoogleAccessToken(orgId, source) {
        if (!source)
            return null;
        const cursor = parseSyncCursor(source.syncCursorJson);
        const accessToken = typeof cursor.accessToken === "string" ? cursor.accessToken : null;
        const refreshToken = typeof cursor.refreshToken === "string" ? cursor.refreshToken : null;
        const expiresAt = typeof cursor.expiresAt === "string" ? new Date(cursor.expiresAt).getTime() : null;
        if (accessToken && source.status !== "error" && (!expiresAt || expiresAt > Date.now() + 60_000)) {
            return { source, accessToken };
        }
        if (!refreshToken) {
            return accessToken ? { source, accessToken } : null;
        }
        const credentials = await googleCredentials(orgId);
        if (!credentials)
            return null;
        const response = await fetch("https://oauth2.googleapis.com/token", {
            method: "POST",
            headers: { "content-type": "application/x-www-form-urlencoded" },
            body: new URLSearchParams({
                client_id: credentials.clientId,
                client_secret: credentials.clientSecret,
                refresh_token: refreshToken,
                grant_type: "refresh_token",
            }),
        });
        if (!response.ok) {
            await db
                .update(calendarSources)
                .set({ status: "error", updatedAt: new Date() })
                .where(eq(calendarSources.id, source.id));
            return null;
        }
        const token = await response.json();
        const nextAccessToken = token.access_token ?? accessToken;
        if (!nextAccessToken)
            return null;
        const [updated] = await db
            .update(calendarSources)
            .set({
            syncCursorJson: {
                ...cursor,
                accessToken: nextAccessToken,
                refreshToken,
                tokenType: token.token_type ?? cursor.tokenType,
                scope: token.scope ?? cursor.scope,
                expiresAt: token.expires_in ? new Date(Date.now() + token.expires_in * 1000).toISOString() : cursor.expiresAt ?? null,
            },
            status: source.status === "paused" ? "paused" : "active",
            updatedAt: new Date(),
        })
            .where(eq(calendarSources.id, source.id))
            .returning();
        return { source: updated, accessToken: nextAccessToken };
    }
    async function refreshGoogleCalendarSources(orgId, credentialSource, accessToken, actor) {
        const response = await fetch("https://www.googleapis.com/calendar/v3/users/me/calendarList", {
            headers: { authorization: `Bearer ${accessToken}` },
        });
        if (!response.ok)
            return [];
        const body = await response.json();
        const existingSources = await listGoogleSourceRows(orgId);
        const existingByExternalId = new Map(existingSources
            .filter((source) => source.externalCalendarId)
            .map((source) => [source.externalCalendarId, source]));
        const refreshed = [];
        for (const item of body.items ?? []) {
            const externalCalendarId = googleCalendarIdForListItem(item);
            if (!externalCalendarId)
                continue;
            const existing = existingByExternalId.get(externalCalendarId)
                ?? (item.primary ? credentialSource : null);
            const name = item.summary?.trim() || (item.primary ? "Primary calendar" : externalCalendarId);
            const metadataCursor = {
                ...(existing?.syncCursorJson ?? {}),
                googleCalendarPrimary: item.primary === true,
                googleCalendarHidden: item.hidden === true,
                googleCalendarColor: item.backgroundColor ?? null,
            };
            const shouldEnableByDefault = item.primary === true || item.selected !== false;
            const status = existing
                ? existing.status === "paused" ? "paused" : "active"
                : shouldEnableByDefault ? "active" : "paused";
            const values = {
                orgId,
                type: "google_calendar",
                name,
                ownerType: "user",
                ownerUserId: existing?.ownerUserId ?? credentialSource.ownerUserId ?? actor?.userId ?? "board",
                ownerAgentId: null,
                externalProvider: "google_calendar",
                externalCalendarId,
                visibilityDefault: existing?.visibilityDefault ?? credentialSource.visibilityDefault ?? "full",
                status,
                syncCursorJson: externalCalendarId === "primary"
                    ? {
                        ...metadataCursor,
                        ...parseSyncCursor(credentialSource.syncCursorJson),
                        googleCalendarPrimary: true,
                        googleCalendarHidden: item.hidden === true,
                        googleCalendarColor: item.backgroundColor ?? null,
                    }
                    : metadataCursor,
                updatedAt: new Date(),
            };
            if (existing) {
                const [updated] = await db
                    .update(calendarSources)
                    .set(values)
                    .where(eq(calendarSources.id, existing.id))
                    .returning();
                refreshed.push(updated);
            }
            else {
                const [created] = await db
                    .insert(calendarSources)
                    .values(values)
                    .returning();
                refreshed.push(created);
            }
        }
        return refreshed;
    }
    async function syncOneGoogleCalendar(orgId, source, accessToken) {
        const timeMin = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
        const timeMax = new Date(Date.now() + 90 * 24 * 60 * 60 * 1000).toISOString();
        const params = new URLSearchParams({
            singleEvents: "true",
            orderBy: "startTime",
            timeMin,
            timeMax,
        });
        const calendarId = source.externalCalendarId ?? "primary";
        const response = await fetch(`https://www.googleapis.com/calendar/v3/calendars/${encodeURIComponent(calendarId)}/events?${params.toString()}`, {
            headers: { authorization: `Bearer ${accessToken}` },
        });
        if (!response.ok) {
            const [updated] = await db
                .update(calendarSources)
                .set({ status: "error", updatedAt: new Date() })
                .where(eq(calendarSources.id, source.id))
                .returning();
            return { source: updated, importedCount: 0 };
        }
        const body = await response.json();
        let importedCount = 0;
        for (const item of body.items ?? []) {
            if (!item.id || item.status === "cancelled")
                continue;
            const startRaw = item.start?.dateTime ?? item.start?.date;
            const endRaw = item.end?.dateTime ?? item.end?.date;
            if (!startRaw || !endRaw)
                continue;
            const allDay = !item.start?.dateTime;
            const visibility = item.visibility === "private"
                ? "private"
                : source.visibilityDefault;
            const title = visibility !== "full"
                ? "Busy"
                : item.summary?.trim() || "Busy";
            const created = await upsertImportedGoogleEvent({
                orgId,
                sourceId: source.id,
                calendarId,
                title,
                startAt: new Date(startRaw),
                endAt: new Date(endRaw),
                timezone: item.start?.timeZone ?? "UTC",
                allDay,
                visibility,
                externalEventId: item.id,
                externalEtag: item.etag ?? null,
                externalUpdatedAt: item.updated ? new Date(item.updated) : null,
            });
            if (created)
                importedCount += 1;
        }
        const [updated] = await db
            .update(calendarSources)
            .set({
            status: "active",
            lastSyncedAt: new Date(),
            updatedAt: new Date(),
        })
            .where(eq(calendarSources.id, source.id))
            .returning();
        return { source: updated, importedCount };
    }
    async function upsertImportedGoogleEvent(params) {
        const existing = await db
            .select({ id: calendarEvents.id })
            .from(calendarEvents)
            .where(and(eq(calendarEvents.orgId, params.orgId), eq(calendarEvents.externalProvider, "google_calendar"), eq(calendarEvents.externalCalendarId, params.calendarId), eq(calendarEvents.externalEventId, params.externalEventId)))
            .then((rows) => rows[0] ?? null);
        const values = {
            orgId: params.orgId,
            sourceId: params.sourceId,
            eventKind: "external_event",
            eventStatus: "external",
            ownerType: "user",
            title: params.title,
            startAt: params.startAt,
            endAt: params.endAt,
            timezone: params.timezone,
            allDay: params.allDay,
            visibility: params.visibility,
            sourceMode: "imported",
            externalProvider: "google_calendar",
            externalCalendarId: params.calendarId,
            externalEventId: params.externalEventId,
            externalEtag: params.externalEtag,
            externalUpdatedAt: params.externalUpdatedAt,
            updatedAt: new Date(),
            deletedAt: null,
        };
        if (existing) {
            await db.update(calendarEvents).set(values).where(eq(calendarEvents.id, existing.id));
            return false;
        }
        await db.insert(calendarEvents).values(values);
        return true;
    }
    return {
        eventSummary,
        async listSources(orgId) {
            const rows = await db
                .select()
                .from(calendarSources)
                .where(eq(calendarSources.orgId, orgId))
                .orderBy(asc(calendarSources.type), asc(calendarSources.name));
            return rows.map(sanitizeSource);
        },
        async createSource(orgId, input, actor) {
            await assertAgentOrg(orgId, input.ownerAgentId);
            const [created] = await db
                .insert(calendarSources)
                .values({
                orgId,
                ...input,
                ownerUserId: input.ownerUserId ?? actor?.userId ?? null,
                ownerAgentId: input.ownerAgentId ?? null,
                externalProvider: input.externalProvider ?? null,
                externalCalendarId: input.externalCalendarId ?? null,
                syncCursorJson: input.syncCursorJson ?? null,
            })
                .returning();
            return sanitizeSource(created);
        },
        async updateSource(orgId, sourceId, input, actor) {
            const existing = await assertSourceOrg(orgId, sourceId);
            await assertAgentOrg(orgId, input.ownerAgentId);
            const [updated] = await db
                .update(calendarSources)
                .set({
                ...input,
                ownerUserId: input.ownerUserId ?? existing?.ownerUserId ?? actor?.userId ?? null,
                ownerAgentId: input.ownerAgentId === undefined ? existing?.ownerAgentId ?? null : input.ownerAgentId,
                externalProvider: input.externalProvider === undefined ? existing?.externalProvider ?? null : input.externalProvider,
                externalCalendarId: input.externalCalendarId === undefined ? existing?.externalCalendarId ?? null : input.externalCalendarId,
                syncCursorJson: input.syncCursorJson === undefined ? existing?.syncCursorJson ?? null : input.syncCursorJson,
                updatedAt: new Date(),
            })
                .where(eq(calendarSources.id, sourceId))
                .returning();
            return sanitizeSource(updated);
        },
        async deleteSource(orgId, sourceId) {
            await assertSourceOrg(orgId, sourceId);
            await db.delete(calendarSources).where(and(eq(calendarSources.id, sourceId), eq(calendarSources.orgId, orgId)));
            return { ok: true };
        },
        async listEvents(orgId, filters) {
            const [persisted, derived, projected] = await Promise.all([
                listPersistedEvents(orgId, filters),
                listDerivedRunEvents(orgId, filters),
                listProjectedHeartbeatEvents(orgId, filters),
            ]);
            return [...persisted, ...derived, ...projected].sort((a, b) => {
                const time = new Date(a.startAt).getTime() - new Date(b.startAt).getTime();
                return time !== 0 ? time : a.title.localeCompare(b.title);
            });
        },
        async getEvent(orgId, eventId) {
            if (eventId.startsWith("run:")) {
                const runId = eventId.slice("run:".length);
                const derived = await listDerivedRunEvents(orgId, {
                    start: new Date(0),
                    end: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000),
                    runId,
                });
                return derived[0] ?? null;
            }
            return getPersistedEvent(orgId, eventId);
        },
        async createEvent(orgId, input, actor) {
            await assertCalendarEventShape(orgId, input);
            const [created] = await db
                .insert(calendarEvents)
                .values(createEventValues(orgId, input, actor))
                .returning();
            return getPersistedEvent(orgId, created.id);
        },
        async updateEvent(orgId, eventId, input, actor) {
            const existing = await writableEvent(orgId, eventId);
            const merged = mergeEventInput(existing, input);
            await assertCalendarEventShape(orgId, merged);
            if (merged.endAt.getTime() <= merged.startAt.getTime()) {
                throw unprocessable("End time must be after start time");
            }
            const [updated] = await db
                .update(calendarEvents)
                .set({
                ...updateEventValues(input, actor),
                updatedAt: new Date(),
            })
                .where(eq(calendarEvents.id, eventId))
                .returning();
            return { previous: existing, event: await getPersistedEvent(orgId, updated.id) };
        },
        async deleteEvent(orgId, eventId, actor) {
            const existing = await writableEvent(orgId, eventId);
            await db
                .update(calendarEvents)
                .set({
                deletedAt: new Date(),
                updatedAt: new Date(),
                updatedByUserId: actor?.userId ?? null,
                eventStatus: "cancelled",
            })
                .where(eq(calendarEvents.id, eventId));
            return existing;
        },
        getGoogleOAuthConfig: googleOAuthConfig,
        updateGoogleOAuthConfig,
        async connectGoogle(orgId, redirectUri, actor) {
            const credentials = await googleCredentials(orgId);
            let source = await getOrCreateGoogleSource(orgId, actor, credentials ? "disconnected" : "error");
            if (!credentials) {
                return {
                    status: "configuration_required",
                    authUrl: null,
                    source: sanitizeSource(source),
                    redirectUri,
                    requiredEnv: [...GOOGLE_CALENDAR_REQUIRED_ENV],
                    acceptedAliases: [...GOOGLE_CALENDAR_ACCEPTED_ENV_ALIASES],
                    config: await googleOAuthConfig(orgId, redirectUri),
                };
            }
            if (source.status === "error" && !sourceHasGoogleToken(source)) {
                const [updated] = await db
                    .update(calendarSources)
                    .set({ status: "disconnected", updatedAt: new Date() })
                    .where(eq(calendarSources.id, source.id))
                    .returning();
                source = updated ?? source;
            }
            const state = Buffer.from(JSON.stringify({ orgId, sourceId: source.id })).toString("base64url");
            const params = new URLSearchParams({
                client_id: credentials.clientId,
                redirect_uri: redirectUri,
                response_type: "code",
                scope: "https://www.googleapis.com/auth/calendar.readonly",
                access_type: "offline",
                include_granted_scopes: "true",
                prompt: "consent",
                state,
            });
            return {
                status: "authorization_required",
                authUrl: `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`,
                source: sanitizeSource(source),
                config: await googleOAuthConfig(orgId, redirectUri),
            };
        },
        async completeGoogleCallback(orgId, input, actor) {
            const credentials = await googleCredentials(orgId);
            if (!credentials)
                throw unprocessable("Google Calendar OAuth is not configured");
            let sourceId = null;
            if (input.state) {
                try {
                    const parsed = JSON.parse(Buffer.from(input.state, "base64url").toString("utf8"));
                    if (parsed.orgId === orgId && typeof parsed.sourceId === "string") {
                        sourceId = parsed.sourceId;
                    }
                }
                catch {
                    sourceId = null;
                }
            }
            const source = sourceId
                ? await assertSourceOrg(orgId, sourceId)
                : await getOrCreateGoogleSource(orgId, actor);
            const response = await fetch("https://oauth2.googleapis.com/token", {
                method: "POST",
                headers: { "content-type": "application/x-www-form-urlencoded" },
                body: new URLSearchParams({
                    code: input.code,
                    client_id: credentials.clientId,
                    client_secret: credentials.clientSecret,
                    redirect_uri: input.redirectUri,
                    grant_type: "authorization_code",
                }),
            });
            if (!response.ok) {
                throw unprocessable(`Google Calendar authorization failed: ${response.status}`);
            }
            const token = await response.json();
            const cursor = parseSyncCursor(source?.syncCursorJson);
            const [updated] = await db
                .update(calendarSources)
                .set({
                status: "active",
                syncCursorJson: {
                    ...cursor,
                    accessToken: token.access_token,
                    refreshToken: token.refresh_token ?? cursor.refreshToken,
                    tokenType: token.token_type,
                    scope: token.scope,
                    expiresAt: token.expires_in ? new Date(Date.now() + token.expires_in * 1000).toISOString() : null,
                },
                updatedAt: new Date(),
            })
                .where(eq(calendarSources.id, source.id))
                .returning();
            if (token.access_token) {
                await refreshGoogleCalendarSources(orgId, updated, token.access_token, actor);
            }
            return sanitizeSource(updated);
        },
        async syncGoogle(orgId, sourceId) {
            const requestedSource = sourceId
                ? await assertSourceOrg(orgId, sourceId)
                : await getOrCreateGoogleSource(orgId);
            if (!requestedSource || requestedSource.type !== "google_calendar") {
                throw unprocessable("Calendar source is not a Google Calendar source");
            }
            const credentialSource = await findGoogleCredentialSource(orgId, requestedSource);
            const token = await ensureGoogleAccessToken(orgId, credentialSource);
            if (!token) {
                return { source: sanitizeSource(requestedSource), importedCount: 0, syncedSourceCount: 0 };
            }
            await refreshGoogleCalendarSources(orgId, token.source, token.accessToken);
            const refreshedRequestedSource = sourceId
                ? (await assertSourceOrg(orgId, sourceId))
                : requestedSource;
            const targets = sourceId
                ? [refreshedRequestedSource]
                : (await listGoogleSourceRows(orgId)).filter((source) => source.status === "active");
            let importedCount = 0;
            let responseSource = refreshedRequestedSource;
            let syncedSourceCount = 0;
            for (const target of targets) {
                if (target.type !== "google_calendar")
                    continue;
                const result = await syncOneGoogleCalendar(orgId, target, token.accessToken);
                importedCount += result.importedCount;
                syncedSourceCount += 1;
                if (target.id === refreshedRequestedSource.id || !sourceId) {
                    responseSource = result.source;
                }
            }
            return { source: sanitizeSource(responseSource), importedCount, syncedSourceCount };
        },
    };
}
//# sourceMappingURL=calendar.js.map