
import { __decorate, __param } from '../../../../../../../external/tslib/tslib.es6.js';
import { createElement, createTextNode, isHTMLElement, reset, addDisposableListener, getWindow } from '../../../../base/browser/dom.js';
import { StandardKeyboardEvent } from '../../../../base/browser/keyboardEvent.js';
import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js';
import { KeyCode } from '../../../../base/common/keyCodes.js';
import { Schemas } from '../../../../base/common/network.js';
import { normalize, posix, join } from '../../../../base/common/path.js';
import { isWindows, isMacintosh } from '../../../../base/common/platform.js';
import { URI } from '../../../../base/common/uri.js';
import { localize } from '../../../../nls.js';
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.service.js';
import { IFileService } from '../../../../platform/files/common/files.service.js';
import { IHoverService } from '../../../../platform/hover/browser/hover.service.js';
import { IOpenerService } from '../../../../platform/opener/common/opener.service.js';
import { ITunnelService } from '../../../../platform/tunnel/common/tunnel.service.js';
import { IEditorService } from '../../../services/editor/common/editorService.service.js';
import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.service.js';
import { IPathService } from '../../../services/path/common/pathService.service.js';
import { Iterable } from '../../../../base/common/iterator.js';

const CONTROL_CODES = "\\u0000-\\u0020\\u007f-\\u009f";
const WEB_LINK_REGEX = ( new RegExp(
    "(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\\/\\/|data:|www\\.)[^\\s" + CONTROL_CODES + "\"]{2,}[^\\s" + CONTROL_CODES + "\"')}\\],:;.!?]",
    "ug"
));
const WIN_ABSOLUTE_PATH = /(?:[a-zA-Z]:(?:(?:\\|\/)[\w\s\.@\-\(\)\[\]{}!#$%^&'`~+=]+)+)/;
const WIN_RELATIVE_PATH = /(?:(?:\~|\.+)(?:(?:\\|\/)[\w\s\.@\-\(\)\[\]{}!#$%^&'`~+=]+)+)/;
const WIN_PATH = ( new RegExp(`(${WIN_ABSOLUTE_PATH.source}|${WIN_RELATIVE_PATH.source})`));
const POSIX_PATH = /((?:\~|\.+)?(?:\/[\w\s\.@\-\(\)\[\]{}!#$%^&'`~+=]+)+)/;
const LINE_COLUMN = /(?::(?:line\s+)?([\d]+))?(?::([\d]+))?/;
const PATH_LINK_REGEX = ( new RegExp(
    `${isWindows ? WIN_PATH.source : POSIX_PATH.source}${LINE_COLUMN.source}`,
    "g"
));
const LINE_COLUMN_REGEX = /:(?:line\s+)?([\d]+)(?::([\d]+))?$/;
const MAX_LENGTH = 2000;
var DebugLinkHoverBehavior;
(function(DebugLinkHoverBehavior) {
    DebugLinkHoverBehavior[DebugLinkHoverBehavior["Rich"] = 0] = "Rich";
    DebugLinkHoverBehavior[DebugLinkHoverBehavior["Basic"] = 1] = "Basic";
    DebugLinkHoverBehavior[DebugLinkHoverBehavior["None"] = 2] = "None";
})(DebugLinkHoverBehavior || (DebugLinkHoverBehavior = {}));
let LinkDetector = class LinkDetector {
    constructor(
        editorService,
        fileService,
        openerService,
        pathService,
        tunnelService,
        environmentService,
        configurationService,
        hoverService
    ) {
        this.editorService = editorService;
        this.fileService = fileService;
        this.openerService = openerService;
        this.pathService = pathService;
        this.tunnelService = tunnelService;
        this.environmentService = environmentService;
        this.configurationService = configurationService;
        this.hoverService = hoverService;
    }
    linkify(
        text,
        hoverBehavior,
        splitLines,
        workspaceFolder,
        includeFulltext,
        highlights
    ) {
        return this._linkify(
            text,
            hoverBehavior,
            splitLines,
            workspaceFolder,
            includeFulltext,
            highlights
        );
    }
    _linkify(
        text,
        hoverBehavior,
        splitLines,
        workspaceFolder,
        includeFulltext,
        highlights,
        defaultRef
    ) {
        if (splitLines) {
            const lines = text.split("\n");
            for (let i = 0; i < lines.length - 1; i++) {
                lines[i] = lines[i] + "\n";
            }
            if (!lines[lines.length - 1]) {
                lines.pop();
            }
            const elements = ( lines.map(line => this._linkify(
                line,
                hoverBehavior,
                false,
                workspaceFolder,
                includeFulltext,
                highlights,
                defaultRef
            )));
            if (elements.length === 1) {
                return elements[0];
            }
            const container = createElement("span");
            elements.forEach(e => container.appendChild(e));
            return container;
        }
        const container = createElement("span");
        for (const part of this.detectLinks(text)) {
            try {
                let node;
                switch (part.kind) {
                case "text":
                    node = defaultRef ? this.linkifyLocation(
                        part.value,
                        defaultRef.locationReference,
                        defaultRef.session,
                        hoverBehavior
                    ) : createTextNode(part.value);
                    break;
                case "web":
                    node = this.createWebLink(includeFulltext ? text : undefined, part.value, hoverBehavior);
                    break;
                case "path":
                    {
                        const path = part.captures[0];
                        const lineNumber = part.captures[1] ? Number(part.captures[1]) : 0;
                        const columnNumber = part.captures[2] ? Number(part.captures[2]) : 0;
                        node = this.createPathLink(
                            includeFulltext ? text : undefined,
                            part.value,
                            path,
                            lineNumber,
                            columnNumber,
                            workspaceFolder,
                            hoverBehavior
                        );
                        break;
                    }
                default:
                    node = createTextNode(part.value);
                }
                container.append(...this.applyHighlights(node, part.index, part.value.length, highlights));
            } catch (e) {
                container.appendChild(createTextNode(part.value));
            }
        }
        return container;
    }
    applyHighlights(node, startIndex, length, highlights) {
        const children = [];
        let currentIndex = startIndex;
        const endIndex = startIndex + length;
        for (const highlight of highlights || []) {
            if (highlight.end <= currentIndex || highlight.start >= endIndex) {
                continue;
            }
            if (highlight.start > currentIndex) {
                children.push(
                    node.textContent.substring(currentIndex - startIndex, highlight.start - startIndex)
                );
                currentIndex = highlight.start;
            }
            const highlightEnd = Math.min(highlight.end, endIndex);
            const highlightedText = node.textContent.substring(currentIndex - startIndex, highlightEnd - startIndex);
            const highlightSpan = createElement("span");
            highlightSpan.classList.add("highlight");
            if (highlight.extraClasses) {
                highlightSpan.classList.add(...highlight.extraClasses);
            }
            highlightSpan.textContent = highlightedText;
            children.push(highlightSpan);
            currentIndex = highlightEnd;
        }
        if (currentIndex === startIndex) {
            return Iterable.single(node);
        }
        if (currentIndex < endIndex) {
            children.push(node.textContent.substring(currentIndex - startIndex));
        }
        if (isHTMLElement(node)) {
            reset(node, ...children);
            return Iterable.single(node);
        }
        return children;
    }
    linkifyLocation(text, locationReference, session, hoverBehavior) {
        const link = this.createLink(text);
        this.decorateLink(link, undefined, text, hoverBehavior, async preserveFocus => {
            const location = await session.resolveLocationReference(locationReference);
            await location.source.openInEditor(this.editorService, {
                startLineNumber: location.line,
                startColumn: location.column,
                endLineNumber: location.endLine ?? location.line,
                endColumn: location.endColumn ?? location.column
            }, preserveFocus);
        });
        return link;
    }
    makeReferencedLinkDetector(locationReference, session) {
        return {
            linkify: (
                text,
                hoverBehavior,
                splitLines,
                workspaceFolder,
                includeFulltext,
                highlights
            ) => this._linkify(
                text,
                hoverBehavior,
                splitLines,
                workspaceFolder,
                includeFulltext,
                highlights,
                {
                    locationReference,
                    session
                }
            ),
            linkifyLocation: this.linkifyLocation.bind(this)
        };
    }
    createWebLink(fulltext, url, hoverBehavior) {
        const link = this.createLink(url);
        let uri = ( URI.parse(url));
        const lineCol = LINE_COLUMN_REGEX.exec(uri.path);
        if (lineCol) {
            uri = uri.with({
                path: uri.path.slice(0, lineCol.index),
                fragment: `L${lineCol[0].slice(1)}`
            });
        }
        this.decorateLink(link, uri, fulltext, hoverBehavior, async () => {
            if (uri.scheme === Schemas.file) {
                const fsPath = uri.fsPath;
                const path = await this.pathService.path;
                const fileUrl = normalize(
                    ((path.sep === posix.sep) && isWindows) ? fsPath.replace(/\\/g, posix.sep) : fsPath
                );
                const fileUri = ( URI.parse(fileUrl));
                const exists = await this.fileService.exists(fileUri);
                if (!exists) {
                    return;
                }
                await this.editorService.openEditor({
                    resource: fileUri,
                    options: {
                        pinned: true,
                        selection: lineCol ? {
                            startLineNumber: +lineCol[1],
                            startColumn: lineCol[2] ? +lineCol[2] : 1
                        } : undefined
                    }
                });
                return;
            }
            this.openerService.open(url, {
                allowTunneling: (!!this.environmentService.remoteAuthority && this.configurationService.getValue("remote.forwardOnOpen"))
            });
        });
        return link;
    }
    createPathLink(
        fulltext,
        text,
        path,
        lineNumber,
        columnNumber,
        workspaceFolder,
        hoverBehavior
    ) {
        if (path[0] === "/" && path[1] === "/") {
            return createTextNode(text);
        }
        const options = lineNumber > 0 ? {
            selection: {
                startLineNumber: lineNumber,
                startColumn: columnNumber > 0 ? columnNumber : 1
            }
        } : {};
        if (path[0] === ".") {
            if (!workspaceFolder) {
                return createTextNode(text);
            }
            const uri = workspaceFolder.toResource(path);
            const link = this.createLink(text);
            this.decorateLink(
                link,
                uri,
                fulltext,
                hoverBehavior,
                preserveFocus => this.editorService.openEditor({
                    resource: uri,
                    options: {
                        ...options,
                        preserveFocus
                    }
                })
            );
            return link;
        }
        if (path[0] === "~") {
            const userHome = this.pathService.resolvedUserHome;
            if (userHome) {
                path = join(userHome.fsPath, path.substring(1));
            }
        }
        const link = this.createLink(text);
        link.tabIndex = 0;
        const uri = URI.file(normalize(path));
        this.fileService.stat(uri).then(stat => {
            if (stat.isDirectory) {
                return;
            }
            this.decorateLink(
                link,
                uri,
                fulltext,
                hoverBehavior,
                preserveFocus => this.editorService.openEditor({
                    resource: uri,
                    options: {
                        ...options,
                        preserveFocus
                    }
                })
            );
        }).catch(() => {});
        return link;
    }
    createLink(text) {
        const link = createElement("a");
        link.textContent = text;
        return link;
    }
    decorateLink(link, uri, fulltext, hoverBehavior, onClick) {
        if (hoverBehavior.store.isDisposed) {
            return;
        }
        link.classList.add("link");
        const followLink = uri && this.tunnelService.canTunnel(uri) ? ( localize(8464, "follow link using forwarded port")) : ( localize(8465, "follow link"));
        const title = link.ariaLabel = fulltext ? (isMacintosh ? ( localize(8466, "Cmd + click to {0}\n{1}", followLink, fulltext)) : ( localize(8467, "Ctrl + click to {0}\n{1}", followLink, fulltext))) : (isMacintosh ? ( localize(8468, "Cmd + click to {0}", followLink)) : ( localize(8469, "Ctrl + click to {0}", followLink)));
        if (hoverBehavior.type === DebugLinkHoverBehavior.Rich) {
            hoverBehavior.store.add(
                this.hoverService.setupManagedHover(getDefaultHoverDelegate("element"), link, title)
            );
        } else if (hoverBehavior.type !== DebugLinkHoverBehavior.None) {
            link.title = title;
        }
        hoverBehavior.store.add(addDisposableListener(link, "mousemove", event => {
            link.classList.toggle("pointer", isMacintosh ? event.metaKey : event.ctrlKey);
        }));
        hoverBehavior.store.add(addDisposableListener(link, "mouseleave", () => {
            link.classList.remove("pointer");
        }));
        hoverBehavior.store.add(addDisposableListener(link, "click", event => {
            const selection = getWindow(link).getSelection();
            if (!selection || selection.type === "Range") {
                return;
            }
            if (!(isMacintosh ? event.metaKey : event.ctrlKey)) {
                return;
            }
            event.preventDefault();
            event.stopImmediatePropagation();
            onClick(false);
        }));
        hoverBehavior.store.add(addDisposableListener(link, "keydown", e => {
            const event = ( new StandardKeyboardEvent(e));
            if (event.keyCode === KeyCode.Enter || event.keyCode === KeyCode.Space) {
                event.preventDefault();
                event.stopPropagation();
                onClick(event.keyCode === KeyCode.Space);
            }
        }));
    }
    detectLinks(text) {
        if (text.length > MAX_LENGTH) {
            return [{
                kind: "text",
                value: text,
                captures: [],
                index: 0
            }];
        }
        const regexes = [WEB_LINK_REGEX, PATH_LINK_REGEX];
        const kinds = ["web", "path"];
        const result = [];
        const splitOne = (text, regexIndex, baseIndex) => {
            if (regexIndex >= regexes.length) {
                result.push({
                    value: text,
                    kind: "text",
                    captures: [],
                    index: baseIndex
                });
                return;
            }
            const regex = regexes[regexIndex];
            let currentIndex = 0;
            let match;
            regex.lastIndex = 0;
            while ((match = regex.exec(text)) !== null) {
                const stringBeforeMatch = text.substring(currentIndex, match.index);
                if (stringBeforeMatch) {
                    splitOne(stringBeforeMatch, regexIndex + 1, baseIndex + currentIndex);
                }
                const value = match[0];
                result.push({
                    value: value,
                    kind: kinds[regexIndex],
                    captures: match.slice(1),
                    index: baseIndex + match.index
                });
                currentIndex = match.index + value.length;
            }
            const stringAfterMatches = text.substring(currentIndex);
            if (stringAfterMatches) {
                splitOne(stringAfterMatches, regexIndex + 1, baseIndex + currentIndex);
            }
        };
        splitOne(text, 0, 0);
        return result;
    }
};
LinkDetector = ( __decorate([( __param(0, IEditorService)), ( __param(1, IFileService)), ( __param(2, IOpenerService)), ( __param(3, IPathService)), ( __param(4, ITunnelService)), ( __param(5, IWorkbenchEnvironmentService)), ( __param(6, IConfigurationService)), ( __param(7, IHoverService))], LinkDetector));

export { DebugLinkHoverBehavior, LinkDetector };
