// Live local weather. When the visitor saves a postal code we geocode it once
// (Zippopotam — free, no key), then poll Open-Meteo (free, no key) for the
// current conditions + a 5-day forecast. The result drives two bits of UI — a
// header chip and a home-page card — and overrides the seasonal canvas via
// PeteWeather.set(). Everything lives in the browser: no server calls, no keys.
//
// Politeness: the geocode never changes for a postal code, so it's cached
// forever. Forecasts are cached for 2 hours so a reload (or hopping between
// pages) doesn't re-hit Open-Meteo.
(function () {
  var LOC_KEY = "pete.weather.loc.v1";       // {country, postal, lat, lon, name, unit}
  var CACHE_KEY = "pete.weather.cache.v2";    // {key, fetchedAt, current, daily, aqi}
  var TTL_MS = 2 * 60 * 60 * 1000;            // 2 hours

  // ---- storage helpers -----------------------------------------------------
  function readJSON(key) {
    try { var r = localStorage.getItem(key); return r ? JSON.parse(r) : null; }
    catch (e) { return null; }
  }
  function writeJSON(key, val) {
    try { localStorage.setItem(key, JSON.stringify(val)); } catch (e) {}
  }
  function getLoc() { return readJSON(LOC_KEY); }
  function setLoc(loc) {
    if (loc) writeJSON(LOC_KEY, loc);
    else { try { localStorage.removeItem(LOC_KEY); } catch (e) {} }
    if (window.PetePrefs) window.PetePrefs.push();
  }

  // ---- WMO weather code → canvas variant + label + emoji -------------------
  // https://open-meteo.com/en/docs — `weather_code` is the WMO code.
  function describe(code, isDay) {
    var c = Number(code);
    function r(variant, intensity, label, dayEmoji, nightEmoji) {
      return { variant: variant, intensity: intensity, label: label,
               emoji: (isDay === 0 && nightEmoji) ? nightEmoji : dayEmoji };
    }
    if (c === 0) return r("clear", "heavy", "Clear sky", "☀️", "🌙");
    if (c === 1) return r("clouds", "light", "Mainly clear", "🌤️", "🌙");
    if (c === 2) return r("clouds", "medium", "Partly cloudy", "⛅", "☁️");
    if (c === 3) return r("clouds", "heavy", "Overcast", "☁️", "☁️");
    if (c === 45 || c === 48) return r("fog", "heavy", "Fog", "🌫️", "🌫️");
    if (c === 51) return r("rain", "light", "Light drizzle", "🌦️", "🌧️");
    if (c === 53) return r("rain", "medium", "Drizzle", "🌦️", "🌧️");
    if (c === 55) return r("rain", "heavy", "Heavy drizzle", "🌧️", "🌧️");
    if (c === 56 || c === 57) return r("rain", "medium", "Freezing drizzle", "🌧️", "🌧️");
    if (c === 61) return r("rain", "light", "Light rain", "🌦️", "🌧️");
    if (c === 63) return r("rain", "medium", "Rain", "🌧️", "🌧️");
    if (c === 65) return r("rain", "heavy", "Heavy rain", "🌧️", "🌧️");
    if (c === 66 || c === 67) return r("rain", "medium", "Freezing rain", "🌧️", "🌧️");
    if (c === 71) return r("snow", "light", "Light snow", "🌨️", "🌨️");
    if (c === 73) return r("snow", "medium", "Snow", "🌨️", "🌨️");
    if (c === 75) return r("snow", "heavy", "Heavy snow", "❄️", "❄️");
    if (c === 77) return r("snow", "light", "Snow grains", "🌨️", "🌨️");
    if (c === 80) return r("rain", "medium", "Light showers", "🌦️", "🌧️");
    if (c === 81) return r("rain", "medium", "Showers", "🌧️", "🌧️");
    if (c === 82) return r("rain", "heavy", "Heavy showers", "⛈️", "⛈️");
    if (c === 85) return r("snow", "medium", "Snow showers", "🌨️", "🌨️");
    if (c === 86) return r("snow", "heavy", "Heavy snow showers", "❄️", "❄️");
    if (c === 95) return r("storm", "heavy", "Thunderstorm", "⛈️", "⛈️");
    if (c === 96 || c === 99) return r("storm", "heavy", "Thunderstorm + hail", "⛈️", "⛈️");
    return r("clouds", "medium", "—", "☁️", "☁️");
  }

  // ---- US EPA AQI → label + color -----------------------------------------
  // https://www.airnow.gov/aqi/aqi-basics/ — standard six-band scale.
  function describeAqi(v) {
    if (v == null) return null;
    if (v <= 50)  return { label: "Good",                    color: "#16a34a" };
    if (v <= 100) return { label: "Moderate",                color: "#ca8a04" };
    if (v <= 150) return { label: "Unhealthy for sensitive", color: "#ea580c" };
    if (v <= 200) return { label: "Unhealthy",               color: "#dc2626" };
    if (v <= 300) return { label: "Very unhealthy",          color: "#9333ea" };
    return { label: "Hazardous", color: "#7f1d1d" };
  }

  // ---- API calls -----------------------------------------------------------
  function geocode(country, postal) {
    var url = "https://api.zippopotam.us/" + encodeURIComponent(country.toLowerCase()) +
              "/" + encodeURIComponent(postal);
    return fetch(url).then(function (res) {
      if (!res.ok) throw new Error("Unknown postal code");
      return res.json();
    }).then(function (data) {
      var place = data.places && data.places[0];
      if (!place) throw new Error("Unknown postal code");
      return {
        lat: parseFloat(place.latitude),
        lon: parseFloat(place.longitude),
        name: place["place name"] + (place["state abbreviation"] ? ", " + place["state abbreviation"] : "")
      };
    });
  }

  function fetchForecast(loc) {
    var unit = loc.unit === "fahrenheit" ? "fahrenheit" : "celsius";
    var url = "https://api.open-meteo.com/v1/forecast" +
      "?latitude=" + loc.lat + "&longitude=" + loc.lon +
      "&current=temperature_2m,weather_code,is_day" +
      "&daily=weather_code,temperature_2m_max,temperature_2m_min" +
      "&temperature_unit=" + unit +
      "&timezone=auto&forecast_days=5";
    return fetch(url).then(function (res) {
      if (!res.ok) throw new Error("Forecast unavailable");
      return res.json();
    });
  }

  // Air quality rides on the same lat/lon. Separate Open-Meteo host, no key,
  // returns a pre-computed US EPA AQI so we don't reimplement the breakpoints.
  function fetchAqi(loc) {
    var url = "https://air-quality-api.open-meteo.com/v1/air-quality" +
      "?latitude=" + loc.lat + "&longitude=" + loc.lon +
      "&current=us_aqi&timezone=auto";
    return fetch(url).then(function (res) {
      if (!res.ok) throw new Error("AQI unavailable");
      return res.json();
    });
  }

  // Cache key ties a forecast to one location; changing location invalidates it.
  function cacheKey(loc) { return loc.country + ":" + loc.postal + ":" + (loc.unit || "celsius"); }

  function loadForecast(loc, force) {
    var key = cacheKey(loc);
    var cached = readJSON(CACHE_KEY);
    var fresh = cached && cached.key === key && cached.fetchedAt &&
                (nowMs() - cached.fetchedAt) < TTL_MS;
    if (fresh && !force) return Promise.resolve(cached);
    return Promise.all([
      fetchForecast(loc),
      fetchAqi(loc).catch(function () { return null; })   // AQI is best-effort
    ]).then(function (results) {
      var data = results[0], air = results[1];
      var entry = {
        key: key,
        fetchedAt: nowMs(),
        current: data.current,
        daily: data.daily,
        unitSymbol: (data.current_units && data.current_units.temperature_2m) || "°",
        aqi: (air && air.current && typeof air.current.us_aqi === "number") ? air.current.us_aqi : null
      };
      writeJSON(CACHE_KEY, entry);
      return entry;
    }).catch(function (err) {
      // Stale-but-present beats nothing if the network blips.
      if (cached && cached.key === key) return cached;
      throw err;
    });
  }

  function nowMs() { return new Date().getTime(); }

  // ---- rendering -----------------------------------------------------------
  var chipEl = document.querySelector("[data-weather-chip]");
  var chipBtn = document.querySelector("[data-weather-loc]");
  var aqiChipEl = document.querySelector("[data-aqi-chip]");
  var cardMount = document.querySelector("[data-weather-card]");

  function setChip(html, title) {
    if (chipEl) chipEl.innerHTML = html;
    if (chipBtn && title) chipBtn.title = title;
  }

  // The AQI chip hides itself when there's no reading (no location, or the
  // air-quality fetch failed) so it never shows an empty pill.
  function setAqiChip(aqi) {
    if (!aqiChipEl) return;
    var air = describeAqi(aqi);
    if (!air) { aqiChipEl.classList.add("hidden"); aqiChipEl.classList.remove("inline-flex"); return; }
    aqiChipEl.innerHTML =
      '<span class="inline-block w-2.5 h-2.5 rounded-full" style="background:' + air.color + '"></span>' +
      'AQI ' + aqi;
    aqiChipEl.title = "Air quality · " + air.label;
    aqiChipEl.classList.remove("hidden");
    aqiChipEl.classList.add("inline-flex");
  }

  function temp(v, sym) { return Math.round(v) + (sym || "°"); }

  function weekday(iso) {
    var d = new Date(iso + "T00:00:00");
    return ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][d.getDay()];
  }

  // The /weather page is a variant preview — never let the live forecast
  // hijack the canvas the user is inspecting.
  var isDemoPage = location.pathname === "/weather";

  function applyBackground(desc) {
    if (isDemoPage) return;
    if (window.PeteWeather && typeof window.PeteWeather.set === "function") {
      window.PeteWeather.set(desc.variant, desc.intensity);
    }
  }

  function renderForecast(loc, entry) {
    var cur = entry.current;
    var sym = entry.unitSymbol;
    var desc = describe(cur.weather_code, cur.is_day);
    applyBackground(desc);

    setChip(desc.emoji + " <span class=\"tabular-nums\">" + temp(cur.temperature_2m, sym) + "</span>",
            loc.name + " · " + desc.label);
    setAqiChip(entry.aqi);

    if (!cardMount) return;
    var air = describeAqi(entry.aqi);
    var aqiRow = air
      ? '<div class="flex items-center gap-2 border-t border-[color:var(--ink)]/10 px-5 py-3 text-sm">' +
          '<span class="inline-block w-2.5 h-2.5 rounded-full shrink-0" style="background:' + air.color + '"></span>' +
          '<span class="font-bold tabular-nums">AQI ' + entry.aqi + '</span>' +
          '<span class="text-[color:var(--ink)]/60">' + air.label + '</span>' +
        '</div>'
      : '';
    var daily = entry.daily;
    var days = "";
    for (var i = 0; i < daily.time.length; i++) {
      var dd = describe(daily.weather_code[i], 1);
      days +=
        '<div class="flex flex-col items-center gap-1 rounded-2xl px-2 py-2 min-w-0">' +
          '<span class="text-xs font-semibold text-[color:var(--ink)]/60">' + (i === 0 ? "Today" : weekday(daily.time[i])) + '</span>' +
          '<span class="text-2xl leading-none" aria-hidden="true">' + dd.emoji + '</span>' +
          '<span class="text-sm font-bold tabular-nums">' + Math.round(daily.temperature_2m_max[i]) + '°</span>' +
          '<span class="text-xs tabular-nums text-[color:var(--ink)]/50">' + Math.round(daily.temperature_2m_min[i]) + '°</span>' +
        '</div>';
    }
    cardMount.innerHTML =
      '<div class="rounded-3xl bg-[color:var(--card)] border-2 border-[color:var(--ink)]/10 shadow-pete overflow-hidden">' +
        '<div class="flex flex-wrap items-center justify-between gap-4 p-5">' +
          '<div class="flex items-center gap-4 min-w-0">' +
            '<span class="text-5xl leading-none" aria-hidden="true">' + desc.emoji + '</span>' +
            '<div class="min-w-0">' +
              '<div class="flex items-baseline gap-2">' +
                '<span class="font-display text-4xl font-bold tabular-nums">' + temp(cur.temperature_2m, sym) + '</span>' +
                '<span class="text-sm font-semibold text-[color:var(--ink)]/70 truncate">' + desc.label + '</span>' +
              '</div>' +
              '<div class="text-sm text-[color:var(--ink)]/60 truncate">📍 ' + escapeHtml(loc.name) + '</div>' +
            '</div>' +
          '</div>' +
          '<button type="button" data-weather-loc class="shrink-0 rounded-full px-3 py-1.5 text-xs font-semibold text-[color:var(--ink)]/60 hover:bg-[color:var(--ink)]/5 border-2 border-[color:var(--ink)]/10 transition">Change location</button>' +
        '</div>' +
        '<div class="grid grid-cols-5 gap-1 border-t border-[color:var(--ink)]/10 bg-[color:var(--ink)]/[0.03] px-2 py-3">' + days + '</div>' +
        aqiRow +
      '</div>';
    // The injected "Change location" button needs the same handler.
    var changeBtn = cardMount.querySelector("[data-weather-loc]");
    if (changeBtn) changeBtn.addEventListener("click", openPopover);
  }

  function escapeHtml(s) {
    return String(s).replace(/[&<>"']/g, function (c) {
      return { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c];
    });
  }

  function showError(msg) {
    setChip("📍 Weather", "Set your location");
    setAqiChip(null);
    if (cardMount && getLoc()) {
      cardMount.innerHTML =
        '<div class="rounded-3xl bg-[color:var(--card)] border-2 border-dashed border-[color:var(--ink)]/15 p-5 text-center text-sm text-[color:var(--ink)]/60">' +
          escapeHtml(msg) + ' · <button type="button" data-weather-loc class="font-semibold underline">try again</button>' +
        '</div>';
      var b = cardMount.querySelector("[data-weather-loc]");
      if (b) b.addEventListener("click", openPopover);
    }
  }

  // ---- location popover ----------------------------------------------------
  // Built in JS so layout.html only needs the chip button. Class strings are
  // full literals so Tailwind's content scan keeps them.
  var popover = null;
  var selectedUnit = "celsius"; // default for everyone; °F is opt-in

  function setUnit(u) {
    selectedUnit = u === "fahrenheit" ? "fahrenheit" : "celsius";
    if (!popover) return;
    popover.querySelectorAll("[data-loc-unit]").forEach(function (b) {
      var on = b.getAttribute("data-loc-unit") === selectedUnit;
      b.classList.toggle("bg-[color:var(--accent)]", on);
      b.classList.toggle("text-white", on);
      b.classList.toggle("text-[color:var(--ink)]/60", !on);
    });
  }

  function defaultCountry() {
    var lang = (navigator.language || "en-US");
    var m = lang.match(/[-_]([A-Za-z]{2})$/);
    return m ? m[1].toUpperCase() : "US";
  }

  function buildPopover() {
    var dlg = document.createElement("div");
    dlg.id = "pete-weather-loc";
    dlg.className = "hidden fixed inset-0 z-50 bg-[color:var(--ink)]/40 backdrop-blur-sm flex items-start justify-center pt-[12vh] px-4";
    dlg.setAttribute("role", "dialog");
    dlg.setAttribute("aria-modal", "true");
    dlg.innerHTML =
      '<div class="w-full max-w-sm rounded-3xl bg-[color:var(--card)] shadow-pete-lg border-2 border-[color:var(--ink)]/10 overflow-hidden">' +
        '<div class="flex items-center justify-between gap-3 px-5 py-4 border-b border-[color:var(--ink)]/10">' +
          '<h2 class="font-display text-lg font-bold">Weather location</h2>' +
          '<button type="button" data-loc-close class="rounded-full px-2 py-1 text-sm text-[color:var(--ink)]/60 hover:bg-[color:var(--ink)]/5">esc</button>' +
        '</div>' +
        '<div class="p-5 space-y-3">' +
          '<p class="text-xs text-[color:var(--ink)]/60">Enter your postal code to see the local forecast. The background follows the weather. Saved in this browser only.</p>' +
          '<div class="flex gap-2">' +
            '<select data-loc-country class="rounded-2xl border-2 border-[color:var(--ink)]/10 bg-[color:var(--bg)] px-3 py-2 text-sm font-semibold outline-none focus:border-[color:var(--accent)]"></select>' +
            '<input data-loc-postal type="text" inputmode="text" autocomplete="postal-code" placeholder="Postal code" class="flex-1 min-w-0 rounded-2xl border-2 border-[color:var(--ink)]/10 bg-[color:var(--bg)] px-3 py-2 text-sm outline-none focus:border-[color:var(--accent)]">' +
          '</div>' +
          '<div class="flex items-center justify-between gap-2">' +
            '<span class="text-xs font-semibold text-[color:var(--ink)]/60">Units</span>' +
            '<div class="inline-flex rounded-full border-2 border-[color:var(--ink)]/10 p-0.5 text-xs font-bold">' +
              '<button type="button" data-loc-unit="celsius" class="rounded-full px-3 py-1 transition">°C</button>' +
              '<button type="button" data-loc-unit="fahrenheit" class="rounded-full px-3 py-1 transition">°F</button>' +
            '</div>' +
          '</div>' +
          '<p data-loc-error class="hidden text-xs font-semibold text-red-500"></p>' +
          '<div class="flex items-center justify-between gap-2 pt-1">' +
            '<button type="button" data-loc-clear class="rounded-full px-3 py-2 text-xs font-semibold text-[color:var(--ink)]/50 hover:text-[color:var(--ink)]">Clear</button>' +
            '<button type="button" data-loc-save class="rounded-full bg-[color:var(--accent)] px-5 py-2 text-sm font-bold text-white shadow-pete hover:brightness-105 transition disabled:opacity-50">Save</button>' +
          '</div>' +
        '</div>' +
      '</div>';
    document.body.appendChild(dlg);

    // A small, friendly country list (Zippopotam supports ~60). Common ones up
    // top; the user can type any postal code that matches their selected country.
    var countries = [
      ["US", "🇺🇸 United States"], ["PT", "🇵🇹 Portugal"], ["GB", "🇬🇧 United Kingdom"],
      ["ES", "🇪🇸 Spain"], ["FR", "🇫🇷 France"], ["DE", "🇩🇪 Germany"], ["IT", "🇮🇹 Italy"],
      ["NL", "🇳🇱 Netherlands"], ["BE", "🇧🇪 Belgium"], ["CH", "🇨🇭 Switzerland"],
      ["AT", "🇦🇹 Austria"], ["IE", "🇮🇪 Ireland"], ["DK", "🇩🇰 Denmark"],
      ["SE", "🇸🇪 Sweden"], ["PL", "🇵🇱 Poland"], ["CZ", "🇨🇿 Czechia"],
      ["CA", "🇨🇦 Canada"], ["AU", "🇦🇺 Australia"], ["NZ", "🇳🇿 New Zealand"],
      ["BR", "🇧🇷 Brazil"], ["MX", "🇲🇽 Mexico"], ["JP", "🇯🇵 Japan"], ["IN", "🇮🇳 India"]
    ];
    var sel = dlg.querySelector("[data-loc-country]");
    countries.forEach(function (c) {
      var o = document.createElement("option");
      o.value = c[0]; o.textContent = c[1];
      sel.appendChild(o);
    });

    dlg.addEventListener("click", function (e) { if (e.target === dlg) closePopover(); });
    dlg.querySelector("[data-loc-close]").addEventListener("click", closePopover);
    dlg.querySelector("[data-loc-clear]").addEventListener("click", function () {
      setLoc(null);
      try { localStorage.removeItem(CACHE_KEY); } catch (e) {}
      closePopover();
      // Hand the background back to the server's seasonal default.
      if (window.PeteWeather) window.PeteWeather.set(seasonalDefault.variant, seasonalDefault.intensity);
      setChip("📍 Weather", "Set your location");
      setAqiChip(null);
      if (cardMount) cardMount.innerHTML = "";
    });
    dlg.querySelectorAll("[data-loc-unit]").forEach(function (b) {
      b.addEventListener("click", function () { setUnit(b.getAttribute("data-loc-unit")); });
    });
    dlg.querySelector("[data-loc-save]").addEventListener("click", onSave);
    dlg.querySelector("[data-loc-postal]").addEventListener("keydown", function (e) {
      if (e.key === "Enter") onSave();
    });
    return dlg;
  }

  function showLocError(msg) {
    var el = popover.querySelector("[data-loc-error]");
    if (!el) return;
    if (msg) { el.textContent = msg; el.classList.remove("hidden"); }
    else { el.classList.add("hidden"); }
  }

  function onSave() {
    var country = popover.querySelector("[data-loc-country]").value;
    var postal = popover.querySelector("[data-loc-postal]").value.trim();
    var saveBtn = popover.querySelector("[data-loc-save]");
    if (!postal) { showLocError("Enter a postal code."); return; }
    showLocError("");
    saveBtn.disabled = true; saveBtn.textContent = "…";
    geocode(country, postal).then(function (geo) {
      var loc = {
        country: country, postal: postal,
        lat: geo.lat, lon: geo.lon, name: geo.name,
        unit: selectedUnit
      };
      setLoc(loc);
      try { localStorage.removeItem(CACHE_KEY); } catch (e) {}
      closePopover();
      run(true);
    }).catch(function (err) {
      showLocError((err && err.message) || "Couldn't find that postal code.");
    }).then(function () {
      saveBtn.disabled = false; saveBtn.textContent = "Save";
    });
  }

  function openPopover() {
    if (!popover) popover = buildPopover();
    var loc = getLoc();
    popover.querySelector("[data-loc-country]").value = loc ? loc.country : defaultCountry();
    popover.querySelector("[data-loc-postal]").value = loc ? loc.postal : "";
    setUnit(loc && loc.unit ? loc.unit : "celsius");
    showLocError("");
    popover.classList.remove("hidden");
    var inp = popover.querySelector("[data-loc-postal]");
    if (inp) setTimeout(function () { inp.focus(); }, 30);
  }
  function closePopover() {
    if (popover) popover.classList.add("hidden");
  }

  // ---- boot ----------------------------------------------------------------
  // Remember the server's seasonal pick so "Clear" can restore it.
  var seasonalDefault = {
    variant: document.documentElement.dataset.weather || "",
    intensity: document.documentElement.dataset.intensity || "heavy"
  };

  function run(force) {
    var loc = getLoc();
    if (!loc) { setChip("📍 Weather", "Set your location"); return; }
    setChip("📍 " + escapeHtml(loc.name.split(",")[0]) + "…", "Loading forecast…");
    loadForecast(loc, force)
      .then(function (entry) { renderForecast(loc, entry); })
      .catch(function () { showError("Forecast unavailable"); });
  }

  document.addEventListener("DOMContentLoaded", function () {
    if (chipBtn) chipBtn.addEventListener("click", openPopover);
    document.addEventListener("keydown", function (e) {
      if (e.key === "Escape") closePopover();
    });
    run(false);
  });
})();
