/* ============================================================
   Yardley Country Club — mock data + golf helpers
   Exposed on window.YCC for the React app.
   ============================================================ */

// --- The signed-in member ----------------------------------
// Fallback/guest shape — real identity comes from the signed-in account.
const MEMBER = {
  id: null,
  first: "",
  last: "",
  name: "",
  email: "",
  ghin: "",
  handicapIndex: null,
  memberSince: null,
  homeClub: "Yardley Country Club",
  tier: "Member",
  tee: "White",
  phone: "",
  initials: "",
};

// No demo roster — members come from the database / real sign-ups.
const ROSTER = [];

// --- Date helpers: find this coming weekend ----------------
function fmtDate(d) {
  return d.toLocaleDateString("en-US", { weekday: "long", month: "long", day: "numeric" });
}
function shortDate(d) {
  return d.toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric" });
}
function isoDay(d) {
  return d.toISOString().slice(0, 10);
}
// Use the actual current date; the weekend is computed from today.
const TODAY = (() => { const d = new Date(); d.setHours(0, 0, 0, 0); return d; })();
function nextDow(from, dow) {
  const d = new Date(from);
  const diff = (dow - d.getDay() + 7) % 7 || 7;
  d.setDate(d.getDate() + diff);
  return d;
}
const SAT = nextDow(TODAY, 6);
const SUN = nextDow(TODAY, 0);

