import Label from "../../../Models/DatabaseModels/Label";
import Includes from "../../../Types/BaseDatabase/Includes";
import Search from "../../../Types/BaseDatabase/Search";
import SortOrder from "../../../Types/BaseDatabase/SortOrder";
import { LIMIT_PER_PROJECT } from "../../../Types/Database/LimitMax";
import IconProp from "../../../Types/Icon/IconProp";
import ObjectID from "../../../Types/ObjectID";
import ModelAPI from "../../Utils/ModelAPI/ModelAPI";
import Icon from "../Icon/Icon";
import React, { useCallback, useEffect, useMemo, useRef, useState, } from "react";
const SEARCH_DEBOUNCE_MS = 250;
const SEARCH_PAGE_SIZE = 50;
const LABEL_PREVIEW_LIMIT = 50;
const flattenOptions = (options) => {
    if (!options) {
        return [];
    }
    const flat = [];
    for (const item of options) {
        if (item &&
            typeof item === "object" &&
            "options" in item &&
            Array.isArray(item.options)) {
            for (const sub of item.options) {
                flat.push(sub);
            }
        }
        else {
            flat.push(item);
        }
    }
    return flat;
};
/*
 * Pull a string key out of any incoming DropdownValue (string | number |
 * boolean | ObjectID-stringified). All comparisons + map keys use the
 * string form to dodge mismatches between numeric and string IDs.
 */
const valueKey = (v) => {
    if (typeof v === "string") {
        return v;
    }
    return String(v);
};
/*
 * Normalize the prop value down to a list of string keys regardless of
 * the shape callers pass. Mirrors react-select-Dropdown's value coercion
 * so the form layer can keep storing whichever shape it has today.
 */
const valueToKeys = (v) => {
    if (v === undefined || v === null) {
        return [];
    }
    if (typeof v === "string") {
        return v === "" ? [] : [v];
    }
    if (typeof v === "number" || typeof v === "boolean") {
        return [String(v)];
    }
    if (v instanceof ObjectID) {
        return [v.toString()];
    }
    if (Array.isArray(v)) {
        const keys = [];
        for (const item of v) {
            if (item === undefined || item === null) {
                continue;
            }
            if (item instanceof ObjectID) {
                keys.push(item.toString());
                continue;
            }
            if (typeof item === "string") {
                if (item !== "") {
                    keys.push(item);
                }
                continue;
            }
            if (typeof item === "number" || typeof item === "boolean") {
                keys.push(String(item));
                continue;
            }
            if (typeof item === "object" && "value" in item) {
                keys.push(valueKey(item.value));
                continue;
            }
        }
        return keys;
    }
    if (typeof v === "object" && v && "value" in v) {
        return [valueKey(v.value)];
    }
    return [];
};
/*
 * Best-effort runtime detection of a `labels` ManyToMany. The convention
 * across OneUptime models is a property literally named `labels` typed
 * `Array<Label>` (44 such models at last count), so a property-existence
 * probe is the cheapest reliable check we can do without yanking column
 * metadata. Callers can override via `enableLabelsTab` if heuristics fail.
 */
