// ===== Teuanjai Radio — Attendance Database layer =====
// เก็บ session การเข้าร่วมจริงทุกครั้งที่ผู้ใช้ล็อกอิน/ฟัง/ออก ลงใน localStorage
// โครงสร้างนี้พร้อมสลับไปใช้ backend จริง (Firebase/Sheets/REST) ได้ทันที
//
// record shape:
// { id, branchCode, branchName, date:"YYYY-MM-DD",
//   checkIn:ts, checkOut:ts|null, totalMs:Number, deviceId, demo:bool }

const DB_KEY = "teuanjai_db_v1";
const DEVICE_KEY = "teuanjai_device_v1";
const SEED_FLAG = "teuanjai_seed_v1";
const EVENT_START_H = 8, EVENT_END_H = 20;

function rngFrom(seed) {
  let a = seed >>> 0;
  return function () {
    a |= 0; a = (a + 0x6D2B79F5) | 0;
    let t = Math.imul(a ^ (a >>> 15), 1 | a);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}

// ---- date helpers (local) ----
function dateKey(ts) {
  const d = new Date(ts);
  return d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate());
}
function keyToDate(key) {
  const [y, m, d] = key.split("-").map(Number);
  return new Date(y, m - 1, d, 0, 0, 0, 0);
}
function hourTs(dateKey, hour, min) {
  const d = keyToDate(dateKey);
  d.setHours(hour, min || 0, 0, 0);
  return d.getTime();
}
function shiftDateKey(key, deltaDays) {
  const d = keyToDate(key);
  d.setDate(d.getDate() + deltaDays);
  return dateKey(d.getTime());
}
const THAI_MONTHS_FULL = ["มกราคม","กุมภาพันธ์","มีนาคม","เมษายน","พฤษภาคม","มิถุนายน","กรกฎาคม","สิงหาคม","กันยายน","ตุลาคม","พฤศจิกายน","ธันวาคม"];
function fmtThaiDate(key) {
  const d = keyToDate(key);
  return d.getDate() + " " + ["ม.ค.","ก.พ.","มี.ค.","เม.ย.","พ.ค.","มิ.ย.","ก.ค.","ส.ค.","ก.ย.","ต.ค.","พ.ย.","ธ.ค."][d.getMonth()] + " " + (d.getFullYear() + 543);
}
function fmtThaiDateLong(key) {
  const d = keyToDate(key);
  const days = ["อาทิตย์","จันทร์","อังคาร","พุธ","พฤหัสบดี","ศุกร์","เสาร์"];
  return "วัน" + days[d.getDay()] + "ที่ " + d.getDate() + " " + THAI_MONTHS_FULL[d.getMonth()] + " " + (d.getFullYear() + 543);
}

// ---- device id ----
function getDeviceId() {
  try {
    let id = localStorage.getItem(DEVICE_KEY);
    if (!id) { id = "DV-" + Math.random().toString(36).slice(2, 8).toUpperCase(); localStorage.setItem(DEVICE_KEY, id); }
    return id;
  } catch (e) { return "DV-LOCAL"; }
}

// ---- DB CRUD ----
function dbLoad() {
  try { const raw = localStorage.getItem(DB_KEY); return raw ? JSON.parse(raw) : []; }
  catch (e) { return []; }
}
function dbSave(records) {
  try {
    localStorage.setItem(DB_KEY, JSON.stringify(records));
    return true;
  } catch (e) {
    // localStorage เต็ม (QuotaExceeded) — ตัดข้อมูลเก่าออกแล้วลองใหม่
    try {
      const today = dateKey(Date.now());
      // เก็บเฉพาะ record ที่ยังเปิดอยู่ + ของวันนี้ + ที่ใหม่ที่สุด
      let kept = records.filter((r) => !r.closed || r.date === today);
      const old = records.filter((r) => r.closed && r.date !== today)
        .sort((a, b) => (b.checkIn || 0) - (a.checkIn || 0));
      // ค่อยๆ ใส่ของเก่ากลับเท่าที่บันทึกได้
      for (let i = 0; i < old.length; i++) {
        const trial = kept.concat(old.slice(0, i + 1));
        try { localStorage.setItem(DB_KEY, JSON.stringify(trial)); kept = trial; }
        catch (e2) { break; }
      }
      try { localStorage.setItem(DB_KEY, JSON.stringify(kept)); } catch (e3) {}
      return false;
    } catch (e4) { return false; }
  }
}

