/**
 * @module SyncPeer_V1 witness class for base class plumbing of sync peers.
 *
 * You are expected to implement concrete peer classes for concrete architecture.
 */
import { extractErrorMsg } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
import { GLOBAL_LOG_A_LOT } from '../../core-constants.mjs';
import { LightWitnessBase_V1 } from '../../witness/light-witness-base-v1.mjs';
import { newupSubject } from '../../common/pubsub/subject/subject-helper.mjs';
import { authenticateContextIntrinsically, validateContextAndSagaFrame } from '../sync-saga-context/sync-saga-context-helpers.mjs';
import { getFromSpace } from '../../witness/space/space-helper.mjs';
import { getFullSyncSagaHistory, deriveSessionSecret } from '../sync-helpers.mjs';
import { KeystoneService_V1 } from '../../keystone/keystone-service-v1.mjs';
import { KEYSTONE_VERB_CONNECT, KEYSTONE_VERB_SYNC, POOL_ID_CONNECT, } from '../../keystone/keystone-constants.mjs';
import { getTjpAddr } from '../../common/other/ibgib-helper.mjs';
const logalot = GLOBAL_LOG_A_LOT;
/**
 * Abstract witness for talking to a Sync Peer (e.g. Remote Node or Local Simulator).
 *
 * Takes a SyncSagaContext (Request) and returns a SyncSagaContext (Response).
 *
 * Implements the core "Sync Protocol Transport" logic. Once connected, does:
 * 1. Push Context & Dependencies (Sender -> Receiver)
 * 2. Send Request (RPC)
 * 3. Pull Response & Dependencies (Receiver -> Sender)
 */
