// Shared library — cities, timezone helpers, jet lag science.

// Curated city set — major travel hubs across all continents. Stored as a
// flat array so we can render it directly in the searchable picker.
const CITIES = [
  { name: 'Auckland',     country: 'New Zealand',       code: 'nz', tz: 'Pacific/Auckland' },
  { name: 'Sydney',       country: 'Australia',         code: 'au', tz: 'Australia/Sydney' },
  { name: 'Melbourne',    country: 'Australia',         code: 'au', tz: 'Australia/Melbourne' },
  { name: 'Perth',        country: 'Australia',         code: 'au', tz: 'Australia/Perth' },
  { name: 'Tokyo',        country: 'Japan',             code: 'jp', tz: 'Asia/Tokyo' },
  { name: 'Seoul',        country: 'South Korea',       code: 'kr', tz: 'Asia/Seoul' },
  { name: 'Beijing',      country: 'China',             code: 'cn', tz: 'Asia/Shanghai' },
  { name: 'Shanghai',     country: 'China',             code: 'cn', tz: 'Asia/Shanghai' },
  { name: 'Hong Kong',    country: 'Hong Kong',         code: 'hk', tz: 'Asia/Hong_Kong' },
  { name: 'Taipei',       country: 'Taiwan',            code: 'tw', tz: 'Asia/Taipei' },
  { name: 'Singapore',    country: 'Singapore',         code: 'sg', tz: 'Asia/Singapore' },
  { name: 'Bangkok',      country: 'Thailand',          code: 'th', tz: 'Asia/Bangkok' },
  { name: 'Jakarta',      country: 'Indonesia',         code: 'id', tz: 'Asia/Jakarta' },
  { name: 'Manila',       country: 'Philippines',       code: 'ph', tz: 'Asia/Manila' },
  { name: 'Ho Chi Minh',  country: 'Vietnam',           code: 'vn', tz: 'Asia/Ho_Chi_Minh' },
  { name: 'Delhi',        country: 'India',             code: 'in', tz: 'Asia/Kolkata' },
  { name: 'Mumbai',       country: 'India',             code: 'in', tz: 'Asia/Kolkata' },
  { name: 'Bangalore',    country: 'India',             code: 'in', tz: 'Asia/Kolkata' },
  { name: 'Dhaka',        country: 'Bangladesh',        code: 'bd', tz: 'Asia/Dhaka' },
  { name: 'Karachi',      country: 'Pakistan',          code: 'pk', tz: 'Asia/Karachi' },
  { name: 'Tehran',       country: 'Iran',              code: 'ir', tz: 'Asia/Tehran' },
  { name: 'Dubai',        country: 'UAE',               code: 'ae', tz: 'Asia/Dubai' },
  { name: 'Doha',         country: 'Qatar',             code: 'qa', tz: 'Asia/Qatar' },
  { name: 'Riyadh',       country: 'Saudi Arabia',      code: 'sa', tz: 'Asia/Riyadh' },
  { name: 'Tel Aviv',     country: 'Israel',            code: 'il', tz: 'Asia/Jerusalem' },
  { name: 'Istanbul',     country: 'Türkiye',           code: 'tr', tz: 'Europe/Istanbul' },
  { name: 'Moscow',       country: 'Russia',            code: 'ru', tz: 'Europe/Moscow' },
  { name: 'Cairo',        country: 'Egypt',             code: 'eg', tz: 'Africa/Cairo' },
  { name: 'Lagos',        country: 'Nigeria',           code: 'ng', tz: 'Africa/Lagos' },
  { name: 'Johannesburg', country: 'South Africa',      code: 'za', tz: 'Africa/Johannesburg' },
  { name: 'Cape Town',    country: 'South Africa',      code: 'za', tz: 'Africa/Johannesburg' },
  { name: 'Nairobi',      country: 'Kenya',             code: 'ke', tz: 'Africa/Nairobi' },
  { name: 'Athens',       country: 'Greece',            code: 'gr', tz: 'Europe/Athens' },
  { name: 'Rome',         country: 'Italy',             code: 'it', tz: 'Europe/Rome' },
  { name: 'Madrid',       country: 'Spain',             code: 'es', tz: 'Europe/Madrid' },
  { name: 'Barcelona',    country: 'Spain',             code: 'es', tz: 'Europe/Madrid' },
  { name: 'Lisbon',       country: 'Portugal',          code: 'pt', tz: 'Europe/Lisbon' },
  { name: 'Paris',        country: 'France',            code: 'fr', tz: 'Europe/Paris' },
  { name: 'Berlin',       country: 'Germany',           code: 'de', tz: 'Europe/Berlin' },
  { name: 'Munich',       country: 'Germany',           code: 'de', tz: 'Europe/Berlin' },
  { name: 'Amsterdam',    country: 'Netherlands',       code: 'nl', tz: 'Europe/Amsterdam' },
  { name: 'Brussels',     country: 'Belgium',           code: 'be', tz: 'Europe/Brussels' },
  { name: 'Zürich',       country: 'Switzerland',       code: 'ch', tz: 'Europe/Zurich' },
  { name: 'Vienna',       country: 'Austria',           code: 'at', tz: 'Europe/Vienna' },
  { name: 'Stockholm',    country: 'Sweden',            code: 'se', tz: 'Europe/Stockholm' },
  { name: 'Copenhagen',   country: 'Denmark',           code: 'dk', tz: 'Europe/Copenhagen' },
  { name: 'Oslo',         country: 'Norway',            code: 'no', tz: 'Europe/Oslo' },
  { name: 'Helsinki',     country: 'Finland',           code: 'fi', tz: 'Europe/Helsinki' },
  { name: 'Reykjavík',    country: 'Iceland',           code: 'is', tz: 'Atlantic/Reykjavik' },
  { name: 'Dublin',       country: 'Ireland',           code: 'ie', tz: 'Europe/Dublin' },
  { name: 'London',       country: 'United Kingdom',    code: 'gb', tz: 'Europe/London' },
  { name: 'Edinburgh',    country: 'United Kingdom',    code: 'gb', tz: 'Europe/London' },
  { name: 'New York',     country: 'United States',     code: 'us', tz: 'America/New_York' },
  { name: 'Washington',   country: 'United States',     code: 'us', tz: 'America/New_York' },
  { name: 'Miami',        country: 'United States',     code: 'us', tz: 'America/New_York' },
  { name: 'Chicago',      country: 'United States',     code: 'us', tz: 'America/Chicago' },
  { name: 'Dallas',       country: 'United States',     code: 'us', tz: 'America/Chicago' },
  { name: 'Denver',       country: 'United States',     code: 'us', tz: 'America/Denver' },
  { name: 'Phoenix',      country: 'United States',     code: 'us', tz: 'America/Phoenix' },
  { name: 'Los Angeles',  country: 'United States',     code: 'us', tz: 'America/Los_Angeles' },
  { name: 'San Francisco',country: 'United States',     code: 'us', tz: 'America/Los_Angeles' },
  { name: 'Seattle',      country: 'United States',     code: 'us', tz: 'America/Los_Angeles' },
  { name: 'Anchorage',    country: 'United States',     code: 'us', tz: 'America/Anchorage' },
  { name: 'Honolulu',     country: 'United States',     code: 'us', tz: 'Pacific/Honolulu' },
  { name: 'Toronto',      country: 'Canada',            code: 'ca', tz: 'America/Toronto' },
  { name: 'Montréal',     country: 'Canada',            code: 'ca', tz: 'America/Toronto' },
  { name: 'Vancouver',    country: 'Canada',            code: 'ca', tz: 'America/Vancouver' },
  { name: 'Mexico City',  country: 'Mexico',            code: 'mx', tz: 'America/Mexico_City' },
  { name: 'Bogotá',       country: 'Colombia',          code: 'co', tz: 'America/Bogota' },
  { name: 'Lima',         country: 'Peru',              code: 'pe', tz: 'America/Lima' },
  { name: 'Santiago',     country: 'Chile',             code: 'cl', tz: 'America/Santiago' },
  { name: 'Buenos Aires', country: 'Argentina',         code: 'ar', tz: 'America/Argentina/Buenos_Aires' },
  { name: 'São Paulo',    country: 'Brazil',            code: 'br', tz: 'America/Sao_Paulo' },
  { name: 'Rio de Janeiro',country: 'Brazil',           code: 'br', tz: 'America/Sao_Paulo' },
];
// Build haystack on each city for fuzzy search.
CITIES.forEach(c => { c.haystack = `${c.name} ${c.country} ${c.tz}`.toLowerCase(); });

