"use strict";
/**
 * UniFFI client for Node.js — calls the same shared library as Python/Kotlin.
 *
 * Uses koffi to call the UniFFI C ABI directly, replacing NAPI entirely.
 * Handles RustBuffer serialization/deserialization for the UniFFI protocol.
 *
 * Flow dispatch is generic: callReq(flow, ...) and callRes(flow, ...) load
 * the corresponding C symbol dynamically from the flow list in _generated_flows.js.
 * No flow names are hardcoded here — add new flows to flows.yaml and run `make generate`.
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.UniffiClient = void 0;
const koffi_1 = __importDefault(require("koffi"));
const path_1 = __importDefault(require("path"));
// @ts-ignore - generated CommonJS module
const _generated_flows_js_1 = require("./_generated_flows.js");
// @ts-ignore - generated protobuf types
const proto_js_1 = require("./generated/proto.js");
const errors_1 = require("./errors");
const _dirname = __dirname;
const FLOW_NAMES = Object.keys(_generated_flows_js_1.FLOWS);
const SINGLE_FLOW_NAMES = Object.keys((_generated_flows_js_1.SINGLE_FLOWS || {}));
const RustBufferStruct = koffi_1.default.struct("RustBuffer", {
    capacity: "uint64",
    len: "uint64",
    data: "void *",
});
const RustCallStatusStruct = koffi_1.default.struct("RustCallStatus", {
    code: "int8",
    error_buf: RustBufferStruct,
});
function loadLib(libPath) {
    if (!libPath) {
        const ext = process.platform === "darwin" ? "dylib" : "so";
        libPath = path_1.default.join(_dirname, "generated", `libconnector_service_ffi.${ext}`);
    }
    const lib = koffi_1.default.load(libPath);
    const fns = {
        alloc: lib.func("ffi_connector_service_ffi_rustbuffer_alloc", RustBufferStruct, ["uint64", koffi_1.default.out(koffi_1.default.pointer(RustCallStatusStruct))]),
        free: lib.func("ffi_connector_service_ffi_rustbuffer_free", "void", [RustBufferStruct, koffi_1.default.out(koffi_1.default.pointer(RustCallStatusStruct))]),
    };
    // Load req and res transformer symbols for every registered flow.
    for (const flow of FLOW_NAMES) {
        fns[`${flow}_req`] = lib.func(`uniffi_connector_service_ffi_fn_func_${flow}_req_transformer`, RustBufferStruct, [RustBufferStruct, RustBufferStruct, koffi_1.default.out(koffi_1.default.pointer(RustCallStatusStruct))]);
        fns[`${flow}_res`] = lib.func(`uniffi_connector_service_ffi_fn_func_${flow}_res_transformer`, RustBufferStruct, [RustBufferStruct, RustBufferStruct, RustBufferStruct, koffi_1.default.out(koffi_1.default.pointer(RustCallStatusStruct))]);
    }
    // Load single-step transformer symbols (no HTTP round-trip, e.g. webhook processing).
    for (const flow of SINGLE_FLOW_NAMES) {
        fns[`${flow}_direct`] = lib.func(`uniffi_connector_service_ffi_fn_func_${flow}_transformer`, RustBufferStruct, [RustBufferStruct, RustBufferStruct, koffi_1.default.out(koffi_1.default.pointer(RustCallStatusStruct))]);
    }
    return fns;
}
// ── Helpers ──────────────────────────────────────────────────────
function makeCallStatus() {
    return { code: 0, error_buf: { capacity: 0n, len: 0n, data: null } };
}
function checkCallStatus(ffi, status) {
    if (status.code === 0)
        return;
    // Only Rust panics should reach here now (status.code === 2)
    // Normal errors are encoded as protobuf IntegrationError/ConnectorError in returned bytes
    if (status.error_buf.len > 0n) {
        const msg = liftString(status.error_buf);
        freeRustBuffer(ffi, status.error_buf);
        throw new Error(`Rust panic: ${msg}`);
    }
    throw new Error("Unknown Rust panic");
}
/**
 * UniFFI Strings are serialized as raw UTF8 bytes when top-level in RustBuffer.
 */
