(function (factory) {
    if (typeof module === "object" && typeof module.exports === "object") {
        var v = factory(require, exports);
        if (v !== undefined) module.exports = v;
    }
    else if (typeof define === "function" && define.amd) {
        define(["require", "exports", "../parser/cssScanner", "../parser/scssScanner", "../parser/lessScanner"], factory);
    }
})(function (require, exports) {
    /*---------------------------------------------------------------------------------------------
     *  Copyright (c) Microsoft Corporation. All rights reserved.
     *  Licensed under the MIT License. See License.txt in the project root for license information.
     *--------------------------------------------------------------------------------------------*/
    'use strict';
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.getFoldingRanges = getFoldingRanges;
    const cssScanner_1 = require("../parser/cssScanner");
    const scssScanner_1 = require("../parser/scssScanner");
    const lessScanner_1 = require("../parser/lessScanner");
    function getFoldingRanges(document, context) {
        const ranges = computeFoldingRanges(document);
        return limitFoldingRanges(ranges, context);
    }
    function computeFoldingRanges(document) {
        function getStartLine(t) {
            return document.positionAt(t.offset).line;
        }
        function getEndLine(t) {
            return document.positionAt(t.offset + t.len).line;
        }
        function getScanner() {
            switch (document.languageId) {
                case 'scss':
                    return new scssScanner_1.SCSSScanner();
                case 'less':
                    return new lessScanner_1.LESSScanner();
                default:
                    return new cssScanner_1.Scanner();
            }
        }
        function tokenToRange(t, kind) {
            const startLine = getStartLine(t);
            const endLine = getEndLine(t);
            if (startLine !== endLine) {
                return {
                    startLine,
                    endLine,
                    kind
                };
            }
            else {
                return null;
            }
        }
        const ranges = [];
        const delimiterStack = [];
        const scanner = getScanner();
        scanner.ignoreComment = false;
        scanner.setSource(document.getText());
        let token = scanner.scan();
        let prevToken = null;
        while (token.type !== cssScanner_1.TokenType.EOF) {
            switch (token.type) {
                case cssScanner_1.TokenType.CurlyL:
                case scssScanner_1.InterpolationFunction:
                    {
                        delimiterStack.push({ line: getStartLine(token), type: 'brace', isStart: true });
                        break;
                    }
                case cssScanner_1.TokenType.CurlyR: {
                    if (delimiterStack.length !== 0) {
                        const prevDelimiter = popPrevStartDelimiterOfType(delimiterStack, 'brace');
                        if (!prevDelimiter) {
                            break;
                        }
                        let endLine = getEndLine(token);
                        if (prevDelimiter.type === 'brace') {
                            /**
                             * Other than the case when curly brace is not on a new line by itself, for example
                             * .foo {
                             *   color: red; }
                             * Use endLine minus one to show ending curly brace
                             */
                            if (prevToken && getEndLine(prevToken) !== endLine) {
                                endLine--;
                            }
                            if (prevDelimiter.line !== endLine) {
                                ranges.push({
                                    startLine: prevDelimiter.line,
                                    endLine,
                                    kind: undefined
                                });
                            }
                        }
                    }
                    break;
                }
                /**
                 * In CSS, there is no single line comment prefixed with //
                 * All comments are marked as `Comment`
                 */
                case cssScanner_1.TokenType.Comment: {
                    const commentRegionMarkerToDelimiter = (marker) => {
                        if (marker === '#region') {
                            return { line: getStartLine(token), type: 'comment', isStart: true };
                        }
                        else {
                            return { line: getEndLine(token), type: 'comment', isStart: false };
                        }
                    };
                    const getCurrDelimiter = (token) => {
                        const matches = token.text.match(/^\s*\/\*\s*(#region|#endregion)\b\s*(.*?)\s*\*\//);
                        if (matches) {
                            return commentRegionMarkerToDelimiter(matches[1]);
                        }
                        else if (document.languageId === 'scss' || document.languageId === 'less') {
                            const matches = token.text.match(/^\s*\/\/\s*(#region|#endregion)\b\s*(.*?)\s*/);
                            if (matches) {
                                return commentRegionMarkerToDelimiter(matches[1]);
                            }
                        }
                        return null;
                    };
                    const currDelimiter = getCurrDelimiter(token);
                    // /* */ comment region folding
                    // All #region and #endregion cases
                    if (currDelimiter) {
                        if (currDelimiter.isStart) {
                            delimiterStack.push(currDelimiter);
                        }
                        else {
                            const prevDelimiter = popPrevStartDelimiterOfType(delimiterStack, 'comment');
                            if (!prevDelimiter) {
                                break;
                            }
                            if (prevDelimiter.type === 'comment') {
                                if (prevDelimiter.line !== currDelimiter.line) {
                                    ranges.push({
                                        startLine: prevDelimiter.line,
                                        endLine: currDelimiter.line,
                                        kind: 'region'
                                    });
                                }
                            }
                        }
                    }
                    // Multiline comment case
                    else {
                        const range = tokenToRange(token, 'comment');
                        if (range) {
                            ranges.push(range);
                        }
                    }
                    break;
                }
            }
            prevToken = token;
            token = scanner.scan();
        }
        return ranges;
    }
    function popPrevStartDelimiterOfType(stack, type) {
        if (stack.length === 0) {
            return null;
        }
        for (let i = stack.length - 1; i >= 0; i--) {
            if (stack[i].type === type && stack[i].isStart) {
                return stack.splice(i, 1)[0];
            }
        }
        return null;
    }
    /**
     * - Sort regions
     * - Remove invalid regions (intersections)
     * - If limit exceeds, only return `rangeLimit` amount of ranges
     */
    function limitFoldingRanges(ranges, context) {
        const maxRanges = context && context.rangeLimit || Number.MAX_VALUE;
        const sortedRanges = ranges.sort((r1, r2) => {
            let diff = r1.startLine - r2.startLine;
            if (diff === 0) {
                diff = r1.endLine - r2.endLine;
            }
            return diff;
        });
        const validRanges = [];
        let prevEndLine = -1;
        sortedRanges.forEach(r => {
            if (!(r.startLine < prevEndLine && prevEndLine < r.endLine)) {
                validRanges.push(r);
                prevEndLine = r.endLine;
            }
        });
        if (validRanges.length < maxRanges) {
            return validRanges;
        }
        else {
            return validRanges.slice(0, maxRanges);
        }
    }
});
