import { HttpResponseError } from 'ng2-rest/lib';
import { chalk, config, CoreModels, tnpPackageName, UtilsWaitNotifier, _, Utils, } from 'tnp-core/lib';
import { BaseFeatureForProject, Helpers } from 'tnp-helpers/lib';
import { DEBOUCE_takeLeadOfBuildingDebounce, DEBOUNCE_trigerLeadBuilding, OBSERVER_PARALLELS, } from '../../../../../constants';
import { DevMode } from '../../../../abstract/taon-worker/dev-mode/dev-mode.models';
// @ts-ignore TODO weird inheritance problem
export class BuildLeader extends BaseFeatureForProject {
    taonBuildObserver;
    //#region lead build / fields
    firstBuildDoneAndLeadBuildIsAllowed() {
        // @ts-expect-error
        this._firstBuildDoneAndLeadBuildIsAllowed = true;
    }
    get isAllowedForLeadBuild() {
        return this._firstBuildDoneAndLeadBuildIsAllowed;
    }
    _firstBuildDoneAndLeadBuildIsAllowed = false;
    projectStaredLeadingBuild = false;
    // public toNotifyBuildType: CoreModels.BuildType;
    isDrityLeadBuild = false;
    buildsNotifiers = new Map();
    //#endregion
    //#region constructor
    constructor(project, taonBuildObserver) {
        super(project);
        this.taonBuildObserver = taonBuildObserver;
    }
    //#endregion
    //#region public methods / set as dirty
    setAsDirty() {
        Helpers.warn(`Build dirty. Starting build again.`);
        this.isDrityLeadBuild = true;
        for (const [key, notifer] of this.buildsNotifiers.entries()) {
            notifer.next();
        }
    }
    //#endregion
    //#region public methods / trigger lead building debounced
    trigerLeadBuilding = _.debounce(() => {
        if (this.projectStaredLeadingBuild) {
            this.setAsDirty();
            return;
        }
        this.takeLeadOfBuildingDebounce();
    }, DEBOUNCE_trigerLeadBuilding);
    //#endregion
    //#region public methods / recreat notifiers
    recreateNotifiers() {
        //#region @backend
        for (const buildType of CoreModels.BuildTypeArr) {
            if (!this.buildsNotifiers.has(buildType)) {
                this.buildsNotifiers.set(buildType, UtilsWaitNotifier.getNotifier({
                    // max 3 minutes to finish build part
                    exitTimeoutMs: 200 * 1000,
                }));
            }
        }
        //#endregion
    }
    //#endregion
    //#region public methods / set leader as dirty if running
    setLeadBuildDirtyIfRunning() {
        Helpers.warn(`Trying to set lead build as dirty.`);
        if (this.projectStaredLeadingBuild) {
            this.setAsDirty();
        }
    }
    //#endregion
    //#region private methods / tak lead of building debouced
    takeLeadOfBuildingDebounce = _.debounce(async () => {
        await this.takeLeadOfBuilding();
    }, DEBOUCE_takeLeadOfBuildingDebounce);
    //#endregion
    //#region private methods / take lead of building
    async takeLeadOfBuilding(options) {
        //#region @backend
        options = options || {};
        this.projectStaredLeadingBuild = true;
        while (true) {
            Helpers.log('Taking lead of building');
            this.isDrityLeadBuild = false;
            //#region get current project data fn
            const getCurrentProjectData = () => this.project.ins.devBuildRepository.dataToRequest({
                buildStatusInfo: this.taonBuildObserver.buildStatusInfo,
            });
            //#endregion
            //#region set as leader and get all project for building
            const devModeControllerWorker = await this.project.ins.taonProjectsWorker.devModeWorker.getRemoteControllerFor({
                methodOptions: {
                    calledFrom: 'taon build observer',
                },
            });
            try {
                const dirtyBuild = Array.from(this.taonBuildObserver.dirtyBuild.entries())
                    .filter(([key, value]) => value)
                    .reduce((a, [key, value]) => {
                    return { ...a, [key]: value };
                }, {});
                var allDepProjectData = await devModeControllerWorker
                    .setAsLeadProjectAndReturnDependcies(getCurrentProjectData(), dirtyBuild)
                    .request();
            }
            catch (error) {
                if (error instanceof HttpResponseError) {
                    const err = error;
                    Helpers.error(err.body.json.message, true, true);
                }
                else {
                    Helpers.warn(`Not able to communicate with watch mode main worker`);
                }
                this.projectStaredLeadingBuild = false;
                this.isDrityLeadBuild = false;
                return;
            }
            let allDepProject = (allDepProjectData.body.json || []).map(c => DevMode.ProjectBuildNotificaiton.from(c));
            if (options.skipProjectsNames) {
                allDepProject = allDepProject.filter(f => !options.skipProjectsNames.includes(f.nameForNpmPackage));
            }
            Helpers.info(`
        Rebuilding in order...

${allDepProject.map((c, i) => `${i + 1}. ${c.nameForNpmPackage} (port=${c.port})`).join('\n')}

        `);
            //#endregion
            //#region leader check fn
            const isLeaderCheck = async () => {
                try {
                    let isStillLeader = await devModeControllerWorker
                        .checkIfStillBuildLeader(getCurrentProjectData())
                        .request();
                    return isStillLeader.body.booleanValue;
                }
                catch (error) {
                    config.frameworkName === tnpPackageName && console.log(error);
                    return false;
                }
            };
            //#endregion
            //#region check worker healty fn
            const isProjectHealty = async (devBuildControllerForProj) => {
                let maxTrys = 3;
                do {
                    try {
                        await devBuildControllerForProj.healthCheck().request({
                            timeout: 500,
                        });
                        return true;
                    }
                    catch (error) {
                        config.frameworkName === tnpPackageName && console.log(error);
                        await Utils.waitMilliseconds(100);
                    }
                    break;
                } while (--maxTrys > 0);
                return false;
            };
            //#endregion
            //#region cancel mesage fn
            const cancelMessage = () => {
                Helpers.logInfo(chalk.green('Lead build cancel - project is no longer leading build'));
            };
            //#endregion
            //#region resolve what should be rebuild (backend or browser or whatever)
            let shouldBeRebuildArr = CoreModels.BuildTypeArr;
            try {
                var shouoldBeRebuildData = await devModeControllerWorker
                    .getWhatShouldBeRebuild(getCurrentProjectData())
                    .request();
                shouldBeRebuildArr = Object.keys(shouoldBeRebuildData.body.json);
            }
            catch (error) {
                config.frameworkName === tnpPackageName && console.error(error);
                Helpers.warn(`Not able to access worker to get partial rebuild info`);
            }
            Helpers.info(`Should be rebuild: ${shouldBeRebuildArr.length > 0 ? shouldBeRebuildArr.join(',') : `< nothing >`}`);
            //#endregion
            //#region build project by project in order
            for (let projectIndex = 0; projectIndex < allDepProject.length; projectIndex++) {
                //#region prepare child project controller
                const proj = allDepProject[projectIndex];
                const devBuildControllerForProj = await this.project.ins.taonProjectsWorker.getDevBuildControllerForPort(proj.port);
                //#endregion
                //#region skip build project worker not healty
                if (!(await isProjectHealty(devBuildControllerForProj))) {
                    break;
                }
                //#endregion
                //#region skip/restart build if is dirty
                if (this.isDrityLeadBuild) {
                    Helpers.logInfo('Restarting lead build - current project code changes.');
                    break;
                }
                //#endregion
                if (OBSERVER_PARALLELS) {
                    //#region execute parallels builds
                    Helpers.taskStarted(`Building ${proj.nameForNpmPackage}(port=${proj.port}) ${shouldBeRebuildArr.join(',')},`);
                    //#region process build and wait fn
                    const processBuildAndWaitFn = async (rebuildArr) => {
                        const promises = [];
                        for (const buildType of rebuildArr) {
                            promises.push(this.buildsNotifiers.get(buildType).wait());
                        }
                        for (const buildType of rebuildArr) {
                            await devBuildControllerForProj
                                .updateTaonBuildStatus(buildType, DevMode.ProjectBuildStatus.__START_BUILD__, this.project.ins.currentActionPort)
                                .request();
                        }
                        try {
                            await Promise.all(promises);
                            return true;
                        }
                        catch (error) {
                            return false;
                        }
                    };
                    //#endregion
                    //#region start all build and wait for all to finish (backend)
                    if (shouldBeRebuildArr.includes('backend-cjs') ||
                        shouldBeRebuildArr.includes('backend-esm') ||
                        shouldBeRebuildArr.includes('backend-js-maps')) {
                        const buildOK = await processBuildAndWaitFn(shouldBeRebuildArr.filter(f => [
                            'backend-cjs',
                            'backend-esm',
                            'backend-js-maps',
                        ].includes(f)));
                        if (!buildOK) {
                            this.setAsDirty();
                        }
                    }
                    //#endregion
                    //#region skip/restart build if is dirty
                    if (this.isDrityLeadBuild) {
                        Helpers.logInfo('Restarting lead build - current project code changes.');
                        break;
                    }
                    //#endregion
                    //#region check if build is still leader
                    if (!(await isLeaderCheck())) {
                        this.projectStaredLeadingBuild = false;
                        cancelMessage();
                        return;
                    }
                    //#endregion
                    //#region start all build and wait for all to finish (frontend)
                    if (shouldBeRebuildArr.includes('browser') ||
                        shouldBeRebuildArr.includes('websql')) {
                        const buildOK = await processBuildAndWaitFn(shouldBeRebuildArr.filter(f => ['browser', 'websql'].includes(f)));
                        if (!buildOK) {
                            this.setAsDirty();
                        }
                    }
                    //#endregion
                    Helpers.taskDone(`Done Building ${proj.nameForNpmPackage}(port=${proj.port})`);
                    //#endregion
                }
                else {
                    //#region execute builds
                    for (const buildType of shouldBeRebuildArr) {
                        Helpers.taskStarted(`Building build type "${buildType}" for ${proj.port} ${proj.nameForNpmPackage}`);
                        //#region skip/restart build if is dirty
                        if (this.isDrityLeadBuild) {
                            Helpers.logInfo('Restarting lead build - current project code changes.');
                            break;
                        }
                        //#endregion
                        //#region check if build is still leader
                        if (!(await isLeaderCheck())) {
                            this.projectStaredLeadingBuild = false;
                            cancelMessage();
                            return;
                        }
                        //#endregion
                        //#region skip/restart build if is dirty
                        if (this.isDrityLeadBuild) {
                            Helpers.logInfo('Restarting lead build - current project code changes.');
                            break;
                        }
                        //#endregion
                        //#region trigger single build
                        await devBuildControllerForProj
                            .updateTaonBuildStatus(buildType, DevMode.ProjectBuildStatus.__START_BUILD__, this.project.ins.currentActionPort)
                            .request();
                        //#endregion
                        //#region check if build is still leader
                        if (!(await isLeaderCheck())) {
                            this.projectStaredLeadingBuild = false;
                            cancelMessage();
                            return;
                        }
                        //#endregion
                        //#region wait for single build to finish
                        Helpers.logInfo(`Waiting for build "${buildType}" done  in  ${proj.nameForNpmPackage} (port=${proj.port}) `);
                        await this.buildsNotifiers.get(buildType).wait();
                        //#endregion
                        //#region check if build is still leader
                        if (!(await isLeaderCheck())) {
                            this.projectStaredLeadingBuild = false;
                            cancelMessage();
                            return;
                        }
                        //#endregion
                        //#region skip/restart build if is dirty
                        if (this.isDrityLeadBuild) {
                            Helpers.logInfo('Restarting lead build - current project code changes.');
                            break;
                        }
                        //#endregion
                        Helpers.taskDone(`Build "${buildType}" done  in  ${proj.nameForNpmPackage}`);
                    }
                    //#endregion
                }
                //#region display done message for project when build done
                if (!getCurrentProjectData().isEqual(proj)) {
                    try {
                        await devBuildControllerForProj
                            .displayRebuildDoneMessage(this.project.nameForNpmPackage)
                            .request({
                            timeout: 500,
                        });
                    }
                    catch (error) {
                        config.frameworkName === tnpPackageName && console.log(error);
                    }
                }
                //#endregion
                //#region skip/restart build if is dirty
                if (this.isDrityLeadBuild) {
                    Helpers.logInfo('Restarting lead build - current project code changes.');
                    break;
                }
                //#endregion
            } // end projects loop
            //#endregion
            //#region restart when dirty build
            if (this.isDrityLeadBuild) {
                continue;
            }
            //#endregion
            //#region finish lead build and flush projects
            // UtilsTerminal.drawHorizontalLine();
            console.log(chalk.green(`

    BUILD (CURRENT LIB + DEPENDECIES) ${chalk.bold('DONE')}.

        `));
            // UtilsTerminal.drawHorizontalLine();
            try {
                await devModeControllerWorker
                    .finishLeadBuildAndUnregisterLeadProject(getCurrentProjectData())
                    .request({
                    timeout: 500,
                });
            }
            catch (error) {
                config.frameworkName === tnpPackageName && console.log(error);
                Helpers.logWarn(`Not able to unregister lead build project`, false);
            }
            //#endregion
            break;
        }
        this.projectStaredLeadingBuild = false;
        //#endregion
    }
}
//# sourceMappingURL=build-leader-process.js.map