package contentapi

import "encoding/json"

// Payload types for the backend's /content/* endpoints, shaped after the
// captured fixtures in frontend/tests/fixtures/*.json and the field accesses in
// content.py + templates/*.j2. The backend (backend/api/content_payloads.py,
// backend/api/homepage.py) owns these shapes — page_title / canonical_path /
// breadcrumb / jsonld are all computed there, never here.
//
// Conventions:
//   - *float64 / *string / *int for anything the Python checked `is not None`
//     or that the fixtures show as nullable.
//   - json.RawMessage for jsonld: the Python re-serialized the parsed object
//     with separators=(",", ":") and dict order preserved — in Go, re-marshaling
//     a map would SORT the keys and break golden-diff parity, so keep the raw
//     bytes and compact them (json.Compact) at render time instead.
//   - json.Number for values templates print raw (home ranked lat/lon): it
//     preserves the backend's exact decimal text, dodging any float-formatting
//     drift between Python's repr and Go's strconv.

// Crumb is one breadcrumb element; the terminal crumb has a null href.
type Crumb struct {
	Name string  `json:"name"`
	Href *string `json:"href"`
}

// --- /content/hub -----------------------------------------------------------

type HubPayload struct {
	NCities    int          `json:"n_cities"`
	NCountries int          `json:"n_countries"`
	Countries  []HubCountry `json:"countries"`
}

type HubCountry struct {
	Country string    `json:"country"`
	Cities  []HubCity `json:"cities"`
}

type HubCity struct {
	Slug    string `json:"slug"`
	Name    string `json:"name"`
	Display string `json:"display"`
}

// --- /content/sitemap -------------------------------------------------------

type SitemapEntry struct {
	Path       string `json:"path"`
	Changefreq string `json:"changefreq"`
	Priority   string `json:"priority"`
}

// --- /content/indexnow-key --------------------------------------------------

type indexNowPayload struct {
	Key string `json:"key"`
}

// --- /content/home ----------------------------------------------------------

type HomePayload struct {
	Unusual *HomeUnusual `json:"unusual"`
	Stale   bool         `json:"stale"`
	Ranked  []HomeRanked `json:"ranked"`
	Cities  []HomeCity   `json:"cities"`
}

// HomeUnusual is the hero card's pick (home.html.j2). Built by
// backend/api/homepage.py's _grade_city + the is_default flag.
type HomeUnusual struct {
	Cls         string  `json:"cls"`
	Display     string  `json:"display"`
	Grade       string  `json:"grade"`
	MetricLabel string  `json:"metric_label"`
	Percentile  float64 `json:"percentile"` // rendered via the ordinal filter
	WindowLabel string  `json:"window_label"`
	IsDefault   bool    `json:"is_default"`
	Date        string  `json:"date"`
}

// HomeRanked is one "Unusual right now" strip card.
type HomeRanked struct {
	Cls     string `json:"cls"`
	Display string `json:"display"`
	// Lat/Lon are printed verbatim into the /day#lat=…&lon=… fragment —
	// json.Number keeps the backend's exact text (round(x, 4) in Python).
	Lat         json.Number `json:"lat"`
	Lon         json.Number `json:"lon"`
	Value       *float64    `json:"value"` // temp(r.value)
	GradeLabel  string      `json:"grade_label"`
	MetricLabel string      `json:"metric_label"`
	PctLabel    string      `json:"pct_label"`
	Normal      *float64    `json:"normal"`     // `is not none` in the template
	DateLabel   *string     `json:"date_label"` // null when the reading is today's
}

type HomeCity struct {
	Slug string `json:"slug"`
	Name string `json:"name"`
}

// --- /content/city/{slug} ---------------------------------------------------

type CityPayload struct {
	City             CityInfo        `json:"city"`
	Display          string          `json:"display"`
	Title            string          `json:"title"`
	YearRange        []int           `json:"year_range"`
	NYears           int             `json:"n_years"`
	Months           []CityMonth     `json:"months"`
	WarmestMonthSlug string          `json:"warmest_month_slug"`
	ColdestMonthSlug string          `json:"coldest_month_slug"`
	WettestMonthSlug string          `json:"wettest_month_slug"`
	AllTimeRecords   AllTimeRecords  `json:"all_time_records"`
	TodayVsNormal    *TodayVsNormal  `json:"today_vs_normal"`
	Flavor           *Flavor         `json:"flavor"`
	Event            *Event          `json:"event"`
	DefaultUnit      string          `json:"default_unit"`
	Breadcrumb       []Crumb         `json:"breadcrumb"`
	CanonicalPath    string          `json:"canonical_path"`
	PageTitle        string          `json:"page_title"`
	PageDescription  string          `json:"page_description"`
	JSONLD           json.RawMessage `json:"jsonld"`
}

type CityInfo struct {
	Slug        string  `json:"slug"`
	Name        string  `json:"name"`
	Admin1      string  `json:"admin1"`
	Country     string  `json:"country"`
	CountryCode string  `json:"country_code"`
	Lat         float64 `json:"lat"` // formatted %.5f / %.4f by the handlers
	Lon         float64 `json:"lon"`
	Population  int     `json:"population"`
}

