import { pathExists } from 'fs-extra';
import { confirm } from '@inquirer/prompts';
import log from '../lib/log.js';
import { EMULSIFY_PROJECT_CONFIG_FILE } from '../lib/constants.js';
import CliError from '../lib/CliError.js';
import installComponentFromCache, { getComponentDestination, } from '../util/project/installComponentFromCache.js';
import buildComponentDependencyList from '../util/project/buildComponentDependencyList.js';
import catchLater from '../util/catchLater.js';
import findFileInCurrentPath from '../util/fs/findFileInCurrentPath.js';
import { withEmulsifySystem } from './hofs/withEmulsifySystem.js';
function getDryRunInstallAction(exists, force) {
    if (!exists) {
        return 'copy component';
    }
    if (force) {
        return 'replace existing destination';
    }
    return 'prompt before replacing or skipping';
}
async function buildComponentInstallPlan(variant, componentNames, rootComponentName, force) {
    const projectConfigPath = findFileInCurrentPath(EMULSIFY_PROJECT_CONFIG_FILE);
    if (!projectConfigPath) {
        throw new CliError('Unable to find an Emulsify project to preview component installation into.');
    }
    const plan = [];
    for (const componentName of componentNames) {
        const destination = getComponentDestination(variant, componentName, projectConfigPath);
        const exists = await pathExists(destination);
        plan.push({
            name: componentName,
            isDependency: Boolean(rootComponentName && componentName !== rootComponentName),
            destination,
            exists,
            action: getDryRunInstallAction(exists, force),
        });
    }
    return plan;
}
function logComponentInstallDryRun(targetLabel, dependencies, plan) {
    const dependencyList = dependencies.length > 0
        ? dependencies.map((dependency) => `  - ${dependency}`).join('\n')
        : '  - none';
    const plannedInstalls = plan
        .map((item) => [
        `  - ${item.name}${item.isDependency ? ` (dependency of "${targetLabel}")` : ''}`,
        `    Destination: ${item.destination}`,
        `    Destination exists: ${item.exists ? 'yes' : 'no'}`,
        `    Real run would: ${item.action}`,
    ].join('\n'))
        .join('\n');
    log('info', [
        `Dry run: component install "${targetLabel}"`,
        'Dependencies:',
        dependencyList,
        'Planned component installs:',
        plannedInstalls,
        'No files were copied, removed, or overwritten.',
    ].join('\n'));
}
export default async function componentInstall(name, { force, all, dryRun }) {
    if (!name && !all) {
        throw new CliError('Please specify a component to install, or pass --all to install all available components.');
    }
    const { systemConf, variantConf } = await withEmulsifySystem('install components');
    const components = [];
    if (all) {
        const componentNames = variantConf.components.map((component) => component.name);
        if (dryRun) {
            const plan = await buildComponentInstallPlan(variantConf, componentNames, undefined, true);
            logComponentInstallDryRun('all components', [], plan);
            return;
        }
        components.push(...componentNames.map((component) => [
            component,
            false,
            catchLater(installComponentFromCache(systemConf, variantConf, component, true)),
        ]));
    }
    else {
        const componentsWithDependencies = buildComponentDependencyList(variantConf.components, name);
        if (componentsWithDependencies.length === 0) {
            throw new CliError(`Cannot find the definition for component "${name}".\n\nRun "emulsify component list" to see the full list.`);
        }
        if (dryRun) {
            const dependencies = componentsWithDependencies.filter((componentName) => componentName !== name);
            const plan = await buildComponentInstallPlan(variantConf, componentsWithDependencies, name, Boolean(force));
            logComponentInstallDryRun(name, dependencies, plan);
            return;
        }
        const projectConfigPath = findFileInCurrentPath(EMULSIFY_PROJECT_CONFIG_FILE);
        for (const componentName of componentsWithDependencies) {
            const isDependency = componentName !== name;
            let currentForce = force;
            const destination = projectConfigPath
                ? getComponentDestination(variantConf, componentName, projectConfigPath)
                : undefined;
            if (destination && (await pathExists(destination)) && !force) {
                const dependencyNote = isDependency ? ` (required by "${name}")` : '';
                const result = await confirm({
                    message: `The component "${componentName}"${dependencyNote} already exists. Would you like to replace it?`,
                    default: false,
                });
                if (result) {
                    currentForce = true;
                }
                else {
                    log('info', `Skipping installation of component "${componentName}".`);
                    continue;
                }
            }
            components.push([
                componentName,
                isDependency,
                catchLater(installComponentFromCache(systemConf, variantConf, componentName, currentForce)),
            ]);
        }
    }
    const installedDeps = [];
    const failedDeps = [];
    for (const [cname, isDependency, promise] of components) {
        try {
            await promise;
            if (isDependency) {
                installedDeps.push(cname);
            }
            else {
                log('success', `Success! The ${cname} component has been added to your project.`);
            }
        }
        catch (e) {
            const msg = e instanceof Error ? e.message : String(e);
            if (e instanceof Error && components.find(([c]) => c === cname)?.[1]) {
                failedDeps.push(cname);
            }
            else {
                log('warn', `Unable to install ${cname}: ${msg}`);
            }
        }
    }
    if (installedDeps.length > 0) {
        const depList = installedDeps.map((d) => `  → ${d}`).join('\n');
        log('info', `The following dependencies were also installed:\n${depList}`);
    }
    if (failedDeps.length > 0) {
        const failList = failedDeps.map((d) => `  → ${d}`).join('\n');
        log('warn', `The following dependencies could not be installed:\n${failList}`);
    }
}
