const PLUGIN_NAME = 'CssChunkingPlugin';
/**
 * Merge chunks until they are bigger than the target size.
 */ const MIN_CSS_CHUNK_SIZE = 30 * 1024;
/**
 * Avoid merging chunks when they would be bigger than this size.
 */ const MAX_CSS_CHUNK_SIZE = 100 * 1024;
function isGlobalCss(module) {
    return !/\.module\.(css|scss|sass)$/.test(module.nameForCondition() || '');
}
export class CssChunkingPlugin {
    constructor(strict){
        this.strict = strict;
    }
    apply(compiler) {
        const strict = this.strict;
        const summary = !!process.env.CSS_CHUNKING_SUMMARY;
        compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation)=>{
            let once = false;
            compilation.hooks.optimizeChunks.tap({
                name: PLUGIN_NAME,
                stage: 5
            }, ()=>{
                if (once) {
                    return;
                }
                once = true;
                const chunkGraph = compilation.chunkGraph;
                let changed = undefined;
                const chunkStates = new Map();
                const chunkStatesByModule = new Map();
                // Collect all css modules in chunks and the execpted order of them
                for (const chunk of compilation.chunks){
                    var _chunk_name;
                    if ((_chunk_name = chunk.name) == null ? void 0 : _chunk_name.startsWith('pages/')) continue;
                    const modules = [];
                    for (const module of chunkGraph.getChunkModulesIterable(chunk)){
                        var _module_type;
                        if (!((_module_type = module.type) == null ? void 0 : _module_type.startsWith('css'))) continue;
                        modules.push(module);
                    }
                    if (!modules.length) continue;
                    const chunkState = {
                        chunk,
                        modules,
                        order: 0,
                        requests: modules.length
                    };
                    chunkStates.set(chunk, chunkState);
                    for(let i = 0; i < modules.length; i++){
                        const module = modules[i];
                        let moduleChunkStates = chunkStatesByModule.get(module);
                        if (!moduleChunkStates) {
                            moduleChunkStates = new Map();
                            chunkStatesByModule.set(module, moduleChunkStates);
                        }
                        moduleChunkStates.set(chunkState, i);
                        chunkStatesByModule.set(module, moduleChunkStates);
                    }
                }
                // Sort modules by their index sum
                const orderedModules = [];
                for (const [module, moduleChunkStates] of chunkStatesByModule){
                    let sum = 0;
                    for (const i of moduleChunkStates.values()){
                        sum += i;
                    }
                    orderedModules.push({
                        module,
                        sum
                    });
                }
                orderedModules.sort((a, b)=>a.sum - b.sum);
                // A queue of modules that still need to be processed
                const remainingModules = new Set(orderedModules.map(({ module })=>module));
                // In loose mode we guess the dependents of modules from the order
                // assuming that when a module is a dependency of another module
                // it will always appear before it in every chunk.
                const allDependents = new Map();
                if (!this.strict) {
                    for (const b of remainingModules){
                        const dependent = new Set();
                        loop: for (const a of remainingModules){
                            if (a === b) continue;
                            // check if a depends on b
                            for (const [chunkState, ia] of chunkStatesByModule.get(a)){
                                const bChunkStates = chunkStatesByModule.get(b);
                                const ib = bChunkStates.get(chunkState);
                                if (ib === undefined) {
                                    continue loop;
                                }
                                if (ib > ia) {
                                    continue loop;
                                }
                            }
                            dependent.add(a);
                        }
                        if (dependent.size > 0) allDependents.set(b, dependent);
                    }
                }
                // Stores the new chunk for every module
                const newChunksByModule = new Map();
                // Process through all modules
                for (const startModule of remainingModules){
                    let globalCssMode = isGlobalCss(startModule);
                    // The current position of processing in all selected chunks
                    let allChunkStates = new Map(chunkStatesByModule.get(startModule));
                    // The list of modules that goes into the new chunk
                    const newChunkModules = new Set([
                        startModule
                    ]);
                    // The current size of the new chunk
                    let currentSize = startModule.size();
                    // A pool of potential modules where the next module is selected from.
                    // It's filled from the next module of the selected modules in every chunk.
                    // It also keeps some metadata to improve performance [size, chunkStates].
                    const potentialNextModules = new Map();
                    for (const [chunkState, i] of allChunkStates){
                        const nextModule = chunkState.modules[i + 1];
                        if (nextModule && remainingModules.has(nextModule)) {
                            potentialNextModules.set(nextModule, [
                                nextModule.size(),
                                chunkStatesByModule.get(nextModule)
                            ]);
                        }
                    }
                    // Try to add modules to the chunk until a break condition is met
                    let cont;
                    do {
                        cont = false;
                        // We try to select a module that reduces request count and
                        // has the highest number of requests
                        const orderedPotentialNextModules = [];
                        for (const [nextModule, [size, nextChunkStates]] of potentialNextModules){
                            let maxRequests = 0;
                            for (const chunkState of nextChunkStates.keys()){
                                // There is always some overlap
                                if (allChunkStates.has(chunkState)) {
                                    maxRequests = Math.max(maxRequests, chunkState.requests);
                                }
                            }
                            orderedPotentialNextModules.push([
                                nextModule,
                                size,
                                nextChunkStates,
                                maxRequests
                            ]);
                        }
                        orderedPotentialNextModules.sort((a, b)=>b[3] - a[3] || (a[0].identifier() < b[0].identifier() ? -1 : 1));
                        // Try every potential module
                        loop: for (const [nextModule, size, nextChunkStates] of orderedPotentialNextModules){
                            if (currentSize + size > MAX_CSS_CHUNK_SIZE) {
                                continue;
                            }
                            if (!strict) {
                                // In loose mode we only check if the dependencies are not violated
                                const dependent = allDependents.get(nextModule);
                                if (dependent) {
                                    for (const dep of dependent){
                                        if (newChunkModules.has(dep)) {
                                            continue loop;
                                        }
                                    }
                                }
                            } else {
                                // In strict mode we check that none of the order in any chunk is changed by adding the module
                                for (const [chunkState, i] of nextChunkStates){
                                    const prevState = allChunkStates.get(chunkState);
                                    if (prevState === undefined) {
                                        // New chunk group, can add it, but should we?
                                        // We only add that if below min size
                                        if (currentSize < MIN_CSS_CHUNK_SIZE) {
                                            continue;
                                        } else {
                                            continue loop;
                                        }
                                    } else if (prevState + 1 === i) {
                                        continue;
                                    } else {
                                        continue loop;
                                    }
                                }
                            }
                            // Global CSS must not leak into unrelated chunks
                            const nextIsGlobalCss = isGlobalCss(nextModule);
                            if (nextIsGlobalCss && globalCssMode) {
                                if (allChunkStates.size !== nextChunkStates.size) {
                                    continue;
                                }
                            }
                            if (globalCssMode) {
                                for (const chunkState of nextChunkStates.keys()){
                                    if (!allChunkStates.has(chunkState)) {
                                        continue loop;
                                    }
                                }
                            }
                            if (nextIsGlobalCss) {
                                for (const chunkState of allChunkStates.keys()){
                                    if (!nextChunkStates.has(chunkState)) {
                                        continue loop;
                                    }
                                }
                            }
                            potentialNextModules.delete(nextModule);
                            currentSize += size;
                            if (nextIsGlobalCss) {
                                globalCssMode = true;
                            }
                            for (const [chunkState, i] of nextChunkStates){
                                if (allChunkStates.has(chunkState)) {
                                    // This reduces the request count of the chunk group
                                    chunkState.requests--;
                                }
                                allChunkStates.set(chunkState, i);
                                const newNextModule = chunkState.modules[i + 1];
                                if (newNextModule && remainingModules.has(newNextModule) && !newChunkModules.has(newNextModule)) {
                                    potentialNextModules.set(newNextModule, [
                                        newNextModule.size(),
                                        chunkStatesByModule.get(newNextModule)
                                    ]);
                                }
                            }
                            newChunkModules.add(nextModule);
                            cont = true;
                            break;
                        }
                    }while (cont);
                    const newChunk = compilation.addChunk();
                    newChunk.preventIntegration = true;
                    newChunk.idNameHints.add('css');
                    for (const module of newChunkModules){
                        remainingModules.delete(module);
                        chunkGraph.connectChunkAndModule(newChunk, module);
                        newChunksByModule.set(module, newChunk);
                    }
                    changed = true;
                }
                for (const { chunk, modules } of chunkStates.values()){
                    const chunks = new Set();
                    for (const module of modules){
                        const newChunk = newChunksByModule.get(module);
                        if (newChunk) {
                            chunkGraph.disconnectChunkAndModule(chunk, module);
                            if (chunks.has(newChunk)) continue;
                            chunks.add(newChunk);
                            chunk.split(newChunk);
                        }
                    }
                }
                if (summary) {
                    console.log('Top 20 chunks by request count:');
                    const orderedChunkStates = [
                        ...chunkStates.values()
                    ];
                    orderedChunkStates.sort((a, b)=>b.requests - a.requests);
                    for (const { chunk, modules, requests } of orderedChunkStates.slice(0, 20)){
                        console.log(`- ${requests} requests for ${chunk.name} (has ${modules.length} modules)`);
                    }
                }
                return changed;
            });
        });
    }
}

//# sourceMappingURL=css-chunking-plugin.js.map