import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useEffect, useMemo, useRef, useState } from "react";
import { Loader2 } from "lucide-react";
import { Tooltip, TooltipContent, TooltipTrigger, } from "../../components/ui/tooltip";
import { useEditContext, useFieldsEditContext } from "../client/editContext";
import { SimpleTable } from "../ui/SimpleTable";
import { getAbsoluteIconUrl, stripGuid } from "../utils";
// All validation click-throughs open in this one shared slot (EditorSlot.tag).
const VALIDATION_SLOT_TAG = "validation";
// Key parts must normalize the same way for descriptors coming from
// validation results (no database, possibly braced ids) and stubs coming from
// the items repository (database always set, e.g. "master"). Asymmetric keys
// make a fetched stub "miss" its requested slot, which re-triggers the fetch
// effect indefinitely.
function getValidationItemKey(item) {
    return [
        stripGuid(item.id ?? ""),
        item.language?.toLowerCase(),
        item.version ?? 0,
        item.database?.toLowerCase() || "master",
    ].join("|");
}
function getValidationItemIdentityKey(item) {
    return [
        stripGuid(item.id ?? ""),
        item.language?.toLowerCase(),
        item.database?.toLowerCase() || "master",
    ].join("|");
}
export function Validation() {
    const editContext = useEditContext();
    const fieldsContext = useFieldsEditContext();
    const [stubByItemKey, setStubByItemKey] = useState({});
    // Fetch each result's stub at most once per results set. A response that
    // cannot satisfy the requested key (deleted item, language fallback, no
    // access) must not re-trigger the effect chain, or it fetches forever.
    const requestedStubKeysRef = useRef(new Set());
    const validationResults = useMemo(() => editContext?.validationResult?.flatMap((x) => x.results.map((y) => ({
        ...y,
        item: { ...x.item, id: y.itemId },
        language: x.item.language,
        version: x.item.version,
    }))), [editContext?.validationResult]);
    useEffect(() => {
        requestedStubKeysRef.current = new Set();
    }, [validationResults]);
    useEffect(() => {
        if (!editContext?.itemsRepository ||
            !validationResults ||
            validationResults.length === 0)
            return;
        const descriptorsByKey = new Map();
        validationResults.forEach((result) => {
            const key = getValidationItemKey(result.item);
            if (!stubByItemKey[key] && !requestedStubKeysRef.current.has(key)) {
                descriptorsByKey.set(key, result.item);
            }
        });
        const descriptorsMissingStubs = Array.from(descriptorsByKey.values());
        if (descriptorsMissingStubs.length === 0)
            return;
        descriptorsByKey.forEach((_, key) => {
            requestedStubKeysRef.current.add(key);
        });
        let cancelled = false;
        const descriptorKeysByIdentity = new Map();
        descriptorsMissingStubs.forEach((descriptor) => {
            const identityKey = getValidationItemIdentityKey(descriptor);
            const keys = descriptorKeysByIdentity.get(identityKey) ?? [];
            keys.push(getValidationItemKey(descriptor));
            descriptorKeysByIdentity.set(identityKey, keys);
        });
        editContext.itemsRepository
            .getItemsStubs(descriptorsMissingStubs, editContext.currentItemDescriptor)
            .then((stubs) => {
            if (cancelled || stubs.length === 0)
                return;
            setStubByItemKey((current) => {
                const next = { ...current };
                let changed = false;
                stubs.forEach((stub) => {
                    next[getValidationItemKey(stub)] = stub;
                    changed = true;
                    descriptorKeysByIdentity
                        .get(getValidationItemIdentityKey(stub))
                        ?.forEach((key) => {
                        next[key] = stub;
                    });
                });
                return changed ? next : current;
            });
        })
            .catch((error) => {
            console.error("Error loading validation item stubs:", error);
        });
        return () => {
            cancelled = true;
        };
    }, [
        editContext?.itemsRepository,
        editContext?.currentItemDescriptor,
        stubByItemKey,
        validationResults,
    ]);
    if (!validationResults)
        return null;
    const nameBodyTemplate = (validationResult) => {
        const stub = stubByItemKey[getValidationItemKey(validationResult.item)];
        const name = (_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [stub?.icon && (_jsx("img", { src: getAbsoluteIconUrl(stub.icon, 24), alt: "", width: 24, height: 24, className: "h-6 w-6 shrink-0 self-center object-contain" })), _jsxs("span", { className: "min-w-0 break-all", children: [_jsx("span", { className: "font-normal", children: validationResult.itemName }), " ", _jsxs("span", { className: "text-neutral-grey-50", children: ["(", validationResult.language, ", ", validationResult.version, ")"] })] })] }));
        if (!stub?.path)
            return name;
        return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: name }), _jsx(TooltipContent, { side: "bottom", align: "start", className: "max-w-72 break-all", children: stub.path })] }));
    };
    const validationTemplate = (validationResult) => {
        return _jsx("div", { className: "break-words", children: validationResult.message });
    };
    const columns = [
        { body: nameBodyTemplate, header: "Item" },
        {
            body: validationTemplate,
            header: "Issue",
        },
    ];
    if (validationResults.length === 0) {
        return (_jsx("div", { className: "text-neutral-grey-50 p-4 text-center text-xs", children: "No validation results" }));
    }
    return (_jsxs("div", { children: [editContext?.validating && (_jsx("div", { className: "p-3 text-center", children: _jsx(Loader2, { strokeWidth: 1, className: "h-5 w-5 animate-spin" }) })), !editContext?.validating && (_jsx(SimpleTable, { "data-testid": "validation-results-table", items: validationResults, columns: columns, onRowClick: async ({ item: result }) => {
                    if (!editContext)
                        return;
                    const itemDescriptor = {
                        id: result.itemId,
                        language: result.language,
                        version: result.version,
                    };
                    const slots = editContext.editorSlots ?? [];
                    const openSlot = slots.find((slot) => stripGuid(slot.itemDescriptor?.id ?? "") ===
                        stripGuid(itemDescriptor.id) &&
                        slot.itemDescriptor?.language?.toLowerCase() ===
                            itemDescriptor.language?.toLowerCase() &&
                        slot.itemDescriptor?.version === itemDescriptor.version);
                    if (openSlot) {
                        editContext.setActiveSlot(openSlot.slotId);
                    }
                    else {
                        // Open the result in the shared validation slot instead of
                        // replacing the reviewed page: replacing the page collapses the
                        // workbox scope and re-scopes this list to the single clicked
                        // item. Every click routes into the same tagged slot, so
                        // stepping through results never accumulates panes.
                        const validationSlot = slots.find((slot) => slot.tag === VALIDATION_SLOT_TAG);
                        const loadedItem = await editContext.loadItem(itemDescriptor, validationSlot
                            ? { targetSlotId: validationSlot.slotId }
                            : { openInNewSlot: true, newSlotTag: VALIDATION_SLOT_TAG });
                        if (!loadedItem)
                            return;
                    }
                    // Selection first: it keeps the component highlighted on its page
                    // canvas, and the focus-clearing effect only retains focusedField
                    // while selection[0] matches its item id.
                    editContext.select?.([result.itemId]);
                    if (result.fieldId) {
                        await fieldsContext?.setFocusedField({
                            item: itemDescriptor,
                            fieldId: result.fieldId,
                        }, false);
                    }
                } }))] }));
}
//# sourceMappingURL=Validation.js.map