// Data layer for The Atmosphere
// All data loaded from real GHCN pipeline output.

// ------- Color scale (RdBu diverging) ----------
const RdBu = [
  { stop: -5, color: '#08306B' },
  { stop: -3, color: '#2166AC' },
  { stop: -1.5, color: '#4393C3' },
  { stop: -0.5, color: '#74ADD1' },
  { stop: 0, color: '#F7F7F7' },
  { stop: 0.5, color: '#FDAE61' },
  { stop: 1.5, color: '#F46D43' },
  { stop: 3, color: '#D73027' },
  { stop: 5, color: '#A50026' },
];
function lerpColor(a, b, t) {
  const pa = parseInt(a.slice(1), 16), pb = parseInt(b.slice(1), 16);
  const ar = (pa>>16)&255, ag = (pa>>8)&255, ab = pa&255;
  const br = (pb>>16)&255, bg = (pb>>8)&255, bb = pb&255;
  const r = Math.round(ar+(br-ar)*t), g = Math.round(ag+(bg-ag)*t), b2 = Math.round(ab+(bb-ab)*t);
  return `rgb(${r},${g},${b2})`;
}
function anomalyColor(v) {
  for (let i = 0; i < RdBu.length - 1; i++) {
    if (v >= RdBu[i].stop && v <= RdBu[i+1].stop) {
      const t = (v - RdBu[i].stop) / (RdBu[i+1].stop - RdBu[i].stop);
      return lerpColor(RdBu[i].color, RdBu[i+1].color, t);
    }
  }
  return v < -5 ? '#08306B' : '#A50026';
}

// ------- Placeholders — overwritten by real data loader ----------
let annualAnomaly = [];
let BASELINE_STATS = [];
let STATIONS = [];
let STRANGEST = { default: { city: 'Loading…', station: '—', date: '—', sigma: 0, high: 0, avgHigh: 0, recordYears: 0, headline: '', note: '', distMean: 0, distSd: 1, point: 0 } };
let RATIO_BY_DECADE = [];
let RETURN_PERIOD = { threshold: 105, city: 'Loading…', byYear: [] };
let EVENT_CLUSTERS = [];

function strangestLookup(q) {
  return STRANGEST.default;
}

// ------- Synth series helper (for station annual charts when timeline data unavailable) ----------
function synthSeries(start, end, trendPerYear, dustBowlAmp, noiseAmp) {
  const out = [];
  for (let y = start; y <= end; y++) {
    const t = y - start;
    let v = -1.0 + trendPerYear * t;
    if (y === 1936) v += dustBowlAmp * 1.4;
    if (y === 1934) v += dustBowlAmp;
    if (y >= 1960 && y <= 1978) v -= 0.4;
    if (y === 2012 || y === 2016 || y === 2023 || y === 2024) v += 0.9;
    const r = Math.sin(y * 31.7 + start) * 1000;
    v += (r - Math.floor(r) - 0.5) * 2 * noiseAmp;
    out.push({ year: y, anomaly: +v.toFixed(2) });
  }
  return out;
}

// ------- Map grid geometry for The Fronts ----------
function mulberry(a) {
  return function() {
    a |= 0; a = a + 0x6D2B79F5 | 0;
    let t = Math.imul(a ^ a >>> 15, 1 | a);
    t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
    return ((t ^ t >>> 14) >>> 0) / 4294967296;
  };
}
const GRID_POINTS = (() => {
  const pts = [];
  const rand = mulberry(7);
  const inUS = (x, y) => {
    if (y < 0.18 || y > 0.78) return false;
    if (x < 0.06 || x > 0.94) return false;
    if (x < 0.18 && y > 0.62) return false;
    if (y > 0.68 && x > 0.36 && x < 0.58) return false;
    if (y > 0.72 && (x < 0.74 || x > 0.86)) return false;
    if (y > 0.78) return false;
    if (x > 0.92 && y < 0.28) return false;
    return true;
  };
  let tries = 0;
  while (pts.length < 90 && tries < 3000) {
    const x = rand(), y = rand();
    if (inUS(x, y)) pts.push({ x, y });
    tries++;
  }
  return pts;
})();

