pragma Singleton

// ─────────────────────────────────────────────────────────────────────────────
// Current conditions from Open-Meteo (no API key, no account).
//
// Fetched with curl rather than XMLHttpRequest: curl is guaranteed present and
// the response is a single small JSON blob, so there is nothing to stream.
//
// Failure is silent by design — a weather widget that can't reach the network
// must not produce error popups or retry storms. A failed fetch just leaves
// `available` false until the next scheduled refresh.
// ─────────────────────────────────────────────────────────────────────────────

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

Singleton {
    id: root

    // False until a fetch has succeeded, and again after one fails.
    property bool available: false

    property real temperature: 0
    property int weatherCode: -1

    // Nerd Font glyph and a short human label for `weatherCode`.
    readonly property string icon: root._codeIcon(root.weatherCode)
    readonly property string description: root._codeDescription(root.weatherCode)

    readonly property string unitSuffix: Settings.temperatureUnit === "fahrenheit" ? "°F" : "°C"

    readonly property string url: "https://api.open-meteo.com/v1/forecast" + "?latitude=" + Settings.latitude + "&longitude=" + Settings.longitude + "&current=temperature_2m,weather_code" + "&temperature_unit=" + Settings.temperatureUnit

    // The timer is the *only* thing that starts a fetch, so a hard failure can
    // never retry faster than the refresh interval.
    Timer {
        interval: Settings.weatherRefreshMinutes * 60 * 1000
        running: true
        repeat: true
        triggeredOnStart: true
        onTriggered: root.refresh()
    }

    Process {
        id: fetch
        // -s silences the progress meter, --max-time keeps a black-holed
        // connection from leaving the process alive until the next refresh.
        command: ["curl", "-s", "--max-time", "10", root.url]

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

        onExited: (exitCode, exitStatus) => {
            if (exitCode !== 0)
                root.available = false;
        }
    }

    // Nothing was chosen yet: fetch nothing, claim nothing. The card offers
    // the location picker instead.
    readonly property bool configured: Settings.weatherLocation !== ""

    function refresh(): void {
        if (!root.configured) {
            root.available = false;
            return;
        }
        // Skip if the previous fetch is somehow still in flight.
        if (!fetch.running)
            fetch.running = true;
    }

    function _parse(text: string): void {
        if (!text) {
            root.available = false;
            return;
        }

        try {
            const current = JSON.parse(text).current;
            if (!current || current.temperature_2m === undefined) {
                root.available = false;
                return;
            }

            root.temperature = current.temperature_2m;
            root.weatherCode = current.weather_code;
            root.available = true;
        } catch (e) {
            root.available = false;
        }
    }

    // ── WMO 4677 weather codes ──────────────────────────────────────────────
    // Open-Meteo's `weather_code` is the WMO set. Grouped here into the same
    // buckets GNOME Weather uses, because the distinctions finer than this are
    // not legible at bar size.
    // Glyphs come from the nf-weather range. They are written as escapes rather
    // than literal characters so tooling that mishandles private-use codepoints
    // cannot silently eat them.
    function _codeIcon(code: int): string {
        switch (code) {
        case 0:
            return "\u{E30D}";  // weather-day_sunny
        case 1:
            return "\u{E30C}";  // weather-day_sunny_overcast
        case 2:
            return "\u{E302}";  // weather-day_cloudy
        case 3:
            return "\u{E312}";  // weather-cloudy
        case 45:
        case 48:
            return "\u{E313}";  // weather-fog
        case 51:
        case 53:
        case 55:
            return "\u{E31B}";  // weather-sprinkle
        case 56:
        case 57:
        case 66:
        case 67:
            return "\u{E316}";  // weather-rain_mix (freezing)
        case 61:
        case 63:
        case 65:
            return "\u{E318}";  // weather-rain
        case 71:
        case 73:
        case 75:
        case 77:
        case 85:
        case 86:
            return "\u{E31A}";  // weather-snow
        case 80:
        case 81:
        case 82:
            return "\u{E319}";  // weather-showers
        case 95:
        case 96:
        case 99:
            return "\u{E31D}";  // weather-thunderstorm
        default:
            return "\u{E374}";  // weather-na — unknown, or nothing fetched yet
        }
    }

    function _codeDescription(code: int): string {
        switch (code) {
        case 0:
            return "Clear";
        case 1:
            return "Mainly clear";
        case 2:
            return "Partly cloudy";
        case 3:
            return "Overcast";
        case 45:
            return "Fog";
        case 48:
            return "Rime fog";
        case 51:
        case 53:
        case 55:
            return "Drizzle";
        case 56:
        case 57:
            return "Freezing drizzle";
        case 61:
        case 63:
        case 65:
            return "Rain";
        case 66:
        case 67:
            return "Freezing rain";
        case 71:
        case 73:
        case 75:
            return "Snow";
        case 77:
            return "Snow grains";
        case 80:
        case 81:
        case 82:
            return "Showers";
        case 85:
        case 86:
            return "Snow showers";
        case 95:
            return "Thunderstorm";
        case 96:
        case 99:
            return "Thunderstorm, hail";
        default:
            return "";
        }
    }
}
