/**
 * Core settings persistence and config application.
 * Handles load/save from SettingsStore and application to APP_CONFIG.
 */

import { APP_CONFIG, APP_DEFAULTS } from "../config.js";
import {
    getSecuritySettings,
    bootstrapSecurityToken,
    setRuntimeSecurityToken,
    getVectorSearchSettings,
    getExecutionGroupingSettings,
} from "../../api/client.js";
import { safeDispatchCustomEvent } from "../../utils/events.js";
import { SettingsStore } from "./SettingsStore.js";
import { SETTINGS_KEY, SETTINGS_SCHEMA_VERSION } from "../settingsStore.js";
import {
    _safeBool,
    _safeNum,
    deepMerge,
    detectGridSizePreset,
    resolveFeedGridMinSize,
    resolveGridMinSize,
} from "./settingsUtils.js";
import { t } from "../i18n.js";
import { comfyAlert } from "../dialogs.js";

// --- Default settings -----------------------------------------------------

export const DEFAULT_SETTINGS = {
    debug: {
        safeCall: APP_DEFAULTS.DEBUG_SAFE_CALL,
        safeListeners: APP_DEFAULTS.DEBUG_SAFE_LISTENERS,
        viewer: APP_DEFAULTS.DEBUG_VIEWER,
    },
    grid: {
        pageSize: APP_DEFAULTS.DEFAULT_PAGE_SIZE,
        minSize: APP_DEFAULTS.GRID_MIN_SIZE,
        minSizePreset: detectGridSizePreset(APP_DEFAULTS.GRID_MIN_SIZE),
        gap: APP_DEFAULTS.GRID_GAP,
        showExtBadge: APP_DEFAULTS.GRID_SHOW_BADGES_EXTENSION,
        showRatingBadge: APP_DEFAULTS.GRID_SHOW_BADGES_RATING,
        showTagsBadge: APP_DEFAULTS.GRID_SHOW_BADGES_TAGS,
        showDetails: APP_DEFAULTS.GRID_SHOW_DETAILS,
        showFilename: APP_DEFAULTS.GRID_SHOW_DETAILS_FILENAME,
        showDate: APP_DEFAULTS.GRID_SHOW_DETAILS_DATE,
        showDimensions: APP_DEFAULTS.GRID_SHOW_DETAILS_DIMENSIONS,
        showGenTime: APP_DEFAULTS.GRID_SHOW_DETAILS_GENTIME,
        showHoverInfo: APP_DEFAULTS.GRID_SHOW_HOVER_INFO,
        showWorkflowDot: APP_DEFAULTS.GRID_SHOW_WORKFLOW_DOT,
        workflowGroupBy: APP_DEFAULTS.WORKFLOW_GRID_GROUP_BY,
        videoAutoplayMode: APP_DEFAULTS.GRID_VIDEO_AUTOPLAY_MODE,
        starColor: APP_DEFAULTS.BADGE_STAR_COLOR,
        badgeImageColor: APP_DEFAULTS.BADGE_IMAGE_COLOR,
        badgeVideoColor: APP_DEFAULTS.BADGE_VIDEO_COLOR,
        badgeAudioColor: APP_DEFAULTS.BADGE_AUDIO_COLOR,
        badgeModel3dColor: APP_DEFAULTS.BADGE_MODEL3D_COLOR,
        badgeDuplicateAlertColor: APP_DEFAULTS.BADGE_DUPLICATE_ALERT_COLOR,
    },
    infiniteScroll: {
        enabled: APP_DEFAULTS.INFINITE_SCROLL_ENABLED,
        rootMargin: APP_DEFAULTS.INFINITE_SCROLL_ROOT_MARGIN,
        threshold: APP_DEFAULTS.INFINITE_SCROLL_THRESHOLD,
        bottomGapPx: APP_DEFAULTS.BOTTOM_GAP_PX,
    },
    siblings: {
        hidePngSiblings: true,
    },
    autoScan: {
        onStartup: APP_DEFAULTS.AUTO_SCAN_ON_STARTUP,
    },
    scan: {
        fastMode: true,
        jxlEnabled: false,
    },
    watcher: {
        enabled: true,
        debounceMs: APP_DEFAULTS.WATCHER_DEBOUNCE_MS,
        dedupeTtlMs: APP_DEFAULTS.WATCHER_DEDUPE_TTL_MS,
        maxPending: 500,
        minSize: 100,
        maxSize: 4294967296,
    },
    safety: {
        confirmDeletion: true,
    },
    status: {
        pollInterval: APP_DEFAULTS.STATUS_POLL_INTERVAL,
    },
    viewer: {
        allowPanAtZoom1: APP_DEFAULTS.VIEWER_ALLOW_PAN_AT_ZOOM_1,
        disableWebGL: APP_DEFAULTS.VIEWER_DISABLE_WEBGL_VIDEO,
        pauseDuringExecution: APP_DEFAULTS.VIEWER_PAUSE_DURING_EXECUTION,
        floatingPauseDuringExecution: APP_DEFAULTS.FLOATING_VIEWER_PAUSE_DURING_EXECUTION,
        mfvLiveDefault: APP_DEFAULTS.MFV_LIVE_DEFAULT,
        mfvPreviewDefault: APP_DEFAULTS.MFV_PREVIEW_DEFAULT,
        videoGradeThrottleFps: APP_DEFAULTS.VIEWER_VIDEO_GRADE_THROTTLE_FPS,
        scopesFps: APP_DEFAULTS.VIEWER_SCOPES_FPS,
        metaTtlMs: APP_DEFAULTS.VIEWER_META_TTL_MS,
        metaMaxEntries: APP_DEFAULTS.VIEWER_META_MAX_ENTRIES,
        mfvSidebarPosition: "right",
        mfvPreviewMethod: APP_DEFAULTS.MFV_PREVIEW_METHOD,
        ltxavRgbFallback: false,
    },
    rtHydrate: {
        concurrency: APP_DEFAULTS.RT_HYDRATE_CONCURRENCY,
        queueMax: APP_DEFAULTS.RT_HYDRATE_QUEUE_MAX,
        seenMax: APP_DEFAULTS.RT_HYDRATE_SEEN_MAX,
        pruneBudget: APP_DEFAULTS.RT_HYDRATE_PRUNE_BUDGET,
        seenTtlMs: APP_DEFAULTS.RT_HYDRATE_SEEN_TTL_MS,
    },
    observability: {
        enabled: false,
        runtimeDashboardMode: "autoHide30",
        verboseErrors: false,
        verboseRouteRegistrationLogs: false,
        verboseStartupLogs: false,
    },
    feed: {
        minSize: APP_DEFAULTS.FEED_GRID_MIN_SIZE,
        showInfo: APP_DEFAULTS.FEED_SHOW_INFO,
        showFilename: APP_DEFAULTS.FEED_SHOW_FILENAME,
        showDimensions: APP_DEFAULTS.FEED_SHOW_DIMENSIONS,
        showDate: APP_DEFAULTS.FEED_SHOW_DATE,
        showGenTime: APP_DEFAULTS.FEED_SHOW_GENTIME,
        showWorkflowDot: APP_DEFAULTS.FEED_SHOW_WORKFLOW_DOT,
        showExtBadge: APP_DEFAULTS.FEED_SHOW_BADGES_EXTENSION,
        showRatingBadge: APP_DEFAULTS.FEED_SHOW_BADGES_RATING,
        showTagsBadge: APP_DEFAULTS.FEED_SHOW_BADGES_TAGS,
    },
    sidebar: {
        position: "right",
        showPreviewThumb: true,
        widthPx: 360,
        assetBadgeEnabled: APP_DEFAULTS.SIDEBAR_ASSET_BADGE_ENABLED,
    },
    probeBackend: {
        mode: "auto",
    },
    i18n: {
        followComfyLanguage: true,
    },
    metadataFallback: {
        image: true,
        media: true,
    },
    paths: {
        outputDirectory: "",
        indexDirectory: "",
        workflowRoots: "",
    },
    db: {
        timeoutMs: 5000,
        maxConnections: 10,
        queryTimeoutMs: 1000,
    },
    ratingTagsSync: {
        enabled: true,
    },
    cache: {
        tagsTTLms: 30_000,
    },
    search: {
        maxResults: APP_DEFAULTS.SEARCH_DEFAULT_LIMIT,
    },
    ai: {
        vectorSearchEnabled: true,
        vectorCaptionOnIndex: false,
        verboseAiLogs: false,
    },
    executionGrouping: {
        enabled: APP_DEFAULTS.EXECUTION_GROUPING_ENABLED,
    },
    workflowMinimap: {
        enabled: APP_DEFAULTS.WORKFLOW_MINIMAP_ENABLED,
        nodeColors: true,
        showLinks: true,
        showGroups: true,
        renderBypassState: true,
        renderErrorState: true,
        showViewport: true,
        showNodeLabels: false,
        size: "comfortable",
    },
    ui: {
        cardHoverColor: APP_DEFAULTS.UI_CARD_HOVER_COLOR,
        cardSelectionColor: APP_DEFAULTS.UI_CARD_SELECTION_COLOR,
        ratingColor: APP_DEFAULTS.UI_RATING_COLOR,
        tagColor: APP_DEFAULTS.UI_TAG_COLOR,
    },
    security: {
        safeMode: false,
        allowWrite: true,
        requireAuth: false,
        allowRemoteWrite: false,
        allowInsecureTokenTransport: false,
        allowDelete: true,
        allowRename: true,
        allowOpenInFolder: true,
        allowResetIndex: true,
        apiToken: "",
        tokenConfigured: false,
        tokenHint: "",
    },
};

