//#region import
import { config, FilesNames__NS__tmpLastSelectedJsonFile, Helpers__NS__createSymLink, Helpers__NS__info, Helpers__NS__isSymlinkFileExitedOrUnexisted, Helpers__NS__readJson, Helpers__NS__remove, Helpers__NS__success, Helpers__NS__taskDone, Helpers__NS__taskStarted, Helpers__NS__writeJson, Utils__NS__uniqArray } from 'tnp-core/lib-prod';
import { chalk } from 'tnp-core/lib-prod';
import { path, crossPlatformPath } from 'tnp-core/lib-prod';
import { ___NS__debounce, ___NS__isArray, ___NS__isNil, ___NS__isUndefined, ___NS__merge, ___NS__times, UtilsTerminal__NS__multiselect } from 'tnp-core/lib-prod';
import { HelpersTaon__NS__consoleGui__NS__multiselect, HelpersTaon__NS__consoleGui__NS__question__NS__yesNo, HelpersTaon__NS__copy, HelpersTaon__NS__copyFile } from '../../index';
import { BaseFeatureForProject } from './base-feature-for-project';
//#endregion
/**
 * Base library build for standard angular/typescript projects
 */
export class BaseLibraryBuild extends BaseFeatureForProject {
    cache = {};
    tempOrgTsConfigFile = `tmp-original-${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 (!___NS__isUndefined(proj.cache['deps'])) {
                return proj.cache['deps'];
            }
            const allLibs = Object.keys(proj.packageJson.allDependencies);
            proj.cache['deps'] = allLibs.filter(f => !___NS__isUndefined(libs.find(c => c.name === f)));
            return proj.cache['deps'];
        }, proj => {
            // resolve name to compare
            if (!___NS__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 (___NS__isUndefined(watchBuildSupported)) {
            watchBuildSupported = true;
        }
        const lastSelected = Helpers__NS__readJson(this.project.pathFor(FilesNames__NS__tmpLastSelectedJsonFile))
            ?.lastSelected || [];
        if (___NS__isArray(lastSelected) && lastSelected.length > 0) {
            const selected = lastSelected
                .map(c => selectedLibs.find(l => l.name == c))
                .filter(c => !!c);
            Helpers__NS__info(`
Last selected libs

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

            `);
            if (useLastUserConfiguration
                ? true
                : await HelpersTaon__NS__consoleGui__NS__question__NS__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(config.folder.dist)) {
                        skipRebuildingAllForWatch =
                            useLastUserConfiguration ||
                                (await HelpersTaon__NS__consoleGui__NS__question__NS__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 HelpersTaon__NS__consoleGui__NS__question__NS__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 UtilsTerminal__NS__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__NS__warn(`Please restart this command`);
                //   process.exit(0);
                // }
                return !!c;
            });
            Helpers__NS__info(`

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

