import { extractErrorMsg, getUUID, pretty } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
import { validateIbGibIntrinsically } from "@ibgib/ts-gib/dist/V1/validate-helper.mjs";
import { isDna, isPrimitive } from "@ibgib/ts-gib/dist/V1/transforms/transform-helper.mjs";
import { getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
import { GLOBAL_LOG_A_LOT } from "../core-constants.mjs";
import { SYNC_ATOM, SYNC_MSG_REL8N_NAME, SyncStage } from "./sync-constants.mjs";
import { SyncSagaFrameOrigin } from "./sync-types.mjs";
import { isValidSyncConflictStrategy, SYNC_CONFLICT_STRATEGY_VALID_VALUES, } from "./sync-constants.mjs";
import { getFromSpace, putInSpace } from "../witness/space/space-helper.mjs";
import { getIbGibsFromCache_fallbackToSpaces } from "../common/other/ibgib-helper.mjs";
import { isGraftInfo } from "./graft-info/graft-info-helpers.mjs";
import { GRAFT_ORPHAN_REL8N_NAME } from "./graft-info/graft-info-constants.mjs";
import { deriveKey } from "../keystone/kdf/kdf-helpers.mjs";
import { KdfStrategy } from "../keystone/kdf/kdf-constants.mjs";
const logalot = GLOBAL_LOG_A_LOT;
/**
 * Constructs the standard 'ib' string for a Sync frame.
 */
/**
 * Constructs the standard 'ib' string for a Sync frame.
 */
export async function getSyncIb({ data, }) {
    const lc = `[${getSyncIb.name}]`;
    try {
        // sync uuid
        // e.g. "sync 1234-5678" (stage removed from ib)
        const ib = [
            SYNC_ATOM,
            data.uuid
        ].join(' ');
        return ib;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
}
/**
 * Parses a standard Sync 'ib' string.
 */
export async function parseSyncIb({ ib, }) {
    const lc = `[${parseSyncIb.name}]`;
    try {
        const parts = ib.split(' ');
        if (parts.length !== 2) {
            throw new Error(`Invalid sync ib. Expected 2 parts [atom uuid]. Got ${parts.length}. (E: 7db603e0dcf8c35af826cce89a125825)`);
        }
        const [atom, uuid] = parts;
        if (atom !== SYNC_ATOM) {
            throw new Error(`Atom mismatch. Expected ${SYNC_ATOM}. Got ${atom}. (E: cc6a48143be7de70bafbfdbbc0776826)`);
        }
        return { atom, uuid };
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
}
/**
 * @returns the name for a tempSpace in the sync
 */
export async function getTempSpaceName() {
    const uuid = await getUUID();
    return `tmp_sync_space_${uuid.substring(0, 8)}`;
}
/**
 * UNTESTED
 *
 * Checks if an {@link olderAddr} is the address of a past frame in the ibgib
 * with {@link newerAddr}.
 */
export async function isPastFrame({ olderAddr, newerAddr, space, }) {
    const lc = `[${isPastFrame.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: 4b3548cbc9a2eb749f447ed82fdcf826)`);
        }
        // quick check...
        if (olderAddr === newerAddr) {
            return true; /* <<<< returns early */
        }
        // diff, so get the actual ibGib
        let newerIbGib;
        const res = await getFromSpace({ space, addr: newerAddr });
        if (res.success && res.ibGibs?.length === 1) {
            newerIbGib = res.ibGibs[0];
        }
        else {
            throw new Error(`couldn't get newerAddr (${newerAddr}) from space (${space.ib}). (E: 36c758c22b181b2c89a1cd28937a6826)`);
        }
        if (!newerIbGib.rel8ns) {
            console.warn(`${lc} newerIbGib.rel8ns falsy? newerAddr: ${newerAddr}. (W: 6f920811f7080281189f767813814826)`);
            return false; /* <<<< returns early */
        }
        const pastAddrs = newerIbGib.rel8ns.past ?? [];
        if (pastAddrs.length === 0) {
            return false; /* <<<< returns early */
        }
        if (pastAddrs.includes(olderAddr)) {
            return true; /* <<<< returns early */
        }
        // at this point, for most ibgibs we could leave it here. But
        // conceivably, the past could be a linked rel8n. so recurse on the
        // oldest in the past
        const oldestAddrInThisPast = pastAddrs[0];
        const resRecurse = await isPastFrame({
            olderAddr,
            newerAddr: oldestAddrInThisPast,
            space,
        });
        return resRecurse;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
/**
 * Gets a shallow dependency graph for a Sync Saga Frame.
 *
 * DOES NOT GET THE ENTIRE DEPENDENCY FRAME (INCLUDING PAST).
 *
 * Unlike standard `getDependencyGraph`, this does NOT traverse `ancestor` (history) or `past`.
 * It only retrieves the *content* of the current frame:
 * 1. The Frame itself.
 * 2. The Message Stone(s) (`rel8ns.msg`).
 *
 * This is used for efficient transmission of the "Current Step" without re-sending the entire history.
 */
export async function getSyncSagaFrameDependencyGraph({ sagaIbGib, localSpace, }) {
    const lc = `[${getSyncSagaFrameDependencyGraph.name}]`;
    // Helper to fetch and push
    const fnGet = async (addrs) => {
        const resGet = await getFromSpace({ space: localSpace, addrs });
        if (resGet.success && resGet.ibGibs?.length === addrs.length) {
            return resGet.ibGibs;
        }
        else {
            const resIbGib = resGet.rawResultIbGib;
            if (!resIbGib.data) {
                throw new Error(`(UNEXPECTED) resGet.rawResultIbGib.data falsy? (E: c26c681d2f831fc537ba6f48fee36d26)`);
            }
            throw new Error(`(UNEXPECTED) couldn't find addrs? addrsNotFound: ${resIbGib.data.addrsNotFound}? space.ib: ${localSpace.ib} (E: 43b7b5a6ad982715c8f3eed69bfa0d26)`);
        }
    };
    const validationErrors = await validateSyncSagaFrame({ sagaIbGib });
    if (validationErrors.length > 0) {
        throw new Error(`invalid sync sagaIbGib. validationErrors: ${validationErrors} (E: 19f9e8c9643858ff41312b62293d2126)`);
    }
    if (!sagaIbGib.rel8ns) {
        throw new Error(`(UNEXPECTED) validated sagaIbGib.rel8ns falsy? (E: 73af48be8bbf271ec5cf78187ce8c826)`);
    }
    // msg stones
    const msgStoneAddrs = sagaIbGib.rel8ns[SYNC_MSG_REL8N_NAME];
    const msgStones = await fnGet(msgStoneAddrs);
    return { sagaIbGib, msgStones, };
}
/**
 * gets the {@link SyncSagaFrameDependencyGraph} for each step in the saga,
 * including the {@link sagaIbGib} passed in.
 * @returns ordered array of saga frame dependency graphs
 */
export async function getFullSyncSagaHistory({ sagaIbGib, space, }) {
    const lc = `[${getFullSyncSagaHistory.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: 1ea3f804dd07d1e917a2afaaad497826)`);
        }
        if (!sagaIbGib.data) {
            throw new Error(`(UNEXPECTED) sagaIbGib.data falsy? (E: fe0a0d0965e87ac852fe7ba4710cc826)`);
        }
        if (!sagaIbGib.rel8ns) {
            throw new Error(`(UNEXPECTED) sagaIbGib.rel8ns falsy? (E: b79ff82a732346bdf8a9e1eb9ac96826)`);
        }
        const sagaAddr = getIbGibAddr({ ibGib: sagaIbGib });
        const addrs = sagaIbGib.rel8ns.past && sagaIbGib.rel8ns.past.length > 0 ?
            [...sagaIbGib.rel8ns.past, sagaAddr] :
            [sagaAddr];
        const resGraphs = [];
        for (const addr of addrs) {
            let sagaFrame;
            let resGet = await getFromSpace({ addr, space });
            if (resGet.success && resGet.ibGibs && resGet.ibGibs.length === 1) {
                sagaFrame = resGet.ibGibs[0];
            }
            else {
                if (logalot) {
                    console.log(`${lc} failed getting full history of sagaIbGib:\n${pretty(sagaIbGib)} (I: 2a35685bfe038f63182c7db39b44c826)`);
                }
                throw new Error(`couldn't get saga addr (${addr}) from space (${space.ib}) (E: 865b78ecd918c244158813650bea7d26)`);
            }
            const graph = await getSyncSagaFrameDependencyGraph({
                sagaIbGib: sagaFrame, localSpace: space,
            });
            resGraphs.push(graph);
        }
        // the \n are my my attempt at lining up an indent
        if (logalot) {
            console.log(`${lc} resGraphs:...\n  ${pretty(resGraphs).split('\n').join('\n  ')}\n...(I: d8e11827d88a3454d887ff05fb4ff526)`);
        }
        return resGraphs;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
/**
 * only validates this single frame. does not validate any dependencies or past.
 * @returns array of errors, empty if valid
 */
export async function validateSyncSagaFrame({ sagaIbGib }) {
    const lc = `[${validateSyncSagaFrame.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: dac978e9ec5820f128fbbff817b82826)`);
        }
        const errors = await validateIbGibIntrinsically({ ibGib: sagaIbGib }) ?? [];
        // intrinsic errors are a non-starter
        if (errors.length > 0) {
            return errors; /* <<<< returns early */
        }
        if (sagaIbGib.data) {
            const { data } = sagaIbGib;
            if (data.uuid) {
                // todo: validate uuid
            }
            else {
                errors.push(`data.uuid required (E: aa24eeda7b88d22e5832a2e8083c0826)`);
            }
            if (data.timestamp) {
                // todo: validate timestamp
            }
            else {
                errors.push(`data.timestamp required (E: 9c82a88548f4b63c38687428754b3826)`);
            }
            if (data.timestampMs || data.timestampMs === 0) {
                // todo: validate timestampMs
            }
            else {
                errors.push(`data.timestampMs required (E: 9c82a88548f4b63c38687428754b3826)`);
            }
            if (data.conflictStrategy) {
                if (!isValidSyncConflictStrategy(data.conflictStrategy)) {
                    errors.push(`data.conflictStrategy (${data.conflictStrategy}) is invalid. Must be one of "${SYNC_CONFLICT_STRATEGY_VALID_VALUES}". (E: 6223a8813b688fa2c8644fa8a491bf26)`);
                }
            }
            else {
                errors.push(`data.conflictStrategy required (E: 9c82a88548f4b63c38687428754b3826)`);
            }
            if (data.expirationTimestamp) {
                // todo: validate expirationTimestamp
            }
            if (data.errors) {
                // todo: validate errors?
            }
            if (data.successCount) {
                // todo: validate successCount
            }
        }
        else {
            errors.push(`sagaIbGib.data falsy (E: 4de5e8de589d0af7245600fb4fe30926)`);
        }
        if (sagaIbGib.rel8ns) {
            const msgAddrs = sagaIbGib.rel8ns[SYNC_MSG_REL8N_NAME];
            if (msgAddrs && msgAddrs.length > 0) {
                // todo: validate the syncmsg addr?
            }
            else {
                errors.push(`sagaIbGib.rel8ns[SYNC_MSG_REL8N_NAME] required. (E: df648dc18148e718682acb4842ecf826)`);
            }
        }
        else {
            errors.push(`sagaIbGib.rel8ns falsy (E: da37aa492a7275b68a5e1912c7a5e126)`);
        }
        return errors;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
