"use strict";
/**
 * ConnectorClient — high-level wrapper using UniFFI bindings via koffi.
 *
 * Handles the full round-trip for any payment flow:
 *   1. Serialize protobuf request to bytes
 *   2. Build connector HTTP request via UniffiClient.callReq (generic FFI dispatch)
 *   3. Execute the HTTP request via our standardized HttpClient
 *   4. Parse the connector response via UniffiClient.callRes (generic FFI dispatch)
 *   5. Deserialize protobuf response from bytes
 *
 * Flow methods (authorize, capture, void, refund, …) are in _generated_connector_client_flows.ts.
 * To add a new flow: edit sdk/flows.yaml and run `make codegen`.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConnectorClient = exports.ConnectorError = exports.IntegrationError = void 0;
const uniffi_client_1 = require("./uniffi_client");
const http_client_1 = require("../http_client");
// @ts-ignore - protobuf generated files might not have types yet
const proto_1 = require("./generated/proto");
const v2 = proto_1.types;
// Re-export error classes from errors.ts
var errors_1 = require("./errors");
Object.defineProperty(exports, "IntegrationError", { enumerable: true, get: function () { return errors_1.IntegrationError; } });
Object.defineProperty(exports, "ConnectorError", { enumerable: true, get: function () { return errors_1.ConnectorError; } });
class ConnectorClient {
    uniffi;
    config;
    defaults;
    baseHttpConfig;
    dispatcherCache;
    /**
     * Initialize the client with mandatory config and optional request defaults.
     *
     * @param config - Immutable connector config and environment (ConnectorSpecificConfig, SdkOptions).
     * @param defaults - Optional per-request defaults (Http, Vault).
     * @param libPath - optional path to the UniFFI shared library.
     */
    constructor(config, defaults = {}, libPath) {
        this.uniffi = new uniffi_client_1.UniffiClient(libPath);
        this.config = proto_1.types.ConnectorConfig.create(config);
        this.defaults = defaults;
        const hasConnectorVariant = !!config.connectorConfig
            && Object.values(config.connectorConfig).some((value) => value != null);
        if (!hasConnectorVariant) {
            throw new http_client_1.NetworkError("connectorConfig with a connector variant is required in ConnectorConfig", proto_1.types.NetworkErrorCode.CLIENT_INITIALIZATION_FAILURE, 400);
        }
        // Store base HTTP config (merged with defaults)
        this.baseHttpConfig = defaults.http || {};
        this.dispatcherCache = new Map();
    }
    /**
     * Merges request-level options with client defaults. Environment comes from config (immutable).
     */
    _resolveConfig(overrides) {
        const opt = overrides || {};
        const overrideHttp = opt.http || {};
        const http = {
            totalTimeoutMs: overrideHttp.totalTimeoutMs ?? this.baseHttpConfig.totalTimeoutMs,
            connectTimeoutMs: overrideHttp.connectTimeoutMs ?? this.baseHttpConfig.connectTimeoutMs,
            responseTimeoutMs: overrideHttp.responseTimeoutMs ?? this.baseHttpConfig.responseTimeoutMs,
            keepAliveTimeoutMs: overrideHttp.keepAliveTimeoutMs ?? this.baseHttpConfig.keepAliveTimeoutMs,
            proxy: overrideHttp.proxy ?? this.baseHttpConfig.proxy,
            caCert: overrideHttp.caCert ?? this.baseHttpConfig.caCert,
        };
        const ffi = proto_1.types.FfiOptions.create({
            environment: this.config.options?.environment ?? proto_1.types.Environment.SANDBOX,
            connectorConfig: this.config.connectorConfig,
        });
        return { ffi, http };
    }
    /**
     * Get or create a cached dispatcher based on the effective proxy configuration.
     */
    _getOrCreateDispatcher(httpConfig) {
        const cacheKey = (0, http_client_1.generateProxyCacheKey)(httpConfig.proxy);
        // Fast path - check cache
        const cached = this.dispatcherCache.get(cacheKey);
        if (cached !== undefined) {
            return cached;
        }
        // Slow path - create new dispatcher
        const newDispatcher = (0, http_client_1.createDispatcher)(httpConfig);
        this.dispatcherCache.set(cacheKey, newDispatcher);
        return newDispatcher;
    }
    /**
     * Execute a full round-trip for any registered payment flow.
     *
     * @param flow - Flow name matching the FFI transformer prefix (e.g. "authorize").
     * @param requestMsg - Protobuf request message object.
     * @param options - Optional ConfigOptions override (Environment, Http).
     */
    async _executeFlow(flow, requestMsg, options, reqTypeName, resTypeName) {
        const reqType = reqTypeName ? v2[reqTypeName] : undefined;
        const resType = resTypeName ? v2[resTypeName] : undefined;
        if (!reqType || !resType) {
            throw new Error(`Unknown flow: '${flow}' or missing type names.`);
        }
        // 1. Resolve final configuration
        const { ffi, http } = this._resolveConfig(options);
        const optionsBytes = Buffer.from(v2.FfiOptions.encode(ffi).finish());
        // 2. Serialize domain request
        const requestBytes = Buffer.from(reqType.encode(requestMsg).finish());
        // 3. Build connector HTTP request via FFI
        const resultBytes = this.uniffi.callReq(flow, requestBytes, optionsBytes);
        const connectorReq = v2.FfiConnectorHttpRequest.decode(resultBytes);
        const connectorRequest = {
            url: connectorReq.url,
            method: connectorReq.method,
            headers: connectorReq.headers || {},
            body: connectorReq.body ?? undefined
        };
        // 4. Get or create cached dispatcher based on effective proxy config
        const dispatcher = this._getOrCreateDispatcher(http);
        // 5. Execute HTTP using the cached connection pool
        const response = await (0, http_client_1.execute)(connectorRequest, http, dispatcher);
        // 6. Encode HTTP response for FFI
        const resProto = v2.FfiConnectorHttpResponse.create({
            statusCode: response.statusCode,
            headers: response.headers,
            body: response.body
        });
        const resBytes = Buffer.from(v2.FfiConnectorHttpResponse.encode(resProto).finish());
        // 7. Parse connector response via FFI and decode
        const resultBytesRes = this.uniffi.callRes(flow, resBytes, requestBytes, optionsBytes);
        // callRes returns FfiConnectorHttpResponse, extract domain response from body
        const httpResponse = v2.FfiConnectorHttpResponse.decode(resultBytesRes);
        return resType.decode(httpResponse.body);
    }
    /**
     * Execute a single-step flow directly via FFI (no HTTP round-trip).
     * Used for inbound flows like webhook processing where the connector sends data to us.
     */
    async _executeDirect(flow, requestMsg, options, reqTypeName, resTypeName) {
        const reqType = reqTypeName ? v2[reqTypeName] : undefined;
        const resType = resTypeName ? v2[resTypeName] : undefined;
        if (!reqType || !resType) {
            throw new Error(`Unknown flow: '${flow}' or missing type names.`);
        }
        // 1. Serialize request
        const requestBytes = Buffer.from(reqType.encode(requestMsg).finish());
        // 2. Resolve FFI options from identity + defaults + request override
        const { ffi } = this._resolveConfig(options);
        const optionsBytes = Buffer.from(v2.FfiOptions.encode(ffi).finish());
        // 3. Call the single-step transformer directly (no HTTP)
        const resultBytes = this.uniffi.callDirect(flow, requestBytes, optionsBytes);
        return resType.decode(resultBytes);
    }
}
exports.ConnectorClient = ConnectorClient;
//# sourceMappingURL=connector_client.js.map