import { TaonBaseKvRepository, TaonRepository, Taon__NS__error } from 'taon/lib-prod';
import { config, crossPlatformPath, taonPackageName, tnpPackageName, ___NS__debounce, CoreModels__NS__BuildWatcherTypeArr, Helpers__NS__info, Helpers__NS__taskDone, Helpers__NS__taskStarted, Helpers__NS__writeJson, Utils__NS__uniqArray, UtilsOs__NS__getRealHomeDir, UtilsProjects__NS__sortGroupOfProject } from 'tnp-core/lib-prod';
import { debugModeTaonLightWeightWatchMode } from '../../../../constants';
import { DevBuildController } from '../dev-build/dev-build.controller';
import { DevBuildUtils__NS__context } from '../dev-build/dev-build.utils';
import { DevMode__NS__ProjectBuildNotificaiton } from './dev-mode.models';
import { DevModeUtils__NS__dependenciesTreeForBuild } from './dev-mode.utils';
import { DevBuildModels__NS__START_PORT_BUID_PROCESS } from '../dev-build/dev-build.models';
//#endregion
const WRITE_LOG_TO_FILE = debugModeTaonLightWeightWatchMode;
let DevModeRepository = class DevModeRepository extends TaonBaseKvRepository {
    //#region fields
    logMessages = [];
    contexts = new Map();
    //#endregion
    //#region API
    //#region API / public methods / uniregister as build leader
    async finishLeadBuildAndUnregisterLeadProject(body) {
        //#region @backendFunc
        const currentLead = DevMode__NS__ProjectBuildNotificaiton.from(await this.get('currentLeadProject'));
        if (currentLead.isEqual(body)) {
            await this.set('currentLeadProject', null);
            //#region remove required builds when normal lead build
            const frameworkVersion = currentLead.coreContainerVersion;
            await this.merge('requiredToBeInLeadBuild', {
                [frameworkVersion]: [],
            });
            await this.merge('shouldBeRebuild', {
                [frameworkVersion]: CoreModels__NS__BuildWatcherTypeArr.reduce((a, b) => {
                    return { ...a, [b]: false };
                }, {}),
            });
            //#endregion
            return true;
        }
        return false;
        //#endregion
    }
    //#endregion
    //#region API / private methods / get deps tree for
    async getDepsTreeFor(projects, onlyAffectedByProject, frameworkVersion) {
        //#region @backendFunc
        const requiredToBeInLeadBuildData = (await this.get('requiredToBeInLeadBuild')) || {};
        const requiredToBeInLeadBuildArr = (requiredToBeInLeadBuildData[frameworkVersion] ||
            []).map(c => c.nameForNpmPackage);
        projects = DevModeUtils__NS__dependenciesTreeForBuild(projects, [
            onlyAffectedByProject,
            ...requiredToBeInLeadBuildArr,
        ]);
        return projects;
        //#endregion
    }
    //#endregion
    //#region API / private methods / get what should be rebuild
    async getWhatShouldBeRebuild(body) {
        //#region @backendFunc
        const frameworkVersion = body.coreContainerVersion;
        const data = (await this.get('shouldBeRebuild')) || {};
        const result = {};
        data[frameworkVersion] = data[frameworkVersion] ?? {};
        if (data[frameworkVersion]['backend-watcher']) {
            result['backend-cjs'] = true;
            result['backend-esm'] = true;
            result['backend-js-maps'] = true;
        }
        if (data[frameworkVersion]['browser-watcher']) {
            result['browser'] = true;
        }
        if (data[frameworkVersion]['websql-watcher']) {
            result['websql'] = true;
        }
        return result;
        //#endregion
    }
    //#endregion
    //#region API / public methods / set as leader project and return dependencies
    async setAsLeadProjectAndReturnDependcies(projectRequestestingLeadPos, dirtyBuild) {
        //#region @backendFunc
        const frameworkVersion = projectRequestestingLeadPos.coreContainerVersion;
        await this.merge('shouldBeRebuild', {
            [frameworkVersion]: dirtyBuild,
        });
        const data = (await this.get('poolOfDevModeProjects')) || {
            [frameworkVersion]: [],
        };
        // console.log({ data });
        let projects = (data[frameworkVersion] || []).map(c => DevMode__NS__ProjectBuildNotificaiton.from(c));
        //#region start build if some lead build already builds this project
        let currentLeadProject = DevMode__NS__ProjectBuildNotificaiton.from(await this.get('currentLeadProject'));
        if (currentLeadProject &&
            currentLeadProject.uniqueKey !== projectRequestestingLeadPos.uniqueKey) {
            const currentLeadDepsTree = await this.getDepsTreeFor(projects, currentLeadProject.nameForNpmPackage, currentLeadProject.coreContainerVersion);
            if (currentLeadDepsTree.some(f => f.uniqueKey === projectRequestestingLeadPos.uniqueKey)) {
                try {
                    const leadController = await this.getDevBuildControllerForPort(currentLeadProject.port);
                    await leadController.setLeadBuildDirtyIfRunning().request({
                        timeout: 500,
                    });
                }
                catch (error) {
                    this.addMessage(`Not able to set lead build as dirty`);
                    throw error;
                }
                await Taon__NS__error({
                    message: `Project already part of "${currentLeadProject.nameForNpmPackage}" build`,
                });
                return;
            }
        }
        //#endregion
        await this.set('currentLeadProject', projectRequestestingLeadPos);
        this.addMessage(`Before sorting for ${projectRequestestingLeadPos.nameForNpmPackage}: ${projects.map(c => c.nameForNpmPackage).join(',')}`);
        projects = await this.getDepsTreeFor(projects, projectRequestestingLeadPos.nameForNpmPackage, frameworkVersion);
        await this.addToRequireBuilds(projects, frameworkVersion);
        this.addMessage(`After sorting for ${projectRequestestingLeadPos.nameForNpmPackage}: ${projects.map(c => c.nameForNpmPackage).join(',')}`);
        return projects;
        //#endregion
    }
    //#endregion
    //#region API / public methods / check if still build leader
    async checkIfStillBuildLeader(body) {
        //#region @backendFunc
        const buildLeader = DevMode__NS__ProjectBuildNotificaiton.from(await this.get('currentLeadProject'));
        return buildLeader.uniqueKey === body.uniqueKey;
        //#endregion
    }
    //#endregion
    //#region API/ private methods / update pool of dev projects
    async updatePoolOfDevProjects(opt) {
        //#region @backendFunc
        //#region save/update/sort pool of dev mode projects
        const sortedPoolOfDevModeProjects = UtilsProjects__NS__sortGroupOfProject({
            projects: opt.newListOfPorjects,
            resoveDepsArray: proj => proj.devModeDependenciesNames || [],
            projNameToCompare: proj => proj.nameForNpmPackage,
            projUniqueKeyToCompare: proj => proj.uniqueKey,
        });
        this.addMessage(`saving pool (${sortedPoolOfDevModeProjects.length}) ${sortedPoolOfDevModeProjects.map(c => `(${c.port})${c.nameForNpmPackage}`).join(',')}`);
        await this.merge('poolOfDevModeProjects', {
            [opt.frameworkVersion]: sortedPoolOfDevModeProjects,
        });
        //#endregion
        //#region update framework versions
        const frameworkVersions = [
            ...opt.newListOfPorjects,
            ...opt.oldListOfProjects,
        ].reduce((a, b) => {
            return [...a, b.coreContainerVersion];
        }, []);
        await this.set('frameworkVersions', Utils__NS__uniqArray(frameworkVersions).sort());
        //#endregion
        if (Array.isArray(opt.deletedProjects) && opt.deletedProjects.length > 0) {
            //#region add non watch build to last 10 cyclic queue
            const leadProj = DevMode__NS__ProjectBuildNotificaiton.from(await this.get('currentLeadProject'));
            for (const deletedBuild of opt.deletedProjects || []) {
                await this.deleteContextByPort(deletedBuild.port);
                if (leadProj && leadProj.isEqual(deletedBuild)) {
                    await this.set('currentLeadProject', null);
                }
            }
            //#endregion
        }
        //#endregion
    }
    //#endregion
    //#region API/ private methods / deletec context by port
    async deleteContextByPort(port) {
        //#region @backendFunc
        port = Number(port);
        const ref = this.contexts.get(port);
        if (ref) {
            await ref.destroy();
        }
        this.contexts.delete(port);
        //#endregion
    }
    //#endregion
    //#region API/ public methods / delete from pool
    async deleteFromPool(projectsToDelete) {
        //#region @backendFunc
        if (!Array.isArray(projectsToDelete)) {
            projectsToDelete = [projectsToDelete];
        }
        const allDeleted = [];
        this.addMessage(`to delete ${allDeleted.map(c => c.uniqueKey).join(',')}`);
        for (let index = 0; index < projectsToDelete.length; index++) {
            const projToDelete = projectsToDelete[index];
            const frameworkVersion = projToDelete.coreContainerVersion;
            const poolOfDevModeProjectsData = (await this.get('poolOfDevModeProjects')) || {
                [projToDelete.coreContainerVersion]: [],
            };
            const requiredToBeInLeadBuildData = (await this.get('requiredToBeInLeadBuild')) || {
                [projToDelete.coreContainerVersion]: [],
            };
            poolOfDevModeProjectsData[frameworkVersion] =
                poolOfDevModeProjectsData[frameworkVersion] || [];
            requiredToBeInLeadBuildData[frameworkVersion] =
                requiredToBeInLeadBuildData[frameworkVersion] || [];
            let poolOfDevModeProjects = poolOfDevModeProjectsData[frameworkVersion].map(f => DevMode__NS__ProjectBuildNotificaiton.from(f));
            let requiredToBeInLeadBuild = requiredToBeInLeadBuildData[frameworkVersion].map(f => DevMode__NS__ProjectBuildNotificaiton.from(f));
            const requiredToBeInLeadBuildMap = new Map(requiredToBeInLeadBuild.map(c => [c.uniqueKey, c]));
            const oldListOfProjects = [...poolOfDevModeProjects];
            const deletedProjects = [];
            poolOfDevModeProjects = poolOfDevModeProjects.filter(f => {
                const isDeleted = f.isEqual(projToDelete);
                if (isDeleted) {
                    this.addMessage(`Deleting ${f.uniqueKey}`);
                    requiredToBeInLeadBuildMap.delete(f.uniqueKey);
                    deletedProjects.push(f);
                    allDeleted.push(f);
                    return false;
                }
                return true;
            });
            await this.merge('requiredToBeInLeadBuild', {
                [frameworkVersion]: Array.from(requiredToBeInLeadBuildMap.values()),
            });
            await this.updatePoolOfDevProjects({
                newListOfPorjects: poolOfDevModeProjects,
                oldListOfProjects,
                deletedProjects,
                frameworkVersion,
            });
            this.addMessage(`removing from pool - package "${projToDelete.nameForNpmPackage}" port ${projToDelete.port}`);
        }
        return allDeleted;
        //#endregion
    }
    //#endregion
    //#region API/ public methods / add to required builds
    async addToRequireBuilds(projects, frameworkVersion) {
        //#region @backendFunc
        const requiredToBeInLeadBuildData = (await this.get('requiredToBeInLeadBuild')) || {
            [frameworkVersion]: [],
        };
        requiredToBeInLeadBuildData[frameworkVersion] =
            requiredToBeInLeadBuildData[frameworkVersion] || [];
        let requiredToBeInLeadBuild = requiredToBeInLeadBuildData[frameworkVersion].map(f => DevMode__NS__ProjectBuildNotificaiton.from(f));
        requiredToBeInLeadBuild.push(...projects);
        requiredToBeInLeadBuild = Utils__NS__uniqArray(requiredToBeInLeadBuild, 'uniqueKey');
        await this.merge('requiredToBeInLeadBuild', {
            [frameworkVersion]: requiredToBeInLeadBuild,
        });
        return requiredToBeInLeadBuild;
        //#endregion
    }
    //#endregion
    //#region API/ public methods / update pool
    async updatePool(body) {
        //#region @backendFunc
        const frameworkVersion = body.coreContainerVersion;
        const poolOfDevModeProjectsData = (await this.get('poolOfDevModeProjects')) || {
            [frameworkVersion]: [],
        };
        poolOfDevModeProjectsData[frameworkVersion] =
            poolOfDevModeProjectsData[frameworkVersion] || [];
        let poolOfDevModeProjects = poolOfDevModeProjectsData[frameworkVersion].map(f => DevMode__NS__ProjectBuildNotificaiton.from(f));
        const changedProjects = [];
        const addedProjects = [];
        const oldListOfProjects = [...poolOfDevModeProjects];
        // const toCanel = ___NS__cloneDeep(buildQueue);
        const existedIndex = poolOfDevModeProjects.findIndex(c => c.isEqual(body));
        if (existedIndex === -1) {
            poolOfDevModeProjects.push(body);
            addedProjects.push(body);
        }
        else {
            poolOfDevModeProjects[existedIndex] = body;
            changedProjects.push(body);
        }
        await this.updatePoolOfDevProjects({
            newListOfPorjects: poolOfDevModeProjects,
            oldListOfProjects,
            changedProjects,
            addedProjects,
            frameworkVersion,
        });
        this.addMessage(`updating pool - package "${body.nameForNpmPackage}" port ${body.port}`);
        return poolOfDevModeProjects;
        //#endregion
    }
    //#endregion
    //#region API / private methods / get dev build controller
    async getDevBuildControllerForPort(port) {
        //#region @backendFunc
        port = Number(port);
        let contextForPort = this.contexts.get(Number(port));
        if (!contextForPort) {
            const ref = await DevBuildUtils__NS__context
                .cloneAsRemote({
                overrideRemoteHost: `http://localhost:${port}`,
            })
                .initialize();
            contextForPort = ref;
            this.contexts.set(Number(port), ref);
        }
        const devBuildController = contextForPort.getInstanceBy(DevBuildController);
        return devBuildController;
        //#endregion
    }
    //#endregion
    //#region API / private methods / quick health check
    async childQuickHealthCheck(project) {
        //#region @backendFunc
        const devBuildController = await this.getDevBuildControllerForPort(project.port);
        this.addMessage(`Health check in ${project.nameForNpmPackage}`);
        let maxTrys = 3;
        do {
            try {
                await devBuildController.healthCheck().request({
                    timeout: 500,
                });
                return true;
            }
            catch (error) { }
        } while (--maxTrys > 0);
        return false;
        //#endregion
    }
    //#endregion
    //#region API / public methods / get all frameowrk verisons in dev mode
    async getAllFrameworkVersionInDevMode() {
        //#region @backendFunc
        return (await this.get('frameworkVersions')) || [];
        //#endregion
    }
    //#endregion
    //#region API / public methods / get pool of dev mode projects
    async getPoolOfDevModePorjects(frameworkVersion) {
        //#region @backendFunc
        const data = await this.get('poolOfDevModeProjects');
        // console.log({ data });
        return data[frameworkVersion];
        //#endregion
    }
    //#endregion
    //#region API / public methods / get all messages
    getAllLogMessage() {
        return this.logMessages;
    }
    //#endregion
    //#region API / private fields / get path to log
    pathToLog = crossPlatformPath([
        UtilsOs__NS__getRealHomeDir(),
        `.${taonPackageName}`,
        `log-files-for/deb-mode-worker.json`,
    ]);
    //#endregion
    //#region API / private fields / debouce write log
    debouceWriteLog = ___NS__debounce(() => {
        //#region @backendFunc
        if (!WRITE_LOG_TO_FILE) {
            return;
        }
        Helpers__NS__writeJson(this.pathToLog, this.logMessages);
        //#endregion
    }, 1000);
    //#endregion
    //#region API /  private methods / add message
    addMessage(msg) {
        //#region @backendFunc
        if (debugModeTaonLightWeightWatchMode) {
            this.logMessages.push(msg);
            this.logMessages = this.logMessages.slice(-100);
            this.debouceWriteLog();
        }
        //#endregion
    }
    //#endregion
    //#region API / public methods / clear message
    clearMessage() {
        //#region @backendFunc
        this.logMessages.length = 0;
        if (WRITE_LOG_TO_FILE) {
            Helpers__NS__writeJson(this.pathToLog, []);
        }
        //#endregion
    }
    //#endregion
    //#region API / protected methods / use in db memory
    useInMemoryDB() {
        return !debugModeTaonLightWeightWatchMode;
    }
    //#endregion
    //#region API /  private methods / scann for processes
    async scanAndAddExitingProcesses() {
        //#region @backendFunc
        Helpers__NS__taskStarted(`Scanning for existing build processes...`);
        const limit = 20;
        for (let index = 0; index < limit; index++) {
            const portToCheck = DevBuildModels__NS__START_PORT_BUID_PROCESS + index;
            const possibleDevBuildController = await this.getDevBuildControllerForPort(portToCheck);
            try {
                await possibleDevBuildController.healthCheck().request({
                    timeout: 500,
                });
                const statusData = await possibleDevBuildController
                    .getProjectInfo()
                    .request({
                    timeout: 500,
                });
                const frameworkVersion = statusData.body.json.coreContainerVersion;
                const data = (await this.get('poolOfDevModeProjects')) || {
                    [frameworkVersion]: [],
                };
                const newProjBuild = DevMode__NS__ProjectBuildNotificaiton.from(statusData.body.json);
                const newListOfPorjects = (data[frameworkVersion] || []).map(c => DevMode__NS__ProjectBuildNotificaiton.from(c));
                const oldListOfProjects = [...newListOfPorjects];
                const projectsMap = new Map(newListOfPorjects.map(c => [c.uniqueKey, c]));
                if (!projectsMap.has(newProjBuild.uniqueKey)) {
                    newListOfPorjects.push(newProjBuild);
                    await this.updatePoolOfDevProjects({
                        frameworkVersion,
                        newListOfPorjects,
                        oldListOfProjects,
                    });
                    Helpers__NS__info(`Added existed porject build from port ${newProjBuild.port}`);
                }
                else {
                    await this.deleteContextByPort(portToCheck);
                }
            }
            catch (error) {
                config.frameworkName === tnpPackageName && console.log(error);
            }
            // await UtilsTerminal__NS__pressAnyKeyToContinueAsync();
        }
        Helpers__NS__taskDone('Done scanning for existing build processes');
        //#endregion
    }
    //#endregion
    //#region API / init _
    async _() {
        await this.set('poolOfDevModeProjects', {});
        await this.set('requiredToBeInLeadBuild', {});
        await this.set('frameworkVersions', []);
        await this.set('currentLeadProject', null);
        await this.set('shouldBeRebuild', {});
        if (WRITE_LOG_TO_FILE) {
            Helpers__NS__writeJson(this.pathToLog, []);
        }
        await this.scanAndAddExitingProcesses();
    }
};
DevModeRepository = __decorate([
    TaonRepository({
        className: 'DevModeRepository',
    })
], DevModeRepository);
export { DevModeRepository };
//# sourceMappingURL=dev-mode.repository.js.map