// Compute UTC offset (in minutes) of a tz at a given Date.
function tzOffsetMinutes(tz, at = new Date()) {
  const dtf = new Intl.DateTimeFormat('en-US', {
    timeZone: tz, hour12: false,
    year: 'numeric', month: '2-digit', day: '2-digit',
    hour: '2-digit', minute: '2-digit', second: '2-digit',
  });
  const parts = dtf.formatToParts(at).reduce((a, p) => (a[p.type] = p.value, a), {});
  const asUTC = Date.UTC(+parts.year, +parts.month - 1, +parts.day, +parts.hour % 24, +parts.minute, +parts.second);
  return Math.round((asUTC - at.getTime()) / 60000);
}

function tzOffsetLabel(tz, at = new Date()) {
  const m = tzOffsetMinutes(tz, at);
  const sign = m >= 0 ? '+' : '−';
  const abs = Math.abs(m);
  const h = Math.floor(abs / 60);
  const mm = abs % 60;
  return mm === 0 ? `${sign}${h}` : `${sign}${h}:${String(mm).padStart(2, '0')}`;
}

// Get the local wall-clock hours/minutes of a given Date in a given tz.
function tzLocalParts(tz, at = new Date()) {
  const dtf = new Intl.DateTimeFormat('en-US', {
    timeZone: tz, hour12: false,
    year: 'numeric', month: '2-digit', day: '2-digit',
    hour: '2-digit', minute: '2-digit', weekday: 'short',
  });
  const parts = dtf.formatToParts(at).reduce((a, p) => (a[p.type] = p.value, a), {});
  return {
    year: +parts.year, month: +parts.month, day: +parts.day,
    hour: (+parts.hour) % 24, minute: +parts.minute, weekday: parts.weekday,
  };
}

