// tc-logic.jsx — parsing + payroll rules for the BTL Crew Time Card.
// Pure functions, exported on window. WEEK ENDING is the anchor: the user sets it
// first, and every pasted date is slotted into that Sun→Sat week.

// ── Time helpers ────────────────────────────────────────────────────────────
function parseTime(tok) {
  if (!tok) return null;
  const m = String(tok).trim().toLowerCase().match(/^(\d{1,2})(?::?(\d{2}))?\s*([ap])m?$/);
  if (!m) return null;
  let h = parseInt(m[1], 10);
  const min = m[2] ? parseInt(m[2], 10) : 0;
  const ap = m[3];
  if (h === 12) h = 0;
  let total = h * 60 + min;
  if (ap === 'p') total += 12 * 60;
  return total;
}
function fmtTime(total) {
  if (total == null) return '';
  total = ((total % 1440) + 1440) % 1440;
  let h = Math.floor(total / 60);
  const min = total % 60;
  const ap = h >= 12 ? 'P' : 'A';
  h = h % 12; if (h === 0) h = 12;
  return `${h}:${String(min).padStart(2, '0')}${ap}`;
}

// 1st Meal OUT = call + 6h, IN = call + 6h30m. ALWAYS. (No 2nd meal auto-fill.)
function mealFromCall(callMin) {
  if (callMin == null) return { out: '', in: '' };
  return { out: fmtTime(callMin + 360), in: fmtTime(callMin + 390) };
}

// ── Date helpers ─────────────────────────────────────────────────────────────
function saturdayOf(date) {
  const d = new Date(date.getTime());
  const add = (6 - d.getDay() + 7) % 7;
  d.setDate(d.getDate() + add);
  d.setHours(0, 0, 0, 0);
  return d;
}
function fmtDateMMDDYY(date) {
  const mm = String(date.getMonth() + 1).padStart(2, '0');
  const dd = String(date.getDate()).padStart(2, '0');
  const yy = String(date.getFullYear()).slice(-2);
  return `${mm}/${dd}/${yy}`;
}
function fmtDateShort(date) { return `${date.getMonth() + 1}/${date.getDate()}`; }

// Accept a Date, ISO "YYYY-MM-DD", or "MM/DD/YY[YY]" → the Saturday of that week.
function parseWeekEnding(input) {
  if (!input) return null;
  if (input instanceof Date) return isNaN(input) ? null : saturdayOf(input);
  const s = String(input).trim();
  let d = null;
  let m = s.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/);
  if (m) d = new Date(+m[1], +m[2] - 1, +m[3]);
  else {
    m = s.match(/^(\d{1,2})\/(\d{1,2})\/(\d{2,4})$/);
    if (m) { let y = +m[3]; if (y < 100) y += 2000; d = new Date(y, +m[1] - 1, +m[2]); }
  }
  if (!d || isNaN(d)) return null;
  return saturdayOf(d);
}
function weekDatesOf(saturday) {
  const out = [];
  for (let i = 0; i < 7; i++) {
    const d = new Date(saturday.getTime());
    d.setDate(saturday.getDate() - 6 + i);  // Sun..Sat
    out.push(d);
  }
  return out;
}

// Fallback year inference (only used if no week ending is set yet).
function inferYear(mds, refYear) {
  if (!mds.length) return refYear;
  for (let y = refYear + 1; y >= refYear - 5; y--) {
    const sats = mds.map(({ mo, da }) => saturdayOf(new Date(y, mo - 1, da)).getTime());
    if (sats.every(s => s === sats[0])) return y;
  }
  return refYear;
}

// ── Main parser ──────────────────────────────────────────────────────────────
const WEEKDAY_LETTERS = ['S', 'M', 'T', 'W', 'T', 'F', 'S'];

