/**
 * @module agent-helpers
 *
 * Helper functions designed to simplify common ibGib operations for agentic usage.
 * These abstract away the low-level Factory_V1 details and provide clear, semantic
 * actions for creating and modifying data.
 */
import { extractErrorMsg, getUUID } 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 { KeystoneService_V1 } from './keystone/keystone-service-v1.mjs';
import { Factory_V1 } from '@ibgib/ts-gib/dist/V1/factory.mjs';
import { mut8Timeline, appendToTimeline } from './timeline/timeline-api.mjs';
import { persistTransformResult, registerNewIbGib } from './witness/space/space-helper.mjs';
const logalot = GLOBAL_LOG_A_LOT;
/**
 * Creates a "Stone" ibGib - an immutable, primitive-like data record with no history/timeline.
 * Useful for static data, configurations, or standalone records.
 */
export async function createStoneTestHelper({ ib, data, rel8ns, uuid, noTimestamp, }) {
    const lc = `[${createStoneTestHelper.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: e4b15ee74de22ec9a917c578adc83425)`);
        }
        if (!ib) {
            throw new Error(`ib required (E: 3d5df8fd5b880e0e0dcedd37dbc3f825)`);
        }
        // the parent primitive ib is almost always the atom of the ib.
        const atom = ib.split(' ').at(0);
        if (uuid) {
            if (!data) {
                data = {};
            }
            data.uuid = await getUUID();
        }
        const stone = await Factory_V1.stone({
            parentPrimitiveIb: atom,
            ib,
            data,
            rel8ns,
            uuid,
        });
        return stone;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
/**
 * Creates a new "Living" Timeline Root.
 * This is the starting point for any mutable data structure (a file, a user profile, a post).
 */
export async function createTimelineRootTestHelper({ ib, data, space, }) {
    const lc = `[${createTimelineRootTestHelper.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: 8f50a8cf0268742048f7c188feb5a825)`);
        }
        // const resTimeline = await createTimeline({
        //     ib,
        //     data,
        //     rel8ns: undefined,
        //     metaspace, space
        // });
        // const timeline = resTimeline.newIbGib;
        // return timeline;
        // Determine the parent to fork based on the provided options.
        const atom = ib.split(' ').at(0);
        const parentIbGib = Factory_V1.primitive({ ib: atom });
        const resNew = await Factory_V1.firstGen({
            parentIbGib,
            ib,
            data,
            rel8ns: undefined,
            // these options are what make this a "timeline"
            dna: true,
            nCounter: true,
            tjp: { timestamp: true, uuid: true },
        });
        // Persist the new timeline and its DNA to the designated space.
        await persistTransformResult({ resTransform: resNew, space });
        await registerNewIbGib({
            ibGib: resNew.newIbGib,
            space,
            /**
             * We don't need to broadcast here because it's a new, independent
             * timeline, so there are no listeners yet.
             */
            fnBroadcast: undefined,
        });
        // We don't need to lock here because it's a new, independent timeline,
        // so there are no concurrency concerns yet.
        return resNew.newIbGib;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
/**
 * Creates a valid, mock Keystone Service for testing purposes.
 * This mocks the identity and signing logic needed for Sync operations.
 */
export async function getTestKeystoneServiceHelper() {
    class MockKeystoneService extends KeystoneService_V1 {
        async getIdentity() {
            // Return a dummy identity
            // We use the Factory directly here to just get *something* that looks like an ibgib
            const res = await Factory_V1.firstGen({
                parentIbGib: Factory_V1.primitive({ ib: 'identity' }),
                ib: 'identity',
                data: { uuid: await getUUID() },
                dna: true,
            });
            return res.newIbGib;
        }
        // Implement other methods as no-ops or simple mocks
        async sign(args) { return args.latestKeystone; /* minimal mock */ }
        async verify(args) { return []; /* no errors */ }
    }
    return new MockKeystoneService();
}
/**
 * Stateful transformer for test operations.
 * Manages counters, rounds, and execution history.
 *
 * Usage:
 * ```typescript
 * const transformer = new TestTransformer();
 * const r1 = transformer.newRound({ name: 'r1_common_history' });
 *
 * const step = await transformer.mut8({
 * Test transformer class that provides fluent method chaining for timeline testing.
 * Automatically tracks rounds, steps, provides test data snapshots, and auto-versions ibs.
 *
 * Ib versioning schema: `[base] [r1r2...] [v0v1v2]` (space-delimited)
 * Example: `alpha r1 v0` → `alpha r1 v1` → `alpha r1r2 v0`
 */
