// data-sources.jsx — realistic mock data + real API call shapes behind one interface.
// In preview: returns mock values that drift like the real thing.
// In production: hits /api/* on the Cloudflare Worker (which holds the keys).
//
// Swap between modes by editing DATA_MODE below or setting window.__DD_REAL__ = true.

const DATA_MODE = (typeof window !== 'undefined' && window.__DD_REAL__) ? 'real' : 'mock';

// ────────── utilities ──────────
const rand = (min, max) => min + Math.random() * (max - min);
const drift = (v, pct = 0.002) => v * (1 + rand(-pct, pct));

// ────────── CLOCK ────────── (no API — just system time, assumed ET on Pi)
function useClock() {
  const [now, setNow] = React.useState(() => new Date());
  React.useEffect(() => {
    const id = setInterval(() => setNow(new Date()), 1000);
    return () => clearInterval(id);
  }, []);
  return now;
}

// ────────── WEATHER (Open-Meteo, no key needed — safe to call directly too) ──────────
// 02169 = Quincy MA → 42.2529, -71.0023
const WX_URL = 'https://api.open-meteo.com/v1/forecast'
  + '?latitude=42.2529&longitude=-71.0023'
  + '&current=temperature_2m,weather_code,wind_speed_10m,wind_direction_10m,is_day'
  + '&daily=temperature_2m_max,temperature_2m_min,sunrise,sunset,weather_code'
  + '&hourly=temperature_2m,weather_code'
  + '&temperature_unit=fahrenheit&wind_speed_unit=mph'
  + '&timezone=America%2FNew_York&forecast_days=7';

function mockWeather() {
  const now = new Date();
  const hour = now.getHours();
  const isDay = hour >= 6 && hour < 19;
  const baseTemp = 52 + Math.sin((hour - 6) / 12 * Math.PI) * 10;
  const dayNames = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
  const todayIdx = now.getDay();
  return {
    tempF: Math.round(drift(baseTemp, 0.01)),
    code: 1,
    condition: 'Mostly clear',
    windMph: 8,
    windDir: 'NE',
    hi: 62, lo: 44,
    isDay,
    sunrise: '6:12 AM', sunset: '7:28 PM',
    feelsF: Math.round(baseTemp - 2),
    humidity: 48, dewF: 41, uv: 4,
    hourly: Array.from({ length: 6 }, (_, i) => ({
      hour: `${((hour + i) % 12) || 12}${(hour + i) % 24 < 12 ? 'a' : 'p'}`,
      tempF: Math.round(baseTemp + i * 0.8),
      code: i < 3 ? 1 : 2,
    })),
    daily: Array.from({ length: 7 }, (_, i) => ({
      day: i === 0 ? 'TDY' : dayNames[(todayIdx + i) % 7].toUpperCase(),
      hi: 58 + Math.round(Math.sin(i * 0.9) * 8 + i * 1.2),
      lo: 42 + Math.round(Math.sin(i * 0.7) * 5 + i * 0.8),
      code: [1, 1, 2, 3, 61, 2, 1][i],
    })),
    updated: now,
    error: null,
  };
}

async function fetchWeather() {
  if (DATA_MODE === 'mock') return mockWeather();
  try {
    const r = await fetch(WX_URL);
    const d = await r.json();
    const c = d.current, day = d.daily;
    const windDeg = c.wind_direction_10m;
    const dirs = ['N','NE','E','SE','S','SW','W','NW'];
    const windDir = dirs[Math.round(windDeg / 45) % 8];
    const fmtTime = (iso) => new Date(iso).toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', timeZone: 'America/New_York' });
    const nowH = new Date().getHours();
    return {
      tempF: Math.round(c.temperature_2m),
      code: c.weather_code,
      condition: (window.WX_CODE_MAP?.[c.weather_code]?.label) || 'Weather',
      windMph: Math.round(c.wind_speed_10m),
      windDir,
      hi: Math.round(day.temperature_2m_max[0]),
      lo: Math.round(day.temperature_2m_min[0]),
      isDay: !!c.is_day,
      sunrise: fmtTime(day.sunrise[0]),
      sunset: fmtTime(day.sunset[0]),
      feelsF: Math.round(c.temperature_2m - 2),
      humidity: null, dewF: null, uv: null,
      hourly: d.hourly.time.slice(nowH, nowH + 6).map((t, i) => ({
        hour: new Date(t).toLocaleTimeString('en-US', { hour: 'numeric', hour12: true }).replace(' ', '').toLowerCase().replace('m', ''),
        tempF: Math.round(d.hourly.temperature_2m[nowH + i]),
        code: d.hourly.weather_code[nowH + i],
      })),
      daily: (day.time || []).slice(0, 7).map((t, i) => ({
        day: i === 0 ? 'TDY' : new Date(t).toLocaleDateString('en-US', { weekday: 'short', timeZone: 'America/New_York' }).toUpperCase(),
        hi: Math.round(day.temperature_2m_max[i]),
        lo: Math.round(day.temperature_2m_min[i]),
        code: (day.weather_code && day.weather_code[i]) || 0,
      })),
      updated: new Date(),
      error: null,
    };
  } catch (e) {
    return { ...mockWeather(), error: 'offline' };
  }
}

