/* =========================================================================
   store.jsx — data model, seed, persistence, payroll calc, formatters
   ========================================================================= */
const { createContext, useContext, useState, useEffect, useMemo, useCallback, useRef } = React;

/* ---------- keys ---------- */
const LS_DB   = 'kharaeng.db.v3';
const LS_AUTH = 'kharaeng.auth.v1';

/* ---------- ids ---------- */
const uid = (p='id') => p + '_' + Math.random().toString(36).slice(2,9);

/* ---------- date helpers ---------- */
const TH_MONTHS = ['มกราคม','กุมภาพันธ์','มีนาคม','เมษายน','พฤษภาคม','มิถุนายน','กรกฎาคม','สิงหาคม','กันยายน','ตุลาคม','พฤศจิกายน','ธันวาคม'];
const TH_MONTHS_SHORT = ['ม.ค.','ก.พ.','มี.ค.','เม.ย.','พ.ค.','มิ.ย.','ก.ค.','ส.ค.','ก.ย.','ต.ค.','พ.ย.','ธ.ค.'];
const TH_DOW = ['อา.','จ.','อ.','พ.','พฤ.','ศ.','ส.'];

const _now = new Date();
const TODAY = new Date(_now.getFullYear(), _now.getMonth(), _now.getDate()); // วันที่จริงของวันนี้ (ตัดเวลาออก)
const pad = n => String(n).padStart(2,'0');
const ymd = d => `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}`;
const ym  = d => `${d.getFullYear()}-${pad(d.getMonth()+1)}`;
const parseYMD = s => { const [y,m,d]=s.split('-').map(Number); return new Date(y,m-1,d); };
const thaiDate = (s) => { const d=parseYMD(s); return `${d.getDate()} ${TH_MONTHS_SHORT[d.getMonth()]} ${d.getFullYear()+543}`; };
const thaiDateLong = (s) => { const d=parseYMD(s); return `${d.getDate()} ${TH_MONTHS[d.getMonth()]} พ.ศ. ${d.getFullYear()+543}`; };
const thaiMonthLabel = (m) => { const [y,mo]=m.split('-').map(Number); return `${TH_MONTHS[mo-1]} ${y+543}`; };
const daysInMonth = (m) => { const [y,mo]=m.split('-').map(Number); return new Date(y,mo,0).getDate(); };

/* ---------- money ---------- */
const baht = (n, dec=2) => '฿\u202f' + (Number(n)||0).toLocaleString('th-TH',{minimumFractionDigits:dec,maximumFractionDigits:dec});
const num  = (n, dec=0) => (Number(n)||0).toLocaleString('th-TH',{minimumFractionDigits:dec,maximumFractionDigits:dec});

/* ---------- pay periods (งวด — ตัดวิกวันที่ 10 และ 25 ของทุกเดือน) ---------- */
function periodForCut(y, mo, day){
  const cut = new Date(y, mo, day);
  let start, end;
  if(day===10){ start = new Date(y, mo-1, 26); end = new Date(y, mo, 10); }
  else        { start = new Date(y, mo, 11);   end = new Date(y, mo, 25); }
  return {
    id: ymd(cut), cutDate: ymd(cut), start: ymd(start), end: ymd(end),
    label: `งวดตัด ${day} ${TH_MONTHS_SHORT[mo]} ${y+543}`,
    range: `${thaiDate(ymd(start))} – ${thaiDate(ymd(end))}`,
  };
}
function listPeriods(n=8){
  const res=[];
  for(let off=0; off<14 && res.length<n; off++){
    const d = new Date(TODAY.getFullYear(), TODAY.getMonth()-off, 1);
    for(const day of [25,10]){
      const p = periodForCut(d.getFullYear(), d.getMonth(), day);
      if(parseYMD(p.start) <= TODAY && !res.find(x=>x.id===p.id) && res.length<n) res.push(p);
    }
  }
  return res;
}
const currentPeriod = () => listPeriods(1)[0];

/* ---------- worker type labels ---------- */
const TYPE = {
  daily:     { key:'daily',     th:'รายวัน',     short:'รายวัน',   tone:'brand' },
  monthly:   { key:'monthly',   th:'รายเดือน',   short:'รายเดือน', tone:'hazard' },
  permanent: { key:'permanent', th:'พนักงานประจำ', short:'ประจำ',    tone:'ok' },
};
const STATUS = {
  full: { key:'full', th:'มาเต็มวัน', val:1,   tone:'ok' },
  half: { key:'half', th:'ครึ่งวัน',  val:0.5, tone:'warn' },
  absent:{ key:'absent', th:'ขาดงาน', val:0,   tone:'bad' },
};

