/* eslint-disable import/no-cycle */
// @todo fix cycle dep
import chalk from 'chalk';
import path from 'path';
import inquirer from 'inquirer';

import { logToSummary, logTask, logSuccess, logError, logDebug, logWarning } from '../systemTools/logger';
import { generateOptions, inquirerPrompt } from '../systemTools/prompt';
import {
    cleanFolder,
    copyFolderContentsRecursiveSync,
    writeFileSync,
    removeDirs
} from '../systemTools/fileutils';
import { cleanPlaformAssets } from '../projectTools/projectParser';
import { PLATFORMS, SUPPORTED_PLATFORMS } from '../constants';
import { checkAndConfigureSdks } from './sdkManager';
import { configureEntryPoints } from '../templateTools';
import { getTimestampPathsConfig } from '../common';

export const rnvPlatformList = c => new Promise((resolve) => {
    const opts = _genPlatOptions(c);
    logToSummary(`Platforms:\n\n${opts.asString}`);
    resolve();
});

export const rnvPlatformConfigure = async (c) => {
    // c.platform = c.program.platform || 'all';
    logTask(`rnvPlatformConfigure:${c.platform}`);

    await isPlatformSupported(c);
    await cleanPlatformBuild(c, c.platform);
    await cleanPlaformAssets(c, c.platform);
    await _runCopyPlatforms(c, c.platform);
};

const updateProjectPlatforms = (c, platforms) => {
    const {
        project: { config }
    } = c.paths;
    const currentConfig = c.files.project.config;
    currentConfig.defaults = currentConfig.defaults || {};
    currentConfig.defaults.supportedPlatforms = platforms;
    writeFileSync(config, currentConfig);
};

export const rnvPlatformSetup = async (c) => {
    const currentPlatforms = c.files.project.config.defaults?.supportedPlatforms || [];

    const { inputSupportedPlatforms } = await inquirer.prompt({
        name: 'inputSupportedPlatforms',
        type: 'checkbox',
        pageSize: 20,
        message: 'What platforms would you like to use?',
        validate: val => !!val.length || 'Please select at least a platform',
        default: currentPlatforms,
        choices: SUPPORTED_PLATFORMS
    });

    updateProjectPlatforms(c, inputSupportedPlatforms);
};

const _generatePlatformChoices = c => c.buildConfig.defaults.supportedPlatforms.map((platform) => {
    const isConnected = c.paths.project.platformTemplatesDirs[
        platform
    ].includes(c.paths.rnv.platformTemplates.dir);
    return {
        name: `${platform} - ${
            isConnected
                ? chalk.green('(connected)')
                : chalk.yellow('(ejected)')
        }`,
        value: platform,
        isConnected
    };
});

export const rnvPlatformEject = async (c) => {
    logTask('rnvPlatformEject');

    const { ejectedPlatforms } = await inquirer.prompt({
        name: 'ejectedPlatforms',
        message:
            'This will copy platformTemplates folders from ReNative managed directly to your project Select platforms you would like to connect (use SPACE key)',
        type: 'checkbox',
        choices: _generatePlatformChoices(c).map(choice => ({
            ...choice,
            disabled: !choice.isConnected
        }))
    });

    if (ejectedPlatforms.length) {
        const ptfn = 'platformTemplates';
        const rptf = c.paths.rnv.platformTemplates.dir;
        const prf = c.paths.project.dir;

        let copyShared = false;

        ejectedPlatforms.forEach((platform) => {
            if (PLATFORMS[platform].requiresSharedConfig) {
                copyShared = true;
            }
            copyFolderContentsRecursiveSync(
                path.join(rptf, platform),
                path.join(prf, ptfn, platform)
            );

            if (copyShared) {
                copyFolderContentsRecursiveSync(
                    path.join(rptf, '_shared'),
                    path.join(prf, ptfn, '_shared')
                );
            }

            c.files.project.config.paths
                .platformTemplatesDirs = c.files.project.config.paths.platformTemplatesDirs || {};
            c.files.project.config.paths.platformTemplatesDirs[
                platform
            ] = `./${ptfn}`;
            writeFileSync(c.paths.project.config, c.files.project.config);
        });

        logSuccess(
            `${chalk.white(
                ejectedPlatforms.join(',')
            )} platform templates are located in ${chalk.white(
                c.files.project.config.paths.platformTemplatesDirs[ejectedPlatforms[0]]
            )} now. You can edit them directly!`
        );
    } else {
        logError(`You haven't selected any platform to eject.
TIP: You can select options with ${chalk.white('SPACE')} key before pressing ENTER!`);
    }
};