// ────────── CRCL (Twelve Data via Worker proxy) ──────────
// Production call: fetch('/api/crcl') — the Worker adds your API key server-side.
let mockCrclState = { price: 142.88, prev: 139.67 };

function isMarketOpen(d = new Date()) {
  // Rough US equity hours in ET — good enough for UI state
  const et = new Date(d.toLocaleString('en-US', { timeZone: 'America/New_York' }));
  const day = et.getDay();
  const mins = et.getHours() * 60 + et.getMinutes();
  return day >= 1 && day <= 5 && mins >= 570 && mins < 960; // 9:30–16:00
}

function mockCrcl() {
  mockCrclState.price = Math.max(50, drift(mockCrclState.price, 0.004));
  const open = isMarketOpen();
  const changed = +(mockCrclState.price - mockCrclState.prev).toFixed(2);
  const pct = +(changed / mockCrclState.prev * 100).toFixed(2);
  const spark = Array.from({ length: 32 }, (_, i) =>
    mockCrclState.prev + Math.sin(i * 0.35) * 2 + (i / 32) * changed + rand(-0.8, 0.8));
  return {
    symbol: 'CRCL',
    price: +mockCrclState.price.toFixed(2),
    prev: mockCrclState.prev,
    changeAbs: changed,
    changePct: pct,
    open: 140.10, high: 143.12, low: 139.88,
    vol: '4.12M', mktCap: '34.2B',
    wk52hi: 301.50, wk52lo: 58.20,
    marketOpen: open,
    extended: !open ? +(mockCrclState.price + rand(-0.3, 0.3)).toFixed(2) : null,
    spark,
    updated: new Date(),
    error: null,
  };
}

async function fetchCrcl() {
  if (DATA_MODE === 'mock') return mockCrcl();
  try {
    const r = await fetch('/api/crcl');
    if (!r.ok) throw new Error('api');
    const d = await r.json();
    return { ...d, updated: new Date(), error: null };
  } catch (e) {
    return { ...mockCrcl(), error: 'offline' };
  }
}

// ────────── USDC (CoinGecko via Worker proxy) ──────────
let mockUsdcState = 60.4;
function mockUsdc() {
  mockUsdcState = drift(mockUsdcState, 0.0008);
  return {
    symbol: 'USDC',
    mktCap: +mockUsdcState.toFixed(2),   // in B
    mktCapStr: `$${mockUsdcState.toFixed(2)}B`,
    change24h: +rand(-0.15, 0.15).toFixed(2),
    price: 1.00,
    vol24h: 7.1,
    supply: +mockUsdcState.toFixed(2),
    rank: 7,
    updated: new Date(),
    error: null,
  };
}
async function fetchUsdc() {
  if (DATA_MODE === 'mock') return mockUsdc();
  try {
    const r = await fetch('/api/usdc');
    if (!r.ok) throw new Error('api');
    const d = await r.json();
    return { ...d, updated: new Date(), error: null };
  } catch (e) {
    return { ...mockUsdc(), error: 'offline' };
  }
}

// ────────── FLIGHTS (AirLabs → OpenSky via Worker proxy) ──────────
// 02169 center: 42.2529, -71.0023. 5-mile radius.
const MOCK_FLIGHTS = [
  { flight: 'JBU1432', from: 'LGA', to: 'BOS', alt: 32000, progress: 0.42 },
  { flight: 'DAL2245', from: 'ATL', to: 'BOS', alt: 28500, progress: 0.68 },
  { flight: 'AAL198',  from: 'BOS', to: 'ORD', alt: 18000, progress: 0.22 },
  { flight: 'UAL441',  from: 'BOS', to: 'SFO', alt: 35000, progress: 0.15 },
  { flight: 'SWA1102', from: 'BWI', to: 'BOS', alt: 24000, progress: 0.55 },
  { flight: 'FDX1234', from: 'MEM', to: 'BOS', alt: 30000, progress: 0.79 },
];

