"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.HtmlTagsPlugin = exports.FILE_ATTRS = exports.HEAD_TAGS = exports.VOID_TAGS = void 0;
const lodash_1 = __importDefault(require("@modern-js/utils/lodash"));
const url_1 = require("../url");
/** @see {@link https://developer.mozilla.org/en-US/docs/Glossary/Void_element} */
exports.VOID_TAGS = [
    'area',
    'base',
    'br',
    'col',
    'embed',
    'hr',
    'img',
    'input',
    'keygen',
    'link',
    'meta',
    'param',
    'source',
    'track',
    'wbr',
];
/** @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head#see_also} */
exports.HEAD_TAGS = [
    'title',
    'base',
    'link',
    'style',
    'meta',
    'script',
    'noscript',
    'template',
];
exports.FILE_ATTRS = {
    link: 'href',
    script: 'src',
};
const withHash = (url, hash) => `${url}?${hash}`;
class HtmlTagsPlugin {
    constructor(opts) {
        this.name = 'HtmlTagsPlugin';
        this.meta = { plugin: this.name };
        this.ctx = {
            append: true,
            ...opts,
        };
    }
    apply(compiler) {
        compiler.hooks.compilation.tap(this.name, compilation => {
            const compilationHash = compilation.hash || '';
            const hooks = this.ctx.HtmlPlugin.getHooks(compilation);
            hooks.alterAssetTagGroups.tap(this.name, params => {
                var _a;
                // skip unmatched file and empty tag list.
                const includesCurrentFile = !this.ctx.includes || this.ctx.includes.includes(params.outputName);
                if (!includesCurrentFile || !((_a = this.ctx.tags) === null || _a === void 0 ? void 0 : _a.length)) {
                    return params;
                }
                // convert tags between `HtmlInjectTag` and `HtmlWebpackPlugin.HtmlTagObject`.
                const fromWebpackTags = (tags, override) => {
                    const ret = [];
                    for (const tag of tags) {
                        ret.push({
                            tag: tag.tagName,
                            attrs: tag.attributes,
                            children: tag.innerHTML,
                            publicPath: false,
                            ...override,
                        });
                    }
                    return ret;
                };
                const fromInjectTags = (tags) => {
                    var _a, _b;
                    const ret = [];
                    for (const tag of tags) {
                        // apply publicPath and hash to filename attr.
                        const attrs = { ...tag.attrs };
                        const filenameTag = exports.FILE_ATTRS[tag.tag];
                        let filename = attrs[filenameTag];
                        if (typeof filename === 'string') {
                            const optPublicPath = (_a = tag.publicPath) !== null && _a !== void 0 ? _a : this.ctx.publicPath;
                            if (typeof optPublicPath === 'function') {
                                filename = optPublicPath(filename, params.publicPath);
                            }
                            else if (typeof optPublicPath === 'string') {
                                filename = (0, url_1.withPublicPath)(filename, optPublicPath);
                            }
                            else if (optPublicPath !== false) {
                                filename = (0, url_1.withPublicPath)(filename, params.publicPath);
                            }
                            const optHash = (_b = tag.hash) !== null && _b !== void 0 ? _b : this.ctx.hash;
                            if (typeof optHash === 'function') {
                                compilationHash.length &&
                                    (filename = optHash(filename, compilationHash));
                            }
                            else if (typeof optHash === 'string') {
                                optHash.length && (filename = withHash(filename, optHash));
                            }
                            else if (optHash === true) {
                                compilationHash.length &&
                                    (filename = withHash(filename, compilationHash));
                            }
                            attrs[filenameTag] = filename;
                        }
                        ret.push({
                            tagName: tag.tag,
                            attributes: attrs,
                            meta: this.meta,
                            voidTag: exports.VOID_TAGS.includes(tag.tag),
                            innerHTML: tag.children,
                        });
                    }
                    return ret;
                };
                // create tag list from html-webpack-plugin and options.
                const [handlers, records] = lodash_1.default.partition(this.ctx.tags, lodash_1.default.isFunction);
                let tags = [
                    ...fromWebpackTags(params.headTags, { head: true }),
                    ...fromWebpackTags(params.bodyTags, { head: false }),
                    ...records,
                ];
                // apply tag handler callbacks.
                tags = lodash_1.default.sortBy(tags, tag => {
                    var _a, _b;
                    let priority = 0;
                    const head = (_a = tag.head) !== null && _a !== void 0 ? _a : exports.HEAD_TAGS.includes(tag.tag);
                    const append = (_b = tag.append) !== null && _b !== void 0 ? _b : this.ctx.append;
                    priority += head ? -2 : 2;
                    typeof append === 'boolean' && (priority += append ? 1 : -1);
                    return priority;
                });
                const utils = {
                    outputName: params.outputName,
                    publicPath: params.publicPath,
                    hash: compilationHash,
                };
                for (const handler of handlers) {
                    tags = handler(tags, utils) || tags;
                }
                // apply to html-webpack-plugin.
                const [headTags, bodyTags] = lodash_1.default.partition(tags, tag => { var _a; return (_a = tag.head) !== null && _a !== void 0 ? _a : exports.HEAD_TAGS.includes(tag.tag); });
                params.headTags = fromInjectTags(headTags);
                params.bodyTags = fromInjectTags(bodyTags);
                return params;
            });
        });
    }
}
exports.HtmlTagsPlugin = HtmlTagsPlugin;
