import { inject, Injectable, Injector } from '@angular/core';
import { LOGGER } from '@anglr/common';
import { getDebugData, RelationsComponentManager, RelationsDebugger } from '@anglr/dynamic/relations';
import { extend, generateId, nameof } from '@jscrpt/common';
import { Subject } from 'rxjs';
import * as i0 from "@angular/core";
const COMPONENT_DEBUGGER_PROPERTY = 'COMPONENT_DEBUGGER_PROPERTY';
//TODO: maybe reset steps on change of init
/**
 * Service used for debugging relations
 */
export class RelationsDebuggerImpl extends RelationsDebugger {
    constructor() {
        super(...arguments);
        //######################### protected properties #########################
        /**
         * Instance of Angular injector
         */
        this.injector = inject(Injector);
        /**
         * Instance of logger used for logging
         */
        this.logger = inject(LOGGER, { optional: true });
        /**
         * Used for emitting changes in current step index
         */
        this.stepChangeSubject = new Subject();
        /**
         * Current step index for previewing of data
         */
        this.currentStepIndex = 0;
        /**
         * Registered components by id and their internal ids for each step
         */
        this.components = [];
        /**
         * Array of steps recorded by debugger
         */
        this.steps = [];
        /**
         * Definition of component endpoints
         */
        this.componentDefs = {};
    }
    /**
     * Gets instance of relations component manager
     */
    get relationsComponentManager() {
        var _a;
        return ((_a = this.ɵrelationsComponentManager) !== null && _a !== void 0 ? _a : (this.ɵrelationsComponentManager = this.injector.get(RelationsComponentManager)));
    }
    //######################### public properties #########################
    /**
     * Occurs when current step index changes
     */
    get stepChange() {
        return this.stepChangeSubject.asObservable();
    }
    //######################### public methods #########################
    /**
     * @inheritdoc
     */
    initialize(relations, backwardRelations) {
        var _a;
        const addInputOutput = (inputOutput) => {
            this.addOutputDef(inputOutput.outputComponentId, inputOutput.outputName);
            this.addInputDef(inputOutput.inputComponentId, inputOutput.inputName);
        };
        for (const id in relations) {
            const def = relations[id];
            for (const inputOutput of (_a = def.inputOutputs) !== null && _a !== void 0 ? _a : []) {
                addInputOutput(inputOutput);
            }
        }
        for (const id in backwardRelations) {
            const def = backwardRelations[id];
            for (const inputOutput of def) {
                addInputOutput(inputOutput);
            }
        }
    }
    /**
     * @inheritdoc
     */
    registerComponent(id, component) {
        var _a, _b, _c, _d, _e;
        this.tryToGetEndpoints(id, component);
        let components = this.getLastComponents();
        if (components[id] && components[id].indexOf((_a = component.ɵɵRelationsComponentId) !== null && _a !== void 0 ? _a : '') >= 0) {
            (_b = this.logger) === null || _b === void 0 ? void 0 : _b.warn('RelationsDebugger: component "{{@id}}" has already been registered', { id: `${id}--${component.ɵɵRelationsComponentId}` });
            return;
        }
        this.components.push(extend(true, {}, components));
        components = this.getLastComponents();
        (_c = components[id]) !== null && _c !== void 0 ? _c : (components[id] = []);
        components[id].push((_d = component.ɵɵRelationsComponentId) !== null && _d !== void 0 ? _d : '');
        //update component for debugging info
        if (!Reflect.getOwnPropertyDescriptor(component, COMPONENT_DEBUGGER_PROPERTY)) {
            Reflect.defineProperty(component, COMPONENT_DEBUGGER_PROPERTY, {
                configurable: true,
                enumerable: false,
                writable: false,
                value: true,
            });
            let obj = component;
            let property;
            do {
                if ((property = Reflect.getOwnPropertyDescriptor(obj, nameof('relationsOptions')))) {
                    break;
                }
            } while ((obj = Reflect.getPrototypeOf(obj)));
            // eslint-disable-next-line @typescript-eslint/no-this-alias
            const $this = this;
            //property exists
            if (property) {
                const getter = property.get;
                const setter = property.set;
                if (!getter || !setter) {
                    throw new Error('RelationsDebugger: relations options property must have getter and setter!');
                }
                Reflect.defineProperty(component, nameof('relationsOptions'), {
                    configurable: true,
                    enumerable: false,
                    get: function () {
                        return getter.call(this);
                    },
                    set: function (value) {
                        setter.call(this, value);
                        $this.tryToGetEndpoints(id, component);
                        $this.readComponentState(id);
                        $this.setComponentRelationsOptions(id, value);
                    }
                });
            }
            else {
                Reflect.defineProperty(component, nameof('relationsOptions'), {
                    configurable: true,
                    enumerable: false,
                    get: function () {
                        return this['ɵrelationsOptions'];
                    },
                    set: function (value) {
                        this['ɵrelationsOptions'] = value;
                        $this.tryToGetEndpoints(id, component);
                        $this.readComponentState(id);
                        $this.setComponentRelationsOptions(id, value);
                    }
                });
            }
        }
        this.steps.push({
            timestamp: Date.now(),
            componentRegistration: {
                componentId: id,
                componentInternalId: (_e = component.ɵɵRelationsComponentId) !== null && _e !== void 0 ? _e : '',
            },
            componentState: null,
            componentUnregistration: null,
            dataTransfer: null,
            componentRelationsOptions: null,
            previousStep: this.getLastStepData(),
        });
    }
    /**
     * @inheritdoc
     */
    unregisterComponent(id, component) {
        var _a, _b, _c;
        let index;
        let components = this.getLastComponents();
        if (!components[id] || (index = components[id].indexOf((_a = component.ɵɵRelationsComponentId) !== null && _a !== void 0 ? _a : '')) < 0) {
            (_b = this.logger) === null || _b === void 0 ? void 0 : _b.warn('RelationsDebugger: component "{{@id}}" does not exists!', { id: `${id}--${component.ɵɵRelationsComponentId}` });
            return;
        }
        this.components.push(extend(true, {}, components));
        components = this.getLastComponents();
        components[id].splice(index, 1);
        if (!components[id].length) {
            delete components[id];
        }
        //update component for debugging info
        if (Reflect.getOwnPropertyDescriptor(component, COMPONENT_DEBUGGER_PROPERTY)) {
            Reflect.deleteProperty(component, COMPONENT_DEBUGGER_PROPERTY);
            Reflect.deleteProperty(component, nameof('relationsOptions'));
        }
        this.steps.push({
            timestamp: Date.now(),
            componentRegistration: null,
            componentState: null,
            componentUnregistration: {
                componentId: id,
                componentInternalId: (_c = component.ɵɵRelationsComponentId) !== null && _c !== void 0 ? _c : '',
            },
            dataTransfer: null,
            componentRelationsOptions: null,
            previousStep: this.getLastStepData(),
        });
    }
    /**
     * @inheritdoc
     */
    transferData(transfer, options) {
        var _a;
        (_a = transfer.ɵuniqueId) !== null && _a !== void 0 ? _a : (transfer.ɵuniqueId = generateId(16));
        const applyChangesOriginal = transfer.applyChanges;
        transfer.applyChanges = (() => {
            var _a, _b;
            applyChangesOriginal.call(transfer);
            const components = this.getLastComponents();
            this.components.push(extend(true, {}, components));
            this.steps.push({
                timestamp: Date.now(),
                componentRegistration: null,
                componentState: null,
                componentUnregistration: null,
                dataTransfer: {
                    change: transfer.changes[options.inputName],
                    inputComponentId: options.inputComponentId,
                    inputName: options.inputName,
                    outputComponentId: options.outputComponentId,
                    outputName: options.outputName,
                    scope: options.scope,
                    ɵuniqueId: (_a = transfer.ɵuniqueId) !== null && _a !== void 0 ? _a : '',
                },
                componentRelationsOptions: null,
                previousStep: this.getLastStepData(),
            });
            this.readComponentState(options.inputComponentId);
            this.readComponentState(options.outputComponentId);
            const step = this.getLastStepData();
            if (!step) {
                throw Error('RelationsDebugger: no step available!');
            }
            if (step.componentState) {
                for (const comp of step.componentState[options.inputComponentId]) {
                    comp.inputs[options.inputName] = (_b = step.dataTransfer) === null || _b === void 0 ? void 0 : _b.change.currentValue;
                }
            }
        }).bind(transfer);
    }
    /**
     * @inheritdoc
     */
    getCurrentComponents() {
        if (!this.components.length) {
            return {};
        }
        return this.components[this.currentStepIndex];
    }
    /**
     * Gets component definition
     * @param id - Id of component which definition will be obtained
     */
    getComponentDef(id) {
        var _a;
        return (_a = this.componentDefs[id]) !== null && _a !== void 0 ? _a : null;
    }
    /**
     * @inheritdoc
     */
    getComponentState(id) {
        var _a;
        let step = this.getCurrentStep();
        if (!step) {
            return [];
        }
        do {
            if ((_a = step.componentState) === null || _a === void 0 ? void 0 : _a[id]) {
                return step.componentState[id];
            }
        } while ((step = step.previousStep));
        return [];
    }
    /**
     * @inheritdoc
     */
    getComponentRelationsOptions(id) {
        var _a;
        let step = this.getCurrentStep();
        if (!step) {
            return undefined;
        }
        do {
            if ((_a = step.componentRelationsOptions) === null || _a === void 0 ? void 0 : _a[id]) {
                return step.componentRelationsOptions[id];
            }
        } while ((step = step.previousStep));
        return undefined;
    }
    /**
     * @inheritdoc
     */
    getLastStep() {
        if (!this.steps.length || (this.steps.length - 1) == this.currentStepIndex) {
            return null;
        }
        this.currentStepIndex = this.steps.length - 1;
        this.stepChangeSubject.next();
        return this.steps[this.currentStepIndex];
    }
    /**
     * @inheritdoc
     */
    getFirstStep() {
        if (!this.steps.length || this.currentStepIndex == 0) {
            return null;
        }
        this.currentStepIndex = 0;
        this.stepChangeSubject.next();
        return this.steps[this.currentStepIndex];
    }
    /**
     * @inheritdoc
     */
    getNextStep() {
        if (!this.steps.length || (this.steps.length - 1) == this.currentStepIndex) {
            return null;
        }
        this.currentStepIndex++;
        this.stepChangeSubject.next();
        return this.steps[this.currentStepIndex];
    }
    /**
     * @inheritdoc
     */
    getPreviousStep() {
        if (!this.steps.length || this.currentStepIndex == 0) {
            return null;
        }
        this.currentStepIndex--;
        this.stepChangeSubject.next();
        return this.steps[this.currentStepIndex];
    }
    /**
     * @inheritdoc
     */
    getCurrentStep() {
        if (!this.steps.length) {
            return null;
        }
        return this.steps[this.currentStepIndex];
    }
    /**
     * @inheritdoc
     */
    clearSteps() {
        this.currentStepIndex = 0;
        this.stepChangeSubject.next();
        this.steps = [];
        this.components = [];
    }
    //######################### protected methods #########################
    /**
     * Reads component state
     * @param id - Id of component which state is read
     */
    readComponentState(id) {
        var _a, _b;
        let components = this.relationsComponentManager.get(id);
        if (!components) {
            (_a = this.logger) === null || _a === void 0 ? void 0 : _a.warn('RelationsDebugger: unable to find component with id {{@id}}', { id });
            return;
        }
        if (!Array.isArray(components)) {
            components = [components];
        }
        const state = components.map(itm => {
            var _a, _b, _c, _d;
            const def = this.componentDefs[id];
            const itmDict = itm;
            const inputs = {};
            const outputs = {};
            if (def) {
                for (const input of def.inputs) {
                    try {
                        inputs[input] = itmDict[input];
                    }
                    catch (e) {
                        (_a = this.logger) === null || _a === void 0 ? void 0 : _a.warn(`RelationsDebugger: failed reading input '${input}', {{@error}}`, { error: e });
                        inputs[input] = undefined;
                    }
                }
                for (const output of def.outputs) {
                    try {
                        (_b = outputs[output]) !== null && _b !== void 0 ? _b : (outputs[output] = {
                            assigned: undefined,
                            skipInit: undefined,
                            value: undefined,
                        });
                        outputs[output].value = itmDict[output];
                        outputs[output].assigned = itmDict[`${output}Assigned`];
                        outputs[output].skipInit = itmDict[`${output}SkipInit`];
                    }
                    catch (e) {
                        (_c = this.logger) === null || _c === void 0 ? void 0 : _c.warn(`RelationsDebugger: failed reading output '${output}', {{@error}}`, { error: e });
                        outputs[output] =
                            {
                                assigned: undefined,
                                skipInit: undefined,
                                value: undefined,
                            };
                    }
                }
            }
            return {
                componentId: id,
                componentInternalId: (_d = itm.ɵɵRelationsComponentId) !== null && _d !== void 0 ? _d : '',
                inputs,
                outputs,
            };
        });
        const step = this.getLastStepData();
        if (!step) {
            throw Error('RelationsDebugger: no step available!');
        }
        (_b = step.componentState) !== null && _b !== void 0 ? _b : (step.componentState = {});
        step.componentState[id] = state;
    }
    /**
     * Sets component relations options
     * @param id - Id of component which state is read
     * @param relationsOptions - Instance of relations options to be set
     */
    setComponentRelationsOptions(id, relationsOptions) {
        var _a;
        const step = this.getLastStepData();
        if (!step) {
            throw Error('RelationsDebugger: no step available!');
        }
        (_a = step.componentRelationsOptions) !== null && _a !== void 0 ? _a : (step.componentRelationsOptions = {});
        step.componentRelationsOptions[id] = relationsOptions;
    }
    /**
     * Gets last step
     */
    getLastStepData() {
        return this.steps.length > 0 ? this.steps[this.steps.length - 1] : null;
    }
    /**
     * Gets last components
     */
    getLastComponents() {
        if (!this.components.length) {
            return {};
        }
        return this.components[this.components.length - 1];
    }
    /**
     * Tries to get endpoints for component
     * @param id - Id of component whose endpoints are going to be obtained
     * @param component - Component which endpoints are going to be obtained
     */
    tryToGetEndpoints(id, component) {
        var _a;
        const debugData = getDebugData(Reflect.getPrototypeOf(component).constructor);
        if (!debugData) {
            return;
        }
        if (debugData.inputs) {
            for (const input of debugData.inputs) {
                this.addInputDef(id, input);
            }
        }
        if (debugData.outputs) {
            for (const output of debugData.outputs) {
                this.addOutputDef(id, output);
            }
        }
        if (debugData.dynamicEndpointsGetter) {
            const inputsOutputsDef = debugData.dynamicEndpointsGetter(component, (_a = component.ɵɵinjector) !== null && _a !== void 0 ? _a : this.injector);
            for (const input of inputsOutputsDef.inputs) {
                this.addInputDef(id, input);
            }
            for (const output of inputsOutputsDef.outputs) {
                this.addOutputDef(id, output);
            }
        }
    }
    /**
     * Adds input for component def
     * @param id - Id of component whose input definition will be added
     * @param input - Input name to be added
     */
    addInputDef(id, input) {
        var _a;
        var _b;
        (_a = (_b = this.componentDefs)[id]) !== null && _a !== void 0 ? _a : (_b[id] = {
            inputs: [],
            outputs: [],
        });
        if (this.componentDefs[id].inputs.indexOf(input) < 0) {
            this.componentDefs[id].inputs.push(input);
        }
    }
    /**
     * Adds output for component def
     * @param id - Id of component whose output definition will be added
     * @param output - Output name to be added
     */
    addOutputDef(id, output) {
        var _a;
        var _b;
        (_a = (_b = this.componentDefs)[id]) !== null && _a !== void 0 ? _a : (_b[id] = {
            inputs: [],
            outputs: [],
        });
        if (this.componentDefs[id].outputs.indexOf(output) < 0) {
            this.componentDefs[id].outputs.push(output);
        }
    }
}
RelationsDebuggerImpl.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.4", ngImport: i0, type: RelationsDebuggerImpl, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
RelationsDebuggerImpl.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.0.4", ngImport: i0, type: RelationsDebuggerImpl });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.4", ngImport: i0, type: RelationsDebuggerImpl, decorators: [{
            type: Injectable
        }] });
//# sourceMappingURL=relationsDebugger.service.js.map