import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
import path from 'node:path';
import { Compiler as BindingCompiler } from '../../binding/index.js';
import { Logger } from '../utils/logger.js';
export const VIRTUAL_FARM_DYNAMIC_IMPORT_SUFFIX = '.farm_dynamic_import_virtual_module';
export class Compiler {
    constructor(config, logger = new Logger()) {
        this.config = config;
        this.logger = logger;
        this._updateQueue = [];
        this._onUpdateFinishQueue = [];
        this.compiling = false;
        this._bindingCompiler = new BindingCompiler(this.config);
    }
    async traceDependencies() {
        return this._bindingCompiler.traceDependencies();
    }
    async compile() {
        if (this.compiling) {
            this.logger.error('Already compiling', {
                exit: true
            });
        }
        this.compiling = true;
        if (process.env.FARM_PROFILE) {
            this._bindingCompiler.compileSync();
        }
        else {
            await this._bindingCompiler.compile();
        }
        this.compiling = false;
    }
    compileSync() {
        if (this.compiling) {
            this.logger.error('Already compiling', {
                exit: true
            });
        }
        this.compiling = true;
        this._bindingCompiler.compileSync();
        this.compiling = false;
    }
    async update(paths, sync = false, ignoreCompilingCheck = false, generateUpdateResource = true) {
        let resolve;
        const promise = new Promise((r) => {
            resolve = r;
        });
        // if there is already a update process, we need to wait for it to finish
        if (this.compiling && !ignoreCompilingCheck) {
            this._updateQueue.push({ paths, resolve });
            return promise;
        }
        this.compiling = true;
        try {
            const res = await this._bindingCompiler.update(paths, async () => {
                const next = this._updateQueue.shift();
                if (next) {
                    await this.update(next.paths, true, true, generateUpdateResource).then(next.resolve);
                }
                else {
                    this.compiling = false;
                    for (const cb of this._onUpdateFinishQueue) {
                        await cb();
                    }
                    // clear update finish queue
                    this._onUpdateFinishQueue = [];
                }
            }, sync, generateUpdateResource);
            return res;
        }
        catch (e) {
            this.compiling = false;
            throw e;
        }
    }
    hasModule(resolvedPath) {
        return this._bindingCompiler.hasModule(resolvedPath);
    }
    getParentFiles(idOrResolvedPath) {
        return this._bindingCompiler.getParentFiles(idOrResolvedPath);
    }
    resources() {
        return this._bindingCompiler.resources();
    }
    resource(path) {
        return this._bindingCompiler.resource(path);
    }
    resourcesMap() {
        return this._bindingCompiler.resourcesMap();
    }
    pluginStats() {
        return this._bindingCompiler.pluginStats();
    }
    writeResourcesToDisk() {
        const resources = this.resources();
        const configOutputPath = this.config.config.output.path;
        const outputPath = path.isAbsolute(configOutputPath)
            ? configOutputPath
            : path.join(this.config.config.root, configOutputPath);
        for (const [name, resource] of Object.entries(resources)) {
            // remove query params and hash of name
            const nameWithoutQuery = name.split('?')[0];
            const nameWithoutHash = nameWithoutQuery.split('#')[0];
            let filePath = path.join(outputPath, nameWithoutHash);
            if (!existsSync(path.dirname(filePath))) {
                mkdirSync(path.dirname(filePath), { recursive: true });
            }
            writeFileSync(filePath, resource);
        }
        this.callWriteResourcesHook();
    }
    callWriteResourcesHook() {
        for (const jsPlugin of this.config.jsPlugins ?? []) {
            jsPlugin.writeResources?.executor?.({
                resourcesMap: this._bindingCompiler.resourcesMap(),
                config: this.config.config
            });
        }
    }
    removeOutputPathDir() {
        const outputPath = this.outputPath();
        if (existsSync(outputPath)) {
            rmSync(outputPath, { recursive: true });
        }
    }
    resolvedWatchPaths() {
        return this._bindingCompiler.watchModules();
    }
    resolvedModulePaths(root) {
        return this._bindingCompiler
            .relativeModulePaths()
            .map((p) => this.transformModulePath(root, p));
    }
    transformModulePath(root, p) {
        if (p.endsWith(VIRTUAL_FARM_DYNAMIC_IMPORT_SUFFIX)) {
            p = p.slice(0, -VIRTUAL_FARM_DYNAMIC_IMPORT_SUFFIX.length);
        }
        if (path.isAbsolute(p)) {
            return p;
        }
        if (p.includes('?')) {
            return path.join(root, p.split('?')[0]);
        }
        return path.join(root, p);
    }
    onUpdateFinish(cb) {
        this._onUpdateFinishQueue.push(cb);
    }
    outputPath() {
        const { output, root } = this.config.config;
        const configOutputPath = output.path;
        const outputPath = path.isAbsolute(configOutputPath)
            ? configOutputPath
            : path.join(root, configOutputPath);
        return outputPath;
    }
    addExtraWatchFile(root, paths) {
        this._bindingCompiler.addWatchFiles(root, paths);
    }
    modules() {
        return this._bindingCompiler.modules();
    }
    getResolveRecords(id) {
        return this._bindingCompiler.getResolveRecordsById(id);
    }
    getTransformRecords(id) {
        return this._bindingCompiler.getTransformRecordsById(id);
    }
    getProcessRecords(id) {
        return this._bindingCompiler.getProcessRecordsById(id);
    }
    getAnalyzeDepsRecords(id) {
        return this._bindingCompiler.getAnalyzeDepsRecordsById(id);
    }
    getResourcePotRecordsById(id) {
        return this._bindingCompiler.getResourcePotRecordsById(id);
    }
}
//# sourceMappingURL=index.js.map