import posthtml from 'posthtml';
import { cosmiconfigSync } from 'cosmiconfig';
import safePreset from './presets/safe.mjs';
import ampSafePreset from './presets/ampSafe.mjs';
import maxPreset from './presets/max.mjs';

const presets = {
    safe: safePreset,
    ampSafe: ampSafePreset,
    max: maxPreset
};
function loadConfig(options, preset) {
    const { skipConfigLoading = false, configPath, ...rest } = options || {};
    let restConfig = rest;
    if (!skipConfigLoading) {
        const explorer = cosmiconfigSync('htmlnano');
        const rc = configPath ? explorer.load(configPath) : explorer.search();
        if (rc) {
            const { preset: presetName } = rc.config;
            if (presetName) {
                if (!preset && presetName in presets) {
                    preset = presets[presetName];
                }
                delete rc.config.preset;
            }
            restConfig = {
                ...rc.config,
                ...restConfig
            };
        }
    }
    return [
        restConfig || {},
        preset || safePreset
    ];
}
const optionalDependencies = {
    minifyCss: [
        'cssnano',
        'postcss'
    ],
    minifyJs: [
        'terser'
    ],
    minifyUrls: [
        'relateurl',
        'srcset',
        'terser'
    ],
    minifySvg: [
        'svgo'
    ]
};
/**
 * And the old mixing named export and default export again.
 *
 * TL; DR: our bundler has bundled our mixed default/named export module into a "exports" object,
 * and when dynamically importing a CommonJS module using "import" instead of "require", Node.js wraps
 * another layer of default around the "exports" object.
 *
 * The longer version:
 *
 * The bundler we are using outputs:
 *
 * ESM: export { [named], xxx as default }
 * CJS: exports.default = xxx; exports.[named] = ...; exports.__esModule = true;
 *
 * With ESM, the Module object looks like this:
 *
 * ```js
 * Module {
 *   default: xxx,
 *   [named]: ...,
 * }
 * ```
 *
 * With CJS, Node.js handles dynamic import differently. Node.js doesn't respect `__esModule`,
 * and will wrongly treat a CommonJS module as ESM, i.e. assign the "exports" object on its
 * own "default" on the "Module" object.
 *
 * Now we have:
 *
 * ```js
 * Module {
 *   // this is actually the "exports" inside among "exports.__esModule", "exports.[named]", and "exports.default"
 *   default: {
 *     __esModule: true,
 *     // This is the actual "exports.default"
 *     default: xxx
 *   }
 * }
 * ```
 */ const interop = (imported)=>imported.then((mod)=>{
        let htmlnanoModule;
        while('default' in mod){
            htmlnanoModule = mod;
            mod = mod.default;
            // If we find any htmlnano module hook methods, we know this object is a htmlnano module, return directly
            if ('onAttrs' in mod || 'onContent' in mod || 'onNode' in mod) {
                return mod;
            }
        }
        if (htmlnanoModule && typeof htmlnanoModule.default === 'function') {
            return htmlnanoModule;
        }
        throw new TypeError('The imported module is not a valid htmlnano module');
    });