/* ---------- site types ---------- */
const SITE_TYPE = {
  construction: { key:'construction', th:'ไซต์ก่อสร้าง', icon:'site',  tone:'brand'  },
  shop:         { key:'shop',         th:'ร้านค้า',      icon:'shop',  tone:'hazard' },
  fuel:         { key:'fuel',         th:'ปั๊มน้ำมัน',    icon:'fuel',  tone:'ok'     },
};

/* ---------- work types (ประเภทงาน) — grouped, with std man-days benchmark per typical unit ---------- */
const WORK_GROUPS = [
  { key:'structure', th:'งานโครงสร้าง',        tone:'brand'  },
  { key:'wall',      th:'งานก่อ-ฉาบ-ผนัง',     tone:'hazard' },
  { key:'finish',    th:'งานพื้น-ผิว-สี',       tone:'ok'     },
  { key:'roof',      th:'งานหลังคา-ฝ้า',        tone:'warn'   },
  { key:'opening',   th:'ประตู-หน้าต่าง',       tone:'brand'  },
  { key:'system',    th:'งานระบบ',             tone:'hazard' },
  { key:'civil',     th:'งานโยธา-ถนน-น้ำ',     tone:'ok'     },
  { key:'machine',   th:'งานเครื่องจักร-ขนส่ง', tone:'warn'   },
  { key:'retail',    th:'งานร้านค้า-บริการ',    tone:'brand'  },
];
const WORK_TYPES = [
  // โครงสร้าง
  { id:'t_found',   group:'structure', name:'งานฐานราก / เสาเข็ม',        std:25 },
  { id:'t_rebar',   group:'structure', name:'งานผูกเหล็กเสริม',           std:18 },
  { id:'t_form',    group:'structure', name:'งานไม้แบบ / เข้าแบบหล่อ',     std:16 },
  { id:'t_pour',    group:'structure', name:'งานเทคอนกรีต',              std:10 },
  { id:'t_frame',   group:'structure', name:'งานเสา-คาน-พื้น คสล.',       std:22 },
  // ก่อ-ฉาบ-ผนัง
  { id:'t_brick',   group:'wall',      name:'งานก่ออิฐ',                  std:20 },
  { id:'t_plaster', group:'wall',      name:'งานฉาบปูน',                  std:22 },
  { id:'t_tilewall',group:'wall',      name:'งานปูกระเบื้องผนัง',          std:14 },
  // พื้น-ผิว-สี
  { id:'t_tilefloor',group:'finish',   name:'งานปูกระเบื้องพื้น',          std:16 },
  { id:'t_paint',   group:'finish',    name:'งานทาสี',                    std:18 },
  { id:'t_water',   group:'finish',    name:'งานกันซึม',                  std:6  },
  // หลังคา-ฝ้า
  { id:'t_roofstr', group:'roof',      name:'งานโครงหลังคา + มุงกระเบื้อง', std:18 },
  { id:'t_eaves',   group:'roof',      name:'งานไม้ฝา / ฝ้าชายคา',         std:10 },
  { id:'t_ceiling', group:'roof',      name:'งานฝ้าเพดานภายใน',           std:12 },
  // ประตู-หน้าต่าง
  { id:'t_alum',    group:'opening',   name:'งานประตู-หน้าต่างอลูมิเนียม',  std:9  },
  { id:'t_door',    group:'opening',   name:'งานติดตั้งประตู-วงกบไม้',      std:6  },
  // ระบบ
  { id:'t_elec',    group:'system',    name:'งานไฟฟ้า',                   std:14 },
  { id:'t_plumb',   group:'system',    name:'งานประปา-สุขาภิบาล',         std:12 },
  { id:'t_septic',  group:'system',    name:'งานถังบำบัด + สุขภัณฑ์',       std:7  },
  // โยธา-ถนน-น้ำ
  { id:'t_road',    group:'civil',     name:'งานถนน คสล.',               std:30 },
  { id:'t_drain',   group:'civil',     name:'งานรางระบายน้ำ',             std:16 },
  { id:'t_weir',    group:'civil',     name:'งานฝาย มข.2528',            std:35 },
  { id:'t_weir2',   group:'civil',     name:'งานฝายน้ำล้นแบบรถผ่าน',       std:45 },
  { id:'t_earth',   group:'civil',     name:'งานถมดิน / ปรับพื้นที่',       std:8  },
  { id:'t_demo',    group:'civil',     name:'งานรื้อถอน',                 std:5  },
  // เครื่องจักร-ขนส่ง
  { id:'t_t6',      group:'machine',   name:'ขับรถ 6 ล้อ',               std:0  },
  { id:'t_t10',     group:'machine',   name:'ขับรถ 10 ล้อ',              std:0  },
  { id:'t_exbig',   group:'machine',   name:'แม็คโครใหญ่',                std:0  },
  { id:'t_exsmall', group:'machine',   name:'แม็คโครเล็ก',                std:0  },
  // ร้านค้า-บริการ
  { id:'t_shop',    group:'retail',    name:'ขายของหน้าร้าน',             std:0  },
  { id:'t_fuel',    group:'retail',    name:'เด็กปั๊ม (ขายน้ำมัน)',         std:0  },
];
/* live registry — replaced by DB rows once loaded (so work types are editable) */
let _liveWorkTypes = WORK_TYPES.slice();
const setLiveWorkTypes = (arr) => { _liveWorkTypes = (arr && arr.length) ? arr.map(t=>({ ...t, group: t.group || t.grp })) : WORK_TYPES.slice(); window.__workTypes = _liveWorkTypes; };
const getWorkTypes = () => _liveWorkTypes;
setLiveWorkTypes(WORK_TYPES);
const workTypeById = (id) => _liveWorkTypes.find(t=>t.id===id);
const workGroupOf = (id) => { const t=workTypeById(id); return t? WORK_GROUPS.find(g=>g.key===(t.group||t.grp)) : null; };
const holidayByDate = (db, date) => (db.holidays||[]).find(h=>h.date===date) || null;