// --- Load / Save ----------------------------------------------------------

export const loadMajoorSettings = (): Record<string, any> => {
    try {
        const raw = SettingsStore.get(SETTINGS_KEY);
        if (!raw) return { ...DEFAULT_SETTINGS };
        const parsed = JSON.parse(raw);
        const isWrapped =
            parsed &&
            typeof parsed === "object" &&
            Number.isInteger(parsed.version) &&
            parsed.data &&
            typeof parsed.data === "object";
        const isLegacyObject = parsed && typeof parsed === "object" && !Array.isArray(parsed);
        if (!isWrapped && !isLegacyObject) return { ...DEFAULT_SETTINGS };
        if (isWrapped && Number(parsed.version) > Number(SETTINGS_SCHEMA_VERSION)) {
            console.warn(
                "[Majoor] settings schema version is newer than this build, using defaults",
            );
            return { ...DEFAULT_SETTINGS };
        }
        const payload = isWrapped ? parsed.data : parsed;
        const allowed = new Set([
            "debug",
            "grid",
            "infiniteScroll",
            "siblings",
            "autoScan",
            "scan",
            "watcher",
            "status",
            "viewer",
            "rtHydrate",
            "observability",
            "feed",
            "sidebar",
            "probeBackend",
            "i18n",
            "paths",
            "db",
            "ratingTagsSync",
            "cache",
            "search",
            "ai",
            "executionGrouping",
            "workflowMinimap",
            "ui",
            "security",
            "safety",
        ]);
        const sanitized: Record<string, any> = {};
        if (payload && typeof payload === "object") {
            for (const [key, value] of Object.entries(payload)) {
                if (allowed.has(key)) sanitized[key] = value;
            }
        }
        const merged = deepMerge(DEFAULT_SETTINGS, sanitized);
        // Auto-migrate legacy format to wrapped versioned format.
        if (!isWrapped) {
            try {
                saveMajoorSettings(merged);
            } catch (e) {
                console.debug?.(e);
            }
        }
        return merged;
    } catch (error: any) {
        console.warn("[Majoor] settings load failed, using defaults", error);
        return { ...DEFAULT_SETTINGS };
    }
};