function fmtTime24(h, m) { return `${String(h).padStart(2,'0')}:${String(m).padStart(2,'0')}`; }
function fmtTime12(h, m) {
  const ap = h < 12 ? 'AM' : 'PM';
  let hh = h % 12; if (hh === 0) hh = 12;
  return `${hh}:${String(m).padStart(2, '0')} ${ap}`;
}
function pad2(n) { return String(n).padStart(2, '0'); }

// ── Jet lag science ─────────────────────────────────────────────────
// Compute the signed timezone difference between two tz at a given moment.
// Returns destination_offset - origin_offset, in HOURS, rounded to nearest 0.5
// for display, but kept fractional for math.
function tzDeltaHours(originTz, destTz, at = new Date()) {
  return (tzOffsetMinutes(destTz, at) - tzOffsetMinutes(originTz, at)) / 60;
}

// Direction of travel: east = positive delta, west = negative.
// Eastward travel is harder to adjust to because we need to advance our
// circadian rhythm (sleep earlier), which is biologically harder than
// delaying it.
function travelDirection(deltaHours) {
  if (Math.abs(deltaHours) < 0.5) return 'none';
  return deltaHours > 0 ? 'east' : 'west';
}

// Severity score 1–10. Uses absolute timezone delta with eastward penalty.
//   |Δ| < 1   → 1 (none)
//   |Δ| 3    → ~4
//   |Δ| 6    → ~6
//   |Δ| 12   → ~10 (east) / ~8 (west)
// Plus chronotype adjustment: morning people handle east better, night owls handle west better.
function severityScore(deltaHours, chronotype = 'neutral') {
  const abs = Math.abs(deltaHours);
  if (abs < 1) return 1;
  const east = deltaHours > 0;
  let score = abs * (east ? 0.85 : 0.65);
  // chronotype tweak
  if (chronotype === 'lark'  && east) score -= 0.4;
  if (chronotype === 'lark'  && !east) score += 0.4;
  if (chronotype === 'owl'   && east) score += 0.5;
  if (chronotype === 'owl'   && !east) score -= 0.4;
  score = Math.round(Math.max(1, Math.min(10, score)) * 10) / 10;
  return score;
}

