/* ============================================================
   store.jsx — data model, persistence, spaced repetition
   Pure logic (no React). Exposed as window.Store
   ============================================================
   Expression record:
   {
     id, expression, meaning,
     examples: string[],          // AI-generated applied examples
     situation: string,           // AI: 사용 상황/뉘앙스 (Korean)
     formality: 'formal'|'neutral'|'casual',
     tags: string[],              // 상황/주제 태그
     createdAt, lastReviewedAt, reviewCount,
     nextReviewAt,                // ISO date — when it's due again
     box,                         // Leitner level 0..5 (drives interval)
     difficulty                   // last rating: 'easy'|'good'|'hard'|null
   }
   ============================================================ */

const STORE_KEY = "cnh_expressions_v1";
const VIEW_KEY = "cnh_view_v1";
const ACT_KEY = "cnh_activity_v1";

const DAY = 86400000;
// Leitner intervals (days) per box level
const BOX_DAYS = [1, 2, 4, 8, 16, 35];

const now = () => Date.now();
const todayStart = () => { const d = new Date(); d.setHours(0,0,0,0); return d.getTime(); };
const uid = () => Math.random().toString(36).slice(2, 10);

function load() {
  try {
    const raw = localStorage.getItem(STORE_KEY);
    if (raw) return JSON.parse(raw);
  } catch (e) { /* ignore */ }
  return seed();
}

function persistLocal(list) {
  try { localStorage.setItem(STORE_KEY, JSON.stringify(list)); } catch (e) {}
  return list;
}

/* localStorage is the fast read-through cache; Notion is the source of truth.
   persist() writes the cache immediately, then debounces a push of ONLY the
   records that actually changed (keeps us well under CF's subrequest limit). */
function persist(list) {
  persistLocal(list);
  schedulePush(list);
  return list;
}

/* ---- Notion sync (write-through) ---- */
function _authKey() {
  return (typeof window !== "undefined" && window.Auth && window.Auth.getKey()) || null;
}
function _headers(key) {
  return { "Content-Type": "application/json", "x-app-key": key };
}
function _snapshot(list) {
  const m = {};
  for (const r of list) m[r.id] = JSON.stringify(r);
  return m;
}
let _synced = {};            // id -> JSON string of last server-confirmed state
let _pushTimer = null;

function schedulePush(list) {
  if (!_authKey()) return;   // offline / not authed -> cache only
  if (_pushTimer) clearTimeout(_pushTimer);
  _pushTimer = setTimeout(() => { pushNow(list); }, 1200);
}

async function pushNow(list) {
  const key = _authKey();
  if (!key) return;
  const changed = list.filter((r) => _synced[r.id] !== JSON.stringify(r));
  if (!changed.length) return;
  try {
    for (let i = 0; i < changed.length; i += 40) {   // server caps a POST at 40 records
      const batch = changed.slice(i, i + 40);
      const res = await fetch("/api/notion", {
        method: "POST", headers: _headers(key), body: JSON.stringify({ records: batch }),
      });
      if (res.status === 401) { if (window.Auth) window.Auth.clear(); return; }
      if (!res.ok) return;                           // keep cache; retry on next change
      for (const r of batch) _synced[r.id] = JSON.stringify(r);
    }
  } catch (e) { /* offline: keep cache, retry later */ }
}

/* Run once before the app renders. Pulls this user's records from Notion into
   the cache (source of truth). If Notion is empty, migrates whatever is already
   in the local cache (no sample seeding into the real DB). */
async function syncFromServer() {
  const key = _authKey();
  if (!key) return load();                           // not authed -> local cache
  try {
    const res = await fetch("/api/notion", { headers: _headers(key) });
    if (res.status === 401) { if (window.Auth) window.Auth.clear(); return load(); }
    if (!res.ok) return load();
    const data = await res.json();
    const remote = data.records || [];
    if (remote.length) {
      persistLocal(remote);
      _synced = _snapshot(remote);
      return remote;
    }
    // Notion empty -> migrate raw cache up (skip sample seed)
    let local = [];
    try { const raw = localStorage.getItem(STORE_KEY); if (raw) local = JSON.parse(raw); } catch (e) {}
    if (local.length) { _synced = {}; await pushNow(local); }
    persistLocal(local);
    _synced = _snapshot(local);
    return local;
  } catch (e) {
    return load();                                   // offline -> local cache
  }
}