export const saveMajoorSettings = (settings: Record<string, any>): void => {
    try {
        const sanitized = JSON.parse(JSON.stringify(settings || {}));
        if (sanitized?.security && typeof sanitized.security === "object") {
            sanitized.security.apiToken = "";
        }
        const wrapped = {
            version: SETTINGS_SCHEMA_VERSION,
            data: sanitized,
        };
        const saved = SettingsStore.set(SETTINGS_KEY, JSON.stringify(wrapped));
        if (!saved) {
            throw new Error("SettingsStore rejected the write");
        }
    } catch (error: any) {
        console.warn("[Majoor] settings save failed", error);
        try {
            const now = Date.now();
            const last = Number(window?._mjrSettingsSaveFailAt || 0) || 0;
            if (now - last > 30_000) {
                window._mjrSettingsSaveFailAt = now;
                comfyAlert(
                    t(
                        "dialog.settingsSaveFailed",
                        "Majoor: Failed to save settings (browser storage full or blocked).",
                    ),
                );
            }
        } catch (e) {
            console.debug?.(e);
        }
        try {
            safeDispatchCustomEvent(
                "mjr-settings-save-failed",
                { error: String(error?.message || error || "") },
                { warnPrefix: "[Majoor]" },
            );
        } catch (e) {
            console.debug?.(e);
        }
    }
};

// --- Apply settings to runtime APP_CONFIG ---------------------------------