const _genPlatOptions = (c) => {
    const opts = generateOptions(
        c.buildConfig.defaults.supportedPlatforms,
        true,
        null,
        (i, obj, mapping, defaultVal) => {
            const isEjected = c.paths.project.platformTemplatesDirs[
                obj
            ].includes(c.paths.rnv.platformTemplates.dir)
                ? chalk.green('(connected)')
                : chalk.yellow('(ejected)');
            return ` [${chalk.white(i + 1)}]> ${chalk.bold(
                defaultVal
            )} - ${isEjected} \n`;
        }
    );
    return opts;
};

export const rnvPlatformConnect = async (c) => {
    logTask('rnvPlatformConnect');

    const { connectedPlatforms } = await inquirer.prompt({
        name: 'connectedPlatforms',
        message:
            'This will point platformTemplates folders from your local project to ReNative managed one. Select platforms you would like to connect',
        type: 'checkbox',
        choices: _generatePlatformChoices(c).map(choice => ({
            ...choice,
            disabled: choice.isConnected
        }))
    });

    if (connectedPlatforms.length) {
        connectedPlatforms.forEach((platform) => {
            if (c.files.project.config.paths.platformTemplatesDirs?.[platform]) {
                delete c.files.project.config.paths.platformTemplatesDirs[platform];
            }

            if (
                !Object.keys(c.files.project.config.paths.platformTemplatesDirs)
                    .length
            ) {
                delete c.files.project.config.paths.platformTemplatesDirs; // also cleanup the empty object
            }

            writeFileSync(c.paths.project.config, c.files.project.config);
        });
    }

    const { deletePlatformFolder } = await inquirer.prompt({
        name: 'deletePlatformFolder',
        type: 'confirm',
        message:
            'Would you also like to delete the previously used platform folder?'
    });

    if (deletePlatformFolder) {
        const pathsToRemove = [];
        connectedPlatforms.forEach((platform) => {
            pathsToRemove.push(
                path.join(
                    c.paths.project.platformTemplatesDirs[platform],
                    platform
                )
            );
        });

        // TODO: Remove shared folders as well

        await removeDirs(pathsToRemove);
    }

    logSuccess(
        `${chalk.white(
            connectedPlatforms.join(',')
        )} now using ReNative platformTemplates located in ${chalk.white(
            c.paths.rnv.platformTemplates.dir
        )} now!`
    );
};

const _runCopyPlatforms = (c, platform) => new Promise((resolve) => {
    logTask(`_runCopyPlatforms:${platform}`);
    const copyPlatformTasks = [];

    if (platform === 'all') {
        Object.keys(c.buildConfig.platforms).forEach((k) => {
            if (_isPlatformSupportedSync(k)) {
                const ptPath = path.join(
                    c.paths.project.platformTemplatesDirs[k],
                    `${k}`
                );
                const pPath = path.join(
                    c.paths.project.builds.dir,
                    `${c.runtime.appId}_${k}`
                );
                copyPlatformTasks.push(
                    copyFolderContentsRecursiveSync(ptPath, pPath, true, false, false, {},
                        getTimestampPathsConfig(c, platform), c)
                );
            }
        });
    } else if (_isPlatformSupportedSync(platform)) {
        const ptPath = path.join(
            c.paths.project.platformTemplatesDirs[platform],
            `${platform}`
        );
        const pPath = path.join(
            c.paths.project.builds.dir,
            `${c.runtime.appId}_${platform}`
        );
        copyPlatformTasks.push(
            copyFolderContentsRecursiveSync(ptPath, pPath, true, false, false, {},
                getTimestampPathsConfig(c, platform), c)
        );
    } else {
        logWarning(
            `Your platform ${chalk.white(
                platform
            )} config is not present. Check ${chalk.white(
                c.paths.appConfig.config
            )}`
        );
    }

    Promise.all(copyPlatformTasks).then(() => {
        resolve();
    });
});

