import { __decorate, __param } from '../../../../../../../external/tslib/tslib.es6.js';
import { append, $ } from '../../../../base/browser/dom.js';
import { localize2WithPath, localizeWithPath } from '../../../../nls.js';
import { renderMarkdown } from '../../../../base/browser/markdownRenderer.js';
import { DisposableStore } from '../../../../base/common/lifecycle.js';
import { IOpenerService } from '../../../../platform/opener/common/opener.service.js';
import { ResourceWithCommentThreads, CommentNode } from '../common/commentModel.js';
import { TreeVisibility } from '../../../../base/browser/ui/tree/tree.js';
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.service.js';
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.service.js';
import { WorkbenchObjectTree } from '../../../../platform/list/browser/listService.js';
import { IListService } from '../../../../platform/list/browser/listService.service.js';
import { IThemeService } from '../../../../platform/theme/common/themeService.service.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { TimestampWidget } from './timestamp.js';
import { Codicon } from '../../../../base/common/codicons.js';
import { ThemeIcon } from '../../../../base/common/themables.js';
import { commentViewThreadStateColorVar, getCommentThreadStateIconColor } from './commentColors.js';
import { CommentThreadState, CommentThreadApplicability } from '../../../../editor/common/languages.js';
import { FilterOptions } from './commentsFilterOptions.js';
import { basename } from '../../../../base/common/resources.js';
import { openLinkFromMarkdown } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js';
import { CommentsModel } from './commentsModel.js';
import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js';
import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js';
import { createAndFillInContextMenuActions, createActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js';
import { MenuId } from '../../../../platform/actions/common/actions.js';
import { IMenuService } from '../../../../platform/actions/common/actions.service.js';
import { MarshalledId } from '../../../../base/common/marshallingIds.js';
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.service.js';
import { ActionViewItem } from '../../../../base/browser/ui/actionbar/actionViewItems.js';
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.service.js';
import { IHoverService } from '../../../../platform/hover/browser/hover.service.js';
const _moduleId = "vs/workbench/contrib/comments/browser/commentsTreeViewer";
const COMMENTS_VIEW_ID = 'workbench.panel.comments';
const COMMENTS_VIEW_STORAGE_ID = 'Comments';
const COMMENTS_VIEW_TITLE = ( localize2WithPath(_moduleId, 0, "Comments"));
class CommentsModelVirualDelegate {
    static { this.RESOURCE_ID = 'resource-with-comments'; }
    static { this.COMMENT_ID = 'comment-node'; }
    getHeight(element) {
        if ((element instanceof CommentNode) && element.hasReply()) {
            return 44;
        }
        return 22;
    }
    getTemplateId(element) {
        if (element instanceof ResourceWithCommentThreads) {
            return CommentsModelVirualDelegate.RESOURCE_ID;
        }
        if (element instanceof CommentNode) {
            return CommentsModelVirualDelegate.COMMENT_ID;
        }
        return '';
    }
}
class ResourceWithCommentsRenderer {
    constructor(labels) {
        this.labels = labels;
        this.templateId = 'resource-with-comments';
    }
    renderTemplate(container) {
        const labelContainer = append(container, $('.resource-container'));
        const resourceLabel = this.labels.create(labelContainer);
        const separator = append(labelContainer, $('.separator'));
        const owner = labelContainer.appendChild($('.owner'));
        return { resourceLabel, owner, separator };
    }
    renderElement(node, index, templateData, height) {
        templateData.resourceLabel.setFile(node.element.resource);
        templateData.separator.innerText = '\u00b7';
        if (node.element.ownerLabel) {
            templateData.owner.innerText = node.element.ownerLabel;
            templateData.separator.style.display = 'inline';
        }
        else {
            templateData.owner.innerText = '';
            templateData.separator.style.display = 'none';
        }
    }
    disposeTemplate(templateData) {
        templateData.resourceLabel.dispose();
    }
}
let CommentsMenus = class CommentsMenus {
    constructor(menuService) {
        this.menuService = menuService;
    }
    getResourceActions(element) {
        const actions = this.getActions(MenuId.CommentsViewThreadActions, element);
        return { menu: actions.menu, actions: actions.primary };
    }
    getResourceContextActions(element) {
        return this.getActions(MenuId.CommentsViewThreadActions, element).secondary;
    }
    setContextKeyService(service) {
        this.contextKeyService = service;
    }
    getActions(menuId, element) {
        if (!this.contextKeyService) {
            return { primary: [], secondary: [] };
        }
        const overlay = [
            ['commentController', element.owner],
            ['resourceScheme', element.resource.scheme],
            ['commentThread', element.contextValue],
            ['canReply', element.thread.canReply]
        ];
        const contextKeyService = this.contextKeyService.createOverlay(overlay);
        const menu = this.menuService.createMenu(menuId, contextKeyService);
        const primary = [];
        const secondary = [];
        const result = { primary, secondary, menu };
        createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, 'inline');
        menu.dispose();
        return result;
    }
    dispose() {
        this.contextKeyService = undefined;
    }
};
CommentsMenus = ( (__decorate([
    ( (__param(0, IMenuService)))
], CommentsMenus)));
let CommentNodeRenderer = class CommentNodeRenderer {
    constructor(actionViewItemProvider, menus, openerService, configurationService, hoverService, themeService) {
        this.actionViewItemProvider = actionViewItemProvider;
        this.menus = menus;
        this.openerService = openerService;
        this.configurationService = configurationService;
        this.hoverService = hoverService;
        this.themeService = themeService;
        this.templateId = 'comment-node';
    }
    renderTemplate(container) {
        const threadContainer = append(container, $('.comment-thread-container'));
        const metadataContainer = append(threadContainer, $('.comment-metadata-container'));
        const metadata = append(metadataContainer, $('.comment-metadata'));
        const threadMetadata = {
            icon: append(metadata, $('.icon')),
            userNames: append(metadata, $('.user')),
            timestamp: ( (new TimestampWidget(
                this.configurationService,
                this.hoverService,
                append(metadata, $('.timestamp-container'))
            ))),
            relevance: append(metadata, $('.relevance')),
            separator: append(metadata, $('.separator')),
            commentPreview: append(metadata, $('.text')),
            range: append(metadata, $('.range'))
        };
        threadMetadata.separator.innerText = '\u00b7';
        const actionsContainer = append(metadataContainer, $('.actions'));
        const actionBar = ( (new ActionBar(actionsContainer, {
            actionViewItemProvider: this.actionViewItemProvider
        })));
        const snippetContainer = append(threadContainer, $('.comment-snippet-container'));
        const repliesMetadata = {
            container: snippetContainer,
            icon: append(snippetContainer, $('.icon')),
            count: append(snippetContainer, $('.count')),
            lastReplyDetail: append(snippetContainer, $('.reply-detail')),
            separator: append(snippetContainer, $('.separator')),
            timestamp: ( (new TimestampWidget(
                this.configurationService,
                this.hoverService,
                append(snippetContainer, $('.timestamp-container'))
            ))),
        };
        repliesMetadata.separator.innerText = '\u00b7';
        repliesMetadata.icon.classList.add(...ThemeIcon.asClassNameArray(Codicon.indent));
        const disposables = [threadMetadata.timestamp, repliesMetadata.timestamp];
        return { threadMetadata, repliesMetadata, actionBar, disposables };
    }
    getCountString(commentCount) {
        if (commentCount > 2) {
            return ( localizeWithPath(_moduleId, 1, "{0} replies", commentCount - 1));
        }
        else if (commentCount === 2) {
            return ( localizeWithPath(_moduleId, 2, "1 reply"));
        }
        else {
            return ( localizeWithPath(_moduleId, 3, "1 comment"));
        }
    }
    getRenderedComment(commentBody, disposables) {
        const renderedComment = renderMarkdown(commentBody, {
            inline: true,
            actionHandler: {
                callback: (link) => openLinkFromMarkdown(this.openerService, link, commentBody.isTrusted),
                disposables: disposables
            }
        });
        const images = renderedComment.element.getElementsByTagName('img');
        for (let i = 0; i < images.length; i++) {
            const image = images[i];
            const textDescription = $('');
            textDescription.textContent = image.alt ? ( localizeWithPath(_moduleId, 4, "Image: {0}", image.alt)) : ( localizeWithPath(_moduleId, 5, "Image"));
            image.parentNode.replaceChild(textDescription, image);
        }
        return renderedComment;
    }
    getIcon(threadState) {
        if (threadState === CommentThreadState.Unresolved) {
            return Codicon.commentUnresolved;
        }
        else {
            return Codicon.comment;
        }
    }
    renderElement(node, index, templateData, height) {
        templateData.actionBar.clear();
        const commentCount = node.element.replies.length + 1;
        if (node.element.threadRelevance === CommentThreadApplicability.Outdated) {
            templateData.threadMetadata.relevance.style.display = '';
            templateData.threadMetadata.relevance.innerText = ( localizeWithPath(_moduleId, 6, "Outdated"));
            templateData.threadMetadata.separator.style.display = 'none';
        }
        else {
            templateData.threadMetadata.relevance.innerText = '';
            templateData.threadMetadata.relevance.style.display = 'none';
            templateData.threadMetadata.separator.style.display = '';
        }
        templateData.threadMetadata.icon.classList.remove(...Array.from(( (templateData.threadMetadata.icon.classList.values())))
            .filter(value => value.startsWith('codicon')));
        templateData.threadMetadata.icon.classList.add(...ThemeIcon.asClassNameArray(this.getIcon(node.element.threadState)));
        if (node.element.threadState !== undefined) {
            const color = this.getCommentThreadWidgetStateColor(node.element.threadState, this.themeService.getColorTheme());
            templateData.threadMetadata.icon.style.setProperty(commentViewThreadStateColorVar, `${color}`);
            templateData.threadMetadata.icon.style.color = `var(${commentViewThreadStateColorVar})`;
        }
        templateData.threadMetadata.userNames.textContent = node.element.comment.userName;
        templateData.threadMetadata.timestamp.setTimestamp(node.element.comment.timestamp ? ( (new Date(node.element.comment.timestamp))) : undefined);
        const originalComment = node.element;
        templateData.threadMetadata.commentPreview.innerText = '';
        templateData.threadMetadata.commentPreview.style.height = '22px';
        if (typeof originalComment.comment.body === 'string') {
            templateData.threadMetadata.commentPreview.innerText = originalComment.comment.body;
        }
        else {
            const disposables = ( (new DisposableStore()));
            templateData.disposables.push(disposables);
            const renderedComment = this.getRenderedComment(originalComment.comment.body, disposables);
            templateData.disposables.push(renderedComment);
            templateData.threadMetadata.commentPreview.appendChild(renderedComment.element.firstElementChild ?? renderedComment.element);
            templateData.disposables.push(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), templateData.threadMetadata.commentPreview, renderedComment.element.textContent ?? ''));
        }
        if (node.element.range) {
            if (node.element.range.startLineNumber === node.element.range.endLineNumber) {
                templateData.threadMetadata.range.textContent = ( localizeWithPath(_moduleId, 7, "[Ln {0}]", node.element.range.startLineNumber));
            }
            else {
                templateData.threadMetadata.range.textContent = ( localizeWithPath(
                    _moduleId,
                    8,
                    "[Ln {0}-{1}]",
                    node.element.range.startLineNumber,
                    node.element.range.endLineNumber
                ));
            }
        }
        const menuActions = this.menus.getResourceActions(node.element);
        templateData.actionBar.push(menuActions.actions, { icon: true, label: false });
        templateData.actionBar.context = {
            commentControlHandle: node.element.controllerHandle,
            commentThreadHandle: node.element.threadHandle,
            $mid: MarshalledId.CommentThread
        };
        if (!node.element.hasReply()) {
            templateData.repliesMetadata.container.style.display = 'none';
            return;
        }
        templateData.repliesMetadata.container.style.display = '';
        templateData.repliesMetadata.count.textContent = this.getCountString(commentCount);
        const lastComment = node.element.replies[node.element.replies.length - 1].comment;
        templateData.repliesMetadata.lastReplyDetail.textContent = ( localizeWithPath(_moduleId, 9, "Last reply from {0}", lastComment.userName));
        templateData.repliesMetadata.timestamp.setTimestamp(lastComment.timestamp ? ( (new Date(lastComment.timestamp))) : undefined);
    }
    getCommentThreadWidgetStateColor(state, theme) {
        return (state !== undefined) ? getCommentThreadStateIconColor(state, theme) : undefined;
    }
    disposeTemplate(templateData) {
        templateData.disposables.forEach(disposeable => disposeable.dispose());
        templateData.actionBar.dispose();
    }
};
CommentNodeRenderer = ( (__decorate([
    ( (__param(2, IOpenerService))),
    ( (__param(3, IConfigurationService))),
    ( (__param(4, IHoverService))),
    ( (__param(5, IThemeService)))
], CommentNodeRenderer)));
var FilterDataType;
( ((function(FilterDataType) {
    FilterDataType[FilterDataType["Resource"] = 0] = "Resource";
    FilterDataType[FilterDataType["Comment"] = 1] = "Comment";
})(FilterDataType || (FilterDataType = {}))));
class Filter {
    constructor(options) {
        this.options = options;
    }
    filter(element, parentVisibility) {
        if (this.options.filter === '' && this.options.showResolved && this.options.showUnresolved) {
            return TreeVisibility.Visible;
        }
        if (element instanceof ResourceWithCommentThreads) {
            return this.filterResourceMarkers(element);
        }
        else {
            return this.filterCommentNode(element, parentVisibility);
        }
    }
    filterResourceMarkers(resourceMarkers) {
        if (this.options.textFilter.text && !this.options.textFilter.negate) {
            const uriMatches = FilterOptions._filter(this.options.textFilter.text, basename(resourceMarkers.resource));
            if (uriMatches) {
                return { visibility: true, data: { type: FilterDataType.Resource, uriMatches: uriMatches || [] } };
            }
        }
        return TreeVisibility.Recurse;
    }
    filterCommentNode(comment, parentVisibility) {
        const matchesResolvedState = (comment.threadState === undefined) || (this.options.showResolved && CommentThreadState.Resolved === comment.threadState) ||
            (this.options.showUnresolved && CommentThreadState.Unresolved === comment.threadState);
        if (!matchesResolvedState) {
            return false;
        }
        if (!this.options.textFilter.text) {
            return true;
        }
        const textMatches =
        FilterOptions._messageFilter(this.options.textFilter.text, typeof comment.comment.body === 'string' ? comment.comment.body : comment.comment.body.value)
            || FilterOptions._messageFilter(this.options.textFilter.text, comment.comment.userName)
            || ( (comment.replies.map(reply => {
                return FilterOptions._messageFilter(this.options.textFilter.text, reply.comment.userName)
                    || FilterOptions._messageFilter(this.options.textFilter.text, typeof reply.comment.body === 'string' ? reply.comment.body : reply.comment.body.value);
            }))).filter(value => !!value).flat();
        if (textMatches.length && !this.options.textFilter.negate) {
            return { visibility: true, data: { type: FilterDataType.Comment, textMatches } };
        }
        if (textMatches.length && this.options.textFilter.negate && parentVisibility === TreeVisibility.Recurse) {
            return false;
        }
        if ((textMatches.length === 0) && this.options.textFilter.negate && parentVisibility === TreeVisibility.Recurse) {
            return true;
        }
        return parentVisibility;
    }
}
let CommentsList = class CommentsList extends WorkbenchObjectTree {
    constructor(labels, container, options, contextKeyService, listService, instantiationService, configurationService, contextMenuService, keybindingService) {
        const delegate = ( (new CommentsModelVirualDelegate()));
        const actionViewItemProvider = createActionViewItem.bind(undefined, instantiationService);
        const menus = instantiationService.createInstance(CommentsMenus);
        menus.setContextKeyService(contextKeyService);
        const renderers = [
            instantiationService.createInstance(ResourceWithCommentsRenderer, labels),
            instantiationService.createInstance(CommentNodeRenderer, actionViewItemProvider, menus)
        ];
        super('CommentsTree', container, delegate, renderers, {
            accessibilityProvider: options.accessibilityProvider,
            identityProvider: {
                getId: (element) => {
                    if (element instanceof CommentsModel) {
                        return 'root';
                    }
                    if (element instanceof ResourceWithCommentThreads) {
                        return `${element.uniqueOwner}-${element.id}`;
                    }
                    if (element instanceof CommentNode) {
                        return `${element.uniqueOwner}-${( (element.resource.toString()))}-${element.threadId}-${element.comment.uniqueIdInThread}` + (element.isRoot ? '-root' : '');
                    }
                    return '';
                }
            },
            expandOnlyOnTwistieClick: true,
            collapseByDefault: false,
            overrideStyles: options.overrideStyles,
            filter: options.filter,
            findWidgetEnabled: false,
            multipleSelectionSupport: false,
        }, instantiationService, contextKeyService, listService, configurationService);
        this.contextMenuService = contextMenuService;
        this.keybindingService = keybindingService;
        this.menus = menus;
        this.disposables.add(this.onContextMenu(e => this.commentsOnContextMenu(e)));
    }
    commentsOnContextMenu(treeEvent) {
        const node = treeEvent.element;
        if (!(node instanceof CommentNode)) {
            return;
        }
        const event = treeEvent.browserEvent;
        event.preventDefault();
        event.stopPropagation();
        this.setFocus([node]);
        const actions = this.menus.getResourceContextActions(node);
        if (!actions.length) {
            return;
        }
        this.contextMenuService.showContextMenu({
            getAnchor: () => treeEvent.anchor,
            getActions: () => actions,
            getActionViewItem: (action) => {
                const keybinding = this.keybindingService.lookupKeybinding(action.id);
                if (keybinding) {
                    return (
                         (new ActionViewItem(action, action, { label: true, keybinding: keybinding.getLabel() }))
                    );
                }
                return undefined;
            },
            onHide: (wasCancelled) => {
                if (wasCancelled) {
                    this.domFocus();
                }
            },
            getActionsContext: () => ({
                commentControlHandle: node.controllerHandle,
                commentThreadHandle: node.threadHandle,
                $mid: MarshalledId.CommentThread,
                thread: node.thread
            })
        });
    }
    filterComments() {
        this.refilter();
    }
    getVisibleItemCount() {
        let filtered = 0;
        const root = this.getNode();
        for (const resourceNode of root.children) {
            for (const commentNode of resourceNode.children) {
                if (commentNode.visible && resourceNode.visible) {
                    filtered++;
                }
            }
        }
        return filtered;
    }
};
CommentsList = ( (__decorate([
    ( (__param(3, IContextKeyService))),
    ( (__param(4, IListService))),
    ( (__param(5, IInstantiationService))),
    ( (__param(6, IConfigurationService))),
    ( (__param(7, IContextMenuService))),
    ( (__param(8, IKeybindingService)))
], CommentsList)));
export { COMMENTS_VIEW_ID, COMMENTS_VIEW_STORAGE_ID, COMMENTS_VIEW_TITLE, CommentNodeRenderer, CommentsList, CommentsMenus, Filter, ResourceWithCommentsRenderer };