// Expected recovery days at the destination — eastward is slower.
function recoveryDays(deltaHours) {
  const abs = Math.abs(deltaHours);
  if (abs < 1) return 0;
  return Math.ceil(abs / (deltaHours > 0 ? 1.0 : 1.5));
}

// PER-DAY PLAN ─────────────────────────────────────────────────────
// We model a "shift" as moving the user's circadian baseline by N hours per
// day toward the destination. Reasonable shift speed:
//   strict mode: 1.5h per day
//   gentle mode: 1.0h per day
// Pre-shift mode: start adjustment 3 days before departure.
// On-arrival mode: stay on origin schedule until arrival, then adjust at destination.
//
// For each day we compute the "current circadian time" (where the body thinks
// it is) and produce sleep / wake / meal / light / caffeine / melatonin windows
// shifted relative to that baseline.
function buildPlan({
  originTz, destTz,
  departureDate, arrivalDate, tripLengthDays = 7,
  usualBedtime = '23:00', usualWaketime = '07:00',
  chronotype = 'neutral',
  caffeineCutoffH = 8,  // hours before bedtime
  strictness = 'gentle',   // 'gentle' | 'strict'
  direction = 'preshift',  // 'preshift' | 'arrival'
  enableCaffeine = true,
  enableMelatonin = true,
  overrides = {},   // { breakfast, lunch, dinner, melatonin } as {h,m} — apply to every day
  melatoninLeadH = 0.5,
}) {
  const deltaHours = tzDeltaHours(originTz, destTz, departureDate);
  const east = deltaHours > 0;
  const totalShift = Math.abs(deltaHours);
  const perDay = strictness === 'strict' ? 1.5 : 1.0;
  const preShiftDays = direction === 'preshift' ? Math.min(3, Math.ceil(totalShift / perDay)) : 0;
  const recovery = recoveryDays(deltaHours);

  // Plan window: pre-shift days + travel day + trip days + recovery
  const totalDays = preShiftDays + 1 + tripLengthDays + recovery;
  // Day 0 anchored to (preShiftDays days before departure).
  const startDate = new Date(departureDate.getTime() - preShiftDays * 86400000);
  startDate.setHours(0, 0, 0, 0);

  const usualBed  = parseHM(usualBedtime);
  const usualWake = parseHM(usualWaketime);

  const days = [];
  for (let i = 0; i < totalDays; i++) {
    const date = new Date(startDate.getTime() + i * 86400000);
    // current shift progress: how much we have shifted toward destination by day i
    let shifted; // in hours, signed (positive = toward destination)
    if (i < preShiftDays) {
      // Pre-shift phase: gradually shift toward destination
      shifted = (i + 1) * perDay * (east ? 1 : -1);
    } else if (i === preShiftDays) {
      // Travel day — apply the rest of the shift suddenly upon arrival
      shifted = deltaHours;
    } else if (i <= preShiftDays + recovery) {
      // Recovery — stay at destination time fully (already adjusted)
      shifted = deltaHours;
    } else {
      shifted = deltaHours;
    }
    // clamp |shifted| ≤ |delta|
    if (Math.abs(shifted) > Math.abs(deltaHours)) shifted = deltaHours;

    // Body clock thinks current time is (destination time - shifted).
    // We schedule sleep/wake/meals at DESTINATION time so the user becomes
    // entrained. Recommended bedtime is usualBed adjusted by shifted hours.
    const bedDest  = addHours(usualBed,  -((east ? -1 : 1) * (Math.abs(deltaHours) - Math.abs(shifted))));
    const wakeDest = addHours(usualWake, -((east ? -1 : 1) * (Math.abs(deltaHours) - Math.abs(shifted))));

    // Phase tag for the day
    let phase;
    if (i < preShiftDays) phase = 'pre-shift';
    else if (i === preShiftDays) phase = 'travel';
    else if (i <= preShiftDays + recovery) phase = 'recovery';
    else phase = 'destination';

    // Meal times — 3 meals spread between wake and bed, OR user overrides
    const wakeM = wakeDest.h * 60 + wakeDest.m;
    const bedM  = bedDest.h * 60 + bedDest.m;
    let awakeM = bedM - wakeM; if (awakeM < 0) awakeM += 1440;
    const breakfast = overrides.breakfast || minToHM((wakeM + 30) % 1440);
    const lunch     = overrides.lunch     || minToHM((wakeM + Math.round(awakeM * 0.4)) % 1440);
    const dinner    = overrides.dinner    || minToHM((wakeM + Math.round(awakeM * 0.75)) % 1440);

    // Light exposure: morning light advances (good for east); evening light delays (good for west)
    const light = east
      ? { kind: 'morning bright light', start: addHours(wakeDest, 0), end: addHours(wakeDest, 2) }
      : { kind: 'evening bright light', start: addHours(bedDest, -3), end: addHours(bedDest, -1) };

    // Caffeine window: ok from wake until N hours before bed
    const caffEnd = addHours(bedDest, -caffeineCutoffH);
    const caffeine = enableCaffeine ? { start: addHours(wakeDest, 0), end: caffEnd } : null;

    // Melatonin: 0.5–1mg some N hours/min before bedtime (default 30 min).
    // If user-overridden, use that exact time directly.
    const melatonin = enableMelatonin && (phase !== 'destination' || i <= preShiftDays + recovery)
      ? { time: overrides.melatonin || addHours(bedDest, -melatoninLeadH), dose: '0.5–1 mg' }
      : null;

    days.push({
      date, dayIndex: i, phase, shiftedHours: shifted,
      sleep: { start: bedDest, end: wakeDest },
      meals: { breakfast, lunch, dinner },
      light, caffeine, melatonin,
    });
  }

  return {
    deltaHours, direction: travelDirection(deltaHours),
    severity: severityScore(deltaHours, chronotype),
    recoveryDays: recovery, preShiftDays,
    days, originTz, destTz, departureDate, arrivalDate, tripLengthDays,
  };
}

