"""Access to the curated city set (backend/cities.json) that gets crawlable
climate pages. Loaded once, lazily; regenerate the JSON with gen_cities.py."""
import json
import os

import paths

_PATH = paths.CITIES_JSON
_FLAVOR_PATH = paths.CITIES_FLAVOR_JSON
_CITIES: list[dict] | None = None
_BY_SLUG: dict[str, dict] | None = None
_FLAVOR: dict[str, dict] | None = None
_DUPES: dict[str, dict[str, int]] | None = None


def _load() -> list[dict]:
    global _CITIES, _BY_SLUG
    if _CITIES is None:
        with open(_PATH, encoding="utf-8") as f:
            _CITIES = json.load(f)
        _BY_SLUG = {c["slug"]: c for c in _CITIES}
    return _CITIES


def all_cities() -> list[dict]:
    return _load()


def all_slugs() -> list[str]:
    return [c["slug"] for c in _load()]


def get(slug: str) -> dict | None:
    """The city for a slug, or None (→ 404)."""
    _load()
    return _BY_SLUG.get(slug)


def flavor(slug: str) -> dict | None:
    """A city's descriptive blurb {extract, url, title} from cities_flavor.json, or
    None when we have no confident match (the page renders fine without it)."""
    global _FLAVOR
    if _FLAVOR is None:
        try:
            with open(_FLAVOR_PATH, encoding="utf-8") as f:
                _FLAVOR = json.load(f)
        except (OSError, ValueError):
            _FLAVOR = {}
    return _FLAVOR.get(slug)


def title_name(city: dict) -> str:
    """The shortest label that still names this city unambiguously — for page
    titles, where display_name()'s region + country spend the width Google gives
    us before it ever reaches the payload ("… in July").

    'Seattle' for the 968 cities whose name is unique; 'London, GB' when the name
    recurs in another country; 'Columbus, Ohio' when it recurs *within* one
    country, where the country code would collide too and hand two different
    pages the same title.
    """
    name = city["name"]
    dupes = _dupe_names().get(name)
    if not dupes:
        return name
    if dupes.get(city.get("country_code")) == 1:
        return f"{name}, {city['country_code']}"
    return f"{name}, {city['admin1']}" if city.get("admin1") else name


def _dupe_names() -> dict[str, dict[str, int]]:
    """{name: {country_code: how many cities share both}} for duplicated names
    only. Built once alongside the city list."""
    global _DUPES
    if _DUPES is None:
        counts: dict[str, dict[str, int]] = {}
        for c in _load():
            counts.setdefault(c["name"], {})
            cc = c.get("country_code")
            counts[c["name"]][cc] = counts[c["name"]].get(cc, 0) + 1
        _DUPES = {n: by_cc for n, by_cc in counts.items() if sum(by_cc.values()) > 1}
    return _DUPES


def display_name(city: dict) -> str:
    """Human label: 'Seattle, Washington, United States' (drops repeated admin1)."""
    parts = [city["name"]]
    if city.get("admin1") and city["admin1"] != city["name"]:
        parts.append(city["admin1"])
    if city.get("country"):
        parts.append(city["country"])
    return ", ".join(parts)


def by_country() -> dict[str, list[dict]]:
    """Cities grouped by country, both the countries and the cities within each
    ordered alphabetically — for the /climate hub's crawlable link graph + search."""
    groups: dict[str, list[dict]] = {}
    for c in _load():
        key = c.get("country") or c.get("country_code") or "Other"
        groups.setdefault(key, []).append(c)
    for v in groups.values():
        v.sort(key=lambda x: x["name"].lower())
    return dict(sorted(groups.items(), key=lambda kv: kv[0].lower()))
