/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
    return function (target, key) { decorator(target, key, paramIndex); }
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var _a;
import { DisposableStore } from '../../../../base/common/lifecycle.js';
import { registerEditorContribution } from '../../../browser/editorExtensions.js';
import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';
import { OutlineModel, OutlineElement } from '../../documentSymbols/browser/outlineModel.js';
import { CancellationTokenSource } from '../../../../base/common/cancellation.js';
import * as dom from '../../../../base/browser/dom.js';
import { createStringBuilder } from '../../../common/core/stringBuilder.js';
import { RenderLineInput, renderViewLine } from '../../../common/viewLayout/viewLineRenderer.js';
import { LineDecoration } from '../../../common/viewLayout/lineDecorations.js';
let StickyScrollController = class StickyScrollController {
    constructor(editor, _languageFeaturesService) {
        this._store = new DisposableStore();
        this._sessionStore = new DisposableStore();
        this._ranges = [];
        this._lastScrollPosition = -1;
        this._editor = editor;
        this._languageFeaturesService = _languageFeaturesService;
        this.stickyScrollWidget = new StickyScrollWidget(this._editor);
        this._store.add(this._editor.onDidChangeConfiguration(e => {
            if (e.hasChanged(106 /* EditorOption.stickyScroll */)) {
                this.onConfigurationChange();
            }
        }));
        this.onConfigurationChange();
    }
    onConfigurationChange() {
        const options = this._editor.getOption(106 /* EditorOption.stickyScroll */);
        if (options.enabled === false) {
            this.stickyScrollWidget.emptyRootNode();
            this._editor.removeOverlayWidget(this.stickyScrollWidget);
            this._sessionStore.clear();
            return;
        }
        else {
            this._editor.addOverlayWidget(this.stickyScrollWidget);
            this._sessionStore.add(this._editor.onDidChangeModel(() => this._update(true)));
            this._sessionStore.add(this._editor.onDidScrollChange(() => this._update(false)));
            this._sessionStore.add(this._editor.onDidChangeModelContent(() => this._update(true)));
            this._sessionStore.add(this._languageFeaturesService.documentSymbolProvider.onDidChange(() => this._update(true)));
            this._update(true);
        }
    }
    _update(updateOutline = false) {
        var _a;
        return __awaiter(this, void 0, void 0, function* () {
            if (updateOutline) {
                (_a = this._cts) === null || _a === void 0 ? void 0 : _a.dispose(true);
                this._cts = new CancellationTokenSource();
                yield this._updateOutlineModel(this._cts.token);
            }
            this._renderStickyScroll();
        });
    }
    _findLineRanges(outlineElement, depth) {
        if (outlineElement === null || outlineElement === void 0 ? void 0 : outlineElement.children.size) {
            let didRecursion = false;
            for (const outline of outlineElement === null || outlineElement === void 0 ? void 0 : outlineElement.children.values()) {
                const kind = outline.symbol.kind;
                if (kind === 4 /* SymbolKind.Class */ || kind === 8 /* SymbolKind.Constructor */ || kind === 11 /* SymbolKind.Function */ || kind === 10 /* SymbolKind.Interface */ || kind === 5 /* SymbolKind.Method */) {
                    didRecursion = true;
                    this._findLineRanges(outline, depth + 1);
                }
            }
            if (!didRecursion) {
                this._addOutlineRanges(outlineElement, depth);
            }
        }
        else {
            this._addOutlineRanges(outlineElement, depth);
        }
    }
    _addOutlineRanges(outlineElement, depth) {
        let currentStartLine = 0;
        let currentEndLine = 0;
        while (outlineElement) {
            const kind = outlineElement.symbol.kind;
            if (kind === 4 /* SymbolKind.Class */ || kind === 8 /* SymbolKind.Constructor */ || kind === 11 /* SymbolKind.Function */ || kind === 10 /* SymbolKind.Interface */ || kind === 5 /* SymbolKind.Method */) {
                currentStartLine = outlineElement === null || outlineElement === void 0 ? void 0 : outlineElement.symbol.range.startLineNumber;
                currentEndLine = outlineElement === null || outlineElement === void 0 ? void 0 : outlineElement.symbol.range.endLineNumber;
                this._ranges.push([currentStartLine, currentEndLine, depth]);
                depth--;
            }
            if (outlineElement.parent instanceof OutlineElement) {
                outlineElement = outlineElement.parent;
            }
            else {
                break;
            }
        }
    }
    _updateOutlineModel(token) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this._editor.hasModel()) {
                const model = this._editor.getModel();
                const outlineModel = yield OutlineModel.create(this._languageFeaturesService.documentSymbolProvider, model, token);
                if (token.isCancellationRequested) {
                    return;
                }
                this._ranges = [];
                for (const outline of outlineModel.children.values()) {
                    if (outline instanceof OutlineElement) {
                        const kind = outline.symbol.kind;
                        if (kind === 4 /* SymbolKind.Class */ || kind === 8 /* SymbolKind.Constructor */ || kind === 11 /* SymbolKind.Function */ || kind === 10 /* SymbolKind.Interface */ || kind === 5 /* SymbolKind.Method */) {
                            this._findLineRanges(outline, 1);
                        }
                        else {
                            this._findLineRanges(outline, 0);
                        }
                    }
                    this._ranges = this._ranges.sort(function (a, b) {
                        if (a[0] !== b[0]) {
                            return a[0] - b[0];
                        }
                        else if (a[1] !== b[1]) {
                            return b[1] - a[1];
                        }
                        else {
                            return a[2] - b[2];
                        }
                    });
                    let previous = [];
                    for (const [index, arr] of this._ranges.entries()) {
                        const [start, end, _depth] = arr;
                        if (previous[0] === start && previous[1] === end) {
                            this._ranges.splice(index, 1);
                        }
                        else {
                            previous = arr;
                        }
                    }
                }
            }
        });
    }
    _renderStickyScroll() {
        if (!(this._editor.hasModel())) {
            return;
        }
        const lineHeight = this._editor.getOption(60 /* EditorOption.lineHeight */);
        const model = this._editor.getModel();
        const scrollTop = this._editor.getScrollTop();
        let scrollDirection;
        if (this._lastScrollPosition < scrollTop) {
            scrollDirection = 0 /* ScrollDirection.Down */;
        }
        else {
            scrollDirection = 1 /* ScrollDirection.Up */;
        }
        this._lastScrollPosition = scrollTop;
        const scrollToBottomOfWidget = this._editor.getScrollTop() + this.stickyScrollWidget.codeLineCount * lineHeight;
        this.stickyScrollWidget.emptyRootNode();
        const beginningLinesConsidered = new Set();
        let topOfElementAtDepth;
        let bottomOfElementAtDepth;
        let bottomOfBeginningLine;
        let topOfEndLine;
        let bottomOfEndLine;
        for (const [index, arr] of this._ranges.entries()) {
            const [start, end, depth] = arr;
            topOfElementAtDepth = this._editor.getScrollTop() + (depth - 1) * lineHeight;
            bottomOfElementAtDepth = this._editor.getScrollTop() + depth * lineHeight;
            bottomOfBeginningLine = start * lineHeight;
            topOfEndLine = (end - 1) * lineHeight;
            bottomOfEndLine = end * lineHeight;
            if (!beginningLinesConsidered.has(start)) {
                if (topOfElementAtDepth >= topOfEndLine - 1 && topOfElementAtDepth < bottomOfEndLine - 2) {
                    beginningLinesConsidered.add(start);
                    this.stickyScrollWidget.pushCodeLine(new StickyScrollCodeLine(model.getLineContent(start), start, this._editor, -1, (depth - 1) * lineHeight + bottomOfEndLine - bottomOfElementAtDepth));
                    break;
                }
                else if (scrollDirection === 0 /* ScrollDirection.Down */ && bottomOfElementAtDepth > bottomOfBeginningLine - 1 && bottomOfElementAtDepth < bottomOfEndLine - 1) {
                    beginningLinesConsidered.add(start);
                    this.stickyScrollWidget.pushCodeLine(new StickyScrollCodeLine(model.getLineContent(start), start, this._editor, 0));
                }
                else if (scrollDirection === 1 /* ScrollDirection.Up */ && scrollToBottomOfWidget > bottomOfBeginningLine - 1 && scrollToBottomOfWidget < bottomOfEndLine ||
                    scrollDirection === 1 /* ScrollDirection.Up */ && bottomOfElementAtDepth > bottomOfBeginningLine && bottomOfElementAtDepth < topOfEndLine - 1) {
                    beginningLinesConsidered.add(start);
                    this.stickyScrollWidget.pushCodeLine(new StickyScrollCodeLine(model.getLineContent(start), start, this._editor, 0));
                }
            }
            else {
                this._ranges.splice(index, 1);
            }
        }
        this.stickyScrollWidget.updateRootNode();
    }
    dispose() {
        this._store.dispose();
        this._sessionStore.dispose();
    }
};
StickyScrollController.ID = 'store.contrib.stickyScrollController';
StickyScrollController = __decorate([
    __param(1, ILanguageFeaturesService)
], StickyScrollController);
const _ttPolicy = (_a = window.trustedTypes) === null || _a === void 0 ? void 0 : _a.createPolicy('stickyScrollViewLayer', { createHTML: value => value });
class StickyScrollCodeLine {
    constructor(_line, _lineNumber, _editor, _zIndex, _position) {
        this._line = _line;
        this._lineNumber = _lineNumber;
        this._editor = _editor;
        this._zIndex = _zIndex;
        this._position = _position;
    }
    getDomNode() {
        const root = document.createElement('div');
        const modifiedLine = this._line.replace(/\s/g, '\xa0');
        const lineRenderingData = this._editor._getViewModel().getViewLineRenderingData(this._editor.getVisibleRangesPlusViewportAboveBelow()[0], this._lineNumber);
        let actualInlineDecorations;
        try {
            actualInlineDecorations = LineDecoration.filter(lineRenderingData.inlineDecorations, this._lineNumber, lineRenderingData.minColumn, lineRenderingData.maxColumn);
        }
        catch (err) {
            console.log(err);
            actualInlineDecorations = [];
        }
        const renderLineInput = new RenderLineInput(true, true, modifiedLine, lineRenderingData.continuesWithWrappedLine, lineRenderingData.isBasicASCII, lineRenderingData.containsRTL, 0, lineRenderingData.tokens, actualInlineDecorations, lineRenderingData.tabSize, lineRenderingData.startVisibleColumn, 1, 1, 1, 100, 'none', true, true, null);
        const sb = createStringBuilder(2000);
        renderViewLine(renderLineInput, sb);
        let newLine;
        if (_ttPolicy) {
            newLine = _ttPolicy.createHTML(sb.build());
        }
        else {
            newLine = sb.build();
        }
        const lineHTMLNode = document.createElement('div');
        lineHTMLNode.style.paddingLeft = this._editor.getLayoutInfo().contentLeft - this._editor.getLayoutInfo().lineNumbersLeft - this._editor.getLayoutInfo().lineNumbersWidth + 'px';
        lineHTMLNode.style.float = 'left';
        lineHTMLNode.style.width = this._editor.getLayoutInfo().width - this._editor.getLayoutInfo().contentLeft + 'px';
        lineHTMLNode.style.backgroundColor = `var(--vscode-editorStickyScroll-background)`;
        lineHTMLNode.innerHTML = newLine;
        const lineNumberHTMLNode = document.createElement('div');
        lineNumberHTMLNode.style.width = this._editor.getLayoutInfo().contentLeft.toString() + 'px';
        lineNumberHTMLNode.style.backgroundColor = `var(--vscode-editorStickyScroll-background)`;
        lineNumberHTMLNode.style.color = 'var(--vscode-editorLineNumber-foreground)';
        const innerLineNumberHTML = document.createElement('div');
        innerLineNumberHTML.innerText = this._lineNumber.toString();
        innerLineNumberHTML.style.paddingLeft = this._editor.getLayoutInfo().lineNumbersLeft.toString() + 'px';
        innerLineNumberHTML.style.width = this._editor.getLayoutInfo().lineNumbersWidth.toString() + 'px';
        innerLineNumberHTML.style.backgroundColor = `var(--vscode-editorStickyScroll-background)`;
        innerLineNumberHTML.style.textAlign = 'right';
        innerLineNumberHTML.style.float = 'left';
        lineNumberHTMLNode.appendChild(innerLineNumberHTML);
        lineHTMLNode.onclick = e => {
            e.stopPropagation();
            e.preventDefault();
            this._editor.revealLine(this._lineNumber);
        };
        lineHTMLNode.onmouseover = e => {
            innerLineNumberHTML.style.background = `var(--vscode-editorStickyScrollHover-background)`;
            lineHTMLNode.style.backgroundColor = `var(--vscode-editorStickyScrollHover-background)`;
            innerLineNumberHTML.style.cursor = `pointer`;
            lineHTMLNode.style.cursor = `pointer`;
        };
        lineHTMLNode.onmouseleave = e => {
            innerLineNumberHTML.style.background = `var(--vscode-editorStickyScroll-background)`;
            lineHTMLNode.style.backgroundColor = `var(--vscode-editorStickyScroll-background)`;
        };
        this._editor.applyFontInfo(lineHTMLNode);
        this._editor.applyFontInfo(innerLineNumberHTML);
        root.appendChild(lineNumberHTMLNode);
        root.appendChild(lineHTMLNode);
        root.style.zIndex = this._zIndex.toString();
        root.style.backgroundColor = `var(--vscode-editorStickyScroll-background)`;
        // Special case for last line of sticky scroll
        if (this._position) {
            root.style.position = 'absolute';
            root.style.top = this._position + 'px';
            root.style.width = '100%';
        }
        return root;
    }
}
class StickyScrollWidget {
    constructor(_editor) {
        this._editor = _editor;
        this.arrayOfCodeLines = [];
        this.rootDomNode = document.createElement('div');
        this.rootDomNode = document.createElement('div');
        this.rootDomNode.style.width = '100%';
    }
    get codeLineCount() {
        return this.arrayOfCodeLines.length;
    }
    pushCodeLine(codeLine) {
        this.arrayOfCodeLines.push(codeLine);
    }
    updateRootNode() {
        for (const line of this.arrayOfCodeLines) {
            this.rootDomNode.appendChild(line.getDomNode());
        }
    }
    emptyRootNode() {
        this.arrayOfCodeLines.length = 0;
        dom.clearNode(this.rootDomNode);
    }
    getId() {
        return 'editor.contrib.stickyScrollWidget';
    }
    getDomNode() {
        this.rootDomNode.style.zIndex = '2';
        this.rootDomNode.style.backgroundColor = `var(--vscode-editorStickyScroll-background)`;
        return this.rootDomNode;
    }
    getPosition() {
        return {
            preference: null
        };
    }
}
registerEditorContribution(StickyScrollController.ID, StickyScrollController);
