import { extractErrorMsg } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
import { getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
import {
    IbGibAddr, TransformOpts, TransformOpts_Mut8, TransformOpts_Rel8,
    TransformResult,
} from "@ibgib/ts-gib/dist/types.mjs";
import { mut8 } from "@ibgib/ts-gib/dist/V1/transforms/mut8.mjs";
import { rel8 } from "@ibgib/ts-gib/dist/V1/transforms/rel8.mjs";
import { IbGib_V1 } from "@ibgib/ts-gib/dist/V1/types.mjs";

/**
 * iteratively (recursively) apply transforms in
 * `dnaAddrsToApplyToStoreVersion` to the store's version of the ibgib,
 * keeping track of all dependencies used/created.  ibgibs that we create
 * along the way that we'll set to createdIbGibs to include dna,
 * intermediate ibGibs, and newer versions of the source.
 *
 * populates the given `createdibGibs_Running` array, INCLUDING THE MOST
 * RECENT IBGIB THAT WILL BE RETURNED BY THE FUNCTION.
 *
 * @returns the final ibgib produced from the transform applications.
 */
export async function applyTransforms({
    src,
    createdIbGibs_Running,
    dnaAddrsToApplyToStoreVersion,
    allLocalIbGibs,
}: {
    /**
     * Most recent src to which we will apply the given
     * `dnaAddrsToApplyToStoreVersion`.
     */
    src: IbGib_V1,
    /**
     * This will be populated through recursive iterations while applying
     * dnas. Everything, including new dnas, new intermediate ibgibs, and
     * new "src" ibgibs will be added to this before the final recursive
     * call returns the final produced (out) ibgib.
     */
    createdIbGibs_Running: IbGib_V1[],
    /**
     * Transform addresses whose ibgib will be applied to the given `src`.
     * This will be reduced by one per each recursive call to this function
     * that is made.
     */
    dnaAddrsToApplyToStoreVersion: IbGibAddr[],
    /**
     * Should contain the dna ibGibs that are given in
     * `dnaAddrsToApplyToStoreVersion`
     */
    allLocalIbGibs: IbGib_V1[],
}): Promise<IbGib_V1> {
    const lc = `[${applyTransforms.name}]`;
    try {
        if (dnaAddrsToApplyToStoreVersion.length > 0) {
            const dnaAddrToApply = dnaAddrsToApplyToStoreVersion.splice(0, 1)[0];
            // we expect this dna ibgib to supplied to us, but need to check
            const dnaIbGib_IntermediateArray =
                allLocalIbGibs.filter(x => getIbGibAddr({ ibGib: x }) === dnaAddrToApply);
            let dnaIbGib: IbGib_V1;
            if (dnaIbGib_IntermediateArray.length === 1) {
                dnaIbGib = dnaIbGib_IntermediateArray[0];
            } else if (dnaIbGib_IntermediateArray.length === 0) {
                throw new Error(`dna ibGib not found in supplied allLocalIbGibs. dnaAddr: ${dnaAddrToApply}. (E: 7f56826852cf48a79ab8af16bf27e284)`);
            } else {
                // more than one ibgib with the dna address?
                throw new Error(`More than one ibGib in allLocalIbGibs with the dna address of ${dnaAddrToApply}? (E: a726134f4cc14a4fb2ed2d39d22af17c)(UNEXPECTED)`);
            }

            // we have our dna to apply, so do so against the incoming src
            // any transform options is actually the dna.data plus src info.
            let argTransform = dnaIbGib.data as TransformOpts<IbGib_V1>;
            argTransform.src = src;
            argTransform.srcAddr = getIbGibAddr({ ibGib: src });
            let resTransform: TransformResult<IbGib_V1>;
            switch (argTransform.type) {
                case 'mut8':
                    resTransform = await mut8(argTransform as TransformOpts_Mut8<IbGib_V1, any>);
                    break;
                case 'rel8':
                    resTransform = await rel8(argTransform as TransformOpts_Rel8<IbGib_V1>);
                    break;
                case 'fork':
                    throw new Error(`fork transform not expected. atow only a single fork is expected at the beginning of the lifetime of a tjp ibgib. This fork defines the uniqueness of the tjp ibgib. When merging another tjp ibgib, we would only expect update transforms to be mut8 or rel8. (E: f8ad6996ac5545edad2d58a293d37d94)`);
                default:
                    throw new Error(`unknown dna argTransform.type (${argTransform.type}) for dna address: ${dnaAddrToApply}.  Expecting either mut8 or rel8. (fork transform is known but not expected either.) (E: 52a98db56e934e4cb42c64e2f45fa552)`);
            }

            // add intermediate ibgibs (including dna) to createdIbGibs_Running
            (resTransform.intermediateIbGibs ?? []).forEach(x => createdIbGibs_Running.push(x));
            (resTransform.dnas ?? []).forEach(x => createdIbGibs_Running.push(x));
            createdIbGibs_Running.push(resTransform.newIbGib);

            // not 100% but I believe the created dna should deep equal our
            // incoming dna that we applied. so we'll warn if it doesn't.
            // (ibgib addresses matching necessitates deep equal)
            if ((resTransform.dnas ?? []).length > 0) {
                if (!resTransform.dnas!.some(x => getIbGibAddr(x) === dnaAddrToApply)) {
                    console.warn(`${lc}(UNEXPECTED) Expected to generate exact dna that we applied in transform. dnaAddr: ${dnaAddrToApply} (W: 4a364c0b5d8d46c8af6bd540915fd973)`);
                }
            } else {
                console.warn(`${lc}(UNEXPECTED) expected resTransform to include generated dna. dnaAddr: ${dnaAddrToApply}(W: 9648e2d5c40d42b7b9fb7efb09c5b13a)`)
            }


            // call recursively
            return await applyTransforms({
                src: resTransform.newIbGib,
                createdIbGibs_Running,
                dnaAddrsToApplyToStoreVersion,
                allLocalIbGibs,
            });
        } else {
            // no more dna to apply.
            // createdIbGibs_Running should be populated
            if (createdIbGibs_Running.length === 0) {
                console.warn(`${lc} no dna transforms to apply but createdIbGibs_Running is empty. Dna is expected to start with at least one transform, otherwise don't call this function. (W: 52c7f2f6bd7e4bd0b31f53611b90268b)(UNEXPECTED)`)
            }
            return src;
        }
    } catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
}
