import { __decorate, __metadata } from "tslib";
import { inject, Inject, Injectable, Injector, Optional } from '@angular/core';
import { LOGGER } from '@anglr/common';
import { BindThis, isEmptyObject, isString } from '@jscrpt/common';
import { RelationsComponentManager } from '../relationsComponentManager/relationsComponentManager.service';
import { RelationsProcessor } from '../relationsProcessor/relationsProcessor.service';
import { RelationsChangeDetectorOptions } from './relationsChangeDetector.options';
import * as i0 from "@angular/core";
import * as i1 from "./relationsChangeDetector.options";
/**
 * Change detector for relations, which allows async change detection
 */
export class RelationsChangeDetector {
    /**
     * Gets instance of relations processor for handling relations
     */
    get relationsProcessor() {
        var _a;
        return ((_a = this.ɵrelationsProcessor) !== null && _a !== void 0 ? _a : (this.ɵrelationsProcessor = this.injector.get(RelationsProcessor)));
    }
    /**
     * Gets instance of relations component manager storing registered components
     */
    get relationsComponentManager() {
        var _a;
        return ((_a = this.ɵrelationsComponentManager) !== null && _a !== void 0 ? _a : (this.ɵrelationsComponentManager = this.injector.get(RelationsComponentManager)));
    }
    /**
     * Objects storing components and theirs outputs and related output input relations
     */
    get outputsComponents() {
        if (isEmptyObject(this.ɵoutputsComponents) && this.parent) {
            return this.parent.outputsComponents;
        }
        return this.ɵoutputsComponents;
    }
    //######################### constructor #########################
    constructor(injector, logger, options) {
        this.injector = injector;
        this.logger = logger;
        //######################### protected properties #########################
        /**
         * Array of mark for check ids that were called before initialization
         */
        this.markedBeforeInit = [];
        /**
         * Objects storing components and theirs outputs and related output input relations
         */
        this.ɵoutputsComponents = {};
        /**
         * Instance of parent relations change detector
         */
        this.parent = inject(RelationsChangeDetector, { optional: true, skipSelf: true, });
        /**
         * Array of changes that should be checked and transfered in first run
         */
        this.firstRunChanges = [];
        /**
         * Array of changes that should be checked and transfered in second run
         */
        this.secondRunChanges = [];
        /**
         * Indication whether is check running
         */
        this.checkRunning = false;
        if (!options || !(options instanceof RelationsChangeDetectorOptions)) {
            options = new RelationsChangeDetectorOptions();
        }
        this.options = options;
    }
    //######################### public methods #########################
    /**
     * Marks input component connected to this for checking
     * @param id - Identification of what should be checked
     */
    markForCheck(id) {
        var _a, _b;
        const componentId = isString(id.componentId) ? id.componentId : this.relationsComponentManager.getId(id.componentId);
        if (!componentId) {
            this.logger.warn('RelationsChangeDetector: Unable to find component!');
            return;
        }
        this.logger.debug('RelationsChangeDetector: marking for check {{@id}}', { id: { componentId, outputName: id.outputName } });
        const relationDefs = (_b = (_a = this.outputsComponents[componentId]) === null || _a === void 0 ? void 0 : _a[id.outputName]) !== null && _b !== void 0 ? _b : [];
        const changes = this.checkRunning && !this.options.detectionInSingleRun ? this.secondRunChanges : this.firstRunChanges;
        //TODO: think of better solution, cache mark when not initialized
        if (!relationDefs.length) {
            this.markedBeforeInit.push(id);
            return;
        }
        for (const relationDef of relationDefs) {
            const relation = changes.find(itm => itm.id == relationDef.inputComponentId);
            //not exists yet
            if (!relation) {
                changes.push({
                    id: relationDef.inputComponentId,
                    inputs: [relationDef.inputName],
                });
                continue;
            }
            //already exists
            if (relation.inputs.indexOf(relationDef.inputName) >= 0) {
                continue;
            }
            relation.inputs.push(relationDef.inputName);
        }
        //schedule check
        if (!this.timeout) {
            this.timeout = setTimeout(this.runCheck, 0);
        }
    }
    /**
     *
     * @param relations - Object storing current relations
     */
    initialize(relations) {
        var _a, _b;
        var _c, _d, _e;
        this.logger.debug('RelationsChangeDetector: initializing {{@relations}}', { relations: relations });
        this.ɵoutputsComponents = {};
        for (const componentId in relations) {
            const relationsDef = relations[componentId];
            (_a = (_c = this.ɵoutputsComponents)[componentId]) !== null && _a !== void 0 ? _a : (_c[componentId] = {});
            if (!relationsDef.inputOutputs) {
                continue;
            }
            for (const inputOutput of relationsDef.inputOutputs) {
                (_b = (_d = this.ɵoutputsComponents[componentId])[_e = inputOutput.outputName]) !== null && _b !== void 0 ? _b : (_d[_e] = []);
                this.ɵoutputsComponents[componentId][inputOutput.outputName].push(inputOutput);
            }
        }
        const ids = [...this.markedBeforeInit];
        this.markedBeforeInit = [];
        for (const markForCheckId of ids) {
            this.markForCheck(markForCheckId);
        }
    }
    //######################### protected methods #########################
    /**
     * Runs check for marked components
     */
    runCheck() {
        this.logger.debug('RelationsChangeDetector: running check');
        this.checkRunning = true;
        for (const change of this.firstRunChanges) {
            const transfer = this.relationsProcessor.transferInputsData(change.id, true, change.inputs);
            transfer.applyChanges();
        }
        if (this.options.detectionInSingleRun) {
            this.firstRunChanges = [];
        }
        else {
            this.firstRunChanges = this.secondRunChanges;
            this.secondRunChanges = [];
        }
        this.checkRunning = false;
        this.timeout = null;
    }
}
RelationsChangeDetector.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.0.4", ngImport: i0, type: RelationsChangeDetector, deps: [{ token: i0.Injector }, { token: LOGGER }, { token: i1.RelationsChangeDetectorOptions, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
RelationsChangeDetector.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.0.4", ngImport: i0, type: RelationsChangeDetector });
__decorate([
    BindThis,
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", void 0)
], RelationsChangeDetector.prototype, "runCheck", null);
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.0.4", ngImport: i0, type: RelationsChangeDetector, decorators: [{
            type: Injectable
        }], ctorParameters: () => [{ type: i0.Injector }, { type: undefined, decorators: [{
                    type: Inject,
                    args: [LOGGER]
                }] }, { type: i1.RelationsChangeDetectorOptions, decorators: [{
                    type: Optional
                }] }], propDecorators: { runCheck: [] } });
//# sourceMappingURL=relationsChangeDetector.service.js.map