// เริ่ม session: reuse record ที่เปิดค้างของ (สาขา+อุปกรณ์+วันนี้) ถ้ามี ไม่งั้นสร้างใหม่
function dbStartSession(branchCode, branchName) {
  const recs = dbLoad();
  const dev = getDeviceId();
  const today = dateKey(Date.now());
  let r = recs.find((x) => x.date === today && x.branchCode === branchCode && x.deviceId === dev && !x.closed);
  if (r) {
    r.checkOut = null; dbSave(recs);
    if (window.Api) Api.startSession({ id: r.id, branch_code: branchCode, member_name: branchName, device_id: dev, date: today });
    return r.id;
  }
  const id = "S-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 5);
  recs.push({ id, branchCode, branchName, date: today, checkIn: Date.now(), checkOut: null, totalMs: 0, deviceId: dev, closed: false, demo: false });
  dbSave(recs);
  // mirror ไปยัง D1 ผ่าน /api/session (สำหรับมอนิเตอร์ admin)
  if (window.Api) Api.startSession({ id, branch_code: branchCode, member_name: branchName, device_id: dev, date: today });
  return id;
}
// อัปเดตเวลาฟังสะสมแบบเรียลไทม์
function dbTouch(id, totalMs) {
  if (!id) return;
  const recs = dbLoad();
  const r = recs.find((x) => x.id === id);
  if (r) { r.totalMs = totalMs; r.lastSeen = Date.now(); dbSave(recs); }
  if (window.Api) Api.updateSession(id, { total_listen_ms: totalMs });
}
// ปิด session (เช็คเอาท์)
function dbEndSession(id, totalMs) {
  if (!id) return;
  const recs = dbLoad();
  const r = recs.find((x) => x.id === id);
  if (r) { if (typeof totalMs === "number") r.totalMs = totalMs; r.checkOut = Date.now(); r.closed = true; dbSave(recs); }
  if (window.Api) Api.updateSession(id, { total_listen_ms: totalMs, check_out: Date.now() });
}

// แคชจาก D1 ต่อวัน — แอดมินจะ refresh ทุกครั้งที่เปลี่ยนวัน + ทุก 5 วินาที (ในหน้าภาพรวม)
const _remoteCache = {};
function dbRecordsForDate(key) {
  // โหมดแอดมิน + มีข้อมูลรีโมตของวันนี้ → ใช้ของจาก D1 (cross-device)
  if (_remoteCache[key] && _remoteCache[key].records) return _remoteCache[key].records;
  return dbLoad().filter((r) => r.date === key);
}
// ดึงข้อมูล session ของวันนี้จาก D1 — admin only (ใช้ token ที่ตั้งไว้ตอน login)
async function dbFetchRemote(key) {
  if (!window.__adminToken) return false;
  try {
    const r = await fetch("/api/sessions?date=" + encodeURIComponent(key), {
      headers: { "x-admin-token": window.__adminToken },
      cache: "no-store",
    });
    if (!r.ok) return false;
    const data = await r.json();
    if (data && Array.isArray(data.records)) {
      _remoteCache[key] = { records: data.records, at: Date.now() };
      return true;
    }
  } catch (e) {}
  return false;
}
function dbAvailableDates() {
  const set = {};
  dbLoad().forEach((r) => { set[r.date] = (set[r.date] || 0) + 1; });
  return Object.keys(set).sort();
}

// ---- aggregate ----
function computeKPIs(records, refNow) {
  const total = Object.keys(MEMBERS).length;
  const branches = {};
  let durSum = 0, durCount = 0;
  records.forEach((r) => {
    branches[r.branchCode] = true;
    durSum += r.totalMs; durCount++;
  });
  const joined = Object.keys(branches).length;
  return {
    total, joined, notJoined: Math.max(0, total - joined),
    avgMs: durCount ? durSum / durCount : 0,
    sessions: durCount,
  };
}

// hourly arrays (08:00..20:00) for selected date
function computeHourly(records, key, refNow) {
  const hours = [], idxs = [];
  for (let h = EVENT_START_H; h <= EVENT_END_H; h++) { hours.push(pad2(h) + ":00"); idxs.push(h); }

  const cumulative = [], active = [];
  idxs.forEach((h) => {
    const hourEnd = hourTs(key, h, 59) + 999;
    const hourStart = hourTs(key, h, 0);
    // cumulative distinct branches that had checked-in by end of this hour
    const cset = {}; const aset = {};
    records.forEach((r) => {
      if (r.checkIn <= hourEnd) cset[r.branchCode] = true;
      const effEnd = r.checkOut != null ? r.checkOut : (r.lastSeen || refNow);
      if (r.checkIn < hourEnd && effEnd >= hourStart) aset[r.branchCode] = true;
    });
    cumulative.push(Object.keys(cset).length);
    active.push(Object.keys(aset).length);
  });
  return { hours, idxs, cumulative, active };
}

