/**
 * Pinia store — Panel UI state
 *
 * Replaces the Proxy-based panelState.js with a fully reactive Pinia store.
 * State is persisted to localStorage (same key as the old system for backwards
 * compatibility) with a 750 ms debounce.
 *
 * Vue components consume this store via `usePanelStore()`.
 * Legacy DOM controllers consume this store through `panelStateBridge.js`
 * during the incremental migration.
 */

import { defineStore } from "pinia";
import { ref, watch, onScopeDispose } from "vue";
import { get as apiGet } from "../api/client.js";

type PanelScope = "output" | "input" | "all" | "custom" | "workflow";
type ResolutionCompare = "gte" | "lte";

interface PanelState {
    scope: PanelScope;
    customRootId: string;
    customRootLabel: string;
    currentFolderRelativePath: string;
    collectionId: string;
    collectionName: string;
    kindFilter: string;
    dateRangeFilter: string;
    dateExactFilter: string;
    workflowOnly: boolean;
    minRating: number;
    minSizeMB: number;
    maxSizeMB: number;
    resolutionCompare: ResolutionCompare;
    minWidth: number;
    minHeight: number;
    maxWidth: number;
    maxHeight: number;
    workflowType: string;
    workflowId: string;
    workflowModelFilter: string;
    workflowRunsOnFilter: string;
    sort: string;
    searchQuery: string;
    metadataSearchMode: string;
    scrollTop: number;
    activeAssetId: string;
    selectedAssetIds: string[];
    sidebarOpen: boolean;
    lastGridCount?: number;
    lastGridTotal?: number;
    viewScope?: string;
    similarResults?: unknown[];
    similarTitle?: string;
    similarSourceAssetId?: string;
}

type StoredPanelState = Partial<PanelState>;

interface NavigateToScopeOptions {
    folder?: string;
    collectionId?: string;
    collectionName?: string;
    customRootId?: string;
    customRootLabel?: string;
}

interface CustomRootRow {
    id?: unknown;
    label?: unknown;
}

const STORAGE_KEY = "mjr_panel_state";
const ALLOWED_SCOPES = new Set<PanelScope>(["output", "input", "all", "custom", "workflow"]);

// ─── helpers ──────────────────────────────────────────────────────────────────

function toBool(value: any, fallback = false): boolean {
    if (typeof value === "boolean") return value;
    if (typeof value === "string") {
        const n = value.trim().toLowerCase();
        if (["1", "true", "yes", "on"].includes(n)) return true;
        if (["0", "false", "no", "off"].includes(n)) return false;
    }
    return !!fallback;
}

function toNumber(value: any, fallback = 0): number {
    const n = Number(value);
    return Number.isFinite(n) ? n : fallback;
}

function toStr(value: any, fallback = ""): string {
    if (value === null || value === undefined) return fallback;
    return String(value);
}

function clampInt(value: any, min = 0, max = Infinity): number {
    return Math.max(min, Math.min(max, Math.floor(toNumber(value, min))));
}

function normalizeScope(raw: any): PanelScope {
    const s = toStr(raw || "output").toLowerCase();
    const mapped = (s === "outputs" ? "output" : s === "inputs" ? "input" : s) as PanelScope;
    return ALLOWED_SCOPES.has(mapped) ? mapped : "output";
}

