/*
 * @module Subject_V1 witness class
 *
 * this is where you will find the witness class that contains behavior
 * for the Subject ibgib.
 *
 * A observable can do two things from the caller's side:
 *
 * 1. unsubscribe
 * 2. give a boolean on whether it has been unsubscribed or not
 *
 * A observable is created from a call to subscribe on an observable.  Since
 * the observable is also a witness, we can have this observable visit the
 * observable via `observable.witness(observable)`. IOW, the observable
 * witness ibgib is the argument to `witness` function.
 *
 * The observable will know that if a observable visits it, then the
 * observable needs to release/unsubscribe that observable.
 *
 * So since this is a light witness, we can just have two primitives be the
 * incoming argument ibgibs: unsubscribe^gib and unsubscribed^gib.
 */
import { extractErrorMsg, getUUID, pretty, clone, getIdPool, } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
import { DEFAULT_DATA_PATH_DELIMITER } from '@ibgib/helper-gib/dist/constants.mjs';
import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
import { ROOT } from '@ibgib/ts-gib/dist/V1/constants.mjs';
import { argy_, resulty_ } from '../../../witness/witness-helper.mjs';
import { getErrorIbGib, isError } from '../../error/error-helper.mjs';
import { DynamicFormFactoryBase } from '../../../witness/factory/dynamic-form-factory-base.mjs';
import { GLOBAL_LOG_A_LOT } from '../../../core-constants.mjs';
import { SubjectCmd, DEFAULT_SUBJECT_DATA_V1, DEFAULT_SUBJECT_REL8NS_V1, } from './subject-types.mjs';
import { SubjectFormBuilder, getSubjectIb, isObserver } from './subject-helper.mjs';
import { DEFAULT_DESCRIPTION_SUBJECT, } from './subject-constants.mjs';
import { ObservableCmd, } from '../observable/observable-types.mjs';
import { constantIbGib } from '../../other/ibgib-helper.mjs';
import { ObservableBase_V1 } from '../observable/observable-base-v1.mjs';
/**
 * for logging. import this constant from your project.
 */
const logalot = GLOBAL_LOG_A_LOT; // change this when you want to turn off verbose logging
/**
 * sketching...
 * under construction...
 */