export async function validateFullSyncSagaHistory({ history, }) {
    const lc = `[${validateFullSyncSagaHistory.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: df02b802f6989096b8d8f728e2c0a526)`);
        }
        console.error(`${lc} NAG ERROR (NOT THROWN) need to implement more validation on full saga history. (E: 9bfcc9247a1c783fd361a4a8216fef26)`);
        return [];
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
export async function putInSpace_dnasThenNonDnas({ ibGibs, space, }) {
    const lc = `[${putInSpace_dnasThenNonDnas.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: aa3ab828402836b3914800a8ffa1b826)`);
        }
        const payload_Dnas = [];
        const payload_NonDnas = [];
        for (const ibGib of ibGibs) {
            // never store primitives
            if (isPrimitive({ ibGib })) {
                continue;
            }
            if (isDna({ ibGib })) {
                payload_Dnas.push(ibGib);
            }
            else {
                payload_NonDnas.push(ibGib);
            }
        }
        // dnas are the simplest and we store those first...
        if (payload_Dnas.length > 0) {
            await putInSpace({ ibGibs: payload_Dnas, isDna: true, space, });
        }
        // then put all non-dnas ("regular" ibgibs) in the space
        if (payload_NonDnas.length > 0) {
            await putInSpace({ ibGibs: payload_NonDnas, isDna: false, space, });
        }
        return { payload_Dnas, payload_NonDnas };
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
/**
 * @see {@link SyncSagaFrameOrigin}
 */