function makeExpression(partial) {
  return {
    id: uid(),
    kind: "expression",            // 'expression' | 'word'
    expression: "",
    meaning: "",
    examples: [],
    situation: "",
    formality: "neutral",
    tags: [],
    createdAt: now(),
    lastReviewedAt: null,
    reviewCount: 0,
    nextReviewAt: todayStart(),   // due immediately by default
    box: 0,
    difficulty: null,
    ...partial,
  };
}

/* ---- spaced repetition ---- */
// rating: 'hard' | 'good' | 'easy'
function applyReview(expr, rating) {
  let box = expr.box ?? 0;
  if (rating === "hard") box = Math.max(0, box - 1);
  else if (rating === "good") box = Math.min(BOX_DAYS.length - 1, box + 1);
  else box = Math.min(BOX_DAYS.length - 1, box + 2); // easy
  const days = BOX_DAYS[box];
  return {
    ...expr,
    box,
    difficulty: rating,
    reviewCount: (expr.reviewCount ?? 0) + 1,
    lastReviewedAt: now(),
    nextReviewAt: todayStart() + days * DAY,
  };
}

function isDue(expr) {
  return (expr.nextReviewAt ?? 0) <= now();
}

/* ---- activity log: how many expressions studied per day ---- */
function dkey(ts) {
  const x = new Date(ts);
  return `${x.getFullYear()}-${String(x.getMonth() + 1).padStart(2, "0")}-${String(x.getDate()).padStart(2, "0")}`;
}
function loadActivity() {
  try { const r = localStorage.getItem(ACT_KEY); if (r) return JSON.parse(r); } catch (e) {}
  return seedActivity();
}
function saveActivity(m) { try { localStorage.setItem(ACT_KEY, JSON.stringify(m)); } catch (e) {} return m; }
function logStudy(n = 1) {
  const m = loadActivity();
  const k = dkey(now());
  m[k] = (m[k] || 0) + n;
  return saveActivity(m);
}
function seedActivity() {
  const m = {};
  const today = new Date(); today.setHours(0, 0, 0, 0);
  // light, plausible history across this + last week
  [[1, 2], [2, 4], [3, 3], [5, 5], [6, 2], [8, 3], [9, 6], [10, 2], [12, 4], [13, 3]].forEach(([off, c]) => {
    const d = new Date(today); d.setDate(d.getDate() - off); m[dkey(d)] = c;
  });
  return saveActivity(m);
}
// Monday-based week
function weekDays(offset = 0) {
  const d = new Date(); d.setHours(0, 0, 0, 0);
  const dow = (d.getDay() + 6) % 7;
  d.setDate(d.getDate() - dow + offset * 7);
  const arr = [];
  for (let i = 0; i < 7; i++) { const x = new Date(d); x.setDate(d.getDate() + i); arr.push({ date: x, key: dkey(x) }); }
  return arr;
}

function daysUntil(ts) {
  const diff = ts - todayStart();
  return Math.round(diff / DAY);
}