function parseTimecard(text, opts = {}) {
  const refYear = opts.year || new Date().getFullYear();
  const lines = String(text || '').split('\n').map(l => l.trim()).filter(Boolean);
  const we = parseWeekEnding(opts.weekEnding);
  const wkDates = we ? weekDatesOf(we) : null;

  // first pass for year inference (when no week ending)
  const prelim = [];
  for (const line of lines) {
    const m = line.match(/^(\d{1,2})\/(\d{1,2})\b\s*(.*)$/);
    if (!m) { prelim.push({ raw: line, error: 'Expected "M/D …"' }); continue; }
    prelim.push({ raw: line, mo: parseInt(m[1], 10), da: parseInt(m[2], 10), rest: m[3].trim() });
  }
  const goodMDs = prelim.filter(p => !p.error).map(p => ({ mo: p.mo, da: p.da }));
  const year = we ? we.getFullYear() : (opts.year || inferYear(goodMDs, refYear));

  const days = [];
  const errors = [];
  let finalWorkState = '', finalWorkCity = '';

  for (const p of prelim) {
    if (p.error) { errors.push(`"${p.raw}" — ${p.error}`); continue; }

    // resolve the full date + weekday row
    let date, weekday;
    if (wkDates) {
      const idx = wkDates.findIndex(d => d.getMonth() === p.mo - 1 && d.getDate() === p.da);
      if (idx < 0) { errors.push(`${p.mo}/${p.da} is not in week ending ${fmtDateMMDDYY(we)}`); continue; }
      date = wkDates[idx]; weekday = idx;
    } else {
      date = new Date(year, p.mo - 1, p.da); weekday = date.getDay();
    }

    const base = {
      raw: p.raw, date, mmdd: fmtDateShort(date), weekday,
      letter: WEEKDAY_LETTERS[weekday],
      state: '', city: '', call: '', wrap: '', mealOut: '', mealIn: '',
    };
    const rest = p.rest, low = rest.toLowerCase();

    if (/^(home|off)\b/.test(low) || rest === '') { days.push({ ...base, type: 'off' }); continue; }

    if (/^travel\b/.test(low)) {
      const full = /\bfull\b/.test(low), half = /\bhalf\b/.test(low);
      // a trailing word (e.g. DRONE) becomes a note → box right of WRAP
      const note = rest
        .replace(/\btravel\b/ig, '').replace(/\bfull\b/ig, '').replace(/\bhalf\b/ig, '')
        .replace(/\s+/g, ' ').trim().toUpperCase();
      days.push({
        ...base, type: 'travel', full, half, note,
        travelText: 'TRAVEL', span: full ? 'FULL' : (half ? 'HALF' : ''),
      });
      continue;
    }

    const range = rest.match(/^(\d{1,2}(?::?\d{2})?\s*[ap]m?)\s*[-–to]+\s*(\d{1,2}(?::?\d{2})?\s*[ap]m?)(.*)$/i);
    if (range) {
      const callMin = parseTime(range[1]), wrapMin = parseTime(range[2]);
      const meal = opts.autoMeal === false ? { out: '', in: '' } : mealFromCall(callMin);
      const note = (range[3] || '').replace(/\s+/g, ' ').trim().toUpperCase();
      days.push({ ...base, type: 'work', call: fmtTime(callMin), wrap: fmtTime(wrapMin), mealOut: meal.out, mealIn: meal.in, note, callMin, wrapMin });
      continue;
    }

    // Anything else (no times, not travel/off): a free-form STATUS that spans the
    // time boxes — e.g. DOWN, DARK, HOLD, WRITE OFF, WX, TBD, PICKUP. Type whatever.
    const statusText = rest.replace(/\s+/g, ' ').trim().toUpperCase();
    days.push({ ...base, type: 'status', statusText });
  }

  const firstGood = days.find(d => d.date);
  const weekEnding = we || (firstGood ? saturdayOf(firstGood.date) : null);
  const perDiemDays = days.filter(d => d.type === 'work' || d.type === 'travel' || d.type === 'status').length;

  return {
    year, days, errors, weekEnding,
    weekEndingStr: weekEnding ? fmtDateMMDDYY(weekEnding) : '',
    perDiemDays, finalWorkState, finalWorkCity,
    anchored: !!we,
  };
}

function buildWeekRows(parsed) {
  const rows = WEEKDAY_LETTERS.map((letter, i) => ({ letter, weekday: i, day: null }));
  for (const d of parsed.days) { if (d.date) rows[d.weekday].day = d; }
  return rows;
}