export function getSyncSagaFrameOrigin({ sagaFrame, }) {
    const lc = `[${getSyncSagaFrameOrigin.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: 93521332d0dd2f5e88398b7db95e8126)`);
        }
        if (!sagaFrame) {
            throw new Error(`(UNEXPECTED) sagaFrame falsy? (E: 01fb28be6668d88ea49bb298c739c926)`);
        }
        if (!sagaFrame.data) {
            throw new Error(`(UNEXPECTED) sagaFrame.data falsy? (E: 998298eb17685bdb9a85b46488ccd826)`);
        }
        if (!sagaFrame.data.n && sagaFrame.data.n !== 0) {
            throw new Error(`(UNEXPECTED) sagaFrame.data.n falsy and not 0? (E: 0394919d5b58300ceb09ba6a264ad826)`);
        }
        const modulo = sagaFrame.data.n % 2;
        // even => sender
        // odd => receiver
        if (modulo === 0) {
            return SyncSagaFrameOrigin.sender;
        }
        else if (modulo === 1) {
            return SyncSagaFrameOrigin.receiver;
        }
        else {
            throw new Error(`(UNEXPECTED) modulo is neither 0 nor 1? my logic sucks! This was way too defensive on my part... (E: 559488a2eef8d17b57079ca8886ee826)`);
        }
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
/**
 * @returns the last delta frame's {@link SyncSagaMessageDeltaData_V1.conflicts}
 * in the given {@link sagaHistory}.
 *
 * ## intent
 *
 * we need to be able to get the final aggregated info on what happened with
 * conflicts and their resolutions. ATOW (02/02/2026) This exists in the final
 * delta frame in the saga. this is needed, e.g., in `getAllOrphanedAddresses`
 *
 * @see {@link getAllOrphanedAddresses}
 */