export const applySettingsToConfig = (settings: Record<string, any>): void => {
    const maxPage = Number(APP_DEFAULTS.MAX_PAGE_SIZE) || 2000;
    const pageSize = Math.max(
        50,
        Math.min(maxPage, Number(settings.grid?.pageSize) || APP_DEFAULTS.DEFAULT_PAGE_SIZE),
    );
    APP_CONFIG.DEFAULT_PAGE_SIZE = pageSize;
    APP_CONFIG.AUTO_SCAN_ON_STARTUP = !!settings.autoScan?.onStartup;
    APP_CONFIG.EXECUTION_GROUPING_ENABLED = !!(
        settings.executionGrouping?.enabled ?? APP_DEFAULTS.EXECUTION_GROUPING_ENABLED
    );
    APP_CONFIG.STATUS_POLL_INTERVAL = Math.max(
        1000,
        Number(settings.status?.pollInterval) || APP_DEFAULTS.STATUS_POLL_INTERVAL,
    );

    APP_CONFIG.DEBUG_SAFE_CALL = !!settings.debug?.safeCall;
    APP_CONFIG.DEBUG_SAFE_LISTENERS = !!settings.debug?.safeListeners;
    APP_CONFIG.DEBUG_VIEWER = !!settings.debug?.viewer;

    APP_CONFIG.GRID_MIN_SIZE = resolveGridMinSize(settings.grid);
    APP_CONFIG.FEED_GRID_MIN_SIZE = resolveFeedGridMinSize(settings.feed);
    APP_CONFIG.GRID_GAP = Math.max(
        0,
        Math.min(40, Math.round(_safeNum(settings.grid?.gap, APP_DEFAULTS.GRID_GAP))),
    );

    APP_CONFIG.GRID_SHOW_BADGES_EXTENSION = !!(
        settings.grid?.showExtBadge ?? APP_DEFAULTS.GRID_SHOW_BADGES_EXTENSION
    );
    APP_CONFIG.GRID_SHOW_BADGES_RATING = !!(
        settings.grid?.showRatingBadge ?? APP_DEFAULTS.GRID_SHOW_BADGES_RATING
    );
    APP_CONFIG.GRID_SHOW_BADGES_TAGS = !!(
        settings.grid?.showTagsBadge ?? APP_DEFAULTS.GRID_SHOW_BADGES_TAGS
    );
    APP_CONFIG.GRID_SHOW_DETAILS = !!(settings.grid?.showDetails ?? APP_DEFAULTS.GRID_SHOW_DETAILS);
    APP_CONFIG.GRID_SHOW_DETAILS_FILENAME = !!(
        settings.grid?.showFilename ?? APP_DEFAULTS.GRID_SHOW_DETAILS_FILENAME
    );
    APP_CONFIG.GRID_SHOW_DETAILS_DATE = !!(
        settings.grid?.showDate ?? APP_DEFAULTS.GRID_SHOW_DETAILS_DATE
    );
    APP_CONFIG.GRID_SHOW_DETAILS_DIMENSIONS = !!(
        settings.grid?.showDimensions ?? APP_DEFAULTS.GRID_SHOW_DETAILS_DIMENSIONS
    );
    APP_CONFIG.GRID_SHOW_DETAILS_GENTIME = !!(
        settings.grid?.showGenTime ?? APP_DEFAULTS.GRID_SHOW_DETAILS_GENTIME
    );
    APP_CONFIG.GRID_SHOW_HOVER_INFO = !!(
        settings.grid?.showHoverInfo ?? APP_DEFAULTS.GRID_SHOW_HOVER_INFO
    );
    APP_CONFIG.GRID_SHOW_WORKFLOW_DOT = !!(
        settings.grid?.showWorkflowDot ?? APP_DEFAULTS.GRID_SHOW_WORKFLOW_DOT
    );
    {
        const mode = String(
            settings.grid?.workflowGroupBy ?? APP_DEFAULTS.WORKFLOW_GRID_GROUP_BY,
        ).toLowerCase();
        APP_CONFIG.WORKFLOW_GRID_GROUP_BY = ["none", "task", "model", "category"].includes(mode)
            ? mode
            : APP_DEFAULTS.WORKFLOW_GRID_GROUP_BY;
    }

    // Bottom feed card display
    APP_CONFIG.FEED_SHOW_INFO = !!(settings.feed?.showInfo ?? APP_DEFAULTS.FEED_SHOW_INFO);
    APP_CONFIG.FEED_SHOW_FILENAME = !!(
        settings.feed?.showFilename ?? APP_DEFAULTS.FEED_SHOW_FILENAME
    );
    APP_CONFIG.FEED_SHOW_DIMENSIONS = !!(
        settings.feed?.showDimensions ?? APP_DEFAULTS.FEED_SHOW_DIMENSIONS
    );
    APP_CONFIG.FEED_SHOW_DATE = !!(settings.feed?.showDate ?? APP_DEFAULTS.FEED_SHOW_DATE);
    APP_CONFIG.FEED_SHOW_GENTIME = !!(settings.feed?.showGenTime ?? APP_DEFAULTS.FEED_SHOW_GENTIME);
    APP_CONFIG.FEED_SHOW_WORKFLOW_DOT = !!(
        settings.feed?.showWorkflowDot ?? APP_DEFAULTS.FEED_SHOW_WORKFLOW_DOT
    );
    APP_CONFIG.FEED_SHOW_BADGES_EXTENSION = !!(
        settings.feed?.showExtBadge ?? APP_DEFAULTS.FEED_SHOW_BADGES_EXTENSION
    );
    APP_CONFIG.FEED_SHOW_BADGES_RATING = !!(
        settings.feed?.showRatingBadge ?? APP_DEFAULTS.FEED_SHOW_BADGES_RATING
    );
    APP_CONFIG.FEED_SHOW_BADGES_TAGS = !!(
        settings.feed?.showTagsBadge ?? APP_DEFAULTS.FEED_SHOW_BADGES_TAGS
    );

    // Video autoplay mode: migrate old boolean -> new tri-state
    {
        let mode = settings.grid?.videoAutoplayMode ?? APP_DEFAULTS.GRID_VIDEO_AUTOPLAY_MODE;
        // Backward compat: old boolean "videoHoverAutoplay" -> "hover"
        if (mode === undefined || mode === null) {
            mode = settings.grid?.videoHoverAutoplay === false ? "off" : "hover";
        }
        if (mode === true) mode = "hover";
        if (mode === false) mode = "off";
        if (mode !== "hover" && mode !== "always" && mode !== "off") mode = "hover";
        APP_CONFIG.GRID_VIDEO_AUTOPLAY_MODE = mode;
    }

    const _safeColor = (v: any, fallback: any) => {
        let c = String(v || "").trim();
        if (/^[0-9a-fA-F]{6}$/.test(c)) c = `#${c}`;
        return /^#[0-9a-fA-F]{3,8}$/.test(c) ? c : fallback;
    };
    APP_CONFIG.BADGE_STAR_COLOR = _safeColor(
        settings.grid?.starColor,
        APP_DEFAULTS.BADGE_STAR_COLOR,
    );
    APP_CONFIG.BADGE_IMAGE_COLOR = _safeColor(
        settings.grid?.badgeImageColor,
        APP_DEFAULTS.BADGE_IMAGE_COLOR,
    );
    APP_CONFIG.BADGE_VIDEO_COLOR = _safeColor(
        settings.grid?.badgeVideoColor,
        APP_DEFAULTS.BADGE_VIDEO_COLOR,
    );
    APP_CONFIG.BADGE_AUDIO_COLOR = _safeColor(
        settings.grid?.badgeAudioColor,
        APP_DEFAULTS.BADGE_AUDIO_COLOR,
    );
    APP_CONFIG.BADGE_MODEL3D_COLOR = _safeColor(
        settings.grid?.badgeModel3dColor,
        APP_DEFAULTS.BADGE_MODEL3D_COLOR,
    );
    APP_CONFIG.BADGE_DUPLICATE_ALERT_COLOR = _safeColor(
        settings.grid?.badgeDuplicateAlertColor,
        APP_DEFAULTS.BADGE_DUPLICATE_ALERT_COLOR,
    );
    APP_CONFIG.UI_CARD_HOVER_COLOR = _safeColor(
        settings.ui?.cardHoverColor,
        APP_DEFAULTS.UI_CARD_HOVER_COLOR,
    );
    APP_CONFIG.UI_CARD_SELECTION_COLOR = _safeColor(
        settings.ui?.cardSelectionColor,
        APP_DEFAULTS.UI_CARD_SELECTION_COLOR,
    );
    APP_CONFIG.UI_RATING_COLOR = _safeColor(settings.ui?.ratingColor, APP_DEFAULTS.UI_RATING_COLOR);
    APP_CONFIG.UI_TAG_COLOR = _safeColor(settings.ui?.tagColor, APP_DEFAULTS.UI_TAG_COLOR);

    try {
        const roots = Array.from(document.querySelectorAll<HTMLElement>(".mjr-assets-manager"));
        for (const root of roots) {
            root.style.setProperty("--mjr-star-active", APP_CONFIG.BADGE_STAR_COLOR);
            root.style.setProperty("--mjr-badge-image", APP_CONFIG.BADGE_IMAGE_COLOR);
            root.style.setProperty("--mjr-badge-video", APP_CONFIG.BADGE_VIDEO_COLOR);
            root.style.setProperty("--mjr-badge-audio", APP_CONFIG.BADGE_AUDIO_COLOR);
            root.style.setProperty("--mjr-badge-model3d", APP_CONFIG.BADGE_MODEL3D_COLOR);
            root.style.setProperty(
                "--mjr-badge-duplicate-alert",
                APP_CONFIG.BADGE_DUPLICATE_ALERT_COLOR,
            );
            root.style.setProperty("--mjr-card-hover-color", APP_CONFIG.UI_CARD_HOVER_COLOR);
            root.style.setProperty(
                "--mjr-card-selection-color",
                APP_CONFIG.UI_CARD_SELECTION_COLOR,
            );
            root.style.setProperty("--mjr-rating-color", APP_CONFIG.UI_RATING_COLOR);
            root.style.setProperty("--mjr-tag-color", APP_CONFIG.UI_TAG_COLOR);
        }
    } catch (e) {
        console.debug?.(e);
    }

    APP_CONFIG.INFINITE_SCROLL_ENABLED = !!settings.infiniteScroll?.enabled;
    APP_CONFIG.INFINITE_SCROLL_ROOT_MARGIN = String(
        settings.infiniteScroll?.rootMargin || APP_DEFAULTS.INFINITE_SCROLL_ROOT_MARGIN,
    );
    APP_CONFIG.INFINITE_SCROLL_THRESHOLD = Math.max(
        0,
        Math.min(
            1,
            _safeNum(settings.infiniteScroll?.threshold, APP_DEFAULTS.INFINITE_SCROLL_THRESHOLD),
        ),
    );
    APP_CONFIG.BOTTOM_GAP_PX = Math.max(
        0,
        Math.min(
            5000,
            Math.round(_safeNum(settings.infiniteScroll?.bottomGapPx, APP_DEFAULTS.BOTTOM_GAP_PX)),
        ),
    );

    APP_CONFIG.VIEWER_ALLOW_PAN_AT_ZOOM_1 = !!settings.viewer?.allowPanAtZoom1;
    APP_CONFIG.VIEWER_DISABLE_WEBGL_VIDEO = !!settings.viewer?.disableWebGL;
    APP_CONFIG.VIEWER_PAUSE_DURING_EXECUTION = !!(
        settings.viewer?.pauseDuringExecution ?? APP_DEFAULTS.VIEWER_PAUSE_DURING_EXECUTION
    );
    APP_CONFIG.FLOATING_VIEWER_PAUSE_DURING_EXECUTION = !!(
        settings.viewer?.floatingPauseDuringExecution ??
        APP_DEFAULTS.FLOATING_VIEWER_PAUSE_DURING_EXECUTION
    );
    APP_CONFIG.MFV_LIVE_DEFAULT = settings.viewer?.mfvLiveDefault ?? APP_DEFAULTS.MFV_LIVE_DEFAULT;
    APP_CONFIG.MFV_PREVIEW_DEFAULT =
        settings.viewer?.mfvPreviewDefault ?? APP_DEFAULTS.MFV_PREVIEW_DEFAULT;
    APP_CONFIG.MFV_LIVE_AUTO_OPEN = false;
    APP_CONFIG.MFV_PREVIEW_AUTO_OPEN = false;
    APP_CONFIG.MFV_NODE_STREAM_AUTO_OPEN = false;
    {
        const previewMethod = String(
            settings.viewer?.mfvPreviewMethod || APP_DEFAULTS.MFV_PREVIEW_METHOD,
        ).toLowerCase();
        APP_CONFIG.MFV_PREVIEW_METHOD = ["default", "auto", "latent2rgb", "taesd", "none"].includes(
            previewMethod,
        )
            ? previewMethod
            : APP_DEFAULTS.MFV_PREVIEW_METHOD;
    }
    {
        const pos = String(settings.viewer?.mfvSidebarPosition || "right").toLowerCase();
        APP_CONFIG.MFV_SIDEBAR_POSITION = ["left", "right", "bottom"].includes(pos) ? pos : "right";
    }
    APP_CONFIG.VIEWER_VIDEO_GRADE_THROTTLE_FPS = Math.max(
        1,
        Math.min(
            60,
            Math.round(
                _safeNum(
                    settings.viewer?.videoGradeThrottleFps,
                    APP_DEFAULTS.VIEWER_VIDEO_GRADE_THROTTLE_FPS,
                ),
            ),
        ),
    );
    APP_CONFIG.VIEWER_SCOPES_FPS = Math.max(
        1,
        Math.min(
            60,
            Math.round(_safeNum(settings.viewer?.scopesFps, APP_DEFAULTS.VIEWER_SCOPES_FPS)),
        ),
    );
    APP_CONFIG.VIEWER_META_TTL_MS = Math.max(
        1000,
        Math.min(
            10 * 60_000,
            Math.round(_safeNum(settings.viewer?.metaTtlMs, APP_DEFAULTS.VIEWER_META_TTL_MS)),
        ),
    );
    APP_CONFIG.VIEWER_META_MAX_ENTRIES = Math.max(
        50,
        Math.min(
            5000,
            Math.round(
                _safeNum(settings.viewer?.metaMaxEntries, APP_DEFAULTS.VIEWER_META_MAX_ENTRIES),
            ),
        ),
    );

    APP_CONFIG.WORKFLOW_MINIMAP_ENABLED = !!(
        settings.workflowMinimap?.enabled ?? APP_DEFAULTS.WORKFLOW_MINIMAP_ENABLED
    );

    APP_CONFIG.RT_HYDRATE_CONCURRENCY = Math.max(
        1,
        Math.min(
            16,
            Math.round(
                _safeNum(settings.rtHydrate?.concurrency, APP_DEFAULTS.RT_HYDRATE_CONCURRENCY),
            ),
        ),
    );
    APP_CONFIG.RT_HYDRATE_QUEUE_MAX = Math.max(
        10,
        Math.min(
            5000,
            Math.round(_safeNum(settings.rtHydrate?.queueMax, APP_DEFAULTS.RT_HYDRATE_QUEUE_MAX)),
        ),
    );
    APP_CONFIG.RT_HYDRATE_SEEN_MAX = Math.max(
        1000,
        Math.min(
            200_000,
            Math.round(_safeNum(settings.rtHydrate?.seenMax, APP_DEFAULTS.RT_HYDRATE_SEEN_MAX)),
        ),
    );
    APP_CONFIG.RT_HYDRATE_PRUNE_BUDGET = Math.max(
        10,
        Math.min(
            10_000,
            Math.round(
                _safeNum(settings.rtHydrate?.pruneBudget, APP_DEFAULTS.RT_HYDRATE_PRUNE_BUDGET),
            ),
        ),
    );
    APP_CONFIG.RT_HYDRATE_SEEN_TTL_MS = Math.max(
        5_000,
        Math.min(
            6 * 60 * 60_000,
            Math.round(
                _safeNum(settings.rtHydrate?.seenTtlMs, APP_DEFAULTS.RT_HYDRATE_SEEN_TTL_MS),
            ),
        ),
    );

    APP_CONFIG.DELETE_CONFIRMATION = !!settings.safety?.confirmDeletion;
    APP_CONFIG.DEBUG_VERBOSE_ERRORS = !!settings.observability?.verboseErrors;
    APP_CONFIG.WATCHER_MAX_PENDING = Math.max(
        10,
        Math.min(5000, Math.round(_safeNum(settings.watcher?.maxPending, 500))),
    );
    APP_CONFIG.WATCHER_MIN_SIZE = Math.max(
        0,
        Math.min(1000000, Math.round(_safeNum(settings.watcher?.minSize, 100))),
    );
    APP_CONFIG.WATCHER_MAX_SIZE = Math.max(
        100000,
        Math.min(17179869184, Math.round(_safeNum(settings.watcher?.maxSize, 4294967296))),
    );
    APP_CONFIG.DB_TIMEOUT_MS = Math.max(
        1000,
        Math.min(30000, Math.round(_safeNum(settings.db?.timeoutMs, 5000))),
    );
    APP_CONFIG.DB_MAX_CONNECTIONS = Math.max(
        1,
        Math.min(100, Math.round(_safeNum(settings.db?.maxConnections, 10))),
    );
    APP_CONFIG.DB_QUERY_TIMEOUT_MS = Math.max(
        500,
        Math.min(10000, Math.round(_safeNum(settings.db?.queryTimeoutMs, 1000))),
    );
    APP_CONFIG.SIDEBAR_ASSET_BADGE_ENABLED = !!(
        settings.sidebar?.assetBadgeEnabled ?? APP_DEFAULTS.SIDEBAR_ASSET_BADGE_ENABLED
    );
    // Search request limit (client-side); backend still enforces MAJOOR_SEARCH_MAX_LIMIT
    APP_CONFIG.SEARCH_REQUEST_LIMIT = Math.max(
        10,
        Math.min(
            APP_DEFAULTS.MAX_PAGE_SIZE || 2000,
            Math.round(_safeNum(settings.search?.maxResults, APP_DEFAULTS.SEARCH_DEFAULT_LIMIT)),
        ),
    );
};