let mockFlightIdx = 0;
function mockFlight() {
  const f = MOCK_FLIGHTS[mockFlightIdx % MOCK_FLIGHTS.length];
  mockFlightIdx = (mockFlightIdx + 1) % MOCK_FLIGHTS.length;
  return { ...f, updated: new Date(), error: null };
}
async function fetchFlight() {
  if (DATA_MODE === 'mock') return mockFlight();
  try {
    const r = await fetch('/api/flight');
    if (!r.ok) throw new Error('api');
    const d = await r.json();
    if (!d || !d.flight) return { flight: null, updated: new Date(), error: null };
    return { ...d, updated: new Date(), error: null };
  } catch (e) {
    return { flight: null, updated: new Date(), error: 'offline' };
  }
}

// IATA → city/airport name map, rendered as "Name (CODE)" in the ticker.
// Missing codes fall back to the IATA code alone.
const AIRPORT_NAMES = {
  // Northeast US (Boston approach/overflight traffic)
  BOS: 'Boston', JFK: 'New York JFK', LGA: 'New York LGA', EWR: 'Newark',
  PHL: 'Philadelphia', BWI: 'Baltimore', DCA: 'Washington DCA', IAD: 'Washington Dulles',
  PVD: 'Providence', BDL: 'Hartford', MHT: 'Manchester', PWM: 'Portland ME',
  ALB: 'Albany', BUF: 'Buffalo', ROC: 'Rochester', SYR: 'Syracuse',
  FRG: 'Farmingdale', ISP: 'Islip', HPN: 'White Plains', TEB: 'Teterboro',
  // Major US hubs
  ORD: 'Chicago', ATL: 'Atlanta', DFW: 'Dallas', IAH: 'Houston',
  LAX: 'Los Angeles', SFO: 'San Francisco', SEA: 'Seattle', DEN: 'Denver',
  PHX: 'Phoenix', LAS: 'Las Vegas', MIA: 'Miami', FLL: 'Fort Lauderdale',
  MCO: 'Orlando', TPA: 'Tampa', MSP: 'Minneapolis', DTW: 'Detroit',
  CLT: 'Charlotte', MEM: 'Memphis', BNA: 'Nashville', STL: 'St. Louis',
  MSY: 'New Orleans', CVG: 'Cincinnati', CMH: 'Columbus', CLE: 'Cleveland',
  IND: 'Indianapolis', PIT: 'Pittsburgh', RDU: 'Raleigh', MCI: 'Kansas City',
  SLC: 'Salt Lake City', PDX: 'Portland OR', SAN: 'San Diego', SJC: 'San Jose',
  OAK: 'Oakland', AUS: 'Austin', SAT: 'San Antonio', MKE: 'Milwaukee',
  // Canada
  YYZ: 'Toronto', YUL: 'Montreal', YVR: 'Vancouver', YYC: 'Calgary', YHZ: 'Halifax',
  // Europe (transatlantic)
  LHR: 'London Heathrow', LGW: 'London Gatwick', LTN: 'London Luton',
  STN: 'London Stansted', MAN: 'Manchester UK', EDI: 'Edinburgh', DUB: 'Dublin',
  CDG: 'Paris CDG', ORY: 'Paris Orly', AMS: 'Amsterdam', FRA: 'Frankfurt',
  MUC: 'Munich', ZRH: 'Zurich', BRU: 'Brussels', CPH: 'Copenhagen',
  ARN: 'Stockholm', OSL: 'Oslo', HEL: 'Helsinki', VIE: 'Vienna',
  MAD: 'Madrid', BCN: 'Barcelona', LIS: 'Lisbon', FCO: 'Rome',
  MXP: 'Milan', ATH: 'Athens', IST: 'Istanbul',
  // Middle East / Asia (long-haul)
  DXB: 'Dubai', DOH: 'Doha', AUH: 'Abu Dhabi', TLV: 'Tel Aviv',
  NRT: 'Tokyo Narita', HND: 'Tokyo Haneda', ICN: 'Seoul', HKG: 'Hong Kong',
  SIN: 'Singapore', PEK: 'Beijing', PVG: 'Shanghai',
  // Latin America / Caribbean
  MEX: 'Mexico City', CUN: 'Cancun', SJU: 'San Juan', NAS: 'Nassau',
  GRU: 'São Paulo', GIG: 'Rio de Janeiro', PTY: 'Panama City', BOG: 'Bogotá',
  LIM: 'Lima', EZE: 'Buenos Aires',
};
function airportLabel(code) {
  if (!code || code === '—') return code || '';
  const name = AIRPORT_NAMES[code];
  return name ? `${name} (${code})` : code;
}

Object.assign(window, {
  DATA_MODE, useClock, fetchWeather, fetchCrcl, fetchUsdc, fetchFlight, isMarketOpen,
  AIRPORT_NAMES, airportLabel,
});