// Decade definitions for the Fronts map
const DECADES = [
  { label: '1900s', year: 1905, bias: 0, pattern: 'flat' },
  { label: '1910s', year: 1915, bias: 0, pattern: 'flat' },
  { label: '1920s', year: 1925, bias: 0, pattern: 'flat' },
  { label: '1930s', year: 1935, bias: 0, pattern: 'flat' },
  { label: '1940s', year: 1945, bias: 0, pattern: 'flat' },
  { label: '1950s', year: 1955, bias: 0, pattern: 'flat' },
  { label: '1960s', year: 1965, bias: 0, pattern: 'flat' },
  { label: '1970s', year: 1975, bias: 0, pattern: 'flat' },
  { label: '1980s', year: 1985, bias: 0, pattern: 'flat' },
  { label: '1990s', year: 1995, bias: 0, pattern: 'flat' },
  { label: '2000s', year: 2005, bias: 0, pattern: 'flat' },
  { label: '2010s', year: 2015, bias: 0, pattern: 'flat' },
  { label: '2020s', year: 2024, bias: 0, pattern: 'flat' },
];

function decadeField(d, rngSeed) {
  const r = mulberry(rngSeed);
  return GRID_POINTS.map(p => {
    let v = d.bias + (r() - 0.5) * 1.0;
    return { ...p, v: +v.toFixed(2) };
  });
}

// ------- Title case helper ----------
function titleCase(s) {
  if (!s) return '';
  // First, do standard title case
  let r = s.toLowerCase().replace(/(?:^|\s|[-/])\w/g, m => m.toUpperCase());
  // Preserve common abbreviations
  const abbrevs = {
    'Ap': 'AP', 'Np': 'NP', 'Wnw': 'WNW', 'Sse': 'SSE', 'Nne': 'NNE',
    'Nws': 'NWS', 'Wsw': 'WSW', 'Ene': 'ENE', 'Ese': 'ESE', 'Nnw': 'NNW',
    'Ssw': 'SSW', 'Ny': 'NY', 'Nj': 'NJ', 'Mn': 'MN', 'Mi': 'MI',
    'Cntrl': 'Central', 'Intl': 'Intl', 'Rsch': 'Research', 'Coop': 'Observatory',
    'Wfo': 'WFO', 'Faa': 'FAA', 'Asos': 'ASOS', 'Awos': 'AWOS',
  };
  for (const [from, to] of Object.entries(abbrevs)) {
    r = r.replace(new RegExp(`\\b${from}\\b`, 'g'), to);
  }
  return r;
}

// ------- Expose to window ----------
Object.assign(window, {
  RdBu, anomalyColor, lerpColor,
  annualAnomaly, BASELINE_STATS,
  STATIONS, STRANGEST, strangestLookup,
  GRID_POINTS, DECADES, decadeField,
  EVENT_CLUSTERS, RATIO_BY_DECADE, RETURN_PERIOD,
});

// =====================================================================
// REAL DATA LOADER — fetches pipeline JSON and populates all globals
// =====================================================================
const DATA_BASE = 'data';

async function fetchJSON(file) {
  try {
    const r = await fetch(`${DATA_BASE}/${file}`);
    if (!r.ok) return null;
    return await r.json();
  } catch { return null; }
}

