import { ActionExecutionInputSchema, AppFactoryInputSchema } from "./schemas";
import { ZapierValidationError } from "../../types/errors";
import { definePlugin } from "../../utils/plugin-utils";
import { ActionResultItemSchema } from "../../schemas/Run";
/**
 * Resolves which connection params to forward, choosing per-call values over pinned (factory) values.
 * Validates mutual exclusivity and that at least one connection is specified.
 */
function resolveProxyConnection({ providedConnectionId, providedConnection, providedAuthenticationId, pinnedAuthId, pinnedConnection, errorContext, }) {
    const hasDirectId = providedConnectionId != null || providedAuthenticationId != null;
    if (hasDirectId && providedConnection != null) {
        throw new ZapierValidationError("Cannot specify both connectionId/authenticationId and connection. Use connectionId for a direct ID, or connection for an alias or numeric ID.");
    }
    const hasPerCall = providedConnectionId != null ||
        providedConnection != null ||
        providedAuthenticationId != null;
    let finalConnectionId;
    let finalConnection;
    if (hasPerCall) {
        finalConnectionId = providedConnectionId ?? providedAuthenticationId;
        finalConnection =
            finalConnectionId != null ? undefined : providedConnection;
    }
    else {
        finalConnectionId = pinnedAuthId;
        finalConnection = pinnedAuthId != null ? undefined : pinnedConnection;
    }
    if (finalConnectionId == null && finalConnection == null) {
        throw new ZapierValidationError(errorContext);
    }
    return { connectionId: finalConnectionId, connection: finalConnection };
}
function createActionFunction(appKey, actionType, actionKey, options, pinnedAuthId, pinnedConnection) {
    return (actionOptions = {}) => {
        const { sdk } = options;
        const { inputs, connectionId: providedConnectionId, connection: providedConnection, authenticationId: providedAuthenticationId, timeoutMs, } = actionOptions;
        const { connectionId, connection } = resolveProxyConnection({
            providedConnectionId,
            providedConnection,
            providedAuthenticationId,
            pinnedAuthId,
            pinnedConnection,
            errorContext: `Connection is required. Either use the factory pattern: sdk.apps.${appKey}({ connectionId }) or sdk.apps.${appKey}({ connection: "name" }), or provide connectionId/connection in the action call.`,
        });
        return sdk.runAction({
            app: appKey,
            actionType: actionType,
            action: actionKey,
            inputs,
            connection: connectionId ?? connection,
            timeoutMs,
        });
    };
}
function createActionTypeProxy(appKey, actionType, options, pinnedAuthId, pinnedConnection) {
    // Special handling for "fetch" action type
    if (actionType === "fetch") {
        return async (url, init) => {
            const { sdk } = options;
            const { connectionId, connection } = resolveProxyConnection({
                providedConnectionId: init?.connectionId,
                providedConnection: init?.connection,
                providedAuthenticationId: init?.authenticationId,
                pinnedAuthId,
                pinnedConnection,
                errorContext: `Connection is required for fetch. Either use the factory pattern: sdk.apps.${appKey}({ connectionId }).fetch(...) or provide connectionId/connection in the fetch call.`,
            });
            return sdk.fetch(url, {
                ...init,
                connection: connectionId ?? connection,
            });
        };
    }
    return new Proxy({}, {
        get(_, actionKey) {
            if (typeof actionKey === "string") {
                return createActionFunction(appKey, actionType, actionKey, options, pinnedAuthId, pinnedConnection);
            }
            return undefined;
        },
    });
}
function createPinnedAppProxy(appKey, options, pinnedAuthId, pinnedConnection) {
    return new Proxy({}, {
        get(_, actionType) {
            if (typeof actionType === "string") {
                return createActionTypeProxy(appKey, actionType, options, pinnedAuthId, pinnedConnection);
            }
            return undefined;
        },
    });
}
function createAppProxy(appKey, options) {
    // Create the factory function that returns a pinned version
    const appFactory = (factoryOptions) => {
        const resolvedConnectionId = factoryOptions.connectionId ?? factoryOptions.authenticationId;
        const resolvedConnection = factoryOptions.connection;
        if (resolvedConnectionId != null && resolvedConnection != null) {
            throw new ZapierValidationError("Cannot specify both connectionId/authenticationId and connection. Use connectionId for a direct ID, or connection for an alias or numeric ID.");
        }
        if (resolvedConnectionId == null && resolvedConnection == null) {
            throw new ZapierValidationError(`Connection is required. Use sdk.apps.${appKey}({ connectionId: "..." }) or sdk.apps.${appKey}({ connection: "name" })`);
        }
        return createPinnedAppProxy(appKey, options, resolvedConnectionId, resolvedConnection);
    };
    // Add properties for direct usage (backward compatibility)
    return new Proxy(appFactory, {
        get(_, actionType) {
            if (typeof actionType === "string") {
                return createActionTypeProxy(appKey, actionType, options);
            }
            return undefined;
        },
    });
}
function createAppsProxy(options) {
    // Create a proxy object that intercepts property access
    const appsProxy = new Proxy({}, {
        get(_, prop) {
            if (typeof prop === "string") {
                // First level: App names (e.g., 'slack')
                return createAppProxy(prop, options);
            }
            return undefined;
        },
    });
    return appsProxy;
}
export const appsPlugin = definePlugin((sdk) => {
    return {
        // Cast: ZapierSdkApps is augmented by user-generated .d.ts files to
        // give specific types like `slack: SlackAppWithFactory`. At runtime
        // the Proxy intercepts any property access, so the augmented entries
        // always resolve correctly even though TS can't see that statically.
        apps: createAppsProxy({ sdk }),
        context: {
            meta: {
                "apps.{appKey}": {
                    categories: ["app"],
                    packages: ["sdk"],
                    type: "function",
                    inputSchema: AppFactoryInputSchema,
                    returnType: "AppProxy",
                },
                "apps.{appKey}.{actionType}.{actionKey}": {
                    categories: ["app"],
                    packages: ["sdk"],
                    type: "list",
                    inputSchema: ActionExecutionInputSchema,
                    itemType: "ActionResult",
                    outputSchema: ActionResultItemSchema,
                },
            },
        },
    };
});