// ── Hours worked + overtime ───────────────────────────────────────────────────
// Elapsed hours from call → wrap (handles an overnight wrap past midnight).
// We work full 12s, so OT is anything past the 12th hour.
const OT_AFTER_HOURS = 12;
function dayElapsedHours(day) {
  if (!day || day.callMin == null || day.wrapMin == null) return 0;
  let mins = day.wrapMin - day.callMin;
  if (mins <= 0) mins += 1440;   // wrapped past midnight
  return mins / 60;
}
// Tally a parsed timecard: total hours, OT (>12h/day), straight, and per-day.
function timecardHours(parsed) {
  let total = 0, ot = 0, workDays = 0;
  const perDay = [];
  for (const d of (parsed && parsed.days ? parsed.days : [])) {
    if (d.type !== 'work') continue;
    const h = dayElapsedHours(d);
    if (h <= 0) continue;
    const dot = Math.max(0, h - OT_AFTER_HOURS);
    total += h; ot += dot; workDays++;
    perDay.push({ mmdd: d.mmdd, hours: h, ot: dot });
  }
  return { total, ot, straight: total - ot, workDays, perDay };
}
// Tally a raw saved card (parses its pasted text first).
function cardHours(card) {
  if (!card) return { total: 0, ot: 0, straight: 0, workDays: 0, perDay: [] };
  const parsed = parseTimecard(card.pasteText, { weekEnding: card.weekEnding, autoMeal: card.autoMeal !== false });
  return timecardHours(parsed);
}
function fmtHours(h) {
  if (!h) return '0';
  return (Math.round(h * 10) / 10).toString();
}

// ── Output file naming ───────────────────────────────────────────────────────
// Users can customize the download name with a token template. Tokens:
//   {INITIALS}  the profile/card initials (e.g. HSK)
//   {TYPE}      "TC" for a time card, "<KIT>KIT" for a kit (e.g. DRONEKIT)
//   {DATE}      week-ending date as MMDDYY
//   {DATELONG}  week-ending date as YYYYMMDD
//   {PROJECT}   project title (time cards only), spaces→underscores
// The default reproduces the original scheme: HSK_TC_WE_061326
const DEFAULT_FN_PATTERN = '{INITIALS}_{TYPE}_WE_{DATE}';

function fnDate(we, long) {
  if (!we) return '';
  const mm = String(we.getMonth() + 1).padStart(2, '0');
  const dd = String(we.getDate()).padStart(2, '0');
  if (long) return `${we.getFullYear()}${mm}${dd}`;
  return `${mm}${dd}${String(we.getFullYear()).slice(-2)}`;
}

