import {Controller} from "@hotwired/stimulus";

/**
 * Multi-select dropdown switcher used by the page-headline filters
 * (EditionSwitcher, AccountTypeSwitcher, …). Replaces the previous Alpine
 * implementation, which suffered from morphdom interference: typing into a
 * sibling search input triggered a Live re-render that would corrupt
 * Alpine's x-data state and pop the dropdown open without user intent.
 *
 * Stimulus state lives on the controller instance and is naturally insulated
 * from morphs as long as the host element survives (which it does — Live
 * only re-renders the component's own inner content, and the switcher sits
 * in the page header outside the component).
 *
 * DOM contract:
 *   data-controller="ferienpass--admin-entity-switcher"
 *   data-ferienpass--admin-entity-switcher-selected-slugs-value='["foo","bar"]'
 *   data-ferienpass--admin-entity-switcher-unscoped-url-value="/path"
 *   data-ferienpass--admin-entity-switcher-scoped-url-template-value="/path/__SLUGS__"
 *   data-ferienpass--admin-entity-switcher-target="trigger|popover|searchInput|item|counter|clearButton"
 *
 *   On each toggleable item, set data-slug="…" and data-name="…" (the latter
 *   is searched against by the search input, lowercased contains).
 */
export default class extends Controller {
    static targets = ["trigger", "popover", "searchInput", "item", "counter", "clearButton"];
    static values = {
        selectedSlugs:      {type: Array,  default: []},
        unscopedUrl:        String,
        scopedUrlTemplate:  String,
        emptyLabel:         {type: String, default: ""},
        countLabel:         {type: String, default: ""}, // contains {n} placeholder
    };

    initialize() {
        // `pending` is the working set the user is editing; only applied
        // (= navigated) when they click "Fertig" or "Alle anzeigen".
        this.pending = [...this.selectedSlugsValue];
        this._onDocumentClick = this._onDocumentClick.bind(this);
        this._onKeydown = this._onKeydown.bind(this);
    }

    connect() {
        this._reflectPending();
    }

    disconnect() {
        document.removeEventListener("click", this._onDocumentClick);
        document.removeEventListener("keydown", this._onKeydown);
    }

    // ── Open / close ────────────────────────────────────────────────────

    open() {
        if (!this.popoverTarget.classList.contains("hidden")) return;
        this.popoverTarget.classList.remove("hidden");
        this.triggerTarget?.setAttribute("aria-expanded", "true");
        document.addEventListener("click", this._onDocumentClick);
        document.addEventListener("keydown", this._onKeydown);
        // Focus the search box if present.
        if (this.hasSearchInputTarget) {
            // requestAnimationFrame so focus happens after the element is
            // actually visible (avoids browser quirks with display:none).
            requestAnimationFrame(() => this.searchInputTarget.focus());
        }
    }

    close() {
        if (this.popoverTarget.classList.contains("hidden")) return;
        this.popoverTarget.classList.add("hidden");
        this.triggerTarget?.setAttribute("aria-expanded", "false");
        document.removeEventListener("click", this._onDocumentClick);
        document.removeEventListener("keydown", this._onKeydown);
    }

    toggle(event) {
        event?.preventDefault();
        if (this.popoverTarget.classList.contains("hidden")) {
            this.open();
        } else {
            this.close();
        }
    }

    _onDocumentClick(event) {
        if (!this.element.contains(event.target)) this.close();
    }

    _onKeydown(event) {
        if (event.key === "Escape") this.close();
    }

    // ── Selection state ────────────────────────────────────────────────

    toggleItem(event) {
        event.preventDefault();
        const slug = event.currentTarget.dataset.slug;
        if (!slug) return;
        const i = this.pending.indexOf(slug);
        if (i >= 0) this.pending.splice(i, 1);
        else this.pending.push(slug);
        this._reflectPending();
    }

    clear(event) {
        event?.preventDefault();
        this.pending = [];
        this._reflectPending();
    }

    _reflectPending() {
        const set = new Set(this.pending);
        for (const item of this.itemTargets) {
            const selected = set.has(item.dataset.slug);
            item.setAttribute("aria-pressed", selected ? "true" : "false");
        }
        if (this.hasCounterTarget) {
            this.counterTarget.textContent = this.pending.length === 0
                ? this.emptyLabelValue
                : this.countLabelValue.replace("{n}", String(this.pending.length));
        }
        if (this.hasClearButtonTarget) {
            this.clearButtonTarget.classList.toggle("hidden", this.pending.length === 0);
        }
    }

    // ── Search filtering ───────────────────────────────────────────────

    filter(event) {
        const q = (event.target.value || "").toLowerCase().trim();
        for (const item of this.itemTargets) {
            const name = (item.dataset.name || "").toLowerCase();
            const hide = q !== "" && !name.includes(q);
            // Hide the LI wrapper (or button) — both work with display:none.
            item.classList.toggle("hidden", hide);
        }
    }

    // ── Navigation ─────────────────────────────────────────────────────

    apply(event) {
        event?.preventDefault();
        if (this.pending.length === 0) {
            this._clearSavedSlug();
        }
        this._navigate(this._buildUrl(this.pending));
    }

    showAll(event) {
        event?.preventDefault();
        this._clearSavedSlug();
        this._navigate(this._buildUrl([]));
    }

    _buildUrl(slugs) {
        const qs = window.location.search || "";
        const path = slugs.length === 0
            ? this.unscopedUrlValue
            : this.scopedUrlTemplateValue.replace("__SLUGS__", slugs.join("+"));
        return path + qs;
    }

    /**
     * The list-page persister (controllers/list.js) re-injects the saved
     * path-slug whenever a navigation lands on the bare list URL — a navbar
     * "Angebote" click on `/admin/angebote` becomes `/admin/angebote/herbst`.
     * That's what makes navigation between admin pages remember the active
     * selection.
     *
     * The flip side: when the user explicitly drops to "Alle Saisons" / "Alle
     * Konten-Typen", we need a way to signal "no filter, really" so the
     * persister doesn't override the bare destination. We do that by wiping
     * the saved slug here, just before Turbo.visit fires. The persister then
     * finds nothing in localStorage and leaves the URL alone.
     *
     * Identifies which storage entry to wipe by matching the switcher's
     * unscoped URL against the registry in `admin-view-prefered--routes`
     * (written by list.js on every list-page mount).
     */
    _clearSavedSlug() {
        try {
            const raw = localStorage.getItem("admin-view-prefered--routes");
            const routes = raw ? JSON.parse(raw) : null;
            if (!routes || typeof routes !== "object") return;
            const target = (this.unscopedUrlValue || "").replace(/\/+$/, "");
            if (!target) return;
            for (const [viewKey, prefix] of Object.entries(routes)) {
                if (typeof prefix !== "string") continue;
                if (prefix.replace(/\/+$/, "") === target) {
                    localStorage.removeItem(`admin-path-prefered--${viewKey}`);
                    return;
                }
            }
        } catch (_) {}
    }

    _navigate(url) {
        if (window.Turbo && typeof window.Turbo.visit === "function") {
            window.Turbo.visit(url);
        } else {
            window.location.href = url;
        }
    }
}
