'use strict';

var node = require('vscode-languageserver/node');
var config = require('@herb-tools/config');
var formatter = require('@herb-tools/formatter');
var vscodeLanguageserverTextdocument = require('vscode-languageserver-textdocument');
var core = require('@herb-tools/core');
var nodeWasm = require('@herb-tools/node-wasm');
var linter = require('@herb-tools/linter');
var loader = require('@herb-tools/linter/loader');
var loader$1 = require('@herb-tools/rewriter/loader');

var version = "0.8.4";

class Settings {
    constructor(params, connection) {
        var _a;
        this.defaultSettings = {
            linter: {
                enabled: true,
                fixOnSave: true
            },
            formatter: {
                enabled: false,
                indentWidth: formatter.defaultFormatOptions.indentWidth,
                maxLineLength: formatter.defaultFormatOptions.maxLineLength
            }
        };
        this.globalSettings = this.defaultSettings;
        this.documentSettings = new Map();
        this.hasConfigurationCapability = false;
        this.hasWorkspaceFolderCapability = false;
        this.hasDiagnosticRelatedInformationCapability = false;
        this.hasShowDocumentCapability = false;
        this.params = params;
        this.capabilities = params.capabilities;
        this.connection = connection;
        this.hasConfigurationCapability = !!(this.capabilities.workspace && !!this.capabilities.workspace.configuration);
        this.hasShowDocumentCapability = !!((_a = this.capabilities.window) === null || _a === void 0 ? void 0 : _a.showDocument);
        this.hasWorkspaceFolderCapability = !!(this.capabilities.workspace && !!this.capabilities.workspace.workspaceFolders);
        this.hasDiagnosticRelatedInformationCapability = !!(this.capabilities.textDocument &&
            this.capabilities.textDocument.publishDiagnostics &&
            this.capabilities.textDocument.publishDiagnostics.relatedInformation);
    }
    async initializeProjectConfig(config$1) {
        if (config$1) {
            this.projectConfig = config$1;
            return;
        }
        try {
            this.projectConfig = await config.Config.loadForEditor(this.projectPath, version);
        }
        catch (error) {
            this.connection.console.warn(`Failed to load project config: ${error}`);
            this.projectConfig = undefined;
        }
    }
    async refreshProjectConfig(config) {
        await this.initializeProjectConfig(config);
        this.documentSettings.clear();
    }
    // TODO: ideally we can just use Config all the way through
    mergeSettings(userSettings, projectConfig) {
        var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
        const settings = userSettings || this.defaultSettings;
        const hasConfigFile = projectConfig ? config.Config.exists(projectConfig.projectPath) : false;
        if (!projectConfig || !hasConfigFile) {
            return {
                trace: settings.trace,
                linter: {
                    enabled: (_b = (_a = settings.linter) === null || _a === void 0 ? void 0 : _a.enabled) !== null && _b !== void 0 ? _b : this.defaultSettings.linter.enabled,
                    fixOnSave: (_d = (_c = settings.linter) === null || _c === void 0 ? void 0 : _c.fixOnSave) !== null && _d !== void 0 ? _d : this.defaultSettings.linter.fixOnSave
                },
                formatter: {
                    enabled: (_f = (_e = settings.formatter) === null || _e === void 0 ? void 0 : _e.enabled) !== null && _f !== void 0 ? _f : this.defaultSettings.formatter.enabled,
                    indentWidth: (_h = (_g = settings.formatter) === null || _g === void 0 ? void 0 : _g.indentWidth) !== null && _h !== void 0 ? _h : this.defaultSettings.formatter.indentWidth,
                    maxLineLength: (_k = (_j = settings.formatter) === null || _j === void 0 ? void 0 : _j.maxLineLength) !== null && _k !== void 0 ? _k : this.defaultSettings.formatter.maxLineLength
                }
            };
        }
        return {
            trace: settings.trace,
            linter: {
                enabled: projectConfig.isLinterEnabled,
                fixOnSave: (_m = (_l = settings.linter) === null || _l === void 0 ? void 0 : _l.fixOnSave) !== null && _m !== void 0 ? _m : this.defaultSettings.linter.fixOnSave
            },
            formatter: {
                enabled: projectConfig.isFormatterEnabled,
                indentWidth: (_p = (_o = projectConfig.formatter) === null || _o === void 0 ? void 0 : _o.indentWidth) !== null && _p !== void 0 ? _p : this.defaultSettings.formatter.indentWidth,
                maxLineLength: (_r = (_q = projectConfig.formatter) === null || _q === void 0 ? void 0 : _q.maxLineLength) !== null && _r !== void 0 ? _r : this.defaultSettings.formatter.maxLineLength
            }
        };
    }
    get projectPath() {
        var _a, _b, _c, _d, _e;
        const uri = (_e = (_d = (_c = (_b = (_a = this.params.workspaceFolders) === null || _a === void 0 ? void 0 : _a.at(0)) === null || _b === void 0 ? void 0 : _b.uri) !== null && _c !== void 0 ? _c : this.params.rootUri) !== null && _d !== void 0 ? _d : this.params.rootPath) !== null && _e !== void 0 ? _e : "";
        return uri.replace(/^file:\/\//, "");
    }
    getDocumentSettings(resource) {
        if (!this.hasConfigurationCapability) {
            const merged = this.mergeSettings(this.globalSettings, this.projectConfig);
            return Promise.resolve(merged);
        }
        let result = this.documentSettings.get(resource);
        if (!result) {
            result = this.connection.workspace.getConfiguration({
                scopeUri: resource,
                section: "languageServerHerb",
            }).then((userSettings) => {
                return this.mergeSettings(userSettings, this.projectConfig);
            });
            this.documentSettings.set(resource, result);
        }
        return result;
    }
}

class DocumentService {
    constructor(connection) {
        this.documents = new node.TextDocuments(vscodeLanguageserverTextdocument.TextDocument);
        this.documents.listen(connection);
    }
    get(uri) {
        return this.documents.get(uri);
    }
    getAll() {
        return this.documents.all();
    }
    get onDidChangeContent() {
        return this.documents.onDidChangeContent;
    }
    get onDidOpen() {
        return this.documents.onDidOpen;
    }
    get onDidClose() {
        return this.documents.onDidClose;
    }
}

class Diagnostics {
    constructor(connection, documentService, parserService, linterService, configService) {
        this.diagnostics = new Map();
        this.connection = connection;
        this.documentService = documentService;
        this.parserService = parserService;
        this.linterService = linterService;
        this.configService = configService;
    }
    async validate(textDocument) {
        let allDiagnostics = [];
        if (textDocument.uri.endsWith('.herb.yml')) {
            allDiagnostics = await this.configService.validateDocument(textDocument);
        }
        else {
            const parseResult = this.parserService.parseDocument(textDocument);
            const lintResult = await this.linterService.lintDocument(textDocument);
            const unreachableCodeDiagnostics = this.getUnreachableCodeDiagnostics(parseResult.document);
            allDiagnostics = [
                ...parseResult.diagnostics,
                ...lintResult.diagnostics,
                ...unreachableCodeDiagnostics,
            ];
        }
        this.diagnostics.set(textDocument, allDiagnostics);
        this.sendDiagnosticsFor(textDocument);
    }
    getUnreachableCodeDiagnostics(document) {
        const collector = new UnreachableCodeCollector();
        collector.visit(document);
        return collector.diagnostics;
    }
    async refreshDocument(document) {
        await this.validate(document);
    }
    async refreshAllDocuments() {
        const documents = this.documentService.getAll();
        await Promise.all(documents.map(document => this.refreshDocument(document)));
    }
    sendDiagnosticsFor(textDocument) {
        const diagnostics = this.diagnostics.get(textDocument) || [];
        this.connection.sendDiagnostics({
            uri: textDocument.uri,
            diagnostics,
        });
        this.diagnostics.delete(textDocument);
    }
}
class UnreachableCodeCollector extends core.Visitor {
    constructor() {
        super(...arguments);
        this.diagnostics = [];
    }
    visitERBCaseNode(node) {
        this.checkUnreachableChildren(node.children);
        this.visitChildNodes(node);
    }
    visitERBCaseMatchNode(node) {
        this.checkUnreachableChildren(node.children);
        this.visitChildNodes(node);
    }
    checkUnreachableChildren(children) {
        for (const child of children) {
            if (core.isHTMLTextNode(child) && child.content.trim() === "") {
                continue;
            }
            const diagnostic = {
                range: {
                    start: {
                        line: this.toZeroBased(child.location.start.line),
                        character: child.location.start.column
                    },
                    end: {
                        line: this.toZeroBased(child.location.end.line),
                        character: child.location.end.column
                    }
                },
                message: "Unreachable code: content between case and when/in is never executed",
                severity: node.DiagnosticSeverity.Hint,
                tags: [node.DiagnosticTag.Unnecessary],
                source: "Herb Language Server"
            };
            this.diagnostics.push(diagnostic);
        }
    }
    toZeroBased(line) {
        return line - 1;
    }
}

class ErrorVisitor extends nodeWasm.Visitor {
    constructor() {
        super(...arguments);
        this.source = "Herb Parser ";
        this.diagnostics = [];
    }
    visitChildNodes(node) {
        super.visitChildNodes(node);
        node.errors.forEach(error => this.addDiagnosticForError(error, node));
    }
    addDiagnosticForError(error, node$1) {
        const diagnostic = {
            source: this.source,
            severity: node.DiagnosticSeverity.Error,
            range: this.rangeFromHerbError(error),
            message: error.message,
            code: error.type,
            data: {
                error: error.toJSON(),
                node: node$1.toJSON()
            }
        };
        this.diagnostics.push(diagnostic);
    }
    rangeFromHerbError(error) {
        return node.Range.create(node.Position.create(error.location.start.line - 1, error.location.start.column), node.Position.create(error.location.end.line - 1, error.location.end.column));
    }
}
class ParserService {
    parseDocument(textDocument) {
        const content = textDocument.getText();
        const result = nodeWasm.Herb.parse(content);
        const errorVisitor = new ErrorVisitor();
        result.visit(errorVisitor);
        return {
            document: result.value,
            diagnostics: errorVisitor.diagnostics
        };
    }
}

function camelize(value) {
    return value.replace(/(?:[_-])([a-z0-9])/g, (_, char) => char.toUpperCase());
}
function dasherize(value) {
    return value.replace(/([A-Z])/g, (_, char) => `-${char.toLowerCase()}`);
}
function capitalize(value) {
    return value.charAt(0).toUpperCase() + value.slice(1);
}
function lintToDignosticSeverity(severity) {
    switch (severity) {
        case "error": return node.DiagnosticSeverity.Error;
        case "warning": return node.DiagnosticSeverity.Warning;
        case "info": return node.DiagnosticSeverity.Information;
        case "hint": return node.DiagnosticSeverity.Hint;
    }
}
/**
 * Returns a Range that spans the entire document
 */
function getFullDocumentRange(document) {
    const lastLine = document.lineCount - 1;
    const lastLineText = document.getText({
        start: node.Position.create(lastLine, 0),
        end: node.Position.create(lastLine + 1, 0)
    });
    const lastLineLength = lastLineText.length;
    return {
        start: node.Position.create(0, 0),
        end: node.Position.create(lastLine, lastLineLength)
    };
}

const OPEN_CONFIG_ACTION$1 = 'Open .herb.yml';
class LinterService {
    constructor(connection, settings, project) {
        this.source = "Herb Linter ";
        this.allRules = linter.rules;
        this.customRulesLoaded = false;
        this.failedCustomRules = new Map();
        this.hasShownCustomRuleWarning = false;
        this.customRulePaths = new Map();
        this.connection = connection;
        this.settings = settings;
        this.project = project;
    }
    /**
     * Rebuild the linter when config changes
     * This ensures the linter uses the latest rule configuration
     */
    rebuildLinter() {
        this.linter = undefined;
        this.allRules = linter.rules;
        this.customRulesLoaded = false;
        this.hasShownCustomRuleWarning = false;
        this.failedCustomRules.clear();
        this.customRulePaths.clear();
    }
    /**
     * Load custom rules from the project and merge with built-in rules
     */
    async loadCustomRules() {
        if (this.customRulesLoaded) {
            return;
        }
        const baseDir = this.project.projectPath;
        try {
            const { rules: customRules, ruleInfo, warnings: duplicateWarnings } = await loader.loadCustomRules({ baseDir, silent: true });
            if (customRules.length > 0) {
                this.connection.console.log(`[Linter] Loaded ${customRules.length} custom rules: ${ruleInfo.map(r => r.name).join(', ')}`);
                ruleInfo.forEach(({ name, path }) => {
                    this.customRulePaths.set(name, path);
                });
                this.allRules = [...linter.rules, ...customRules];
                if (duplicateWarnings.length > 0) {
                    duplicateWarnings.forEach(warning => {
                        this.connection.console.warn(`[Linter] ${warning}`);
                    });
                }
            }
            this.customRulesLoaded = true;
        }
        catch (error) {
            const errorMessage = error instanceof Error ? error.message : String(error);
            this.connection.console.error(`[Linter] Failed to load custom rules: ${errorMessage}`);
            this.failedCustomRules.set('custom-rules', errorMessage);
            this.customRulesLoaded = true;
        }
    }
    /**
     * Show warning message to user about failed custom rules
     */
    showCustomRuleWarnings() {
        if (this.failedCustomRules.size === 0 || this.hasShownCustomRuleWarning) {
            return;
        }
        this.hasShownCustomRuleWarning = true;
        const failures = Array.from(this.failedCustomRules.entries());
        const message = failures.length === 1
            ? `Failed to load custom linter rules: ${failures[0][1]}`
            : `Failed to load custom linter rules:\n${failures.map(([_, error], i) => `${i + 1}. ${error}`).join('\n')}`;
        if (this.settings.hasShowDocumentCapability) {
            this.connection.window.showWarningMessage(message, { title: OPEN_CONFIG_ACTION$1 }).then(action => {
                if ((action === null || action === void 0 ? void 0 : action.title) === OPEN_CONFIG_ACTION$1) {
                    const configPath = `${this.project.projectPath}/.herb.yml`;
                    this.connection.window.showDocument({ uri: `file://${configPath}`, takeFocus: true });
                }
            });
        }
        else {
            this.connection.window.showWarningMessage(message);
        }
    }
    async lintDocument(textDocument) {
        var _a, _b, _c, _d;
        const settings = await this.settings.getDocumentSettings(textDocument.uri);
        const linterEnabled = (_b = (_a = settings === null || settings === void 0 ? void 0 : settings.linter) === null || _a === void 0 ? void 0 : _a.enabled) !== null && _b !== void 0 ? _b : true;
        if (!linterEnabled) {
            return { diagnostics: [] };
        }
        const projectConfig = this.settings.projectConfig;
        if (!this.linter) {
            await this.loadCustomRules();
            this.showCustomRuleWarnings();
            const linterConfig = ((_c = projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.config) === null || _c === void 0 ? void 0 : _c.linter) || { enabled: true, rules: {} };
            const config$1 = config.Config.fromObject({
                linter: {
                    ...linterConfig,
                    rules: {
                        ...linterConfig.rules,
                        'parser-no-errors': { enabled: false }
                    }
                }
            }, { projectPath: (projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.projectPath) || process.cwd() });
            const filteredRules = linter.Linter.filterRulesByConfig(this.allRules, (_d = config$1.linter) === null || _d === void 0 ? void 0 : _d.rules);
            this.linter = new linter.Linter(nodeWasm.Herb, filteredRules, config$1, this.allRules);
        }
        const content = textDocument.getText();
        const lintResult = this.linter.lint(content, { fileName: textDocument.uri });
        const diagnostics = lintResult.offenses.map(offense => {
            const range = node.Range.create(node.Position.create(offense.location.start.line - 1, offense.location.start.column), node.Position.create(offense.location.end.line - 1, offense.location.end.column));
            const customRulePath = this.customRulePaths.get(offense.rule);
            const codeDescription = {
                href: customRulePath
                    ? `file://${customRulePath}`
                    : `https://herb-tools.dev/linter/rules/${offense.rule}`
            };
            return {
                source: this.source,
                severity: lintToDignosticSeverity(offense.severity),
                range,
                message: offense.message,
                code: offense.rule,
                data: { rule: offense.rule },
                codeDescription
            };
        });
        return { diagnostics };
    }
}

class Project {
    constructor(connection, projectPath) {
        this.projectPath = projectPath;
        this.connection = connection;
        this.herbBackend = nodeWasm.Herb;
    }
    async initialize() {
        await this.herbBackend.load();
    }
    async refresh() {
        // TODO
    }
}

const OPEN_CONFIG_ACTION = 'Open .herb.yml';
class FormattingService {
    constructor(connection, documents, project, settings) {
        this.preRewriters = [];
        this.postRewriters = [];
        this.failedRewriters = new Map();
        this.connection = connection;
        this.documents = documents;
        this.project = project;
        this.settings = settings;
    }
    async initialize() {
        var _a;
        try {
            this.config = await config.Config.loadForEditor(this.project.projectPath, version);
            this.connection.console.log(`[Formatting] Config loaded, formatter config: ${JSON.stringify(((_a = this.config) === null || _a === void 0 ? void 0 : _a.formatter) || 'none')}`);
            await this.loadConfiguredRewriters();
            this.connection.console.log("Herb formatter initialized successfully");
        }
        catch (error) {
            this.connection.console.error(`Failed to initialize Herb formatter: ${error}`);
        }
    }
    async refreshConfig(config$1) {
        if (config$1) {
            this.config = config$1;
        }
        else {
            try {
                this.config = await config.Config.loadForEditor(this.project.projectPath, version);
            }
            catch (error) {
                this.connection.console.error(`Failed to refresh Herb formatter config: ${error}`);
            }
        }
        await this.loadConfiguredRewriters();
    }
    async loadConfiguredRewriters() {
        var _a, _b, _c, _d;
        this.connection.console.log(`[Rewriter] Loading rewriters from config: ${JSON.stringify(((_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.formatter) === null || _b === void 0 ? void 0 : _b.rewriter) || 'none')}`);
        this.failedRewriters.clear();
        if (!((_d = (_c = this.config) === null || _c === void 0 ? void 0 : _c.formatter) === null || _d === void 0 ? void 0 : _d.rewriter)) {
            this.connection.console.log(`[Rewriter] No rewriter config found, clearing rewriters`);
            this.preRewriters = [];
            this.postRewriters = [];
            return;
        }
        try {
            const baseDir = this.config.projectPath || this.project.projectPath;
            this.connection.console.log(`[Rewriter] Using baseDir: ${baseDir}`);
            this.connection.console.log(`[Rewriter] config.projectPath: ${this.config.projectPath}`);
            this.connection.console.log(`[Rewriter] project.projectPath: ${this.project.projectPath}`);
            const preNames = this.config.formatter.rewriter.pre || [];
            const postNames = this.config.formatter.rewriter.post || [];
            const warnings = [];
            const allRewriterClasses = [];
            allRewriterClasses.push(...loader$1.builtinRewriters);
            const loader = new loader$1.CustomRewriterLoader({ baseDir });
            const { rewriters: customRewriters, duplicateWarnings } = await loader.loadRewritersWithInfo();
            allRewriterClasses.push(...customRewriters);
            warnings.push(...duplicateWarnings);
            const rewriterMap = new Map();
            for (const RewriterClass of allRewriterClasses) {
                const instance = new RewriterClass();
                if (rewriterMap.has(instance.name)) {
                    warnings.push(`Rewriter "${instance.name}" is defined multiple times. Using the last definition.`);
                }
                rewriterMap.set(instance.name, RewriterClass);
            }
            const preRewriters = [];
            const postRewriters = [];
            for (const name of preNames) {
                const RewriterClass = rewriterMap.get(name);
                if (!RewriterClass) {
                    warnings.push(`Pre-format rewriter "${name}" not found. Skipping.`);
                    continue;
                }
                if (!loader$1.isASTRewriterClass(RewriterClass)) {
                    warnings.push(`Rewriter "${name}" is not a pre-format rewriter. Skipping.`);
                    continue;
                }
                const instance = new RewriterClass();
                try {
                    await instance.initialize({ baseDir });
                    preRewriters.push(instance);
                }
                catch (error) {
                    const errorMessage = error instanceof Error ? error.message : String(error);
                    warnings.push(`Failed to initialize pre-format rewriter "${name}": ${errorMessage}`);
                    this.failedRewriters.set(name, errorMessage);
                }
            }
            for (const name of postNames) {
                const RewriterClass = rewriterMap.get(name);
                if (!RewriterClass) {
                    warnings.push(`Post-format rewriter "${name}" not found. Skipping.`);
                    continue;
                }
                if (!loader$1.isStringRewriterClass(RewriterClass)) {
                    warnings.push(`Rewriter "${name}" is not a post-format rewriter. Skipping.`);
                    continue;
                }
                const instance = new RewriterClass();
                try {
                    await instance.initialize({ baseDir });
                    postRewriters.push(instance);
                }
                catch (error) {
                    const errorMessage = error instanceof Error ? error.message : String(error);
                    warnings.push(`Failed to initialize post-format rewriter "${name}": ${errorMessage}`);
                    this.failedRewriters.set(name, errorMessage);
                }
            }
            this.preRewriters = preRewriters;
            this.postRewriters = postRewriters;
            this.connection.console.log(`[Rewriter] Loaded ${preRewriters.length} pre-rewriters: ${preRewriters.map(r => r.name).join(', ')}`);
            this.connection.console.log(`[Rewriter] Loaded ${postRewriters.length} post-rewriters: ${postRewriters.map(r => r.name).join(', ')}`);
            if (warnings.length > 0) {
                // Log each warning individually
                warnings.forEach(warning => {
                    this.connection.console.warn(`Rewriter: ${warning}`);
                });
                // Show a single combined warning message
                const message = warnings.length === 1
                    ? `Herb Rewriter: ${warnings[0]}`
                    : `Herb Rewriters (${warnings.length} failed):\n${warnings.map((w, i) => `${i + 1}. ${w}`).join('\n')}`;
                if (this.settings.hasShowDocumentCapability) {
                    this.connection.window.showWarningMessage(message, { title: OPEN_CONFIG_ACTION }).then(action => {
                        if ((action === null || action === void 0 ? void 0 : action.title) === OPEN_CONFIG_ACTION) {
                            const configPath = `${this.project.projectPath}/.herb.yml`;
                            this.connection.window.showDocument({ uri: `file://${configPath}`, takeFocus: true });
                        }
                    });
                }
                else {
                    this.connection.window.showWarningMessage(message);
                }
            }
        }
        catch (error) {
            this.connection.console.error(`Failed to load rewriters: ${error}`);
            this.preRewriters = [];
            this.postRewriters = [];
        }
    }
    async formatOnSave(document, reason) {
        this.connection.console.log(`[Formatting] formatOnSave called for ${document.uri}`);
        if (reason !== node.TextDocumentSaveReason.Manual) {
            this.connection.console.log(`[Formatting] Skipping: reason=${reason} (not manual)`);
            return [];
        }
        const filePath = document.uri.replace(/^file:\/\//, '');
        if (!this.shouldFormatFile(filePath)) {
            this.connection.console.log(`[Formatting] Skipping: file not in formatter config`);
            return [];
        }
        return this.performFormatting({ textDocument: { uri: document.uri }, options: { tabSize: 2, insertSpaces: true } });
    }
    shouldFormatFile(filePath) {
        if (filePath.endsWith('.herb.yml'))
            return false;
        if (!this.config)
            return true;
        const hasConfigFile = config.Config.exists(this.config.projectPath);
        if (!hasConfigFile)
            return true;
        const relativePath = filePath.replace('file://', '').replace(this.project.projectPath + '/', '');
        return this.config.isFormatterEnabledForPath(relativePath);
    }
    async getConfigWithSettings(uri) {
        var _a, _b, _c, _d;
        const settings = await this.settings.getDocumentSettings(uri);
        if (!this.config)
            return undefined;
        const projectFormatter = this.config.formatter || {};
        return {
            ...this.config,
            formatter: {
                ...projectFormatter,
                indentWidth: (_b = (_a = settings === null || settings === void 0 ? void 0 : settings.formatter) === null || _a === void 0 ? void 0 : _a.indentWidth) !== null && _b !== void 0 ? _b : projectFormatter.indentWidth,
                maxLineLength: (_d = (_c = settings === null || settings === void 0 ? void 0 : settings.formatter) === null || _c === void 0 ? void 0 : _c.maxLineLength) !== null && _d !== void 0 ? _d : projectFormatter.maxLineLength
            }
        };
    }
    async performFormatting(params) {
        const document = this.documents.get(params.textDocument.uri);
        if (!document) {
            return [];
        }
        try {
            const text = document.getText();
            const config = await this.getConfigWithSettings(params.textDocument.uri);
            this.connection.console.log(`[Formatting] Creating formatter with ${this.preRewriters.length} pre-rewriters, ${this.postRewriters.length} post-rewriters`);
            if (this.failedRewriters.size > 0) {
                const failedList = Array.from(this.failedRewriters.entries());
                const message = failedList.length === 1
                    ? `Herb Rewriter "${failedList[0][0]}" is not available: ${failedList[0][1]}`
                    : `Herb Rewriters (${failedList.length} not available):\n${failedList.map(([name, error], i) => `${i + 1}. ${name}: ${error}`).join('\n')}`;
                if (this.settings.hasShowDocumentCapability) {
                    this.connection.window.showWarningMessage(message, { title: OPEN_CONFIG_ACTION }).then(action => {
                        if ((action === null || action === void 0 ? void 0 : action.title) === OPEN_CONFIG_ACTION) {
                            const configPath = `${this.project.projectPath}/.herb.yml`;
                            this.connection.window.showDocument({ uri: `file://${configPath}`, takeFocus: true });
                        }
                    });
                }
                else {
                    this.connection.window.showWarningMessage(message);
                }
            }
            const formatter$1 = formatter.Formatter.from(this.project.herbBackend, config, {
                preRewriters: this.preRewriters,
                postRewriters: this.postRewriters
            });
            let newText = formatter$1.format(text);
            if (!newText.endsWith('\n')) {
                newText = newText + '\n';
            }
            if (newText === text) {
                return [];
            }
            const range = {
                start: node.Position.create(0, 0),
                end: node.Position.create(document.lineCount, 0)
            };
            return [{ range, newText }];
        }
        catch (error) {
            this.connection.console.error(`Formatting failed: ${error}`);
            return [];
        }
    }
    async formatDocument(params) {
        var _a;
        if (params.textDocument.uri.endsWith('.herb.yml')) {
            return [];
        }
        const settings = await this.settings.getDocumentSettings(params.textDocument.uri);
        if (((_a = settings === null || settings === void 0 ? void 0 : settings.formatter) === null || _a === void 0 ? void 0 : _a.enabled) === false) {
            return [];
        }
        const filePath = params.textDocument.uri.replace(/^file:\/\//, '');
        if (!this.shouldFormatFile(filePath)) {
            return [];
        }
        return this.performFormatting(params);
    }
    async formatDocumentIgnoreConfig(params) {
        return this.performFormatting(params);
    }
    async performRangeFormatting(params) {
        var _a, _b, _c;
        const document = this.documents.get(params.textDocument.uri);
        if (!document) {
            return [];
        }
        try {
            const config = await this.getConfigWithSettings(params.textDocument.uri);
            const formatter$1 = formatter.Formatter.from(this.project.herbBackend, config, {
                preRewriters: this.preRewriters,
                postRewriters: this.postRewriters
            });
            const rangeText = document.getText(params.range);
            const lines = rangeText.split('\n');
            let minIndentLevel = Infinity;
            const indentWidth = (_b = (_a = config === null || config === void 0 ? void 0 : config.formatter) === null || _a === void 0 ? void 0 : _a.indentWidth) !== null && _b !== void 0 ? _b : formatter.defaultFormatOptions.indentWidth;
            for (const line of lines) {
                const trimmedLine = line.trim();
                if (trimmedLine !== '') {
                    const indent = ((_c = line.match(/^\s*/)) === null || _c === void 0 ? void 0 : _c[0]) || '';
                    const indentLevel = Math.floor(indent.length / indentWidth);
                    minIndentLevel = Math.min(minIndentLevel, indentLevel);
                }
            }
            if (minIndentLevel === Infinity) {
                minIndentLevel = 0;
            }
            let textToFormat = rangeText;
            if (minIndentLevel > 0) {
                const minIndentString = ' '.repeat(minIndentLevel * indentWidth);
                textToFormat = lines.map(line => {
                    if (line.trim() === '') {
                        return line;
                    }
                    return line.startsWith(minIndentString) ? line.slice(minIndentString.length) : line;
                }).join('\n');
            }
            let formattedText = formatter$1.format(textToFormat);
            if (minIndentLevel > 0) {
                const formattedLines = formattedText.split('\n');
                const indentString = ' '.repeat(minIndentLevel * indentWidth);
                formattedText = formattedLines.map((line, _index) => {
                    if (line.trim() === '') {
                        return line;
                    }
                    return indentString + line;
                }).join('\n');
            }
            if (!formattedText.endsWith('\n')) {
                formattedText += '\n';
            }
            if (formattedText === rangeText) {
                return [];
            }
            return [{ range: params.range, newText: formattedText }];
        }
        catch (error) {
            this.connection.console.error(`Range formatting failed: ${error}`);
            return [];
        }
    }
    async formatRange(params) {
        if (params.textDocument.uri.endsWith('.herb.yml')) {
            return [];
        }
        const filePath = params.textDocument.uri.replace(/^file:\/\//, '');
        if (!this.shouldFormatFile(filePath)) {
            return [];
        }
        return this.performRangeFormatting(params);
    }
    async formatRangeIgnoreConfig(params) {
        return this.performRangeFormatting(params);
    }
}

/**
 * Configuration service for validation of .herb.yml configuration files
 */
class ConfigService {
    constructor(projectPath) {
        this.projectPath = projectPath;
    }
    async validateDocument(document) {
        var _a, _b;
        const diagnostics = [];
        if (!document.uri.endsWith('.herb.yml')) {
            return diagnostics;
        }
        const text = document.getText();
        const validationErrors = await config.Config.validateConfigText(text, {
            version,
            projectPath: this.projectPath
        });
        for (const error of validationErrors) {
            let line = (_a = error.line) !== null && _a !== void 0 ? _a : 0;
            let column = (_b = error.column) !== null && _b !== void 0 ? _b : 0;
            if (error.line === undefined && error.path.length > 0) {
                const searchKey = error.path[error.path.length - 1].toString();
                const lines = text.split('\n');
                for (let i = 0; i < lines.length; i++) {
                    if (lines[i].includes(searchKey + ':')) {
                        line = i;
                        column = lines[i].indexOf(searchKey);
                        break;
                    }
                }
            }
            const path = error.path.join('.');
            const message = path ? `${path}: ${error.message}` : error.message;
            const severity = error.severity ? lintToDignosticSeverity(error.severity) : node.DiagnosticSeverity.Error;
            diagnostics.push({
                severity,
                range: node.Range.create(line, column, line, column + 20),
                message: message,
                source: 'Herb Config ',
                code: error.code
            });
        }
        return diagnostics;
    }
}

class AutofixService {
    constructor(connection, config) {
        this.connection = connection;
        this.linter = this.buildLinter(config);
    }
    setConfig(config) {
        this.linter = this.buildLinter(config);
    }
    buildLinter(config) {
        return linter.Linter.from(nodeWasm.Herb, config);
    }
    async autofix(document) {
        try {
            const text = document.getText();
            const lintResult = this.linter.lint(text, { fileName: document.uri });
            const offensesToFix = lintResult.offenses;
            if (offensesToFix.length === 0)
                return [];
            const autofixResult = this.linter.autofix(text, { fileName: document.uri }, offensesToFix);
            if (autofixResult.source === text)
                return [];
            return [{ range: getFullDocumentRange(document), newText: autofixResult.source }];
        }
        catch (error) {
            this.connection.console.error(`[Autofix] Failed: ${error}`);
            return [];
        }
    }
}

class CodeActionService {
    constructor(project, config) {
        this.project = project;
        this.config = config;
        this.linter = linter.Linter.from(nodeWasm.Herb, config);
    }
    setConfig(config) {
        this.config = config;
    }
    createCodeActions(uri, diagnostics, documentText) {
        var _a;
        const actions = [];
        const linesWithDisableAll = new Set();
        const disableAllActions = [];
        const linterDiagnostics = diagnostics.filter(diagnostic => diagnostic.source === "Herb Linter ");
        for (const diagnostic of linterDiagnostics) {
            const ruleName = ((_a = diagnostic.data) === null || _a === void 0 ? void 0 : _a.rule) || (typeof diagnostic.code === 'string' ? diagnostic.code : undefined);
            if (!ruleName)
                continue;
            const disableLineAction = this.createDisableLineAction(uri, diagnostic, ruleName, documentText);
            if (disableLineAction) {
                actions.push(disableLineAction);
            }
            const disableInConfigAction = this.createDisableInConfigAction(diagnostic, ruleName);
            if (disableInConfigAction) {
                actions.push(disableInConfigAction);
            }
            const line = diagnostic.range.start.line;
            if (!linesWithDisableAll.has(line)) {
                const disableAllAction = this.createDisableAllAction(uri, diagnostic, documentText);
                if (disableAllAction) {
                    disableAllActions.push(disableAllAction);
                    linesWithDisableAll.add(line);
                }
            }
        }
        return actions.concat(disableAllActions);
    }
    autofixCodeActions(params, document) {
        if (this.config && !this.config.isLinterEnabled) {
            return [];
        }
        const codeActions = [];
        const text = document.getText();
        const lintResult = this.linter.lint(text, { fileName: document.uri });
        const offenses = lintResult.offenses;
        const relevantDiagnostics = params.context.diagnostics.filter(diagnostic => {
            return diagnostic.source === "Herb Linter " && this.isInRange(diagnostic.range, params.range);
        });
        for (const diagnostic of relevantDiagnostics) {
            const offense = offenses.find(offense => this.rangesEqual(this.offenseToRange(offense), diagnostic.range) && offense.rule === diagnostic.code);
            if (!offense) {
                continue;
            }
            const fixResult = this.linter.autofix(text, { fileName: document.uri }, [offense]);
            if (fixResult.fixed.length > 0 && fixResult.source !== text) {
                const codeAction = {
                    title: `Herb Linter: Fix "${offense.message}"`,
                    kind: node.CodeActionKind.QuickFix,
                    diagnostics: [diagnostic],
                    edit: this.createDocumentEdit(document, fixResult.source)
                };
                codeActions.push(codeAction);
            }
        }
        const allFixableOffenses = offenses.filter(offense => {
            const fixResult = this.linter.autofix(text, { fileName: document.uri }, [offense]);
            return fixResult.fixed.length > 0;
        });
        if (allFixableOffenses.length > 0) {
            const fixAllResult = this.linter.autofix(text, { fileName: document.uri }, allFixableOffenses);
            if (fixAllResult.fixed.length > 0 && fixAllResult.source !== text) {
                const fixAllAction = {
                    title: `Herb Linter: Fix all ${fixAllResult.fixed.length} autocorrectable offense${fixAllResult.fixed.length === 1 ? '' : 's'}`,
                    kind: node.CodeActionKind.SourceFixAll,
                    edit: this.createDocumentEdit(document, fixAllResult.source)
                };
                codeActions.push(fixAllAction);
            }
        }
        return codeActions;
    }
    createDisableLineAction(uri, diagnostic, ruleName, documentText) {
        const line = diagnostic.range.start.line;
        const edit = this.createDisableCommentEdit(uri, line, ruleName, documentText);
        if (!edit)
            return null;
        const action = {
            title: `Herb Linter: Disable \`${ruleName}\` for this line`,
            kind: node.CodeActionKind.QuickFix,
            diagnostics: [diagnostic],
            edit,
        };
        return action;
    }
    createDisableAllAction(uri, diagnostic, documentText) {
        const line = diagnostic.range.start.line;
        const edit = this.createDisableCommentEdit(uri, line, "all", documentText);
        if (!edit)
            return null;
        const action = {
            title: "Herb Linter: Disable all linter rules for this line",
            kind: node.CodeActionKind.QuickFix,
            diagnostics: [diagnostic],
            edit,
        };
        return action;
    }
    createDisableCommentEdit(uri, line, ruleName, documentText) {
        const lines = documentText.split("\n");
        if (line >= lines.length)
            return null;
        const lineText = lines[line];
        const existingDisableMatch = lineText.match(/<%#\s*herb:disable\s+(.+?)\s*%>/);
        let textEdit;
        if (existingDisableMatch) {
            const existingRules = existingDisableMatch[1].split(",").map(r => r.trim());
            if (existingRules.includes("all") || ruleName === "all") {
                if (existingRules.includes("all")) {
                    return null;
                }
                textEdit = node.TextEdit.replace(node.Range.create(line, 0, line, lineText.length), lineText.replace(/<%#\s*herb:disable\s+.+?\s*%>/, "<%# herb:disable all %>"));
            }
            else {
                if (existingRules.includes(ruleName)) {
                    return null;
                }
                const newRules = [...existingRules, ruleName].join(", ");
                textEdit = node.TextEdit.replace(node.Range.create(line, 0, line, lineText.length), lineText.replace(/<%#\s*herb:disable\s+.+?\s*%>/, `<%# herb:disable ${newRules} %>`));
            }
        }
        else {
            const disableComment = ruleName === "all"
                ? " <%# herb:disable all %>"
                : ` <%# herb:disable ${ruleName} %>`;
            textEdit = node.TextEdit.replace(node.Range.create(line, 0, line, lineText.length), lineText + disableComment);
        }
        const workspaceEdit = {
            changes: {
                [uri]: [textEdit]
            }
        };
        return workspaceEdit;
    }
    createDisableInConfigAction(diagnostic, ruleName) {
        const edit = this.createConfigDisableEdit(ruleName);
        if (!edit)
            return null;
        const projectPath = this.project.projectPath;
        const configPath = config.Config.configPathFromProjectPath(projectPath);
        const configUri = `file://${configPath}`;
        const action = {
            title: `Herb Linter: Disable \`${ruleName}\` in \`.herb.yml\``,
            kind: node.CodeActionKind.QuickFix,
            diagnostics: [diagnostic],
            edit,
            command: {
                title: 'Open .herb.yml',
                command: 'vscode.open',
                arguments: [configUri]
            }
        };
        return action;
    }
    createConfigDisableEdit(ruleName) {
        var _a, _b;
        try {
            const projectPath = this.project.projectPath;
            if (!projectPath) {
                return null;
            }
            if ((_a = this.config) === null || _a === void 0 ? void 0 : _a.isRuleDisabled(ruleName)) {
                return null;
            }
            const configPath = config.Config.configPathFromProjectPath(projectPath);
            const configUri = `file://${configPath}`;
            const mutation = {
                linter: {
                    rules: {
                        [ruleName]: { enabled: false }
                    }
                }
            };
            const configExists = config.Config.exists(configPath);
            let newContent = '';
            let textEdit;
            if (configExists) {
                try {
                    const existingYaml = config.Config.readRawYaml(configPath);
                    newContent = config.Config.applyMutationToYamlString(existingYaml, mutation);
                    const lines = existingYaml.split('\n');
                    const endLine = lines.length;
                    const endCharacter = ((_b = lines[lines.length - 1]) === null || _b === void 0 ? void 0 : _b.length) || 0;
                    textEdit = node.TextEdit.replace(node.Range.create(0, 0, endLine, endCharacter), newContent);
                }
                catch (error) {
                    console.error('[Code Action] Error processing existing config:', error);
                    return null;
                }
            }
            else {
                try {
                    newContent = config.Config.createConfigYamlString(mutation);
                    textEdit = node.TextEdit.insert({ line: 0, character: 0 }, newContent);
                }
                catch (error) {
                    console.error('[Code Action] Error creating new config:', error);
                    return null;
                }
            }
            let workspaceEdit;
            if (configExists) {
                workspaceEdit = {
                    changes: {
                        [configUri]: [textEdit]
                    }
                };
            }
            else {
                const createFile = {
                    kind: 'create',
                    uri: configUri
                };
                const textDocumentEdit = {
                    textDocument: node.OptionalVersionedTextDocumentIdentifier.create(configUri, null),
                    edits: [textEdit]
                };
                workspaceEdit = {
                    documentChanges: [createFile, textDocumentEdit]
                };
            }
            return workspaceEdit;
        }
        catch (error) {
            console.error('[Code Action] Unexpected error:', error);
            return null;
        }
    }
    createDocumentEdit(document, newText) {
        return {
            changes: {
                [document.uri]: [{
                        range: getFullDocumentRange(document),
                        newText
                    }]
            }
        };
    }
    offenseToRange(offense) {
        return {
            start: node.Position.create(offense.location.start.line - 1, offense.location.start.column),
            end: node.Position.create(offense.location.end.line - 1, offense.location.end.column)
        };
    }
    rangesEqual(r1, r2) {
        return (r1.start.line === r2.start.line &&
            r1.start.character === r2.start.character &&
            r1.end.line === r2.end.line &&
            r1.end.character === r2.end.character);
    }
    isInRange(diagnosticRange, requestedRange) {
        if (diagnosticRange.start.line > requestedRange.end.line)
            return false;
        if (diagnosticRange.end.line < requestedRange.start.line)
            return false;
        return true;
    }
}

class DocumentSaveService {
    constructor(connection, settings, autofixService, formattingService) {
        this.connection = connection;
        this.settings = settings;
        this.autofixService = autofixService;
        this.formattingService = formattingService;
    }
    /**
     * Apply only autofix edits on save.
     * Called by willSaveWaitUntil - formatting is handled separately by editor.formatOnSave
     */
    async applyFixes(document) {
        var _a;
        const settings = await this.settings.getDocumentSettings(document.uri);
        const fixOnSave = ((_a = settings === null || settings === void 0 ? void 0 : settings.linter) === null || _a === void 0 ? void 0 : _a.fixOnSave) !== false;
        this.connection.console.log(`[DocumentSave] applyFixes fixOnSave=${fixOnSave}`);
        if (!fixOnSave)
            return [];
        return this.autofixService.autofix(document);
    }
    /**
     * Apply autofix and formatting.
     * Called by onDocumentFormatting (manual format or editor.formatOnSave)
     */
    async applyFixesAndFormatting(document, reason) {
        var _a, _b, _c;
        const settings = await this.settings.getDocumentSettings(document.uri);
        const fixOnSave = ((_a = settings === null || settings === void 0 ? void 0 : settings.linter) === null || _a === void 0 ? void 0 : _a.fixOnSave) !== false;
        const formatterEnabled = (_c = (_b = settings === null || settings === void 0 ? void 0 : settings.formatter) === null || _b === void 0 ? void 0 : _b.enabled) !== null && _c !== void 0 ? _c : false;
        this.connection.console.log(`[DocumentSave] applyFixesAndFormatting fixOnSave=${fixOnSave}, formatterEnabled=${formatterEnabled}`);
        let autofixEdits = [];
        if (fixOnSave) {
            autofixEdits = await this.autofixService.autofix(document);
        }
        if (!formatterEnabled)
            return autofixEdits;
        if (autofixEdits.length === 0) {
            return this.formattingService.formatOnSave(document, reason);
        }
        const autofixedDocument = {
            ...document,
            uri: document.uri,
            getText: () => autofixEdits[0].newText,
        };
        return this.formattingService.formatOnSave(autofixedDocument, reason);
    }
}

class Service {
    constructor(connection, params) {
        this.connection = connection;
        this.settings = new Settings(params, this.connection);
        this.documentService = new DocumentService(this.connection);
        this.project = new Project(connection, this.settings.projectPath.replace("file://", ""));
        this.parserService = new ParserService();
        this.linterService = new LinterService(this.connection, this.settings, this.project);
        this.formattingService = new FormattingService(this.connection, this.documentService.documents, this.project, this.settings);
        this.autofixService = new AutofixService(this.connection, this.config);
        this.configService = new ConfigService(this.project.projectPath);
        this.codeActionService = new CodeActionService(this.project, this.config);
        this.diagnostics = new Diagnostics(this.connection, this.documentService, this.parserService, this.linterService, this.configService);
        this.documentSaveService = new DocumentSaveService(this.connection, this.settings, this.autofixService, this.formattingService);
        if (params.initializationOptions) {
            this.settings.globalSettings = params.initializationOptions;
        }
    }
    async init() {
        await this.project.initialize();
        await this.formattingService.initialize();
        try {
            this.config = await config.Config.loadForEditor(this.project.projectPath, version);
            this.codeActionService.setConfig(this.config);
            this.autofixService.setConfig(this.config);
            if (this.config.version && this.config.version !== version) {
                this.connection.console.warn(`Config file version (${this.config.version}) does not match current version (${version}). ` +
                    `Consider updating your .herb.yml file.`);
            }
        }
        catch (error) {
            this.connection.console.warn(`Failed to load config: ${error instanceof Error ? error.message : String(error)}. Using personal settings with defaults.`);
            this.config = config.Config.fromObject({
                linter: this.settings.globalSettings.linter,
                formatter: this.settings.globalSettings.formatter
            }, { projectPath: this.project.projectPath, version });
            this.codeActionService.setConfig(this.config);
            this.autofixService.setConfig(this.config);
        }
        await this.settings.initializeProjectConfig(this.config);
        await this.formattingService.refreshConfig(this.config);
        this.linterService.rebuildLinter();
        this.documentService.onDidClose((change) => {
            this.settings.documentSettings.delete(change.document.uri);
        });
        this.documentService.onDidChangeContent(async (change) => {
            await this.diagnostics.refreshDocument(change.document);
        });
    }
    async refresh() {
        await this.project.refresh();
        await this.formattingService.refreshConfig(this.config);
        await this.diagnostics.refreshAllDocuments();
    }
    async refreshConfig() {
        try {
            this.config = await config.Config.loadForEditor(this.project.projectPath, version);
            this.codeActionService.setConfig(this.config);
            this.autofixService.setConfig(this.config);
            if (this.config.version && this.config.version !== version) {
                this.connection.console.warn(`Config file version (${this.config.version}) does not match current version (${version}). ` +
                    `Consider updating your .herb.yml file.`);
            }
        }
        catch (error) {
            this.connection.console.warn(`Failed to load config: ${error instanceof Error ? error.message : String(error)}. Using personal settings with defaults.`);
            this.config = config.Config.fromObject({
                linter: this.settings.globalSettings.linter,
                formatter: this.settings.globalSettings.formatter
            }, { projectPath: this.project.projectPath, version });
            this.codeActionService.setConfig(this.config);
            this.autofixService.setConfig(this.config);
        }
        await this.settings.refreshProjectConfig(this.config);
        await this.formattingService.refreshConfig(this.config);
        this.linterService.rebuildLinter();
    }
}

class Server {
    constructor() {
        this.connection = node.createConnection(node.ProposedFeatures.all);
        this.setupEventHandlers();
    }
    setupEventHandlers() {
        this.connection.onInitialize(async (params) => {
            this.service = new Service(this.connection, params);
            await this.service.init();
            this.service.documentService.documents.onWillSaveWaitUntil(async (event) => {
                return this.service.documentSaveService.applyFixes(event.document);
            });
            const result = {
                capabilities: {
                    textDocumentSync: {
                        openClose: true,
                        change: node.TextDocumentSyncKind.Incremental,
                        willSave: true,
                        willSaveWaitUntil: true,
                        save: {
                            includeText: false
                        }
                    },
                    documentFormattingProvider: true,
                    documentRangeFormattingProvider: true,
                    codeActionProvider: {
                        codeActionKinds: [node.CodeActionKind.QuickFix, node.CodeActionKind.SourceFixAll]
                    },
                },
            };
            if (this.service.settings.hasWorkspaceFolderCapability) {
                result.capabilities.workspace = {
                    workspaceFolders: {
                        supported: true,
                    },
                };
            }
            return result;
        });
        this.connection.onInitialized(() => {
            if (this.service.settings.hasConfigurationCapability) {
                this.connection.client.register(node.DidChangeConfigurationNotification.type, undefined);
            }
            if (this.service.settings.hasWorkspaceFolderCapability) {
                this.connection.workspace.onDidChangeWorkspaceFolders((_event) => {
                    this.connection.console.log("Workspace folder change event received.");
                });
            }
            const patterns = config.Config.getDefaultFilePatterns().map(globPattern => ({
                globPattern
            }));
            this.connection.client.register(node.DidChangeWatchedFilesNotification.type, {
                watchers: [
                    ...patterns,
                    { globPattern: `**/.herb.yml` },
                    { globPattern: `**/.herb/rules/**/*.mjs` },
                    { globPattern: `**/.herb/rewriters/**/*.mjs` },
                ],
            });
        });
        this.connection.onDidChangeConfiguration(async (change) => {
            if (this.service.settings.hasConfigurationCapability) {
                // Reset all cached document settings
                this.service.settings.documentSettings.clear();
            }
            else {
                this.service.settings.globalSettings = ((change.settings.languageServerHerb || this.service.settings.defaultSettings));
            }
            await this.service.refresh();
        });
        this.connection.onDidOpenTextDocument(async (params) => {
            const document = this.service.documentService.get(params.textDocument.uri);
            if (document) {
                await this.service.diagnostics.refreshDocument(document);
            }
        });
        this.connection.onDidChangeWatchedFiles(async (params) => {
            for (const event of params.changes) {
                const isConfigChange = event.uri.endsWith("/.herb.yml");
                const isCustomRuleChange = event.uri.includes("/.herb/rules/");
                const isCustomRewriterChange = event.uri.includes("/.herb/rewriters/");
                if (isConfigChange) {
                    await this.service.refreshConfig();
                    const documents = this.service.documentService.getAll();
                    await Promise.all(documents.map(document => this.service.diagnostics.refreshDocument(document)));
                }
                else if (isCustomRuleChange || isCustomRewriterChange) {
                    if (isCustomRuleChange) {
                        this.connection.console.log(`[Linter] Custom rule changed: ${event.uri}`);
                        this.service.linterService.rebuildLinter();
                    }
                    if (isCustomRewriterChange) {
                        this.connection.console.log(`[Rewriter] Custom rewriter changed: ${event.uri}`);
                        await this.service.formattingService.refreshConfig(this.service.config);
                    }
                    const documents = this.service.documentService.getAll();
                    await Promise.all(documents.map(document => this.service.diagnostics.refreshDocument(document)));
                }
            }
        });
        this.connection.onDocumentFormatting(async (params) => {
            const document = this.service.documentService.get(params.textDocument.uri);
            if (!document)
                return [];
            return this.service.documentSaveService.applyFixesAndFormatting(document, node.TextDocumentSaveReason.Manual);
        });
        this.connection.onDocumentRangeFormatting((params) => {
            return this.service.formattingService.formatRange(params);
        });
        this.connection.onCodeAction((params) => {
            const document = this.service.documentService.get(params.textDocument.uri);
            if (!document)
                return [];
            const diagnostics = params.context.diagnostics;
            const documentText = document.getText();
            const linterDisableCodeActions = this.service.codeActionService.createCodeActions(params.textDocument.uri, diagnostics, documentText);
            const autofixCodeActions = this.service.codeActionService.autofixCodeActions(params, document);
            return autofixCodeActions.concat(linterDisableCodeActions);
        });
    }
    listen() {
        this.connection.listen();
    }
}

class CLI {
    constructor() {
        this.usage = `
  Usage: herb-language-server [options]

  Options:
    --stdio          use stdio
    --node-ipc       use node-ipc
    --socket=<port>  use socket
`;
    }
    run() {
        if (process.argv.length <= 2) {
            console.error(`Error: Connection input stream is not set. Set command line parameters: '--node-ipc', '--stdio' or '--socket=<port>'`);
            console.error(this.usage);
            process.exit(1);
        }
        const server = new Server();
        server.listen();
    }
}

exports.CLI = CLI;
exports.Diagnostics = Diagnostics;
exports.DocumentService = DocumentService;
exports.FormattingService = FormattingService;
exports.Project = Project;
exports.Server = Server;
exports.Service = Service;
exports.Settings = Settings;
exports.UnreachableCodeCollector = UnreachableCodeCollector;
exports.camelize = camelize;
exports.capitalize = capitalize;
exports.dasherize = dasherize;
exports.getFullDocumentRange = getFullDocumentRange;
exports.lintToDignosticSeverity = lintToDignosticSeverity;
//# sourceMappingURL=index.cjs.map