/* =========================================================================
   SEED — realistic mock data
   ========================================================================= */
function seed() {
  const settings = {
    company: 'หจก.เจริญธุรกิจการก่อสร้าง',
    address: '88/12 หมู่ 4 ต.บางพลีใหญ่ อ.บางพลี จ.สมุทรปราการ 10540',
    taxId: '0 1035 58000 12 3',
    phone: '02-750-1234',
    ssRate: 0.05,        // ประกันสังคม 5%
    ssCap: 750,          // เพดานหักประกันสังคม/เดือน
    otMultiplier: 1.5,
    slipNote: 'สลิปนี้ออกโดยระบบ กรุณาเก็บไว้เป็นหลักฐาน',
    payday: 5,
  };

  const sites = [
    { id:'st1', code:'BKK-01', name:'คอนโดสุขุมวิท 71', location:'สุขุมวิท 71 กรุงเทพฯ', type:'construction', budget:450000, active:true },
    { id:'st2', code:'BP-02',  name:'บ้านจัดสรร บางนา เฟส 2', location:'บางนา-ตราด กม.15', type:'construction', budget:300000, active:true },
    { id:'st3', code:'RY-03',  name:'โกดังสินค้า ระยอง', location:'นิคมฯ มาบตาพุด ระยอง', type:'construction', budget:200000, active:true },
    { id:'st4', code:'NB-04',  name:'ต่อเติมอาคาร นนทบุรี', location:'งามวงศ์วาน นนทบุรี', type:'construction', budget:0, active:false },
    { id:'st5', code:'SHOP-1', name:'ร้านเจริญวัสดุ-ของชำ', location:'หน้าปากซอย', type:'shop', budget:60000, active:true },
    { id:'st6', code:'FUEL-1', name:'ปั๊มน้ำมันเจริญ', location:'ริมถนนสายหลัก', type:'fuel', budget:50000, active:true },
  ];

  const workers = [
    { id:'w1', code:'EMP-001', name:'สมชาย ใจดี',      nickname:'ชาย',  type:'permanent', dailyRate:0,   monthlyRate:18000, otRate:120, group:'ทีมโครงสร้าง', phone:'081-234-5678', startDate:'2022-03-01', active:true },
    { id:'w2', code:'EMP-002', name:'ประเสริฐ มั่นคง',  nickname:'เสริฐ', type:'monthly',   dailyRate:0,   monthlyRate:15000, otRate:100, group:'ทีมโครงสร้าง', phone:'082-345-6789', startDate:'2023-06-15', active:true },
    { id:'w3', code:'EMP-003', name:'วิชัย แข็งแรง',     nickname:'ชัย',  type:'daily',     dailyRate:550, monthlyRate:0,     otRate:90,  group:'ทีมปูน',       phone:'083-456-7890', startDate:'2024-01-10', active:true },
    { id:'w4', code:'EMP-004', name:'บุญมา ทองคำ',      nickname:'มา',   type:'daily',     dailyRate:600, monthlyRate:0,     otRate:95,  group:'ทีมปูน',       phone:'084-567-8901', startDate:'2023-11-20', active:true },
    { id:'w5', code:'EMP-005', name:'สุนทร ก่อสร้าง',    nickname:'น้อย', type:'daily',     dailyRate:500, monthlyRate:0,     otRate:85,  group:'ทีมรับเหมา A', phone:'085-678-9012', startDate:'2024-08-05', active:true },
    { id:'w6', code:'EMP-006', name:'อนุชา พากเพียร',   nickname:'นุช', type:'daily',     dailyRate:520, monthlyRate:0,     otRate:85,  group:'ทีมรับเหมา A', phone:'086-789-0123', startDate:'2025-02-01', active:true },
    { id:'w7', code:'EMP-007', name:'ธนพล ไฟฟ้า',       nickname:'พล',   type:'monthly',   dailyRate:0,   monthlyRate:16500, otRate:110, group:'ทีมงานระบบ',   phone:'087-890-1234', startDate:'2023-09-12', active:true },
    { id:'w8', code:'EMP-008', name:'จิราพร บัญชี',      nickname:'แอ๋ม', type:'permanent', dailyRate:0,   monthlyRate:20000, otRate:0,   group:'สำนักงาน',     phone:'088-901-2345', startDate:'2021-05-01', active:true },
    { id:'w9', code:'EMP-009', name:'มานพ ขยันยิ่ง',    nickname:'นพ',   type:'daily',     dailyRate:480, monthlyRate:0,     otRate:80,  group:'ทีมเครื่องจักร', phone:'089-012-3456', startDate:'2024-04-18', active:true },
    { id:'w10',code:'EMP-010', name:'สมหญิง ค้าขาย',    nickname:'หญิง', type:'monthly',   dailyRate:0,   monthlyRate:12000, otRate:60,  group:'ร้านค้า',      phone:'090-123-4567', startDate:'2024-07-01', active:true },
  ];
  workers.forEach(w=>{ w.photo=''; });

  Object.assign(workers[0], { bankName:'กสิกรไทย',   bankAccount:'012-3-45678-9' });
  Object.assign(workers[1], { bankName:'ไทยพาณิชย์', bankAccount:'401-2-34567-8' });
  Object.assign(workers[6], { bankName:'กรุงไทย',    bankAccount:'987-6-54321-0' });
  Object.assign(workers[7], { bankName:'กสิกรไทย',   bankAccount:'111-2-33344-5' });
  workers.forEach(w=>{ if(w.bankName===undefined){ w.bankName=''; w.bankAccount=''; } });

  // ผังงานหลักของแต่ละคน (ใช้สุ่มงานรายวันให้สมจริง)
  const taskPlan = {
    w1:['t_found','t_rebar','t_frame','t_pour'],
    w2:['t_rebar','t_form','t_frame'],
    w3:['t_brick','t_plaster','t_tilewall'],
    w4:['t_plaster','t_tilefloor','t_paint'],
    w5:['t_brick','t_plaster','t_roofstr'],
    w6:['t_road','t_drain','t_weir'],
    w7:['t_elec','t_plumb','t_septic'],
    w8:['t_shop'],
    w9:['t_exbig','t_t10','t_earth'],
    w10:['t_shop','t_fuel'],
  };

  // attendance — สร้างย้อนหลังตั้งแต่ 26 เม.ย. ถึงวันนี้ (ครอบหลายงวด)
  const attendance = [];
  const siteAssign = { w1:'st1', w2:'st1', w3:'st2', w4:'st2', w5:'st1', w6:'st3', w7:'st3', w8:'st1', w9:'st2', w10:'st5' };
  for (const w of workers) {
    for (let dt=new Date(2026,3,26); dt<=TODAY; dt.setDate(dt.getDate()+1)) {
      const dow = dt.getDay();
      if (dow===0) continue; // วันอาทิตย์หยุด
      const day = dt.getDate();
      let status='full', otHours=0;
      const r = (w.id.charCodeAt(1)+day*3)%10;
      if (r===0) status='absent';
      else if (r===1) status='half';
      if (status!=='absent' && (day%2===0) && w.type!=='permanent') otHours = (r%3===0)?2:(r%4===0?3:0);
      if (w.type==='monthly' || w.type==='permanent') otHours = (day%3===0)?1:0;
      let site = siteAssign[w.id];
      if (w.id==='w5' && day>3) site='st3';
      if (w.id==='w3' && day===2) site='st1';
      if (w.id==='w10' && day%5===0) site='st6'; // สมหญิงไปช่วยปั๊มบางวัน
      const plan = taskPlan[w.id]||['t_brick'];
      const taskId = plan[(day+ w.id.length)%plan.length];
      attendance.push({ id:uid('att'), date:ymd(dt), workerId:w.id, siteId:site, taskId, status, otHours, otFlat:0, note:'' });
    }
  }

  // adjustments (รายการหัก / เบิก) — ผูกกับงวด — คนทำบัญชีเพิ่มเอง
  const adjustments = [
    { id:uid('adj'), workerId:'w3', period:'2026-06-10', kind:'advance', label:'เบิกระหว่างงวด', amount:2000 },
    { id:uid('adj'), workerId:'w4', period:'2026-06-10', kind:'advance', label:'เบิกค่าน้ำมัน',   amount:800  },
    { id:uid('adj'), workerId:'w1', period:'2026-06-10', kind:'social',  label:'ประกันสังคม',     amount:750  },
    { id:uid('adj'), workerId:'w1', period:'2026-06-10', kind:'other',   label:'หักค่าที่พัก',     amount:1500 },
    { id:uid('adj'), workerId:'w5', period:'2026-06-10', kind:'advance', label:'เบิกระหว่างงวด',   amount:1000 },
  ];

  return { settings, sites, workers, attendance, adjustments, workTypes:WORK_TYPES };
}

