import { asNimiError, createNimiError } from '../errors.js';
import { ReasonCode } from '../../types/index.js';
const DEFAULT_COMMAND_NAMESPACE = 'runtime_bridge';
const DEFAULT_EVENT_NAMESPACE = 'runtime_bridge';
function readGlobalTauriInvoke() {
    const value = globalThis;
    const fromWindowTestHook = value.window?.__NIMI_TAURI_TEST__?.invoke;
    if (typeof fromWindowTestHook === 'function') {
        return fromWindowTestHook.bind(value.window?.__NIMI_TAURI_TEST__);
    }
    const fromGlobalTestHook = value.__NIMI_TAURI_TEST__?.invoke;
    if (typeof fromGlobalTestHook === 'function') {
        return fromGlobalTestHook.bind(value.__NIMI_TAURI_TEST__);
    }
    const fromWindowRuntimeHook = value.window?.__NIMI_TAURI_RUNTIME__?.invoke;
    if (typeof fromWindowRuntimeHook === 'function') {
        return fromWindowRuntimeHook.bind(value.window?.__NIMI_TAURI_RUNTIME__);
    }
    const fromGlobalRuntimeHook = value.__NIMI_TAURI_RUNTIME__?.invoke;
    if (typeof fromGlobalRuntimeHook === 'function') {
        return fromGlobalRuntimeHook.bind(value.__NIMI_TAURI_RUNTIME__);
    }
    const fromWindowInternals = value.window?.__TAURI_INTERNALS__?.invoke;
    if (typeof fromWindowInternals === 'function') {
        return fromWindowInternals.bind(value.window?.__TAURI_INTERNALS__);
    }
    const fromGlobalInternals = value.__TAURI_INTERNALS__?.invoke;
    if (typeof fromGlobalInternals === 'function') {
        return fromGlobalInternals.bind(value.__TAURI_INTERNALS__);
    }
    const fromWindowIpc = value.window?.__TAURI_IPC__?.invoke;
    if (typeof fromWindowIpc === 'function') {
        return fromWindowIpc.bind(value.window?.__TAURI_IPC__);
    }
    const fromGlobalIpc = value.__TAURI_IPC__?.invoke;
    if (typeof fromGlobalIpc === 'function') {
        return fromGlobalIpc.bind(value.__TAURI_IPC__);
    }
    const fromWindow = value.window?.__TAURI__?.core?.invoke;
    if (typeof fromWindow === 'function') {
        return fromWindow.bind(value.window?.__TAURI__?.core);
    }
    const fromGlobal = value.__TAURI__?.core?.invoke;
    if (typeof fromGlobal === 'function') {
        return fromGlobal.bind(value.__TAURI__?.core);
    }
    return null;
}
function readGlobalTauriListen() {
    const value = globalThis;
    const fromWindowTestHook = value.window?.__NIMI_TAURI_TEST__?.listen;
    if (typeof fromWindowTestHook === 'function') {
        return fromWindowTestHook.bind(value.window?.__NIMI_TAURI_TEST__);
    }
    const fromGlobalTestHook = value.__NIMI_TAURI_TEST__?.listen;
    if (typeof fromGlobalTestHook === 'function') {
        return fromGlobalTestHook.bind(value.__NIMI_TAURI_TEST__);
    }
    const fromWindowRuntimeHook = value.window?.__NIMI_TAURI_RUNTIME__?.listen;
    if (typeof fromWindowRuntimeHook === 'function') {
        return fromWindowRuntimeHook.bind(value.window?.__NIMI_TAURI_RUNTIME__);
    }
    const fromGlobalRuntimeHook = value.__NIMI_TAURI_RUNTIME__?.listen;
    if (typeof fromGlobalRuntimeHook === 'function') {
        return fromGlobalRuntimeHook.bind(value.__NIMI_TAURI_RUNTIME__);
    }
    const fromWindowInternals = value.window?.__TAURI_INTERNALS__?.listen;
    if (typeof fromWindowInternals === 'function') {
        return fromWindowInternals.bind(value.window?.__TAURI_INTERNALS__);
    }
    const fromGlobalInternals = value.__TAURI_INTERNALS__?.listen;
    if (typeof fromGlobalInternals === 'function') {
        return fromGlobalInternals.bind(value.__TAURI_INTERNALS__);
    }
    const fromWindowIpc = value.window?.__TAURI_IPC__?.listen;
    if (typeof fromWindowIpc === 'function') {
        return fromWindowIpc.bind(value.window?.__TAURI_IPC__);
    }
    const fromGlobalIpc = value.__TAURI_IPC__?.listen;
    if (typeof fromGlobalIpc === 'function') {
        return fromGlobalIpc.bind(value.__TAURI_IPC__);
    }
    const fromWindow = value.window?.__TAURI__?.event?.listen;
    if (typeof fromWindow === 'function') {
        return fromWindow.bind(value.window?.__TAURI__?.event);
    }
    const fromGlobal = value.__TAURI__?.event?.listen;
    if (typeof fromGlobal === 'function') {
        return fromGlobal.bind(value.__TAURI__?.event);
    }
    return null;
}
function ensureTauriInvoke() {
    const invoke = readGlobalTauriInvoke();
    if (invoke) {
        return invoke;
    }
    throw createNimiError({
        message: 'tauri-ipc transport is unavailable (missing window.__TAURI__.core.invoke)',
        reasonCode: ReasonCode.SDK_RUNTIME_TAURI_INVOKE_MISSING,
        actionHint: 'use_tauri_shell_or_switch_transport',
        source: 'sdk',
    });
}
function ensureTauriListen() {
    const listen = readGlobalTauriListen();
    if (listen) {
        return listen;
    }
    throw createNimiError({
        message: 'tauri-ipc transport is unavailable (missing window.__TAURI__.event.listen)',
        reasonCode: ReasonCode.SDK_RUNTIME_TAURI_LISTEN_MISSING,
        actionHint: 'use_tauri_shell_or_switch_transport',
        source: 'sdk',
    });
}
function createCommandName(config, suffix) {
    const prefix = String(config.commandNamespace || DEFAULT_COMMAND_NAMESPACE).trim();
    return `${prefix}_${suffix}`;
}
function createEventName(config, streamId) {
    const prefix = String(config.eventNamespace || DEFAULT_EVENT_NAMESPACE).trim();
    return `${prefix}:stream:${streamId}`;
}
async function invokeCommand(invoke, config, suffix, payload) {
    const command = createCommandName(config, suffix);
    return invoke(command, { payload });
}
function asObject(value) {
    if (!value || typeof value !== 'object' || Array.isArray(value)) {
        return {};
    }
    return value;
}
function normalizeResponseMetadata(response) {
    const candidates = [response.responseMetadata, response.response_metadata];
    for (const metadata of candidates) {
        if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)) {
            continue;
        }
        if (Object.keys(metadata).length > 0) {
            return metadata;
        }
    }
    const metadata = candidates.find((item) => item && typeof item === 'object' && !Array.isArray(item));
    if (!metadata) {
        return null;
    }
    return metadata;
}
function toBase64(bytes) {
    if (typeof Buffer !== 'undefined') {
        return Buffer.from(bytes).toString('base64');
    }
    let binary = '';
    for (let index = 0; index < bytes.length; index += 1) {
        binary += String.fromCharCode(bytes[index] || 0);
    }
    if (typeof btoa === 'function') {
        return btoa(binary);
    }
    throw createNimiError({
        message: 'base64 encoder unavailable in current runtime',
        reasonCode: ReasonCode.SDK_RUNTIME_BASE64_ENCODER_UNAVAILABLE,
        actionHint: 'use_node_or_tauri_runtime',
        source: 'sdk',
    });
}
function fromBase64(value) {
    const normalized = String(value || '').trim();
    if (!normalized) {
        return new Uint8Array(0);
    }
    if (typeof Buffer !== 'undefined') {
        return Uint8Array.from(Buffer.from(normalized, 'base64'));
    }
    if (typeof atob === 'function') {
        const binary = atob(normalized);
        const bytes = new Uint8Array(binary.length);
        for (let index = 0; index < binary.length; index += 1) {
            bytes[index] = binary.charCodeAt(index);
        }
        return bytes;
    }
    throw createNimiError({
        message: 'base64 decoder unavailable in current runtime',
        reasonCode: ReasonCode.SDK_RUNTIME_BASE64_DECODER_UNAVAILABLE,
        actionHint: 'use_node_or_tauri_runtime',
        source: 'sdk',
    });
}
function normalizeRequestBytes(input) {
    if (input instanceof Uint8Array) {
        return input;
    }
    if (input instanceof ArrayBuffer) {
        return new Uint8Array(input);
    }
    if (input instanceof DataView) {
        return new Uint8Array(input.buffer, input.byteOffset, input.byteLength);
    }
    if (input
        && typeof input === 'object'
        && 'buffer' in input
        && input.buffer instanceof ArrayBuffer
        && typeof input.byteOffset === 'number'
        && typeof input.byteLength === 'number') {
        const source = input;
        return new Uint8Array(source.buffer, source.byteOffset, source.byteLength);
    }
    throw createNimiError({
        message: 'runtime request must be protobuf bytes (Uint8Array)',
        reasonCode: ReasonCode.SDK_RUNTIME_REQUEST_BYTES_REQUIRED,
        actionHint: 'encode_protobuf_request_first',
        source: 'sdk',
    });
}
export function createTauriIpcTransport(config) {
    const internalConfig = config;
    const openStreams = new Map();
    return {
        invokeUnary: async (input) => {
            const invoke = ensureTauriInvoke();
            try {
                const result = await invokeCommand(invoke, internalConfig, 'unary', {
                    methodId: input.methodId,
                    requestBytesBase64: toBase64(normalizeRequestBytes(input.request)),
                    metadata: input.metadata,
                    authorization: input.authorization,
                    protectedAccessToken: input.protectedAccessToken,
                    appSession: input.appSession,
                    timeoutMs: input.timeoutMs,
                });
                const response = asObject(result);
                if (typeof response.responseBytesBase64 !== 'string') {
                    throw createNimiError({
                        message: 'tauri unary response missing responseBytesBase64',
                        reasonCode: ReasonCode.SDK_RUNTIME_TAURI_UNARY_BYTES_MISSING,
                        actionHint: 'check_runtime_bridge_payload_contract',
                        source: 'runtime',
                    });
                }
                const observer = input._responseMetadataObserver || internalConfig._responseMetadataObserver;
                const meta = normalizeResponseMetadata(response);
                if (observer && meta && Object.keys(meta).length > 0) {
                    observer(meta);
                }
                return fromBase64(response.responseBytesBase64);
            }
            catch (error) {
                throw asNimiError(error, {
                    reasonCode: ReasonCode.SDK_RUNTIME_TAURI_UNARY_FAILED,
                    actionHint: 'check_runtime_bridge_and_daemon',
                    source: 'runtime',
                });
            }
        },
        openStream: async (input) => {
            const invoke = ensureTauriInvoke();
            const listen = ensureTauriListen();
            let streamId = '';
            let unsubscribe = null;
            const queue = [];
            const waiters = [];
            let done = false;
            let pendingError = null;
            let closed = false;
            let detachAbortListener = null;
            const releaseAbortListener = () => {
                if (!detachAbortListener) {
                    return;
                }
                detachAbortListener();
                detachAbortListener = null;
            };
            const closeRemoteStream = async () => {
                if (!streamId) {
                    return;
                }
                try {
                    await invokeCommand(invoke, internalConfig, 'stream_close', { streamId });
                }
                catch {
                    // best effort
                }
            };
            const flush = () => {
                while (waiters.length > 0 && queue.length > 0) {
                    const waiter = waiters.shift();
                    const nextValue = queue.shift();
                    if (waiter && nextValue !== undefined) {
                        waiter.resolve({ done: false, value: nextValue });
                    }
                }
                if (pendingError) {
                    while (waiters.length > 0) {
                        const waiter = waiters.shift();
                        if (waiter) {
                            waiter.reject(pendingError);
                        }
                    }
                    return;
                }
                if (done) {
                    while (waiters.length > 0) {
                        const waiter = waiters.shift();
                        if (waiter) {
                            waiter.resolve({ done: true, value: undefined });
                        }
                    }
                }
            };
            const onStreamError = (error) => {
                pendingError = asNimiError(error, {
                    reasonCode: ReasonCode.SDK_RUNTIME_TAURI_STREAM_FAILED,
                    actionHint: 'retry_or_reopen_stream',
                    source: 'runtime',
                });
                done = true;
                if (streamId) {
                    openStreams.delete(streamId);
                }
                releaseAbortListener();
                if (unsubscribe) {
                    unsubscribe();
                    unsubscribe = null;
                }
                void closeRemoteStream();
                flush();
            };
            try {
                const opened = await invokeCommand(invoke, internalConfig, 'stream_open', {
                    methodId: input.methodId,
                    requestBytesBase64: toBase64(normalizeRequestBytes(input.request)),
                    metadata: input.metadata,
                    authorization: input.authorization,
                    protectedAccessToken: input.protectedAccessToken,
                    appSession: input.appSession,
                    timeoutMs: input.timeoutMs,
                    eventNamespace: internalConfig.eventNamespace,
                });
                const response = asObject(opened);
                streamId = String(response.streamId || '').trim();
                if (!streamId) {
                    throw createNimiError({
                        message: 'tauri stream open did not return streamId',
                        reasonCode: ReasonCode.SDK_RUNTIME_TAURI_STREAM_ID_MISSING,
                        actionHint: 'check_runtime_bridge',
                        source: 'runtime',
                    });
                }
                unsubscribe = await Promise.resolve(listen(createEventName(internalConfig, streamId), (event) => {
                    const payload = asObject(event.payload);
                    const eventType = String(payload.eventType || '').trim();
                    if (eventType === 'next') {
                        queue.push(fromBase64(String(payload.payloadBytesBase64 || '')));
                        flush();
                        return;
                    }
                    if (eventType === 'error') {
                        onStreamError(payload.error || createNimiError({
                            message: 'runtime stream reported error',
                            reasonCode: ReasonCode.SDK_RUNTIME_TAURI_STREAM_REMOTE_ERROR,
                            actionHint: 'retry_or_reopen_stream',
                            source: 'runtime',
                        }));
                        return;
                    }
                    if (eventType === 'completed') {
                        done = true;
                        releaseAbortListener();
                        openStreams.delete(streamId);
                        if (unsubscribe) {
                            unsubscribe();
                            unsubscribe = null;
                        }
                        flush();
                    }
                }));
            }
            catch (error) {
                releaseAbortListener();
                if (unsubscribe) {
                    unsubscribe();
                }
                await closeRemoteStream();
                throw asNimiError(error, {
                    reasonCode: ReasonCode.SDK_RUNTIME_TAURI_STREAM_OPEN_FAILED,
                    actionHint: 'check_runtime_bridge_and_daemon',
                    source: 'runtime',
                });
            }
            const close = async () => {
                if (closed) {
                    return;
                }
                closed = true;
                done = true;
                flush();
                releaseAbortListener();
                if (streamId) {
                    openStreams.delete(streamId);
                }
                if (unsubscribe) {
                    unsubscribe();
                    unsubscribe = null;
                }
                await closeRemoteStream();
            };
            if (streamId) {
                openStreams.set(streamId, close);
            }
            if (input.signal) {
                if (input.signal.aborted) {
                    await close();
                }
                else {
                    const abortHandler = () => {
                        void close();
                    };
                    input.signal.addEventListener('abort', abortHandler, { once: true });
                    detachAbortListener = () => {
                        input.signal?.removeEventListener('abort', abortHandler);
                    };
                }
            }
            const iterator = {
                [Symbol.asyncIterator]() {
                    return {
                        next: async () => {
                            if (pendingError) {
                                const error = pendingError;
                                pendingError = null;
                                throw error;
                            }
                            if (queue.length > 0) {
                                const value = queue.shift();
                                return { done: false, value };
                            }
                            if (done) {
                                return { done: true, value: undefined };
                            }
                            return new Promise((resolve, reject) => {
                                waiters.push({
                                    resolve,
                                    reject,
                                });
                            });
                        },
                        return: async () => {
                            await close();
                            return { done: true, value: undefined };
                        },
                        throw: async (error) => {
                            await close();
                            throw error;
                        },
                    };
                },
            };
            return iterator;
        },
        closeStream: async (input) => {
            const invoke = ensureTauriInvoke();
            try {
                await invokeCommand(invoke, internalConfig, 'stream_close', {
                    streamId: input.streamId,
                });
            }
            catch (error) {
                throw asNimiError(error, {
                    reasonCode: ReasonCode.SDK_RUNTIME_TAURI_STREAM_CLOSE_FAILED,
                    actionHint: 'retry_close_or_drop_stream',
                    source: 'runtime',
                });
            }
        },
        destroy: async () => {
            const closers = Array.from(openStreams.values());
            openStreams.clear();
            await Promise.allSettled(closers.map((close) => close()));
        },
    };
}
//# sourceMappingURL=tauri-ipc.js.map