import { Ajv } from 'ajv';
import addFormats from 'ajv-formats';
import { join } from 'path';
import { existsSync } from 'fs';
import { EMULSIFY_SYSTEM_CONFIG_FILE, EMULSIFY_PROJECT_HOOK_FOLDER, EMULSIFY_PROJECT_HOOK_SYSTEM_INSTALL, } from '../lib/constants.js';
import log from '../lib/log.js';
import CliError from '../lib/CliError.js';
import getAvailableSystems from '../util/system/getAvailableSystems.js';
import getGitRepoNameFromUrl from '../util/getGitRepoNameFromUrl.js';
import cloneIntoCache from '../util/cache/cloneIntoCache.js';
import getCachedItemCheckout from '../util/cache/getCachedItemCheckout.js';
import getRepositoryLatestTag from '../util/getRepositoryLatestTag.js';
import installComponentFromCache from '../util/project/installComponentFromCache.js';
import installGeneralAssetsFromCache from '../util/project/installGeneralAssetsFromCache.js';
import getJsonFromCachedFile from '../util/cache/getJsonFromCachedFile.js';
import setEmulsifyConfig from '../util/project/setEmulsifyConfig.js';
import getEmulsifyConfig from '../util/project/getEmulsifyConfig.js';
import findFileInCurrentPath from '../util/fs/findFileInCurrentPath.js';
import executeScript from '../util/fs/executeScript.js';
async function loadSchemas() {
    const [{ default: systemSchema }, { default: variantSchema }] = await Promise.all([
        import('../schemas/system.json', { with: { type: 'json' } }),
        import('../schemas/variant.json', { with: { type: 'json' } }),
    ]);
    return { systemSchema, variantSchema };
}
export async function getSystemRepoInfo(name, { repository, checkout }) {
    if (repository && checkout) {
        try {
            const repoName = getGitRepoNameFromUrl(repository);
            if (repoName) {
                return {
                    name: repoName,
                    repository,
                    checkout,
                };
            }
        }
        catch (error) {
            if (error instanceof Error) {
                throw new CliError(error.message);
            }
            else {
                throw error;
            }
        }
    }
    if (name) {
        const system = (await getAvailableSystems()).find((s) => s.name === name);
        if (system) {
            return {
                name,
                repository: system.repository,
                checkout: system.checkout,
            };
        }
    }
}
export default async function systemInstall(name, options) {
    const projectConfig = await getEmulsifyConfig();
    if (!projectConfig) {
        throw new CliError('No Emulsify project detected. You must run this command within an existing Emulsify project. For more information about creating Emulsify projects, run "emulsify init --help"');
    }
    if (projectConfig.system) {
        throw new CliError('You have already selected a system within this Emulsify project.');
    }
    const repo = await getSystemRepoInfo(name, options);
    if (!repo) {
        throw new CliError('Unable to download specified system. You must either specify a valid name of an out-of-the-box system using the --name flag, or specify a valid repository and branch/tag/commit using the --repository and --checkout flags.');
    }
    if (repo.checkout === undefined) {
        repo.checkout = await getRepositoryLatestTag(repo.repository);
    }
    await cloneIntoCache('systems', [repo.name])({
        repository: repo.repository,
        checkout: repo.checkout,
    });
    const systemConf = await getJsonFromCachedFile('systems', [repo.name], repo.checkout, EMULSIFY_SYSTEM_CONFIG_FILE);
    if (!systemConf) {
        throw new CliError(`The system you attempted to install (${repo.name}) is invalid, as it does not contain a valid configuration file.`);
    }
    try {
        const { systemSchema, variantSchema } = await loadSchemas();
        const ajv = new Ajv();
        addFormats(ajv, ['uri']);
        ajv.addSchema(variantSchema, 'variant.json');
        const validate = ajv.compile(systemSchema);
        if (!validate(systemConf)) {
            throw validate.errors;
        }
    }
    catch (e) {
        console.error('System configuration errors:', e);
        throw new CliError(`The system install failed due to the validation errors reported above. Please fix the the errors in the "${systemConf.name}" configuration and try again.`);
    }
    const variantName = options.variant || projectConfig.project.platform;
    if (!variantName) {
        throw new CliError('Unable to determine a variant for the specified system. Please either pass in a valid variant using the --variant flag.');
    }
    const variantConf = systemConf.variants?.find(({ platform }) => platform === variantName);
    if (!variantConf) {
        throw new CliError(`Unable to find a variant (${variantName}) within the system (${systemConf.name}). Please check your Emulsify project config and make sure the project.platform value is correct, or select a system with a variant that is compatible with the platform you are using.`);
    }
    try {
        let checkout = repo.checkout;
        if (!checkout) {
            checkout = await getCachedItemCheckout('systems', [repo.name]);
        }
        await setEmulsifyConfig({
            system: {
                repository: repo.repository,
                checkout,
            },
            variant: {
                platform: variantConf.platform,
                structureImplementations: variantConf.structureImplementations,
            },
        });
    }
    catch (e) {
        throw new CliError('Unable to update your Emulsify project configuration.');
    }
    try {
        const componentsList = variantConf.components;
        const requiredComponents = componentsList.filter(({ required }) => required === true);
        for (const component of options.all ? componentsList : requiredComponents) {
            await installComponentFromCache(systemConf, variantConf, component.name, true);
        }
        await installGeneralAssetsFromCache(systemConf, variantConf);
        const path = findFileInCurrentPath(EMULSIFY_SYSTEM_CONFIG_FILE);
        const hookPath = path
            ? join(path, EMULSIFY_PROJECT_HOOK_FOLDER, EMULSIFY_PROJECT_HOOK_SYSTEM_INSTALL)
            : undefined;
        if (hookPath && existsSync(hookPath)) {
            await executeScript(hookPath);
        }
    }
    catch (e) {
        throw new CliError(`Unable to install system assets and/or required components: ${String(e)}`);
    }
    return log('success', `Successfully installed the ${systemConf.name} system using the ${variantConf.platform} variant.`);
}