export async function getFinalConflictsInfo({ sagaHistory, }) {
    const lc = `[${getFinalConflictsInfo.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: fe93a8f29e5d8ab2c8bd0e08e56f7826)`);
        }
        const lastDeltaHistorySlice = sagaHistory.findLast(x => x.msgStones.some(msgStone => msgStone.data.stage === SyncStage.delta));
        if (!lastDeltaHistorySlice) {
            throw new Error(`(UNEXPECTED) no delta frame? What are we calling this function for? (E: 9b3201207908f79868b93cd893a91626)`);
        }
        const deltaStones = lastDeltaHistorySlice.msgStones.filter(x => x.data.stage === SyncStage.delta);
        if (deltaStones.length > 1) {
            throw new Error(`(UNEXPECTED) multiple delta stones in history slice? we're expecting a single delta stone per saga frame. (E: a4bd5860b4aa0743753d4798d3163826)`);
        }
        const deltaData = deltaStones[0].data;
        return deltaData.conflicts;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
export async function getAllOrphanedAddresses({ sagaHistory, myTempSpace, }) {
    const lc = `[${getAllOrphanedAddresses.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: b755627e99c9a221566d86a81b0da526)`);
        }
        // orphaned addresses should only happenn when we have conflicts and
        // merged timelines. the final delta frame should have all conflicts
        // resolved with full info. we should be able to work off of that.
        const conflicts = await getFinalConflictsInfo({ sagaHistory });
        if (!conflicts || conflicts.length === 0) {
            if (logalot) {
                console.log(`${lc} no conflicts found. returning empty array (I: 5dce98010128a284c8ec92cd466a3826)`);
            }
            return []; /* <<<< returns early */
        }
        const resOrphanedAddrs = [];
        for (const conflict of conflicts) {
            if (!conflict.resolved) {
                throw new Error(`(UNEXPECTED) conflict.resolved falsy? we're expecting all conflicts to be resolved when getting orphaned addresses. (E: 3861e89a18c8ff405eb2117f6d4b9b26)`);
            }
            if (!conflict.graftInfoAddr) {
                throw new Error(`(UNEXPECTED) conflict.graftInfoAddr falsy? we're expecting a resolved conflict whose final ibgib (graft tip) was the graft info and for this addr to be in conflict.graftInfoAddr. (E: 80d86e333028585908f4757bc4d6f826)`);
            }
            const ibgibsGotten = await getIbGibsFromCache_fallbackToSpaces({
                addrs: [conflict.graftInfoAddr],
                space: myTempSpace,
            });
            if (ibgibsGotten.length !== 1) {
                throw new Error(`(UNEXPECTED) couldn't get conflict.graftInfoAddr (${conflict.graftInfoAddr}) from myTempSpace (${myTempSpace.ib})? (E: cf1fe87096f8ea22f84f250d7ab30226)`);
            }
            const graftInfo = ibgibsGotten[0];
            if (!isGraftInfo(graftInfo)) {
                throw new Error(`(UNEXPECTED) graft info from conflict.graftInfoAddr (${conflict.graftInfoAddr}) does not pass the isGraftInfo guard? We're expecting this addr to point to a valid GraftInfoIbGib_V1 (E: 6aa9ecb9a428d92be8d1bdbe0696f826)`);
            }
            const orphanedAddrs = graftInfo.rel8ns[GRAFT_ORPHAN_REL8N_NAME];
            if (!orphanedAddrs || orphanedAddrs.length === 0) {
                throw new Error(`(UNEXPECTED) orphanedAddrs empty for conflict graftInfo (${conflict.graftInfoAddr})? we're expecting a graft to always necessarily orphan one or other branches, otherwise what are we "grafting"? (E: 3ed1284f6ca84754582801484d751926)`);
            }
            orphanedAddrs.forEach(x => resOrphanedAddrs.push(x));
        }
        return resOrphanedAddrs;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