// --- Backend security sync ------------------------------------------------

export async function syncBackendSecuritySettings(): Promise<void> {
    try {
        const res = await getSecuritySettings();
        if (!res?.ok) return;
        const prefs = res.data?.prefs;
        if (!prefs || typeof prefs !== "object") return;

        const settings = loadMajoorSettings();
        settings.security = settings.security || {};
        settings.security.safeMode = _safeBool(prefs.safe_mode, settings.security.safeMode);
        settings.security.allowWrite = _safeBool(prefs.allow_write, settings.security.allowWrite);
        settings.security.requireAuth = _safeBool(
            prefs.require_auth,
            settings.security.requireAuth,
        );
        settings.security.allowRemoteWrite = _safeBool(
            prefs.allow_remote_write,
            settings.security.allowRemoteWrite,
        );
        settings.security.allowInsecureTokenTransport = _safeBool(
            prefs.allow_insecure_token_transport,
            settings.security.allowInsecureTokenTransport,
        );
        settings.security.allowDelete = _safeBool(
            prefs.allow_delete,
            settings.security.allowDelete,
        );
        settings.security.allowRename = _safeBool(
            prefs.allow_rename,
            settings.security.allowRename,
        );
        settings.security.allowOpenInFolder = _safeBool(
            prefs.allow_open_in_folder,
            settings.security.allowOpenInFolder,
        );
        settings.security.allowResetIndex = _safeBool(
            prefs.allow_reset_index,
            settings.security.allowResetIndex,
        );
        settings.security.tokenConfigured = _safeBool(
            prefs.token_configured,
            settings.security.tokenConfigured,
        );
        settings.security.tokenHint = String(prefs.token_hint || "").trim();
        // Security settings endpoint intentionally does not expose the token.
        // Bootstrap it once so write actions work without manual user input.
        if (!String(settings.security.apiToken || "").trim()) {
            try {
                const boot = await bootstrapSecurityToken();
                const bootToken = String(boot?.data?.token || "").trim();
                if (boot?.ok && bootToken) {
                    setRuntimeSecurityToken(bootToken);
                }
            } catch (e) {
                console.debug?.(e);
            }
        }
        saveMajoorSettings(settings);
        applySettingsToConfig(settings);
        safeDispatchCustomEvent(
            "mjr-settings-changed",
            { key: "security" },
            { warnPrefix: "[Majoor]" },
        );
    } catch (error: any) {
        console.warn("[Majoor] failed to sync backend security settings", error);
    }
}

