"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BuildLeader = void 0;
const lib_1 = require("ng2-rest/lib");
const lib_2 = require("tnp-core/lib");
const lib_3 = require("tnp-helpers/lib");
const app_utils_1 = require("../../../../../app-utils");
const constants_1 = require("../../../../../constants");
const dev_mode_models_1 = require("../../../../abstract/taon-worker/dev-mode/dev-mode.models");
// @ts-ignore TODO weird inheritance problem
class BuildLeader extends lib_3.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() {
        lib_3.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 = lib_2._.debounce(() => {
        if (this.projectStaredLeadingBuild) {
            this.setAsDirty();
            return;
        }
        this.takeLeadOfBuildingDebounce();
    }, constants_1.DEBOUNCE_trigerLeadBuilding);
    //#endregion
    //#region public methods / recreat notifiers
    recreateNotifiers() {
        //#region @backend
        for (const buildType of lib_2.CoreModels.BuildTypeArr) {
            if (!this.buildsNotifiers.has(buildType)) {
                this.buildsNotifiers.set(buildType, lib_2.UtilsWaitNotifier.getNotifier({
                    // max 3 minutes to finish build part
                    exitTimeoutMs: 200 * 1000,
                }));
            }
        }
        //#endregion
    }
    //#endregion
    //#region public methods / set leader as dirty if running
    setLeadBuildDirtyIfRunning() {
        lib_3.Helpers.warn(`Trying to set lead build as dirty.`);
        if (this.projectStaredLeadingBuild) {
            this.setAsDirty();
        }
    }
    //#endregion
    //#region private methods / tak lead of building debouced
    takeLeadOfBuildingDebounce = lib_2._.debounce(async () => {
        await this.takeLeadOfBuilding();
    }, constants_1.DEBOUCE_takeLeadOfBuildingDebounce);
    //#endregion
    //#region private methods / take lead of building
    async takeLeadOfBuilding(options) {
        //#region @backend
        options = options || {};
        this.projectStaredLeadingBuild = true;
        while (true) {
            lib_3.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 lib_1.HttpResponseError) {
                    const err = error;
                    lib_2.config.frameworkName === lib_2.tnpPackageName && console.error(error);
                    lib_3.Helpers.error(err.body.json.message || err.body.text, true, true);
                }
                this.projectStaredLeadingBuild = false;
                this.isDrityLeadBuild = false;
                return;
            }
            let allDepProject = (allDepProjectData.body.json || []).map(c => dev_mode_models_1.DevMode.ProjectBuildNotificaiton.from(c));
            if (options.skipProjectsNames) {
                allDepProject = allDepProject.filter(f => !options.skipProjectsNames.includes(f.nameForNpmPackage));
            }
            lib_3.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) {
                    lib_2.config.frameworkName === lib_2.tnpPackageName &&
                        console.error(`is leader check` + error);
                    (0, app_utils_1.errorMainWorkerCommunication)();
                    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) {
                        lib_2.config.frameworkName === lib_2.tnpPackageName &&
                            console.error(`is project healty`, error);
                        await lib_2.Utils.waitMilliseconds(100);
                    }
                    break;
                } while (--maxTrys > 0);
                lib_3.Helpers.warn(`Project is down.`);
                (0, app_utils_1.errorMainWorkerCommunication)();
                return false;
            };
            //#endregion
            //#region cancel mesage fn
            const cancelMessage = () => {
                lib_3.Helpers.logInfo(lib_2.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 = lib_2.CoreModels.BuildTypeArr;
            try {
                var shouoldBeRebuildData = await devModeControllerWorker
                    .getWhatShouldBeRebuild(getCurrentProjectData())
                    .request();
                shouldBeRebuildArr = Object.keys(shouoldBeRebuildData.body.json);
            }
            catch (error) {
                lib_2.config.frameworkName === lib_2.tnpPackageName &&
                    console.error('resove what to rebuild', error);
                lib_3.Helpers.warn(`Not able to access worker to get partial rebuild info`);
                (0, app_utils_1.errorMainWorkerCommunication)();
            }
            lib_3.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) {
                    lib_3.Helpers.logInfo('Restarting lead build - current project code changes.');
                    break;
                }
                //#endregion
                if (constants_1.OBSERVER_PARALLELS) {
                    //#region execute parallels builds
                    lib_3.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, dev_mode_models_1.DevMode.ProjectBuildStatus.__START_BUILD__, this.project.ins.currentActionPort)
                                .request();
                        }
                        try {
                            await Promise.all(promises);
                            return true;
                        }
                        catch (error) {
                            lib_2.config.frameworkName === lib_2.tnpPackageName &&
                                console.error(`update trigger build status`, error);
                            lib_3.Helpers.warn(`Update/wait failed.`);
                            (0, app_utils_1.errorMainWorkerCommunication)();
                            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) {
                        lib_3.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
                    lib_3.Helpers.taskDone(`Done Building ${proj.nameForNpmPackage}(port=${proj.port})`);
                    //#endregion
                }
                else {
                    //#region execute builds
                    for (const buildType of shouldBeRebuildArr) {
                        lib_3.Helpers.taskStarted(`Building build type "${buildType}" for ${proj.port} ${proj.nameForNpmPackage}`);
                        //#region skip/restart build if is dirty
                        if (this.isDrityLeadBuild) {
                            lib_3.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) {
                            lib_3.Helpers.logInfo('Restarting lead build - current project code changes.');
                            break;
                        }
                        //#endregion
                        //#region trigger single build
                        await devBuildControllerForProj
                            .updateTaonBuildStatus(buildType, dev_mode_models_1.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
                        lib_3.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) {
                            lib_3.Helpers.logInfo('Restarting lead build - current project code changes.');
                            break;
                        }
                        //#endregion
                        lib_3.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) {
                        lib_2.config.frameworkName === lib_2.tnpPackageName &&
                            console.error(`display rebuild done message`, error);
                        lib_3.Helpers.warn(`Can't display rebuild message in child build.`);
                        (0, app_utils_1.errorMainWorkerCommunication)();
                    }
                }
                //#endregion
                //#region skip/restart build if is dirty
                if (this.isDrityLeadBuild) {
                    lib_3.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(lib_2.chalk.green(`

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

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