function liftString(buf) {
    if (!buf.data || buf.len === 0n)
        return "";
    const raw = Buffer.from(koffi_1.default.decode(buf.data, "uint8", Number(buf.len)));
    return raw.toString("utf-8");
}
/**
 * UniFFI Vec<u8> (Bytes) as return values are serialized as [i32 length] + [raw bytes]
 */
function liftBytes(buf) {
    if (!buf.data || buf.len === 0n)
        return Buffer.alloc(0);
    const raw = Buffer.from(koffi_1.default.decode(buf.data, "uint8", Number(buf.len)));
    // UniFFI protocol for return values: first 4 bytes are the length of the actual payload
    const len = raw.readInt32BE(0);
    return raw.subarray(4, 4 + len);
}
function freeRustBuffer(ffi, buf) {
    if (buf.data && buf.len > 0n) {
        ffi.free(buf, makeCallStatus());
    }
}
function allocRustBuffer(ffi, data) {
    const status = makeCallStatus();
    const buf = ffi.alloc(BigInt(data.length), status);
    checkCallStatus(ffi, status);
    koffi_1.default.encode(buf.data, "uint8", Array.from(data), data.length);
    buf.len = BigInt(data.length);
    return buf;
}
/**
 * Lowers raw bytes into a UniFFI-compliant buffer for top-level arguments.
 * Protocol: [i32 length prefix] + [raw bytes]
 */
