/**
 * @module sync-peer-websocket-receiver-v1
 */
import { extractErrorMsg, getUUID } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
import { validateIbGibIntrinsically } from '@ibgib/ts-gib/dist/V1/validate-helper.mjs';
import { GLOBAL_LOG_A_LOT } from '../../../../core-constants.mjs';
import { SyncPeer_V1 } from '../../sync-peer-v1.mjs';
import { putInSpace, registerNewIbGib } from '../../../../witness/space/space-helper.mjs';
import { SyncWebSocketMsgType } from '../sync-peer-websocket-constants.mjs';
import { SESSION_KEYSTONE_POLICY, verifyConnectProof } from './sync-websocket-peer-helpers.mjs';
import { toDto } from '../../../../common/other/ibgib-helper.mjs';
const logalot = GLOBAL_LOG_A_LOT || true;
/**
 * Platform-agnostic WebSocket Receiver Peer.
 *
 * Fully encapsulates the multi-turn cryptographic challenge connect and runtime
 * turn routing.
 */
export class SyncPeerWebSocketReceiver_V1 extends SyncPeer_V1 {
    lc = `[${SyncPeerWebSocketReceiver_V1.name}]`;
    get classname() {
        return SyncPeerWebSocketReceiver_V1.name;
    }
    socketWrapper;
    // Connect State Machine variables
    isAuthenticated = false;
    challengeUuid;
    demandedIds;
    sessionS_tjpAddr;
    // Runtime state variables for delayed payload streaming
    pendingContext;
    pendingPayloadAddrs;
    pendingResponsePayloadsToSend = [];
    receivedPayloads = [];
    constructor(initialData, initialRel8ns) {
        super(initialData, initialRel8ns);
    }
    /**
     * Wires the peer receiver to the platform-specific socket stream and
     * triggers the connect.
     */
    async bindSocket(socketWrapper) {
        const lc = `${this.lc}[${this.bindSocket.name}]`;
        this.socketWrapper = socketWrapper;
        socketWrapper.onMessage((data) => this.handleIncomingMessage(data));
        // Immediately trigger multi-turn challenge connection
        try {
            this.challengeUuid = await getUUID();
            socketWrapper.send(JSON.stringify({
                type: SyncWebSocketMsgType.auth_challenge_init,
                challengeUuid: this.challengeUuid
            }));
        }
        catch (error) {
            console.error(`${lc} failed triggering challenge init: ${extractErrorMsg(error)}`);
            socketWrapper.send(JSON.stringify({
                type: SyncWebSocketMsgType.auth_fail,
                message: 'Internal server connect error'
            }));
        }
    }
    async preConnectCheck(opts) {
        // Handled dynamically on upgrade
    }
    async connectImpl(opts) {
        // Handled dynamically on upgrade
    }
    async postEstablishToReceiver(opts) {
        throw new Error(`postEstablishToReceiver is not supported on Receiver Peer (E: 309f3cf8e7c813d338394f28c576da26)`);
    }
    async sendContextRequest(context) {
        throw new Error(`sendContextRequest is not supported on Receiver Peer (E: e5327ed6c64883e12ef95984f1409926)`);
    }
    /** Helper to load a Session Keystone from storage */
    async getSessionKeystone(sAddr) {
        const metaspace = this.opts.localMetaspace;
        const space = this.opts.localSpace;
        const resGet = await metaspace.get({ addrs: [sAddr], space });
        if (resGet.success && resGet.ibGibs && resGet.ibGibs.length === 1) {
            return resGet.ibGibs[0];
        }
        throw new Error(`Session keystone not found in storage: ${sAddr} (E: 42315837e3b1deb5b81072f8601e6a26)`);
    }
    async ensureLocalTempSpace() {
        const lc = `${this.lc}[${this.ensureLocalTempSpace.name}]`;
        try {
            if (!this.opts) {
                throw new Error(`opts not initialized. (E: c4929fb1596833a7186d119855bb7e26)`);
            }
            if (!this.opts.localTempSpace) {
                const { localMetaspace } = this.opts;
                const uuid = crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36);
                const tempSpaceName = `tmp_sync_recv_${uuid.substring(0, 8)}`;
                const localTempSpace = await localMetaspace.createNewLocalSpace({
                    opts: {
                        allowCancel: false,
                        spaceName: tempSpaceName,
                        getFnPrompt: localMetaspace.getFnPrompt,
                        logalot
                    }
                });
                if (!localTempSpace) {
                    throw new Error(`couldn't create a temp space (E: d82461769b4bbb35df5d6a8a9c665426)`);
                }
                await localTempSpace.initialized;
                this.opts.localTempSpace = localTempSpace;
            }
            return this.opts.localTempSpace;
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            throw error;
        }
    }
    /**
     * Entry point for processing raw text frames received over the active WebSocket connection.
     */
    async handleIncomingMessage(messageText) {
        const lc = `${this.lc}[${this.handleIncomingMessage.name}]`;
        try {
            if (!this.socketWrapper) {
                throw new Error(`WebSocket wrapper not bound to receiver peer (E: ad4c5838d1b87259586b21a89b2c7726)`);
            }
            const msg = JSON.parse(messageText);
            if (logalot) {
                console.log(`${lc} received frame: ${msg.type}`);
            }
            // 1. Connect Phase Route Guard
            if (!this.isAuthenticated) {
                await this.handleConnectFrame(msg);
                return; /* <<<< returns early */
            }
            // 2. Authenticated Runtime Sync Route
            if (msg.type === SyncWebSocketMsgType.domain_payload) {
                const ibGib = msg.ibGib;
                const validationErrors = await validateIbGibIntrinsically({ ibGib }) ?? [];
                if (validationErrors.length > 0) {
                    throw new Error(`controlIbGibs invalid intrinsically. validationErrors: ${validationErrors.join('|')} (E: 5ee1787d4cc53d3d2c55f3d4f2865226)`);
                }
                const pendingPayloadAddrs = this.pendingPayloadAddrs;
                if (this.pendingContext && pendingPayloadAddrs) {
                    const addr = getIbGibAddr({ ibGib });
                    if (pendingPayloadAddrs.has(addr)) {
                        const tempSpace = await this.ensureLocalTempSpace();
                        await putInSpace({ space: tempSpace, ibGibs: [ibGib] });
                        this.receivedPayloads.push(ibGib);
                        pendingPayloadAddrs.delete(addr);
                        // If all expected payloads for the pending context are received
                        if (pendingPayloadAddrs.size === 0 && this.pendingPayloadAddrs !== undefined) {
                            const context = this.pendingContext;
                            context.payloadIbGibsDomain = this.receivedPayloads;
                            this.receivedPayloads = [];
                            this.pendingContext = undefined;
                            this.pendingPayloadAddrs = undefined;
                            await this.executeIncomingSyncRequestAndRespond({ context });
                        }
                    }
                    else {
                        console.warn(`${lc} received payload not in expected list: ${addr}`);
                    }
                }
            }
            else if (msg.type === SyncWebSocketMsgType.sync_frame) {
                const context = msg.context;
                // First, validate and authenticate the context
                const allControlIbGibs = [
                    toDto({ ibGib: context }),
                    context.sagaFrame,
                    context.sagaFrameMsg
                ];
                if (context.signedSessionIdentity) {
                    allControlIbGibs.push(context.signedSessionIdentity);
                }
                for (const controlIbGib of allControlIbGibs) {
                    const validationErrors = await validateIbGibIntrinsically({ ibGib: controlIbGib }) ?? [];
                    if (validationErrors.length > 0) {
                        throw new Error(`controlIbGibs invalid intrinsically. validationErrors: ${validationErrors.join('|')} (E: d40dfa87265a0b73c8ef784d1265ea26)`);
                    }
                }
                await this.authenticateAndValidate({ context });
                // Put control ibgibs into durable space immediately for audit trail
                for (const ibGib of allControlIbGibs) {
                    await putInSpace({ space: this.opts.localSpace, ibGibs: [ibGib] });
                    await registerNewIbGib({ space: this.opts.localSpace, ibGib });
                }
                // Check if payloads are expected
                const expectedPayloadAddrs = context.data?.['@payloadAddrsDomain'] || [];
                if (expectedPayloadAddrs.length > 0) {
                    // Set up wait state and request payloads
                    this.pendingContext = context;
                    this.pendingPayloadAddrs = new Set(expectedPayloadAddrs);
                    this.receivedPayloads = [];
                    this.socketWrapper.send(JSON.stringify({
                        type: SyncWebSocketMsgType.sync_frame_authenticated,
                        contextAddr: getIbGibAddr({ ibGib: context })
                    }));
                }
                else {
                    // No payloads expected, process immediately
                    await this.executeIncomingSyncRequestAndRespond({ context });
                }
            }
            else if (msg.type === SyncWebSocketMsgType.sync_frame_response_authenticated) {
                // Alice authenticated our response context, stream our payloads now
                const payloads = this.pendingResponsePayloadsToSend || [];
                this.pendingResponsePayloadsToSend = [];
                for (const ibGib of payloads) {
                    this.socketWrapper.send(JSON.stringify({
                        type: SyncWebSocketMsgType.domain_payload,
                        ibGib
                    }));
                }
            }
            else {
                throw new Error(`(UNEXPECTED) msg.type is ${msg.type}? not expected at this time (E: bad7c4fa1958b1976904cb884e624826)`);
            }
        }
        catch (error) {
            console.error(`${lc} message frame handling failed: ${extractErrorMsg(error)}`);
            try {
                this.socketWrapper?.send(JSON.stringify({
                    type: this.isAuthenticated ? SyncWebSocketMsgType.sync_error : SyncWebSocketMsgType.auth_fail,
                    message: extractErrorMsg(error)
                }));
            }
            catch (nestedError) {
                console.error(`${lc}[nested catch] failed to send error frame: ${extractErrorMsg(nestedError)}`);
            }
            finally {
                this.socketWrapper?.close();
            }
        }
    }
    /**
     * Handles the multi-turn cryptographic challenge connect messages.
     */
    async handleConnectFrame(msg) {
        const lc = `${this.lc}[${this.handleConnectFrame.name}]`;
        const metaspace = this.opts.localMetaspace;
        const space = this.opts.localSpace;
        if (msg.type === SyncWebSocketMsgType.auth_init) {
            const { sAddr } = msg;
            if (logalot) {
                console.log(`${lc} auth-init for ${sAddr}`);
            }
            const authorizedS = await this.getSessionKeystone(sAddr);
            const connectPool = (authorizedS.data?.challengePools ?? [])
                .find(p => p.id === SESSION_KEYSTONE_POLICY.CONNECT_POOL.ID);
            if (!connectPool) {
                throw new Error(`Session keystone missing "${SESSION_KEYSTONE_POLICY.CONNECT_POOL.ID}" pool (E: 66b2a2e32db8f03168f6f2a8b746cb26)`);
            }
            // Demand random challenges
            const challengeIds = Object.keys(connectPool.challenges);
            this.demandedIds = challengeIds.sort(() => 0.5 - Math.random()).slice(0, SESSION_KEYSTONE_POLICY.CONNECT_POOL.SERVER_DEMAND_COUNT);
            // Resolve secure S TJP
            const past = authorizedS.rel8ns?.past;
            this.sessionS_tjpAddr = (past && past.length > 0) ? past[0] : getIbGibAddr({ ibGib: authorizedS });
            this.socketWrapper.send(JSON.stringify({
                type: SyncWebSocketMsgType.auth_challenge,
                challengeUuid: this.challengeUuid,
                demandedIds: this.demandedIds
            }));
        }
        else if (msg.type === SyncWebSocketMsgType.auth_proof) {
            const { proofFrame } = msg;
            if (logalot) {
                console.log(`${lc} verifying auth-proof...`);
            }
            if (!this.sessionS_tjpAddr) {
                throw new Error(`Missing active connect session TJP address (E: bf271853de61a04b6d05d6889263f826)`);
            }
            const sAddr_latest = await metaspace.getLatestAddr({ tjpAddr: this.sessionS_tjpAddr, space });
            if (!sAddr_latest) {
                throw new Error(`Authorized session keystone tip not found (E: b8cda5cdc058903318db536f59e8e826)`);
            }
            const sKeystone_latest = await this.getSessionKeystone(sAddr_latest);
            // Crytographically verify proof evolution and demand solutions
            await verifyConnectProof({
                proofFrame,
                sKeystone_latest,
                challengeUuid: this.challengeUuid,
                demandedIds: this.demandedIds
            });
            // Persist the newly validated evolved session keystone tip
            await metaspace.put({ ibGibs: [proofFrame], space });
            await metaspace.registerNewIbGib({ ibGib: proofFrame, space });
            if (logalot) {
                console.log(`${lc} connect validation successful! Connection upgraded to active sync session.`);
            }
            this.isAuthenticated = true;
            this.socketWrapper.send(JSON.stringify({
                type: SyncWebSocketMsgType.auth_ok
            }));
        }
        else {
            throw new Error(`Unexpected message type ${msg.type} during connect phase (E: f67a0f47f8426c2b01af5bc3d0146b26)`);
        }
    }
    /**
     * Executes the incoming sync request through the local SyncSagaCoordinator and sends response.
     */
    async executeIncomingSyncRequestAndRespond({ context }) {
        const lc = `${this.lc}[${this.executeIncomingSyncRequestAndRespond.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} executing incoming sync turn...`);
            }
            if (!this.opts) {
                throw new Error(`opts not initialized. (E: 0c98186714e85b9a08bb9d98daada826)`);
            }
            const { localCoordinator, localMetaspace, localSpace } = this.opts;
            const localTempSpace = await this.ensureLocalTempSpace();
            const responseCtx = await localCoordinator.continueSync({
                sagaContext: context,
                metaspace: localMetaspace,
                mySpace: localSpace,
                myTempSpace: localTempSpace,
                peer: this,
            });
            if (responseCtx) {
                const responsePayloads = responseCtx.payloadIbGibsDomain ?? [];
                delete responseCtx.payloadIbGibsDomain;
                // Save payloads to stream once authenticated by Alice
                this.pendingResponsePayloadsToSend = responsePayloads;
                this.socketWrapper.send(JSON.stringify({
                    type: SyncWebSocketMsgType.sync_frame_response,
                    context: responseCtx
                }));
            }
            else {
                if (logalot) {
                    console.log(`${lc} synchronization session completed successfully.`);
                }
            }
        }
        catch (error) {
            console.error(`${lc} executeIncomingSyncRequestAndRespond failed: ${extractErrorMsg(error)}`);
            throw error;
        }
    }
}
//# sourceMappingURL=sync-peer-websocket-receiver-v1.mjs.map