type CityMonth struct {
	Name     string   `json:"name"`
	Slug     string   `json:"slug"`
	HighF    *float64 `json:"high_f"`
	LowF     *float64 `json:"low_f"`
	RangeLoF *float64 `json:"range_lo_f"`
	RangeHiF *float64 `json:"range_hi_f"`
	PrecipF  *float64 `json:"precip_f"`
}

// AllTimeRecords maps metric -> extremes; templates access fields by name
// (records.tmax.max …) and nil-check each metric, so pointers, not a map.
type AllTimeRecords struct {
	Tmax   *MetricRecord `json:"tmax"`
	Tmin   *MetricRecord `json:"tmin"`
	Feels  *MetricRecord `json:"feels"`
	Fmax   *MetricRecord `json:"fmax"`
	Fmin   *MetricRecord `json:"fmin"`
	Humid  *MetricRecord `json:"humid"`
	Wind   *MetricRecord `json:"wind"`
	Gust   *MetricRecord `json:"gust"`
	Precip *MetricRecord `json:"precip"`
}

type MetricRecord struct {
	Max     *float64 `json:"max"`
	MaxDate string   `json:"max_date"`
	Min     *float64 `json:"min"`
	MinDate string   `json:"min_date"`
}

type TodayVsNormal struct {
	Date  string      `json:"date"`
	Cards []TodayCard `json:"cards"`
}

type TodayCard struct {
	Label      string   `json:"label"`
	Metric     string   `json:"metric"` // key into format's fmt() dispatch
	ValueF     *float64 `json:"value_f"`
	Percentile *float64 `json:"percentile"` // `is not none` in city.html.j2
	Grade      string   `json:"grade"`
	Cls        string   `json:"cls"`
}

type Flavor struct {
	Extract string `json:"extract"`
	Title   string `json:"title"`
	URL     string `json:"url"`
}

type Event struct {
	Text string `json:"text"`
	URL  string `json:"url"`
}

// --- /content/city/{slug}/month/{month} -------------------------------------

type MonthPayload struct {
	MonthName         string        `json:"month_name"`
	MonthSlug         string        `json:"month_slug"`
	YearRange         []int         `json:"year_range"`
	NYears            int           `json:"n_years"`
	AvgHighF          *float64      `json:"avg_high_f"`
	AvgLowF           *float64      `json:"avg_low_f"`
	TypicalHighRangeF []*float64    `json:"typical_high_range_f"` // [lo, hi]
	TypicalLowRangeF  []*float64    `json:"typical_low_range_f"`
	AvgPrecipF        *float64      `json:"avg_precip_f"`
	Records           []MonthRecord `json:"records"`
	Prev              *MonthLink    `json:"prev"`
	Next              *MonthLink    `json:"next"`
	// Display is optional in the payload; content.py fell back to the city
	// name (j.get("display", city["name"])) — nil means "use the fallback".
	Display         *string `json:"display"`
	Breadcrumb      []Crumb `json:"breadcrumb"`
	CanonicalPath   string  `json:"canonical_path"`
	PageTitle       string  `json:"page_title"`
	PageDescription string  `json:"page_description"`
}

type MonthRecord struct {
	Label    string   `json:"label"` // maps to a metric via the display layer's label table
	HighF    *float64 `json:"high_f"`
	HighDate string   `json:"high_date"`
	HighTag  string   `json:"high_tag"`
	LowF     *float64 `json:"low_f"`
	LowDate  string   `json:"low_date"`
	LowTag   string   `json:"low_tag"`
}

type MonthLink struct {
	Name string `json:"name"`
	Slug string `json:"slug"`
}

// --- /content/city/{slug}/records -------------------------------------------

type RecordsPayload struct {
	YearRange       []int           `json:"year_range"`
	NYears          int             `json:"n_years"`
	Rows            []RecordRow     `json:"rows"`
	AllTimeRecords  AllTimeRecords  `json:"all_time_records"`
	Monthly         []PeriodRecords `json:"monthly"`
	Seasonal        []PeriodRecords `json:"seasonal"`
	Hemisphere      string          `json:"hemisphere"`
	Display         *string         `json:"display"` // same fallback as MonthPayload.Display
	Breadcrumb      []Crumb         `json:"breadcrumb"`
	CanonicalPath   string          `json:"canonical_path"`
	PageTitle       string          `json:"page_title"`
	PageDescription string          `json:"page_description"`
	JSONLD          json.RawMessage `json:"jsonld"`
}

type RecordRow struct {
	Label         string   `json:"label"`
	HighF         *float64 `json:"high_f"`
	HighDate      string   `json:"high_date"`
	LowF          *float64 `json:"low_f"`
	LowDate       *string  `json:"low_date"` // content.py: r["low_date"] or "—"
	DryStreakDays *int     `json:"dry_streak_days"`
}

// PeriodRecords is one monthly or seasonal block; Slug is set for monthly
// entries, Span for seasonal ones.
type PeriodRecords struct {
	Name string     `json:"name"`
	Slug string     `json:"slug,omitempty"`
	Span string     `json:"span,omitempty"`
	High PeriodSide `json:"high"`
	Low  PeriodSide `json:"low"`
}

// PeriodSide is the {'warm': …|None, 'cold': …|None} pair records.html.j2's
// extremes() macro reads (after display formatting).
type PeriodSide struct {
	Warm *Extreme `json:"warm"`
	Cold *Extreme `json:"cold"`
}

type Extreme struct {
	ValueF float64 `json:"value_f"`
	Date   string  `json:"date"`
}
