pragma Singleton

// Turning a place name into coordinates.
//
// The weather card needs latitude and longitude, but nobody knows their own
// coordinates, and a settings page that demands them is a settings page nobody
// changes. Open-Meteo publishes a geocoding endpoint that needs no API key and
// no account, which is the same reason the forecast itself uses them.
//
// Fetched with curl rather than XMLHttpRequest for the same reason as
// services/Weather.qml: curl is guaranteed present, and a search that fails
// must leave the page usable rather than producing an error popup.
//
// Only the query is sent. The stored location label never leaves the machine.

import Quickshell
import Quickshell.Io
import QtQuick
import qs.config

Singleton {
    id: root

    // [{ name, admin, country, latitude, longitude, label }]
    property var results: []
    property bool searching: false
    property string lastError: ""
    property string lastQuery: ""

    readonly property string endpoint: "https://geocoding-api.open-meteo.com/v1/search"

    Process {
        id: fetch

        stdout: StdioCollector {
            onStreamFinished: root.parse(this.text)
        }

        onExited: (exitCode, exitStatus) => {
            root.searching = false;
            if (exitCode !== 0)
                root.lastError = "Could not reach the location service.";
        }
    }

    // Debounced: typing "Denver" should not fire six searches.
    Timer {
        id: debounce
        interval: 350
        onTriggered: root.run()
    }

    property string pending: ""

    function search(query: string): void {
        const trimmed = String(query).trim();
        root.pending = trimmed;
        if (trimmed.length < 2) {
            root.results = [];
            root.lastError = "";
            debounce.stop();
            return;
        }
        debounce.restart();
    }

    function run(): void {
        if (fetch.running || root.pending.length < 2)
            return;
        root.searching = true;
        root.lastError = "";
        root.lastQuery = root.pending;

        // --get with --data-urlencode makes curl do the escaping, so a place
        // name with spaces or an ampersand cannot alter the request.
        fetch.exec(["curl", "-s", "--max-time", "10", "--get",
            "--data-urlencode", `name=${root.pending}`,
            "--data-urlencode", "count=8",
            "--data-urlencode", "format=json",
            root.endpoint]);
    }

    function parse(text: string): void {
        try {
            const parsed = JSON.parse(text);
            const out = [];
            for (const item of (parsed.results ?? [])) {
                if (typeof item.latitude !== "number" || typeof item.longitude !== "number")
                    continue;
                const admin = item.admin1 ?? "";
                const country = item.country ?? "";
                out.push({
                    name: item.name ?? "",
                    admin: admin,
                    country: country,
                    latitude: item.latitude,
                    longitude: item.longitude,
                    // What the user will see stored as their location label.
                    label: [item.name, admin, country].filter(part => !!part).join(", ")
                });
            }
            root.results = out;
            root.lastError = out.length === 0 ? "No places match that name." : "";
        } catch (error) {
            root.results = [];
            root.lastError = "The location service returned something unreadable.";
        }
    }

    // Stores a chosen place. Coordinates are rounded to four decimals -- roughly
    // ten metres, far finer than a weather reading resolves, and it keeps a
    // precise home location out of the settings file.
    function choose(place: var): bool {
        const latitude = Math.round(place.latitude * 10000) / 10000;
        const longitude = Math.round(place.longitude * 10000) / 10000;
        const label = String(place.label).slice(0, 64);

        const ok = DesktopPreferences.set("weatherLatitude", latitude)
            && DesktopPreferences.set("weatherLongitude", longitude)
            && DesktopPreferences.set("weatherLocation", label);
        if (!ok) {
            root.lastError = "That location could not be saved.";
            return false;
        }
        root.results = [];
        root.lastError = "";
        return true;
    }
}
