"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.FoldingRangeProviderImpl = void 0;
const typescript_1 = __importDefault(require("typescript"));
const vscode_languageserver_1 = require("vscode-languageserver");
const documents_1 = require("../../../lib/documents");
const utils_1 = require("../../../utils");
const utils_2 = require("../utils");
const utils_3 = require("./utils");
const indentFolding_1 = require("../../../lib/foldingRange/indentFolding");
const svelte_ast_utils_1 = require("../svelte-ast-utils");
class FoldingRangeProviderImpl {
    constructor(lsAndTsDocResolver, configManager) {
        this.lsAndTsDocResolver = lsAndTsDocResolver;
        this.configManager = configManager;
        this.foldEndPairCharacters = ['}', ']', ')', '`', '>'];
    }
    async getFoldingRanges(document) {
        // don't use ls.getProgram unless it's necessary
        // this feature is pure syntactic and doesn't need type information
        const { lang, tsDoc } = await this.lsAndTsDocResolver.getLsForSyntheticOperations(document);
        const foldingRanges = tsDoc.parserError && !document.moduleScriptInfo && !document.scriptInfo
            ? []
            : lang.getOutliningSpans(tsDoc.filePath);
        const lineFoldingOnly = !!this.configManager.getClientCapabilities()?.textDocument?.foldingRange
            ?.lineFoldingOnly;
        const result = foldingRanges
            .filter((span) => !(0, utils_3.isTextSpanInGeneratedCode)(tsDoc.getFullText(), span.textSpan))
            .map((span) => ({
            originalRange: this.mapToOriginalRange(tsDoc, span.textSpan, document),
            span
        }))
            .map(({ originalRange, span }) => this.convertOutliningSpan(span, document, originalRange, lineFoldingOnly))
            .filter(utils_1.isNotNullOrUndefined)
            .concat(this.collectSvelteBlockFolding(document, tsDoc, lineFoldingOnly))
            .concat(this.getSvelteTagFoldingIfParserError(document, tsDoc))
            .filter(lineFoldingOnly ? (r) => r.startLine < r.endLine : (r) => r.startLine <= r.endLine);
        return result;
    }
    mapToOriginalRange(tsDoc, textSpan, document) {
        const range = (0, documents_1.mapRangeToOriginal)(tsDoc, (0, utils_2.convertRange)(tsDoc, textSpan));
        const startOffset = document.offsetAt(range.start);
        if (range.start.line < 0 || range.end.line < 0 || range.start.line > range.end.line) {
            return;
        }
        if ((0, documents_1.isInTag)(range.start, document.scriptInfo) ||
            (0, documents_1.isInTag)(range.start, document.moduleScriptInfo)) {
            return range;
        }
        const endOffset = document.offsetAt(range.end);
        const originalText = document.getText().slice(startOffset, endOffset);
        if (originalText.length === 0) {
            return;
        }
        const generatedText = tsDoc.getText(textSpan.start, textSpan.start + textSpan.length);
        const oneToOne = originalText.trim() === generatedText.trim();
        if (oneToOne) {
            return range;
        }
    }
    /**
     * Doing this here with the svelte2tsx's svelte ast is slightly
     * less prone to error and faster than
     * using the svelte ast in the svelte plugins.
     */
    collectSvelteBlockFolding(document, tsDoc, lineFoldingOnly) {
        if (tsDoc.parserError) {
            return [];
        }
        const ranges = [];
        const provider = this;
        const enter = function (node, parent, key) {
            if (key === 'attributes') {
                this.skip();
            }
            // use sub-block for await block
            if (!node.type.endsWith('Block') || node.type === 'AwaitBlock') {
                return;
            }
            if (node.type === 'IfBlock') {
                provider.getIfBlockFolding(node, document, ranges);
                return;
            }
            if ((0, svelte_ast_utils_1.isElseBlockWithElseIf)(node)) {
                return;
            }
            if ((node.type === 'CatchBlock' || node.type === 'ThenBlock') && (0, svelte_ast_utils_1.isAwaitBlock)(parent)) {
                const expressionEnd = (node.type === 'CatchBlock' ? parent.error?.end : parent.value?.end) ??
                    document.getText().indexOf('}', node.start);
                const beforeBlockStartTagEnd = document.getText().indexOf('}', expressionEnd);
                if (beforeBlockStartTagEnd == -1) {
                    return;
                }
                ranges.push(provider.createFoldingRange(document, beforeBlockStartTagEnd + 1, node.end));
                return;
            }
            if ((0, svelte_ast_utils_1.isEachBlock)(node)) {
                const start = document.getText().indexOf('}', (node.key ?? node.expression).end);
                const elseStart = node.else
                    ? (0, svelte_ast_utils_1.findElseBlockTagStart)(document.getText(), node.else)
                    : -1;
                ranges.push(provider.createFoldingRange(document, start, elseStart === -1 ? node.end : elseStart));
                return;
            }
            if ('expression' in node && node.expression && typeof node.expression === 'object') {
                const start = provider.getStartForNodeWithExpression(node, document);
                const end = node.end;
                ranges.push(provider.createFoldingRange(document, start, end));
                return;
            }
            if (node.start != null && node.end != null) {
                const start = node.start;
                const end = node.end;
                ranges.push(provider.createFoldingRange(document, start, end));
            }
        };
        tsDoc.walkSvelteAst({
            enter
        });
        if (lineFoldingOnly) {
            return ranges.map((r) => ({
                startLine: r.startLine,
                endLine: this.previousLineOfEndLine(r.startLine, r.endLine)
            }));
        }
        return ranges;
    }
    getIfBlockFolding(node, document, ranges) {
        const typed = node;
        const documentText = document.getText();
        const start = this.getStartForNodeWithExpression(typed, document);
        const end = (0, svelte_ast_utils_1.hasElseBlock)(typed)
            ? (0, svelte_ast_utils_1.findElseBlockTagStart)(documentText, typed.else)
            : (0, svelte_ast_utils_1.findIfBlockEndTagStart)(documentText, typed);
        ranges.push(this.createFoldingRange(document, start, end));
    }
    getStartForNodeWithExpression(node, document) {
        return document.getText().indexOf('}', node.expression.end) + 1;
    }
    createFoldingRange(document, start, end) {
        const range = (0, documents_1.toRange)(document, start, end);
        return {
            startLine: range.start.line,
            startCharacter: range.start.character,
            endLine: range.end.line,
            endCharacter: range.end.character
        };
    }
    convertOutliningSpan(span, document, originalRange, lineFoldingOnly) {
        if (!originalRange) {
            return null;
        }
        const end = lineFoldingOnly
            ? this.adjustFoldingEndToNotHideEnd(originalRange, document)
            : originalRange.end;
        const result = {
            startLine: originalRange.start.line,
            endLine: end.line,
            kind: this.getFoldingRangeKind(span),
            startCharacter: lineFoldingOnly ? undefined : originalRange.start.character,
            endCharacter: lineFoldingOnly ? undefined : end.character
        };
        return result;
    }
    getFoldingRangeKind(span) {
        switch (span.kind) {
            case typescript_1.default.OutliningSpanKind.Comment:
                return vscode_languageserver_1.FoldingRangeKind.Comment;
            case typescript_1.default.OutliningSpanKind.Region:
                return vscode_languageserver_1.FoldingRangeKind.Region;
            case typescript_1.default.OutliningSpanKind.Imports:
                return vscode_languageserver_1.FoldingRangeKind.Imports;
            case typescript_1.default.OutliningSpanKind.Code:
            default:
                return undefined;
        }
    }
    adjustFoldingEndToNotHideEnd(range, document) {
        // don't fold end bracket, brace...
        if (range.end.character > 0) {
            const text = document.getText();
            const offsetBeforeEnd = document.offsetAt({
                line: range.end.line,
                character: range.end.character - 1
            });
            const foldEndCharacter = text[offsetBeforeEnd];
            if (this.foldEndPairCharacters.includes(foldEndCharacter)) {
                return { line: this.previousLineOfEndLine(range.start.line, range.end.line) };
            }
        }
        return range.end;
    }
    getSvelteTagFoldingIfParserError(document, tsDoc) {
        if (!tsDoc.parserError) {
            return [];
        }
        const htmlTemplateRanges = this.getHtmlTemplateRangesForChecking(document);
        return (0, indentFolding_1.indentBasedFoldingRange)({
            document,
            skipFold: (_, lineContent) => {
                return !/{\s*(#|\/|:)/.test(lineContent);
            },
            ranges: htmlTemplateRanges
        });
    }
    getHtmlTemplateRangesForChecking(document) {
        const ranges = [];
        const excludeTags = [
            document.templateInfo,
            document.moduleScriptInfo,
            document.scriptInfo,
            document.styleInfo
        ]
            .filter(utils_1.isNotNullOrUndefined)
            .map((info) => ({
            startLine: document.positionAt(info.container.start).line,
            endLine: document.positionAt(info.container.end).line
        }))
            .sort((a, b) => a.startLine - b.startLine);
        if (excludeTags.length === 0) {
            return [{ startLine: 0, endLine: document.lineCount - 1 }];
        }
        if (excludeTags[0].startLine > 0) {
            ranges.push({
                startLine: 0,
                endLine: excludeTags[0].startLine - 1
            });
        }
        for (let index = 0; index < excludeTags.length; index++) {
            const element = excludeTags[index];
            const next = excludeTags[index + 1];
            ranges.push({
                startLine: element.endLine + 1,
                endLine: next ? next.startLine - 1 : document.lineCount - 1
            });
        }
        return ranges;
    }
    previousLineOfEndLine(startLine, endLine) {
        return Math.max(endLine - 1, startLine);
    }
}
exports.FoldingRangeProviderImpl = FoldingRangeProviderImpl;
//# sourceMappingURL=FoldingRangeProvider.js.map