import { Factory_V1 } from "@ibgib/ts-gib/dist/V1/factory.mjs";
import { getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
import { extractErrorMsg, pretty } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
import { rel8 } from "@ibgib/ts-gib/dist/V1/transforms/rel8.mjs";
import { GLOBAL_LOG_A_LOT } from "../../core-constants.mjs";
import { persistTransformResult, putInSpace, registerNewIbGib } from "../../witness/space/space-helper.mjs";
import { applyTransforms, getUniqueDnas } from "../../witness/space/reconciliation-space/reconciliation-space-helper.mjs";
import { GRAFT_INFO_ATOM, GRAFT_BASE_REL8N_NAME, GRAFT_ORPHAN_REL8N_NAME, GRAFT_INFO_REL8N_NAME, GRAFT_LCA_REL8N_NAME, GRAFT_DNAAPPLIED_REL8N_NAME, MergeTextStrategy } from "./graft-info-constants.mjs";
import { getIbGibsFromCache_fallbackToSpaces, getTimelinesGroupedByTjp, getTimestampInfo, splitPerTjpAndOrDna } from "../../common/other/ibgib-helper.mjs";
import { isValidSyncConflictStrategy, SYNC_CONFLICT_STRATEGY_VALID_VALUES, SyncConflictStrategy, } from "../sync-constants.mjs";
import { mut8 } from "@ibgib/ts-gib/dist/V1/transforms/mut8.mjs";
const logalot = GLOBAL_LOG_A_LOT;
const lc = `[graft-info-helpers]`;
/**
 * Validates and constructs the `ib` object for a Graft Info ibGib.
 */
export async function getGraftInfoIb({ data }) {
    if (!data.conflictStrategy) {
        throw new Error(`(UNEXPECTED) data.conflictStrategy required for GraftInfoIb (E: 2c0968a0dac8d0ab3d67da9756d6df26)`);
    }
    if (!isValidSyncConflictStrategy(data.conflictStrategy)) {
        throw new Error(`invalid data.conflictStrategy (${data.conflictStrategy}). valid conflict strategies: ${SYNC_CONFLICT_STRATEGY_VALID_VALUES} (E: 41e2680089786d4d28046b4c0884bd26)`);
    }
    return [
        GRAFT_INFO_ATOM,
        data.conflictStrategy
    ].join(' ');
}
/**
 * Parses a standard Graft Info 'ib' string.
 */
export async function parseGraftInfoIb({ ib }) {
    try {
        const parts = ib.split(' ');
        if (parts[0] !== GRAFT_INFO_ATOM) {
            throw new Error(`Atom mismatch. Expected ${GRAFT_INFO_ATOM} (E: 8f03c92a95144b618821915632599266)`);
        }
        if (parts.length < 2) {
            throw new Error(`Invalid graft info ib. Expected at least 2 parts. (E: 9c2b4c8a6d34469f8263544710183355)`);
        }
        const [atom, conflictStrategy] = parts;
        if (!isValidSyncConflictStrategy(conflictStrategy)) {
            throw new Error(`invalid conflictStrategy (${conflictStrategy}). valid conflict strategies: ${SYNC_CONFLICT_STRATEGY_VALID_VALUES} (E: 353da80a0f1843c0bbd9d5a1730a7826)`);
        }
        return {
            atom,
            conflictStrategy,
        };
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
}
/**
 * Creates a new GraftInfo ibGib.
 */
export async function createGraftInfo({ conflictStrategy, details, addlGraftDetails, latestCommonFrameAddr, baseAddrsSinceLCA, orphanAddrsSinceLCA, dnaAddrsApplied, }) {
    const lc = `[${createGraftInfo.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: 0030f62cbae28cf5882f87981edc3826)`);
        }
        if (baseAddrsSinceLCA.length === 0) {
            throw new Error(`(UNEXPECTED) baseAddrsSinceLCA empty? (E: 005f8449a9236d445902a148f59fc826)`);
        }
        if (orphanAddrsSinceLCA.length === 0) {
            throw new Error(`(UNEXPECTED) orphanAddrsSinceLCA empty? (E: 19e4082e82c67759c880392aca186826)`);
        }
        const data = {
            conflictStrategy,
            description: details,
        };
        if (addlGraftDetails) {
            data.addlGraftDetails = addlGraftDetails;
        }
        const rel8ns = {
            [GRAFT_LCA_REL8N_NAME]: [latestCommonFrameAddr],
            [GRAFT_BASE_REL8N_NAME]: baseAddrsSinceLCA.concat(),
            [GRAFT_ORPHAN_REL8N_NAME]: orphanAddrsSinceLCA.concat(),
            [GRAFT_DNAAPPLIED_REL8N_NAME]: dnaAddrsApplied.concat(),
        };
        const ib = await getGraftInfoIb({ data });
        const res = await Factory_V1.stone({
            parentPrimitiveIb: GRAFT_INFO_ATOM,
            ib, data, rel8ns,
            uuid: true,
        });
        return res;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
/**
 * Naive line-by-line text merge using LCS.
 *
 * ## notes
 *
 * A lot of magic short vars/constants/algo here. testing works AFAICT though.
 */
export async function mergeTextByLongestCommonSubsequence({ textA, textB, }) {
    if (textA === textB) {
        return textA;
    }
    if (textA === '') {
        return textB;
    }
    if (textB === '') {
        return textA;
    }
    function longestCommonSubsequence(a, b) {
        const m = a.length;
        const n = b.length;
        const dp = Array(m + 1).fill(0).map(() => Array(n + 1).fill(0));
        // Fill DP table
        for (let i = 1; i <= m; i++) {
            for (let j = 1; j <= n; j++) {
                if (a[i - 1] === b[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                }
                else {
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
                }
            }
        }
        // Backtrack to find LCS
        let i = m, j = n;
        const lcs = [];
        while (i > 0 && j > 0) {
            if (a[i - 1] === b[j - 1]) {
                lcs.unshift({ line: a[i - 1], indexA: i - 1, indexB: j - 1 });
                i--;
                j--;
            }
            else if (dp[i - 1][j] > dp[i][j - 1]) {
                i--;
            }
            else {
                j--;
            }
        }
        return lcs;
    }
    const linesA = textA.split('\n');
    const linesB = textB.split('\n');
    const common = longestCommonSubsequence(linesA, linesB); // Array of { line, indexA, indexB }
    let resultLines = [];
    let currentA = 0;
    let currentB = 0;
    for (const match of common) {
        // Add unique lines from A (before this match)
        while (currentA < match.indexA) {
            resultLines.push(linesA[currentA]);
            currentA++;
        }
        // Add unique lines from B (before this match)
        while (currentB < match.indexB) {
            resultLines.push(linesB[currentB]);
            currentB++;
        }
        // Add the common line
        resultLines.push(match.line);
        currentA++;
        currentB++;
    }
    // Add remaining unique lines from A
    while (currentA < linesA.length) {
        resultLines.push(linesA[currentA]);
        currentA++;
    }
    // Add remaining unique lines from B
    while (currentB < linesB.length) {
        resultLines.push(linesB[currentB]);
        currentB++;
    }
    return resultLines.join('\n');
}
/**
 * Grafts two divergent timelines by splicing missing DNA from one branch
 * (Orphan) onto another (Base).
 *
 * Logic:
 * 1. Determine latestCommonFrameAddr if not provided (validates if it is
 *    provided), and organizes branchA and branchB (since LCA, i.e., excludes
 *    LCA itself and all other previous past).
 * 2. Determine which to use as base/orphan by timestamp info: Earlier = Base,
 *    Later = Orphan.
 * 3. Replay DNA from Orphan's branch (since LCA) onto Base, starting with base
 *    tip.
 * 4. Merge Base tip and Orphan tip `data.text` fields (if applicable).
 * 5. Create/append GraftInfo describing this entire graft process (to
 *    completely preserve original context of each branch).
 *
 * @returns the newly created ibgibs that were created in the graft. The last in
 * this array will be the new tip
 */
export async function graftTimelines({ timelineAddrsA, timelineAddrsB, latestCommonFrameAddr, conflictStrategy, ibGibReadCache, space, }) {
    const lc = `[${graftTimelines.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: 3b4c3e8e59c883d9d3c352efd6380826)`);
        }
        /**
         * This will be our running list that contains ALL ibgibs in the
         * grafted timeline itself (i.e. does NOT contain any dna). We will use
         * this to compose the `result.intermediateIbGibs`.
         *
         * ## notes
         *
         * I'm doing this because I don't want to continually shift the newIbGib
         * out of the running list of ibgibs (result.intermediateIbGibs must NOT
         * contain newIbGib/tip), and I already have this implemented.
         *
         * any NEW dna created in this graft will be added directly to the result.dnas
         */
        const newGraftedIntermediateIbGibs_ordered = [];
        /**
         * does NOT contain **replayed** dna from the orphan branch. Those
         * should already be stored in the tempSpace.
         */
        const newGraftDnaIbGibs = [];
        // #region validation/sanity & init
        if (timelineAddrsA.length === 0) {
            throw new Error(`timelineAddrsA empty (E: 93a78cd1cd2a4a16ba99bd4820468f26)`);
        }
        if (timelineAddrsB.length === 0) {
            throw new Error(`timelineAddrsB empty (E: 76d53848152a66971b734068c3316826)`);
        }
        if (latestCommonFrameAddr) {
            // ensure both contain this
            if (!timelineAddrsA.includes(latestCommonFrameAddr)) {
                throw new Error(`timelineAddrsA does not include latestCommonFrameAddr (${latestCommonFrameAddr}). timelineAddrsA: ${timelineAddrsA} (E: c5a9afe371888da2a79963983426e826)`);
            }
            if (!timelineAddrsB.includes(latestCommonFrameAddr)) {
                throw new Error(`timelineAddrsB does not include latestCommonFrameAddr (${latestCommonFrameAddr}). timelineAddrsB: ${timelineAddrsB} (E: 7e641f67c452b67d582d78484a88c826)`);
            }
        }
        else {
            latestCommonFrameAddr = timelineAddrsA.findLast(addrA => timelineAddrsB.includes(addrA));
            if (!latestCommonFrameAddr) {
                throw new Error(`timelines do not share a common frame. A: ${timelineAddrsA}. B: ${timelineAddrsB} (E: 4c0e78a88a375d8588736668573f1e26)`);
            }
        }
        if (timelineAddrsA.at(-1) === latestCommonFrameAddr) {
            debugger;
            throw new Error(`timelineAddrsA.at(-1) === latestCommonFrameAddr. Graft requires a branch BEYOND the LCA (E: 3796fc4074a8c5899d549bb8c2e68826)`);
        }
        if (timelineAddrsB.at(-1) === latestCommonFrameAddr) {
            throw new Error(`timelineAddrsB.at(-1) === latestCommonFrameAddr. Graft requires a branch BEYOND the LCA (E: 66fbb86e9e98531549888bf10a344f26)`);
        }
        // #endregion validation/sanity & init
        // #region compose unique branches since LCA
        /** guaranteed above to be existing AND not the last element. */
        const indexLCA_A = timelineAddrsA.lastIndexOf(latestCommonFrameAddr);
        /** does NOT include LCA */
        const addrsA_sinceLCA = timelineAddrsA.slice(indexLCA_A + 1);
        const branchA_sinceLCA = await getIbGibsFromCache_fallbackToSpaces({
            readCache_graph: ibGibReadCache,
            addrs: addrsA_sinceLCA,
            space,
        });
        const tipA = branchA_sinceLCA.at(-1);
        /** guaranteed above to be existing AND not the last element. */
        const indexLCA_B = timelineAddrsB.lastIndexOf(latestCommonFrameAddr);
        /** does NOT include LCA */
        const addrsB_sinceLCA = timelineAddrsB.slice(indexLCA_B + 1);
        const branchB_sinceLCA = await getIbGibsFromCache_fallbackToSpaces({
            readCache_graph: ibGibReadCache,
            addrs: addrsB_sinceLCA,
            space,
        });
        const tipB = branchB_sinceLCA.at(-1);
        // at this point, we have gathered unique branch slices. however, it's
        // possible that we are trying to merge two branches that don't have
        // DNA. For now, let's just throw if this is the case.
        function throwIfUnexpected(someIbGibs) {
            const lcThrow = `${lc}[${throwIfUnexpected.name}]`;
            for (const someIbGib of someIbGibs) {
                if (!someIbGib.rel8ns) {
                    throw new Error(`(UNEXPECTED) ${lcThrow} someIbGib.rel8ns falsy? All ibgibs with mergeable timelines are expected to have rel8ns. addr: ${getIbGibAddr({ ibGib: someIbGib })} (E: 5534a478cd183a5ee27b28487448b226)`);
                }
                if (!someIbGib.rel8ns.dna) {
                    throw new Error(`(UNEXPECTED) someIbGib.rel8ns.dna falsy? We only have implemented grafting for dna. addr: ${getIbGibAddr({ ibGib: someIbGib })} (there should be another route for merging for non-dna timelines) (E: d86b388c627afd613f897e3819d8ab26)`);
                }
                if (someIbGib.rel8ns.dna.length === 0) {
                    throw new Error(`(UNEXPECTED) someIbGib.rel8ns.dna.length === 0? We only have implemented grafting for dna. addr: ${getIbGibAddr({ ibGib: someIbGib })} (there should be another route for merging for non-dna timelines) (E: bf973662a0689dd9089912b861af2826)`);
                }
            }
        }
        throwIfUnexpected(branchA_sinceLCA);
        throwIfUnexpected(branchB_sinceLCA);
        // #endregion compose unique branches since LCA
        // So we now have two timeline slices. We need to decide which one we
        // will graft onto the other, i.e., which one is the base and continued,
        // and which one will ultimately be orphaned.
        // #region Determine Base and Orphan based on timestamp
        /**
         * tip of the branch which we will graft onto
         */
        let baseTip;
        let baseBranch;
        /**
         * tip of the branch that gets orphaned.
         */
        let orphanTip;
        /**
         * we will use these ibgib's dna to graft onto {@link baseTip}
         */
        let orphanBranch;
        const timestampInfoA = getTimestampInfo({ ibGib: tipA, });
        const ticksA = timestampInfoA.valid ?
            Number.parseInt(timestampInfoA.ticks) :
            Number.MAX_SAFE_INTEGER;
        const msA = timestampInfoA.valid ? (timestampInfoA.ms ?? 0) : 0;
        const timestampInfoB = getTimestampInfo({ ibGib: tipB, });
        const ticksB = timestampInfoB.valid ?
            Number.parseInt(timestampInfoB.ticks) :
            Number.MAX_SAFE_INTEGER;
        const msB = timestampInfoB.valid ? (timestampInfoB.ms ?? 0) : 0;
        if (ticksA < ticksB) {
            // A is earlier, use A as base
            baseTip = tipA;
            baseBranch = branchA_sinceLCA;
            orphanTip = tipB;
            orphanBranch = branchB_sinceLCA;
        }
        else if (ticksA > ticksB) {
            // B is earlier, use B as base
            baseTip = tipB;
            baseBranch = branchB_sinceLCA;
            orphanTip = tipA;
            orphanBranch = branchA_sinceLCA;
        }
        else {
            // equal ticks, use ms as tiebreak
            if (msA <= msB) {
                // A is earlier (or equal), use A as base
                baseTip = tipA;
                baseBranch = branchA_sinceLCA;
                orphanTip = tipB;
                orphanBranch = branchB_sinceLCA;
            }
            else {
                // B is earlier, use B as base
                baseTip = tipB;
                baseBranch = branchB_sinceLCA;
                orphanTip = tipA;
                orphanBranch = branchA_sinceLCA;
            }
        }
        const baseTipAddr = getIbGibAddr({ ibGib: baseTip });
        const baseBranchAddrs = baseBranch.map(x => getIbGibAddr({ ibGib: x }));
        const orphanTipAddr = getIbGibAddr({ ibGib: orphanTip });
        const orphanBranchAddrs = orphanBranch.map(x => getIbGibAddr({ ibGib: x }));
        if (logalot) {
            console.log(`${lc} baseTipAddr: ${baseTipAddr} (I: 93353d9b9b68b17088f57958df85e826)`);
        }
        if (logalot) {
            console.log(`${lc} orphanTipAddr: ${orphanTipAddr} (I: 14df8867adc5dab97ff3c1e989b40e26)`);
        }
        // #endregion Determine Base and Orphan based on timestamp
        // #region Replay DNA from Orphan onto Base
        // Gather DNA addresses from the Orphan branch that have not yet been
        // applied.
        // NOTE: in my prototype, I believe this is where we would decide to
        // merge via state or dna, but right now we're just going to throw if it
        // doesn't have dna.
        const [latestCommonFrameIbGib] = await getIbGibsFromCache_fallbackToSpaces({
            addrs: [latestCommonFrameAddr],
            space,
        });
        // #region sanity/validation
        if (!latestCommonFrameIbGib) {
            throw new Error(`(UNEXPECTED) latestCommonFrameIbGib falsy? (E: ead2020b7ab8e4c4f824573133e33826)`);
        }
        if (!latestCommonFrameIbGib.rel8ns) {
            throw new Error(`(UNEXPECTED) latestCommonFrameIbGib.rel8ns falsy? (E: b3f338032d19816fc87d3c78af255726)`);
        }
        if (!latestCommonFrameIbGib.rel8ns.dna || latestCommonFrameIbGib.rel8ns.dna.length === 0) {
            throw new Error(`(UNEXPECTED) latestCommonFrameIbGib.rel8ns.dna falsy/empty? It should at least have the first fork transform. (E: 98fd8c725f484d85c8d23ceb181ce826)`);
        }
        // #endregion sanity/validation
        /**
         * we want to apply each unique dna per orphan frame. so we start with the LCF dna and iterate through each orphan frame, tacking on that frame's unique dna.
         */
        const dnaAddrsAlreadyAccountedFor = latestCommonFrameIbGib.rel8ns.dna.concat();
        const dnaAddrsToApply = [];
        for (const orphanFrame of orphanBranch) {
            if (!orphanFrame.rel8ns) {
                throw new Error(`(UNEXPECTED) orphanFrame.rel8ns falsy? (E: ed910e3b930b1b5388d87c6f736a4826)`);
            }
            const orphanDnaAddrsToApply = getUniqueDnas({
                base: dnaAddrsAlreadyAccountedFor,
                extended: orphanFrame.rel8ns.dna ?? [],
                throwIfExtendedDoesntContainBase: true,
            });
            orphanDnaAddrsToApply.forEach(x => dnaAddrsAlreadyAccountedFor.push(x));
            if (orphanDnaAddrsToApply.length === 0) {
                throw new Error(`(UNEXPECTED) orphanDnaAddrsToApply empty? (E: d44ed439e538e5b538d12d71063f0826)`);
            }
            orphanDnaAddrsToApply.forEach(x => dnaAddrsToApply.push(x));
        }
        // 3. Fetch the DNA stones
        /**
         * NOTE: These are in the same exact order as the addrs
         */
        const dnaIbGibsToApply = await getIbGibsFromCache_fallbackToSpaces({
            addrs: dnaAddrsToApply,
            readCache_graph: ibGibReadCache,
            space,
        });
        // 4. Replay Transforms onto Base
        /**
         * This will contain ALL ibgibs created, including the return new tip
         * and all DNA.
         */
        const createdIbGibs_Running = [];
        /**
         * This is the new tip of the graft, but before we have added the graft info metadata.
         *
         * NOTE: this is not a constant because we may reassign this if we do
         * any additional processing before doing that final graft info (which
         * will always be the very last part of this graft).
         */
        let graftTip_sansGraftInfo = await applyTransforms({
            src: baseTip,
            dnaAddrsToApplyToStoreVersion_MUTATES_IN_PLACE: dnaAddrsToApply.concat(),
            allLocalIbGibs: dnaIbGibsToApply,
            createdIbGibs_Running,
        });
        // #region validation/sanity
        if (!graftTip_sansGraftInfo.data) {
            throw new Error(`(UNEXPECTED) graftTip_sansGraftInfo.data falsy? (E: 4792f8e84fc84966e89220fae0657826)`);
        }
        if (!graftTip_sansGraftInfo.data.n) {
            throw new Error(`(UNEXPECTED) graftTip_sansGraftInfo.data.n falsy? (E: a2b3ca4cdec8d01f282dd3e4ff9eab26)`);
        }
        if (createdIbGibs_Running.length === 0) {
            throw new Error(`(UNEXPECTED) applyTransforms called but createdIbGibs_Running is empty? This should contain all of the  (E: 5b6c788488f5095b08eb8e31d55b1726)`);
        }
        // #endregion validation/sanity
        console.warn(`${lc} START...................... Do we get a bunch of "already had addr(s)" messages for dna ibgibs? I'm thinking we will already have the dna ibgibs stored in the space (temp space?) before we do this graft. Maybe it creates new dna, I don't know. remove this later when we decide something to do about it. space.ib: ${space.ib} (W: bb60481ecabaa6f49e14203891824a26)`);
        await putInSpace({ space, ibGibs: createdIbGibs_Running }); // probably will already have the dnas?
        console.warn(`${lc} END........................ Do we get a bunch of "already had addr(s)" messages for dna ibgibs? I'm thinking we will already have the dna ibgibs stored in the space (temp space?) before we do this graft. Maybe it creates new dna, I don't know. remove this later when we decide something to do about it. space.ib: ${space.ib} (W: bb60481ecabaa6f49e14203891824a26)`);
        const { mapWithTjp_NoDna, mapWithTjp_YesDna: newGraftedIbGibsMap, mapWithoutTjps: _unusedBcDnaIsAlreadyStoredDontRegister } = splitPerTjpAndOrDna({ ibGibs: createdIbGibs_Running });
        if (Object.keys(mapWithTjp_NoDna).length > 0) {
            throw new Error(`(UNEXPECTED) mapWithTjp_NoDna is populated? We've just applied the dna from the orphan branch which created ibgibs (and dnas). But we are expected to be executing on an ibgib timeline that has dna. The applyTransforms was not expected to create any other ibgibs than the one in our timeline (with dna) and any corresponding dnas. (E: 236e989fb5e8a4a498a21b98f718c826)`);
        }
        const newGraftedIbGibs_orderNotGuaranteed = Object.values(newGraftedIbGibsMap);
        /** guarantees the order of the timeline */
        const newGraftedIbGibsByTjp = getTimelinesGroupedByTjp({ ibGibs: newGraftedIbGibs_orderNotGuaranteed });
        // #region sanity/validation
        if (Object.keys(newGraftedIbGibsByTjp).length === 0) {
            throw new Error(`(UNEXPECTED) newGraftedIbGibsByTjp has 0 timelines? we expect this to have a single timeline: the one we're grafting. (E: 2b7127bd0c6bfeaec8d66038ba208226)`);
        }
        if (Object.keys(newGraftedIbGibsByTjp).length > 1) {
            throw new Error(`(UNEXPECTED) newGraftedIbGibsByTjp has more than one timeline? we expect this to have a single timeline: the one we're grafting. (E: 076758885b8ea7c89849a76334222326)`);
        }
        // #endregion sanity/validation
        /**
         * Running list of ALL ibgibs CREATED ANEW for this graft INCLUDING
         * graftTip_sansGraftInfo (the last in the array).
         */
        Object.values(newGraftedIbGibsByTjp)[0].forEach(x => newGraftedIntermediateIbGibs_ordered.push(x));
        for (const newGraftedIbGib of newGraftedIntermediateIbGibs_ordered) {
            await registerNewIbGib({ ibGib: newGraftedIbGib, space });
        }
        // #endregion Replay DNA from Orphan onto Base
        // we applied the transforms, but what if the two branch tips had
        // different data.text values? this means that we're talking about a
        // text document/thing where we want to merge the text if possible and
        // if it's part of the conflict strategy.
        // I've though about more advanced architecture for this for future
        // strategies and yada yada, but I'm just doing a simple check here for
        // now.
        // #region merge data.text if applicable
        /**
         * if truthy, then we did a post hoc merge of the two branches'
         * data.text value. We will need to note this in the graft info.
         */
        let mergeTextDetails;
        if (conflictStrategy === SyncConflictStrategy.optimisticWithLCS) {
            // #region sanity/validation
            if (!baseTip.data) {
                throw new Error(`(UNEXPECTED) baseTip.data falsy? (E: 92623857e19b9b4e081e8c8d9eb45526)`);
            }
            if (!orphanTip.data) {
                throw new Error(`(UNEXPECTED) orphanTip.data falsy? (E: 2e6828a4c9226ac917ab6fe833672826)`);
            }
            // #endregion sanity/validation
            // perform the check/merge
            if (
            // either has text...
            (orphanTip.data.text || baseTip.data.text) &&
                // ...but text is not equal
                orphanTip.data.text !== baseTip.data.text) {
                if (logalot) {
                    console.log(`${lc} different data.text found. executing text merge for conflictStrategy ${conflictStrategy} (I: cfb5f9580832040388fc576eb71ac526)`);
                }
                // execute merge on the text itself, best effort
                const textA = orphanTip.data.text ?? '';
                const textAAddr = orphanTipAddr;
                const textB = baseTip.data.text ?? '';
                const textBAddr = baseTipAddr;
                const newText = await mergeTextByLongestCommonSubsequence({
                    textA,
                    textB,
                });
                const resMut8Text = await mut8({
                    src: graftTip_sansGraftInfo,
                    dataToAddOrPatch: { text: newText, },
                    dna: true,
                    nCounter: !!graftTip_sansGraftInfo.data.n,
                });
                // #region sanity/validation
                if (resMut8Text.intermediateIbGibs && resMut8Text.intermediateIbGibs.length > 0) {
                    throw new Error(`(UNEXPECTED) resMut8Text.intermediateIbGibs truthy/non-empty? This is expected to only create the newIbGib with no intermediates. (E: 7932a8964a3869accadc9b0d93d23826)`);
                }
                if (!resMut8Text.dnas || resMut8Text.dnas.length === 0) {
                    throw new Error(`(UNEXPECTED) resMut8Text.dnas falsy/empty? We're expected to generate dna from this merge text mut8. (E: fdf03754bf0f8661f80f59ea0701dd26)`);
                }
                if (resMut8Text.dnas.length !== 1) {
                    throw new Error(`(UNEXPECTED) resMut8Text.dnas.length !== 1? we're expecting a single mut8 dna. (E: 9574d86a9108095cb454b049bcca5326)`);
                }
                // #endregion sanity/validation
                // add dna/newIbGib to running lists of ibgibs created in graft
                newGraftDnaIbGibs.push(resMut8Text.dnas[0]);
                newGraftedIntermediateIbGibs_ordered.push(resMut8Text.newIbGib);
                await persistTransformResult({ resTransform: resMut8Text, space, });
                await registerNewIbGib({ ibGib: resMut8Text.newIbGib, space });
                mergeTextDetails = {
                    mergeTextStrategy: MergeTextStrategy.lcs,
                    textAAddr,
                    textBAddr,
                };
                // update the new graft tip, still sans graft info
                graftTip_sansGraftInfo = resMut8Text.newIbGib;
                if (!graftTip_sansGraftInfo.data) {
                    throw new Error(`(UNEXPECTED) graftTip_sansGraftInfo.data falsy after mut8 merge text? (E: 6b65d83df6681887686a0098144eea26)`);
                }
            }
        }
        // #endregion merge data.text if applicable
        // at this point, we have mechanically created new ibgibs that were the
        // result of reapplying the dna of the orphaned branch. Now we will "cap
        // off" this new merged branch with a metadata ibgib that contains info
        // about the graft we performed. This graft info basically describes the
        // latest common frame and the two branches and what we did with them.
        // Practically speaking, this keeps references to the original orphan
        // branch, so we do not lose information due to our opinionated graft,
        // and later tooling can examine this graft and make custom repairs as
        // needed. This graft info is simply rel8d to the new merged tip we just
        // created.
        // #region append final graft info frame to timeline
        const graftInfo = await createGraftInfo({
            conflictStrategy,
            details: `Replayed ${dnaAddrsToApply.length} transforms from orphan (${orphanTipAddr}) onto base (${baseTipAddr}).`,
            addlGraftDetails: mergeTextDetails ? [mergeTextDetails] : undefined,
            latestCommonFrameAddr,
            baseAddrsSinceLCA: baseBranchAddrs,
            orphanAddrsSinceLCA: orphanBranchAddrs,
            dnaAddrsApplied: dnaAddrsToApply,
        });
        if (logalot) {
            console.log(`${lc} intrinsic graft complete. graftInfo to be appended to timeline:\n${pretty(graftInfo)} (I: ab9d1e73db9a3ff4f8241f38197bb826)`);
        }
        await putInSpace({ ibGib: graftInfo, space });
        const graftInfoAddr = getIbGibAddr({ ibGib: graftInfo });
        const resRel8GraftInfo = await rel8({
            src: graftTip_sansGraftInfo,
            rel8nsToAddByAddr: {
                [GRAFT_INFO_REL8N_NAME]: [graftInfoAddr],
            },
            dna: true,
            nCounter: !!graftTip_sansGraftInfo.data.n,
        });
        // #region sanity/validation
        if (resRel8GraftInfo.intermediateIbGibs && resRel8GraftInfo.intermediateIbGibs.length > 0) {
            throw new Error(`(UNEXPECTED) resRel8GraftInfo.intermediateIbGibs truthy/non-empty? This is expected to only create the newIbGib with no intermediates. (E: 78b928ace4287b4dc82f413fdad60826)`);
        }
        if (!resRel8GraftInfo.dnas || resRel8GraftInfo.dnas.length === 0) {
            throw new Error(`(UNEXPECTED) resRel8GraftInfo.dnas falsy/empty? We're expected to generate dna from this rel8. (E: 4c8fdde70368d643f86732dfa48c5526)`);
        }
        if (resRel8GraftInfo.dnas.length !== 1) {
            throw new Error(`(UNEXPECTED) resRel8GraftInfo.dnas.length !== 1? we're expecting a single rel8 dna. (E: c2f5085c1718d7149e04f5c814027926)`);
        }
        // #endregion sanity/validation
        // add dna to running list BUT NOT the newIbGib, because it's NOT an intermediate.
        newGraftDnaIbGibs.push(resRel8GraftInfo.dnas[0]);
        await persistTransformResult({ resTransform: resRel8GraftInfo, space, });
        await registerNewIbGib({ ibGib: resRel8GraftInfo.newIbGib, space });
        // #endregion append final graft info frame to timeline
        const finalGraftTip = resRel8GraftInfo.newIbGib;
        if (logalot) {
            console.log(`${lc} finalGraftTip: ${pretty(finalGraftTip)} (I: 454e72957be8221b96931c289053fc26)`);
        }
        const result = {
            dnasReplayed: dnaIbGibsToApply,
            dnasCreatedInGraft: newGraftDnaIbGibs,
            intermediateIbGibsCreatedInGraft: newGraftedIntermediateIbGibs_ordered,
            newGraftTip: finalGraftTip,
            graftInfoCreated: graftInfo,
        };
        if (logalot) {
            console.log(`${lc} result... (I: d6a02f95eb2564c621810ef855434226)`);
        }
        if (logalot) {
            console.dir(result);
        }
        return result;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
/**
 * Guard that determines if given {@link ibGib} is a {@link GraftInfoIbGib_V1}
 * @returns true if {@link ibGib} is a {@link GraftInfoIbGib_V1}, else false
 */
export function isGraftInfo(ibGib) {
    const lc = `[${isGraftInfo.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: fe9adfe1a3175d87982507c8851f5826)`);
        }
        // gauntlet style, throws if not an ibGib
        if (!ibGib) {
            throw new Error(`(UNEXPECTED) not a graft info, ibGib falsy? (E: bf06cdd700788e777f72fe5a5bdb0e26)`);
        }
        if (!ibGib.data) {
            throw new Error(`not a graft info, ibGib.data falsy (E: 5e1b28843ec8571a7623046a06195826)`);
        }
        if (!ibGib.ib.startsWith(GRAFT_INFO_ATOM)) {
            throw new Error(`not graft info, ibgib.ib does not start with ${GRAFT_INFO_ATOM} (E: cec6a8e25ff8746aef813e38cbebce26)`);
        }
        return true;
    }
    catch (error) {
        if (logalot) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
        }
        return false;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
//# sourceMappingURL=graft-info-helpers.mjs.map