function applyFnPattern(pattern, tokens) {
  let p = (pattern && String(pattern).trim()) || DEFAULT_FN_PATTERN;
  p = p.replace(/\{(\w+)\}/g, (m, key) => {
    const v = tokens[key.toUpperCase()];
    return v != null ? String(v) : '';
  });
  // make it a safe filename: drop illegal chars, spaces→_, collapse/trim underscores
  p = p.replace(/[\/\\:*?"<>|]+/g, '').replace(/\s+/g, '_').replace(/_{2,}/g, '_').replace(/^_+|_+$/g, '');
  return (p || 'TIMECARD') + '.pdf';
}

function pdfFilename(weekEnding, prefix = 'ABC', pattern, project) {
  return applyFnPattern(pattern, {
    INITIALS: (prefix || 'ABC'),
    TYPE: 'TC',
    DATE: fnDate(weekEnding, false),
    DATELONG: fnDate(weekEnding, true),
    PROJECT: String(project || '').toUpperCase().replace(/[^A-Z0-9]+/g, '_').replace(/^_+|_+$/g, ''),
  });
}

function kitFilename(weekEndingISO, prefix = 'ABC', kitType = '', pattern) {
  const type = String(kitType || '').toUpperCase().replace(/[^A-Z0-9]/g, '');
  const we = parseWeekEnding(weekEndingISO);
  return applyFnPattern(pattern, {
    INITIALS: (prefix || 'ABC'),
    TYPE: type ? `${type}KIT` : 'KIT',
    DATE: fnDate(we, false),
    DATELONG: fnDate(we, true),
    PROJECT: '',
  });
}

function perDiemTotal(rate, days) { return (parseFloat(rate) || 0) * days; }

// Effective per-diem day count: the user's manual override if set, else the
// auto count (work + travel + down days). '' / null / invalid → auto.
function effectivePerDiemDays(card, parsed) {
  const auto = parsed ? parsed.perDiemDays : 0;
  if (!card) return auto;
  const ov = card.perDiemDays;
  if (ov === '' || ov == null) return auto;
  const n = parseInt(ov, 10);
  return isNaN(n) || n < 0 ? auto : n;
}
function fmtMoney(n) {
  if (n == null || isNaN(n)) return '';
  return n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}

// ── City / State + airport autofill ──────────────────────────────────────────
const CITY_STATE = {
  // California
  'los angeles':'CA','hollywood':'CA','burbank':'CA','culver city':'CA','santa monica':'CA','malibu':'CA',
  'pasadena':'CA','long beach':'CA','anaheim':'CA','san francisco':'CA','oakland':'CA','san jose':'CA',
  'san diego':'CA','sacramento':'CA','fresno':'CA','santa barbara':'CA','palm springs':'CA',
  // New York
  'new york':'NY','manhattan':'NY','brooklyn':'NY','queens':'NY','bronx':'NY','buffalo':'NY',
  'rochester':'NY','albany':'NY','syracuse':'NY','yonkers':'NY',
  // New Jersey
  'newark':'NJ','jersey city':'NJ','atlantic city':'NJ','trenton':'NJ',
  // Georgia
  'atlanta':'GA','savannah':'GA','augusta':'GA','athens':'GA',
  // Louisiana
  'new orleans':'LA','baton rouge':'LA','shreveport':'LA','lafayette':'LA',
  // Texas
  'austin':'TX','dallas':'TX','houston':'TX','san antonio':'TX','fort worth':'TX','el paso':'TX',
  'arlington':'TX','corpus christi':'TX',
  // New Mexico
  'albuquerque':'NM','santa fe':'NM','las cruces':'NM',
  // Florida
  'miami':'FL','miami beach':'FL','orlando':'FL','tampa':'FL','jacksonville':'FL','fort lauderdale':'FL',
  'tallahassee':'FL','st. petersburg':'FL','key west':'FL',
  // Illinois / Midwest
  'chicago':'IL','springfield':'IL','naperville':'IL',
  'indianapolis':'IN','fort wayne':'IN','des moines':'IA','cedar rapids':'IA',
  'milwaukee':'WI','madison':'WI','green bay':'WI','minneapolis':'MN','st. paul':'MN','saint paul':'MN',
  'kansas city':'MO','st. louis':'MO','saint louis':'MO','omaha':'NE','lincoln':'NE','wichita':'KS',
  // Ohio / PA / MI
  'columbus':'OH','cleveland':'OH','cincinnati':'OH','dayton':'OH','toledo':'OH','akron':'OH',
  'pittsburgh':'PA','philadelphia':'PA','harrisburg':'PA','allentown':'PA',
  'detroit':'MI','grand rapids':'MI','ann arbor':'MI','lansing':'MI',
  // Massachusetts / New England
  'boston':'MA','cambridge':'MA','worcester':'MA','providence':'RI','hartford':'CT','new haven':'CT',
  'stamford':'CT','bridgeport':'CT','manchester':'NH','burlington':'VT',
  // Mid-Atlantic / DC
  'washington':'DC','baltimore':'MD','annapolis':'MD','richmond':'VA','virginia beach':'VA','norfolk':'VA',
  'wilmington':'NC','charlotte':'NC','raleigh':'NC','durham':'NC','greensboro':'NC',
  // Southeast
  'charleston':'SC','columbia':'SC','greenville':'SC','myrtle beach':'SC',
  'nashville':'TN','memphis':'TN','knoxville':'TN','chattanooga':'TN',
  'birmingham':'AL','montgomery':'AL','huntsville':'AL','mobile':'AL',
  'jackson':'MS','biloxi':'MS','louisville':'KY','lexington':'KY','little rock':'AR','fayetteville':'AR',
  // Mountain / West
  'denver':'CO','colorado springs':'CO','boulder':'CO','aurora':'CO',
  'salt lake city':'UT','park city':'UT','provo':'UT','boise':'ID',
  'phoenix':'AZ','tucson':'AZ','scottsdale':'AZ','tempe':'AZ','flagstaff':'AZ','mesa':'AZ',
  'las vegas':'NV','reno':'NV','henderson':'NV',
  'billings':'MT','bozeman':'MT','missoula':'MT','cheyenne':'WY',
  // Pacific NW / AK / HI
  'seattle':'WA','tacoma':'WA','spokane':'WA','bellevue':'WA','olympia':'WA',
  'portland':'OR','eugene':'OR','salem':'OR','anchorage':'AK','juneau':'AK','fairbanks':'AK',
  'honolulu':'HI','hilo':'HI',
  'oklahoma city':'OK','tulsa':'OK',
  // Canada (province codes)
  'vancouver':'BC','victoria':'BC','toronto':'ON','ottawa':'ON','montreal':'QC','quebec city':'QC',
  'calgary':'AB','edmonton':'AB','winnipeg':'MB','halifax':'NS',
};
const AIRPORTS = {
  LAX:['Los Angeles','CA'], BUR:['Burbank','CA'], SFO:['San Francisco','CA'], SAN:['San Diego','CA'],
  JFK:['New York','NY'], LGA:['New York','NY'], EWR:['Newark','NJ'],
  ATL:['Atlanta','GA'], SAV:['Savannah','GA'], MSY:['New Orleans','LA'],
  ORD:['Chicago','IL'], MDW:['Chicago','IL'], AUS:['Austin','TX'], DFW:['Dallas','TX'], IAH:['Houston','TX'],
  ABQ:['Albuquerque','NM'], SAF:['Santa Fe','NM'], MIA:['Miami','FL'], MCO:['Orlando','FL'], TPA:['Tampa','FL'],
  BOS:['Boston','MA'], LAS:['Las Vegas','NV'], SEA:['Seattle','WA'], PDX:['Portland','OR'], BNA:['Nashville','TN'],
  PIT:['Pittsburgh','PA'], PHL:['Philadelphia','PA'], DTW:['Detroit','MI'], PHX:['Phoenix','AZ'], DEN:['Denver','CO'],
  YVR:['Vancouver','BC'], YYZ:['Toronto','ON'], YUL:['Montreal','QC'], YYC:['Calgary','AB'], YOW:['Ottawa','ON'],
  TNR:['Antananarivo','MG'],
  LHR:['London','UK'], LGW:['London','UK'], CDG:['Paris','France'], ORY:['Paris','France'],
  FRA:['Frankfurt','Germany'], MUC:['Munich','Germany'], AMS:['Amsterdam','Netherlands'],
  MAD:['Madrid','Spain'], BCN:['Barcelona','Spain'], FCO:['Rome','Italy'], MXP:['Milan','Italy'],
  ZRH:['Zurich','Switzerland'], DUB:['Dublin','Ireland'], CPH:['Copenhagen','Denmark'], ARN:['Stockholm','Sweden'],
  LIS:['Lisbon','Portugal'], VIE:['Vienna','Austria'], PRG:['Prague','Czechia'], IST:['Istanbul','Turkey'],
  DXB:['Dubai','United Arab Emirates'], DOH:['Doha','Qatar'], TLV:['Tel Aviv','Israel'], CAI:['Cairo','Egypt'],
  JNB:['Johannesburg','South Africa'], CPT:['Cape Town','South Africa'], NBO:['Nairobi','Kenya'],
  BOM:['Mumbai','India'], DEL:['New Delhi','India'], BKK:['Bangkok','Thailand'], SIN:['Singapore','Singapore'],
  HKG:['Hong Kong','Hong Kong'], NRT:['Tokyo','Japan'], HND:['Tokyo','Japan'], ICN:['Seoul','South Korea'],
  PEK:['Beijing','China'], PVG:['Shanghai','China'], SYD:['Sydney','Australia'], MEL:['Melbourne','Australia'],
  AKL:['Auckland','New Zealand'], GRU:['Sao Paulo','Brazil'], GIG:['Rio de Janeiro','Brazil'], EZE:['Buenos Aires','Argentina'],
};
function lookupState(city) {
  if (!city) return '';
  return CITY_STATE[city.trim().toLowerCase()] || '';
}
function expandAirport(code) {
  if (!code) return null;
  const a = AIRPORTS[code.trim().toUpperCase()];
  return a ? { city: a[0], state: a[1] } : null;
}

Object.assign(window, {
  parseTime, fmtTime, mealFromCall, saturdayOf, fmtDateMMDDYY, fmtDateShort,
  parseWeekEnding, weekDatesOf, inferYear, parseTimecard, buildWeekRows, pdfFilename, kitFilename,
  perDiemTotal, fmtMoney, WEEKDAY_LETTERS, lookupState, expandAirport, CITY_STATE, AIRPORTS,
  applyFnPattern, DEFAULT_FN_PATTERN, effectivePerDiemDays,
  dayElapsedHours, timecardHours, cardHours, fmtHours, OT_AFTER_HOURS,
});