      `);
            if (await HelpersTaon__NS__consoleGui__NS__question__NS__yesNo(`Continue build with ${selected.length} selected ?`)) {
                selectedLibs = selected;
                break;
            }
        }
        Helpers__NS__writeJson(this.project.pathFor(FilesNames__NS__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 (!___NS__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(Helpers__NS__readJson(this.project.pathFor(config.file.angular_json))
            ?.projects || {}).filter(f => f.projectType === 'library');
        let libraries = projects.map(c => this.project.ins.From(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 = !___NS__isNil(libraries);
        const allLibs = useExternalProvidedLibs ? libraries : this.libraries;
        const locationsForNodeModules = releaseBuild
            ? []
            : [
                this.project.pathFor(config.folder.node_modules),
                ...copylink_to_node_modules,
                ...(!useExternalProvidedLibs
                    ? []
                    : Utils__NS__uniqArray(libraries.map(c => c.parent.pathFor(config.folder.node_modules)))),
            ];
        console.log('Locations for node_modules:', locationsForNodeModules
            .map(c => 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
            ? Utils__NS__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)) {
                        Helpers__NS__info(`Skipping build for watch mode (${index + 1}/${allLibs.length})` +
                            ` ${lib.basename} (${chalk.bold(lib.name)})`);
                    }
                    continue;
                }
                Helpers__NS__info(`Building (${index + 1}/${allLibs.length}) ${lib.basename}` +
                    ` (${chalk.bold(lib.name)})`);
                await libProj.libraryBuild.libNormalBuildProcess({
                    lib,
                    locationsForNodeModules: useExternalProvidedLibs
                        ? [
                            this.project.pathFor(config.folder.node_modules),
                            libProj.pathFor(config.folder.node_modules),
                        ]
                        : locationsForNodeModules,
                    strategy,
                    outputLineReplace: outputLineReplace &&
                        outputLineReplace(lib, useExternalProvidedLibs),
                });
            }
        }
        //#endregion
        //#region watch build
        if (watch) {
            for (const [index, lib] of selectedLibs.entries()) {
                Helpers__NS__info(`Building for watch (${index + 1}/${selectedLibs.length}) ` +
                    `${lib.basename} (${chalk.bold(lib.name)})`);
                await this.libWatchBuildProcess({
                    lib,
                    locationsForNodeModules,
                    strategy,
                    outputLineReplace: outputLineReplace &&
                        outputLineReplace(lib, useExternalProvidedLibs),
                });
            }
        }
        //#endregion
        //#region success message
        if (watch) {
            Helpers__NS__success('BUILD DONE.. watching..');
        }
        else {
            // await this.__indexRebuilder.start({ taskName: 'index rebuild watch' });
            Helpers__NS__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 = ___NS__debounce(() => {
            const sourceDist = lib.nearestParent.pathFor([
                config.folder.dist,
                lib.basename,
            ]);
            for (const node_modules of locationsForNodeModules) {
                const dest = crossPlatformPath([node_modules, lib.name]);
                if (Helpers__NS__isSymlinkFileExitedOrUnexisted(dest)) {
                    Helpers__NS__remove(dest);
                }
                HelpersTaon__NS__copy(sourceDist, dest, {
                    recursive: true,
                    overwrite: true,
                });
                console.log(`Sync (watch|copy) done for ${lib.basename} to ${lib.name} (${crossPlatformPath([
                    path.basename(path.dirname(node_modules)),
                    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([
                config.folder.dist,
                lib.basename,
            ]);
            for (const node_modules_abs_path of locationsForNodeModules) {
                const libInsideNodeModules = crossPlatformPath([
                    node_modules_abs_path,
                    lib.name,
                ]);
                if (!Helpers__NS__isSymlinkFileExitedOrUnexisted(libInsideNodeModules)) {
                    Helpers__NS__remove(libInsideNodeModules);
                }
                // console.log('linking from ', sourceDist);
                // console.log('linking to ', dest);
                // Helpers__NS__remove(dest);
                Helpers__NS__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([
            config.folder.dist,
            lib.basename,
        ]);
        //#region compile process
        const compileProcess = async () => {
            Helpers__NS__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 = crossPlatformPath([
                    node_modules_abs_path,
                    lib.name,
                ]);
                if (!Helpers__NS__isSymlinkFileExitedOrUnexisted(libInsideNodeModules)) {
                    Helpers__NS__remove(libInsideNodeModules);
                }
                // console.log('linking from ', sourceDist);
                // console.log('linking to ', dest);
                // Helpers__NS__remove(dest);
                Helpers__NS__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')) {
                        Helpers__NS__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} (${crossPlatformPath([
                    path.basename(path.dirname(node_modules_abs_path)),
                    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 = crossPlatformPath([
                    node_modules_abs_path,
                    lib.name,
                ]);
                if (Helpers__NS__isSymlinkFileExitedOrUnexisted(libInsideNodeModules)) {
                    Helpers__NS__remove(libInsideNodeModules);
                }
                HelpersTaon__NS__copy(libCompiledInDist, libInsideNodeModules, {
                    recursive: true,
                    overwrite: true,
                });
                console.log(`Sync copy done for ${lib.basename} to ${lib.name} (${crossPlatformPath([
                    path.basename(path.dirname(node_modules_abs_path)),
                    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(path.dirname(this.project.location))
            .filter(c => c.location !== this.project.location)
            .map(p => p.linkedProjects?.embeddedProject
            ? p.linkedProjects.embeddedProject
            : p);
        const locations = await HelpersTaon__NS__consoleGui__NS__multiselect('Copy compiled version to projects', projects.map(c => {
            return {
                name: c.genericName,
                value: c.pathFor(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', `${___NS__times(howMuchBack, () => `../`).join('')}${this.tempOrgTsConfigFile}`);
        }
        //#endregion
    }
    //#endregion
    //#region private methods / replace tsconfig pathes
    async replaceTsConfigsPathes() {
        //#region @backendFunc
        if (!this.project.hasFile(this.tempOrgTsConfigFile)) {
            HelpersTaon__NS__copyFile(this.project.pathFor(config.file.tsconfig_json), this.project.pathFor(this.tempOrgTsConfigFile));
        }
        const paths = this.project.libraryBuild.libraries.reduce((a, b) => {
            return ___NS__merge(a, {
                [b.name]: [
                    crossPlatformPath([this.project.relative(b.location), 'src', 'lib']),
                ],
                [`${b.name}/*`]: [
                    `${crossPlatformPath([this.project.relative(b.location), 'src', 'lib'])}/*`,
                ],
            });
        }, {});
        // console.log('paths', paths);
        this.project.setValueToJSONC(config.file.tsconfig_json, 'compilerOptions.paths', paths);
        //#endregion
    }
    //#endregion
    //#region private methods  / restore original tsconfig
    async restoreOriginalTsConfig() {
        //#region @backendFunc
        Helpers__NS__taskStarted('  original tsconfig files');
        const commands = [
            `git checkout ${this.tempOrgTsConfigFile}`,
            `git checkout ${config.file.tsconfig_json}`,
            ...this.project.libraryBuild.libraries.map(lib => {
                return `git checkout ${this.project.relative(lib.location)}/${config.file.tsconfig_lib_json}`;
            }),
        ];
        for (const command of commands) {
            try {
                this.project
                    .run(command, { output: false, biggerBuffer: false, silence: true })
                    .sync();
            }
            catch (err) { }
        }
        Helpers__NS__taskDone('Restore original tsconfig files');
        //#endregion
    }
}
//# sourceMappingURL=c:/Users/darek/projects/npm/taon-dev/tnp-helpers/dist-nocutsrc-prod/lib/base/classes/base-library-build.js.map