/* =========================================================================
   NOTE: data now lives in Cloudflare D1 (loaded via /api/state in StoreProvider).
   seed() above is kept only as a reference dataset — the canonical copy used by
   the server is functions/api/seed.js (gen_schema.mjs + the /api/reset endpoint).
   ========================================================================= */

/* =========================================================================
   PAYROLL CALC
   ========================================================================= */
function attForWorkerPeriod(db, workerId, period){
  return db.attendance.filter(a => a.workerId===workerId && a.date>=period.start && a.date<=period.end);
}

function otPayOf(worker, a){
  return (Number(a.otHours)||0) * (Number(worker.otRate)||0) + (Number(a.otFlat)||0);
}

function computePayroll(db, workerId, period){
  const w = db.workers.find(x=>x.id===workerId);
  if(!w) return null;
  const atts = attForWorkerPeriod(db, workerId, period);

  let dayUnits = 0, otHours = 0, otPay = 0;
  const bySite = {}; // siteId -> {days, base, ot}
  for(const a of atts){
    const v = STATUS[a.status]?.val ?? 0;
    dayUnits += v;
    otHours += Number(a.otHours)||0;
    const ot = otPayOf(w, a);
    otPay += ot;
    if(!bySite[a.siteId]) bySite[a.siteId] = { days:0, base:0, ot:0 };
    bySite[a.siteId].days += v;
    bySite[a.siteId].ot   += ot;
  }

  let base = 0, perDayBase = 0;
  if(w.type==='daily'){
    perDayBase = w.dailyRate;
    base = dayUnits * w.dailyRate;
  } else {
    base = (w.monthlyRate||0) / 2; // จ่าย 2 งวด/เดือน → ครึ่งหนึ่งต่องวด
    perDayBase = dayUnits>0 ? base/dayUnits : 0; // for allocation
  }
  for(const sid in bySite){
    bySite[sid].base = bySite[sid].days * perDayBase;
  }

  const gross = base + otPay;

  // deductions — ทั้งหมดเพิ่มเองโดยคนทำบัญชี (เบิก / ประกันสังคม / หักอื่นๆ)
  const adjs = db.adjustments.filter(x=>x.workerId===workerId && x.period===period.id);
  const advance = adjs.filter(x=>x.kind==='advance').reduce((s,x)=>s+(Number(x.amount)||0),0);
  const social  = adjs.filter(x=>x.kind==='social').reduce((s,x)=>s+(Number(x.amount)||0),0);
  const other   = adjs.filter(x=>x.kind==='other').reduce((s,x)=>s+(Number(x.amount)||0),0);
  // เบิกเงินรายวัน (กิน-ใช้) ที่บันทึกในหน้าลงเวลา — รวมเป็นรายการหักของงวด
  const dailyAdvance = atts.reduce((s,a)=> s + (Number(a.advance)||0), 0);
  const dailyAdvanceDays = atts.filter(a=>Number(a.advance)>0).map(a=>({ date:a.date, amount:Number(a.advance)||0 })).sort((x,y)=>x.date<y.date?-1:1);
  const deductions = advance + social + other + dailyAdvance;
  const net = gross - deductions;

  const fullDays   = atts.filter(a=>a.status==='full').map(a=>a.date).sort();
  const halfDays   = atts.filter(a=>a.status==='half').map(a=>a.date).sort();
  const absentDays = atts.filter(a=>a.status==='absent').map(a=>a.date).sort();

  return {
    worker:w, period, atts, dayUnits, otHours, otPay, base, gross,
    advance, social, other, dailyAdvance, dailyAdvanceDays, deductions, net, adjs, bySite, perDayBase,
    fullDays, halfDays, absentDays,
    daysPresent: atts.filter(a=>a.status!=='absent').length,
    daysAbsent: absentDays.length,
  };
}

