"use strict";
var _MLEngine_configProvider, _MLEngine_core, _MLEngine_file, _MLEngine_options, _MLEngine_watcher;
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const file_resolver_1 = require("@markuplint/file-resolver");
const ml_core_1 = require("@markuplint/ml-core");
const chokidar_1 = require("chokidar");
const strict_event_emitter_1 = require("strict-event-emitter");
const debug_1 = require("../debug");
const i18n_1 = require("../i18n");
const log = debug_1.log.extend('ml-engine');
const fileLog = log.extend('file');
class MLEngine extends strict_event_emitter_1.StrictEventEmitter {
    static async toMLFile(target) {
        const files = await (0, file_resolver_1.resolveFiles)([target]);
        return files[0];
    }
    constructor(file, options) {
        var _a, _b;
        super();
        _MLEngine_configProvider.set(this, void 0);
        _MLEngine_core.set(this, null);
        _MLEngine_file.set(this, void 0);
        _MLEngine_options.set(this, void 0);
        _MLEngine_watcher.set(this, new chokidar_1.FSWatcher());
        tslib_1.__classPrivateFieldSet(this, _MLEngine_file, file, "f");
        tslib_1.__classPrivateFieldSet(this, _MLEngine_options, options, "f");
        tslib_1.__classPrivateFieldSet(this, _MLEngine_configProvider, new file_resolver_1.ConfigProvider(), "f");
        this.watchMode(!!((_a = tslib_1.__classPrivateFieldGet(this, _MLEngine_options, "f")) === null || _a === void 0 ? void 0 : _a.watch));
        if ((_b = tslib_1.__classPrivateFieldGet(this, _MLEngine_options, "f")) === null || _b === void 0 ? void 0 : _b.debug) {
            (0, debug_1.verbosely)();
        }
    }
    get document() {
        var _a, _b, _c;
        if (((_a = tslib_1.__classPrivateFieldGet(this, _MLEngine_core, "f")) === null || _a === void 0 ? void 0 : _a.document) instanceof Error) {
            return null;
        }
        return (_c = (_b = tslib_1.__classPrivateFieldGet(this, _MLEngine_core, "f")) === null || _b === void 0 ? void 0 : _b.document) !== null && _c !== void 0 ? _c : null;
    }
    async close() {
        this.removeAllListeners();
        await tslib_1.__classPrivateFieldGet(this, _MLEngine_watcher, "f").close();
    }
    async exec() {
        var _a, _b;
        log('exec: start');
        const core = await this.setup();
        if (!core) {
            log('exec: cancel (unsetuped yet)');
            return null;
        }
        const violations = await core.verify((_a = tslib_1.__classPrivateFieldGet(this, _MLEngine_options, "f")) === null || _a === void 0 ? void 0 : _a.fix).catch(e => {
            if (e instanceof Error) {
                return e;
            }
            throw e;
        });
        const sourceCode = await tslib_1.__classPrivateFieldGet(this, _MLEngine_file, "f").getCode();
        const fixedCode = core.document.toString();
        if (violations instanceof Error) {
            this.emit('lint-error', tslib_1.__classPrivateFieldGet(this, _MLEngine_file, "f").path, sourceCode, violations);
            const errMessage = (_b = violations.stack) !== null && _b !== void 0 ? _b : violations.message;
            log('exec: error %O', errMessage);
            return {
                violations: [
                    {
                        severity: 'error',
                        message: errMessage,
                        ruleId: '@markuplint/ml-core',
                        line: 0,
                        col: 0,
                        raw: '',
                    },
                ],
                filePath: tslib_1.__classPrivateFieldGet(this, _MLEngine_file, "f").path,
                sourceCode,
                fixedCode,
            };
        }
        const debugMap = ('debugMap' in core.document && core.document.debugMap()) || null;
        this.emit('lint', tslib_1.__classPrivateFieldGet(this, _MLEngine_file, "f").path, sourceCode, violations, fixedCode, debugMap);
        log('exec: end');
        return {
            violations,
            filePath: tslib_1.__classPrivateFieldGet(this, _MLEngine_file, "f").path,
            sourceCode,
            fixedCode,
        };
    }
    async setCode(code) {
        const core = await this.setup();
        if (!core) {
            return;
        }
        tslib_1.__classPrivateFieldGet(this, _MLEngine_file, "f").setCode(code);
        core.setCode(code);
    }
    watchMode(enable) {
        tslib_1.__classPrivateFieldSet(this, _MLEngine_options, {
            ...tslib_1.__classPrivateFieldGet(this, _MLEngine_options, "f"),
            watch: enable,
        }, "f");
        if (enable) {
            tslib_1.__classPrivateFieldGet(this, _MLEngine_watcher, "f").on('change', this.onChange.bind(this));
        }
        else {
            tslib_1.__classPrivateFieldGet(this, _MLEngine_watcher, "f").removeAllListeners();
        }
    }
    async createCore(fabric) {
        var _a;
        fileLog('Get source code');
        const sourceCode = await tslib_1.__classPrivateFieldGet(this, _MLEngine_file, "f").getCode();
        fileLog('Source code path: %s', tslib_1.__classPrivateFieldGet(this, _MLEngine_file, "f").path);
        // cspell: disable-next-line
        fileLog('Source code size: %dbyte', sourceCode.length);
        this.emit('code', tslib_1.__classPrivateFieldGet(this, _MLEngine_file, "f").path, sourceCode);
        const core = new ml_core_1.MLCore({
            sourceCode,
            filename: tslib_1.__classPrivateFieldGet(this, _MLEngine_file, "f").path,
            debug: (_a = tslib_1.__classPrivateFieldGet(this, _MLEngine_options, "f")) === null || _a === void 0 ? void 0 : _a.debug,
            ...fabric,
        });
        tslib_1.__classPrivateFieldSet(this, _MLEngine_core, core, "f");
        return core;
    }
    async i18n() {
        var _a;
        const i18nSettings = await (0, i18n_1.i18n)((_a = tslib_1.__classPrivateFieldGet(this, _MLEngine_options, "f")) === null || _a === void 0 ? void 0 : _a.locale);
        this.emit('i18n', tslib_1.__classPrivateFieldGet(this, _MLEngine_file, "f").path, i18nSettings);
        return i18nSettings;
    }
    async onChange(filePath) {
        var _a, _b;
        if (!((_a = tslib_1.__classPrivateFieldGet(this, _MLEngine_options, "f")) === null || _a === void 0 ? void 0 : _a.watch)) {
            return;
        }
        this.emit('log', 'watch:onChange', filePath);
        const fabric = await this.provide(false);
        if (!fabric) {
            return;
        }
        if (fabric.configErrors) {
            this.emit('config-errors', tslib_1.__classPrivateFieldGet(this, _MLEngine_file, "f").path, fabric.configErrors);
        }
        this.emit('log', 'update:core', tslib_1.__classPrivateFieldGet(this, _MLEngine_file, "f").path);
        (_b = tslib_1.__classPrivateFieldGet(this, _MLEngine_core, "f")) === null || _b === void 0 ? void 0 : _b.update(fabric);
        await this.exec();
    }
    async provide(cache = true) {
        var _a, _b, _c;
        const configSet = await this.resolveConfig(cache);
        fileLog('Fetched Config files: %O', configSet.files);
        fileLog('Resolved Config: %O', configSet.config);
        fileLog('Resolved Plugins: %O', configSet.plugins);
        fileLog('Resolve Errors: %O', configSet.errs);
        if (!(await tslib_1.__classPrivateFieldGet(this, _MLEngine_file, "f").isFile())) {
            this.emit('log', 'file-no-exists', `The file doesn't exist or it is not a file: ${tslib_1.__classPrivateFieldGet(this, _MLEngine_file, "f").path}`);
            fileLog("The file doesn't exist or it is not a file: %s", tslib_1.__classPrivateFieldGet(this, _MLEngine_file, "f").path);
            return null;
        }
        // Exclude
        const excludeFiles = configSet.config.excludeFiles || [];
        for (const excludeFile of excludeFiles) {
            if (tslib_1.__classPrivateFieldGet(this, _MLEngine_file, "f").matches(excludeFile)) {
                this.emit('exclude', tslib_1.__classPrivateFieldGet(this, _MLEngine_file, "f").path, excludeFile);
                fileLog('Excludes the file: %s', tslib_1.__classPrivateFieldGet(this, _MLEngine_file, "f").path);
                return null;
            }
        }
        const { parser, parserOptions, matched } = await this.resolveParser(configSet);
        const checkingExt = !((_a = tslib_1.__classPrivateFieldGet(this, _MLEngine_options, "f")) === null || _a === void 0 ? void 0 : _a.ignoreExt);
        if (checkingExt && !matched) {
            this.emit('log', 'ext-unmatched', `Avoided linting because a file is unmatched by the extension: ${tslib_1.__classPrivateFieldGet(this, _MLEngine_file, "f").path}`);
            fileLog('Avoided linting because a file is unmatched by the extension: %s', tslib_1.__classPrivateFieldGet(this, _MLEngine_file, "f").path);
            return null;
        }
        const ruleset = this.resolveRuleset(configSet);
        fileLog('Resolved ruleset: %O', ruleset);
        const schemas = await this.resolveSchemas(configSet);
        if (fileLog.enabled) {
            if (schemas[0].cites.length) {
                const [, ...additionalSpecs] = schemas;
                fileLog('Resolved schemas: HTML Standard');
                for (const additionalSpec of additionalSpecs) {
                    fileLog('Resolved schemas: %O', additionalSpec);
                }
            }
            else {
                fileLog('Resolved schemas: %O', schemas);
            }
        }
        const rules = await this.resolveRules(configSet.plugins, ruleset);
        fileLog('Resolved rules: %O', rules);
        const locale = await (0, i18n_1.i18n)((_b = tslib_1.__classPrivateFieldGet(this, _MLEngine_options, "f")) === null || _b === void 0 ? void 0 : _b.locale);
        if (fileLog.enabled) {
            fileLog('Loaded %d rules: %O', rules.length, rules.map(r => r.name));
        }
        return {
            parser,
            parserOptions,
            pretenders: (_c = configSet.config.pretenders) !== null && _c !== void 0 ? _c : [],
            ruleset,
            schemas,
            rules,
            locale,
            configErrors: configSet.errs,
        };
    }
    async resolveConfig(cache) {
        var _a, _b, _c, _d, _e, _f;
        const defaultConfigKey = ((_a = tslib_1.__classPrivateFieldGet(this, _MLEngine_options, "f")) === null || _a === void 0 ? void 0 : _a.defaultConfig) && tslib_1.__classPrivateFieldGet(this, _MLEngine_configProvider, "f").set((_b = tslib_1.__classPrivateFieldGet(this, _MLEngine_options, "f")) === null || _b === void 0 ? void 0 : _b.defaultConfig);
        const configFilePathsFromTarget = ((_c = tslib_1.__classPrivateFieldGet(this, _MLEngine_options, "f")) === null || _c === void 0 ? void 0 : _c.noSearchConfig)
            ? defaultConfigKey !== null && defaultConfigKey !== void 0 ? defaultConfigKey : null
            : (await tslib_1.__classPrivateFieldGet(this, _MLEngine_configProvider, "f").search(tslib_1.__classPrivateFieldGet(this, _MLEngine_file, "f"))) || defaultConfigKey;
        this.emit('log', 'configFilePathsFromTarget', configFilePathsFromTarget || 'null');
        const configKey = ((_d = tslib_1.__classPrivateFieldGet(this, _MLEngine_options, "f")) === null || _d === void 0 ? void 0 : _d.config) && tslib_1.__classPrivateFieldGet(this, _MLEngine_configProvider, "f").set(tslib_1.__classPrivateFieldGet(this, _MLEngine_options, "f").config);
        this.emit('log', 'configKey', configKey || 'null');
        const configSet = await tslib_1.__classPrivateFieldGet(this, _MLEngine_configProvider, "f").resolve(tslib_1.__classPrivateFieldGet(this, _MLEngine_file, "f"), [configFilePathsFromTarget, (_e = tslib_1.__classPrivateFieldGet(this, _MLEngine_options, "f")) === null || _e === void 0 ? void 0 : _e.configFile, configKey], cache);
        this.emit('config', tslib_1.__classPrivateFieldGet(this, _MLEngine_file, "f").path, configSet);
        if ((_f = tslib_1.__classPrivateFieldGet(this, _MLEngine_options, "f")) === null || _f === void 0 ? void 0 : _f.watch) {
            // It doesn't watch the main HTML file because it may is watched and managed by a language server or text editor or more.
            tslib_1.__classPrivateFieldGet(this, _MLEngine_watcher, "f").add(Array.from(configSet.files));
        }
        return configSet;
    }
    async resolveParser(configSet) {
        const parser = await (0, file_resolver_1.resolveParser)(tslib_1.__classPrivateFieldGet(this, _MLEngine_file, "f"), configSet.config.parser, configSet.config.parserOptions);
        this.emit('parser', tslib_1.__classPrivateFieldGet(this, _MLEngine_file, "f").path, parser.parserModName);
        fileLog('Fetched Parser module: %s', parser.parserModName);
        return parser;
    }
    async resolveRules(plugins, ruleset) {
        var _a, _b, _c, _d, _e;
        const rules = await (0, file_resolver_1.resolveRules)(plugins, ruleset, (_b = (_a = tslib_1.__classPrivateFieldGet(this, _MLEngine_options, "f")) === null || _a === void 0 ? void 0 : _a.importPresetRules) !== null && _b !== void 0 ? _b : true, (_d = (_c = tslib_1.__classPrivateFieldGet(this, _MLEngine_options, "f")) === null || _c === void 0 ? void 0 : _c.autoLoad) !== null && _d !== void 0 ? _d : true);
        if ((_e = tslib_1.__classPrivateFieldGet(this, _MLEngine_options, "f")) === null || _e === void 0 ? void 0 : _e.rules) {
            rules.push(...tslib_1.__classPrivateFieldGet(this, _MLEngine_options, "f").rules);
        }
        this.emit('rules', tslib_1.__classPrivateFieldGet(this, _MLEngine_file, "f").path, rules);
        return rules;
    }
    resolveRuleset(configSet) {
        const ruleset = (0, ml_core_1.convertRuleset)(configSet.config);
        this.emit('ruleset', tslib_1.__classPrivateFieldGet(this, _MLEngine_file, "f").path, ruleset);
        return ruleset;
    }
    async resolveSchemas(configSet) {
        const { schemas } = await (0, file_resolver_1.resolveSpecs)(tslib_1.__classPrivateFieldGet(this, _MLEngine_file, "f").path, configSet.config.specs);
        this.emit('schemas', tslib_1.__classPrivateFieldGet(this, _MLEngine_file, "f").path, schemas);
        return schemas;
    }
    async setup() {
        if (tslib_1.__classPrivateFieldGet(this, _MLEngine_core, "f")) {
            return tslib_1.__classPrivateFieldGet(this, _MLEngine_core, "f");
        }
        const fabric = await this.provide();
        if (!fabric) {
            return null;
        }
        if (fabric.configErrors) {
            this.emit('config-errors', tslib_1.__classPrivateFieldGet(this, _MLEngine_file, "f").path, fabric.configErrors);
        }
        return this.createCore(fabric);
    }
}
exports.default = MLEngine;
_MLEngine_configProvider = new WeakMap(), _MLEngine_core = new WeakMap(), _MLEngine_file = new WeakMap(), _MLEngine_options = new WeakMap(), _MLEngine_watcher = new WeakMap();