export class Subject_V1 extends ObservableBase_V1 {
    /**
     * Log context for convenience with logging. (Ignore if you don't want to use this.)
     */
    lc = `[${Subject_V1.name}]`;
    // /**
    //  * Reference to the local ibgibs service, which is one way at getting at the
    //  * local user space.
    //  */
    // public metaspace: MetaspaceService | undefined;
    constructor(initialData, initialRel8ns) {
        super(initialData, initialRel8ns);
    }
    // #region public api methods
    asObservable() {
        return this;
    }
    async subscribe(observer) {
        const lc = `${this.lc}[${this.subscribe.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting... (I: 8e47b752314870ec78eb14342621f723)`);
            }
            const arg = await this.argy({
                argData: {
                    cmd: SubjectCmd.subscribe,
                    ibGibAddrs: [getIbGibAddr({ ibGib: observer })],
                },
                ibGibs: [observer],
            });
            const subscription = await this.witness(arg);
            return subscription;
        }
        catch (error) {
            console.error(`${lc} ${error.message}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
    async next(ibGib) {
        const lc = `${this.lc}[${this.next.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting... (I: b30363827c56450ca918c69aa15b0046)`);
            }
            const arg = await this.argy({
                argData: {
                    cmd: SubjectCmd.next,
                    ibGibAddrs: [getIbGibAddr({ ibGib })],
                },
                ibGibs: [ibGib],
            });
            await this.witness(arg); // ignores return value
        }
        catch (error) {
            console.error(`${lc} ${error.message}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
    async error(error) {
        const lc = `${this.lc}[${this.error.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting... (I: 7fcc30e3763c429ea8b212491ad9455a)`);
            }
            // wrap raw Error if necessary into an ErrorIbGib_V1
            let errorIbGib;
            if (typeof error === 'string') {
                errorIbGib = await getErrorIbGib({ rawMsg: error });
            }
            else if (error instanceof Error || typeof error.message === 'string') {
                errorIbGib = await getErrorIbGib({ rawMsg: extractErrorMsg(error) });
            }
            else if (!!error.ib && isError({ ibGib: error })) {
                errorIbGib = error;
            }
            else {
                throw new Error(`unknown error type. shold either be ErrorIbGib_V1 or an Error instance or have error.message string property. (E: 2cfcab85439c3c845bfe18e8ecde3523)`);
            }
            // build the arg
            const arg = await this.argy({
                argData: {
                    cmd: SubjectCmd.error,
                    ibGibAddrs: [getIbGibAddr({ ibGib: errorIbGib })],
                },
                ibGibs: [errorIbGib],
            });
            // do it
            await this.witness(arg); // ignores return value
        }
        catch (error) {
            console.error(`${lc} ${error.message}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
    async complete() {
        const lc = `${this.lc}[${this.complete.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting... (I: 5faaf13c6b0543d399815c03b9cdf80c)`);
            }
            // build the arg
            const arg = await this.argy({
                argData: {
                    cmd: SubjectCmd.complete,
                },
            });
            // do it
            await this.witness(arg); // ignores return value
        }
        catch (error) {
            console.error(`${lc} ${error.message}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
    // #endregion public api methods
    // #region witness sub-methods
    async witness_cmd({ arg, }) {
        const lc = `${this.lc}[${this.witness_cmd.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting... (I: 2bf7cbcadba446fa92cb649906b2b260)`);
            }
            let { cmd } = arg.data;
            switch (cmd) {
                case SubjectCmd.next:
                    return this.witness_next({ ibGib: arg });
                case SubjectCmd.error:
                    return this.witness_error({ ibGib: arg });
                case SubjectCmd.complete:
                    return this.witness_complete({ ibGib: arg });
                default:
                    // note: this is a call to the *super*.witness_cmd
                    return super.witness_cmd({ arg });
            }
        }
        catch (error) {
            console.error(`${lc} ${error.message}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
    async witness_next({ ibGib, }) {
        const lc = `${this.lc}[${this.witness_next.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting... (I: 600da780f13f5137a2f76aad344b2923)`);
            }
            if (this.isCompleteOrErrored) {
                if (logalot) {
                    console.log(`${lc} this.isCompleteOrErrored is true. returning early without producing. (I: aa823831853b134b19657c8673838c23)`);
                }
                return ROOT;
            }
            // validate
            if ((ibGib.ibGibs ?? []).length !== 1) {
                throw new Error(`ibGib.ibGibs required. should contain one payload ibgib (E: 4046802ff96d4adfafcee7a74d96af5e)`);
            }
            // extract the payload
            const payloadIbGib = ibGib.ibGibs[0];
            // go ahead and persist this in our history
            if (this.data.replay) {
                this._pastPayloads.push(payloadIbGib);
            }
            // dispatch to subscribers (serially)
            const subscriberTuples = Object.values(this._subscribers);
            for (let i = 0; i < subscriberTuples.length; i++) {
                const [subscription, observer] = subscriberTuples[i];
                // we remove subscribers on unsubscribe, but just in case we
                // will double-check here if we are still subscribed
                if (logalot) {
                    console.log(`${lc} ensuring subscription not unsubscribed (I: ade3fd6d6ff2b5ec69eb8143e32ce223)`);
                }
                const unsubscribed = await subscription.unsubscribed();
                if (!unsubscribed) {
                    if (logalot) {
                        console.log(`${lc} subscription still active (I: b9ab83b28acecf0d823fdba46a81dc23)`);
                    }
                    // dispatch to observer's via its `next` fn
                    await observer.next(payloadIbGib);
                }
                else {
                    debugger; // unexpected error state
                    console.warn(`${lc} (UNEXPECTED) subscriber reference still held but is unsubscribed? skipping for now (i.e. won't remove subscriber) in case that code is in yielded execution and just hasn't gotten around to it with event loop (but that would be weird). (W: c116e799f6b9483c97fd030eb0dd09ea)`);
                }
            }
            return ROOT;
        }
        catch (error) {
            console.error(`${lc} ${error.message}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
    async witness_error({ ibGib, }) {
        const lc = `${this.lc}[${this.witness_error.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting... (I: 3367a73bc83c47f98d9ecfe426dc300e)`);
            }
            // validate
            if ((ibGib.ibGibs ?? []).length !== 1) {
                throw new Error(`ibGib.ibGibs required. should contain one error ibgib (E: 905474b45bcb4f0abe7bd4618f08f32c)`);
            }
            // extract the payload
            const errIbGib = ibGib.ibGibs[0];
            // store the error in this observable
            this._pastError = errIbGib;
            // dispatch to subscribers (serially)
            const subscriberTuples = Object.values(this._subscribers);
            for (let i = 0; i < subscriberTuples.length; i++) {
                const [subscription, observerOrWitness] = subscriberTuples[i];
                // depends on if observer or witness...
                if (isObserver({ ibGib: observerOrWitness })) {
                    // observer uses observer interface
                    const observer = observerOrWitness;
                    if (observer.error) {
                        // we remove subscribers on unsubscribe, but just in case we
                        // will double-check here if we are still subscribed
                        const unsubscribed = await subscription.unsubscribed();
                        if (!unsubscribed) {
                            // dispatch to observer's via its `next` fn
                            await observer.error(errIbGib);
                        }
                        else {
                            console.warn(`${lc} (UNEXPECTED) subscriber reference still held but is unsubscribed? skipping for now (i.e. won't remove subscriber) in case that code is in yielded execution and just hasn't gotten around to it with event loop (but that would be weird). (W: 8946e97f00fd4146a470064b490a4c29)`);
                        }
                    }
                    else {
                        // no error handler for this observer
                        if (logalot) {
                            console.log(`${lc} no error handler for observer. skipping. (I: 59f4a1ee59a3a27d523d60cd761be923)`);
                        }
                    }
                }
                else {
                    // observer is an ibGib witness (not an observer interface)
                    await observerOrWitness.witness(errIbGib);
                }
            }
            return ROOT;
        }
        catch (error) {
            console.error(`${lc} ${error.message}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
    async witness_complete({ ibGib, }) {
        const lc = `${this.lc}[${this.witness_complete.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting... (I: 4db180f465e24df9a4ded37fbd235558)`);
            }
            // set the flag on this observable
            this._isComplete = true;
            // dispatch to subscribers (serially)
            const subscriberTuples = Object.values(this._subscribers);
            for (let i = 0; i < subscriberTuples.length; i++) {
                const [subscription, observerOrWitness] = subscriberTuples[i];
                // depends on if observer or witness...
                if (isObserver({ ibGib: observerOrWitness })) {
                    // observer uses observer interface
                    const observer = observerOrWitness;
                    if (observer.complete) {
                        // we remove subscribers on unsubscribe, but just in case we
                        // will double-check here if we are still subscribed
                        const unsubscribed = await subscription.unsubscribed();
                        if (!unsubscribed) {
                            // dispatch to observer's via its `next` fn
                            await observer.complete();
                        }
                        else {
                            console.warn(`${lc} (UNEXPECTED) subscriber reference still held but is unsubscribed? skipping for now (i.e. won't remove subscriber) in case that code is in yielded execution and just hasn't gotten around to it with event loop (but that would be weird). (W: 1d98f35d2fdd4345954c947265d9fb85)`);
                        }
                    }
                    else {
                        // no error handler for this observer
                        if (logalot) {
                            console.log(`${lc} no error handler for observer. skipping. (I: 6a8a52c2012c464790eb406d2ae0383f)`);
                        }
                    }
                }
                else {
                    // observer is an ibGib witness (not an observer interface)
                    await observerOrWitness.witness(ibGib);
                }
            }
            return ROOT;
        }
        catch (error) {
            console.error(`${lc} ${error.message}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
    /**
     * validates against common witness qualities.
     *
     * Override this with a call to `super.validateThis` for custom validation
     * for descending witness classes.
     *
     * @returns validation errors common to all observable witnesses, if any errors exist.
     */
    async validateThis() {
        const lc = `${this.lc}[${this.validateThis.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting...`);
            }
            if (!this.data) {
            }
            const errors = [
            // ...await super.validateThis(),
            // ...validateCommonSubjectData({ data: this.data }),
            ];
            return errors;
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
    // #endregion witness sub-methods
    // #region helpers
    /**
     * builds an arg ibGib.
     *
     * wrapper convenience to avoid long generic calls.
     */
    async argy({ argData, ibMetadata, noTimestamp, ibGibs, }) {
        if (argData.cmd === ObservableCmd.subscribe) {
            return super.argy({ argData, ibMetadata, noTimestamp, ibGibs });
        }
        else {
            const arg = await argy_({
                argData,
                ibMetadata,
                noTimestamp
            });
            if (ibGibs) {
                arg.ibGibs = ibGibs;
            }
            return arg;
        }
    }
    /**
     * builds a result ibGib, if indeed a result ibgib is required.
     *
     * This is only useful in witnesses that have more structured
     * inputs/outputs. For those that simply accept any ibgib incoming and
     * return a primitive like ib^gib or whatever, then this is unnecessary.
     *
     * wrapper convenience to avoid long generic calls.
     */
    async resulty({ resultData, ibGibs, }) {
        const result = await resulty_({
            // ibMetadata: getSubjectResultMetadata({space: this}),
            resultData,
        });
        if (ibGibs) {
            result.ibGibs = ibGibs;
        }
        return result;
    }
}
/*
 * factory for random Subject witness.
 *
 * @see {@link DynamicFormFactoryBase}
 */
export class Subject_V1_Factory extends DynamicFormFactoryBase {
    lc = `[${Subject_V1_Factory.name}]`;
    getName() { return Subject_V1.name; }
    async newUp({ data, rel8ns, }) {
        const lc = `${this.lc}[${this.newUp.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting...`);
            }
            data ??= clone(DEFAULT_SUBJECT_DATA_V1);
            data = data;
            rel8ns = rel8ns ?? DEFAULT_SUBJECT_REL8NS_V1 ? clone(DEFAULT_SUBJECT_REL8NS_V1) : undefined;
            data.uuid ||= await getUUID();
            let { classname } = data;
            const ib = getSubjectIb({ data });
            // subscription is a constant, so we dont need to create a
            // dependency graph or timeline or anything like that. it's just a
            // stone.
            const subscriptionIbGibDto = await constantIbGib({
                parentPrimitiveIb: `witness ${classname}`,
                ib, data, rel8ns,
            });
            // replace the newIbGib which is just ib,gib,data,rel8ns with loaded
            // witness class (that has the witness function on it)
            const witnessIbGib = new Subject_V1(undefined, undefined);
            await witnessIbGib.loadIbGibDto(subscriptionIbGibDto);
            if (logalot) {
                console.log(`${lc} witnessDto: ${pretty(subscriptionIbGibDto)} (I: 6736d2b2ddf44fef9a1eb27078dfbf23)`);
            }
            return { newIbGib: witnessIbGib };
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg({ error })}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
    async witnessToForm({ witness }) {
        const lc = `${this.lc}[${this.witnessToForm.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting...`);
            }
            let { data } = witness;
            if (!data) {
                throw new Error(`(UNEXPECTED) witness.data falsy? (E: 7b70c96e982d4f81b6ffda54da16b3b8)`);
            }
            if (logalot) {
                console.log(`${lc} data: ${pretty(data)} (I: 75abef56f5004a94b2113e3b1461ac25)`);
            }
            // be careful of order here because of TS type inference
            const idPool = await getIdPool({ n: 100 });
            const form = new SubjectFormBuilder()
                .with({ idPool })
                .name({ of: data.name, required: false, })
                .description({ of: data.description ?? DEFAULT_DESCRIPTION_SUBJECT })
                .and()
                .and()
                .uuid({ of: data.uuid, required: true })
                .classname({ of: data.classname })
                .and()
                .commonWitnessFields({ data })
                .outputForm({
                formName: 'form',
                label: 'observable',
            });
            return Promise.resolve(form);
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg({ error })}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
    async formToWitness({ form }) {
        let data = clone(DEFAULT_SUBJECT_DATA_V1);
        this.patchDataFromItems({ data, items: form.items, pathDelimiter: DEFAULT_DATA_PATH_DELIMITER });
        let resWitnessIbGib = await this.newUp({ data });
        return resWitnessIbGib;
    }
}
//# sourceMappingURL=subject-v1.mjs.map