import assert from 'node:assert';
import { basename, dirname, isAbsolute, join } from 'node:path';
import { mock } from 'node:test';
import { merge, set, snakeCase } from 'lodash-es';
import { RunContext, YeomanTest, result } from 'yeoman-test';
import { globSync } from 'glob';
import EnvironmentBuilder from '../../cli/environment-builder.mjs';
import { JHIPSTER_CONFIG_DIR } from '../../generators/generator-constants.js';
import { GENERATOR_WORKSPACES } from '../../generators/generator-list.js';
import { createJHipsterLogger, normalizePathEnd, parseCreationTimestamp } from '../../generators/base/support/index.js';
import BaseGenerator from '../../generators/base/index.js';
import { getPackageRoot, getSourceRoot, isDistFolder } from '../index.js';
import { getDefaultJDLApplicationConfig } from '../command/jdl.js';
import { buildJHipster, createProgram } from '../../cli/program.mjs';
import getGenerator, { getGeneratorRelativeFolder } from './get-generator.js';
const runResult = result;
export { runResult, runResult as result };
const DEFAULT_TEST_SETTINGS = { forwardCwd: true };
const DEFAULT_TEST_OPTIONS = { skipInstall: true };
const DEFAULT_TEST_ENV_OPTIONS = { skipInstall: true, dryRun: false };
const toJHipsterNamespace = (ns) => (/^begcode[:-]/.test(ns) ? ns : `begcode:${ns}`);
const generatorsDir = getSourceRoot('generators');
const allGenerators = [
    ...globSync('*/index.{j,t}s', { cwd: generatorsDir, posix: true }).map(file => dirname(file)),
    ...globSync('*/generators/*/index.{j,t}s', { cwd: generatorsDir, posix: true }).map(file => dirname(file).replace('/generators/', ':')),
]
    .map(gen => `begcode:${gen}`)
    .sort();
const filterBootstrapGenerators = (gen) => !gen.startsWith('begcode:bootstrap-');
const composedGeneratorsToCheck = allGenerators
    .filter(filterBootstrapGenerators)
    .filter(gen => !['begcode:bootstrap', 'begcode:project-name'].includes(gen));