/**
 * Derives a saga-specific session secret from the sender's master secret.
 *
 * Uses `KDF(senderSecret, sagaId)` via the `recursive_salt_wrap` strategy so
 * the session secret is:
 * - **Deterministic**: same inputs → same output (needed for the receiver to
 *   independently reproduce the session keystone genesis).
 * - **Saga-bound**: different sagaId → completely different sessionSecret →
 *   completely different session keystone S.
 * - **One-way**: knowing S cannot recover `senderSecret`.
 *
 * @param senderSecret - The sender's plaintext master secret (never transmitted).
 * @param sagaId       - The unique UUID for this sync saga.
 * @returns The derived session secret string.
 */
export async function deriveSessionSecret({ senderSecret, sagaId, }) {
    const lc = `[${deriveSessionSecret.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: 0de03f8dcd3e32f1fca244e8f2a8a826)`);
        }
        if (!senderSecret) {
            throw new Error(`(UNEXPECTED) senderSecret falsy? (E: 8ce053fe59825a6678713128953b9d26)`);
        }
        if (!sagaId) {
            throw new Error(`(UNEXPECTED) sagaId falsy? (E: 3a17e0c1b2f849de8a42cd15b8ee6826)`);
        }
        const sessionSecret = await deriveKey({
            masterSecret: senderSecret,
            kdfOpts: {
                strategy: KdfStrategy.recursive_salt_wrap,
                salt: sagaId,
                rounds: 10000,
                algorithm: 'SHA-256',
            }
        });
        return sessionSecret;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
//# sourceMappingURL=sync-helpers.mjs.map