import fs from 'node:fs/promises';
import path from 'node:path';
import * as prompts from '@inquirer/prompts';
import { logger } from '@rsbuild/core';
import { Command } from 'commander';
import { render } from 'ejs';
import { glob } from 'tinyglobby';
import { cyan, magenta } from 'yoctocolors';
import { JS_STR_FALSY_VALUES } from '../shared/index.js';
import { resolveRepo, resolveStaticConfig } from '../utils/index.js';
export const DEFAULT_PATH = 'templates/scaffolding.yaml';
const scaffoldingTemplates = {
    'product-doc': {
        repo: 'alauda-public/product-doc-guide',
    },
};
const getScaffoldings = async (repoFolder, scaffoldingPath) => {
    const scaffoldingFile = path.resolve(repoFolder, scaffoldingPath);
    try {
        const stat = await fs.stat(scaffoldingFile);
        if (stat.isFile()) {
            const { scaffolding } = await resolveStaticConfig(scaffoldingFile);
            return scaffolding;
        }
    }
    catch (err_) {
        const err = err_;
        if (err.name === 'YAMLParseError') {
            logger.error(`Failed to parse \`${magenta(scaffoldingPath)}\`: ${err.message}`);
        }
    }
};
const resolveScaffoldings = async (name, force) => {
    if (!Object.hasOwn(scaffoldingTemplates, name)) {
        logger.error(`Template \`${magenta(name)}\` not found, current available templates are: ${Object.keys(scaffoldingTemplates)
            .map((t) => cyan(`\`${t}\``))
            .join(', ')}`);
        return;
    }
    const { repo, scaffoldingPath = 'templates/scaffolding.yaml' } = scaffoldingTemplates[name];
    const repoFolder = await resolveRepo(repo, force);
    if (!repoFolder) {
        return;
    }
    const base = path.resolve(repoFolder, scaffoldingPath, '..');
    const scaffoldings = await getScaffoldings(repoFolder, scaffoldingPath);
    return { base, scaffoldings, repoFolder };
};
const handleTemplateFile = async ({ source, target, processors, parameters, writeMode = 'write', }) => {
    let content = await fs.readFile(source, 'utf-8');
    for (const processor of processors || []) {
        switch (processor.type) {
            // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
            case 'ejsTemplate': {
                content = render(content, { data: processor.data, parameters });
                break;
            }
            default: {
                // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
                throw new Error(`Unknown processor type: \`${processor.type}\``);
            }
        }
    }
    await fs.mkdir(path.dirname(target), { recursive: true });
    await fs[`${writeMode}File`](target, content);
};
export const newCommand = new Command('new')
    .description('Generate scaffolding from templates')
    .argument('[template]', 'Scaffolding template name, format: `[name][:type]`')
    .action(async function (template = '') {
    let [name, type] = template.split(':');
    if (!name) {
        name = 'product-doc';
    }
    const { force } = this.optsWithGlobals();
    const { base, repoFolder, scaffoldings } = (await resolveScaffoldings(name, force)) || {};
    if (!scaffoldings) {
        if (repoFolder) {
            logger.error(`Unable to resolve any scaffoldings, if you are sure the template \`${magenta(name)}\` exists, try to use the \`--force\` option, or event remove the local cache at ${magenta(repoFolder)} and try again`);
        }
        return;
    }
    if (!type) {
        type = await prompts.select({
            message: '请选择脚手架类型',
            choices: scaffoldings.map((s) => ({
                value: s.name,
                description: s.description,
            })),
        });
    }
    const scaffolding = scaffoldings.find((s) => s.name === type);
    if (!scaffolding) {
        logger.error(`Scaffolding \`${magenta(type)}\` not found, current available scaffoldings are: ${scaffoldings
            .map((s) => cyan(`\`${s.name}\``))
            .join(', ')}`);
        return;
    }
    const parameters = {};
    for (const param of scaffolding.parameters || []) {
        parameters[param.name] =
            await // @ts-expect-error -- no idea how to fix this
             prompts[param.type](param.options);
    }
    logger.start('Generating scaffolding...');
    for (const layout of scaffolding.layout || []) {
        const source = path.resolve(base, render(layout.source, { parameters }));
        const target = path.resolve(render(layout.target, { parameters }));
        const when = layout.when && render(layout.when, { parameters });
        if (when != null && JS_STR_FALSY_VALUES.has(when)) {
            continue;
        }
        switch (layout.type) {
            case 'file': {
                await handleTemplateFile({
                    source,
                    target,
                    parameters,
                    processors: layout.processors,
                    writeMode: layout.writeMode,
                });
                break;
            }
            case 'folder': {
                const dirents = await fs.readdir(source, {
                    recursive: true,
                    withFileTypes: true,
                });
                const files = new Set(dirents
                    .filter((d) => d.isFile())
                    .map((d) => path.relative(source, path.resolve(d.parentPath ||
                    // eslint-disable-next-line @typescript-eslint/no-deprecated
                    d.path, d.name))));
                for (const matcher of layout.matchers || []) {
                    const matched = await glob(matcher.match.map((m) => render(m, { parameters })), { cwd: source });
                    for (const file of matched) {
                        files.delete(file);
                        await handleTemplateFile({
                            source: path.resolve(source, file),
                            target: path.resolve(target, file.endsWith('.ejs') ? file.slice(0, -4) : file),
                            parameters,
                            processors: matcher.processors,
                            writeMode: matcher.writeMode,
                        });
                    }
                }
                for (const file of files) {
                    await handleTemplateFile({
                        source: path.resolve(source, file),
                        target: path.resolve(target, file),
                    });
                }
                break;
            }
        }
    }
    logger.success('Scaffolding generated successfully!');
});
