pragma Singleton

// Search across every setting, not just page names.
//
// The sidebar's field used to filter the twelve page labels, so "gaps",
// "wallpaper", and "repeat delay" all found nothing — which is precisely the
// thing that makes a settings app feel smaller than it is. This indexes the
// schema itself, so any setting is reachable by typing what it does, and a new
// schema entry becomes searchable with no change here.
//
// Shortcuts are indexed too: "screenshot" should find the key that takes one.

import Quickshell
import QtQuick
import qs.config

Singleton {
    id: root

    // SettingsSearch is part of the always-constructed sidebar. Touching the
    // desktop-style service here gives application preferences their startup
    // replay even when Appearance is not the page that opens first.
    Component.onCompleted: DesktopStyle.ensureStarted()

    // Which page shows the settings in a given schema group. A group with no
    // entry here still appears in results and routes to Home rather than being
    // dropped, so adding a group can never make a setting unreachable.
    readonly property var groupPages: ({
        "clock": "appearance",
        "vitals": "appearance",
        "typography": "appearance",
        "themes": "appearance",
        "titlebar": "appearance",
        "windows": "appearance",
        "effects": "appearance",
        "wallpaper": "appearance",
        "lockAppearance": "appearance",
        "dock": "desktop",
        "focus": "desktop",
        "display": "displays",
        "nightLight": "displays",
        "idle": "power",
        "accessibility": "accessibility",
        "input": "shortcuts",
        "pointer": "mouse",
        "touchpad": "mouse",
        "multitasking": "desktop",
        "edges": "desktop",
        "master": "desktop",
        "notices": "desktop",
        "weather": "home",
        "notifications": "notifications",
        "capture": "screen-intelligence"
    })

    // Settings that are real but have no schema entry, because the system owns
    // them rather than Panama. Without these, searching "timezone" would fail
    // on a settings app that plainly has one.
    readonly property var extraEntries: [
        { label: "Timezone", detail: "Set the system timezone", page: "datetime" },
        { label: "Network time", detail: "Synchronise the clock with a time server", page: "datetime" },
        { label: "Wi-Fi", detail: "Managed by GNOME Settings", page: "connectivity" },
        { label: "Bluetooth", detail: "Managed by GNOME Settings", page: "connectivity" },
        { label: "Printers", detail: "Managed by GNOME Settings", page: "connectivity" },
        { label: "Default applications", detail: "Browser, mail, files", page: "applications" },
        { label: "Restore defaults", detail: "Return every Panama setting to its shipped value", page: "desktop" },
        { label: "Keyboard shortcuts", detail: "Every shortcut the compositor has bound", page: "shortcuts" },
        { label: "System Health", detail: "Check Panama services, integrations, tools, and recovery actions", page: "services" },
        { label: "Copy health report", detail: "Copy a redacted Panama doctor report", page: "services" },
        { label: "Lock screen background", detail: "Choose a blurred desktop, wallpaper, or solid colour", page: "appearance" },
        { label: "Password field", detail: "Choose whether the empty lock-screen field stays visible", page: "appearance" },
        { label: "Per-display wallpaper", detail: "Assign a different image to each connected display", page: "appearance" },
        { label: "Arrange displays", detail: "Drag connected displays into their physical positions", page: "displays" },
        { label: "Monitor position", detail: "Set where each display sits in the desktop", page: "displays" },
        { label: "Primary display", detail: "Choose the display that anchors the desktop", page: "displays" }
    ]

    function pageFor(group: string): string {
        return root.groupPages[group] ?? "home";
    }

    // [{ label, detail, page, kind }] for a query. Empty query yields nothing:
    // the sidebar shows its normal navigation in that case.
    function search(query: string): var {
        const needle = String(query).trim().toLowerCase();
        if (needle === "")
            return [];

        const results = [];
        const seen = {};

        function add(label, detail, page, kind) {
            const dedupe = `${kind}:${label}:${page}`;
            if (seen[dedupe])
                return;
            seen[dedupe] = true;
            results.push({ label: label, detail: detail, page: page, kind: kind });
        }

        for (const entry of PreferenceSchema.entries) {
            if (entry.internal)
                continue;
            const optionLabels = (entry.options ?? []).map(option => option.label).join(" ");
            const haystack = `${entry.label} ${entry.detail ?? ""} ${entry.group} ${optionLabels}`.toLowerCase();
            if (haystack.indexOf(needle) >= 0)
                add(entry.label, entry.detail ?? "", root.pageFor(entry.group), "setting");
        }

        for (const entry of root.extraEntries) {
            if (`${entry.label} ${entry.detail}`.toLowerCase().indexOf(needle) >= 0)
                add(entry.label, entry.detail, entry.page, "setting");
        }

        for (const bind of Keybinds.binds) {
            if (bind.description.toLowerCase().indexOf(needle) >= 0)
                add(bind.description, bind.chord, "shortcuts", "shortcut");
        }

        // Exact prefix matches first: typing "blur" should put "Blur" above
        // "Blur radius", and both above a setting that merely mentions blur in
        // its explanation. An exact enum option also leads: "slideshow" is a
        // mode choice, so Wallpaper mode belongs above the interval row that
        // merely explains it.
        return results.sort((a, b) => {
            const aSpec = PreferenceSchema.entries.find(entry => entry.label === a.label);
            const bSpec = PreferenceSchema.entries.find(entry => entry.label === b.label);
            const ao = (aSpec?.options ?? []).some(option => option.label.toLowerCase() === needle);
            const bo = (bSpec?.options ?? []).some(option => option.label.toLowerCase() === needle);
            if (ao !== bo)
                return ao ? -1 : 1;
            const al = a.label.toLowerCase();
            const bl = b.label.toLowerCase();
            const ap = al === needle ? 0 : (al.indexOf(needle) === 0 ? 1 : 2);
            const bp = bl === needle ? 0 : (bl.indexOf(needle) === 0 ? 1 : 2);
            return ap !== bp ? ap - bp : al.localeCompare(bl);
        }).slice(0, 40);
    }
}
