import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
import { useEffect, useMemo, useState } from "react";
import { useEditContext, useFieldsEditContext } from "../client/editContext";
import { Comment as CommentComponent } from "./Comment";
import { CommentEditor, COMMENT_INPUT_PLACEHOLDER } from "./CommentEditor";
import { SuggestedEditComponent } from "./SuggestedEdit";
import { SuggestionCommentThread } from "./SuggestionCommentThread";
import { SimpleToolbar } from "../ui/SimpleToolbar";
import { SimpleIconButton } from "../ui/SimpleIconButton";
import { Button } from "../../components/ui/button";
import { localStorageService } from "../services/localStorageService";
import { Popover, PopoverContent, PopoverTrigger, } from "../../components/ui/popover";
import { Tooltip, TooltipContent, TooltipTrigger, } from "../../components/ui/tooltip";
import { CloseButton } from "../../components/ui/close-button";
import { MessageSquarePlus, PenLine, User, Calendar, FolderTree, ListChecks, } from "lucide-react";
import { ChevronDown } from "lucide-react";
import { searchUsers } from "../services/userService";
import { createOrUpdateComment } from "../services/reviewsService";
import { canReplyToComment } from "./feedbackCommentRules";
import { toast } from "sonner";
import uuid from "react-uuid";
import { FeedbackCardFrame, getCommentContextSegments } from "./FeedbackCard";
import { selectFeedbackComment } from "./feedbackSelection";
import { getFeedbackActivityTime, isSuggestedEdit, } from "./feedbackOrdering";
import { FEEDBACK_SIDEBAR_TEXT_CLASS } from "./feedbackSidebarText";
import { shouldRemoveNoOpSuggestion } from "./suggestionContentChange";
import { activateCommentsMode, enterSuggestionsMode, exitSuggestionsMode, isSuggestionsModeAvailable, } from "./openCommentsFeedback";
import { FeedbackModeIntro } from "./FeedbackModeIntro";
// Icon spacing must come from margin, not padding: with border-box sizing,
// padding shrinks the SVG viewport and rescales the 24px icon grid off the
// pixel grid. absoluteStrokeWidth keeps the strokes at exactly one device
// pixel — sub-pixel strokes anti-alias into blur.
const toolbarIconProps = {
    strokeWidth: 1,
    absoluteStrokeWidth: true,
    className: "m-0.5",
};
function formatAccountName(userName) {
    const trimmed = userName?.trim();
    if (!trimmed)
        return "Unknown";
    const slashIndex = trimmed.lastIndexOf("\\");
    if (slashIndex >= 0 && slashIndex < trimmed.length - 1) {
        return trimmed.slice(slashIndex + 1);
    }
    return trimmed;
}
function getStoredAuthorName(item) {
    const storedDisplayName = item.authorDisplayName?.trim();
    const author = item.author?.trim() || "";
    if (storedDisplayName &&
        storedDisplayName.localeCompare(author, undefined, {
            sensitivity: "accent",
        }) !== 0) {
        return storedDisplayName;
    }
    return undefined;
}
export function Comments() {
    const editContext = useEditContext();
    const fieldsContext = useFieldsEditContext();
    const [feedbackItems, setFeedbackItems] = useState([]);
    const [collapsedThreads, setCollapsedThreads] = useState({});
    const [resolvedAuthorNames, setResolvedAuthorNames] = useState({});
    const [replyTarget, setReplyTarget] = useState(null);
    const [savingReplyRootId, setSavingReplyRootId] = useState(null);
    const [sortField, setSortField] = useState(() => localStorageService.getString("comments.sortField") ||
        "date");
    const [sortDirection, setSortDirection] = useState(() => localStorageService.getString("comments.sortDirection") || "desc");
    // Filter states with localStorage persistence
    const [selectedPersonFilter, setSelectedPersonFilter] = useState(() => localStorageService.getItem("comments.personFilter") || []);
    // First-run hint explaining the comment-vs-suggest distinction. Dismissed
    // permanently once the user has seen it.
    const [introDismissed, setIntroDismissed] = useState(() => localStorageService.getItem("reviews.feedbackIntroDismissed") ===
        true);
    const dismissIntro = () => {
        setIntroDismissed(true);
        localStorageService.setItem("reviews.feedbackIntroDismissed", true);
    };
    const availableTags = editContext?.availableCommentTags || [];
    // Derive available authors from comments
    const availableAuthors = useMemo(() => {
        const authors = new Map();
        (editContext?.comments || []).forEach((c) => {
            if (c.author) {
                authors.set(c.author, getStoredAuthorName(c) ||
                    resolvedAuthorNames[c.author] ||
                    formatAccountName(c.author));
            }
        });
        return Array.from(authors.entries()).map(([id, name]) => ({ id, name }));
    }, [editContext?.comments, resolvedAuthorNames]);
    const suggestionCommentsBySuggestionId = useMemo(() => {
        const map = new Map();
        (editContext?.comments || []).forEach((comment) => {
            if (!comment.suggestedEditId)
                return;
            const comments = map.get(comment.suggestedEditId) || [];
            comments.push(comment);
            map.set(comment.suggestedEditId, comments);
        });
        map.forEach((comments) => comments.sort((a, b) => new Date(a.created || "").getTime() -
            new Date(b.created || "").getTime()));
        return map;
    }, [editContext?.comments]);
    const repliesByRootId = useMemo(() => {
        // Build lookup for comments by id
        const commentsOnly = feedbackItems.filter((x) => !isSuggestedEdit(x) && !x.suggestedEditId);
        const idToComment = new Map();
        commentsOnly.forEach((c) => idToComment.set(c.id, c));
        const getRootId = (c) => {
            let current = c;
            const seen = new Set();
            while (current && current.parentCommentId) {
                if (seen.has(current.id))
                    break;
                seen.add(current.id);
                const parent = idToComment.get(current.parentCommentId);
                if (!parent)
                    return current.parentCommentId; // parent not loaded; fallback
                current = parent;
            }
            return current?.id;
        };
        const map = new Map();
        commentsOnly.forEach((c) => {
            if (!c.parentCommentId)
                return;
            const rootId = getRootId(c);
            if (!rootId)
                return;
            const arr = map.get(rootId) || [];
            arr.push(c);
            map.set(rootId, arr);
        });
        map.forEach((replies, key) => {
            replies.sort((a, b) => new Date(a.created || "").getTime() -
                new Date(b.created || "").getTime());
            map.set(key, replies);
        });
        return map;
    }, [feedbackItems]);
    const topLevelFeedbackItems = useMemo(() => feedbackItems.filter((item) => isSuggestedEdit(item) ||
        (!item.suggestedEditId && !item.parentCommentId)), [feedbackItems]);
    const getAuthorName = (item) => {
        const storedName = getStoredAuthorName(item);
        if (storedName)
            return storedName;
        if (item.author && resolvedAuthorNames[item.author]) {
            return resolvedAuthorNames[item.author];
        }
        return formatAccountName(item.author);
    };
    const buildReplyFrom = (base, text) => {
        const descriptor = editContext.currentItemDescriptor;
        return {
            id: uuid(),
            isNew: false,
            parentCommentId: base.id,
            itemId: base.itemId,
            itemName: base.itemName,
            fieldId: base.fieldId,
            fieldName: base.fieldName,
            mainItemId: base.mainItemId,
            language: base.language || descriptor.language,
            version: base.version || descriptor.version,
            position: 0,
            rangeStart: base.rangeStart,
            rangeEnd: base.rangeEnd,
            author: editContext?.user?.name,
            authorDisplayName: editContext?.user?.fullName,
            created: new Date().toISOString(),
            text,
            tags: "",
            fieldValue: base.fieldValue,
        };
    };
    const handleReplySave = async (rootId, target, text) => {
        if (!editContext?.currentItemDescriptor)
            return;
        activateCommentsMode(editContext, { reason: "comment-reply" });
        const newReply = buildReplyFrom(target, text);
        setSavingReplyRootId(rootId);
        try {
            const result = await createOrUpdateComment(newReply);
            if (result.type === "error" || result.type === "unauthorized") {
                console.error("Failed to save reply comment:", result);
                toast.error(result.details || result.summary || "Failed to save reply comment");
                return;
            }
            setReplyTarget(null);
            await editContext.loadComments?.();
        }
        catch (error) {
            console.error("Failed to save reply comment:", error);
            toast.error(error instanceof Error ? error.message : "Failed to save reply comment");
        }
        finally {
            setSavingReplyRootId(null);
        }
    };
    const renderThreadReplyEditor = (rootComment) => {
        if (!editContext)
            return null;
        const hasTargetedReply = replyTarget?.rootId === rootComment.id;
        const target = hasTargetedReply ? replyTarget.comment : rootComment;
        if (!canReplyToComment(target, rootComment, editContext.readonly)) {
            return null;
        }
        return (_jsxs("div", { className: "mt-3 pt-3", onClick: (event) => event.stopPropagation(), children: [target.id !== rootComment.id && (_jsxs("div", { className: "text-neutral-grey-50 mb-2 flex items-center justify-between gap-2 text-xs", children: [_jsxs("span", { className: "truncate", children: ["Replying to ", getAuthorName(target)] }), _jsx("button", { type: "button", className: "badge-pad-sm text-neutral-grey-50 hover:bg-neutral-grey-5 shrink-0 rounded", onClick: () => setReplyTarget(null), children: "Cancel" })] })), _jsx(CommentEditor, { onSave: (text) => handleReplySave(rootComment.id, target, text), onCancel: () => setReplyTarget(null), isSaving: savingReplyRootId === rootComment.id, placeholder: COMMENT_INPUT_PLACEHOLDER, submitLabel: "Reply", showTagSelector: false, showCancelButton: false, clearOnSave: true, compact: true }, target.id)] }));
    };
    const handleSort = (field) => {
        if (sortField === field) {
            setSortDirection((prev) => (prev === "asc" ? "desc" : "asc"));
            return;
        }
        setSortField(field);
        setSortDirection("desc");
    };
    useEffect(() => {
        let cancelled = false;
        const load = async () => {
            // Base lists from context
            const mainComments = (editContext?.comments || []).filter((comment) => !comment.suggestedEditId);
            const suggestedEdits = editContext?.suggestedEdits || [];
            // Combine comments and suggestions
            let filtered = [...mainComments, ...suggestedEdits];
            filtered = filtered.filter((item) => {
                if (!isSuggestedEdit(item))
                    return true;
                const commentCount = (suggestionCommentsBySuggestionId.get(item.id) || []).length;
                return !shouldRemoveNoOpSuggestion(item.oldValue, item.newValue, commentCount);
            });
            // Filter resolved comments
            if (!editContext?.showResolvedComments) {
                filtered = filtered.filter((item) => !("isResolved" in item && item.isResolved));
            }
            // Filter by person (author)
            if (selectedPersonFilter.length > 0) {
                filtered = filtered.filter((item) => "author" in item &&
                    item.author &&
                    selectedPersonFilter.includes(item.author));
            }
            const multiplier = sortDirection === "asc" ? 1 : -1;
            filtered = filtered
                .map((item, index) => ({ item, index }))
                .sort((a, b) => {
                switch (sortField) {
                    case "date": {
                        const timeDifference = getFeedbackActivityTime(a.item, suggestionCommentsBySuggestionId) -
                            getFeedbackActivityTime(b.item, suggestionCommentsBySuggestionId);
                        return multiplier * timeDifference || a.index - b.index;
                    }
                    case "structure": {
                        const pathA = "itemName" in a.item ? a.item.itemName || "" : "";
                        const pathB = "itemName" in b.item ? b.item.itemName || "" : "";
                        return (multiplier * pathA.localeCompare(pathB) || a.index - b.index);
                    }
                    default:
                        return a.index - b.index;
                }
            })
                .map(({ item }) => item);
            if (!cancelled)
                setFeedbackItems(filtered);
        };
        load();
        return () => {
            cancelled = true;
        };
    }, [
        editContext,
        suggestionCommentsBySuggestionId,
        selectedPersonFilter,
        sortField,
        sortDirection,
    ]);
    useEffect(() => {
        const allFeedbackItems = [
            ...(editContext?.comments || []),
            ...(editContext?.suggestedEdits || []),
        ];
        const authors = Array.from(new Set(allFeedbackItems
            .map((item) => item.author?.trim())
            .filter((author) => !!author))).filter((author) => !resolvedAuthorNames[author]);
        if (authors.length === 0)
            return;
        let cancelled = false;
        const resolveAuthors = async () => {
            const entries = await Promise.all(authors.map(async (author) => {
                try {
                    const users = await searchUsers(author, 5);
                    const exact = users.find((user) => user.userName.localeCompare(author, undefined, {
                        sensitivity: "accent",
                    }) === 0) || users[0];
                    const displayName = exact?.displayName?.trim();
                    if (displayName &&
                        displayName.localeCompare(author, undefined, {
                            sensitivity: "accent",
                        }) !== 0) {
                        return [author, displayName];
                    }
                }
                catch {
                    // Best effort: older feedback may only have the account id stored.
                }
                return [author, formatAccountName(author)];
            }));
            if (cancelled)
                return;
            setResolvedAuthorNames((current) => {
                const next = { ...current };
                entries.forEach(([author, displayName]) => {
                    next[author] = displayName;
                });
                return next;
            });
        };
        resolveAuthors();
        return () => {
            cancelled = true;
        };
    }, [editContext?.comments, editContext?.suggestedEdits, resolvedAuthorNames]);
    useEffect(() => {
        localStorageService.setString("comments.sortField", sortField);
    }, [sortField]);
    useEffect(() => {
        localStorageService.setString("comments.sortDirection", sortDirection);
    }, [sortDirection]);
    // Persist filter states to localStorage
    useEffect(() => {
        localStorageService.setItem("comments.personFilter", selectedPersonFilter);
    }, [selectedPersonFilter]);
    const suggestionsAvailable = isSuggestionsModeAvailable(editContext);
    const isSuggestionsMode = editContext?.mode === "suggestions";
    const handleAddComment = () => editContext?.addComment();
    const handleStartSuggesting = () => enterSuggestionsMode(editContext, { reason: "sidebar" });
    const handleStopSuggesting = () => exitSuggestionsMode(editContext, { reason: "sidebar" });
    return (_jsxs("div", { className: `${FEEDBACK_SIDEBAR_TEXT_CLASS} flex h-full flex-col`, children: [_jsxs(SimpleToolbar, { children: [_jsxs("div", { className: "flex items-center gap-1", children: [_jsx(SimpleIconButton, { id: "add-comment", "data-testid": "add-comment-button", icon: _jsx(MessageSquarePlus, { size: 16, ...toolbarIconProps }), label: "Add a comment", showTooltip: false, className: "gap-1.5 px-2 py-1", onClick: handleAddComment, children: _jsx("span", { children: "Add a comment" }) }), suggestionsAvailable && (_jsx(SimpleIconButton, { id: "suggest-edits", "data-testid": "suggest-edits-button", selected: isSuggestionsMode, icon: _jsx(PenLine, { size: 16, ...toolbarIconProps }), label: "Suggestions mode", showTooltip: false, className: "gap-1.5 px-2 py-1", onClick: isSuggestionsMode ? handleStopSuggesting : handleStartSuggesting, children: _jsx("span", { children: "Suggestions mode" }) }))] }), _jsxs("div", { className: "ml-auto flex items-center gap-1", children: [_jsx("span", { className: "text-neutral-grey-50 text-xs", children: "Filter by:" }), _jsx(SimpleIconButton, { selected: !!editContext?.showResolvedComments, icon: _jsx(ListChecks, { size: 12, ...toolbarIconProps }), label: editContext?.showResolvedComments
                                    ? "Hide Resolved Comments"
                                    : "Show Resolved Comments", tooltipContentClassName: FEEDBACK_SIDEBAR_TEXT_CLASS, onClick: () => {
                                    editContext?.setShowResolvedComments((x) => !x);
                                } }), _jsxs(Tooltip, { delayDuration: 500, children: [_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(TooltipTrigger, { asChild: true, children: _jsx(SimpleIconButton, { showTooltip: false, selected: selectedPersonFilter.length > 0, icon: _jsx(User, { size: 12, ...toolbarIconProps }), label: selectedPersonFilter.length > 0
                                                            ? `People (${selectedPersonFilter.length})`
                                                            : "People", onClick: () => { } }) }) }), _jsxs(PopoverContent, { className: `${FEEDBACK_SIDEBAR_TEXT_CLASS} w-72 p-3`, align: "start", children: [_jsx("div", { className: "text-neutral-grey-50 mb-2 text-xs", children: "Filter by person" }), availableAuthors.length > 0 ? (_jsx("div", { className: "flex flex-wrap gap-2", children: availableAuthors.map((author) => {
                                                            const isSelected = selectedPersonFilter.includes(author.id);
                                                            return (_jsx(Button, { type: "button", size: "sm", variant: isSelected ? "secondary" : "outline", className: "h-6 px-2 text-xs", onClick: () => {
                                                                    setSelectedPersonFilter((prev) => prev.includes(author.id)
                                                                        ? prev.filter((id) => id !== author.id)
                                                                        : [...prev, author.id]);
                                                                }, children: author.name }, author.id));
                                                        }) })) : (_jsx("div", { className: "text-neutral-grey-50 text-xs", children: "No authors found" })), selectedPersonFilter.length > 0 && (_jsx("div", { className: "mt-3 text-right", children: _jsx(Button, { size: "sm", variant: "ghost", className: "h-6 px-2 text-xs", onClick: () => setSelectedPersonFilter([]), children: "Clear" }) }))] })] }), _jsx(TooltipContent, { className: FEEDBACK_SIDEBAR_TEXT_CLASS, children: "Filter by person" })] }), _jsx("div", { className: "bg-border-default mx-2 h-5 w-px" }), _jsx("span", { className: "text-neutral-grey-50 text-xs", children: "Sort by:" }), _jsx(SimpleIconButton, { selected: sortField === "structure", icon: _jsx(FolderTree, { size: 12, ...toolbarIconProps }), label: `Structure ${sortField === "structure" ? (sortDirection === "asc" ? "↑" : "↓") : ""}`, tooltipContentClassName: FEEDBACK_SIDEBAR_TEXT_CLASS, onClick: () => handleSort("structure") }), _jsx(SimpleIconButton, { selected: sortField === "date", icon: _jsx(Calendar, { size: 12, ...toolbarIconProps }), label: `Date ${sortField === "date" ? (sortDirection === "asc" ? "↑" : "↓") : ""}`, tooltipContentClassName: FEEDBACK_SIDEBAR_TEXT_CLASS, onClick: () => handleSort("date") })] })] }), isSuggestionsMode && (_jsxs("div", { className: "border-border-default text-neutral-grey-100 flex items-center gap-2 border-b px-3 py-2", children: [_jsx(PenLine, { size: 14, strokeWidth: 1.75, className: "text-highlight-100 shrink-0" }), _jsx("span", { children: "Suggesting \u2014 edits you make on the page are saved as suggestions, not direct changes." }), _jsx("button", { type: "button", className: "text-neutral-grey-50 hover:bg-neutral-grey-5 hover:text-neutral-grey-100 ml-auto shrink-0 rounded px-2 py-0.5", onClick: handleStopSuggesting, children: "Turn off" })] })), _jsx("div", { className: "flex-1 overflow-auto", children: topLevelFeedbackItems.length === 0 ? (
                // Wait for the load to settle so the onboarding state does not flash
                // on pages that actually have feedback.
                editContext?.commentsLoaded ? (_jsx(FeedbackModeIntro, { variant: "empty", canSuggest: suggestionsAvailable, onAddComment: handleAddComment, onStartSuggesting: handleStartSuggesting })) : null) : (_jsxs(_Fragment, { children: [!introDismissed && (_jsx(FeedbackModeIntro, { variant: "banner", canSuggest: suggestionsAvailable, onAddComment: handleAddComment, onStartSuggesting: handleStartSuggesting, onDismiss: dismissIntro })), _jsx("div", { className: "h-fill-available p-4", children: topLevelFeedbackItems.map((item) => {
                                if (isSuggestedEdit(item)) {
                                    const suggestionComments = suggestionCommentsBySuggestionId.get(item.id) || [];
                                    return (_jsx("div", { children: _jsx(SuggestedEditComponent, { edit: item, resolvedAuthorName: getAuthorName(item), children: (!editContext?.readonly ||
                                                suggestionComments.length > 0) && (_jsx(SuggestionCommentThread, { suggestion: item, comments: suggestionComments, resolvedAuthorNames: resolvedAuthorNames, compact: true })) }) }, item.id));
                                }
                                const topLevelComment = item;
                                const replies = repliesByRootId.get(topLevelComment.id) || [];
                                const isCollapsed = !!collapsedThreads[topLevelComment.id];
                                const selectedInThread = editContext?.selectedComment?.id === topLevelComment.id ||
                                    replies.some((reply) => reply.id === editContext?.selectedComment?.id);
                                const contextSegments = getCommentContextSegments(topLevelComment, editContext?.currentItemDescriptor?.name);
                                const cancelNewComment = (event) => {
                                    event.stopPropagation();
                                    if (!topLevelComment.isNew)
                                        return;
                                    editContext?.setComments?.((comments) => comments.filter((comment) => comment.id !== topLevelComment.id));
                                    if (editContext?.selectedComment?.id === topLevelComment.id) {
                                        editContext.setSelectedComment(undefined);
                                    }
                                };
                                return (_jsxs(FeedbackCardFrame, { contextSegments: contextSegments, selected: selectedInThread, className: "cursor-pointer", headerAction: topLevelComment.isNew ? (_jsx(CloseButton, { onClick: cancelNewComment, "data-testid": "add-comment-cancel", label: "Cancel" })) : undefined, onClick: () => selectFeedbackComment(topLevelComment, editContext, fieldsContext), children: [_jsx(CommentComponent, { comment: topLevelComment, availableTags: availableTags, rootCommentId: topLevelComment.id, resolvedAuthorName: getAuthorName(topLevelComment), variant: "inline", showContextHeader: false, showEditorCancelButton: !topLevelComment.isNew, onReply: (target) => {
                                                setReplyTarget({
                                                    rootId: topLevelComment.id,
                                                    comment: target,
                                                });
                                                setCollapsedThreads((prev) => ({
                                                    ...prev,
                                                    [topLevelComment.id]: false,
                                                }));
                                            } }), replies.length > 0 && (_jsxs("div", { children: [_jsxs("button", { type: "button", className: "text-neutral-grey-100 hover:text-neutral-grey-100 mb-3 flex items-center gap-2 text-left text-sm", onClick: (event) => {
                                                        event.stopPropagation();
                                                        setCollapsedThreads((prev) => ({
                                                            ...prev,
                                                            [topLevelComment.id]: !prev[topLevelComment.id],
                                                        }));
                                                    }, children: [isCollapsed ? (_jsx(ChevronDown, { size: 16, strokeWidth: 1.75 })) : (_jsx(ChevronDown, { size: 16, strokeWidth: 1.75, className: "rotate-180" })), isCollapsed
                                                            ? `${replies.length} ${replies.length === 1 ? "reply" : "replies"}`
                                                            : `${replies.length} ${replies.length === 1 ? "reply" : "replies"}`] }), !isCollapsed && (_jsx(_Fragment, { children: replies.map((reply) => (_jsx(CommentComponent, { comment: reply, availableTags: availableTags, rootCommentId: topLevelComment.id, resolvedAuthorName: getAuthorName(reply), variant: "inline", showContextHeader: false, onReply: (target) => setReplyTarget({
                                                            rootId: topLevelComment.id,
                                                            comment: target,
                                                        }) }, reply.id))) })), renderThreadReplyEditor(topLevelComment)] })), replies.length === 0 && (_jsx("div", { children: renderThreadReplyEditor(topLevelComment) }))] }, topLevelComment.id));
                            }) })] })) })] }));
}
//# sourceMappingURL=Comments.js.map