function siteCostReport(db, period){
  const rows = {};
  for(const s of db.sites) rows[s.id] = { site:s, base:0, ot:0, total:0, workers:new Set() };
  for(const w of db.workers){
    const p = computePayroll(db, w.id, period);
    if(!p) continue;
    for(const sid in p.bySite){
      if(!rows[sid]) continue;
      rows[sid].base += p.bySite[sid].base;
      rows[sid].ot   += p.bySite[sid].ot;
      rows[sid].total += p.bySite[sid].base + p.bySite[sid].ot;
      if(p.bySite[sid].days>0) rows[sid].workers.add(w.id);
    }
  }
  return Object.values(rows).map(r=>({ ...r, workerCount:r.workers.size }));
}

/* ---------- worker lifetime stats (สถิติ ขาด / ขยัน) ---------- */
function workerStats(db, workerId){
  const atts = db.attendance.filter(a=>a.workerId===workerId).sort((a,b)=>a.date<b.date?1:-1);
  let full=0, half=0, absent=0, otHours=0, dayUnits=0;
  const taskDays = {};
  const siteDays = {};
  for(const a of atts){
    if(a.status==='full') full++; else if(a.status==='half') half++; else absent++;
    const v = STATUS[a.status]?.val ?? 0;
    dayUnits += v;
    otHours += Number(a.otHours)||0;
    if(a.taskId) taskDays[a.taskId] = (taskDays[a.taskId]||0) + v;
    if(a.siteId) siteDays[a.siteId] = (siteDays[a.siteId]||0) + v;
  }
  const logged = full+half+absent;
  const diligence = logged ? Math.round(((full+half*0.5)/logged)*100) : 0;
  // longest present streak (consecutive logged days not absent)
  let streak=0, best=0;
  const chrono = [...atts].sort((a,b)=>a.date<b.date?-1:1);
  for(const a of chrono){ if(a.status!=='absent'){ streak++; best=Math.max(best,streak); } else streak=0; }
  // earnings across all periods present in data
  const periods = listPeriods(14);
  let lifetimeNet=0;
  const byPeriod = periods.map(pd=>{ const p=computePayroll(db, workerId, pd); return { period:pd, p }; }).filter(x=>x.p && x.p.atts.length>0);
  byPeriod.forEach(x=> lifetimeNet += x.p.net);
  const topTask = Object.entries(taskDays).sort((a,b)=>b[1]-a[1])[0];
  return { atts, full, half, absent, logged, otHours, dayUnits, diligence, bestStreak:best, taskDays, siteDays, byPeriod, lifetimeNet, topTaskId: topTask?topTask[0]:null };
}