function sanitize(raw: any): StoredPanelState {
    if (!raw || typeof raw !== "object") return {};
    const row = raw as Record<string, any>;
    return {
        scope: normalizeScope(row.scope),
        customRootId: toStr(row.customRootId),
        customRootLabel: toStr(row.customRootLabel),
        currentFolderRelativePath: toStr(row.currentFolderRelativePath || row.subfolder),
        collectionId: toStr(row.collectionId),
        collectionName: toStr(row.collectionName),
        kindFilter: toStr(row.kindFilter),
        dateRangeFilter: toStr(row.dateRangeFilter),
        dateExactFilter: toStr(row.dateExactFilter),
        workflowOnly: toBool(row.workflowOnly, false),
        minRating: clampInt(row.minRating, 0, 5),
        minSizeMB: Math.max(0, toNumber(row.minSizeMB, 0)),
        maxSizeMB: Math.max(0, toNumber(row.maxSizeMB, 0)),
        resolutionCompare: row.resolutionCompare === "lte" ? "lte" : "gte",
        minWidth: clampInt(row.minWidth),
        minHeight: clampInt(row.minHeight),
        maxWidth: clampInt(row.maxWidth),
        maxHeight: clampInt(row.maxHeight),
        workflowType: toStr(row.workflowType),
        workflowId: toStr(row.workflowId || row.workflow_id),
        workflowModelFilter: toStr(row.workflowModelFilter || row.workflowModel || row.workflow_model),
        workflowRunsOnFilter: toStr(row.workflowRunsOnFilter || row.workflowRunsOn || row.runs_on),
        sort: toStr(row.sort || "mtime_desc"),
        searchQuery: toStr(row.searchQuery),
        metadataSearchMode: String(row.metadataSearchMode || "AND").toUpperCase() === "OR" ? "OR" : "AND",
        scrollTop: clampInt(row.scrollTop),
        activeAssetId: toStr(row.activeAssetId),
        selectedAssetIds: Array.isArray(row.selectedAssetIds)
            ? row.selectedAssetIds
                  .map((x) => toStr(x).trim())
                  .filter(Boolean)
                  .slice(0, 5000)
            : [],
        sidebarOpen: toBool(row.sidebarOpen, false),
    };
}

function loadFromStorage(): StoredPanelState {
    try {
        const raw = localStorage.getItem(STORAGE_KEY);
        if (raw) return sanitize(JSON.parse(raw));
    } catch {
        /* ignore */
    }
    return {};
}

function saveToStorage(state: StoredPanelState) {
    try {
        const toSave = { ...state };
        // Transient fields — never persisted
        delete toSave.lastGridCount;
        delete toSave.lastGridTotal;
        delete toSave.viewScope;
        delete toSave.similarResults;
        delete toSave.similarTitle;
        delete toSave.similarSourceAssetId;
        localStorage.setItem(STORAGE_KEY, JSON.stringify(toSave));
    } catch {
        /* ignore */
    }
}

// ─── store ────────────────────────────────────────────────────────────────────

