"use strict";
/**
 * Workflow invocation service.
 *
 * Encapsulates the business logic for starting a workflow by type:
 * config lookup, role/scope enforcement, principal resolution,
 * envelope building, and Durable client start.
 *
 * Transport-agnostic — called by HTTP routes, CLI, or other transports.
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.InvocationError = void 0;
exports.invokeWorkflow = invokeWorkflow;
exports.checkInvocationRoles = checkInvocationRoles;
const hotmesh_1 = require("@hotmeshio/hotmesh");
const workers_1 = require("../workers");
const configService = __importStar(require("./config"));
const userService = __importStar(require("./user"));
const defaults_1 = require("../modules/defaults");
const registry_1 = require("./workers/registry");
const principal_1 = require("./iam/principal");
// ── Error types ─────────────────────────────────────────────────────────────
class InvocationError extends Error {
    constructor(message, statusCode) {
        super(message);
        this.statusCode = statusCode;
        this.name = 'InvocationError';
    }
}
exports.InvocationError = InvocationError;
// ── Service ─────────────────────────────────────────────────────────────────
/**
 * Start a workflow by its registered type.
 *
 * Thin proxy over `Durable.Client.workflow.start()` — callers have
 * access to the full option set (workflowId, expire, entity) with
 * sensible defaults when omitted. The service resolves the task queue,
 * enforces auth/scope constraints, builds the LTEnvelope with IAM
 * context, and delegates to the Durable client.
 *
 * Throws `InvocationError` with an appropriate status code on failure.
 */
async function invokeWorkflow(input) {
    const { workflowType, data, metadata, executeAs: executeAsOverride, auth } = input;
    // 1. Look up workflow config and resolve task queue
    const taskQueue = await resolveTaskQueue(workflowType);
    // 2. Enforce bot API key scopes
    const authScopes = auth.scopes ?? [];
    if (authScopes.length > 0 && !authScopes.includes('workflow:invoke')) {
        throw new InvocationError('API key scope does not include workflow:invoke', 403);
    }
    // 3. Validate inputs
    if (!data || typeof data !== 'object') {
        throw new InvocationError('Request body must include a data object', 400);
    }
    if (executeAsOverride && auth.role !== 'admin' && auth.role !== 'superadmin') {
        throw new InvocationError('execute_as override requires admin role', 403);
    }
    // 4. Resolve principals and build envelope
    const wfConfig = await configService.getWorkflowConfig(workflowType);
    const isCertified = !!(wfConfig &&
        ((wfConfig.roles?.length ?? 0) > 0 || (wfConfig.consumes?.length ?? 0) > 0));
    const resolvedMetadata = { ...(metadata ?? {}) };
    if (isCertified && resolvedMetadata.certified !== false) {
        resolvedMetadata.certified = true;
    }
    const envelope = await buildEnvelope(data, resolvedMetadata, auth.userId, executeAsOverride, wfConfig?.execute_as ?? undefined);
    // 5. Start workflow — caller options pass through to Durable client
    const client = (0, workers_1.createClient)();
    const callerOpts = input.options ?? {};
    const workflowId = callerOpts.workflowId || `${workflowType}-${hotmesh_1.Durable.guid()}`;
    await client.workflow.start({
        ...callerOpts,
        args: [envelope],
        taskQueue,
        workflowName: workflowType,
        workflowId,
        expire: callerOpts.expire ?? defaults_1.JOB_EXPIRE_SECS,
        entity: callerOpts.entity ?? workflowType,
        signalIn: false,
    });
    return { workflowId };
}
// ── Internal helpers ────────────────────────────────────────────────────────
/**
 * Resolve the task queue for a workflow type. Checks the config table
 * first (with invocable/role guards), then falls back to active workers.
 */
async function resolveTaskQueue(workflowType) {
    const wfConfig = await configService.getWorkflowConfig(workflowType);
    if (wfConfig) {
        if (!wfConfig.invocable) {
            throw new InvocationError('Workflow is not invocable', 403);
        }
        if (!wfConfig.task_queue) {
            throw new InvocationError('Workflow has no task_queue configured', 400);
        }
        return wfConfig.task_queue;
    }
    // No config — fall back to active worker registry
    const worker = (0, registry_1.getRegisteredWorkers)().get(workflowType);
    if (!worker) {
        throw new InvocationError('Workflow not found (no config and no active worker)', 404);
    }
    return worker.taskQueue;
}
/**
 * Check invocation_roles when present on a workflow config.
 * Throws InvocationError if the user lacks the required role.
 */
async function checkInvocationRoles(workflowType, userId, authRole) {
    const wfConfig = await configService.getWorkflowConfig(workflowType);
    if (!wfConfig || wfConfig.invocation_roles.length === 0)
        return;
    // Superadmin from JWT bypasses all invocation role checks
    if (authRole === 'superadmin')
        return;
    const user = await userService.getUserByExternalId(userId);
    if (!user) {
        throw new InvocationError('User not registered', 403);
    }
    const userRoles = user.roles.map((r) => r.role);
    const hasInvocationRole = wfConfig.invocation_roles.some((r) => userRoles.includes(r));
    if (!hasInvocationRole) {
        const isSuperAdmin = user.roles.some((r) => r.type === 'superadmin');
        if (!isSuperAdmin) {
            throw new InvocationError('Insufficient role for invocation', 403);
        }
    }
}
/**
 * Build the LTEnvelope with IAM context.
 */
async function buildEnvelope(data, metadata, userId, executeAsOverride, configExecuteAs) {
    const executeAs = executeAsOverride ?? configExecuteAs;
    let principal;
    let initiatingPrincipal;
    if (executeAs) {
        const [botP, userP] = await Promise.all([
            (0, principal_1.resolvePrincipal)(executeAs),
            userId ? (0, principal_1.resolvePrincipal)(userId) : Promise.resolve(null),
        ]);
        principal = botP ?? undefined;
        initiatingPrincipal = userP ?? undefined;
    }
    else if (userId) {
        principal = (await (0, principal_1.resolvePrincipal)(userId)) ?? undefined;
    }
    const resolvedUserId = principal?.id ?? userId;
    const resolvedInitiatorId = initiatingPrincipal?.id ?? userId;
    return {
        data,
        metadata,
        lt: {
            userId: executeAs ? principal?.id ?? executeAs : resolvedUserId,
            principal: principal ?? undefined,
            scopes: ['workflow:invoke'],
            ...(executeAs && userId
                ? {
                    initiatedBy: resolvedInitiatorId,
                    initiatingPrincipal: initiatingPrincipal ?? undefined,
                }
                : {}),
        },
    };
}