/* ---------- site labor across ALL periods + budget ---------- */
function siteAllPeriods(db, siteId, n=8){
  const periods = listPeriods(n);
  const rows = periods.map(pd=>{
    const rep = siteCostReport(db, pd).find(r=>r.site.id===siteId);
    return { period:pd, total:rep?rep.total:0, base:rep?rep.base:0, ot:rep?rep.ot:0, workers:rep?rep.workerCount:0 };
  });
  const total = rows.reduce((s,r)=>s+r.total,0);
  return { rows, total };
}

/* ---------- work-type stats (man-days, cost, vs standard) ---------- */
function workTypeStats(db, period, siteId='all'){
  const map = {};
  for(const t of (db.workTypes||[])) map[t.id] = { type:t, manDays:0, cost:0, ot:0, workers:new Set() };
  const inRange = a => a.date>=period.start && a.date<=period.end && (siteId==='all'||a.siteId===siteId);
  for(const a of db.attendance.filter(inRange)){
    const w = db.workers.find(x=>x.id===a.workerId); if(!w) continue;
    const t = a.taskId && map[a.taskId]; if(!t) continue;
    const v = STATUS[a.status]?.val ?? 0;
    const perDay = w.type==='daily' ? w.dailyRate : (w.monthlyRate||0)/26;
    const ot = otPayOf(w, a);
    t.manDays += v;
    t.cost += v*perDay + ot;
    t.ot += ot;
    if(v>0) t.workers.add(w.id);
  }
  return Object.values(map).filter(r=>r.manDays>0 || r.cost>0)
    .map(r=>({ ...r, workerCount:r.workers.size, vsStd: r.type.std>0 ? r.manDays/r.type.std : null }))
    .sort((a,b)=>b.cost-a.cost);
}