async function loadRealData() {
  const [meta, annual, ratio, keepers, timelines, returnP, strangest, events] = await Promise.all([
    fetchJSON('meta.json'),
    fetchJSON('annual_anomaly.json'),
    fetchJSON('record_ratio.json'),
    fetchJSON('record_keepers.json'),
    fetchJSON('station_timelines.json'),
    fetchJSON('return_periods.json'),
    fetchJSON('strangest_search.json'),
    fetchJSON('extreme_events.json'),
  ]);

  // --- Annual anomaly ---
  if (annual && annual.length > 0) {
    const filtered = annual.filter(d => d.year >= 1900);
    window.annualAnomaly = filtered.map(d => ({ year: d.year, anomaly: +(d.mean_anomaly_f || 0).toFixed(2) }));

    // Compute real decade biases for the Fronts map from annual data
    const decadeMeans = {};
    for (const d of filtered) {
      const dec = Math.floor(d.year / 10) * 10;
      if (!decadeMeans[dec]) decadeMeans[dec] = { sum: 0, n: 0 };
      decadeMeans[dec].sum += d.mean_anomaly_f || 0;
      decadeMeans[dec].n++;
    }
    for (const dec of window.DECADES) {
      const y = parseInt(dec.label);
      if (decadeMeans[y]) {
        dec.bias = +(decadeMeans[y].sum / decadeMeans[y].n).toFixed(2);
      }
    }
    console.log(`[Atmosphere] Loaded real annual anomaly: ${filtered.length} years`);
  }

  // --- Meta / stats ---
  if (meta && meta.stats) {
    const s = meta.stats;
    window.BASELINE_STATS = [
      { label: 'STATIONS ANALYZED', value: (s.stations_analyzed || 1218).toLocaleString(), sub: 'GHCN-Daily HCN, ≥30 yrs of record' },
      { label: 'YEARS OF RECORD', value: s.years_of_record || '1853–2026', sub: 'Continental US' },
      { label: 'HOTTEST DAY ON RECORD', value: `${Math.round(s.hottest_day?.temp_f || 130)}°F`, sub: `${titleCase(s.hottest_day?.station || '—')}, ${s.hottest_day?.state || ''} · ${s.hottest_day?.date || ''}` },
      { label: 'COLDEST DAY ON RECORD', value: `${Math.round(s.coldest_day?.temp_f || -66)}°F`, sub: `${titleCase(s.coldest_day?.station || '—')}, ${s.coldest_day?.state || ''} · ${s.coldest_day?.date || ''}` },
      { label: 'HOT vs COLD RECORDS (2020s)', value: s.hot_cold_ratio_2020s || '—', sub: 'ratio of new daily highs to lows' },
      { label: 'CONSECUTIVE YEARS ABOVE BASELINE', value: String(s.consecutive_years_above_baseline || '—'), sub: 'annual mean above 1900–1950 average' },
    ];
    console.log('[Atmosphere] Loaded real meta stats');
  }

  // --- Record ratio ---
  if (ratio && ratio.length > 0) {
    const byDecade = {};
    ratio.forEach(d => {
      const dec = Math.floor(d.year / 10) * 10 + 's';
      if (!byDecade[dec]) byDecade[dec] = { hot: 0, cold: 0 };
      byDecade[dec].hot += d.hot_records || 0;
      byDecade[dec].cold += d.cold_records || 0;
    });
    window.RATIO_BY_DECADE = Object.keys(byDecade).sort()
      .filter(d => d >= '1920s' && d <= '2020s')
      .map(d => {
        const total = byDecade[d].hot + byDecade[d].cold;
        return {
          decade: d,
          hot: total > 0 ? +(byDecade[d].hot / (total / 2)).toFixed(2) : 1,
          cold: total > 0 ? +(byDecade[d].cold / (total / 2)).toFixed(2) : 1,
        };
      });
    console.log(`[Atmosphere] Loaded real record ratio: ${window.RATIO_BY_DECADE.length} decades`);
  }

  // --- Record keepers / stations ---
  if (keepers && keepers.length > 0) {
    window.STATIONS = keepers.slice(0, 8).map(s => {
      let annual = [];
      if (timelines && timelines[s.station_id]) {
        const raw = timelines[s.station_id].annual_means;
        // Convert raw mean temps to anomalies by subtracting station baseline mean
        const vals = raw.map(([y, v]) => v).filter(v => v != null);
        const stationMean = vals.length > 0 ? vals.reduce((a, b) => a + b, 0) / vals.length : 0;
        annual = raw.map(([y, v]) => ({ year: y, anomaly: v != null ? +((v - stationMean)).toFixed(2) : 0 }));
      } else {
        annual = synthSeries(s.year_start, s.year_end, (s.trend_f_per_century || 2) / 100, 0.5, 0.2);
      }
      const name = titleCase(s.name);
      return {
        id: s.station_id,
        name,
        state: s.state || '',
        lat: s.lat,
        lon: s.lon,
        since: s.year_start,
        extreme: {
          label: 'Record high',
          date: `${s.hottest_year}`,
          value: `${Math.round(s.hottest_tmax_f)}°F`,
        },
        note: `${s.n_years} years of continuous record. Trend: ${s.trend_f_per_century > 0 ? '+' : ''}${s.trend_f_per_century}°F/century.`,
        annual,
      };
    });
    console.log(`[Atmosphere] Loaded real station profiles: ${window.STATIONS.length}`);
  }

  // --- Strangest day search ---
  if (strangest && strangest.length > 0) {
    const realStrangest = {};
    strangest.forEach(s => {
      const key = (s.name || '').toLowerCase().replace(/[^a-z ]/g, '').trim();
      const prettyName = titleCase(s.name);
      const entry = {
        city: `${prettyName}${s.state ? ', ' + s.state : ''}`,
        station: `${s.id} · ${prettyName}`,
        date: s.strangest.date,
        sigma: s.strangest.anomaly_score || 0,
        high: Math.round(s.strangest.tmax_f),
        avgHigh: Math.round(s.strangest.baseline_mean),
        recordYears: s.strangest.n_years,
        headline: `${(s.strangest.anomaly_score || 0).toFixed(1)}σ from the historical distribution for this date.`,
        note: `The most statistically unusual day at this station in ${s.strangest.n_years} years of record.`,
        distMean: Math.round(s.strangest.baseline_mean),
        distSd: Math.max(3, Math.abs(s.strangest.tmax_f - s.strangest.baseline_mean) / Math.max(s.strangest.anomaly_score, 0.5)),
        point: Math.round(s.strangest.tmax_f),
      };
      realStrangest[key] = entry;
    });
    // Set default to first entry
    const firstKey = Object.keys(realStrangest)[0];
    window.STRANGEST = { default: realStrangest[firstKey], ...realStrangest };
    window.strangestLookup = (q) => {
      const k = (q || '').toLowerCase().trim();
      for (const key of Object.keys(realStrangest)) {
        if (k && key.includes(k)) return realStrangest[key];
      }
      return realStrangest[firstKey];
    };
    console.log(`[Atmosphere] Loaded real strangest days: ${Object.keys(realStrangest).length} stations`);
  }

  // --- Return periods ---
  if (returnP && returnP.length > 0) {
    // Find a station with a good example
    const example = returnP.find(s => s.return_period_start > 50 && s.return_period_end < 30) || returnP[0];
    if (example) {
      const rpStart = Math.round(example.return_period_start);
      const rpEnd = Math.round(example.return_period_end);
      window.RETURN_PERIOD = {
        threshold: Math.round(example.threshold_f),
        city: `${example.name || ''}${example.state ? ', ' + example.state : ''}`,
        byYear: [
          { year: example.year_start, period: rpStart },
          { year: Math.round((example.year_start + example.year_end) / 2), period: Math.round((rpStart + rpEnd) / 2) },
          { year: example.year_end, period: rpEnd },
          { year: 2050, period: Math.max(1, Math.round(rpEnd * 0.35)), projected: true },
        ],
      };
      console.log(`[Atmosphere] Loaded real return periods: ${example.name}`);
    }
  }

  // --- Extreme events ---
  if (events && events.length > 0) {
    window.EVENT_CLUSTERS = events.slice(0, 12).map(e => ({
      date: e.date || '',
      label: `${e.event_type === 'heat' ? 'Heat' : 'Cold'} event · ${e.date}`,
      x: 0.1 + Math.random() * 0.8,
      y: 0.2 + Math.random() * 0.5,
      magnitude: Math.min(1, (e.mean_anomaly_z || 3) / 5),
      stations: e.n_stations || 0,
      kind: e.event_type === 'heat' ? 'hot' : 'cold',
      desc: `${e.n_stations} stations in a spatially coherent cluster. Mean anomaly: ${(e.mean_anomaly_z || 0).toFixed(1)}σ. States: ${(e.states || []).join(', ')}.`,
    }));
    console.log(`[Atmosphere] Loaded real extreme events: ${window.EVENT_CLUSTERS.length}`);
  }

  console.log('[Atmosphere] Real data loading complete');
}

window._realDataPromise = loadRealData();
