import webpack from "webpack";

import type { Compiler } from "webpack";

const { Compilation, sources } = webpack;

/**
 * This plugin creates a simple manifest of all entry chunks for each entrypoint.
 *
 * This can be useful to create `<link rel="preload" href="...">` tags for important chunks
 * in e.g., WordPress environments (without any HTML plugin).
 */
class SimpleEntryChunksPlugin {
    private options: {
        filename?: string;
    };

    public constructor(options: SimpleEntryChunksPlugin["options"] = {}) {
        this.options = { filename: "[name]-entry-chunks-manifest.json", ...options };
    }

    public apply(compiler: Compiler): void {
        const { filename } = this.options;

        compiler.hooks.thisCompilation.tap("EntryChunksWithQueryManifestPlugin", (compilation) => {
            compilation.hooks.processAssets.tapPromise(
                {
                    name: "EntryChunksWithQueryManifestPlugin",
                    stage: Compilation.PROCESS_ASSETS_STAGE_REPORT,
                },
                async () => {
                    const manifest: {
                        [entryName: string]: {
                            [chunkName: string]: string[];
                        };
                    } = {};

                    for (const [, entrypoint] of compilation.entrypoints.entries()) {
                        if (!entrypoint.isInitial()) {
                            continue;
                        }

                        const allFiles = entrypoint.getFiles();

                        for (const file of allFiles) {
                            const asset = compilation.getAsset(file);
                            if (!asset) {
                                continue;
                            }

                            // Skip source maps, hot updates, runtime chunks, etc.
                            if (
                                file.includes("hot-update") ||
                                file.endsWith(".map") ||
                                file.startsWith("runtime~") ||
                                file.startsWith("webpack-internal")
                            ) {
                                continue;
                            }

                            manifest[file] = {};

                            // Only include chunks which are part of the current entrypoint
                            for (const chunkGroup of entrypoint.childrenIterable) {
                                for (const child of chunkGroup.chunks) {
                                    if (child.isOnlyInitial()) {
                                        continue;
                                    }

                                    const chunkName = child.name || String(child.id);
                                    manifest[file][chunkName] = Array.from(child.files);
                                }
                            }
                        }
                    }

                    const manifestJson = JSON.stringify(manifest, null, 2);
                    if (!compiler.name) {
                        throw new Error(
                            "SimpleEntryChunksPlugin: compiler.name must be set (set `name` in your webpack config)",
                        );
                    }
                    const useFilename = filename.replace("[name]", compiler.name);
                    compilation.emitAsset(useFilename, new sources.RawSource(manifestJson), {
                        minimized: true,
                    });
                },
            );
        });
    }
}

export { SimpleEntryChunksPlugin };
