import { definePlugin } from "../../utils/plugin-utils";
import { createPaginatedPluginMethod } from "../../utils/plugin-utils";
import { RunActionInputSchema } from "./schemas";
import { ActionResultItemSchema } from "../../schemas/Run";
import { ZapierValidationError, ZapierConfigurationError, ZapierActionError, } from "../../types/errors";
import { appKeyResolver, actionTypeResolver, actionKeyResolver, connectionIdResolver, inputsResolver, } from "../../resolvers";
import { resolveConnectionId } from "../../utils/domain-utils";
import { DEFAULT_ACTION_TIMEOUT_MS } from "../../constants";
import { setMethodMetadata } from "../../utils/telemetry-context";
import { buildActionRunContext } from "@zapier/policy-context";
async function executeAction(actionOptions) {
    const { api, selectedApi, actionId, actionKey, actionType, executionOptions, cursor, connectionId, timeoutMs, } = actionOptions;
    // Step 1: POST to /actions/v1/runs to start execution
    const runRequestData = {
        selected_api: selectedApi,
        action_id: actionId,
        action_key: actionKey,
        action_type: actionType,
        inputs: executionOptions.inputs || {},
    };
    // Note: API still expects authentication_id, we just renamed the user-facing param
    if (connectionId !== null && connectionId !== undefined) {
        runRequestData.authentication_id = connectionId;
    }
    if (cursor) {
        runRequestData.page = cursor;
    }
    const runRequest = {
        data: runRequestData,
    };
    const runData = await api.post("/zapier/api/actions/v1/runs", runRequest, {
        approvalContext: () => buildActionRunContext({
            selected_api: selectedApi,
            action_type: actionType,
            action_key: actionKey,
            connection_id: connectionId != null ? String(connectionId) : undefined,
            // Cast: inputs is Record<string, unknown> at the SDK surface, but
            // buildActionRunContext coerces to JsonValue at runtime via zod.
            inputs: (executionOptions.inputs ?? {}),
        }),
    });
    const runId = runData.data.id;
    // Step 2: Poll GET /actions/v1/runs/{run_id} for results
    return await api.poll(`/zapier/api/actions/v1/runs/${runId}`, {
        successStatus: 200,
        pendingStatus: 202,
        timeoutMs: timeoutMs ?? DEFAULT_ACTION_TIMEOUT_MS,
        resource: { type: "run", id: runId },
        isPending: (result) => {
            const data = result?.data;
            return data?.status === "waiting";
        },
        resultExtractor: (result) => result.data,
    });
}
export const CONTEXT_CACHE_TTL_MS = 60000;
export const CONTEXT_CACHE_MAX_SIZE = 500;
export const runActionPlugin = definePlugin((sdk) => {
    // Intentionally kept inline — only this plugin needs this pattern today.
    // If a second plugin needs a bounded TTL cache, extract to a shared utility.
    const runActionContextCache = new Map();
    function evictIfNeeded() {
        if (runActionContextCache.size < CONTEXT_CACHE_MAX_SIZE)
            return;
        const now = Date.now();
        let oldestKey;
        let oldestExpiry = Infinity;
        let evictedAny = false;
        for (const [key, entry] of runActionContextCache) {
            if (now >= entry.expiresAt) {
                runActionContextCache.delete(key);
                evictedAny = true;
            }
            else if (entry.expiresAt < oldestExpiry) {
                oldestExpiry = entry.expiresAt;
                oldestKey = key;
            }
        }
        if (!evictedAny && oldestKey) {
            runActionContextCache.delete(oldestKey);
        }
    }
    function getRunActionContext(options) {
        const { appKey, actionKey, actionType } = options;
        const contextKey = `${appKey}:${actionKey}:${actionType}`;
        const cached = runActionContextCache.get(contextKey);
        if (cached && Date.now() < cached.expiresAt) {
            return cached.promise;
        }
        const pendingContext = resolveRunActionContext(options).catch((error) => {
            const current = runActionContextCache.get(contextKey);
            if (current?.promise === pendingContext) {
                runActionContextCache.delete(contextKey);
            }
            throw error;
        });
        evictIfNeeded();
        runActionContextCache.set(contextKey, {
            promise: pendingContext,
            expiresAt: Date.now() + CONTEXT_CACHE_TTL_MS,
        });
        return pendingContext;
    }
    async function resolveRunActionContext(options) {
        const { appKey, actionKey, actionType } = options;
        const selectedApi = await sdk.context.getVersionedImplementationId(appKey);
        if (!selectedApi) {
            throw new ZapierConfigurationError("No current_implementation_id found for app", { configType: "current_implementation_id" });
        }
        // Validate that the action exists using the getAction plugin
        const actionData = await sdk.getAction({
            app: appKey,
            action: actionKey,
            actionType,
        });
        // Validate action type matches
        if (actionData.data.action_type !== actionType) {
            throw new ZapierValidationError(`Action type mismatch: expected ${actionType}, got ${actionData.data.action_type}`);
        }
        return { selectedApi, actionId: actionData.data.id };
    }
    // No defaultPageSize — we leave the default to the Actions API rather
    // than eagerly running more actions than the user intends to (and avoid
    // hitting app rate limits).
    return createPaginatedPluginMethod(sdk, {
        name: "runAction",
        categories: ["action"],
        type: "list",
        itemType: "ActionResult",
        inputSchema: RunActionInputSchema,
        outputSchema: ActionResultItemSchema,
        resolvers: {
            app: appKeyResolver,
            actionType: actionTypeResolver,
            action: actionKeyResolver,
            connection: connectionIdResolver,
            inputs: inputsResolver,
        },
        handler: async ({ sdk, options, }) => {
            const { api } = sdk.context;
            const appKey = "app" in options ? options.app : options.appKey;
            const actionKey = "action" in options ? options.action : options.actionKey;
            const { actionType, connectionId, connection, authenticationId, inputs = {}, cursor, timeoutMs, } = options;
            const resolvedConnectionId = await resolveConnectionId({
                connectionId,
                connection,
                authenticationId,
                resolveConnection: sdk.context.resolveConnection,
            });
            const { selectedApi, actionId } = await getRunActionContext({
                appKey,
                actionKey,
                actionType,
            });
            setMethodMetadata({
                selectedApi,
                operationType: actionType,
                operationKey: actionKey,
            });
            const result = await executeAction({
                api,
                selectedApi,
                // Some actions require the action ID, but it's not guaranteed
                // available at retrieval time (legacy), so pass everything through.
                actionId,
                actionKey,
                actionType,
                executionOptions: { inputs },
                cursor,
                connectionId: resolvedConnectionId,
                timeoutMs,
            });
            if (result.errors && result.errors.length > 0) {
                const errorMessage = result.errors
                    .map((error) => error.detail || error.title || "Unknown error")
                    .join("; ");
                throw new ZapierActionError(`Action execution failed: ${errorMessage}`, { appKey, actionKey });
            }
            return {
                data: result.results || [],
                nextCursor: result.next_page,
            };
        },
    });
});