/* ---- seed data: realistic set for a dev heading abroad ---- */
function seed() {
  const t = todayStart();
  const mk = (o) => makeExpression(o);
  const list = [
    mk({
      expression: "get the ball rolling",
      meaning: "일을 시작하다, 착수하다",
      examples: [
        "Let's get the ball rolling on the migration before the holidays.",
        "I sent the kickoff doc just to get the ball rolling.",
        "Who's going to get the ball rolling on hiring?",
      ],
      situation: "프로젝트나 회의를 본격적으로 시작할 때 쓰는 캐주얼한 표현. 동료 사이에서 자연스럽다.",
      formality: "neutral", tags: ["업무", "회의"],
      box: 1, reviewCount: 2, nextReviewAt: t, createdAt: t - 9 * DAY, difficulty: "good",
    }),
    mk({
      expression: "on the same page",
      meaning: "생각·이해가 일치하다",
      examples: [
        "Let's make sure we're on the same page before the demo.",
        "Quick sync so we're all on the same page?",
        "Are we on the same page about the deadline?",
      ],
      situation: "팀이 같은 이해를 공유하는지 확인할 때. 미팅에서 매우 자주 쓰인다.",
      formality: "neutral", tags: ["업무", "협업"],
      box: 2, reviewCount: 3, nextReviewAt: t, createdAt: t - 14 * DAY, difficulty: "good",
    }),
    mk({
      expression: "circle back",
      meaning: "나중에 다시 논의하다",
      examples: [
        "Let's circle back on pricing next week.",
        "I'll circle back once I hear from the vendor.",
        "Can we circle back to your point after the demo?",
      ],
      situation: "지금 결론 내리지 않고 나중에 다시 다루자고 할 때. 비즈니스 영어에서 흔하다.",
      formality: "neutral", tags: ["업무", "이메일"],
      box: 0, reviewCount: 1, nextReviewAt: t, createdAt: t - 4 * DAY, difficulty: "hard",
    }),
    mk({
      expression: "a steep learning curve",
      meaning: "학습 곡선이 가파른, 익히기 어려운",
      examples: [
        "Kubernetes has a steep learning curve at first.",
        "There's a steep learning curve, but it pays off.",
        "Expect a steep learning curve in your first month abroad.",
      ],
      situation: "새 기술·환경이 처음에 배우기 어렵다는 걸 표현. 면접이나 일상 모두 OK.",
      formality: "neutral", tags: ["학습", "기술"],
      box: 1, reviewCount: 1, nextReviewAt: t + 2 * DAY, createdAt: t - 6 * DAY, difficulty: "good",
    }),
    mk({
      expression: "off the top of my head",
      meaning: "정확히는 모르지만 즉석에서 생각나는 대로",
      examples: [
        "Off the top of my head, I'd say about 200 users.",
        "I can't recall the exact number off the top of my head.",
        "Off the top of my head, three libraries do that.",
      ],
      situation: "확실하지 않은 답을 즉흥적으로 말할 때 앞에 붙인다. 캐주얼한 대화체.",
      formality: "casual", tags: ["일상", "대화"],
      box: 0, reviewCount: 0, nextReviewAt: t, createdAt: t - 2 * DAY,
    }),
    mk({
      expression: "I'd appreciate it if you could...",
      meaning: "~해 주시면 감사하겠습니다 (정중한 요청)",
      examples: [
        "I'd appreciate it if you could review the PR by Friday.",
        "I'd appreciate it if you could clarify the requirements.",
        "I'd really appreciate it if you could send the invoice.",
      ],
      situation: "이메일이나 공식적인 자리에서 정중하게 부탁할 때. 격식 있는 표현.",
      formality: "formal", tags: ["이메일", "요청"],
      box: 0, reviewCount: 0, nextReviewAt: t + DAY, createdAt: t - DAY,
    }),
    mk({
      kind: "word",
      expression: "bandwidth",
      meaning: "(일을 처리할) 여력, 시간적 여유",
      examples: [
        "I don't have the bandwidth to take on another project this week.",
        "Do you have the bandwidth to review my design today?",
        "We need more bandwidth on the backend team.",
      ],
      situation: "원래는 '대역폭'이지만, 일상·업무에서는 '여력/시간'이라는 뜻으로 자주 쓰인다. 캐주얼한 표현.",
      formality: "casual", tags: ["업무", "일상"],
      box: 0, reviewCount: 0, nextReviewAt: t, createdAt: t - 3 * DAY,
    }),
  ];
  return persist(list);
}

window.Store = {
  load, persist, makeExpression, applyReview, isDue, daysUntil,
  todayStart, now, VIEW_KEY,
  logStudy, getActivity: loadActivity, weekDays, dkey,
  dueList: (list) => list.filter(isDue),
  allTags: (list) => Array.from(new Set(list.flatMap((e) => e.tags || []))).sort(),
  syncFromServer, pushNow,
};