export const usePanelStore = defineStore("mjr-panel", () => {
    const saved = loadFromStorage();

    // ── persistent state ──────────────────────────────────────────────────────
    const scope = ref(saved.scope || "output");
    const customRootId = ref(saved.customRootId || "");
    const customRootLabel = ref(saved.customRootLabel || "");
    const currentFolderRelativePath = ref(saved.currentFolderRelativePath || "");
    const collectionId = ref(saved.collectionId || "");
    const collectionName = ref(saved.collectionName || "");
    const kindFilter = ref(saved.kindFilter || "");
    const dateRangeFilter = ref(saved.dateRangeFilter || "");
    const dateExactFilter = ref(saved.dateExactFilter || "");
    const workflowOnly = ref(saved.workflowOnly || false);
    const minRating = ref(saved.minRating || 0);
    const minSizeMB = ref(saved.minSizeMB || 0);
    const maxSizeMB = ref(saved.maxSizeMB || 0);
    const resolutionCompare = ref(saved.resolutionCompare || "gte");
    const minWidth = ref(saved.minWidth || 0);
    const minHeight = ref(saved.minHeight || 0);
    const maxWidth = ref(saved.maxWidth || 0);
    const maxHeight = ref(saved.maxHeight || 0);
    const workflowType = ref(saved.workflowType || "");
    const workflowId = ref(saved.workflowId || "");
    const workflowModelFilter = ref(saved.workflowModelFilter || "");
    const workflowRunsOnFilter = ref(saved.workflowRunsOnFilter || "");
    const sort = ref(saved.sort || "mtime_desc");
    const searchQuery = ref(saved.searchQuery || "");
    const metadataSearchMode = ref(saved.metadataSearchMode || "AND");
    const scrollTop = ref(saved.scrollTop || 0);
    const activeAssetId = ref(saved.activeAssetId || "");
    const selectedAssetIds = ref(saved.selectedAssetIds || []);
    const sidebarOpen = ref(saved.sidebarOpen || false);

    // ── transient state (never persisted) ────────────────────────────────────
    const lastGridCount = ref(0);
    const lastGridTotal = ref(0);
    const viewScope = ref("");
    const similarResults = ref([]);
    const similarTitle = ref("");
    const similarSourceAssetId = ref("");

    // ── persistence watcher ──────────────────────────────────────────────────
    let _saveTimer: ReturnType<typeof setTimeout> | null = null;
    const scheduleSave = () => {
        if (_saveTimer) clearTimeout(_saveTimer);
        _saveTimer = setTimeout(() => {
            _saveTimer = null;
            saveToStorage({
                scope: scope.value,
                customRootId: customRootId.value,
                customRootLabel: customRootLabel.value,
                currentFolderRelativePath: currentFolderRelativePath.value,
                collectionId: collectionId.value,
                collectionName: collectionName.value,
                kindFilter: kindFilter.value,
                dateRangeFilter: dateRangeFilter.value,
                dateExactFilter: dateExactFilter.value,
                workflowOnly: workflowOnly.value,
                minRating: minRating.value,
                minSizeMB: minSizeMB.value,
                maxSizeMB: maxSizeMB.value,
                resolutionCompare: resolutionCompare.value,
                minWidth: minWidth.value,
                minHeight: minHeight.value,
                maxWidth: maxWidth.value,
                maxHeight: maxHeight.value,
                workflowType: workflowType.value,
                workflowId: workflowId.value,
                workflowModelFilter: workflowModelFilter.value,
                workflowRunsOnFilter: workflowRunsOnFilter.value,
                sort: sort.value,
                searchQuery: searchQuery.value,
                metadataSearchMode: metadataSearchMode.value,
                scrollTop: scrollTop.value,
                activeAssetId: activeAssetId.value,
                selectedAssetIds: selectedAssetIds.value,
                sidebarOpen: sidebarOpen.value,
            });
        }, 750);
    };

    watch(
        [
            scope,
            customRootId,
            customRootLabel,
            currentFolderRelativePath,
            collectionId,
            collectionName,
            kindFilter,
            dateRangeFilter,
            dateExactFilter,
            workflowOnly,
            minRating,
            minSizeMB,
            maxSizeMB,
            resolutionCompare,
            minWidth,
            minHeight,
            maxWidth,
            maxHeight,
            workflowType,
            workflowId,
            workflowModelFilter,
            workflowRunsOnFilter,
            sort,
            searchQuery,
            metadataSearchMode,
            scrollTop,
            activeAssetId,
            selectedAssetIds,
            sidebarOpen,
        ],
        scheduleSave,
        { deep: true },
    );

    // ── cross-tab sync ───────────────────────────────────────────────────────
    const _onStorage = (event: StorageEvent) => {
        if (event.key !== STORAGE_KEY || !event.newValue) return;
        try {
            const updated = sanitize(JSON.parse(event.newValue));
            if (updated.scope !== undefined) scope.value = updated.scope;
            if (updated.customRootLabel !== undefined)
                customRootLabel.value = updated.customRootLabel;
            if (updated.searchQuery !== undefined) searchQuery.value = updated.searchQuery;
            if (updated.metadataSearchMode !== undefined)
                metadataSearchMode.value = updated.metadataSearchMode;
            if (updated.sort !== undefined) sort.value = updated.sort;
            if (updated.sidebarOpen !== undefined) sidebarOpen.value = updated.sidebarOpen;
        } catch {
            /* ignore */
        }
    };
    try {
        window.addEventListener("storage", _onStorage);
    } catch {
        /* ignore */
    }
    onScopeDispose(() => {
        try {
            window.removeEventListener("storage", _onStorage);
        } catch {
            /* ignore */
        }
    });

    // ── actions ───────────────────────────────────────────────────────────────
    function resetFilters() {
        kindFilter.value = "";
        dateRangeFilter.value = "";
        dateExactFilter.value = "";
        workflowOnly.value = false;
        minRating.value = 0;
        minSizeMB.value = 0;
        maxSizeMB.value = 0;
        resolutionCompare.value = "gte";
        minWidth.value = 0;
        minHeight.value = 0;
        maxWidth.value = 0;
        maxHeight.value = 0;
        workflowType.value = "";
        workflowId.value = "";
        workflowModelFilter.value = "";
        workflowRunsOnFilter.value = "";
        searchQuery.value = "";
    }

    function clearSelection() {
        selectedAssetIds.value = [];
        activeAssetId.value = "";
    }

    function setSelection(ids: unknown[], activeId = "") {
        selectedAssetIds.value = Array.isArray(ids) ? ids.map(String).filter(Boolean) : [];
        activeAssetId.value = activeId || selectedAssetIds.value[0] || "";
    }

    function navigateToScope(newScope: PanelScope, opts: NavigateToScopeOptions = {}) {
        scope.value = newScope;
        currentFolderRelativePath.value = opts.folder || "";
        collectionId.value = opts.collectionId || "";
        collectionName.value = opts.collectionName || "";
        customRootId.value = opts.customRootId || "";
        customRootLabel.value = opts.customRootLabel || "";
        viewScope.value = "";
        similarResults.value = [];
        similarTitle.value = "";
        similarSourceAssetId.value = "";
        clearSelection();
    }

    /**
     * Verify the persisted `customRootId` still matches a configured custom
     * root.  Stale ids (e.g. a root that was removed in another tab or in a
     * previous session) silently broke the grid by triggering 404 listings;
     * clearing them here lets the panel fall back to the default scope.
     */
    async function validatePersistedCustomRoot() {
        const persistedId = String(customRootId.value || "").trim();
        if (!persistedId) return;
        try {
            const resp = await apiGet("/mjr/am/custom-roots");
            const rows: CustomRootRow[] = Array.isArray(resp?.data) ? resp.data : [];
            const match = rows.find((r) => String(r?.id || "") === persistedId);
            if (!match) {
                customRootId.value = "";
                customRootLabel.value = "";
                if (scope.value === "custom") {
                    scope.value = "output";
                    currentFolderRelativePath.value = "";
                }
                return;
            }
            // Refresh cached label in case it was renamed elsewhere.
            const freshLabel = String(match.label || "").trim();
            if (freshLabel && freshLabel !== customRootLabel.value) {
                customRootLabel.value = freshLabel;
            }
        } catch {
            // Network / auth failure: don't clobber persisted state — the
            // user might be offline; the listing endpoint will surface the
            // real error if/when they actually open the custom scope.
        }
    }

    return {
        // persistent
        scope,
        customRootId,
        customRootLabel,
        currentFolderRelativePath,
        collectionId,
        collectionName,
        kindFilter,
        dateRangeFilter,
        dateExactFilter,
        workflowOnly,
        minRating,
        minSizeMB,
        maxSizeMB,
        resolutionCompare,
        minWidth,
        minHeight,
        maxWidth,
        maxHeight,
        workflowType,
        workflowId,
        workflowModelFilter,
        workflowRunsOnFilter,
        sort,
            searchQuery,
            metadataSearchMode,
        scrollTop,
        activeAssetId,
        selectedAssetIds,
        sidebarOpen,
        // transient
        lastGridCount,
        lastGridTotal,
        viewScope,
        similarResults,
        similarTitle,
        similarSourceAssetId,
        // actions
        resetFilters,
        clearSelection,
        setSelection,
        navigateToScope,
        validatePersistedCustomRoot,
    };
});