function lowerBytes(ffi, data) {
    const buf = Buffer.alloc(4 + data.length);
    buf.writeInt32BE(data.length, 0);
    Buffer.from(data).copy(buf, 4);
    return allocRustBuffer(ffi, buf);
}
class UniffiClient {
    _ffi;
    constructor(libPath) {
        this._ffi = loadLib(libPath);
    }
    /**
     * Build the connector HTTP request for any flow.
     * Returns protobuf-encoded FfiConnectorHttpRequest bytes.
     */
    callReq(flow, requestBytes, optionsBytes) {
        const fn = this._ffi[`${flow}_req`];
        if (!fn)
            throw new Error(`Unknown flow: '${flow}'. Supported: ${FLOW_NAMES.join(", ")}`);
        const rbReq = lowerBytes(this._ffi, requestBytes);
        const rbOpts = lowerBytes(this._ffi, optionsBytes);
        const status = makeCallStatus();
        const result = fn(rbReq, rbOpts, status);
        try {
            checkCallStatus(this._ffi, status);
            const bytes = liftBytes(result);
            const resultMsg = proto_js_1.types.FfiResult.decode(bytes);
            // Enum-based type checking
            switch (resultMsg.type) {
                case proto_js_1.types.FfiResult.Type.HTTP_REQUEST:
                    if (!resultMsg.httpRequest) {
                        throw new Error("Expected httpRequest in FfiResult, but got null/undefined");
                    }
                    return Buffer.from(proto_js_1.types.FfiConnectorHttpRequest.encode(resultMsg.httpRequest).finish());
                case proto_js_1.types.FfiResult.Type.INTEGRATION_ERROR:
                    if (!resultMsg.integrationError) {
                        throw new Error("Expected integrationError in FfiResult, but got null/undefined");
                    }
                    throw new errors_1.IntegrationError(resultMsg.integrationError);
                case proto_js_1.types.FfiResult.Type.CONNECTOR_ERROR:
                    if (!resultMsg.connectorError) {
                        throw new Error("Expected connectorError in FfiResult, but got null/undefined");
                    }
                    throw new errors_1.ConnectorError(resultMsg.connectorError);
                default:
                    throw new Error(`Unknown result type: ${resultMsg.type}`);
            }
        }
        finally {
            freeRustBuffer(this._ffi, result);
        }
    }
    /**
     * Parse the connector HTTP response for any flow.
     * responseBytes: protobuf-encoded FfiConnectorHttpResponse.
     * Returns protobuf-encoded response bytes for the flow's response type.
     */
    callRes(flow, responseBytes, requestBytes, optionsBytes) {
        const fn = this._ffi[`${flow}_res`];
        if (!fn)
            throw new Error(`Unknown flow: '${flow}'. Supported: ${FLOW_NAMES.join(", ")}`);
        const rbRes = lowerBytes(this._ffi, responseBytes);
        const rbReq = lowerBytes(this._ffi, requestBytes);
        const rbOpts = lowerBytes(this._ffi, optionsBytes);
        const status = makeCallStatus();
        const result = fn(rbRes, rbReq, rbOpts, status);
        try {
            checkCallStatus(this._ffi, status);
            const bytes = liftBytes(result);
            const resultMsg = proto_js_1.types.FfiResult.decode(bytes);
            // Enum-based type checking
            switch (resultMsg.type) {
                case proto_js_1.types.FfiResult.Type.HTTP_RESPONSE:
                    if (!resultMsg.httpResponse) {
                        throw new Error("Expected httpResponse in FfiResult, but got null/undefined");
                    }
                    return Buffer.from(proto_js_1.types.FfiConnectorHttpResponse.encode(resultMsg.httpResponse).finish());
                case proto_js_1.types.FfiResult.Type.CONNECTOR_ERROR:
                    if (!resultMsg.connectorError) {
                        throw new Error("Expected connectorError in FfiResult, but got null/undefined");
                    }
                    throw new errors_1.ConnectorError(resultMsg.connectorError);
                case proto_js_1.types.FfiResult.Type.INTEGRATION_ERROR:
                    if (!resultMsg.integrationError) {
                        throw new Error("Expected integrationError in FfiResult, but got null/undefined");
                    }
                    throw new errors_1.IntegrationError(resultMsg.integrationError);
                default:
                    throw new Error(`Unknown result type: ${resultMsg.type}`);
            }
        }
        finally {
            freeRustBuffer(this._ffi, result);
        }
    }
    /**
     * Execute a single-step transformer directly (no HTTP round-trip).
     * Used for inbound flows like webhook processing.
     * Returns protobuf-encoded response bytes.
     */
    callDirect(flow, requestBytes, optionsBytes) {
        const fn = this._ffi[`${flow}_direct`];
        if (!fn)
            throw new Error(`Unknown single-step flow: '${flow}'. Supported: ${SINGLE_FLOW_NAMES.join(", ")}`);
        const rbReq = lowerBytes(this._ffi, requestBytes);
        const rbOpts = lowerBytes(this._ffi, optionsBytes);
        const status = makeCallStatus();
        const result = fn(rbReq, rbOpts, status);
        try {
            checkCallStatus(this._ffi, status);
            const bytes = liftBytes(result);
            const resultMsg = proto_js_1.types.FfiResult.decode(bytes);
            // Enum-based type checking
            switch (resultMsg.type) {
                case proto_js_1.types.FfiResult.Type.PROTO_RESPONSE:
                    if (!resultMsg.protoResponse) {
                        throw new Error("Expected protoResponse in FfiResult, but got null/undefined");
                    }
                    return Buffer.from(resultMsg.protoResponse);
                case proto_js_1.types.FfiResult.Type.CONNECTOR_ERROR:
                    if (!resultMsg.connectorError) {
                        throw new Error("Expected connectorError in FfiResult, but got null/undefined");
                    }
                    throw new errors_1.ConnectorError(resultMsg.connectorError);
                case proto_js_1.types.FfiResult.Type.INTEGRATION_ERROR:
                    if (!resultMsg.integrationError) {
                        throw new Error("Expected integrationError in FfiResult, but got null/undefined");
                    }
                    throw new errors_1.IntegrationError(resultMsg.integrationError);
                default:
                    throw new Error(`Unknown result type: ${resultMsg.type}`);
            }
        }
        finally {
            freeRustBuffer(this._ffi, result);
        }
    }
}
exports.UniffiClient = UniffiClient;
//# sourceMappingURL=uniffi_client.js.map