const modules = {
    collapseAttributeWhitespace: ()=>interop(import('./_modules/collapseAttributeWhitespace.mjs')),
    collapseBooleanAttributes: ()=>interop(import('./_modules/collapseBooleanAttributes.mjs')),
    collapseWhitespace: ()=>interop(import('./_modules/collapseWhitespace.mjs')),
    custom: ()=>interop(import('./_modules/custom.mjs')),
    deduplicateAttributeValues: ()=>interop(import('./_modules/deduplicateAttributeValues.mjs')),
    // example: () => import('./_modules/example.mjs'),
    mergeScripts: ()=>interop(import('./_modules/mergeScripts.mjs')),
    mergeStyles: ()=>interop(import('./_modules/mergeStyles.mjs')),
    minifyConditionalComments: ()=>interop(import('./_modules/minifyConditionalComments.mjs')),
    minifyCss: ()=>interop(import('./_modules/minifyCss.mjs')),
    minifyHtmlTemplate: ()=>interop(import('./_modules/minifyHtmlTemplate.mjs')),
    minifyJs: ()=>interop(import('./_modules/minifyJs.mjs')),
    minifyJson: ()=>interop(import('./_modules/minifyJson.mjs')),
    minifyAttributes: ()=>interop(import('./_modules/minifyAttributes.mjs')),
    minifySvg: ()=>interop(import('./_modules/minifySvg.mjs')),
    minifyUrls: ()=>interop(import('./_modules/minifyUrls.mjs')),
    normalizeAttributeValues: ()=>interop(import('./_modules/normalizeAttributeValues.mjs')),
    removeAttributeQuotes: ()=>interop(import('./_modules/removeAttributeQuotes.mjs')),
    removeComments: ()=>interop(import('./_modules/removeComments.mjs')),
    removeEmptyAttributes: ()=>interop(import('./_modules/removeEmptyAttributes.mjs')),
    removeEmptyElements: ()=>interop(import('./_modules/removeEmptyElements.mjs')),
    removeOptionalTags: ()=>interop(import('./_modules/removeOptionalTags.mjs')),
    removeRedundantAttributes: ()=>interop(import('./_modules/removeRedundantAttributes.mjs')),
    removeUnusedCss: ()=>interop(import('./_modules/removeUnusedCss.mjs')),
    sortAttributes: ()=>interop(import('./_modules/sortAttributes.mjs')),
    sortAttributesWithLists: ()=>interop(import('./_modules/sortAttributesWithLists.mjs'))
};
const htmlnano = Object.assign(function htmlnano(optionsRun = {}, presetRun) {
    // eslint-disable-next-line prefer-const -- re-assign options
    let [options, preset] = loadConfig(optionsRun, presetRun);
    const minifier = async (_tree)=>{
        const tree = _tree;
        const nodeHandlers = [];
        const attrsHandlers = [];
        const contentsHandlers = [];
        options = {
            ...preset,
            ...options
        };
        let promise = Promise.resolve(tree);
        const nonModuleOptions = new Set([
            'skipInternalWarnings'
        ]);
        for (const [moduleName, moduleOptions] of Object.entries(options)){
            if (nonModuleOptions.has(moduleName)) {
                continue;
            }
            if (!moduleOptions) {
                continue;
            }
            if (!(moduleName in safePreset)) {
                throw new Error('Module "' + moduleName + '" is not defined');
            }
            if (moduleName in optionalDependencies) {
                const modules = optionalDependencies[moduleName];
                await Promise.all(modules.map(async (dependency)=>{
                    try {
                        await import(dependency);
                    } catch (e) {
                        if (typeof e === 'object' && e !== null && 'code' in e && typeof e.code === 'string') {
                            if (e.code === 'MODULE_NOT_FOUND' || e.code === 'ERR_MODULE_NOT_FOUND') {
                                if (!options.skipInternalWarnings) {
                                    console.warn(`You have to install "${dependency}" in order to use htmlnano's "${moduleName}" module`);
                                    return;
                                }
                            }
                            throw e;
                        }
                    }
                }));
            }
            const mod = moduleName in modules ? await modules[moduleName]() : await import(`./_modules/${moduleName}.mjs`);
            if (typeof mod.onAttrs === 'function') {
                // eslint-disable-next-line @typescript-eslint/no-explicit-any -- module options are generic
                attrsHandlers.push(mod.onAttrs(options, moduleOptions));
            }
            if (typeof mod.onContent === 'function') {
                // eslint-disable-next-line @typescript-eslint/no-explicit-any -- module options are generic
                contentsHandlers.push(mod.onContent(options, moduleOptions));
            }
            if (typeof mod.onNode === 'function') {
                // eslint-disable-next-line @typescript-eslint/no-explicit-any -- module options are generic
                nodeHandlers.push(mod.onNode(options, moduleOptions));
            }
            if (typeof mod.default === 'function') {
                // eslint-disable-next-line @typescript-eslint/no-explicit-any -- module options are generic
                promise = promise.then(async (tree)=>await mod.default(tree, options, moduleOptions));
            }
        }
        if (attrsHandlers.length + contentsHandlers.length + nodeHandlers.length === 0) {
            return promise;
        }
        return promise.then((tree)=>{
            tree.walk((node)=>{
                if (node) {
                    if (node.attrs && typeof node.attrs === 'object') {
                        // Convert all attrs' key to lower case
                        let newAttrsObj = {};
                        Object.entries(node.attrs).forEach(([attrName, attrValue])=>{
                            newAttrsObj[attrName.toLowerCase()] = attrValue;
                        });
                        for (const handler of attrsHandlers){
                            newAttrsObj = handler(newAttrsObj, node);
                        }
                        node.attrs = newAttrsObj;
                    }
                    if (node.content) {
                        node.content = typeof node.content === 'string' ? [
                            node.content
                        ] : node.content;
                        if (Array.isArray(node.content) && node.content.length > 0) {
                            for (const handler of contentsHandlers){
                                var _node_content;
                                const result = handler((_node_content = node.content) != null ? _node_content : [], node);
                                node.content = Array.isArray(result) ? result : [
                                    result
                                ];
                            }
                        }
                    }
                    for (const handler of nodeHandlers){
                        if (handler) {
                            node = handler(node);
                        }
                    }
                }
                return node;
            });
            return tree;
        });
    };
    return minifier;
}, {
    presets,
    getRequiredOptionalDependencies,
    process,
    htmlMinimizerWebpackPluginMinify,
    loadConfig
});
function getRequiredOptionalDependencies(optionsRun, presetRun) {
    const [options] = loadConfig(optionsRun, presetRun);
    const dependencies = Object.keys(options).flatMap((moduleName)=>{
        if (moduleName in optionalDependencies) {
            return optionalDependencies[moduleName];
        }
        return [];
    });
    return [
        ...new Set(dependencies)
    ];
}
function process(html, options, preset, postHtmlOptions) {
    return posthtml([
        htmlnano(options, preset)
    ]).process(html, postHtmlOptions);
}
// https://github.com/webpack-contrib/html-minimizer-webpack-plugin/blob/faca00f2219514bc671c5942685721f0b5dbaa70/src/utils.js#L74
function htmlMinimizerWebpackPluginMinify(input, minimizerOptions) {
    const [[, code]] = Object.entries(input);
    return process(code, minimizerOptions, presets.safe).then((result)=>{
        return {
            code: result.html
        };
    });
}
if (typeof module !== 'undefined') {
    module.exports = htmlnano;
}

export { htmlnano as default, getRequiredOptionalDependencies, htmlMinimizerWebpackPluginMinify, loadConfig, presets, process };