const detectLabelsField = (ModelType) => {
    var _a;
    if (!ModelType) {
        return false;
    }
    try {
        const instance = new ModelType();
        /*
         * OneUptime models declare `public labels?: Array<Label> = undefined;`
         * so the property is present on the instance (initialized to undefined)
         * and the `in` check is reliable. We add the column-metadata probe
         * underneath as a belt-and-suspenders fallback in case a future model
         * declares the field differently.
         */
        if ("labels" in instance) {
            return true;
        }
        const metaProbe = instance;
        if (typeof metaProbe.getTableColumnMetadata === "function" &&
            metaProbe.getTableColumnMetadata("labels")) {
            return true;
        }
        if (typeof metaProbe.getTableColumns === "function") {
            const cols = metaProbe.getTableColumns();
            if ((_a = cols === null || cols === void 0 ? void 0 : cols.columns) === null || _a === void 0 ? void 0 : _a.includes("labels")) {
                return true;
            }
        }
        return false;
    }
    catch (_b) {
        return false;
    }
};
const EntityDropdown = (props) => {
    const isMulti = Boolean(props.isMultiSelect);
    const modelType = props.modelType;
    const labelField = props.labelField || "name";
    const valueField = props.valueField || "_id";
    /*
     * Color column auto-detection mirrors ModelForm.fetchDropdownOptions —
     * BaseModel exposes getFirstColorColumn() which returns the first column
     * decorated as a color (Severity.color, Status.color, etc.). When set,
     * we include it in the server-side SELECT so our supplemental searches
     * carry the same color metadata as the form's pre-fetch.
     */
    const detectedColorField = useMemo(() => {
        if (!modelType) {
            return undefined;
        }
        try {
            const probe = new modelType();
            if (typeof probe.getFirstColorColumn === "function") {
                return probe.getFirstColorColumn() || undefined;
            }
        }
        catch (_a) {
            // Model can't be instantiated for inspection; fall back to no color.
        }
        return undefined;
    }, [modelType]);
    const colorField = props.colorField || detectedColorField;
    const hasLabelsAutoDetected = useMemo(() => {
        return detectLabelsField(modelType);
    }, [modelType]);
    /*
     * The Labels tab is only meaningful on multi-select against a labeled
     * entity. enableLabelsTab is an opt-out override (or explicit opt-in for
     * cases where the heuristic misses).
     */
    const labelsTabEnabled = isMulti &&
        (props.enableLabelsTab !== undefined
            ? props.enableLabelsTab
            : hasLabelsAutoDetected) &&
        Boolean(modelType);
    /*
     * optionsCache is the single source of truth for which DropdownOption goes
     * with which value. It's seeded with the props.options the form pre-fetched
     * and grows as the user types (server-side search) or as we resolve
     * previously-selected values that weren't in the first page.
     */
    const initialFlat = useMemo(() => {
        return flattenOptions(props.options);
    }, [props.options]);
    const optionsCacheRef = useRef(new Map());
    /*
     * Seed the cache *during render* rather than in a useEffect. A pure-effect
     * seed only fires after first paint, which means the first render's
     * `selectedOptions` lookup misses, the chip falls back to "label = raw
     * UUID", and the user sees an ID flash by before the next render swaps
     * in the real label. Mutating a ref during render is safe because we
     * don't read derived state from it within the same render — we read it
     * via `selectedOptions` below, whose useMemo deps already include the
     * inputs that change the cache.
     */
    for (const opt of initialFlat) {
        optionsCacheRef.current.set(valueKey(opt.value), opt);
    }
    /*
     * selectedKeys is the source of truth for what the user has picked. We
     * never mirror it from props beyond the initial sync — props.value updates
     * are honored through the effect below so saved-state restores work.
     */
    const externalKeys = useMemo(() => {
        /*
         * value takes precedence over initialValue (matching react-select
         * controlled-component semantics). We intentionally treat `undefined`
         * as "fall back to initialValue" and any other value as authoritative
         * so callers can clear the field by passing null.
         */
        const source = props.value !== undefined ? props.value : props.initialValue;
        return valueToKeys(source);
    }, [props.value, props.initialValue]);
    /*
     * Also harvest DropdownOption envelopes from props.value / initialValue,
     * so a parent that hands us a full option (rather than a raw ID) gets
     * its label cached immediately — no resolve round-trip needed.
     */
    const inboundOptions = useMemo(() => {
        const collected = [];
        const candidates = [
            props.value,
            props.initialValue,
        ];
        for (const candidate of candidates) {
            if (!candidate) {
                continue;
            }
            if (Array.isArray(candidate)) {
                for (const item of candidate) {
                    if (item &&
                        typeof item === "object" &&
                        !(item instanceof ObjectID) &&
                        "value" in item &&
                        "label" in item) {
                        collected.push(item);
                    }
                }
                continue;
            }
            if (typeof candidate === "object" &&
                !(candidate instanceof ObjectID) &&
                "value" in candidate &&
                "label" in candidate) {
                collected.push(candidate);
            }
        }
        return collected;
    }, [props.value, props.initialValue]);
    /*
     * Same reasoning as initialFlat — seed at render time so the first paint
     * has the labels.
     */
    for (const opt of inboundOptions) {
        optionsCacheRef.current.set(valueKey(opt.value), opt);
    }
    const [selectedKeys, setSelectedKeys] = useState(externalKeys);
    /*
     * Mirror prop value back into local state when the parent rewrites it
     * (e.g. form reset, saved-view restore). We skip the round-trip if the
     * arrays are identical to avoid render thrash.
     */
    useEffect(() => {
        const same = externalKeys.length === selectedKeys.length &&
            externalKeys.every((k, i) => {
                return k === selectedKeys[i];
            });
        if (same) {
            return;
        }
        setSelectedKeys(externalKeys);
        // Also stash any new options that came in via props into the cache.
        for (const opt of initialFlat) {
            optionsCacheRef.current.set(valueKey(opt.value), opt);
        }
    }, [externalKeys.join("|")]);
    const [searchQuery, setSearchQuery] = useState("");
    const [searchResults, setSearchResults] = useState([]);
    const [isLoading, setIsLoading] = useState(false);
    const [isOpen, setIsOpen] = useState(false);
    const [highlightedIndex, setHighlightedIndex] = useState(-1);
    const [activeTab, setActiveTab] = useState("options");
    // Labels tab state.
    const [allLabels, setAllLabels] = useState([]);
    const [isLoadingLabels, setIsLoadingLabels] = useState(false);
    const [labelsLoaded, setLabelsLoaded] = useState(false);
    const [selectedLabelIds, setSelectedLabelIds] = useState([]);
    const [isApplyingLabels, setIsApplyingLabels] = useState(false);
    const [labelError, setLabelError] = useState("");
    const [expandedLabelIds, setExpandedLabelIds] = useState(new Set());
    const [resourcesByLabel, setResourcesByLabel] = useState({});
    const [loadingLabelIds, setLoadingLabelIds] = useState(new Set());
    const [labelLoadErrors, setLabelLoadErrors] = useState({});
    const containerRef = useRef(null);
    const inputRef = useRef(null);
    const debounceRef = useRef(null);
    const searchSeqRef = useRef(0);
    // Click-outside closes the popover.
    useEffect(() => {
        const handle = (event) => {
            if (containerRef.current &&
                event.target instanceof Node &&
                !containerRef.current.contains(event.target)) {
                setIsOpen(false);
            }
        };
        document.addEventListener("mousedown", handle);
        return () => {
            document.removeEventListener("mousedown", handle);
        };
    }, []);
    useEffect(() => {
        return () => {
            if (debounceRef.current !== null) {
                window.clearTimeout(debounceRef.current);
            }
        };
    }, []);
    /*
     * BaseModel -> DropdownOption. Pulls label/value (and optional color)
     * via the configured field names. We don't try to be clever about nested
     * objects — the form pre-fetcher follows the same flat convention.
     */
    const modelToOption = useCallback((item) => {
        var _a;
        const raw = item;
        const valueRaw = (_a = raw[valueField]) !== null && _a !== void 0 ? _a : item._id;
        const labelRaw = raw[labelField];
        if (valueRaw === undefined || valueRaw === null) {
            return null;
        }
        const label = typeof labelRaw === "string"
            ? labelRaw
            : labelRaw &&
                typeof labelRaw.toString ===
                    "function"
                ? labelRaw.toString()
                : "";
        const valueStr = typeof valueRaw === "string"
            ? valueRaw
            : valueRaw.toString();
        const option = {
            value: valueStr,
            label: label || valueStr,
        };
        if (colorField) {
            const colorRaw = raw[colorField];
            if (colorRaw) {
                option.color = colorRaw;
            }
        }
        return option;
    }, [labelField, valueField, colorField]);
    /*
     * Server-side search. Runs when the popover is open on the options tab.
     * Static dropdowns (no modelType) skip the API and filter the seeded
     * options client-side instead.
     */
    useEffect(() => {
        if (debounceRef.current !== null) {
            window.clearTimeout(debounceRef.current);
        }
        if (!isOpen || activeTab !== "options" || !modelType) {
            return;
        }
        const trimmed = searchQuery.trim();
        const mySeq = ++searchSeqRef.current;
        setIsLoading(true);
        debounceRef.current = window.setTimeout(async () => {
            try {
                const query = {};
                if (trimmed.length > 0) {
                    query[labelField] = new Search(trimmed);
                }
                const baseSelect = {
                    _id: true,
                    [labelField]: true,
                };
                if (colorField) {
                    baseSelect[colorField] = true;
                }
                const result = await ModelAPI.getList({
                    modelType: modelType,
                    query: query,
                    limit: SEARCH_PAGE_SIZE,
                    skip: 0,
                    select: baseSelect,
                    sort: { [labelField]: SortOrder.Ascending },
                });
                if (mySeq !== searchSeqRef.current) {
                    return; // stale
                }
                const options = [];
                for (const item of result.data) {
                    const opt = modelToOption(item);
                    if (!opt) {
                        continue;
                    }
                    options.push(opt);
                    optionsCacheRef.current.set(valueKey(opt.value), opt);
                }
                setSearchResults(options);
            }
            catch (_a) {
                if (mySeq === searchSeqRef.current) {
                    setSearchResults([]);
                }
            }
            finally {
                if (mySeq === searchSeqRef.current) {
                    setIsLoading(false);
                }
            }
        }, trimmed === "" ? 0 : SEARCH_DEBOUNCE_MS);
    }, [
        searchQuery,
        isOpen,
        activeTab,
        modelType,
        labelField,
        colorField,
        modelToOption,
    ]);
    /*
     * Resolve previously-selected values whose labels we don't yet have. Fires
     * once on mount and whenever externalKeys grows beyond what's in cache —
     * keeps the chips readable even when the form was saved with selections
     * that aren't on the dropdown's first page.
     */
    useEffect(() => {
        if (!modelType) {
            return;
        }
        const missing = selectedKeys.filter((key) => {
            return !optionsCacheRef.current.has(key);
        });
        if (missing.length === 0) {
            return;
        }
        let cancelled = false;
        const resolve = async () => {
            try {
                const baseSelect = {
                    _id: true,
                    [labelField]: true,
                };
                if (colorField) {
                    baseSelect[colorField] = true;
                }
                const result = await ModelAPI.getList({
                    modelType: modelType,
                    query: {
                        _id: new Includes(missing),
                    },
                    limit: missing.length,
                    skip: 0,
                    select: baseSelect,
                    sort: {},
                });
                if (cancelled) {
                    return;
                }
                for (const item of result.data) {
                    const opt = modelToOption(item);
                    if (opt) {
                        optionsCacheRef.current.set(valueKey(opt.value), opt);
                    }
                }
                // Force a re-render so chips pick up the resolved labels.
                setSelectedKeys((prev) => {
                    return [...prev];
                });
            }
            catch (_a) {
                // Leave the unresolved chips as raw IDs.
            }
        };
        void resolve();
        return () => {
            cancelled = true;
        };
    }, [
        selectedKeys.join("|"),
        modelType,
        labelField,
        colorField,
        modelToOption,
    ]);
    /*
     * Lazy-load the project's labels the first time the user clicks the
     * Labels tab — same gating as AffectedResourcesPicker. DON'T add
     * isLoadingLabels to the deps; setting it inside the effect would
     * self-cancel the in-flight request.
     */
    useEffect(() => {
        if (!labelsTabEnabled || activeTab !== "labels" || labelsLoaded) {
            return;
        }
        let cancelled = false;
        const loadLabels = async () => {
            setIsLoadingLabels(true);
            setLabelError("");
            try {
                const result = await ModelAPI.getList({
                    modelType: Label,
                    query: {},
                    limit: LIMIT_PER_PROJECT,
                    skip: 0,
                    select: { _id: true, name: true, color: true },
                    sort: { name: SortOrder.Ascending },
                });
                if (cancelled) {
                    return;
                }
                setAllLabels(result.data || []);
                setLabelsLoaded(true);
            }
            catch (_a) {
                if (cancelled) {
                    return;
                }
                setLabelError("Failed to load labels. You may not have permission to read labels.");
            }
            finally {
                if (!cancelled) {
                    setIsLoadingLabels(false);
                }
            }
        };
        void loadLabels();
        return () => {
            cancelled = true;
        };
    }, [labelsTabEnabled, activeTab, labelsLoaded]);
    const filteredLabels = useMemo(() => {
        const q = searchQuery.trim().toLowerCase();
        if (q === "") {
            return allLabels;
        }
        return allLabels.filter((label) => {
            const name = (label.name || "").toLowerCase();
            return name.includes(q);
        });
    }, [allLabels, searchQuery]);
    /*
     * Available results = initialFlat (the form's pre-fetched set, which has
     * the color/labels metadata we want to preserve) UNIONed with searchResults
     * (the supplemental server-side hits for big lists), filtered by the
     * current query. Even for entity-backed dropdowns we keep initialFlat in
     * the mix so colors and any other rich fields the pre-fetch carried
     * survive — our supplemental fetch only requests the minimum SELECT.
     */
    const optionsList = useMemo(() => {
        const q = searchQuery.trim().toLowerCase();
        const matches = (opt) => {
            if (q === "") {
                return true;
            }
            return opt.label.toLowerCase().includes(q);
        };
        const seen = new Set();
        const out = [];
        for (const opt of initialFlat) {
            const key = valueKey(opt.value);
            if (seen.has(key) || !matches(opt)) {
                continue;
            }
            seen.add(key);
            out.push(opt);
        }
        if (modelType) {
            for (const opt of searchResults) {
                const key = valueKey(opt.value);
                if (seen.has(key) || !matches(opt)) {
                    continue;
                }
                seen.add(key);
                out.push(opt);
            }
        }
        return out;
    }, [modelType, searchResults, searchQuery, initialFlat]);
    /*
     * Filter out the currently-selected key(s) ONLY in multi-select. For
     * single-select the user needs to see the current value too so they can
     * compare and switch — react-select keeps the selected option visible
     * (just highlights it), so we match that.
     */
    const availableOptions = useMemo(() => {
        if (!isMulti) {
            return optionsList;
        }
        const selectedSet = new Set(selectedKeys);
        return optionsList.filter((opt) => {
            return !selectedSet.has(valueKey(opt.value));
        });
    }, [optionsList, selectedKeys, isMulti]);
    // Clamp the keyboard cursor when the visible list shrinks.
    useEffect(() => {
        const len = activeTab === "labels" ? filteredLabels.length : availableOptions.length;
        if (highlightedIndex >= len) {
            setHighlightedIndex(len - 1);
        }
    }, [availableOptions, filteredLabels, highlightedIndex, activeTab]);
    const notify = useCallback((next) => {
        if (!props.onChange) {
            return;
        }
        if (isMulti) {
            props.onChange(next);
            return;
        }
        props.onChange(next.length > 0 ? next[0] : null);
    }, [isMulti, props.onChange]);
    const addOption = (opt) => {
        var _a;
        const key = valueKey(opt.value);
        optionsCacheRef.current.set(key, opt);
        if (isMulti) {
            if (selectedKeys.includes(key)) {
                return;
            }
            const next = [...selectedKeys, key];
            setSelectedKeys(next);
            notify(next);
            setSearchQuery("");
            (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
            return;
        }
        setSelectedKeys([key]);
        notify([key]);
        setSearchQuery("");
        setIsOpen(false);
    };
    const removeKey = (key) => {
        const next = selectedKeys.filter((k) => {
            return k !== key;
        });
        setSelectedKeys(next);
        notify(next);
    };
    const clearAll = () => {
        setSelectedKeys([]);
        notify([]);
    };
    /*
     * Bulk-add via labels: fetch every entity tagged with any selected label
     * and merge into the current selection. Caps generous (LIMIT_PER_PROJECT)
     * since this is an intentional bulk action, not a typeahead.
     */
    const applyLabelSelection = async () => {
        if (!modelType || selectedLabelIds.length === 0) {
            return;
        }
        setIsApplyingLabels(true);
        setLabelError("");
        try {
            const baseSelect = {
                _id: true,
                [labelField]: true,
            };
            if (colorField) {
                baseSelect[colorField] = true;
            }
            const result = await ModelAPI.getList({
                modelType: modelType,
                query: {
                    labels: new Includes(selectedLabelIds),
                },
                limit: LIMIT_PER_PROJECT,
                skip: 0,
                select: baseSelect,
                sort: { [labelField]: SortOrder.Ascending },
            });
            const existing = new Set(selectedKeys);
            const additions = [];
            for (const item of result.data) {
                const opt = modelToOption(item);
                if (!opt) {
                    continue;
                }
                const key = valueKey(opt.value);
                optionsCacheRef.current.set(key, opt);
                if (!existing.has(key)) {
                    existing.add(key);
                    additions.push(key);
                }
            }
            if (additions.length === 0) {
                setLabelError("No new entries matched the selected labels (or you don't have read access).");
                return;
            }
            const next = [...selectedKeys, ...additions];
            setSelectedKeys(next);
            notify(next);
            setSelectedLabelIds([]);
            setSearchQuery("");
            setActiveTab("options");
            setIsOpen(false);
        }
        catch (_a) {
            setLabelError("Failed to fetch entries for the selected labels.");
        }
        finally {
            setIsApplyingLabels(false);
        }
    };
    const toggleLabelId = (id) => {
        setSelectedLabelIds((prev) => {
            if (prev.includes(id)) {
                return prev.filter((x) => {
                    return x !== id;
                });
            }
            return [...prev, id];
        });
    };
    const fetchLabelPreview = async (labelId) => {
        if (!modelType) {
            return;
        }
        setLoadingLabelIds((prev) => {
            const next = new Set(prev);
            next.add(labelId);
            return next;
        });
        setLabelLoadErrors((prev) => {
            const next = Object.assign({}, prev);
            delete next[labelId];
            return next;
        });
        try {
            const baseSelect = {
                _id: true,
                [labelField]: true,
            };
            if (colorField) {
                baseSelect[colorField] = true;
            }
            const result = await ModelAPI.getList({
                modelType: modelType,
                query: {
                    labels: new Includes([labelId]),
                },
                limit: LABEL_PREVIEW_LIMIT,
                skip: 0,
                select: baseSelect,
                sort: { [labelField]: SortOrder.Ascending },
            });
            const items = [];
            for (const item of result.data) {
                const opt = modelToOption(item);
                if (opt) {
                    items.push(opt);
                    optionsCacheRef.current.set(valueKey(opt.value), opt);
                }
            }
            setResourcesByLabel((prev) => {
                return Object.assign(Object.assign({}, prev), { [labelId]: items });
            });
        }
        catch (_a) {
            setLabelLoadErrors((prev) => {
                return Object.assign(Object.assign({}, prev), { [labelId]: "Failed to load entries." });
            });
        }
        finally {
            setLoadingLabelIds((prev) => {
                const next = new Set(prev);
                next.delete(labelId);
                return next;
            });
        }
    };
    const toggleLabelExpansion = (labelId) => {
        setExpandedLabelIds((prev) => {
            const next = new Set(prev);
            if (next.has(labelId)) {
                next.delete(labelId);
            }
            else {
                next.add(labelId);
                if (resourcesByLabel[labelId] === undefined) {
                    void fetchLabelPreview(labelId);
                }
            }
            return next;
        });
    };
    const selectedOptions = useMemo(() => {
        return selectedKeys.map((key) => {
            const cached = optionsCacheRef.current.get(key);
            if (cached) {
                return cached;
            }
            return { value: key, label: key };
        });
        /*
         * Cache is mutated during render based on initialFlat / inboundOptions
         * (see seed loops above), so include them here to make the dependency
         * graph honest — when the parent hands us new options, this memo
         * recomputes against the freshly-seeded cache.
         */
    }, [selectedKeys, initialFlat, inboundOptions]);
    /*
     * Visual label color extracted from option.color (which can be either a
     * Color instance or a string). Used to render a small dot next to each
     * row that has one.
     */
    const optionColorString = (opt) => {
        const c = opt.color;
        if (!c) {
            return undefined;
        }
        if (typeof c === "string") {
            return c;
        }
        if (typeof c.toString === "function") {
            return c.toString();
        }
        return undefined;
    };
    const labelColorString = (label) => {
        const c = label.color;
        if (!c) {
            return undefined;
        }
        if (typeof c === "string") {
            return c;
        }
        if (typeof c.toString === "function") {
            return c.toString();
        }
        return undefined;
    };
    /*
     * Render the *current selection* as either a chip row (multi) or the
     * single value in-place when the popover is closed (single). When the
     * popover is open the input takes over for typing.
     */
    const placeholderText = props.placeholder || "Select...";
    const showSingleSelectedText = !isMulti && !isOpen && selectedOptions.length > 0;
    return (React.createElement("div", { ref: containerRef, id: props.id, className: props.className || "relative mt-2 mb-1 w-full" },
        isMulti && selectedOptions.length > 0 && (React.createElement("div", { className: "mb-2 flex flex-wrap gap-1.5" }, selectedOptions.map((opt) => {
            const key = valueKey(opt.value);
            const colorStr = optionColorString(opt);
            return (React.createElement("span", { key: key, className: "inline-flex items-center gap-1.5 rounded-md border border-indigo-100 bg-indigo-50 px-2 py-1 text-xs font-medium text-indigo-900" },
                colorStr && (React.createElement("span", { "aria-hidden": "true", className: "inline-block h-2 w-2 rounded-full", style: { backgroundColor: colorStr } })),
                React.createElement("span", { className: "max-w-[14rem] truncate" }, opt.label),
                !props.disabled && (React.createElement("button", { type: "button", "aria-label": `Remove ${opt.label}`, onClick: () => {
                        removeKey(key);
                    }, className: "ml-0.5 rounded-full text-indigo-400 transition-colors hover:bg-indigo-100 hover:text-indigo-700 focus:outline-none focus:ring-1 focus:ring-indigo-500" },
                    React.createElement("svg", { className: "h-3 w-3", viewBox: "0 0 20 20", fill: "currentColor", "aria-hidden": "true" },
                        React.createElement("path", { fillRule: "evenodd", d: "M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z", clipRule: "evenodd" }))))));
        }))),
        React.createElement("div", { className: "relative" },
            showSingleSelectedText && (React.createElement("button", { type: "button", disabled: props.disabled, onClick: () => {
                    var _a;
                    if (props.disabled) {
                        return;
                    }
                    setIsOpen(true);
                    (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
                }, onFocus: () => {
                    var _a;
                    (_a = props.onFocus) === null || _a === void 0 ? void 0 : _a.call(props);
                }, className: `flex w-full items-center justify-between rounded-lg border bg-white px-3 py-2 text-left text-sm shadow-sm transition-colors ${props.error
                    ? "border-red-400"
                    : "border-gray-300 hover:border-indigo-300"} ${props.disabled
                    ? "cursor-not-allowed bg-gray-100 text-gray-400"
                    : ""}` },
                React.createElement("span", { className: "flex items-center gap-2" },
                    (() => {
                        const colorStr = optionColorString(selectedOptions[0]);
                        if (!colorStr) {
                            return null;
                        }
                        return (React.createElement("span", { "aria-hidden": "true", className: "inline-block h-2.5 w-2.5 rounded-full border border-gray-200", style: { backgroundColor: colorStr } }));
                    })(),
                    React.createElement("span", { className: "font-medium text-gray-900" }, selectedOptions[0].label)),
                React.createElement("div", { className: "flex items-center gap-1 text-gray-400" },
                    !props.disabled && (React.createElement("button", { type: "button", "aria-label": "Clear selection", onClick: (e) => {
                            e.stopPropagation();
                            clearAll();
                        }, className: "rounded p-0.5 hover:bg-gray-100 hover:text-red-500 focus:outline-none focus:ring-1 focus:ring-indigo-500" },
                        React.createElement("svg", { className: "h-3.5 w-3.5", viewBox: "0 0 20 20", fill: "currentColor", "aria-hidden": "true" },
                            React.createElement("path", { fillRule: "evenodd", d: "M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z", clipRule: "evenodd" })))),
                    React.createElement(Icon, { icon: IconProp.ChevronDown, className: "h-4 w-4" })))),
            !showSingleSelectedText && (React.createElement("div", { className: `relative rounded-lg border bg-white shadow-sm transition-colors ${props.error
                    ? "border-red-400 ring-2 ring-red-100"
                    : isOpen
                        ? "border-indigo-400 ring-2 ring-indigo-100"
                        : "border-gray-300 hover:border-indigo-300"} ${props.disabled ? "bg-gray-100" : ""}` },
                React.createElement("input", { ref: inputRef, type: "text", value: searchQuery, disabled: props.disabled, tabIndex: props.tabIndex, "aria-autocomplete": "list", "aria-expanded": isOpen, "aria-label": props.ariaLabel, "aria-invalid": props.error ? true : undefined, "data-testid": props.dataTestId, role: "combobox", placeholder: isMulti && selectedOptions.length > 0
                        ? "Search to add more..."
                        : placeholderText, onChange: (event) => {
                        setSearchQuery(event.target.value);
                        setIsOpen(true);
                        setHighlightedIndex(-1);
                    }, onFocus: () => {
                        var _a;
                        setIsOpen(true);
                        (_a = props.onFocus) === null || _a === void 0 ? void 0 : _a.call(props);
                    }, onBlur: () => {
                        var _a;
                        (_a = props.onBlur) === null || _a === void 0 ? void 0 : _a.call(props);
                    }, onKeyDown: (event) => {
                        const activeLen = activeTab === "labels"
                            ? filteredLabels.length
                            : availableOptions.length;
                        if (event.key === "ArrowDown") {
                            if (activeLen === 0) {
                                return;
                            }
                            event.preventDefault();
                            setIsOpen(true);
                            setHighlightedIndex((prev) => {
                                const next = prev + 1;
                                return next >= activeLen ? 0 : next;
                            });
                            return;
                        }
                        if (event.key === "ArrowUp") {
                            if (activeLen === 0) {
                                return;
                            }
                            event.preventDefault();
                            setIsOpen(true);
                            setHighlightedIndex((prev) => {
                                if (prev <= 0) {
                                    return activeLen - 1;
                                }
                                return prev - 1;
                            });
                            return;
                        }
                        if (event.key === "Enter") {
                            if (highlightedIndex < 0 || highlightedIndex >= activeLen) {
                                return;
                            }
                            event.preventDefault();
                            if (activeTab === "labels") {
                                const label = filteredLabels[highlightedIndex];
                                const labelId = (label === null || label === void 0 ? void 0 : label._id) ? String(label._id) : "";
                                if (labelId) {
                                    toggleLabelId(labelId);
                                }
                                return;
                            }
                            const opt = availableOptions[highlightedIndex];
                            if (opt) {
                                addOption(opt);
                                setHighlightedIndex(-1);
                            }
                            return;
                        }
                        if (event.key === "Escape") {
                            setIsOpen(false);
                            setHighlightedIndex(-1);
                            return;
                        }
                        if (event.key === "Backspace" &&
                            searchQuery === "" &&
                            isMulti &&
                            selectedKeys.length > 0 &&
                            activeTab === "options") {
                            event.preventDefault();
                            removeKey(selectedKeys[selectedKeys.length - 1]);
                        }
                    }, className: "block w-full rounded-lg bg-transparent px-3 py-2 text-sm text-gray-900 placeholder-gray-400 focus:outline-none disabled:cursor-not-allowed disabled:text-gray-500" }),
                !isMulti && selectedKeys.length > 0 && !props.disabled && (React.createElement("button", { type: "button", "aria-label": "Clear selection", onClick: () => {
                        var _a;
                        clearAll();
                        setSearchQuery("");
                        (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
                    }, className: "absolute inset-y-0 right-2 my-auto flex h-6 w-6 items-center justify-center rounded text-gray-400 transition-colors hover:bg-gray-100 hover:text-red-500 focus:outline-none focus:ring-1 focus:ring-indigo-500" },
                    React.createElement("svg", { className: "h-3.5 w-3.5", viewBox: "0 0 20 20", fill: "currentColor", "aria-hidden": "true" },
                        React.createElement("path", { fillRule: "evenodd", d: "M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z", clipRule: "evenodd" }))))))),
        isOpen && !props.disabled && (React.createElement("div", { className: "absolute z-20 mt-1 flex max-h-96 w-full flex-col overflow-hidden rounded-md border border-gray-200 bg-white text-sm shadow-lg", role: "listbox" },
            labelsTabEnabled && (React.createElement("div", { className: "flex flex-shrink-0 items-center gap-1 border-b border-gray-100 bg-gray-50 px-1.5 py-1" },
                React.createElement("button", { type: "button", role: "tab", "aria-selected": activeTab === "options", onMouseDown: (event) => {
                        event.preventDefault();
                    }, onClick: () => {
                        setActiveTab("options");
                        setHighlightedIndex(-1);
                    }, className: `rounded px-2.5 py-1 text-xs font-medium transition-colors focus:outline-none focus:ring-1 focus:ring-indigo-500 ${activeTab === "options"
                        ? "bg-white text-indigo-700 shadow-sm ring-1 ring-gray-200"
                        : "text-gray-600 hover:bg-white/60 hover:text-gray-800"}` }, "Results"),
                React.createElement("button", { type: "button", role: "tab", "aria-selected": activeTab === "labels", onMouseDown: (event) => {
                        event.preventDefault();
                    }, onClick: () => {
                        setActiveTab("labels");
                        setHighlightedIndex(-1);
                    }, className: `inline-flex items-center gap-1.5 rounded px-2.5 py-1 text-xs font-medium transition-colors focus:outline-none focus:ring-1 focus:ring-indigo-500 ${activeTab === "labels"
                        ? "bg-white text-indigo-700 shadow-sm ring-1 ring-gray-200"
                        : "text-gray-600 hover:bg-white/60 hover:text-gray-800"}` },
                    React.createElement(Icon, { icon: IconProp.Tag, className: "h-3.5 w-3.5" }),
                    "Labels",
                    selectedLabelIds.length > 0 && (React.createElement("span", { className: "inline-flex h-4 min-w-[16px] items-center justify-center rounded-full bg-indigo-100 px-1 text-[10px] font-semibold text-indigo-700" }, selectedLabelIds.length))),
                React.createElement("span", { className: "ml-auto pr-1 text-[11px] text-gray-400" }, activeTab === "options"
                    ? "Pick individually"
                    : "Bulk-add by tag"))),
            activeTab === "options" && (React.createElement("div", { className: "flex-1 overflow-auto py-1" },
                isLoading && (React.createElement("div", { className: "flex items-center px-3 py-2 text-gray-500" },
                    React.createElement("svg", { className: "animate-spin -ml-0.5 mr-2 h-4 w-4 text-indigo-500", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", "aria-hidden": "true" },
                        React.createElement("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
                        React.createElement("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" })),
                    React.createElement("span", null, "Searching..."))),
                !isLoading && availableOptions.length === 0 && (React.createElement("div", { className: "px-3 py-2 text-gray-500" }, searchQuery.trim() === ""
                    ? "No options."
                    : "No matching entries.")),
                !isLoading &&
                    availableOptions.map((opt, idx) => {
                        const key = valueKey(opt.value);
                        const isHighlighted = idx === highlightedIndex;
                        const isCurrentSelection = !isMulti && selectedKeys[0] === key;
                        const colorStr = optionColorString(opt);
                        return (React.createElement("button", { key: key, type: "button", role: "option", "aria-selected": isCurrentSelection || isHighlighted, onMouseEnter: () => {
                                setHighlightedIndex(idx);
                            }, onMouseDown: (event) => {
                                event.preventDefault();
                            }, onClick: () => {
                                addOption(opt);
                            }, className: `flex w-full items-center gap-2 px-3 py-2 text-left ${isHighlighted
                                ? "bg-indigo-600 text-white"
                                : isCurrentSelection
                                    ? "bg-indigo-50 text-indigo-900"
                                    : "text-gray-700 hover:bg-indigo-50"}` },
                            colorStr && (React.createElement("span", { "aria-hidden": "true", className: "inline-block h-2.5 w-2.5 flex-shrink-0 rounded-full border border-gray-200", style: { backgroundColor: colorStr } })),
                            React.createElement("span", { className: "truncate" }, opt.label),
                            opt.description && (React.createElement("span", { className: `ml-auto truncate text-xs ${isHighlighted
                                    ? "text-indigo-100"
                                    : "text-gray-500"}` }, opt.description)),
                            isCurrentSelection && (React.createElement("svg", { className: `ml-auto h-4 w-4 flex-shrink-0 ${isHighlighted ? "text-white" : "text-indigo-600"}`, viewBox: "0 0 20 20", fill: "currentColor", "aria-hidden": "true" },
                                React.createElement("path", { fillRule: "evenodd", d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", clipRule: "evenodd" })))));
                    }))),
            activeTab === "labels" && (React.createElement("div", { className: "flex-1 overflow-auto py-1" },
                isLoadingLabels && (React.createElement("div", { className: "flex items-center px-3 py-2 text-gray-500" },
                    React.createElement("svg", { className: "animate-spin -ml-0.5 mr-2 h-4 w-4 text-indigo-500", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", "aria-hidden": "true" },
                        React.createElement("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
                        React.createElement("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" })),
                    React.createElement("span", null, "Loading labels..."))),
                !isLoadingLabels && labelError !== "" && (React.createElement("div", { className: "px-3 py-2 text-red-600" }, labelError)),
                !isLoadingLabels &&
                    labelError === "" &&
                    labelsLoaded &&
                    allLabels.length === 0 && (React.createElement("div", { className: "px-3 py-2 text-gray-500" }, "No labels found in this project. Create labels first to use this shortcut.")),
                !isLoadingLabels &&
                    labelError === "" &&
                    labelsLoaded &&
                    allLabels.length > 0 &&
                    filteredLabels.length === 0 && (React.createElement("div", { className: "px-3 py-2 text-gray-500" },
                    "No labels match \u201C",
                    searchQuery.trim(),
                    "\u201D.")),
                !isLoadingLabels &&
                    filteredLabels.map((label, idx) => {
                        const labelId = label._id ? String(label._id) : "";
                        if (!labelId) {
                            return React.createElement("span", { key: `empty-${idx}` });
                        }
                        const isChecked = selectedLabelIds.includes(labelId);
                        const isHighlighted = idx === highlightedIndex;
                        const isExpanded = expandedLabelIds.has(labelId);
                        const isLoadingPreview = loadingLabelIds.has(labelId);
                        const preview = resourcesByLabel[labelId];
                        const previewError = labelLoadErrors[labelId];
                        const colorStr = labelColorString(label);
                        return (React.createElement("div", { key: labelId, onMouseEnter: () => {
                                setHighlightedIndex(idx);
                            }, className: `border-b border-gray-100 last:border-b-0 ${isHighlighted ? "bg-indigo-50" : ""}` },
                            React.createElement("div", { className: "flex w-full items-center gap-2 px-2 py-1.5" },
                                React.createElement("button", { type: "button", "aria-label": isExpanded ? "Collapse entries" : "Expand entries", "aria-expanded": isExpanded, onMouseDown: (event) => {
                                        event.preventDefault();
                                    }, onClick: () => {
                                        toggleLabelExpansion(labelId);
                                    }, className: "flex h-5 w-5 flex-shrink-0 items-center justify-center rounded text-gray-400 hover:bg-gray-200 hover:text-gray-700 focus:outline-none focus:ring-1 focus:ring-indigo-500" },
                                    React.createElement(Icon, { icon: IconProp.ChevronRight, className: `h-3.5 w-3.5 transition-transform duration-150 ${isExpanded ? "rotate-90" : ""}` })),
                                React.createElement("button", { type: "button", role: "option", "aria-selected": isChecked, onMouseDown: (event) => {
                                        event.preventDefault();
                                    }, onClick: () => {
                                        toggleLabelId(labelId);
                                    }, className: `flex flex-1 items-center gap-2 rounded px-1 py-1 text-left ${isHighlighted
                                        ? "text-gray-900"
                                        : "text-gray-700 hover:bg-indigo-100/50"}` },
                                    React.createElement("span", { "aria-hidden": "true", className: `flex h-4 w-4 flex-shrink-0 items-center justify-center rounded border ${isChecked
                                            ? "border-indigo-600 bg-indigo-600 text-white"
                                            : "border-gray-300 bg-white"}` }, isChecked && (React.createElement("svg", { className: "h-3 w-3", viewBox: "0 0 20 20", fill: "currentColor" },
                                        React.createElement("path", { fillRule: "evenodd", d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", clipRule: "evenodd" })))),
                                    colorStr && (React.createElement("span", { className: "inline-block h-3 w-3 flex-shrink-0 rounded-full", style: { backgroundColor: colorStr }, "aria-hidden": "true" })),
                                    React.createElement("span", { className: "truncate" }, label.name || "Unnamed Label")),
                                preview !== undefined && (React.createElement("span", { className: "flex-shrink-0 rounded-full bg-gray-100 px-2 py-0.5 text-[10px] font-medium text-gray-600" },
                                    preview.length,
                                    preview.length >= LABEL_PREVIEW_LIMIT ? "+" : ""))),
                            isExpanded && (React.createElement("div", { className: "border-t border-gray-100 bg-gray-50 px-3 py-2 pl-10" }, isLoadingPreview ? (React.createElement("div", { className: "flex items-center gap-2 py-1 text-xs text-gray-500" },
                                React.createElement("svg", { className: "h-3.5 w-3.5 animate-spin text-indigo-500", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", "aria-hidden": "true" },
                                    React.createElement("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
                                    React.createElement("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" })),
                                React.createElement("span", null, "Loading entries..."))) : previewError ? (React.createElement("div", { className: "py-1 text-xs text-red-600" }, previewError)) : !preview || preview.length === 0 ? (React.createElement("div", { className: "py-1 text-xs italic text-gray-500" }, "No entries tagged with this label.")) : (React.createElement("div", { className: "flex flex-wrap gap-1" }, preview.map((item) => {
                                return (React.createElement("span", { key: valueKey(item.value), className: "inline-flex items-center gap-1 rounded border border-gray-200 bg-white px-1.5 py-0.5 text-[11px] text-gray-700" },
                                    React.createElement("span", { className: "max-w-[10rem] truncate" }, item.label)));
                            })))))));
                    }))),
            activeTab === "labels" && selectedLabelIds.length > 0 && (React.createElement("div", { className: "flex flex-shrink-0 items-center justify-between gap-2 border-t border-gray-100 bg-gray-50 px-2 py-1.5" },
                React.createElement("button", { type: "button", onMouseDown: (event) => {
                        event.preventDefault();
                    }, onClick: () => {
                        setSelectedLabelIds([]);
                    }, disabled: isApplyingLabels, className: "rounded px-2 py-1 text-xs font-medium text-gray-600 hover:bg-white hover:text-gray-800 focus:outline-none focus:ring-1 focus:ring-indigo-500 disabled:opacity-50" }, "Clear"),
                React.createElement("button", { type: "button", onMouseDown: (event) => {
                        event.preventDefault();
                    }, onClick: () => {
                        void applyLabelSelection();
                    }, disabled: isApplyingLabels, className: "inline-flex items-center gap-1.5 rounded-md bg-indigo-600 px-3 py-1 text-xs font-semibold text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-1 disabled:opacity-60" },
                    isApplyingLabels && (React.createElement("svg", { className: "h-3.5 w-3.5 animate-spin", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", "aria-hidden": "true" },
                        React.createElement("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
                        React.createElement("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" }))),
                    isApplyingLabels
                        ? "Adding..."
                        : `Add entries from ${selectedLabelIds.length} label${selectedLabelIds.length === 1 ? "" : "s"}`))))),
        props.error && (React.createElement("p", { className: "mt-1 text-sm text-red-400", role: "alert" }, props.error))));
};
export default EntityDropdown;
//# sourceMappingURL=EntityDropdown.js.map