import * as _ from 'lodash-es';
import sortKeys from 'sort-keys';
import XmlStorage from '../internal/xml-store.mjs';
const { set, get } = _;
const rootOrder = [
    'modelVersion',
    'groupId',
    'artifactId',
    'version',
    'packaging',
    'name',
    'description',
    'parent',
    'repositories',
    'pluginRepositories',
    'distributionManagement',
    'properties',
    'dependencyManagement',
    'dependencies',
    'build',
    'profiles',
];
const propertiesOrder = [
    'maven.version',
    'java.version',
    'node.version',
    'npm.version',
    'project.build.sourceEncoding',
    'project.reporting.outputEncoding',
    'maven.build.timestamp.format',
    'maven.compiler.source',
    'maven.compiler.target',
    'start-class',
    'argLine',
    'm2e.apt.activation',
    'run.addResources',
    'jhipster-dependencies.version',
    'spring-boot.version',
];
const formatFirstXmlLevel = content => content.replace(/(\n {4}<(?:groupId|distributionManagement|repositories|pluginRepositories|properties|dependencyManagement|dependencies|build|profiles)>)/g, '\n$1');
const isComment = name => name.startsWith('#');
const toMaxInt = nr => (nr === -1 ? Number.MAX_SAFE_INTEGER : nr);
const sortWithTemplate = (template, a, b) => {
    if (isComment(a))
        return -1;
    if (isComment(b))
        return 1;
    const indexOfA = toMaxInt(template.findIndex(item => item === a));
    const indexOfB = toMaxInt(template.findIndex(item => item === b));
    if (indexOfA === indexOfB) {
        return a.localeCompare(b);
    }
    return indexOfA - indexOfB;
};
const comparator = (order) => (a, b) => sortWithTemplate(order, a, b);
const sortProperties = properties => sortKeys(properties, { compare: comparator(propertiesOrder) });
const artifactEquals = (a, b) => {
    return a.groupId === b.groupId && a.artifactId === b.artifactId;
};
const dependencyEquals = (a, b) => {
    return artifactEquals(a, b) && a.scope === b.scope && a.type === b.type;
};
const idEquals = (a, b) => {
    return a.id === b.id;
};
const ensureChildIsArray = (node, childPath) => {
    let dependencyArray = get(node, childPath);
    if (!dependencyArray) {
        dependencyArray = [];
        set(node, childPath, dependencyArray);
    }
    else if (!Array.isArray(dependencyArray)) {
        dependencyArray = [dependencyArray];
        set(node, childPath, dependencyArray);
    }
    return dependencyArray;
};
function appendOrReplace(array, item, equals) {
    const dependencyIndex = array.findIndex(existing => equals(existing, item));
    if (dependencyIndex === -1) {
        array.push(item);
    }
    else {
        array[dependencyIndex] = item;
    }
}
function appendOrGet(array, item, equals) {
    const child = array.find(existing => equals(existing, item));
    if (child) {
        return child;
    }
    array.push(item);
    return item;
}
const ensureProfile = (project, profileId) => {
    const profileArray = ensureChildIsArray(project, 'profiles.profile');
    return appendOrGet(profileArray, { id: profileId }, idEquals);
};
const groupIdOrder = ['tech.jhipster', 'org.springframework.boot', 'org.springframework.security', 'org.springdoc'];
const sortArtifacts = (artifacts) => artifacts.sort((a, b) => {
    if (a.groupId !== b.groupId) {
        if (a.groupId === undefined) {
            return -1;
        }
        if (b.groupId === undefined) {
            return 1;
        }
        const groupIdCompared = sortWithTemplate(groupIdOrder, a.groupId, b.groupId);
        if (groupIdCompared)
            return groupIdCompared;
    }
    return a.artifactId.localeCompare(b.artifactId);
});
const sortProfiles = (profiles) => profiles.sort((a, b) => a.id.localeCompare(b.id));
const ensureChildPath = (node, childPath) => {
    let child = get(node, childPath);
    if (child)
        return child;
    child = {};
    set(node, childPath, child);
    return child;
};
const ensureChild = (current, ...childPath) => {
    for (const node of childPath) {
        if (typeof node === 'string') {
            current = ensureChildPath(current, node);
        }
        else if (typeof node === 'function') {
            current = node(current);
        }
        else {
            throw new Error(`Path section not supported ${node}`);
        }
        if (!current) {
            return undefined;
        }
    }
    return current;
};
export default class PomStorage extends XmlStorage {
    constructor({ saveFile, loadFile }) {
        super({ saveFile, loadFile });
    }
    addProperty({ inProfile, property, value = null }) {
        const node = this.getNode({ nodePath: 'properties', profile: inProfile });
        node[property] = value;
        this.persist();
    }
    addDependency({ inProfile, ...dependency }) {
        this.addDependencyAt(this.getNode({ profile: inProfile }), dependency);
        this.persist();
    }
    addDependencyManagement({ inProfile, ...dependency }) {
        this.addDependencyAt(this.getNode({ profile: inProfile, nodePath: 'dependencyManagement' }), dependency);
        this.persist();
    }
    addDistributionManagement({ inProfile, snapshotsId, snapshotsUrl, releasesId, releasesUrl }) {
        const store = this.getNode({ profile: inProfile });
        store.distributionManagement = {
            snapshotRepository: {
                id: snapshotsId,
                url: snapshotsUrl,
            },
            repository: {
                id: releasesId,
                url: releasesUrl,
            },
        };
        this.persist();
    }
    addProfile({ content, ...profile }) {
        const profileArray = ensureChildIsArray(this.getNode(), 'profiles.profile');
        appendOrReplace(profileArray, this.mergeContent(profile, content), idEquals);
        this.persist();
    }
    addPlugin({ inProfile, ...plugin }) {
        this.addPluginAt(this.getNode({ profile: inProfile, nodePath: 'build' }), plugin);
        this.persist();
    }
    addPluginManagement({ inProfile, ...plugin }) {
        this.addPluginAt(this.getNode({ profile: inProfile, nodePath: 'build.pluginManagement' }), plugin);
        this.persist();
    }
    addRepository({ inProfile, ...repository }) {
        this.addRepositoryAt(this.getNode({ profile: inProfile }), repository);
        this.persist();
    }
    addPluginRepository({ inProfile, ...repository }) {
        this.addPluginRepositoryAt(this.getNode({ profile: inProfile }), repository);
        this.persist();
    }
    addAnnotationProcessor({ inProfile, ...artifact }) {
        const node = this.getNode({ profile: inProfile });
        const plugins = ensureChildIsArray(node, 'build.pluginManagement.plugins.plugin');
        const annotationProcessorPaths = ensureChild(plugins, pluginArray => {
            return appendOrGet(pluginArray, {
                groupId: 'org.apache.maven.plugins',
                artifactId: 'maven-compiler-plugin',
            }, artifactEquals);
        }, 'configuration.annotationProcessorPaths');
        const paths = ensureChildIsArray(annotationProcessorPaths, 'path');
        appendOrReplace(paths, artifact, artifactEquals);
        this.persist();
    }
    getNode({ profile, nodePath } = {}) {
        const node = profile ? ensureProfile(this.store.project, profile) : this.store.project;
        if (nodePath) {
            return ensureChild(node, nodePath);
        }
        return node;
    }
    addDependencyAt(node, { additionalContent, ...dependency }) {
        const dependencyArray = ensureChildIsArray(node, 'dependencies.dependency');
        appendOrReplace(dependencyArray, this.mergeContent(dependency, additionalContent), dependencyEquals);
    }
    addPluginAt(node, { additionalContent, ...artifact }) {
        const artifactArray = ensureChildIsArray(node, 'plugins.plugin');
        appendOrReplace(artifactArray, this.mergeContent(artifact, additionalContent), artifactEquals);
    }
    addRepositoryAt(node, { releasesEnabled, snapshotsEnabled, ...repository }) {
        const releases = releasesEnabled === undefined ? undefined : { enabled: releasesEnabled };
        const snapshots = snapshotsEnabled === undefined ? undefined : { enabled: snapshotsEnabled };
        const repositoryArray = ensureChildIsArray(node, 'repositories.repository');
        appendOrReplace(repositoryArray, { ...repository, releases, snapshots }, idEquals);
    }
    addPluginRepositoryAt(node, { releasesEnabled, snapshotsEnabled, ...repository }) {
        const releases = releasesEnabled === undefined ? undefined : { enabled: releasesEnabled };
        const snapshots = snapshotsEnabled === undefined ? undefined : { enabled: snapshotsEnabled };
        const repositoryArray = ensureChildIsArray(node, 'pluginRepositories.pluginRepository');
        appendOrReplace(repositoryArray, { ...repository, releases, snapshots }, idEquals);
    }
    sort() {
        if (this.store.project) {
            const project = sortKeys(this.store.project, { compare: comparator(rootOrder) });
            this.store.project = project;
            if (project.properties) {
                project.properties = sortProperties(project.properties);
            }
            if (Array.isArray(project.dependencies?.dependency)) {
                project.dependencies.dependency = sortArtifacts(project.dependencies.dependency);
            }
            if (Array.isArray(project.dependencyManagement?.dependencies?.dependency)) {
                project.dependencyManagement.dependencies.dependency = sortArtifacts(project.dependencyManagement.dependencies.dependency);
            }
            if (Array.isArray(project.build?.plugins?.plugin)) {
                project.build.plugins.plugin = sortArtifacts(project.build.plugins.plugin);
            }
            if (Array.isArray(project.build?.pluginManagement?.plugins?.plugin)) {
                project.build.pluginManagement.plugins.plugin = sortArtifacts(project.build.pluginManagement.plugins.plugin);
            }
            if (Array.isArray(project.profiles?.profile)) {
                project.profiles.profile = sortProfiles(project.profiles.profile);
            }
        }
    }
}
const emptyPomFile = `<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
</project>
`;
export const createPomStorage = (generator) => {
    const loadFile = () => generator.readDestination('pom.xml', { defaults: emptyPomFile })?.toString() ?? '';
    const pomStorage = new PomStorage({
        loadFile,
        saveFile: content => generator.writeDestination('pom.xml', formatFirstXmlLevel(content)),
    });
    generator.fs.store.on('change', filename => {
        if (filename === generator.destinationPath('pom.xml')) {
            pomStorage.clearCache();
        }
    });
    return pomStorage;
};
