"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BaseLibraryBuild = void 0;
//#region import
const lib_1 = require("tnp-core/lib");
const lib_2 = require("tnp-core/lib");
const lib_3 = require("tnp-core/lib");
const lib_4 = require("tnp-core/lib");
const index_1 = require("../../index");
const base_feature_for_project_1 = require("./base-feature-for-project");
//#endregion
/**
 * Base library build for standard angular/typescript projects
 */
class BaseLibraryBuild extends base_feature_for_project_1.BaseFeatureForProject {
    cache = {};
    tempOrgTsConfigFile = `tmp-original-${lib_1.config.file.tsconfig_json}`;
    //#region getters & methods / sort by deps
    sortByDeps(libraries) {
        //#region @backendFunc
        const libs = libraries;
        const sorted = this.project.ins.sortGroupOfProject(libs, proj => {
            // resolve dependencies names
            if (!lib_4._.isUndefined(proj.cache['deps'])) {
                return proj.cache['deps'];
            }
            const allLibs = Object.keys(proj.packageJson.allDependencies);
            proj.cache['deps'] = allLibs.filter(f => !lib_4._.isUndefined(libs.find(c => c.name === f)));
            return proj.cache['deps'];
        }, proj => {
            // resolve name to compare
            if (!lib_4._.isUndefined(proj.cache['nameToCompare'])) {
                return proj.cache['nameToCompare'];
            }
            proj.cache['nameToCompare'] = proj.name;
            return proj.cache['nameToCompare'];
        });
        return sorted;
        //#endregion
    }
    //#endregion
    //#region getters & methods / get sorted libraries by deps for build\
    /**
     * Use cases:
     * 1. build all libraries in normal mode
     * 2. build all libraries in watch mode
     * 3. build selected libraries in normal mode
     *   (with first time all libraries will be build)
     * 4. build selected libraries in watch mode
     *    (use normal build for not selected libraries)
     * 5. build selected libraries in watch mode
     *    (skip normal build for not selected libraries)
     */
    async selectAndSaveLibraries(
    // this: {},
    { selectedLibs, watch, watchBuildSupported, skipAllLibsQuestion, useLastUserConfiguration, }) {
        //#region @backendFunc
        let buildAll = false;
        let skipRebuildingAllForWatch = false;
        if (selectedLibs.length <= 1) {
            return { selectedLibs, skipRebuildingAllForWatch };
        }
        if (lib_4._.isUndefined(watchBuildSupported)) {
            watchBuildSupported = true;
        }
        const lastSelected = lib_1.Helpers.readJson(this.project.pathFor(lib_1.FilesNames.tmpLastSelectedJsonFile))
            ?.lastSelected || [];
        if (lib_4._.isArray(lastSelected) && lastSelected.length > 0) {
            const selected = lastSelected
                .map(c => selectedLibs.find(l => l.name == c))
                .filter(c => !!c);
            lib_1.Helpers.info(`
Last selected libs

${selected.map((c, i) => `${i + 1}. ${c.basename} ${lib_2.chalk.bold(c.name)}`).join('\n')}

            `);
            if (useLastUserConfiguration
                ? true
                : await index_1.HelpersTaon.consoleGui.question.yesNo(`Continue${watch ? ' watch ' : ' '}build with last selected ?`)) {
                selectedLibs = selected;
                // TODO maybe safe about skip
                if ((watchBuildSupported && watch) || useLastUserConfiguration) {
                    // if(this.project)
                    if (this.project.hasFolder(lib_1.config.folder.dist)) {
                        skipRebuildingAllForWatch =
                            useLastUserConfiguration ||
                                (await index_1.HelpersTaon.consoleGui.question.yesNo(`Skip rebuilding all libraries for watch mode ?`));
                    }
                    else {
                        // no dist -> rebuild all
                        skipRebuildingAllForWatch = false;
                    }
                }
                return { selectedLibs, skipRebuildingAllForWatch };
            }
        }
        // more than 6 libs can be hard to manage in watch mode
        if (!watch || (watch && selectedLibs.length < 6)) {
            if (!skipAllLibsQuestion) {
                buildAll = await index_1.HelpersTaon.consoleGui.question.yesNo(`Should all libraries be included in${watch ? ' watch' : ' '}build ?`);
            }
        }
        if (buildAll) {
            return { selectedLibs, skipRebuildingAllForWatch };
        }
        while (true) {
            const defaultSelected = lastSelected
                .map(c => selectedLibs.find(l => l.name == c))
                .filter(c => !!c)
                .map(c => c.name);
            const pickedLibs = (await lib_4.UtilsTerminal.multiselect({
                question: `Select libraries to ${watch ? 'watch' : ''} build `,
                choices: selectedLibs.map(c => {
                    return { name: c.name, value: c.name, selected: true };
                }),
                defaultSelected,
            })).map(c => selectedLibs.find(l => l.name == c));
            // console.log({ pickedLibs });
            const selected = pickedLibs.filter(c => {
                // if (!!c) { // TODO QUICK_FIX
                //   this.project.removeFile(lastSelectedJsonFile);
                //   Helpers.warn(`Please restart this command`);
                //   process.exit(0);
                // }
                return !!c;
            });
            lib_1.Helpers.info(`

${selected.map((c, i) => `${i + 1}. ${c.basename} ${lib_2.chalk.bold(c.name)}`).join('\n')}

      `);
            if (await index_1.HelpersTaon.consoleGui.question.yesNo(`Continue build with ${selected.length} selected ?`)) {
                selectedLibs = selected;
                break;
            }
        }
        lib_1.Helpers.writeJson(this.project.pathFor(lib_1.FilesNames.tmpLastSelectedJsonFile), {
            lastSelected: selectedLibs.map(c => c.name),
        });
        return { selectedLibs, skipRebuildingAllForWatch };
        //#endregion
    }
    //#endregion
    //#region getters & methods / angular libraries
    /**
     * angular libraries from angular.json
     */
    get libraries() {
        //#region @backendFunc
        if (!lib_4._.isUndefined(this.cache['libraries'])) {
            return this.cache['libraries'];
        }
        const libraries = this.getLibraries();
        this.cache['libraries'] = libraries;
        return libraries;
        //#endregion
    }
    getLibraries() {
        //#region @backendFunc
        const projects = Object.values(lib_1.Helpers.readJson(this.project.pathFor(lib_1.config.file.angular_json))
            ?.projects || {}).filter(f => f.projectType === 'library');
        let libraries = projects.map(c => this.project.ins.From(lib_3.path.join(this.project.location, c.root)));
        libraries = this.sortByDeps(libraries);
        return libraries;
        //#endregion
    }
    //#endregion
    //#region getters & methods / build libraries
    /**
     * Angular library build
     */
    async buildLibraries(
    // this:{},
    { watch = false, strategy, releaseBuild = false, copylink_to_node_modules, outputLineReplace, libraries, useLastUserConfiguration, } = {}) {
        //#region @backend
        await this.project.linkedProjects.saveAllLinkedProjectsToDB();
        // TODO fix this later (with whole navigatio for projects)
        // await this.changeTsConfigForLibrariesTypes();
        //#region prepare parameters
        if (!Array.isArray(copylink_to_node_modules)) {
            copylink_to_node_modules = [];
        }
        if (!strategy) {
            strategy = 'link';
        }
        //#endregion
        //#region select target node_modules
        const useExternalProvidedLibs = !lib_4._.isNil(libraries);
        const allLibs = useExternalProvidedLibs ? libraries : this.libraries;
        const locationsForNodeModules = releaseBuild
            ? []
            : [
                this.project.pathFor(lib_1.config.folder.node_modules),
                ...copylink_to_node_modules,
                ...(!useExternalProvidedLibs
                    ? []
                    : lib_1.Utils.uniqArray(libraries.map(c => c.parent.pathFor(lib_1.config.folder.node_modules)))),
            ];
        console.log('Locations for node_modules:', locationsForNodeModules
            .map(c => (0, lib_3.crossPlatformPath)(c))
            .map(c => `-${c}`)
            .join('\n'));
        //#endregion
        //#region select libs to build
        let { selectedLibs, skipRebuildingAllForWatch } = await this.selectAndSaveLibraries({
            selectedLibs: libraries ? libraries : allLibs,
            watch,
            skipAllLibsQuestion: useExternalProvidedLibs,
            useLastUserConfiguration,
        });
        //#endregion
        //#region normal build
        const allParenProjsForExtenalLibsBuild = useExternalProvidedLibs
            ? lib_1.Utils.uniqArray(libraries.map(c => c.parent), 'location')
            : [this.project];
        // console.log(
        //   'SORTED PROJECTS',
        //   additionalLibsProjs.map(c => c.name),
        // );
        selectedLibs = this.sortByDeps(selectedLibs);
        // TODO sort additionalLibsProjs thing before ric
        for (const libProj of allParenProjsForExtenalLibsBuild) {
            await libProj.init();
            for (const [index, lib] of libProj.libraryBuild.libraries.entries()) {
                if (watch && skipRebuildingAllForWatch) {
                    const selectedNames = selectedLibs.map(c => c.name);
                    if (!selectedNames.includes(lib.name)) {
                        lib_1.Helpers.info(`Skipping build for watch mode (${index + 1}/${allLibs.length})` +
                            ` ${lib.basename} (${lib_2.chalk.bold(lib.name)})`);
                    }
                    continue;
                }
                lib_1.Helpers.info(`Building (${index + 1}/${allLibs.length}) ${lib.basename}` +
                    ` (${lib_2.chalk.bold(lib.name)})`);
                await libProj.libraryBuild.libNormalBuildProcess({
                    lib,
                    locationsForNodeModules: useExternalProvidedLibs
                        ? [
                            this.project.pathFor(lib_1.config.folder.node_modules),
                            libProj.pathFor(lib_1.config.folder.node_modules),
                        ]
                        : locationsForNodeModules,
                    strategy,
                    outputLineReplace: outputLineReplace &&
                        outputLineReplace(lib, useExternalProvidedLibs),
                });
            }
        }
        //#endregion
        //#region watch build
        if (watch) {
            for (const [index, lib] of selectedLibs.entries()) {
                lib_1.Helpers.info(`Building for watch (${index + 1}/${selectedLibs.length}) ` +
                    `${lib.basename} (${lib_2.chalk.bold(lib.name)})`);
                await this.libWatchBuildProcess({
                    lib,
                    locationsForNodeModules,
                    strategy,
                    outputLineReplace: outputLineReplace &&
                        outputLineReplace(lib, useExternalProvidedLibs),
                });
            }
        }
        //#endregion
        //#region success message
        if (watch) {
            lib_1.Helpers.success('BUILD DONE.. watching..');
        }
        else {
            // await this.__indexRebuilder.start({ taskName: 'index rebuild watch' });
            lib_1.Helpers.success('BUILD DONE');
        }
        //#endregion
        //#endregion
    }
    //#endregion
    //#region getters & methods / lib watch build process
    async libWatchBuildProcess({ lib, locationsForNodeModules, strategy, outputLineReplace, }) {
        //#region @backendFunc
        //#region debouce copy
        const debouncedBuildCopyStrategy = lib_4._.debounce(() => {
            const sourceDist = lib.nearestParent.pathFor([
                lib_1.config.folder.dist,
                lib.basename,
            ]);
            for (const node_modules of locationsForNodeModules) {
                const dest = (0, lib_3.crossPlatformPath)([node_modules, lib.name]);
                if (lib_1.Helpers.isSymlinkFileExitedOrUnexisted(dest)) {
                    lib_1.Helpers.remove(dest);
                }
                index_1.HelpersTaon.copy(sourceDist, dest, {
                    recursive: true,
                    overwrite: true,
                });
                console.log(`Sync (watch|copy) done for ${lib.basename} to ${lib.name} (${(0, lib_3.crossPlatformPath)([
                    lib_3.path.basename(lib_3.path.dirname(node_modules)),
                    lib_1.config.folder.node_modules,
                ])})`);
            }
        }, 500);
        //#endregion
        if (strategy === 'link') {
            // relink just to make sure npm install did not delete links
            const libCompiledInDist = lib.nearestParent.pathFor([
                lib_1.config.folder.dist,
                lib.basename,
            ]);
            for (const node_modules_abs_path of locationsForNodeModules) {
                const libInsideNodeModules = (0, lib_3.crossPlatformPath)([
                    node_modules_abs_path,
                    lib.name,
                ]);
                if (!lib_1.Helpers.isSymlinkFileExitedOrUnexisted(libInsideNodeModules)) {
                    lib_1.Helpers.remove(libInsideNodeModules);
                }
                // console.log('linking from ', sourceDist);
                // console.log('linking to ', dest);
                // Helpers.remove(dest);
                lib_1.Helpers.createSymLink(libCompiledInDist, libInsideNodeModules, {
                    continueWhenExistedFolderDoesntExists: true,
                });
            }
        }
        //#region watch build process
        await lib.nearestParent.execute(lib.libraryBuild.getLibraryBuildCommand({
            watch: true,
        }), {
            outputLineReplace,
            resolvePromiseMsg_stdout: lib.libraryBuild.getLibraryBuildSuccessComamnds,
            resolvePromiseMsgCallback_stdout: () => {
                if (strategy === 'copy') {
                    debouncedBuildCopyStrategy();
                }
            },
        });
        //#endregion
        //#endregion
    }
    //#endregion
    //#region getters & methods / lib normal build process
    async libNormalBuildProcess({ lib, locationsForNodeModules, strategy, outputLineReplace, }) {
        //#region @backendFunc
        const libCompiledInDist = lib.nearestParent.pathFor([
            lib_1.config.folder.dist,
            lib.basename,
        ]);
        //#region compile process
        const compileProcess = async () => {
            lib_1.Helpers.info(`Compiling ${lib.name} ...`);
            await lib.nearestParent.execute(lib.libraryBuild.getLibraryBuildCommand({
                watch: false,
            }), {
                resolvePromiseMsg_stdout: lib.libraryBuild.getLibraryBuildSuccessComamnds,
                outputLineReplace,
            });
        };
        //#endregion
        if (strategy === 'link') {
            //#region link dist to node_modules
            for (const node_modules_abs_path of locationsForNodeModules) {
                const libInsideNodeModules = (0, lib_3.crossPlatformPath)([
                    node_modules_abs_path,
                    lib.name,
                ]);
                if (!lib_1.Helpers.isSymlinkFileExitedOrUnexisted(libInsideNodeModules)) {
                    lib_1.Helpers.remove(libInsideNodeModules);
                }
                // console.log('linking from ', sourceDist);
                // console.log('linking to ', dest);
                // Helpers.remove(dest);
                lib_1.Helpers.createSymLink(libCompiledInDist, libInsideNodeModules, {
                    continueWhenExistedFolderDoesntExists: true,
                });
                if (lib.isAngularLib) {
                    // recreate index d.ts
                }
                else {
                    // create index.ts from index.d.ts
                    // QUICK_FIX for typescript
                    if (lib.hasFile('src/index.d.ts') && !lib.hasFile('src/index.ts')) {
                        lib_1.Helpers.createSymLink(lib.pathFor('src/index.d.ts'), 
                        // crossPlatformPath([libCompiledInDist, 'index.d.ts']),
                        lib.pathFor('src/index.ts'), {
                            continueWhenExistedFolderDoesntExists: true,
                        });
                        try {
                            lib.removeFile('src/index.d.ts');
                        }
                        catch (error) { }
                    }
                }
                console.log(`Sync (link) done for ${lib.basename} to ${lib.name} (${(0, lib_3.crossPlatformPath)([
                    lib_3.path.basename(lib_3.path.dirname(node_modules_abs_path)),
                    lib_1.config.folder.node_modules,
                ])})`);
            }
            await compileProcess();
            //#endregion
        }
        else if (strategy === 'copy') {
            //#region copy dist to node_modules
            await compileProcess();
            for (const node_modules_abs_path of locationsForNodeModules) {
                const libInsideNodeModules = (0, lib_3.crossPlatformPath)([
                    node_modules_abs_path,
                    lib.name,
                ]);
                if (lib_1.Helpers.isSymlinkFileExitedOrUnexisted(libInsideNodeModules)) {
                    lib_1.Helpers.remove(libInsideNodeModules);
                }
                index_1.HelpersTaon.copy(libCompiledInDist, libInsideNodeModules, {
                    recursive: true,
                    overwrite: true,
                });
                console.log(`Sync copy done for ${lib.basename} to ${lib.name} (${(0, lib_3.crossPlatformPath)([
                    lib_3.path.basename(lib_3.path.dirname(node_modules_abs_path)),
                    lib_1.config.folder.node_modules,
                ])})`);
            }
            //#endregion
        }
        //#endregion
    }
    //#endregion
    //#region getters & methods / get library build success command
    getLibraryBuildCommand(options) {
        //#region @backendFunc
        const { watch } = options;
        if (this.project.isAngularLib) {
            return `npm-run ng build ${this.project.basename} ${watch ? '--watch' : ''}`;
        }
        else {
            return (`npm-run tsc -p libraries/${this.project.basename}/tsconfig.lib.json ` +
                ` ${watch ? '--watch' : ''} --preserveWatchOutput`);
        }
        //#endregion
    }
    //#endregion
    //#region getters & methods / get library build success command
    get getLibraryBuildSuccessComamnds() {
        //#region @backendFunc
        const isAngularLib = this.project.isAngularLib;
        if (isAngularLib) {
            return [`Trace: Build complete`, 'Compilation complete'];
        }
        else {
            return [
                `Found 0 errors. Watching for file change`,
                'Compilation complete',
            ];
        }
        //#endregion
    }
    //#endregion
    //#region getters & methods / select copy to projects
    async selectCopytoProjects() {
        //#region @backendFunc
        const projects = this.project.ins.allProjectsFromFolder(lib_3.path.dirname(this.project.location))
            .filter(c => c.location !== this.project.location)
            .map(p => p.linkedProjects?.embeddedProject
            ? p.linkedProjects.embeddedProject
            : p);
        const locations = await index_1.HelpersTaon.consoleGui.multiselect('Copy compiled version to projects', projects.map(c => {
            return {
                name: c.genericName,
                value: c.pathFor(lib_1.config.folder.node_modules),
            };
        }), true);
        return locations;
        //#endregion
    }
    //#endregion
    //#region private methods / change tsconfig for libraries typescript proper local types
    async changeTsConfigForLibrariesTypes() {
        //#region @backendFunc
        await this.restoreOriginalTsConfig();
        await this.replaceTsConfigsPathes();
        for (const libChild of this.project.libraryBuild.libraries) {
            const howMuchBack = this.project
                .relative(libChild.location)
                .split('/').length;
            libChild.setValueToJSONC('tsconfig.lib.json', 'extends', `${lib_4._.times(howMuchBack, () => `../`).join('')}${this.tempOrgTsConfigFile}`);
        }
        //#endregion
    }
    //#endregion
    //#region private methods / replace tsconfig pathes
    async replaceTsConfigsPathes() {
        //#region @backendFunc
        if (!this.project.hasFile(this.tempOrgTsConfigFile)) {
            index_1.HelpersTaon.copyFile(this.project.pathFor(lib_1.config.file.tsconfig_json), this.project.pathFor(this.tempOrgTsConfigFile));
        }
        const paths = this.project.libraryBuild.libraries.reduce((a, b) => {
            return lib_4._.merge(a, {
                [b.name]: [
                    (0, lib_3.crossPlatformPath)([this.project.relative(b.location), 'src', 'lib']),
                ],
                [`${b.name}/*`]: [
                    `${(0, lib_3.crossPlatformPath)([this.project.relative(b.location), 'src', 'lib'])}/*`,
                ],
            });
        }, {});
        // console.log('paths', paths);
        this.project.setValueToJSONC(lib_1.config.file.tsconfig_json, 'compilerOptions.paths', paths);
        //#endregion
    }
    //#endregion
    //#region private methods  / restore original tsconfig
    async restoreOriginalTsConfig() {
        //#region @backendFunc
        lib_1.Helpers.taskStarted('  original tsconfig files');
        const commands = [
            `git checkout ${this.tempOrgTsConfigFile}`,
            `git checkout ${lib_1.config.file.tsconfig_json}`,
            ...this.project.libraryBuild.libraries.map(lib => {
                return `git checkout ${this.project.relative(lib.location)}/${lib_1.config.file.tsconfig_lib_json}`;
            }),
        ];
        for (const command of commands) {
            try {
                this.project
                    .run(command, { output: false, biggerBuffer: false, silence: true })
                    .sync();
            }
            catch (err) { }
        }
        lib_1.Helpers.taskDone('Restore original tsconfig files');
        //#endregion
    }
}
exports.BaseLibraryBuild = BaseLibraryBuild;
//# sourceMappingURL=base-library-build.js.map