/* =========================================================================
   STORE PROVIDER
   ========================================================================= */
const StoreCtx = createContext(null);
const useDB = () => useContext(StoreCtx);

function StoreProvider({ children, onAuthExpired }){
  const [db, setDb] = useState(null);
  const [error, setError] = useState('');

  const loadState = useCallback(async ()=>{
    const r = await API.get('state');
    if(r.ok){
      setLiveWorkTypes(r.data.workTypes);
      setDb({ ...r.data, workTypes: getWorkTypes(), holidays: r.data.holidays || [] });
      setError('');
    }
    else if(r.status===401){ onAuthExpired && onAuthExpired(); }
    else setError((r.data && r.data.error) || 'โหลดข้อมูลไม่สำเร็จ');
  }, [onAuthExpired]);

  useEffect(()=>{ loadState(); }, [loadState]);

  /* Each mutation updates local state optimistically AND persists to the API.
     Signatures match the old localStorage store, so screens need no changes. */
  const api = useMemo(()=>{
    if(!db) return null;
    return {
      db, setDb, reload: loadState,
      // workers
      saveWorker: (w) => {
        const rec = w.id ? w : { ...w, id: uid('w'), code: w.code || ('EMP-'+pad(db.workers.length+1)) };
        setDb(d => ({ ...d, workers: d.workers.some(x=>x.id===rec.id) ? d.workers.map(x=>x.id===rec.id?rec:x) : [...d.workers, rec] }));
        API.post('workers', rec);
        return rec;
      },
      removeWorker: (id) => {
        setDb(d => ({ ...d, workers:d.workers.filter(x=>x.id!==id), attendance:d.attendance.filter(a=>a.workerId!==id), adjustments:d.adjustments.filter(a=>a.workerId!==id) }));
        API.del('workers/'+encodeURIComponent(id));
      },
      // sites
      saveSite: (s) => {
        const rec = s.id ? s : { ...s, id: uid('st') };
        setDb(d => ({ ...d, sites: d.sites.some(x=>x.id===rec.id) ? d.sites.map(x=>x.id===rec.id?rec:x) : [...d.sites, rec] }));
        API.post('sites', rec);
        return rec;
      },
      removeSite: (id) => { setDb(d => ({ ...d, sites:d.sites.filter(x=>x.id!==id) })); API.del('sites/'+encodeURIComponent(id)); },
      // attendance (upsert by date + worker)
      upsertAttendance: (rec) => {
        const existing = db.attendance.find(a => a.date===rec.date && a.workerId===rec.workerId);
        const full = existing
          ? { ...existing, ...rec, id: existing.id }
          : { status:'full', otHours:0, otFlat:0, advance:0, siteId:'', taskId:'', note:'', ...rec, id: rec.id || uid('att') };
        setDb(d => {
          const i = d.attendance.findIndex(a => a.date===full.date && a.workerId===full.workerId);
          if(i>=0){ const next=[...d.attendance]; next[i]=full; return {...d, attendance:next}; }
          return {...d, attendance:[...d.attendance, full]};
        });
        API.post('attendance', full);
        return full;
      },
      removeAttendance: (id) => { setDb(d => ({ ...d, attendance:d.attendance.filter(a=>a.id!==id) })); API.del('attendance/'+encodeURIComponent(id)); },
      // adjustments
      addAdjustment: (a) => {
        const rec = { ...a, id: a.id || uid('adj') };
        setDb(d => ({ ...d, adjustments:[...d.adjustments, rec] }));
        API.post('adjustments', rec);
        return rec;
      },
      removeAdjustment: (id) => { setDb(d => ({ ...d, adjustments:d.adjustments.filter(x=>x.id!==id) })); API.del('adjustments/'+encodeURIComponent(id)); },
      // work types (เพิ่ม/แก้ไข/ลบ ประเภทงาน)
      saveWorkType: (t) => {
        const rec = { ...t, id: t.id || uid('t'), group: t.group || t.grp, grp: t.group || t.grp };
        setDb(d => { const next = d.workTypes.some(x=>x.id===rec.id) ? d.workTypes.map(x=>x.id===rec.id?rec:x) : [...d.workTypes, rec]; setLiveWorkTypes(next); return { ...d, workTypes: next }; });
        API.post('work-types', rec);
        return rec;
      },
      removeWorkType: (id) => {
        setDb(d => { const next = d.workTypes.filter(x=>x.id!==id); setLiveWorkTypes(next); return { ...d, workTypes: next }; });
        API.del('work-types/'+encodeURIComponent(id));
      },
      // holidays (วันหยุด)
      saveHoliday: (h) => {
        setDb(d => ({ ...d, holidays: (d.holidays||[]).some(x=>x.date===h.date) ? d.holidays.map(x=>x.date===h.date?h:x) : [...(d.holidays||[]), h] }));
        API.post('holidays', h);
        return h;
      },
      removeHoliday: (date) => { setDb(d => ({ ...d, holidays:(d.holidays||[]).filter(x=>x.date!==date) })); API.del('holidays/'+encodeURIComponent(date)); },
      // settings
      saveSettings: (s) => { setDb(d => ({ ...d, settings:{...d.settings, ...s} })); API.post('settings', s); },
      // danger — reset to sample data (admin only)
      resetAll: async () => { const r = await API.post('reset'); if(r.ok) await loadState(); else alert((r.data && r.data.error) || 'รีเซ็ตไม่สำเร็จ (เฉพาะผู้ดูแลระบบ)'); },
    };
  }, [db, loadState]);

  if(error) return (
    <div className="min-h-screen flex items-center justify-center p-6 text-center">
      <div>
        <div className="text-bad font-display font-bold text-lg mb-2">เกิดข้อผิดพลาด</div>
        <div className="text-haze text-sm mb-4 max-w-sm">{error}</div>
        <Button onClick={loadState} icon="arrow">ลองใหม่</Button>
      </div>
    </div>
  );
  if(!db) return (
    <div className="min-h-screen flex items-center justify-center">
      <div className="text-haze flex items-center gap-3">
        <div className="w-5 h-5 border-2 border-brand border-t-transparent rounded-full animate-spin"></div>
        กำลังโหลดข้อมูล…
      </div>
    </div>
  );

  return <StoreCtx.Provider value={api}>{children}</StoreCtx.Provider>;
}

/* ---------- export ---------- */
Object.assign(window, {
  StoreProvider, useDB,
  uid, ymd, ym, parseYMD, thaiDate, thaiDateLong, thaiMonthLabel, daysInMonth,
  baht, num, TODAY, pad,
  TH_MONTHS, TH_MONTHS_SHORT, TH_DOW, TYPE, STATUS, SITE_TYPE,
  WORK_GROUPS, WORK_TYPES, workTypeById, workGroupOf, getWorkTypes, holidayByDate,
  periodForCut, listPeriods, currentPeriod,
  computePayroll, siteCostReport, attForWorkerPeriod, otPayOf,
  workerStats, siteAllPeriods, workTypeStats,
});
