import fse from 'fs-extra';
// queue all updates and compile them one by one
import { stat } from 'node:fs/promises';
import { isAbsolute, relative } from 'node:path';
import { bold, clearScreen, cyan, green } from '../utils/index.js';
import { logError } from './error.js';
export class HmrEngine {
    constructor(compiler, devServer, _logger) {
        this._logger = _logger;
        this._updateQueue = [];
        this.recompileAndSendResult = async () => {
            const queue = [...this._updateQueue];
            if (queue.length === 0) {
                return;
            }
            let updatedFilesStr = queue
                .map((item) => {
                if (isAbsolute(item)) {
                    return relative(this._compiler.config.config.root, item);
                }
                else {
                    const resolvedPath = this._compiler.transformModulePath(this._compiler.config.config.root, item);
                    return relative(this._compiler.config.config.root, resolvedPath);
                }
            })
                .join(', ');
            if (updatedFilesStr.length >= 100) {
                updatedFilesStr =
                    updatedFilesStr.slice(0, 100) + `...(${queue.length} files)`;
            }
            try {
                clearScreen();
                const start = Date.now();
                const result = await this._compiler.update(queue);
                this._logger.info(`${bold(cyan(updatedFilesStr))} updated in ${bold(green(`${Date.now() - start}ms`))}`);
                // clear update queue after update finished
                this._updateQueue = this._updateQueue.filter((item) => !queue.includes(item));
                let dynamicResourcesMap = null;
                if (result.dynamicResourcesMap) {
                    for (const [key, value] of Object.entries(result.dynamicResourcesMap)) {
                        if (!dynamicResourcesMap) {
                            dynamicResourcesMap = {};
                        }
                        dynamicResourcesMap[key] = value.map((r) => ({
                            path: r[0],
                            type: r[1]
                        }));
                    }
                }
                const { added, changed, removed, immutableModules, mutableModules, boundaries } = result;
                const resultStr = `{
        added: [${formatHmrResult(added)}],
        changed: [${formatHmrResult(changed)}],
        removed: [${formatHmrResult(removed)}],
        immutableModules: ${JSON.stringify(immutableModules.trim())},
        mutableModules: ${JSON.stringify(mutableModules.trim())},
        boundaries: ${JSON.stringify(boundaries)},
        dynamicResourcesMap: ${JSON.stringify(dynamicResourcesMap)}
      }`;
                this.callUpdates(result);
                this._devServer.ws.clients.forEach((client) => {
                    client.rawSend(`
        {
          type: 'farm-update',
          result: ${resultStr}
        }
      `);
                });
                this._compiler.onUpdateFinish(async () => {
                    // if there are more updates, recompile again
                    if (this._updateQueue.length > 0) {
                        await this.recompileAndSendResult();
                    }
                });
            }
            catch (err) {
                clearScreen();
                throw new Error(logError(err));
            }
        };
        this._compiler = compiler;
        this._devServer = devServer;
        // this._lastAttemptWasError = false;
        this._lastModifiedTimestamp = new Map();
    }
    callUpdates(result) {
        this._onUpdates?.forEach((cb) => cb(result));
    }
    onUpdateFinish(cb) {
        if (!this._onUpdates) {
            this._onUpdates = [];
        }
        this._onUpdates.push(cb);
    }
    async hmrUpdate(absPath, force = false) {
        const paths = Array.isArray(absPath) ? absPath : [absPath];
        for (const path of paths) {
            if (this._compiler.hasModule(path) && !this._updateQueue.includes(path)) {
                if (fse.existsSync(path)) {
                    const lastModifiedTimestamp = this._lastModifiedTimestamp.get(path);
                    const currentTimestamp = (await stat(path)).mtime.toISOString();
                    // only update the file if the timestamp changed since last update
                    if (!force && lastModifiedTimestamp === currentTimestamp) {
                        continue;
                    }
                    this._lastModifiedTimestamp.set(path, currentTimestamp);
                }
                // push the path into the queue
                this._updateQueue.push(path);
            }
        }
        if (!this._compiler.compiling && this._updateQueue.length > 0) {
            try {
                await this.recompileAndSendResult();
            }
            catch (e) {
                // eslint-disable-next-line no-control-regex
                const serialization = e.message.replace(/\x1b\[[0-9;]*m/g, '');
                const errorStr = `${JSON.stringify({
                    message: serialization
                })}`;
                this._devServer.ws.clients.forEach((client) => {
                    client.rawSend(`
            {
              type: 'error',
              err: ${errorStr}
            }
          `);
                });
                this._logger.error(e);
            }
        }
    }
}
function formatHmrResult(array) {
    return array.map((item) => `'${item.replaceAll('\\', '\\\\')}'`).join(', ');
}
//# sourceMappingURL=hmr-engine.js.map