export async function syncBackendVectorSearchSettings(): Promise<void> {
    try {
        const res = await getVectorSearchSettings();
        if (!res?.ok) return;
        const prefs = res.data?.prefs;
        if (!prefs || typeof prefs !== "object") return;

        const settings = loadMajoorSettings();
        settings.ai = settings.ai || {};
        settings.ai.vectorSearchEnabled = _safeBool(
            prefs.enabled,
            settings.ai.vectorSearchEnabled ?? true,
        );
        settings.ai.vectorCaptionOnIndex = _safeBool(
            prefs.caption_on_index ?? prefs.captionOnIndex,
            settings.ai.vectorCaptionOnIndex ?? false,
        );
        settings.ai.vectorIndexOnScan = _safeBool(
            prefs.index_on_scan ?? prefs.indexOnScan,
            settings.ai.vectorIndexOnScan ?? false,
        );
        settings.ai.vectorUnloadAfterUse = _safeBool(
            prefs.unload_after_use ?? prefs.unloadAfterUse,
            settings.ai.vectorUnloadAfterUse ?? false,
        );
        settings.ai.vectorConcurrency = Math.max(
            1,
            Math.min(16, Math.floor(Number(prefs.concurrency ?? settings.ai.vectorConcurrency ?? 1) || 1)),
        );
        saveMajoorSettings(settings);
        applySettingsToConfig(settings);
        safeDispatchCustomEvent(
            "mjr-settings-changed",
            { key: "ai.vectorSearch" },
            { warnPrefix: "[Majoor]" },
        );
    } catch (error: any) {
        console.warn("[Majoor] failed to sync backend vector search settings", error);
    }
}

export async function syncBackendExecutionGroupingSettings(): Promise<void> {
    try {
        const res = await getExecutionGroupingSettings();
        if (!res?.ok) return;
        const prefs = res.data?.prefs;
        if (!prefs || typeof prefs !== "object") return;

        const settings = loadMajoorSettings();
        settings.executionGrouping = settings.executionGrouping || {};
        settings.executionGrouping.enabled = _safeBool(
            prefs.enabled,
            settings.executionGrouping.enabled ?? APP_DEFAULTS.EXECUTION_GROUPING_ENABLED,
        );
        saveMajoorSettings(settings);
        applySettingsToConfig(settings);
        safeDispatchCustomEvent(
            "mjr-settings-changed",
            { key: "executionGrouping.enabled" },
            { warnPrefix: "[Majoor]" },
        );
    } catch (error: any) {
        console.warn("[Majoor] failed to sync backend execution grouping settings", error);
    }
}
