"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DocumentHighlightProviderImpl = void 0;
const typescript_1 = __importDefault(require("typescript"));
const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol");
const vscode_languageserver_types_1 = require("vscode-languageserver-types");
const documents_1 = require("../../../lib/documents");
const utils_1 = require("../../../utils");
const utils_2 = require("../utils");
const utils_3 = require("./utils");
const svelte_ast_utils_1 = require("../svelte-ast-utils");
class DocumentHighlightProviderImpl {
    constructor(lsAndTsDocResolver) {
        this.lsAndTsDocResolver = lsAndTsDocResolver;
    }
    async findDocumentHighlight(document, position) {
        const { tsDoc } = await this.lsAndTsDocResolver.getLsForSyntheticOperations(document);
        const svelteResult = await this.getSvelteDocumentHighlight(document, tsDoc, position);
        if (svelteResult) {
            return svelteResult;
        }
        const { lang } = await this.lsAndTsDocResolver.getLSAndTSDoc(document);
        const offset = tsDoc.offsetAt(tsDoc.getGeneratedPosition(position));
        const highlights = lang
            .getDocumentHighlights(tsDoc.filePath, offset, [tsDoc.filePath])
            ?.filter((highlight) => highlight.fileName === tsDoc.filePath);
        if (!highlights?.length) {
            return null;
        }
        const result = highlights
            .flatMap((highlight) => highlight.highlightSpans)
            .filter(this.notInGeneratedCode(tsDoc.getFullText()))
            .map((highlight) => vscode_languageserver_protocol_1.DocumentHighlight.create((0, utils_2.convertToLocationRange)(tsDoc, highlight.textSpan), this.convertHighlightKind(highlight)))
            .filter((highlight) => !(0, utils_1.isSamePosition)(highlight.range.start, highlight.range.end));
        if (!result.length) {
            return null;
        }
        return result;
    }
    convertHighlightKind(highlight) {
        return highlight.kind === typescript_1.default.HighlightSpanKind.writtenReference
            ? vscode_languageserver_types_1.DocumentHighlightKind.Write
            : vscode_languageserver_types_1.DocumentHighlightKind.Read;
    }
    async getSvelteDocumentHighlight(document, tsDoc, position) {
        if ((0, documents_1.inStyleOrScript)(document, position)) {
            return null;
        }
        const offset = document.offsetAt(position);
        const offsetStart = Math.max(offset - 10, 0);
        const charactersAroundOffset = document
            .getText()
            // use last 10 and next 10 characters, should cover 99% of all cases
            .substr(offsetStart, 20);
        if (!['#', '/', ':', '@', 'then', 'catch'].some((keyword) => charactersAroundOffset.includes(keyword))) {
            return null;
        }
        const candidate = this.findCandidateSvelteTag(tsDoc, offset);
        if (!candidate) {
            return null;
        }
        if (candidate.type.endsWith('Tag')) {
            return this.getTagHighlight(offset, document, candidate);
        }
        if (candidate.type.endsWith('Block')) {
            return this.getBlockHighlight(offset, document, candidate);
        }
        return null;
    }
    findCandidateSvelteTag(tsDoc, offset) {
        let candidate;
        const subBlocks = ['ThenBlock', 'CatchBlock', 'PendingBlock', 'ElseBlock'];
        tsDoc.walkSvelteAst({
            enter(node, parent, key) {
                if (node.type === 'Fragment') {
                    return;
                }
                const templateNode = node;
                const isWithin = templateNode.start <= offset && templateNode.end >= offset;
                const canSkip = !isWithin ||
                    key === 'expression' ||
                    key === 'context' ||
                    ((parent.type === 'InlineComponent' || parent.type === 'Element') &&
                        key !== 'children');
                if (canSkip) {
                    this.skip();
                    return;
                }
                if (node.type.endsWith('Tag')) {
                    candidate = templateNode;
                    return;
                }
                // don't use sub-blocks so we can highlight the whole block
                if (node.type.endsWith('Block') && !subBlocks.includes(node.type)) {
                    if (
                    // else if
                    node.type === 'IfBlock' &&
                        parent.type === 'ElseBlock' &&
                        parent.start === node.start) {
                        return;
                    }
                    candidate = templateNode;
                    return;
                }
            }
        });
        return candidate;
    }
    getTagHighlight(offset, document, candidate) {
        const name = candidate.type === 'RawMustacheTag'
            ? 'html'
            : candidate.type.replace('Tag', '').toLocaleLowerCase();
        const startTag = '@' + name;
        const indexOfName = document.getText().indexOf(startTag, candidate.start);
        if (indexOfName < 0 || indexOfName > offset || candidate.start + startTag.length < offset) {
            return null;
        }
        return [
            {
                kind: vscode_languageserver_types_1.DocumentHighlightKind.Read,
                range: vscode_languageserver_types_1.Range.create(document.positionAt(indexOfName), document.positionAt(indexOfName + startTag.length))
            }
        ];
    }
    getBlockHighlight(offset, document, candidate) {
        const name = candidate.type.replace('Block', '').toLowerCase();
        const startTag = '#' + name;
        const startTagStart = document.getText().indexOf(startTag, candidate.start);
        if (startTagStart < 0) {
            return null;
        }
        const ranges = [];
        ranges.push([startTagStart, startTagStart + startTag.length]);
        const content = document.getText();
        const endTag = '/' + name;
        const endTagStart = content.lastIndexOf(endTag, candidate.end);
        if (endTagStart < startTagStart) {
            return null; // can happen in loose parser mode for unclosed tags
        }
        ranges.push([endTagStart, endTagStart + endTag.length]);
        if (candidate.type === 'EachBlock' && candidate.else) {
            const elseStart = content.lastIndexOf(':else', candidate.else.start);
            ranges.push([elseStart, elseStart + ':else'.length]);
        }
        ranges.push(...this.getElseHighlightsForIfBlock(candidate, content), ...this.getAwaitBlockHighlight(candidate, content));
        if (!ranges.some(([start, end]) => offset >= start && offset <= end)) {
            return null;
        }
        return ranges.map(([start, end]) => ({
            range: vscode_languageserver_types_1.Range.create(document.positionAt(start), document.positionAt(end)),
            kind: vscode_languageserver_types_1.DocumentHighlightKind.Read
        }));
    }
    getElseHighlightsForIfBlock(candidate, content) {
        if (candidate.type !== 'IfBlock' || !candidate.else) {
            return [];
        }
        const ranges = new Map();
        (0, svelte_ast_utils_1.walkSvelteAst)(candidate.else, {
            enter(node) {
                const templateNode = node;
                if (templateNode.type === 'IfBlock' && templateNode.elseif) {
                    const elseIfStart = content.lastIndexOf(':else if', templateNode.expression.start);
                    if (elseIfStart > 0) {
                        ranges.set(elseIfStart, [elseIfStart, elseIfStart + ':else if'.length]);
                    }
                }
                if (templateNode.type === 'ElseBlock') {
                    const elseStart = content.lastIndexOf(':else', templateNode.start);
                    if (elseStart > 0 &&
                        content.slice(elseStart, elseStart + ':else if'.length) !== ':else if') {
                        ranges.set(elseStart, [elseStart, elseStart + ':else'.length]);
                    }
                }
            }
        });
        return Array.from(ranges.values());
    }
    getAwaitBlockHighlight(candidate, content) {
        if (candidate.type !== 'AwaitBlock' || (candidate.then.skip && candidate.catch.skip)) {
            return [];
        }
        const ranges = [];
        if (candidate.value) {
            const thenKeyword = candidate.pending.skip ? 'then' : ':then';
            const thenStart = content.lastIndexOf(thenKeyword, candidate.value.start);
            ranges.push([thenStart, thenStart + thenKeyword.length]);
        }
        // {#await promise catch error} or {:catch error}
        if (candidate.error) {
            const catchKeyword = candidate.pending.skip && candidate.then.skip ? 'catch' : ':catch';
            const catchStart = content.lastIndexOf(catchKeyword, candidate.error.start);
            ranges.push([catchStart, catchStart + catchKeyword.length]);
        }
        else if (!candidate.catch.skip) {
            // {:catch}
            const catchStart = content.indexOf(':catch', candidate.catch.start);
            ranges.push([catchStart, catchStart + ':catch'.length]);
        }
        return ranges;
    }
    notInGeneratedCode(text) {
        return (ref) => {
            return !(0, utils_3.isInGeneratedCode)(text, ref.textSpan.start, ref.textSpan.start + ref.textSpan.length);
        };
    }
}
exports.DocumentHighlightProviderImpl = DocumentHighlightProviderImpl;
//# sourceMappingURL=DocumentHighlightProvider.js.map