import assert from 'node:assert';
import { existsSync, readFileSync, statSync } from 'fs';
import { rm } from 'fs/promises';
import { isAbsolute, join, relative } from 'path';
import { execaCommandSync } from 'execa';
import { lt as semverLessThan } from 'semver';
import { defaults } from 'lodash-es';
import { create } from 'mem-fs-editor';
import { GENERATOR_JHIPSTER } from '../generator-constants.js';
export default class SharedData {
    _storage;
    _editor;
    _log;
    _logCwd;
    constructor(storage, { memFs, destinationPath, log, logCwd }, initialControl = {}) {
        if (!storage) {
            throw new Error('Storage is required for SharedData');
        }
        this._editor = create(memFs);
        this._log = log;
        this._logCwd = logCwd;
        let jhipsterOldVersion;
        if (existsSync(join(destinationPath, '.yo-rc.json'))) {
            jhipsterOldVersion = JSON.parse(readFileSync(join(destinationPath, '.yo-rc.json'), 'utf-8').toString())[GENERATOR_JHIPSTER]
                ?.jhipsterVersion;
        }
        this._storage = storage;
        defaults(this._storage, {
            sharedDeployment: {},
            sharedWorkspaces: {},
            sharedEntities: {},
            sharedApplication: {},
            sharedSource: {},
            control: initialControl,
            props: {},
        });
        defaults(this._storage.sharedApplication, {
            nodeDependencies: {},
            customizeTemplatePaths: [],
        });
        let customizeRemoveFiles = [];
        const removeFiles = async (assertions, ...files) => {
            if (typeof assertions === 'string') {
                files = [assertions, ...files];
                assertions = {};
            }
            for (const customize of customizeRemoveFiles) {
                files = files.map(customize).filter(file => file);
            }
            const { removedInVersion, oldVersion = jhipsterOldVersion } = assertions;
            if (removedInVersion && oldVersion && !semverLessThan(oldVersion, removedInVersion)) {
                return;
            }
            const absolutePaths = files.map(file => (isAbsolute(file) ? file : join(destinationPath, file)));
            this._editor.delete(absolutePaths);
            await Promise.all(absolutePaths.map(async (file) => {
                const relativePath = relative(logCwd, file);
                try {
                    if (statSync(file).isFile()) {
                        this._log.info(`Removing legacy file ${relativePath}`);
                        await rm(file, { force: true });
                    }
                }
                catch {
                    this._log.debug(`Could not remove legacy file ${relativePath}`);
                }
            }));
        };
        const control = this._storage.control;
        defaults(control, {
            jhipsterOldVersion,
            removeFiles,
            customizeRemoveFiles: [],
            cleanupFiles: async (oldVersionOrCleanup, cleanup) => {
                if (!jhipsterOldVersion)
                    return;
                let oldVersion;
                if (typeof oldVersionOrCleanup === 'string') {
                    oldVersion = oldVersionOrCleanup;
                    assert(cleanup, 'cleanupFiles requires cleanup object');
                }
                else {
                    cleanup = oldVersionOrCleanup;
                    oldVersion = jhipsterOldVersion;
                }
                await Promise.all(Object.entries(cleanup).map(async ([version, files]) => {
                    const stringFiles = [];
                    for (const file of files) {
                        if (Array.isArray(file)) {
                            const [condition, ...fileParts] = file;
                            if (condition) {
                                stringFiles.push(join(...fileParts));
                            }
                        }
                        else {
                            stringFiles.push(file);
                        }
                    }
                    await removeFiles({ oldVersion, removedInVersion: version }, ...stringFiles);
                }));
            },
        });
        if (!('enviromentHasDockerCompose' in control)) {
            Object.defineProperty(control, 'enviromentHasDockerCompose', {
                get: () => {
                    if (control._enviromentHasDockerCompose === undefined) {
                        const { exitCode } = execaCommandSync('docker compose version', { reject: false, stdio: 'pipe' });
                        control._enviromentHasDockerCompose = exitCode === 0;
                    }
                    return control._enviromentHasDockerCompose;
                },
            });
        }
        customizeRemoveFiles = this._storage.control.customizeRemoveFiles;
    }
    getSource() {
        return this._storage.sharedSource;
    }
    getControl() {
        return this._storage.control;
    }
    getApplication() {
        if (!this._storage.sharedApplication)
            throw new Error('Shared application not loaded');
        return this._storage.sharedApplication;
    }
    setEntity(entityName, entity) {
        this._storage.sharedEntities[entityName] = entity;
    }
    hasEntity(entityName) {
        return Boolean(this._storage.sharedEntities[entityName]);
    }
    getEntity(entityName) {
        const entity = this._storage.sharedEntities[entityName];
        if (!entity) {
            throw new Error(`Entity definition not loaded for ${entityName}`);
        }
        return entity;
    }
    getEntities(entityNames = Object.keys(this._storage.sharedEntities)) {
        return entityNames.map(entityName => ({ entityName, entity: this.getEntity(entityName) }));
    }
    getEntitiesMap() {
        return this._storage.sharedEntities;
    }
}
