import { select, confirm } from '@inquirer/prompts';
import { promises as fs } from 'fs';
import { dirname } from 'path';
import { pathExists, remove } from 'fs-extra';
import { cyan, green, bold, yellow } from 'colorette';
import log from '../../lib/log.js';
import findFileInCurrentPath from '../fs/findFileInCurrentPath.js';
import safeResolveWithin from '../fs/safeResolveWithin.js';
import { EMULSIFY_PROJECT_CONFIG_FILE } from '../../lib/constants.js';
import deriveComponentNames from '../deriveComponentNames.js';
import resolveComponentTemplate from './resolveComponentTemplate.js';
import { buildScssTemplate, buildSdcJsTemplate, buildSdcMetadataTemplate, buildSdcStoriesTemplate, buildStoriesTemplate, buildTwigTemplate, buildYmlTemplate, } from './componentTemplates/index.js';
const COMPONENT_FORMAT_CHOICES = [
    {
        name: `${bold('Default')} (Standard Emulsify component)`,
        value: 'default',
    },
    {
        name: `${bold('SDC')} (Single Directory Component for Drupal)`,
        value: 'sdc',
    },
];
const COMPONENT_FORMATS = ['default', 'sdc'];
function getComponentFormat(format) {
    const normalizedFormat = format.toLowerCase();
    if (!COMPONENT_FORMATS.includes(normalizedFormat)) {
        throw new Error(`Invalid component format "${format}". Supported formats are: default, sdc.`);
    }
    return normalizedFormat;
}
export default async function generateComponent(variant, componentName, options = {}) {
    const { filename, className, camelName, snakeName, humanName } = deriveComponentNames(componentName);
    const canPrompt = process.stdin.isTTY === true;
    const providedFormat = options.format
        ? getComponentFormat(options.format)
        : undefined;
    let directory = options.directory || '';
    const path = findFileInCurrentPath(EMULSIFY_PROJECT_CONFIG_FILE);
    if (!path) {
        throw new Error('Unable to find an Emulsify project to create the component into.');
    }
    const projectRoot = dirname(path);
    const format = providedFormat
        ? providedFormat
        : canPrompt
            ? await select({
                message: cyan('Choose the component format:'),
                choices: COMPONENT_FORMAT_CHOICES,
            })
            : (() => {
                throw new Error('Component format is required in non-interactive mode. Pass --format default or --format sdc.');
            })();
    if (!directory) {
        if (!canPrompt) {
            throw new Error('Component directory is required in non-interactive mode. Pass --directory <directory>.');
        }
        directory = await select({
            message: cyan('Choose a directory for the new component:'),
            choices: variant.structureImplementations.map((structure) => ({
                name: structure.name,
                value: structure.name,
            })),
        });
    }
    const structure = variant.structureImplementations.find(({ name }) => name === directory);
    if (!structure) {
        throw new Error(`The structure (${directory}) specified within the component ${componentName} is invalid.`);
    }
    const parentPath = safeResolveWithin(projectRoot, structure.directory, 'Component structure directory', { allowRoot: true });
    const parentExists = await pathExists(parentPath);
    const destination = safeResolveWithin(projectRoot, [structure.directory, filename], 'Component destination');
    const componentExists = await pathExists(destination);
    const templateVars = {
        filename,
        className,
        camelName,
        snakeName,
        humanName,
        directory,
        format,
    };
    const formatLabel = format.toUpperCase();
    const sharedArtifacts = [
        {
            logicalName: 'component.twig',
            destinationName: `${filename}.twig`,
            build: () => buildTwigTemplate(filename, snakeName, className, formatLabel),
        },
        {
            logicalName: 'component.scss',
            destinationName: `${filename}.scss`,
            build: () => buildScssTemplate(className, formatLabel),
        },
    ];
    const formatArtifacts = format === 'sdc'
        ? [
            {
                logicalName: 'component.component.yml',
                destinationName: `${filename}.component.yml`,
                build: () => buildSdcMetadataTemplate(snakeName, humanName),
            },
            {
                logicalName: 'component.js',
                destinationName: `${filename}.js`,
                build: () => buildSdcJsTemplate(camelName, filename, className),
            },
            {
                logicalName: 'component.stories.js',
                destinationName: `${filename}.stories.js`,
                build: () => buildSdcStoriesTemplate(camelName, filename, snakeName, humanName, directory),
            },
        ]
        : [
            {
                logicalName: 'component.yml',
                destinationName: `${filename}.yml`,
                build: () => buildYmlTemplate(snakeName, humanName),
            },
            {
                logicalName: 'component.stories.js',
                destinationName: `${filename}.stories.js`,
                build: () => buildStoriesTemplate(camelName, filename, humanName, directory),
            },
        ];
    const artifacts = [...sharedArtifacts, ...formatArtifacts];
    const artifactDestinations = artifacts.map((artifact) => safeResolveWithin(projectRoot, [structure.directory, filename, artifact.destinationName], 'Component file destination'));
    if (options.dryRun) {
        const realRunAction = componentExists
            ? options.yes
                ? 'replace the existing component directory'
                : 'prompt before replacing the existing component directory'
            : 'create the component directory';
        const generatedFiles = artifactDestinations
            .map((filePath) => `  - ${filePath}`)
            .join('\n');
        return log('info', [
            `Dry run: component create "${filename}"`,
            `Format: ${format}`,
            `Directory: ${directory}`,
            `Structure path: ${structure.directory}`,
            `Parent directory: ${parentPath} (${parentExists ? 'exists' : 'would be created'})`,
            `Destination: ${destination}`,
            `Destination exists: ${componentExists ? 'yes' : 'no'}`,
            `Real run would: ${realRunAction}`,
            'Generated files:',
            generatedFiles,
            'No files were written, removed, or created.',
        ].join('\n'));
    }
    if (!parentExists) {
        await fs.mkdir(parentPath, { recursive: true });
    }
    if (componentExists) {
        const shouldReplace = options.yes ||
            (canPrompt
                ? await confirm({
                    message: yellow(`The component "${humanName}" already exists in ${structure.directory}. Would you like to replace it?`),
                    default: false,
                })
                : false);
        if (!shouldReplace) {
            return log('info', `Component creation canceled.`);
        }
        await remove(destination);
    }
    await fs.mkdir(destination, { recursive: true });
    for (const [index, artifact] of artifacts.entries()) {
        const templateFile = (await resolveComponentTemplate(projectRoot, format, artifact.logicalName, templateVars)) ?? artifact.build();
        const artifactDestination = artifactDestinations[index];
        await fs.writeFile(artifactDestination, templateFile);
    }
    return log('success', `${bold(green('Success!'))} The ${bold(cyan(componentName))} component (${yellow(format.toUpperCase())}) has been created in ${bold(directory)}.`);
}
