"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
/*
 * Copyright (c) 2019, salesforce.com, inc.
 * All rights reserved.
 * Licensed under the BSD 3-Clause license.
 * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
 */
const path = require("path");
const utils_1 = require("../utils");
const uiBundleTemplateUtils_1 = require("../utils/uiBundleTemplateUtils");
const baseGenerator_1 = require("./baseGenerator");
const VALID_PROJECT_TEMPLATES = [
    'standard',
    'empty',
    'analytics',
    'reactinternalapp',
    'reactexternalapp',
    'agent',
    'nativemobile',
];
const GITIGNORE = 'gitignore';
const HUSKY_FOLDER = '.husky';
const huskyhookarray = ['pre-commit'];
const vscodearray = ['extensions', 'launch', 'settings'];
const standardfolderarray = [
    'applications',
    'aura',
    'classes',
    'contentassets',
    'flexipages',
    'layouts',
    'lwc',
    'objects',
    'permissionsets',
    'staticresources',
    'tabs',
    'triggers',
];
const filestocopy = [
    '.forceignore',
    GITIGNORE,
    '.prettierignore',
    '.prettierrc',
    'jest.config.js',
    'package.json',
];
const agentFilesToCopy = [
    '.forceignore',
    GITIGNORE,
    '.prettierignore',
    '.prettierrc',
    'package.json',
];
const emptyfolderarray = ['aura', 'lwc'];
const analyticsfolderarray = ['aura', 'classes', 'lwc', 'waveTemplates'];
const analyticsVscodeExt = 'salesforce.analyticsdx-vscode';
const agentfolderarray = [
    'aiAuthoringBundles',
    'bots',
    'classes',
    'flows',
    'genAiPlannerBundles',
    'genAiPromptTemplates',
    'permissionsetgroups',
    'permissionsets',
];
const agentMetadataMap = [
    {
        src: 'aab/Local_Info_Agent.bundle-meta.xml',
        destDir: 'aiAuthoringBundles/Local_Info_Agent',
    },
    {
        src: 'aab/Local_Info_Agent.agent',
        destDir: 'aiAuthoringBundles/Local_Info_Agent',
    },
    { src: 'apex/CheckWeather.cls', destDir: 'classes' },
    { src: 'apex/CheckWeather.cls-meta.xml', destDir: 'classes' },
    { src: 'apex/CurrentDate.cls', destDir: 'classes' },
    { src: 'apex/CurrentDate.cls-meta.xml', destDir: 'classes' },
    { src: 'apex/CurrentDateTest.cls', destDir: 'classes' },
    { src: 'apex/CurrentDateTest.cls-meta.xml', destDir: 'classes' },
    { src: 'apex/WeatherService.cls', destDir: 'classes' },
    { src: 'apex/WeatherService.cls-meta.xml', destDir: 'classes' },
    { src: 'apex/WeatherServiceTest.cls', destDir: 'classes' },
    { src: 'apex/WeatherServiceTest.cls-meta.xml', destDir: 'classes' },
    { src: 'flow/Get_Resort_Hours.flow-meta.xml', destDir: 'flows' },
    {
        src: 'gapt/Get_Event_Info.genAiPromptTemplate-meta.xml',
        destDir: 'genAiPromptTemplates',
    },
    { src: 'ps/Resort_Agent.permissionset-meta.xml', destDir: 'permissionsets' },
    { src: 'ps/Resort_Admin.permissionset-meta.xml', destDir: 'permissionsets' },
    {
        src: 'psg/AFDX_Agent_Perms.permissionsetgroup-meta.xml',
        destDir: 'permissionsetgroups',
    },
    {
        src: 'psg/AFDX_User_Perms.permissionsetgroup-meta.xml',
        destDir: 'permissionsetgroups',
    },
];
class ProjectGenerator extends baseGenerator_1.BaseGenerator {
    constructor(options, context, cwd) {
        super(options, context, cwd);
        this.sourceRootWithPartialPath('project');
    }
    extendJSON(filepath, replacer) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const originalContent = JSON.parse(yield this._fs.promises.readFile(filepath, 'utf8').catch(() => '{}'));
            const newContent = JSON.stringify(originalContent, replacer, 2);
            yield this._fs.promises.writeFile(filepath, newContent);
        });
    }
    /**
     * Returns template path for primary; if it doesn't exist, returns fallback path.
     * Used so reactinternalapp/reactexternalapp can omit shared files and fall back to standard.
     */
    templatePathWithFallback(primary, fallback) {
        const primaryPath = this.templatePath(primary);
        return this._fs.existsSync(primaryPath)
            ? primaryPath
            : this.templatePath(fallback);
    }
    /**
     * Returns the appropriate template path based on whether TypeScript is enabled.
     * TypeScript-specific templates are organized in a typescript/ subdirectory.
     * Only files that have TypeScript-specific variants are looked up there.
     */
    getTemplatePath(file, isTypeScript) {
        // Files that have TypeScript-specific variants
        const tsSpecificFiles = [
            'package.json',
            '.forceignore',
            GITIGNORE,
            'project.eslint.config.js',
            'settings.json'
        ];
        const hasTypescriptVariant = tsSpecificFiles.includes(file);
        const subdir = isTypeScript && hasTypescriptVariant ? 'typescript' : '';
        return this.templatePath(subdir, file);
    }
    /**
     * Generates TypeScript configuration file if TypeScript is enabled.
     */
    generateTypeScriptConfig(isTypeScript, projectname, defaultpackagedir) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            if (isTypeScript) {
                yield this.render(this.templatePath('tsconfig.json'), this.destinationPath(path.join(this.outputdir, projectname, 'tsconfig.json')), { defaultpackagedir });
            }
        });
    }
    /**
     * Generates ESLint configuration file with appropriate template for JS or TS.
     */
    generateEslintConfig(isTypeScript, projectname) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            yield this.render(this.getTemplatePath('project.eslint.config.js', isTypeScript), this.destinationPath(path.join(this.outputdir, projectname, 'eslint.config.js')), {});
        });
    }
    /**
     * Generates VSCode settings with TypeScript-specific configuration if needed.
     */
    generateVSCodeSettings(isTypeScript, projectname) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            for (const file of vscodearray) {
                const templateFile = `${file}.json`;
                // Use TypeScript directory for settings file when TypeScript is enabled
                const templatePath = isTypeScript && file === 'settings'
                    ? this.getTemplatePath(templateFile, true)
                    : this.templatePath(templateFile);
                yield this.render(templatePath, this.destinationPath(path.join(this.outputdir, projectname, '.vscode', `${file}.json`)), {});
            }
        });
    }
    /**
     * Copies project root level files with TypeScript-specific variants when applicable.
     */
    copyProjectFiles(isTypeScript, projectname) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            for (const file of filestocopy) {
                const out = file === GITIGNORE ? `.${file}` : file;
                yield this.render(this.getTemplatePath(file, isTypeScript), this.destinationPath(path.join(this.outputdir, projectname, out)), {});
            }
        });
    }
    validateOptions() {
        utils_1.CreateUtil.checkInputs(this.options.template);
        if (!VALID_PROJECT_TEMPLATES.includes(this.options.template)) {
            throw new Error(`Invalid project template: ${this.options.template}. Valid options: ${VALID_PROJECT_TEMPLATES.join(', ')}`);
        }
        // Validate lwcLanguage if provided
        if (this.options.lwcLanguage &&
            !['javascript', 'typescript'].includes(this.options.lwcLanguage)) {
            throw new Error(`Invalid lwcLanguage value: '${this.options.lwcLanguage}'. Must be 'javascript' or 'typescript'.`);
        }
    }
    generate() {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            // Re-apply source root so customTemplatesRootPath (set in run()) is used when provided
            this.sourceRootWithPartialPath('project');
            const { projectname, template, defaultpackagedir, manifest, ns, loginurl, lwcLanguage, } = this.options;
            const isTypeScript = lwcLanguage === 'typescript';
            const folderlayout = [
                this.outputdir,
                projectname,
                defaultpackagedir,
                'main',
                'default',
            ];
            const scratchDefFile = `${template}/ScratchDef.json`;
            const manifestFile = `${template}/Manifest.xml`;
            const soqlQueryFile = 'account.soql';
            const anonApexFile = 'hello.apex';
            yield this.render(this.templatePathWithFallback(scratchDefFile, 'standard/ScratchDef.json'), this.destinationPath(path.join(this.outputdir, projectname, 'config', 'project-scratch-def.json')), { company: (process.env.USER || 'Demo') + ' company' });
            yield this.render(this.templatePathWithFallback(`${template}/README.md`, 'standard/README.md'), this.destinationPath(path.join(this.outputdir, projectname, 'README.md')), { projectname });
            yield this.render(this.templatePath('sfdx-project.json'), this.destinationPath(path.join(this.outputdir, projectname, 'sfdx-project.json')), {
                defaultpackagedir,
                namespace: ns,
                loginurl,
                apiversion: this.apiversion,
                name: projectname,
                lwcLanguage,
            });
            if (manifest === true) {
                yield this.render(this.templatePathWithFallback(manifestFile, 'standard/Manifest.xml'), this.destinationPath(path.join(this.outputdir, projectname, 'manifest', 'package.xml')), { apiversion: this.apiversion });
            }
            if (template === 'standard') {
                yield this.makeEmptyFolders(folderlayout, standardfolderarray);
                // Add Husky directory and hooks
                this._createHuskyConfig(path.join(this.outputdir, projectname));
                // VSCode config files
                yield this.generateVSCodeSettings(isTypeScript, projectname);
                // ESLint config
                yield this.generateEslintConfig(isTypeScript, projectname);
                // SOQL sample file
                yield this.render(this.templatePath(path.join(template, soqlQueryFile)), this.destinationPath(path.join(this.outputdir, projectname, 'scripts', 'soql', soqlQueryFile)), {});
                // Apex sample script
                yield this.render(this.templatePath(path.join(template, anonApexFile)), this.destinationPath(path.join(this.outputdir, projectname, 'scripts', 'apex', anonApexFile)), {});
                // Copy project root level files
                yield this.copyProjectFiles(isTypeScript, projectname);
                // Generate TypeScript configuration files if TypeScript is selected
                yield this.generateTypeScriptConfig(isTypeScript, projectname, defaultpackagedir);
            }
            if (template === 'empty') {
                yield this.makeEmptyFolders(folderlayout, emptyfolderarray);
                // For TypeScript projects, generate full toolchain
                if (isTypeScript) {
                    // Add Husky directory and hooks
                    this._createHuskyConfig(path.join(this.outputdir, projectname));
                    // VSCode config files
                    yield this.generateVSCodeSettings(isTypeScript, projectname);
                    // ESLint config
                    yield this.generateEslintConfig(isTypeScript, projectname);
                    // Copy project root level files (includes package.json, .gitignore, etc.)
                    yield this.copyProjectFiles(isTypeScript, projectname);
                    // Generate TypeScript configuration
                    yield this.generateTypeScriptConfig(isTypeScript, projectname, defaultpackagedir);
                }
                else {
                    // For JavaScript projects, just copy .forceignore
                    yield this.render(this.templatePath('.forceignore'), this.destinationPath(path.join(this.outputdir, projectname, '.forceignore')), {});
                }
            }
            if (template === 'nativemobile') {
                yield this.makeEmptyFolders(folderlayout, emptyfolderarray);
                yield this.render(this.templatePath('.forceignore'), this.destinationPath(path.join(this.outputdir, projectname, '.forceignore')), {});
                // Derive a camelCase app name from the project name for CAMA metadata
                const appName = projectname.charAt(0).toLowerCase() +
                    projectname.slice(1).replace(/[^a-zA-Z0-9]/g, '');
                // Human-readable label: insert spaces before uppercase runs
                const appLabel = projectname
                    .replace(/([a-z])([A-Z])/g, '$1 $2')
                    .replace(/[_-]/g, ' ')
                    .trim();
                const camaData = { appName, appLabel, projectname };
                const ecBase = path.join(...folderlayout, 'digitalExperiences', 'experiencecontainer', appName);
                // DigitalExperienceBundle meta XML
                yield this.render(this.templatePath('nativemobile/digitalExperience-meta.xml'), this.destinationPath(path.join(ecBase, `${appName}.digitalExperience-meta.xml`)), camaData);
                // EC Definition
                yield this.render(this.templatePath('nativemobile/ecDefinition-meta.json'), this.destinationPath(path.join(ecBase, 'experience__camaECDefinition', appName, '_meta.json')), camaData);
                yield this.render(this.templatePath('nativemobile/ecDefinition-content.json'), this.destinationPath(path.join(ecBase, 'experience__camaECDefinition', appName, 'content.json')), camaData);
                // App Metadata
                yield this.render(this.templatePath('nativemobile/appMetadata-meta.json'), this.destinationPath(path.join(ecBase, 'experience__camaAppMetadata', 'appMetadata', '_meta.json')), camaData);
                yield this.render(this.templatePath('nativemobile/appMetadata-content.json'), this.destinationPath(path.join(ecBase, 'experience__camaAppMetadata', 'appMetadata', 'content.json')), camaData);
                // Build Metadata
                yield this.render(this.templatePath('nativemobile/buildMetadata-meta.json'), this.destinationPath(path.join(ecBase, 'experience__camaBuildMetadata', 'buildMetadata', '_meta.json')), camaData);
                yield this.render(this.templatePath('nativemobile/buildMetadata-content.json'), this.destinationPath(path.join(ecBase, 'experience__camaBuildMetadata', 'buildMetadata', 'content.json')), camaData);
                // Home Screen
                yield this.render(this.templatePath('nativemobile/homeScreen-meta.json'), this.destinationPath(path.join(ecBase, 'experience__camaScreen', 'homeScreen', '_meta.json')), camaData);
                yield this.render(this.templatePath('nativemobile/homeScreen-content.json'), this.destinationPath(path.join(ecBase, 'experience__camaScreen', 'homeScreen', 'content.json')), camaData);
            }
            if (template === 'analytics') {
                yield this.makeEmptyFolders(folderlayout, analyticsfolderarray);
                // Add Husky directory and hooks
                this._createHuskyConfig(path.join(this.outputdir, projectname));
                // VSCode config files
                yield this.generateVSCodeSettings(isTypeScript, projectname);
                // add the analytics vscode extension to the recommendations
                yield this.extendJSON(path.join(this.outputdir, projectname, '.vscode', 'extensions.json'), (key, value) => {
                    if (key === 'recommendations' &&
                        Array.isArray(value) &&
                        !value.some((n) => n === analyticsVscodeExt)) {
                        value.push(analyticsVscodeExt);
                    }
                    return value;
                });
                // ESLint config
                yield this.generateEslintConfig(isTypeScript, projectname);
                // Copy project root level files
                yield this.copyProjectFiles(isTypeScript, projectname);
                // Generate TypeScript configuration files if TypeScript is selected
                yield this.generateTypeScriptConfig(isTypeScript, projectname, defaultpackagedir);
            }
            if (template === 'agent') {
                yield this.makeEmptyFolders(folderlayout, agentfolderarray);
                for (const file of vscodearray) {
                    yield this.render(this.templatePath(`${file}.json`), this.destinationPath(path.join(this.outputdir, projectname, '.vscode', `${file}.json`)), {});
                }
                for (const file of agentFilesToCopy) {
                    const out = file === GITIGNORE ? `.${file}` : file;
                    yield this.render(this.templatePathWithFallback(path.join(template, file), file), this.destinationPath(path.join(this.outputdir, projectname, out)), {});
                }
                for (const { src, destDir } of agentMetadataMap) {
                    const fileName = path.basename(src);
                    yield this.render(this.templatePath(path.join('agent', 'md', src)), this.destinationPath(path.join(...folderlayout, destDir, fileName)), {});
                }
            }
            if (uiBundleTemplateUtils_1.BUILT_IN_FULL_TEMPLATES.has(template)) {
                yield (0, uiBundleTemplateUtils_1.generateBuiltInFullTemplate)(template, projectname, {
                    templateDir: this.templatePath(template),
                    projectDir: path.join(this.outputdir, projectname),
                    defaultpackagedir,
                    ns,
                    loginurl,
                    apiversion: this.apiversion,
                    renderEjs: uiBundleTemplateUtils_1.renderEjsFile,
                    onFileCreated: (destPath) => this.registerChange(destPath),
                });
            }
        });
    }
    registerChange(destPath) {
        const relativePath = path.relative(process.cwd(), destPath);
        this.changes.created.push(relativePath);
    }
    _createHuskyConfig(projectRootDir) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const huskyDirPath = path.join(projectRootDir, HUSKY_FOLDER);
            if (!this._fs.existsSync(huskyDirPath)) {
                this._fs.mkdirSync(huskyDirPath);
            }
            for (const file of huskyhookarray) {
                yield this.render(this.templatePath(path.join(HUSKY_FOLDER, file)), this.destinationPath(path.join(huskyDirPath, file)), {});
            }
        });
    }
    makeEmptyFolders(toplevelfolders, metadatafolders) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            for (const folder of metadatafolders) {
                yield this._fs.promises.mkdir(path.join(...toplevelfolders, folder), {
                    recursive: true,
                });
            }
        });
    }
}
exports.default = ProjectGenerator;
//# sourceMappingURL=projectGenerator.js.map