import { CodeActionKind, Range, Position, TextEdit, OptionalVersionedTextDocumentIdentifier } from "vscode-languageserver/node";
import { Config } from "@herb-tools/config";
import { Herb } from "@herb-tools/node-wasm";
import { Linter } from "@herb-tools/linter";
import { getFullDocumentRange } from "./utils";
export class CodeActionService {
    constructor(project, config) {
        this.project = project;
        this.config = config;
        this.linter = Linter.from(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: 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: 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: 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: 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 = TextEdit.replace(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 = TextEdit.replace(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 = TextEdit.replace(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.configPathFromProjectPath(projectPath);
        const configUri = `file://${configPath}`;
        const action = {
            title: `Herb Linter: Disable \`${ruleName}\` in \`.herb.yml\``,
            kind: 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.configPathFromProjectPath(projectPath);
            const configUri = `file://${configPath}`;
            const mutation = {
                linter: {
                    rules: {
                        [ruleName]: { enabled: false }
                    }
                }
            };
            const configExists = Config.exists(configPath);
            let newContent = '';
            let textEdit;
            if (configExists) {
                try {
                    const existingYaml = Config.readRawYaml(configPath);
                    newContent = 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 = TextEdit.replace(Range.create(0, 0, endLine, endCharacter), newContent);
                }
                catch (error) {
                    console.error('[Code Action] Error processing existing config:', error);
                    return null;
                }
            }
            else {
                try {
                    newContent = Config.createConfigYamlString(mutation);
                    textEdit = 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: 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: Position.create(offense.location.start.line - 1, offense.location.start.column),
            end: 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;
    }
}
//# sourceMappingURL=code_action_service.js.map