export class TestTransformer {
    lc = `[${TestTransformer.name}]`;
    metaspace;
    sourceSpace;
    destSpace;
    mut8Counter = 0;
    rel8Counter = 0;
    rounds = [];
    get currentRound() {
        return this.rounds && this.rounds.length > 0 ?
            this.rounds.at(-1) :
            undefined;
    }
    ;
    constructor(metaspace, sourceSpace, destSpace) {
        this.metaspace = metaspace;
        this.sourceSpace = sourceSpace;
        this.destSpace = destSpace;
    }
    /**
     * Start a new round of testing operations.
     */
    newRound({ name, description }) {
        const round = { name, description, steps: [] };
        this.rounds.push(round);
        return round;
    }
    /**
     * Get the current active round.
     */
    getCurrentRound() {
        return this.currentRound;
    }
    /**
     * Get all steps across all rounds.
     */
    getAllSteps() {
        return this.rounds.flatMap(r => r.steps);
    }
    /**
     * Find a round by name.
     */
    getRound(name) {
        return this.rounds.find(r => r.name === name);
    }
    /**
     * Reset all state (counters and rounds).
     */
    reset() {
        this.mut8Counter = 0;
        this.rel8Counter = 0;
        this.rounds = [];
    }
    /**
     * Get space by location.
     */
    getSpace(location) {
        return location === 'source' ? this.sourceSpace : this.destSpace;
    }
    /**
     * Parses ib to extract base, rounds, and version.
     * Schema: [base] [r1r2...] [v0v1...] (space-delimited)
     * Example: "alpha r1 v2" → { base: "alpha", rounds: "r1", version: 2 }
     */
    parseIb(ib) {
        const parts = ib.split(' ');
        if (parts.length === 1) {
            // Initial ib, no rounds or versions yet
            return { base: parts[0], rounds: '', version: -1 };
        }
        // Find round and version parts
        let base = '';
        let rounds = '';
        let versionStr = '';
        for (const part of parts) {
            if (part.startsWith('r')) {
                rounds = part;
            }
            else if (part.startsWith('v')) {
                versionStr = part;
            }
            else {
                base = part;
            }
        }
        const version = versionStr ? parseInt(versionStr.substring(1)) : -1;
        return { base, rounds, version };
    }
    /**
     * Generates next ib based on current ib and round context.
     * Increments version and appends round if needed.
     */
    generateNextIb(currentIb) {
        const { base, rounds, version } = this.parseIb(currentIb);
        if (!this.currentRound) {
            throw new Error(`${this.lc}: Cannot generate next ib without active round`);
        }
        const currentRoundName = this.currentRound.name;
        const roundPrefix = currentRoundName.split('_')[0]; // e.g., "r1" from "r1_common_history"
        // Check if we need to append new round
        const needsNewRound = !rounds.includes(roundPrefix);
        const newRounds = needsNewRound ? (rounds + roundPrefix) : rounds;
        // Increment version (or start at v0 if new round)
        const nextVersion = needsNewRound ? 0 : version + 1;
        // Build new ib
        return `${base} ${newRounds} v${nextVersion}`;
    }
    /**
     * Create a timeline root with auto-tracking to current round.
     *
     * @example
     * const transformer = new TestTransformer(metaspace, sourceSpace, destSpace);
     * transformer.newRound({ name: 'r1_setup' });
     * const step = await transformer.create({
     *   ib: 'alpha',
     *   data: { type: 'alpha' },
     *   in: 'source',
     * });
     */
    async create({ ib, data, in: location, name, }) {
        const lc = `${this.lc}[create]`;
        if (!this.currentRound) {
            throw new Error(`${lc} No active round. Call newRound() first.`);
        }
        const space = this.getSpace(location);
        const timeline = await createTimelineRootTestHelper({
            ib,
            data,
            space,
        });
        const result = {
            ibGib: timeline,
            addr: getIbGibAddr({ ibGib: timeline }),
            space,
            metaspace: this.metaspace,
            infos: [],
            name: name || `${this.currentRound.name}_${ib}_create`,
        };
        this.currentRound.steps.push(result);
        return result;
    }
    /**
     * Perform a mutation with optional field mutation and auto-versioning.
     *
     * @example
     * // With field and auto value
     * const step = await transformer.mut8({
     *   stepInfo: prevStep,
     *   strField: 'commonField',
     * });
     * // Creates: alpha r1 v0, sets commonField: 'value_1'
     *
     * @example
     * // With field and custom value
     * const step = await transformer.mut8({
     *   stepInfo: prevStep,
     *   strField: { name: 'count', value: '42' },
     * });
     *
     * @example
     * // Ib-only mutation (no field)
     * const step = await transformer.mut8({ stepInfo: prevStep });
     */
    async mut8({ ibGib, stepInfo, in: location, strField, name, }) {
        const lc = `${this.lc}[mut8]`;
        if (!this.currentRound) {
            throw new Error(`${lc} No active round. Call newRound() first.`);
        }
        // Extract context
        let targetIbGib;
        let space;
        if (stepInfo) {
            targetIbGib = stepInfo.ibGib;
            space = stepInfo.space;
        }
        else if (ibGib && location) {
            targetIbGib = ibGib;
            space = this.getSpace(location);
        }
        else {
            throw new Error(`${lc} Must provide either stepInfo or (ibGib + in)`);
        }
        // Generate next ib with auto-versioning
        const nextIb = this.generateNextIb(targetIbGib.ib);
        // Build data to add/patch
        const dataToAddOrPatch = {};
        if (strField) {
            let fieldName;
            let fieldValue;
            if (typeof strField === 'string') {
                // Field name only, generate random value
                fieldName = strField;
                fieldValue = `value_${++this.mut8Counter}`;
            }
            else {
                // Field name and value provided
                fieldName = strField.name;
                fieldValue = strField.value;
                this.mut8Counter++; // Still increment counter
            }
            dataToAddOrPatch[fieldName] = fieldValue;
        }
        // Perform mutation
        const mutatedIbGib = await mut8Timeline({
            timeline: targetIbGib,
            mut8Opts: {
                mut8Ib: nextIb,
                dataToAddOrPatch,
            },
            metaspace: this.metaspace,
            space,
        });
        const mut8Info = {
            type: 'mut8',
            ib: nextIb,
            key: strField ? (typeof strField === 'string' ? strField : strField.name) : undefined,
            value: strField ? (typeof strField === 'string' ? `value_${this.mut8Counter}` : strField.value) : undefined,
        };
        const result = {
            ibGib: mutatedIbGib,
            addr: getIbGibAddr({ ibGib: mutatedIbGib }),
            space,
            metaspace: this.metaspace,
            infos: [mut8Info],
            name: name || `${this.currentRound.name}_${nextIb}`,
        };
        this.currentRound.steps.push(result);
        return result;
    }
    /**
     * Perform a relation with auto-generated rel8n name and auto-versioning.
     *
     * @example
     * const step = await transformer.rel8({
     *   stepInfo: prevStep,
     *   targetIbGibs: [beta, gamma],
     *   rel8nName: 'depends_on',
     * });
     */
    async rel8({ stepInfo, ibGib, in: location, targetIbGibs, rel8nName, name, }) {
        const lc = `${this.lc}[rel8]`;
        if (!this.currentRound) {
            throw new Error(`${lc} No active round. Call newRound() first.`);
        }
        // Extract context
        let sourceIbGib;
        let space;
        if (stepInfo) {
            sourceIbGib = stepInfo.ibGib;
            space = stepInfo.space;
        }
        else if (ibGib && location) {
            sourceIbGib = ibGib;
            space = this.getSpace(location);
        }
        else {
            throw new Error(`${lc} Must provide either stepInfo or (ibGib + in)`);
        }
        // Generate next ib with auto-versioning
        const nextIb = this.generateNextIb(sourceIbGib.ib);
        // Perform relation
        const relatedIbGib = await appendToTimeline({
            timeline: sourceIbGib,
            rel8nInfos: [{
                    rel8nName,
                    ibGibs: targetIbGibs
                }],
            metaspace: this.metaspace,
            space,
        });
        const rel8Info = {
            type: 'rel8',
            ib: nextIb,
            rel8nName,
            targetAddrs: targetIbGibs.map(x => getIbGibAddr({ ibGib: x })),
        };
        const result = {
            ibGib: relatedIbGib,
            addr: getIbGibAddr({ ibGib: relatedIbGib }),
            space,
            metaspace: this.metaspace,
            infos: [rel8Info],
            name: name || `${this.currentRound.name}_${nextIb}`,
        };
        this.currentRound.steps.push(result);
        return result;
    }
}
//# sourceMappingURL=test-helpers.mjs.map