/**
 * WorkerTcpTransport — TcpTransport implementation for Web Workers.
 * Proxies TCP operations to the main thread via postMessage, where the
 * main thread relays them to msgapi.tcp (the C# native bridge).
 *
 * Same interface as BridgeTcpTransport, but works in a Worker context
 * where window/msgapi aren't available.
 */

/** Function to send messages to the main thread. Set during worker init. */
let postToMain: (msg: any) => void = () => { throw new Error("Worker TCP transport not initialized"); };
let reqCounter = 0;
const pendingRequests = new Map<number, { resolve: (v: any) => void; reject: (e: Error) => void }>();
const instances = new Map<number, WorkerTcpTransport>();

/** Call once from worker-entry to wire up the postMessage channel. */
export function initWorkerTcp(post: (msg: any) => void): void {
    postToMain = post;
}

/** Route incoming TCP messages from the main thread to the right transport instance. */
export function handleTcpMessage(msg: any): void {
    if (msg.type === "tcp-response") {
        const pending = pendingRequests.get(msg.reqId);
        if (pending) {
            pendingRequests.delete(msg.reqId);
            if (msg.error) pending.reject(new Error(msg.error));
            else pending.resolve(msg.result);
        }
    } else if (msg.type === "tcp-data") {
        instances.get(msg.streamId)?.dataHandler?.(msg.data);
    } else if (msg.type === "tcp-close") {
        const t = instances.get(msg.streamId);
        if (t) {
            t._connected = false;
            instances.delete(msg.streamId);
            t.closeHandler?.(msg.hadError);
        }
    } else if (msg.type === "tcp-error") {
        const t = instances.get(msg.streamId);
        if (t) {
            t.errorHandler?.(new Error(msg.message));
        }
    }
}

function tcpRequest(op: string, params: any): Promise<any> {
    const reqId = ++reqCounter;
    return new Promise((resolve, reject) => {
        pendingRequests.set(reqId, { resolve, reject });
        postToMain({ type: "tcp", op, reqId, ...params });
        // 30s timeout
        setTimeout(() => {
            if (pendingRequests.has(reqId)) {
                pendingRequests.delete(reqId);
                reject(new Error(`TCP ${op} timeout`));
            }
        }, 30000);
    });
}

export class WorkerTcpTransport {
    streamId: number | null = null;
    dataHandler: ((data: string) => void) | null = null;
    closeHandler: ((hadError: boolean) => void) | null = null;
    errorHandler: ((err: Error) => void) | null = null;
    _connected = false;

    get connected(): boolean { return this._connected; }

    async connect(host: string, port: number, tls: boolean, _servername?: string): Promise<void> {
        const streamId = await tcpRequest("connect", { host, port, tls });
        this.streamId = Number(streamId);
        this._connected = true;
        instances.set(this.streamId, this);
    }

    async upgradeTLS(servername?: string): Promise<void> {
        if (this.streamId == null) throw new Error("Not connected");
        await tcpRequest("upgradeTLS", { streamId: this.streamId, servername: servername || "" });
    }

    async write(data: string | Uint8Array): Promise<void> {
        if (this.streamId == null) throw new Error("Not connected");
        const s = typeof data === "string" ? data : new TextDecoder().decode(data);
        await tcpRequest("write", { streamId: this.streamId, data: s });
    }

    onData(handler: (data: string) => void): void { this.dataHandler = handler; }
    onClose(handler: (hadError: boolean) => void): void { this.closeHandler = handler; }
    onError(handler: (err: Error) => void): void { this.errorHandler = handler; }

    close(): void {
        if (this.streamId != null) {
            postToMain({ type: "tcp", op: "close", streamId: this.streamId });
            instances.delete(this.streamId);
            this.streamId = null;
            this._connected = false;
        }
    }
}