const defaultSharedApplication = Object.fromEntries(['CLIENT_WEBPACK_DIR'].map(key => [key, undefined]));
let defaultMockFactory;
let defaultAccumulateMockArgs;
let helpersDefaults = {};
export const resetDefaults = () => {
    helpersDefaults = {};
};
const createEnvBuilderEnvironment = (...args) => EnvironmentBuilder.createEnv(...args);
export const defineDefaults = async (defaults = {}) => {
    const { mockFactory, accumulateMockArgs, ...rest } = defaults;
    Object.assign(helpersDefaults, rest);
    if (mockFactory) {
        defaultMockFactory = mockFactory;
    }
    else if (!defaultMockFactory) {
        try {
            defaultMockFactory = (...args) => mock.fn(...args);
        }
        catch {
            throw new Error('loadMockFactory should be called before using mock');
        }
    }
    if (!defaultAccumulateMockArgs) {
        defaultAccumulateMockArgs =
            accumulateMockArgs ??
                ((mocks = {}) => Object.fromEntries(Object.entries(mocks)
                    .filter(([_name, fn]) => fn.mock)
                    .map(([name, fn]) => [name, fn.mock.calls.map(call => (call.arguments.length > 1 ? call.arguments : call.arguments[0]))])));
    }
};
const createFiles = (workspaceFolder, configuration, entities) => {
    if (!configuration.baseName) {
        throw new Error('baseName is required');
    }
    workspaceFolder = workspaceFolder ? normalizePathEnd(workspaceFolder) : workspaceFolder;
    const entityFiles = entities
        ? Object.fromEntries(entities?.map(entity => [`${workspaceFolder}${JHIPSTER_CONFIG_DIR}/${entity.name}.json`, entity]))
        : {};
    configuration = { entities: entities?.map(e => e.name), ...configuration };
    return {
        [`${workspaceFolder}.yo-rc.json`]: { 'generator-jhipster': configuration },
        ...entityFiles,
    };
};
export const createJHipsterConfigFiles = (configuration, entities) => createFiles('', configuration, entities);
export const createBlueprintFiles = (blueprintPackage, { packageJson, generator = 'test-blueprint', generatorContent, files = {} } = {}) => {
    generatorContent =
        generatorContent ??
            `export const createGenerator = async env => {
    const BaseGenerator = await env.requireGenerator('begcode:base');
    return class extends BaseGenerator {
      get [BaseGenerator.INITIALIZING]() {
        return {};
      }
    };
  };
  `;
    const generators = Array.isArray(generator) ? generator : [generator];
    return {
        [`node_modules/${blueprintPackage}/package.json`]: {
            name: blueprintPackage,
            version: '9.9.9',
            type: 'module',
            ...packageJson,
        },
        ...Object.fromEntries(generators.map(generator => [`node_modules/${blueprintPackage}/generators/${generator}/index.js`, generatorContent])),
        ...Object.fromEntries(Object.entries(files).map(([file, content]) => [`node_modules/${blueprintPackage}/${file}`, content])),
    };
};
class JHipsterRunContext extends RunContext {
    sharedSource;
    sharedData;
    sharedApplication;
    sharedControl;
    workspaceApplications = [];
    commonWorkspacesConfig;
    generateApplicationsSet = false;
    withOptions(options) {
        return super.withOptions(options);
    }
    withJHipsterConfig(configuration, entities) {
        return this.withFiles(createFiles('', { baseName: 'jhipster', creationTimestamp: parseCreationTimestamp('2020-01-01'), ...configuration }, entities));
    }
    withSkipWritingPriorities() {
        return this.withOptions({ skipPriorities: ['writing', 'postWriting', 'writingEntities', 'postWritingEntities'] });
    }
    withWorkspacesCommonConfig(commonWorkspacesConfig) {
        if (this.workspaceApplications.length > 0) {
            throw new Error('Cannot be called after withWorkspaceApplication');
        }
        this.commonWorkspacesConfig = { ...this.commonWorkspacesConfig, ...commonWorkspacesConfig };
        return this;
    }
    withWorkspaceApplicationAtFolder(workspaceFolder, configuration, entities) {
        if (this.generateApplicationsSet) {
            throw new Error('Cannot be called after withWorkspaceApplication');
        }
        this.workspaceApplications.push(workspaceFolder);
        return this.withFiles(createFiles(workspaceFolder, { ...configuration, ...this.commonWorkspacesConfig }, entities));
    }
    withWorkspaceApplication(configuration, entities) {
        return this.withWorkspaceApplicationAtFolder(configuration.baseName, configuration, entities);
    }
    withWorkspacesSamples(...appNames) {
        return this.onBeforePrepare(async () => {
            try {
                const { default: deploymentTestSamples } = await import('./support/deployment-samples.js');
                for (const appName of appNames) {
                    const application = deploymentTestSamples[appName];
                    if (!application) {
                        throw new Error(`Application ${appName} not found`);
                    }
                    this.withWorkspaceApplicationAtFolder(appName, deploymentTestSamples[appName]);
                }
            }
            catch {
                throw new Error('Samples are currently not available to blueprint testing.');
            }
        });
    }
    withGenerateWorkspaceApplications(generateWorkspaces = false) {
        return this.onBeforePrepare(() => {
            this.generateApplicationsSet = true;
            this.withOptions({ generateApplications: true, workspacesFolders: this.workspaceApplications, workspaces: generateWorkspaces });
        });
    }
    withJHipsterLookup() {
        return this.withJHipsterGenerators();
    }
    withBlueprintConfig(config) {
        const { blueprint } = helpersDefaults;
        assert(blueprint, 'Blueprint must be configured');
        return this.withYoRcConfig(blueprint, config);
    }
    withConfiguredBlueprint() {
        const { blueprint, blueprintPackagePath, entrypointGenerator } = helpersDefaults;
        assert(blueprintPackagePath, 'Blueprint generators package path must be configured');
        assert(blueprint, 'Blueprint must be configured');
        if (entrypointGenerator) {
            this.withOptions({ entrypointGenerator });
        }
        return this.withLookups([
            {
                packagePaths: [blueprintPackagePath],
                lookups: [`generators`, `generators/*/generators`],
                customizeNamespace: ns => ns?.replaceAll(':generators:', ':'),
            },
        ]).withOptions({
            blueprint: [blueprint],
        });
    }
    withParentBlueprintLookup(lookups = ['generators', 'generators/*/generators']) {
        const packageRootParent = join(getPackageRoot(), '..');
        if (basename(packageRootParent) === 'node_modules') {
            this.withLookups([{ packagePaths: [join(packageRootParent, '..')], lookups }]);
        }
        else {
            this.withLookups([{ packagePaths: [process.cwd()], lookups }]);
        }
        return this;
    }
    withFakeTestBlueprint(blueprintPackage, { packageJson, generator = 'test-blueprint' } = {}) {
        return this.withFiles(createBlueprintFiles(blueprintPackage, { packageJson, generator }))
            .withLookups({ localOnly: true })
            .commitFiles();
    }
    withMockedSource(options = {}) {
        const { except = [] } = options;
        this.sharedSource = new Proxy({}, {
            get(target, name) {
                if (!target[name]) {
                    target[name] = defaultMockFactory();
                }
                return target[name];
            },
            set(target, property, value) {
                if (except.includes(property)) {
                    if (target[property]) {
                        throw new Error(`Cannot set ${property} mock`);
                    }
                    target[property] = defaultMockFactory(value);
                }
                return true;
            },
        });
        return this.onBeforePrepare(() => defineDefaults()).withSharedData({ sharedSource: this.sharedSource });
    }
    withControl(sharedControl) {
        this.sharedControl = this.sharedControl ?? {};
        Object.assign(this.sharedControl, sharedControl);
        return this.withSharedData({ control: this.sharedControl });
    }
    withSharedApplication(sharedApplication) {
        this.sharedApplication = this.sharedApplication ?? { ...defaultSharedApplication };
        merge(this.sharedApplication, sharedApplication);
        return this.withSharedData({ sharedApplication: this.sharedApplication });
    }
    withMockedNodeDependencies() {
        return this.withSharedApplication({
            nodeDependencies: new Proxy({}, { get: (_target, prop) => `${snakeCase(prop.toString()).toUpperCase()}_VERSION` }),
        });
    }
    withMockedJHipsterGenerators(options = {}) {
        const optionsObj = Array.isArray(options) ? { except: options } : options;
        const { except = [], filter = filterBootstrapGenerators } = optionsObj;
        const jhipsterExceptList = except.map(toJHipsterNamespace);
        return this.withMockedGenerators(allGenerators.filter(filter).filter(gen => !jhipsterExceptList.includes(gen) && this.Generator !== gen));
    }
    withJHipsterGenerators(options = {}) {
        const { useDefaultMocks, actualGeneratorsList = [], useMock = useDefaultMocks ? filterBootstrapGenerators : () => false } = options;
        const jhipsterExceptList = actualGeneratorsList.map(toJHipsterNamespace);
        const mockedGenerators = allGenerators
            .filter(useMock)
            .filter(gen => !jhipsterExceptList.includes(gen) && this.Generator !== gen);
        const actualGenerators = allGenerators.filter(gen => !mockedGenerators.includes(gen));
        const prefix = isDistFolder() ? 'dist/' : '';
        const filePatterns = actualGenerators.map(ns => getGeneratorRelativeFolder(ns)).map(path => `${prefix}${path}/index.{j,t}s`);
        return this.withMockedGenerators(mockedGenerators).withLookups({
            packagePaths: [getPackageRoot()],
            lookups: [`${prefix}generators`, `${prefix}generators/*/generators`],
            filePatterns,
            customizeNamespace: ns => ns?.replaceAll(':generators:', ':'),
        });
    }
    withGradleBuildTool() {
        return this.withFiles({
            'build.gradle': `
dependencies {
// jhipster-needle-gradle-dependency
}
plugins {
// jhipster-needle-gradle-plugins
}
`,
        }).withJHipsterConfig({ buildTool: 'gradle' });
    }
    withSharedData(sharedData) {
        if (!this.sharedData) {
            const applicationId = 'test-application';
            this.sharedData = { ...sharedData };
            set(this.envOptions, `sharedOptions.sharedData.applications.${applicationId}`, this.sharedData);
            return this.withOptions({
                applicationId,
            });
        }
        Object.assign(this.sharedData, sharedData);
        return this;
    }
    async run() {
        const runResult = (await super.run());
        if (this.sharedSource) {
            const cleanupArguments = (args) => args.map(arg => {
                if (Array.isArray(arg)) {
                    return cleanupArguments(arg);
                }
                const { application, relationships, entities, entity } = arg;
                if (application) {
                    arg = { ...arg, application: `Application[${application.baseName}]` };
                }
                if (entity) {
                    arg = { ...arg, entity: `Entity[${entity.name}]` };
                }
                for (const key of ['control', 'entities', 'source'].filter(key => arg[key])) {
                    arg = { ...arg, [key]: `TaskParameter[${key}]` };
                }
                if (relationships) {
                    arg = { ...arg, relationships: relationships.map(rel => `Relationship[${rel.relationshipName}]`) };
                }
                if (entities) {
                    arg = { ...arg, entities: entities.map(entity => `Entity[${entity.name}]`) };
                }
                return arg;
            });
            runResult.sourceCallsArg = Object.fromEntries(Object.entries(defaultAccumulateMockArgs(this.sharedSource)).map(([name, args]) => [name, cleanupArguments(args)]));
        }
        runResult.composedMockedGenerators = composedGeneratorsToCheck.filter(gen => runResult.mockedGenerators[gen]?.mock.callCount() > 0);
        runResult.createJHipster = (ns, options) => {
            ns = toJHipsterNamespace(ns);
            const context = runResult.create(ns);
            return context.withJHipsterGenerators(options);
        };
        return runResult;
    }
}
class JHipsterTest extends YeomanTest {
    constructor() {
        super();
        this.adapterOptions = { log: createJHipsterLogger() };
    }
    run(GeneratorOrNamespace, settings, envOptions) {
        return super
            .run(GeneratorOrNamespace, settings, envOptions)
            .withOptions({
            jdlDefinition: getDefaultJDLApplicationConfig(),
        })
            .withAdapterOptions({ log: createJHipsterLogger() });
    }
    runJHipster(jhipsterGenerator, settings, envOptions) {
        if (!isAbsolute(jhipsterGenerator) && !jhipsterGenerator.startsWith('@')) {
            jhipsterGenerator = toJHipsterNamespace(jhipsterGenerator);
        }
        const isRunJHipster = (opt) => opt === undefined || 'actualGeneratorsList' in opt || 'useMock' in opt || 'useDefaultMocks' in opt || 'useEnvironmentBuilder' in opt;
        if (isRunJHipster(settings)) {
            const { useEnvironmentBuilder, ...otherOptions } = settings ?? {};
            if (useEnvironmentBuilder) {
                return this.run(jhipsterGenerator, undefined, {
                    createEnv: async (...args) => {
                        const builder = await EnvironmentBuilder.create(...args).prepare();
                        return builder.getEnvironment();
                    },
                });
            }
            return this.run(jhipsterGenerator).withJHipsterGenerators(otherOptions);
        }
        return this.run(getGenerator(jhipsterGenerator), settings, envOptions).withJHipsterGenerators();
    }
    runCli(command, options = {}) {
        const { useEnvironmentBuilder, ...buildJHipsterOptions } = options;
        const context = this.run(this.createDummyGenerator(), { namespace: 'non-used-dummy:generator' }, useEnvironmentBuilder ? { createEnv: createEnvBuilderEnvironment } : undefined);
        if (!useEnvironmentBuilder) {
            context.withJHipsterGenerators();
        }
        return context.withEnvironmentRun(async function (env) {
            const program = createProgram().exitOverride();
            await buildJHipster({ program, env: env, silent: true, ...buildJHipsterOptions });
            await program.parseAsync(['jhipster', 'jhipster', ...(Array.isArray(command) ? command : command.split(' '))]);
            this.generator = env.rootGenerator();
        });
    }
    runJHipsterInApplication(jhipsterGenerator, settings, envOptions) {
        const context = runResult.create(getGenerator(jhipsterGenerator), settings, envOptions);
        return context.withJHipsterGenerators();
    }
    runJDL(jdl, settings, envOptions) {
        return this.runJHipster('jdl', settings, envOptions).withOptions({ inline: jdl });
    }
    runJDLInApplication(jdl, settings, envOptions) {
        return this.runJHipsterInApplication('jdl', settings, envOptions).withOptions({ inline: jdl });
    }
    runTestBlueprintGenerator() {
        const blueprintNS = 'begcode:test-blueprint';
        class BlueprintedGenerator extends BaseGenerator {
            async beforeQueue() {
                if (!this.fromBlueprint) {
                    await this.composeWithBlueprints();
                }
            }
            rootGeneratorName() {
                return 'generator-begcode';
            }
            get [BaseGenerator.INITIALIZING]() {
                return {};
            }
        }
        return this.runJHipster(blueprintNS).withGenerators([[BlueprintedGenerator, { namespace: blueprintNS }]]);
    }
    create(GeneratorOrNamespace, settings, envOptions) {
        return super.create(GeneratorOrNamespace, settings, envOptions);
    }
    createJHipster(jhipsterGenerator, settings, envOptions) {
        return this.create(getGenerator(jhipsterGenerator), settings, envOptions);
    }
    generateDeploymentWorkspaces(commonConfig) {
        return this.runJHipster(GENERATOR_WORKSPACES)
            .withWorkspacesCommonConfig(commonConfig ?? {})
            .withOptions({
            generateWorkspaces: true,
            generateWith: 'docker',
            skipPriorities: ['prompting'],
        });
    }
}
export function createTestHelpers(options = {}) {
    const { environmentOptions = {} } = options;
    const sharedOptions = {
        ...DEFAULT_TEST_OPTIONS,
        ...environmentOptions.sharedOptions,
    };
    const helper = new JHipsterTest();
    helper.settings = { ...DEFAULT_TEST_SETTINGS, ...options.settings };
    helper.environmentOptions = { ...DEFAULT_TEST_ENV_OPTIONS, ...environmentOptions, sharedOptions };
    helper.generatorOptions = { ...DEFAULT_TEST_OPTIONS, ...options.generatorOptions };
    helper.getRunContextType = () => JHipsterRunContext;
    return helper;
}
const commonTestOptions = {
    reproducible: true,
    skipChecks: true,
    reproducibleTests: true,
    noInsight: true,
    useVersionPlaceholders: true,
    fakeKeytool: true,
    skipGit: true,
    skipEslint: true,
    skipForks: true,
};
export const basicHelpers = createTestHelpers({ generatorOptions: { ...commonTestOptions } });
export const defaultHelpers = createTestHelpers({
    generatorOptions: { skipPrettier: true, ...commonTestOptions },
    environmentOptions: { dryRun: true },
});
export const skipPrettierHelpers = createTestHelpers({ generatorOptions: { skipPrettier: true, ...commonTestOptions } });
export const dryRunHelpers = createTestHelpers({
    generatorOptions: { ...commonTestOptions },
    environmentOptions: { dryRun: true },
});