// --- Tee sheet generator -----------------------------------
// Deterministic pseudo-random so reloads look the same.
function mulberry32(seed) {
  return function () {
    let t = (seed += 0x6d2b79f5);
    t = Math.imul(t ^ (t >>> 15), t | 1);
    t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}

function timeLabel(h, m) {
  const ap = h >= 12 ? "PM" : "AM";
  const hh = h % 12 === 0 ? 12 : h % 12;
  return `${hh}:${m.toString().padStart(2, "0")} ${ap}`;
}

function buildTeeSheet(date, seed) {
  const rand = mulberry32(seed);
  const slots = [];
  let h = 7,
    m = 0,
    idx = 0;
  while (h < 11 || (h === 11 && m === 0)) {
    const fillCount = Math.floor(rand() * 5); // 0..4 occupancy
    const players = [];
    const used = new Set();
    for (let i = 0; i < fillCount; i++) {
      let p;
      do {
        p = ROSTER[Math.floor(rand() * ROSTER.length)];
      } while (used.has(p.name) && used.size < ROSTER.length);
      used.add(p.name);
      players.push({ ...p });
    }
    slots.push({
      id: `${isoDay(date)}-${idx}`,
      time: timeLabel(h, m),
      hour: h,
      min: m,
      players,
    });
    idx++;
    m += 9;
    if (m >= 60) {
      m -= 60;
      h += 1;
    }
  }
  return slots;
}

// --- Time helpers ------------------------------------------
function parseTime(label) {
  const m = label.match(/(\d+):(\d+)\s*(AM|PM)/i);
  if (!m) return 0;
  let h = parseInt(m[1], 10) % 12;
  if (/pm/i.test(m[3])) h += 12;
  return h * 60 + parseInt(m[2], 10);
}
function sortByTime(slots) {
  return [...slots].sort((a, b) => parseTime(a.time) - parseTime(b.time));
}

// --- Sign-up windows (Eastern Time) ------------------------
// Open to all members until WED 11:59 PM ET; managers-only until FRI 9 AM ET;
// then final lock. Deadlines are relative to that weekend's Saturday.
function nowEastern() {
  return new Date(new Date().toLocaleString("en-US", { timeZone: "America/New_York" }));
}
function entryWindow(satDate) {
  const y = satDate.getFullYear(), mo = satDate.getMonth(), da = satDate.getDate();
  const wed = new Date(y, mo, da - 3, 23, 59, 59); // Wednesday 11:59 PM ET
  const fri = new Date(y, mo, da - 1, 9, 0, 0);     // Friday 9:00 AM ET
  const now = nowEastern();
  let phase = "open";
  if (now > fri) phase = "locked";
  else if (now > wed) phase = "managerOnly";
  return { phase, wed, fri };
}
// Was a member's handicap confirmed for THIS week (within 7 days before Saturday)?
function confirmedForWeek(confirmedAt, satDate) {
  if (!confirmedAt) return false;
  const cutoff = new Date(satDate); cutoff.setDate(cutoff.getDate() - 7);
  return new Date(confirmedAt) >= cutoff;
}

// --- Phone formatting: (XXX) XXX-XXXX ----------------------
function formatPhone(v) {
  const d = (v || "").replace(/\D/g, "").slice(0, 10);
  if (d.length < 4) return d;
  if (d.length < 7) return `(${d.slice(0, 3)}) ${d.slice(3)}`;
  return `(${d.slice(0, 3)}) ${d.slice(3, 6)}-${d.slice(6)}`;
}

// --- Live weather for Yardley, PA 19067 --------------------
// Coordinates for the 19067 ZIP (Yardley / Lower Makefield, PA).
const YARDLEY_GEO = { lat: 40.2445, lon: -74.8463, label: "Yardley, PA 19067" };
const WMO = {
  0: "Clear", 1: "Mostly clear", 2: "Partly cloudy", 3: "Overcast",
  45: "Fog", 48: "Fog", 51: "Drizzle", 53: "Drizzle", 55: "Drizzle",
  61: "Light rain", 63: "Rain", 65: "Heavy rain", 66: "Freezing rain", 67: "Freezing rain",
  71: "Light snow", 73: "Snow", 75: "Heavy snow", 77: "Snow grains",
  80: "Showers", 81: "Showers", 82: "Heavy showers", 85: "Snow showers", 86: "Snow showers",
  95: "Thunderstorm", 96: "Thunderstorm", 99: "Thunderstorm",
};
function compass(deg) {
  if (deg == null || isNaN(deg)) return "";
  const dirs = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"];
  return dirs[Math.round(deg / 45) % 8];
}
// Returns a promise of { tempNow, high, low, windMph, windDir, condition, source } or null.
// Uses Open-Meteo (free, no key, sourced from national weather services incl. NWS/NOAA).
// Set window.YCC_CONFIG.weatherProxy to an AccuWeather-backed endpoint to override.
async function fetchWeather() {
  const cfg = (window.YCC_CONFIG || {});
  try {
    if (cfg.weatherProxy) {
      const r = await fetch(cfg.weatherProxy);
      if (r.ok) return await r.json();
    }
    const { lat, lon } = YARDLEY_GEO;
    const url = `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}`
      + `&current=temperature_2m,wind_speed_10m,wind_direction_10m,weather_code`
      + `&daily=temperature_2m_max,temperature_2m_min`
      + `&temperature_unit=fahrenheit&wind_speed_unit=mph&timezone=America%2FNew_York&forecast_days=1`;
    const r = await fetch(url);
    if (!r.ok) return null;
    const j = await r.json();
    const c = j.current || {};
    const d = j.daily || {};
    return {
      tempNow: Math.round(c.temperature_2m),
      high: Math.round((d.temperature_2m_max || [])[0]),
      low: Math.round((d.temperature_2m_min || [])[0]),
      windMph: Math.round(c.wind_speed_10m),
      windDir: compass(c.wind_direction_10m),
      condition: WMO[c.weather_code] || "—",
      source: "Open-Meteo · NWS data",
    };
  } catch (e) { return null; }
}

// --- The DIV Tour weekly entry (manager-published) ---------
// The manager publishes specific tee times for Saturday and Sunday.
// Members claim individual open spots; their handicap (HI + GHIN) attaches.
const ADMIN_EMAIL = "grahamat6@gmail.com";
// Emails that ALWAYS have tour-manager access (independent of the database).
const ADMIN_EMAILS = ["grahamat6@gmail.com", "dapaoni3@gmail.com"];
function isAdminEmail(email) {
  const e = (email || "").trim().toLowerCase();
  return !!e && ADMIN_EMAILS.some((a) => a.trim().toLowerCase() === e);
}
// The DIV Tour primary manager — change-request emails go here.
// David Paoni is the app's primary tour manager (and a tour member).
const PRIMARY_MANAGER = { name: "David Paoni", initials: "DP", email: "dapaoni3@gmail.com", phone: "(267) 421-1003", title: "DIV Tour Manager" };
// Additional club inboxes members can also notify on a change request.
const CLUB_CONTACTS = [
  { id: "golfshop", name: "YCC Golf Shop", email: "golfshop@yardleycc.com" },
  { id: "headpro", name: "Head Pro · Yardley Country Club", email: "HeadPro@yardleycc.com" },
];
const DEFAULT_MANAGERS = [
  { name: PRIMARY_MANAGER.name, initials: PRIMARY_MANAGER.initials, role: "Primary", title: PRIMARY_MANAGER.title, email: PRIMARY_MANAGER.email, phone: PRIMARY_MANAGER.phone, available: true },
  { name: "Graham", initials: "G", role: "Backup", title: "Administrator", email: ADMIN_EMAIL, phone: "", available: true, admin: true },
];

// Season default tee times + tournament exceptions (no DIV Tour group times).
// Dates are the affected calendar day (ISO). Through end of October 2026.
const TEE_EXCEPTIONS = {
  "2026-06-06": "Women's Member Guest — no group times. Course opens 2:30 PM; members make times online.",
  "2026-06-13": "Men's & Women's Senior Championship — no group times before 11 AM; members make times online.",
  "2026-06-14": "Men's & Women's Senior Championship — no group times before 11 AM; members make times online.",
  "2026-06-27": "Men's Member Guest — no times available all day.",
  "2026-07-04": "Red, White & Blue Tournament — no group times. Members sign up on ForeTees.",
};
const SEASON_END = "2026-10-31";
function defaultTeeTimes(dayISO, dow) {
  if (TEE_EXCEPTIONS[dayISO] || dayISO > SEASON_END) return [];
  return dow === 6 ? ["11:34 AM", "11:43 AM"] : ["11:45 AM", "11:54 AM"];
}

function buildDivTour(member) {
  const satISO = isoDay(SAT), sunISO = isoDay(SUN);
  const satTimes = defaultTeeTimes(satISO, 6);
  const sunTimes = defaultTeeTimes(sunISO, 0);
  const mk = (times, dayISO) => times.map((time, i) => ({ id: `${dayISO}-${i}`, time, players: [] }));
  const slots = { [satISO]: mk(satTimes, satISO), [sunISO]: mk(sunTimes, sunISO) };
  const notes = {};
  if (TEE_EXCEPTIONS[satISO]) notes[satISO] = TEE_EXCEPTIONS[satISO];
  if (TEE_EXCEPTIONS[sunISO]) notes[sunISO] = TEE_EXCEPTIONS[sunISO];
  const weekendLabel = `${SAT.toLocaleDateString("en-US", { month: "long", day: "numeric" })} – ${SUN.toLocaleDateString("en-US", { day: "numeric" })}`;
  return {
    satISO, sunISO, slots, notes, bookings: [], weekendLabel, published: true,
    managers: DEFAULT_MANAGERS.map(m => ({ ...m })),
    publishedAt: null,
  };
}

// --- Holes / scorecard for the skins game ------------------
// Yardley-style par 71, with stroke index (handicap) per hole.
// --- Yardley Country Club scorecard (real card) ------------
// Par 72. Five tee sets, each with its own rating/slope and stroke-index row:
//   Blue & White share one index; Green & Gold share another; Red has its own.
const HOLE_PAR = [4, 4, 4, 4, 4, 4, 5, 4, 3, 5, 4, 4, 5, 4, 3, 4, 3, 4];
const SI_SETS = {
  BlueWhite: [13, 1, 5, 11, 3, 17, 7, 9, 15, 10, 8, 6, 16, 2, 12, 18, 4, 14],
  GreenGold: [11, 1, 5, 9, 3, 13, 17, 7, 15, 6, 4, 10, 12, 2, 8, 18, 16, 14],
  Red: [13, 7, 3, 9, 1, 15, 5, 11, 17, 2, 14, 4, 12, 8, 18, 10, 16, 6],
};
const TEES = [
  { name: "Blue",  rating: 70.6, slope: 132, si: "BlueWhite", total: 6379, yards: [328, 387, 408, 329, 380, 309, 557, 352, 148, 551, 388, 370, 490, 436, 159, 262, 217, 308] },
  { name: "White", rating: 69.2, slope: 130, si: "BlueWhite", total: 6057, yards: [320, 376, 397, 307, 356, 292, 538, 346, 129, 509, 370, 347, 471, 415, 142, 255, 185, 302] },
  { name: "Green", rating: 67.6, slope: 127, si: "GreenGold", total: 5714, yards: [320, 376, 353, 307, 340, 292, 429, 346, 129, 478, 370, 284, 471, 408, 142, 255, 112, 302] },
  { name: "Gold",  rating: 66.0, slope: 124, si: "GreenGold", total: 5356, yards: [274, 321, 353, 292, 340, 273, 429, 300, 110, 478, 314, 284, 423, 408, 106, 248, 112, 291] },
  { name: "Red",   rating: 70.9, slope: 124, si: "Red",       total: 5304, yards: [309, 318, 348, 283, 336, 265, 426, 297, 106, 472, 309, 280, 417, 400, 99, 244, 108, 287] },
];
function teeByName(name) { return TEES.find((t) => t.name === name) || TEES[0]; }
function siForTee(name, holeIdx) { return SI_SETS[teeByName(name).si][holeIdx]; }
function yardsForTee(name, holeIdx) { return teeByName(name).yards[holeIdx]; }
// HOLES default to Blue yards + Blue/White stroke index for generic display.
const HOLES = HOLE_PAR.map((par, i) => ({ n: i + 1, par, yards: TEES[0].yards[i], si: SI_SETS.BlueWhite[i] }));

// --- The weekend skins game --------------------------------
// The weekend skins game is built from real scores entered during play.
const GAME_PLAYERS = [];
const GAME_SCORES = [];

// Course rating / slope to turn HI into a course handicap (default Blue tees).
const COURSE = { rating: 70.6, slope: 132, par: 72, tee: "Blue", yards: 6379 };
function courseHandicap(hi) {
  return Math.round((hi * COURSE.slope) / 113 + (COURSE.rating - COURSE.par));
}
// Course handicap from a specific set of tees (Blue/White/Green/Red).
function courseHandicapForTee(hi, teeName) {
  const tee = TEES.find((t) => t.name === teeName) || COURSE;
  return Math.round((hi * tee.slope) / 113 + (tee.rating - COURSE.par));
}
// strokes a player receives on a given hole (by stroke index)
function strokesOnHole(courseHcp, si) {
  let s = Math.floor(courseHcp / 18);
  if (si <= courseHcp % 18) s += 1;
  return s;
}
// strokes a player gets on a hole RELATIVE to the low player in the group.
// The low course handicap plays to scratch (0); everyone else gets the
// difference, allocated by stroke index. This is what the scorecard "dots" show.
function strokesVsLow(courseHcp, lowCourseHcp, si) {
  const diff = Math.max(0, Math.round(courseHcp) - Math.round(lowCourseHcp));
  return strokesOnHole(diff, si);
}

/* Compute skins.
   mode: "gross" | "net"
   carryover: boolean
   skinValue: dollars per skin
   returns { holes:[{n, winnerIdx|null, value, carriedIn}], totals:[{idx,skins,money}] } */
function computeSkins({ players, scores, holes, mode, carryover, skinValue, courseHcps }) {
  const cHcps = courseHcps || players.map((p) => courseHandicap(p.hi));
  const out = [];
  let pot = 0;
  const totals = players.map(() => ({ skins: 0, money: 0 }));
  holes.forEach((hole, hIdx) => {
    const vals = players.map((p, pIdx) => {
      const gross = scores[pIdx][hIdx];
      if (mode === "net") return gross - strokesOnHole(cHcps[pIdx], hole.si);
      return gross;
    });
    const min = Math.min(...vals);
    const winners = vals.map((v, i) => (v === min ? i : -1)).filter((i) => i >= 0);
    const carriedIn = pot;
    if (winners.length === 1) {
      const value = skinValue + (carryover ? pot : 0);
      out.push({ n: hole.n, winnerIdx: winners[0], value, carriedIn, scores: vals });
      totals[winners[0]].skins += 1 + (carryover ? carriedIn / skinValue : 0);
      totals[winners[0]].money += value;
      pot = 0;
    } else {
      // tie
      if (carryover) {
        pot += skinValue;
        out.push({ n: hole.n, winnerIdx: null, value: 0, carriedIn, scores: vals });
      } else {
        out.push({ n: hole.n, winnerIdx: null, value: 0, carriedIn: 0, scores: vals });
      }
    }
  });
  return { holes: out, totals, potLeft: pot };
}

window.YCC = {
  MEMBER,
  ROSTER,
  ADMIN_EMAIL,
  ADMIN_EMAILS,
  isAdminEmail,
  PRIMARY_MANAGER,
  CLUB_CONTACTS,
  DEFAULT_MANAGERS,
  TODAY,
  SAT,
  SUN,
  fmtDate,
  shortDate,
  isoDay,
  buildDivTour,
  parseTime,
  sortByTime,
  formatPhone,
  nowEastern,
  entryWindow,
  confirmedForWeek,
  fetchWeather,
  YARDLEY_GEO,
  compass,
  HOLES,
  TEES,
  SI_SETS,
  teeByName,
  siForTee,
  yardsForTee,
  COURSE,
  GAME_PLAYERS,
  GAME_SCORES,
  courseHandicap,
  courseHandicapForTee,
  strokesOnHole,
  strokesVsLow,
  computeSkins,
  timeLabel,
};