// ---- export ----
function csvEscape(v) {
  const s = String(v == null ? "" : v);
  return /[",\n]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s;
}

// แยกวันที่/เวลา สำหรับ Export
function splitDate(ts) { return ts ? new Date(ts).toLocaleDateString("th-TH") : ""; }
function splitTime(ts) { return ts ? fmtTimeOnly(ts) : ""; }

// ---- Excel (SpreadsheetML 2003) — รองรับหลายชีทในไฟล์เดียว ----
function xmlEsc(s) {
  return String(s == null ? "" : s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}
function wbCell(v) {
  if (typeof v === "number" && isFinite(v)) return '<Cell><Data ss:Type="Number">' + v + "</Data></Cell>";
  return '<Cell><Data ss:Type="String">' + xmlEsc(v) + "</Data></Cell>";
}
function wbHeadCell(v) { return '<Cell ss:StyleID="hdr"><Data ss:Type="String">' + xmlEsc(v) + "</Data></Cell>"; }
function buildWorkbook(sheets) {
  let x = '<?xml version="1.0" encoding="UTF-8"?>\n<?mso-application progid="Excel.Sheet"?>\n';
  x += '<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">';
  x += '<Styles><Style ss:ID="hdr"><Font ss:Bold="1"/><Interior ss:Color="#D6E4F0" ss:Pattern="Solid"/></Style></Styles>';
  sheets.forEach((sh) => {
    x += '<Worksheet ss:Name="' + xmlEsc(sh.name) + '"><Table>';
    x += "<Row>" + sh.headers.map(wbHeadCell).join("") + "</Row>";
    sh.rows.forEach((r) => { x += "<Row>" + r.map(wbCell).join("") + "</Row>"; });
    x += "</Table></Worksheet>";
  });
  x += "</Workbook>";
  return x;
}

// ชีทการเข้าร่วม (เช็คอิน/เช็คเอาท์ แยกวันที่-เวลา คนละคอลัมน์)
function attendanceSheet(name, recs) {
  const headers = ["รหัสสาขา","ชื่อสาขา","วันที่","เช็คอิน-วันที่","เช็คอิน-เวลา","เช็คเอาท์-วันที่","เช็คเอาท์-เวลา","เวลาฟังรวม","อยู่ในระบบ","อุปกรณ์","สถานะ"];
  const rows = recs.map((r) => {
    const inSystem = (r.checkOut || r.lastSeen || Date.now()) - r.checkIn;
    return [
      r.branchCode, r.branchName, splitDate(keyToDate(r.date).getTime()),
      splitDate(r.checkIn), splitTime(r.checkIn),
      r.checkOut ? splitDate(r.checkOut) : "กำลังใช้งาน",
      r.checkOut ? splitTime(r.checkOut) : "",
      fmtHMS(r.totalMs), fmtHMS(Math.max(0, inSystem)),
      r.deviceId, r.demo ? "ตัวอย่าง" : "จริง",
    ];
  });
  return { name, headers, rows };
}
// ชีทสาขาที่ไม่เข้าร่วม (สำหรับวันที่เลือก) — รูปแบบเวลาเหมือนชีทการเข้าร่วม
function notJoinedSheet(key) {
  const present = {};
  dbRecordsForDate(key).forEach((r) => { present[r.branchCode] = 1; });
  const headers = ["รหัสสาขา","ชื่อสาขา","วันที่","เวลาฟังรวม","อยู่ในระบบ","สถานะ"];
  const dstr = splitDate(keyToDate(key).getTime());
  const rows = Object.keys(MEMBERS).filter((c) => !present[c]).map((c) => [c, MEMBERS[c], dstr, "00:00:00", "00:00:00", "ไม่เข้าร่วม"]);
  return { name: "ไม่เข้าร่วม", headers, rows };
}

function downloadFile(filename, text, mime) {
  try {
    const blob = new Blob([text], { type: (mime || "text/plain") + ";charset=utf-8" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url; a.download = filename;
    document.body.appendChild(a); a.click();
    setTimeout(() => { document.body.removeChild(a); URL.revokeObjectURL(url); }, 200);
    return true;
  } catch (e) { return false; }
}

// ---- seed realistic history (ครั้งเดียว) ----
function dbSeedIfEmpty() {
  try { if (localStorage.getItem(SEED_FLAG)) return; } catch (e) {}
  const existing = dbLoad();
  if (existing.length > 0) { try { localStorage.setItem(SEED_FLAG, "1"); } catch (e) {} return; }

  const codes = Object.keys(MEMBERS);
  const out = [];
  const todayKey = dateKey(Date.now());
  const nowH = new Date().getHours() + new Date().getMinutes() / 60;

  for (let dayBack = 6; dayBack >= 0; dayBack--) {
    const key = shiftDateKey(todayKey, -dayBack);
    const isToday = dayBack === 0;
    const seed = parseInt(key.replace(/-/g, ""), 10);
    const rng = rngFrom(seed);

    // วันนี้ก่อนเริ่มกิจกรรม -> ยังไม่มีข้อมูล
    const cutoffH = isToday ? Math.min(EVENT_END_H, nowH) : EVENT_END_H;
    if (isToday && nowH < EVENT_START_H) continue;

    // เลือกสาขาที่เข้าร่วม ~62-74%
    const joinRate = 0.62 + rng() * 0.12;
    codes.forEach((code) => {
      if (rng() > joinRate) return;
      const devices = rng() < 0.20 ? (rng() < 0.5 ? 2 : 3) : 1;
      for (let dv = 0; dv < devices; dv++) {
        // check-in เน้นช่วงเช้า
        const r1 = rng();
        let inH = EVENT_START_H + (r1 < 0.55 ? rng() * 3 : 3 + rng() * (EVENT_END_H - EVENT_START_H - 3));
        if (inH > cutoffH - 0.2) inH = Math.max(EVENT_START_H, cutoffH - 0.2 - rng());
        const checkIn = hourTs(key, Math.floor(inH), Math.floor((inH % 1) * 60));

        const durH = 0.5 + rng() * 7.5;
        let outTs = checkIn + durH * 3600 * 1000;
        const maxTs = hourTs(key, EVENT_END_H, 0);
        let checkOut, totalMs, closed;
        const stillActive = isToday && (rng() < 0.30) && outTs > hourTs(key, Math.floor(cutoffH), 0);
        if (stillActive) {
          checkOut = null; closed = false;
          totalMs = Math.max(0, Math.min(Date.now(), maxTs) - checkIn) * (0.75 + rng() * 0.2);
        } else {
          if (outTs > maxTs) outTs = maxTs;
          if (isToday && outTs > Date.now()) outTs = Date.now();
          checkOut = outTs; closed = true;
          totalMs = Math.max(0, (checkOut - checkIn)) * (0.7 + rng() * 0.25);
        }
        out.push({
          id: "SD-" + key + "-" + code + "-" + dv,
          branchCode: code, branchName: MEMBERS[code], date: key,
          checkIn, checkOut, totalMs: Math.round(totalMs),
          deviceId: "DV-" + code + (devices > 1 ? "-" + (dv + 1) : ""),
          lastSeen: stillActive ? Date.now() : checkOut,
          closed, demo: true,
        });
      }
    });
  }
  dbSave(out);
  try { localStorage.setItem(SEED_FLAG, "1"); } catch (e) {}
}

function dbAvailableDatesIncludingRemote() {
  const set = {};
  dbLoad().forEach((r) => { set[r.date] = (set[r.date] || 0) + 1; });
  Object.keys(_remoteCache).forEach((d) => { (_remoteCache[d].records || []).forEach((r) => { set[r.date] = (set[r.date] || 0) + 1; }); });
  return Object.keys(set).sort();
}

Object.assign(window, {
  EVENT_START_H, EVENT_END_H,
  dateKey, keyToDate, hourTs, shiftDateKey, fmtThaiDate, fmtThaiDateLong,
  getDeviceId, dbLoad, dbSave, dbStartSession, dbTouch, dbEndSession,
  dbRecordsForDate, dbAvailableDates, dbAvailableDatesIncludingRemote, dbFetchRemote,
  computeKPIs, computeHourly,
  buildWorkbook, attendanceSheet, notJoinedSheet, downloadFile, dbSeedIfEmpty,
});