// Time helpers
function parseHM(s) {
  const [h, m] = s.split(':').map(n => parseInt(n, 10));
  return { h: h || 0, m: m || 0 };
}
function addHours(t, dh) {
  let total = t.h * 60 + t.m + Math.round(dh * 60);
  while (total < 0) total += 1440;
  total = total % 1440;
  return { h: Math.floor(total / 60), m: total % 60 };
}
function minToHM(min) {
  return { h: Math.floor(min / 60) % 24, m: min % 60 };
}
function hmToMin(t) { return t.h * 60 + t.m; }
function fmtHM(t) { return `${pad2(t.h)}:${pad2(t.m)}`; }

// ── Smooth tween hook (for animations) ─────────────────────────────
function useTween(target, ms = 400) {
  const [val, setVal] = React.useState(target);
  const fromRef = React.useRef(target);
  const startRef = React.useRef(0);
  React.useEffect(() => {
    fromRef.current = val;
    startRef.current = performance.now();
    let raf;
    const tick = (t) => {
      const k = Math.min(1, (t - startRef.current) / ms);
      const e = 1 - Math.pow(1 - k, 3);
      setVal(fromRef.current + (target - fromRef.current) * e);
      if (k < 1) raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [target]);
  return val;
}

window.JL = {
  CITIES, tzOffsetMinutes, tzOffsetLabel, tzLocalParts, tzDeltaHours,
  travelDirection, severityScore, recoveryDays, buildPlan,
  fmtTime24, fmtTime12, fmtHM, parseHM, addHours, hmToMin, minToHM, pad2,
  useTween,
};