export class SyncPeer_V1 extends LightWitnessBase_V1 {
    lc = `[${SyncPeer_V1.name}]`;
    opts;
    payloadIbGibsDomainReceived$;
    currentSessionIdentity;
    get currentSessionIdentityAddr() {
        return this.currentSessionIdentity ?
            getIbGibAddr({ ibGib: this.currentSessionIdentity }) :
            undefined;
    }
    // public getSenderIdentity(): KeystoneIbGib_V1 | undefined {
    //     return this.opts?.senderIdentity;
    // }
    async signContext({ contextAddr, }) {
        const lc = `${this.lc}[${this.signContext.name}]`;
        try {
            if (!this.currentSessionIdentity) {
                return undefined;
            }
            if (!this.opts) {
                throw new Error(`opts not initialized. (E: bcf5978aed789b0ebcbdc51971ebe826)`);
            }
            const { fnSenderSecret, sagaId, localMetaspace, localSpace } = this.opts;
            if (!fnSenderSecret) {
                throw new Error(`fnSenderSecret not initialized. (E: 207fd292a2e8c53c05fd0a74a4ae6d26)`);
            }
            if (!sagaId) {
                throw new Error(`sagaId not initialized. (E: f2e35cc13ed873b638116188119d1826)`);
            }
            const senderSecret = await fnSenderSecret();
            const sessionSecret = await deriveSessionSecret({ senderSecret, sagaId });
            const keystoneSvc = new KeystoneService_V1();
            const evolved = await keystoneSvc.sign({
                latestKeystone: this.currentSessionIdentity,
                masterSecret: sessionSecret,
                claim: {
                    verb: KEYSTONE_VERB_SYNC,
                    target: contextAddr,
                },
                metaspace: localMetaspace,
                space: localSpace,
            });
            this.currentSessionIdentity = evolved;
            return evolved;
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            throw error;
        }
    }
    /**
 * Evolves the session identity (S_n -> S_n+1) solving the demanded connect challenges.
 */
    async signContextConnect({ challengeUuid, demandedIds, }) {
        const lc = `${this.lc}[${this.signContextConnect.name}]`;
        try {
            if (!this.currentSessionIdentity) {
                return undefined;
            }
            if (!this.opts) {
                throw new Error(`opts not initialized. (E: bcf5978aed789b0ebcbdc51971ebe826)`);
            }
            const { fnSenderSecret, sagaId, localMetaspace, localSpace } = this.opts;
            if (!fnSenderSecret) {
                throw new Error(`fnSenderSecret not initialized. (E: 207fd292a2e8c53c05fd0a74a4ae6d26)`);
            }
            if (!sagaId) {
                throw new Error(`sagaId not initialized. (E: f2e35cc13ed873b638116188119d1826)`);
            }
            const senderSecret = await fnSenderSecret();
            const sessionSecret = await deriveSessionSecret({ senderSecret, sagaId });
            const keystoneSvc = new KeystoneService_V1();
            const evolved = await keystoneSvc.sign({
                latestKeystone: this.currentSessionIdentity,
                masterSecret: sessionSecret,
                poolId: POOL_ID_CONNECT,
                requiredChallengeIds: demandedIds,
                claim: {
                    verb: KEYSTONE_VERB_CONNECT,
                    target: challengeUuid,
                },
                metaspace: localMetaspace,
                space: localSpace,
            });
            this.currentSessionIdentity = evolved;
            return evolved;
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            throw error;
        }
    }
    get classname() {
        if (!this.data) {
            throw new Error(`(UNEXPECTED) this.data falsy? (E: 1ab1841e9338b54f3aa615fa37024826)`);
        }
        if (!this.data.classname) {
            throw new Error(`invalid peer. this.data.classname is falsy (E: b0ee28a0abb84a06588d9de7afcef826)`);
        }
        return this.data.classname;
    }
    constructor(initialData, initialRel8ns) {
        super(initialData, initialRel8ns);
    }
    async initialize() {
        this.payloadIbGibsDomainReceived$ = await newupSubject();
    }
    /**
     * Establishes the connection context.
     *
     * This method provides boilerplate error handling and logging,
     * delegating the actual connection logic to `connectImpl`.
     */
    async connect(opts) {
        const lc = `${this.lc}[${this.connect.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting... (I: ad89d8137351662fb8a30114a1fa4826)`);
            }
            await this.preConnectCheck(opts);
            await this.connectImpl(opts);
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
    /**
     * Pre-connect phase: establishes session identity.
     *
     * Shared base implementation:
     * 1. Derives `sessionSecret = KDF(senderSecret, sagaId)`.
     * 2. Creates session keystone genesis S (connect + sync pools) in `localSpace`.
     * 3. Evolves `senderIdentity` → `newSenderIdentity` (I1) with a `sync`
     *    claim targeting S^Stjp, stored in `localSpace`.
     * 4. Delegates posting of both keystones to the receiver via the abstract
     *    hook {@link postEstablishToReceiver} (peer-specific transport).
     *
     * Returns `undefined` (no-op) if no `senderIdentity` was provided in opts.
     *
     * @returns The session keystone genesis (S^Stjp) so the coordinator can
     * hold a reference for subsequent per-turn signing. The name
     * `newSenderIdentity` (I1) is scoped only within this method.
     */
    async establishSessionIdentity() {
        const lc = `${this.lc}[${this.establishSessionIdentity.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting... (I: f2a1b3c4d5e6f7a8b9c0d1e2f3a4b526)`);
            }
            if (!this.opts) {
                throw new Error(`(UNEXPECTED) this.opts falsy? Call initializeOpts first. (E: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c526)`);
            }
            const { senderIdentity, fnSenderSecret, sagaId, localMetaspace, localSpace } = this.opts;
            // No identity provided — anonymous sync, nothing to establish.
            if (!senderIdentity || !fnSenderSecret) {
                if (logalot) {
                    console.log(`${lc} no senderIdentity/fnSenderSecret — skipping establish (I: f29348a77d1542326d14043ea4b69126)`);
                }
                return undefined;
            }
            const senderIdentityAddr = getIbGibAddr({ ibGib: senderIdentity });
            const senderIdentityTjpAddr = getTjpAddr({ ibGib: senderIdentity });
            if (!senderIdentityTjpAddr) {
                throw new Error(`(UNEXPECTED) senderIdentityTjpAddr falsy? (E: d5d8c3ab25a83a2c127904fe96de1526)`);
            }
            if (!sagaId) {
                throw new Error(`(UNEXPECTED) sagaId falsy? Must be set in initializeOpts before calling establishSessionIdentity. (E: c6ba389d51b8af07d82458f875cf9826)`);
            }
            const senderSecret = await fnSenderSecret();
            if (!senderSecret) {
                throw new Error(`senderSecret falsy. senderSecret required. (E: 1a8b0298bf78cbdf284b7988983b9826)`);
            }
            const sessionSecret = await deriveSessionSecret({ senderSecret, sagaId });
            const keystoneSvc = new KeystoneService_V1();
            // Step 1: Create S genesis in localSpace.
            // Pool configs must be provided via opts (set by coordinator before calling establish).
            if (!this.opts.sessionConnectPoolConfig) {
                throw new Error(`(UNEXPECTED) opts.sessionConnectPoolConfig falsy? (E: 3351fd566eb8bbd2f821bb08c4419826)`);
            }
            if (!this.opts.sessionSyncPoolConfig) {
                throw new Error(`(UNEXPECTED) opts.sessionSyncPoolConfig falsy? (E: dbffa810d9e7ff6079088deb5b8e7826)`);
            }
            // session genesis keystone should have soft links to both
            // targetAddrs and the sender identity that spawned it.
            const frameDetails = {
                senderIdentityAddr,
                senderIdentityTjpAddr,
            };
            if (this.opts.targetAddrs) {
                frameDetails.targetAddrs = this.opts.targetAddrs;
            }
            const sessionIdentity = await keystoneSvc.genesis({
                masterSecret: sessionSecret,
                configs: [this.opts.sessionConnectPoolConfig, this.opts.sessionSyncPoolConfig],
                frameDetails,
                metaspace: localMetaspace,
                space: localSpace,
            });
            const sessionIdentityAddr = getIbGibAddr({ ibGib: sessionIdentity });
            // Step 2: Evolve senderIdentity → newSenderIdentity (I1) with sync claim.
            const newSenderIdentity = await keystoneSvc.sign({
                latestKeystone: senderIdentity,
                masterSecret: senderSecret,
                claim: {
                    verb: KEYSTONE_VERB_SYNC,
                    target: sessionIdentityAddr,
                },
                metaspace: localMetaspace,
                space: localSpace,
            });
            // Step 3: Post both to the receiver (peer-specific).
            await this.postEstablishToReceiver({ newSenderIdentity, sessionIdentity });
            this.currentSessionIdentity = sessionIdentity;
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
    /**
     * base implementation just sets the opts property.
     *
     * override this in concrete classes for further initialization code
     * specific to the sender.
     *
     * @see {@link SyncPeerWitness.opts}
     */
    async initializeOpts(opts) {
        const lc = `${this.lc}[${this.initializeOpts.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting... (I: 31bd5fda37c89fa37fbaf14daf5fe726)`);
            }
            this.opts = opts;
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
    setOptionalOpts(arg) {
        const lc = `${this.lc}[${this.setOptionalOpts.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting... (I: ae5ddbd3577db267f84743175a736626)`);
            }
            if (!this.opts) {
                throw new Error(`(UNEXPECTED) this.opts falsy? (E: 0dd4d6080a6e2f31d86cf4d86e11b826)`);
            }
            if (logalot) {
                console.log(`${lc} updating opts. arg keys: ${Object.keys(arg)} (I: 452d78a558f8fc3468c8e2e68c70b226)`);
            }
            this.opts = { ...this.opts, ...arg };
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
    // public setOptionalOpts({
    //     senderTempSpace,
    // }: {
    //     senderTempSpace: IbGibSpaceAny,
    // }): void {
    //     const lc = `${this.lc}[${this.setOptionalOpts.name}]`;
    //     try {
    //         if (logalot) { console.log(`${lc} starting... (I: a17188953e38f07b1884e498ac490b26)`); }
    //         if (!this.opts) { throw new Error(`(UNEXPECTED) this.opts falsy? (E: bdf00fe4b89b07ea78860d6ebe922e26)`); }
    //         this.opts.senderTempSpace = senderTempSpace;
    //     } catch (error) {
    //         console.error(`${lc} ${extractErrorMsg(error)}`);
    //         throw error;
    //     } finally {
    //         if (logalot) { console.log(`${lc} complete.`); }
    //     }
    // }
    async authenticateAndValidate({ context, }) {
        const lc = `${this.lc}[${this.authenticateAndValidate.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting... (I: add238055cd84a222c5b8c89913af526)`);
            }
            if (!this.opts) {
                throw new Error(`(UNEXPECTED) opts falsy in authenticateValidateAuthorize (E: b7e5a8ad99d84e1ba8d8f7facd288826)`);
            }
            const { localSpace } = this.opts;
            // first authenticate, because invalid authn is a non-starter
            // (already validated intrinsically)
            const authenticationErrors = await authenticateContextIntrinsically({
                context,
                space: localSpace,
            });
            if (authenticationErrors.length > 0) {
                throw new Error(`invalid context authentication. authenticationErrors: ${authenticationErrors} (E: da89da5ee1269aeb78952d475d607526)`);
            }
            // next, we have a valid authn, which should point to the
            // context. But is that context and the saga data it contains valid?
            // Does the authentication actually point to this context?
            const validationErrors = await validateContextAndSagaFrame({ context, });
            if (validationErrors.length > 0) {
                throw new Error(`invalid context received. validationErrors: ${validationErrors} (E: 8b34c875c968af29bc433138e57a7826)`);
            }
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
    /**
     * Witness the synchronization context (Send/Receive).
     *
     * At this point in code, we are sending the context OUTGOING. The {@link context} should
     * have a fully populated {@link SyncSagaContextIbGib_V1.sagaFrame}
     *
     * @param arg The OUTGOING context (Request).
     * @returns The INCOMING context (Response), or undefined if failed/empty.
     */
    async witness(context) {
        const lc = `${this.lc}[${this.witness.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting...`);
            }
            if (!this.opts) {
                throw new Error(`(UNEXPECTED) this.opts falsy? Concrete class should have initialized sender opts by now. (E: 0b9e28287318fdf8bf9f5a6886a24826)`);
            }
            // NOTE: There are three basic types of peers:
            // * local-only
            //   * this peer is both sender/receiver peer
            //   * for local merges and relatively fast spaces
            // * proxy to remote space
            //   * this peer is both sender/receiver peer
            //   * works directly with remote/outerspaces via API calls
            //   * Less efficient over-the-wire xfer due to chatiness
            // * symmetric node sender/receiver peers
            //   * separate sender/receiver classes
            //     * e.g., SyncPeerClient[Substrate]Sender_V1,
            //       SyncPeerClient[Substrate]Receiver_V1
            //   * More efficient over-the-wire xfer
            //   * Most difficult to mentally model
            //   * sender.witness does the pushing out
            //     * what the sender/initiator's coordinator has a reference to
            //   * receiver.witness does the coming in
            //     * lives on a server/remote instance with its own coordinator
            //       instance
            //     * must do its own validation/authn/authz for possible MITM
            //       issues
            // The following is a sketch where some of these may be blurred
            // persist the context, sagaframe, authentication details ONLY in the receiver's durable space
            // if sending domain ibgibs...
            //   verify the domain ibgibs intrinsically.
            //   persist the domain ibgibs in the receiver's temp space (push all addrs?)
            //   await until all domain ibgibs are in receiver's temp space
            // process the context with the receiver's coordinator to get a new response context
            // analyze the response context
            //   validate context intrinsically
            // persist the response context's dependency graph (context, saga frame and msg(s))
            // if response has payload domain ibgibs,
            //   spin off...
            //     pull domain ibgibs to local temp space
            //     publish domain ibgibs to observable
            //     when all domain ibgibs are pulled, publish complete to the observable
            // return resulting context ibgib (which the caller should get BEFORE the domain ibgibs observable completes)
            if (!context.sagaFrame) {
                throw new Error(`context.sagaFrame falsy. (E: a33dd88aa108e2bad9e885885731ce26)`);
            }
            // this is not just happening on the client, but this peer may be
            // the one receiving the context as well.
            // const sagaHistory_beforeSend = await getFullSyncSagaHistory({
            //     sagaIbGib: context.sagaFrame,
            //     space: this.opts.localSpace
            // });
            await this.authenticateAndValidate({ context });
            // at this point, we have a valid, authenticated, authorized context
            // execute the transfer of control domain ibgibs & outgoing payload
            // ibgibs...
            const response = await this.sendContextRequest(context);
            if (response) {
                // at this point, all outgoing payload domain ibgibs have been sent.
                // we have received the response context, wherein the sync saga
                // frame/msg stone(s) should be evolved. Depending
                // on the concrete implementation, if there are domain ibgibs to
                // receive, they may still be transferring. These will be published
                // to this.payloadIbGibsDomainReceived$
                const sagaHistory_afterSend = await getFullSyncSagaHistory({
                    sagaIbGib: context.sagaFrame,
                    space: this.opts.localSpace
                });
                // NOTE: I think this is the wrong place for this, as we must do this in the peer **before** storing any control ibgibs. This needs to be done in the continueSync I _think_ (not sure yet)
                // await this.authenticateValidateAuthorize({
                //     context, fullSagaHistory: sagaHistory_afterSend,
                // });
                return response;
            }
            else {
                // response falsy. we could be done, or this could be an error.
                if (logalot) {
                    console.log(`${lc} No response from peer. this may be normal if we just sent the very last commit frame. (I: 1855b7728c8898e1f9c001f8170ba826)`);
                }
                return undefined;
            }
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
    /**
     * Abstract accessor to get an IbGib from the Local space/store.
     * Needed for inspecting rela8ns during the graph walk.
     *
     * Default implementation simply looks in senderSpace
     */
    async getLocalIbGib(addr) {
        const lc = `${this.lc}[${this.getLocalIbGib.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting... (I: 27b248cb9801eeb2386b71485389a826)`);
            }
            console.warn(`${lc} possibly a bottleneck here, getLocalIbGib only gets a single ibgib... (W: 2fd448a435480e6b128f6b8bcbef4826)`);
            if (!this.opts) {
                throw new Error(`(UNEXPECTED) this.opts falsy? (E: cfb9431f2fb851f24a6c88e80c3b3326)`);
            }
            const resGet = await getFromSpace({ space: this.opts.localSpace, addr });
            if (resGet.success && resGet.ibGibs && resGet.ibGibs.length === 1) {
                const ibGib = resGet.ibGibs[0];
                return ibGib;
            }
            else {
                throw new Error(`couldn't get addr (${addr}) from local space ${this.opts.localSpace}. reason: ${resGet.errorMsg ?? 'unknown error (E: 926ef8bf4fcc299ab89dba34ea691a26)'} (E: d8a89807e471d3f8b938ab21df44cb26)`);
            }
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
}
//# sourceMappingURL=sync-peer-v1.mjs.map