export const cleanPlatformBuild = (c, platform) => new Promise((resolve) => {
    logTask(`cleanPlatformBuild:${platform}`);

    const cleanTasks = [];

    if (platform === 'all') {
        Object.keys(c.buildConfig.platforms).forEach((k) => {
            if (_isPlatformSupportedSync(k)) {
                const pPath = path.join(
                    c.paths.project.builds.dir,
                    `${c.runtime.appId}_${k}`
                );
                cleanTasks.push(cleanFolder(pPath));
            }
        });
    } else if (_isPlatformSupportedSync(platform)) {
        const pPath = path.join(
            c.paths.project.builds.dir,
            `${c.runtime.appId}_${platform}`
        );
        cleanTasks.push(cleanFolder(pPath));
    }

    Promise.all(cleanTasks).then(() => {
        resolve();
    });
});

export const createPlatformBuild = (c, platform) => new Promise((resolve, reject) => {
    logTask(`createPlatformBuild:${platform}`);

    if (!_isPlatformSupportedSync(platform, null, reject)) return;

    const pPath = path.join(
        c.paths.project.builds.dir,
        `${c.runtime.appId}_${platform}`
    );
    const ptPath = path.join(
        c.paths.project.platformTemplatesDirs[platform],
        `${platform}`
    );

    copyFolderContentsRecursiveSync(ptPath, pPath, false, [
        path.join(ptPath, '_privateConfig')
    ], false, {}, getTimestampPathsConfig(c, platform), c);

    resolve();
});

export const isPlatformSupported = async (c) => {
    logTask(`isPlatformSupported:${c.platform}`);
    let platformsAsObj = c.buildConfig
        ? c.buildConfig.platforms
        : c.supportedPlatforms;
    if (!platformsAsObj) platformsAsObj = SUPPORTED_PLATFORMS;
    const opts = generateOptions(platformsAsObj);

    if (
        !c.platform
        || c.platform === true
        || !SUPPORTED_PLATFORMS.includes(c.platform)
    ) {
        const { platform } = await inquirerPrompt({
            name: 'platform',
            type: 'list',
            message: 'Pick one of available platforms',
            choices: opts.keysAsArray,
            logMessage: 'You need to specify platform'
        });

        c.platform = platform;
    }

    const configuredPlatforms = c.files.project.config?.defaults?.supportedPlatforms;
    if (
        Array.isArray(configuredPlatforms)
        && !configuredPlatforms.includes(c.platform)
    ) {
        const { confirm } = await inquirerPrompt({
            type: 'confirm',
            message: `Looks like platform ${
                c.platform
            } is not supported by your project. Would you like to enable it?`
        });

        if (confirm) {
            const newPlatforms = [...configuredPlatforms, c.platform];
            updateProjectPlatforms(c, newPlatforms);
            c.buildConfig.defaults.supportedPlatforms = newPlatforms;
            await configureEntryPoints(c);
        } else {
            throw new Error('User canceled');
        }
    }

    // Check global SDKs
    await checkAndConfigureSdks(c);
    return c.platform;
};

const _isPlatformSupportedSync = (platform, resolve, reject) => {
    if (!platform) {
        if (reject) {
            reject(
                chalk.red(
                    `You didn't specify platform. make sure you add "${chalk.white.bold(
                        '-p <PLATFORM>'
                    )}" option to your command!`
                )
            );
        }
        return false;
    }
    if (!SUPPORTED_PLATFORMS.includes(platform)) {
        if (reject) {
            reject(
                chalk.red(
                    `Platform ${platform} is not supported. Use one of the following: ${chalk.white(
                        SUPPORTED_PLATFORMS.join(', ')
                    )} .`
                )
            );
        }
        return false;
    }
    if (resolve) resolve();
    return true;
};

export const isPlatformActive = (c, platform, resolve) => {
    if (!c.buildConfig || !c.buildConfig.platforms) {
        logError(
            `Looks like your appConfigFile is not configured properly! check ${chalk.white(
                c.paths.appConfig.config
            )} location.`
        );
        if (resolve) resolve();
        return false;
    }
    if (!c.buildConfig.platforms[platform]) {
        logDebug(
            `Platform ${platform} not configured for ${c.runtime.appId}. skipping.`
        );
        if (resolve) resolve();
        return false;
    }
    return true;
};
