// data.jsx — 数据层：分类定义、时间段统计、与后端 API 通信、本地离线缓存
// 数据真正存储在服务端 SQLite；localStorage 仅作离线缓存与首屏快速渲染。

const CATS = {
  allowance:  { id: 'allowance',  label: '零花钱', type: 'income'  },
  reward:     { id: 'reward',     label: '奖励',   type: 'income'  },
  toy:        { id: 'toy',        label: '玩具',   type: 'expense' },
  snack:      { id: 'snack',      label: '零食',   type: 'expense' },
  stationery: { id: 'stationery', label: '文具',   type: 'expense' },
  book:       { id: 'book',       label: '图书',   type: 'expense' },
  gift:       { id: 'gift',       label: '礼物',   type: 'expense' },
  other:      { id: 'other',      label: '其他',   type: 'expense' },
  wish:       { id: 'wish',       label: '许愿',   type: 'expense' },
};
const INCOME_CATS  = ['allowance', 'reward'];
const EXPENSE_CATS = ['toy', 'snack', 'stationery', 'book', 'gift', 'other'];

// ---------- 梦想口袋：梦想重量分级 ----------
// 许一个愿花 WISH_COST 金币，最多 MAX_SLOTS 个槽位（放飞即删除，不留冷静期）。
// 价格区间 range 仅作内部/家长参考，不展示给孩子。
const WISH_COST = 1;
const MAX_SLOTS = 5;
const TIERS = {
  small:  { id: 'small',  emoji: '🌱', label: '小梦想',   range: '20 元以内',   wait: 3,   waitLabel: '等 1–3 天',   color: '#7bd13f' },
  medium: { id: 'medium', emoji: '🌿', label: '中梦想',   range: '20–200 元',   wait: 30,  waitLabel: '等约 1 个月', color: '#22c08b' },
  large:  { id: 'large',  emoji: '🌳', label: '大梦想',   range: '200–1000 元', wait: 180, waitLabel: '等约 6 个月', color: '#1fb6d8' },
  super:  { id: 'super',  emoji: '🌌', label: '超级梦想', range: '1000 元以上', wait: 365, waitLabel: '等约 12 个月', color: '#9b7bff' },
};
const TIER_ORDER = ['small', 'medium', 'large', 'super'];

const STORE_KEY = 'coinpenguin.tx.v1';   // 流水本地缓存键
const WISH_KEY  = 'coinpenguin.wish.v1'; // 心愿本地缓存键

// ---------- 本地缓存（离线 / 首屏）----------
function loadTx() {
  try {
    const raw = localStorage.getItem(STORE_KEY);
    if (raw) return JSON.parse(raw);
  } catch (e) {}
  return [];
}

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

function newId() {
  return 't' + Date.now().toString(36) + Math.random().toString(36).slice(2, 5);
}

// ---------- 后端 API ----------
async function apiFetchTx() {
  const res = await fetch('api/tx', { credentials: 'same-origin' });
  if (!res.ok) throw new Error('tx fetch failed: ' + res.status);
  const list = await res.json();
  saveTx(list); // 刷新本地缓存
  return list;
}

async function apiAddTx(tx) {
  const res = await fetch('api/tx', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    credentials: 'same-origin',
    body: JSON.stringify(tx),
  });
  if (!res.ok) throw new Error('tx post failed: ' + res.status);
  return res.json();
}

// ---------- 对话历史 API ----------
async function apiListConvs() {
  const res = await fetch('api/conversations', { credentials: 'same-origin' });
  if (!res.ok) throw new Error('convs fetch failed: ' + res.status);
  return res.json();
}

async function apiCreateConv() {
  const res = await fetch('api/conversations', {
    method: 'POST', credentials: 'same-origin',
    headers: { 'Content-Type': 'application/json' }, body: '{}',
  });
  if (!res.ok) throw new Error('conv create failed: ' + res.status);
  return res.json();
}

async function apiGetMessages(convId) {
  const res = await fetch('api/conversations/' + convId + '/messages', { credentials: 'same-origin' });
  if (!res.ok) throw new Error('messages fetch failed: ' + res.status);
  return res.json();
}

async function apiAddMessage(convId, msg) {
  const res = await fetch('api/conversations/' + convId + '/messages', {
    method: 'POST', credentials: 'same-origin',
    headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(msg),
  });
  if (!res.ok) throw new Error('message save failed: ' + res.status);
  return res.json();
}

async function apiDeleteConv(convId) {
  const res = await fetch('api/conversations/' + convId, {
    method: 'DELETE', credentials: 'same-origin',
  });
  if (!res.ok) throw new Error('conv delete failed: ' + res.status);
  return res.json();
}

// ---------- 梦想口袋：心愿本地缓存 ----------
function loadWishes() {
  try {
    const raw = localStorage.getItem(WISH_KEY);
    if (raw) return JSON.parse(raw);
  } catch (e) {}
  return [];
}
function saveWishes(list) {
  try { localStorage.setItem(WISH_KEY, JSON.stringify(list)); } catch (e) {}
}

const DAY = 86400000;
function wishDaysKept(w) {
  return Math.max(0, Math.floor((Date.now() - w.ts) / DAY));
}
function activeWishes(list) {
  return Array.isArray(list) ? list : [];
}

// ---------- 梦想口袋：心愿后端 API ----------
async function apiFetchWishes() {
  const res = await fetch('api/wishes', { credentials: 'same-origin' });
  if (!res.ok) throw new Error('wishes fetch failed: ' + res.status);
  const list = await res.json();
  saveWishes(list);
  return list;
}

// 许愿：后端单事务（校验余额/槽位 + 扣 1 金币 + 落库），返回 { ok, wish, tx, balanceAfter } 或 { ok:false, reason }
async function apiAddWish(data) {
  const res = await fetch('api/wishes', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    credentials: 'same-origin',
    body: JSON.stringify(data),
  });
  if (!res.ok) {
    try { return await res.json(); } catch (e) { return { ok: false, reason: 'error' }; }
  }
  return res.json();
}

async function apiUpdateWish(id, patch) {
  const res = await fetch('api/wishes/' + id, {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json' },
    credentials: 'same-origin',
    body: JSON.stringify(patch),
  });
  if (!res.ok) throw new Error('wish update failed: ' + res.status);
  return res.json();
}

async function apiReleaseWish(id) {
  const res = await fetch('api/wishes/' + id, { method: 'DELETE', credentials: 'same-origin' });
  if (!res.ok) throw new Error('wish delete failed: ' + res.status);
  return res.json();
}

async function apiReorderWishes(ids) {
  const res = await fetch('api/wishes/reorder', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    credentials: 'same-origin',
    body: JSON.stringify({ ids }),
  });
  if (!res.ok) throw new Error('wish reorder failed: ' + res.status);
  return res.json();
}

// 探测现有会话 cookie 是否仍有效（用于刷新后自动解锁）
async function apiHasSession() {
  try {
    const res = await fetch('api/session', { credentials: 'same-origin' });
    return res.ok;
  } catch (e) {
    return false;
  }
}

async function apiLogin(pin) {
  const res = await fetch('api/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    credentials: 'same-origin',
    body: JSON.stringify({ pin }),
  });
  if (!res.ok) return false;
  const data = await res.json();
  return !!data.ok;
}

// ---------- LLM 代理垫片 ----------
// chat.jsx 通过 window.claude.complete({ messages }) 调用，这里转发到后端 DeepSeek 代理。
window.claude = {
  async complete({ messages }) {
    const res = await fetch('api/chat', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      credentials: 'same-origin',
      body: JSON.stringify({ messages }),
    });
    if (!res.ok) throw new Error('chat failed: ' + res.status);
    const data = await res.json();
    return data.text || '';
  },
};

// ---------- 时间段 ----------
const RANGES = [
  { id: 'today', label: '今天' },
  { id: 'week',  label: '本周' },
  { id: 'month', label: '本月' },
  { id: 'all',   label: '全部' },
];

function rangeStart(rangeId) {
  const d = new Date();
  d.setHours(0, 0, 0, 0);
  if (rangeId === 'today') return d.getTime();
  if (rangeId === 'week') {
    const day = (d.getDay() + 6) % 7; // 周一为一周起点
    d.setDate(d.getDate() - day);
    return d.getTime();
  }
  if (rangeId === 'month') {
    d.setDate(1);
    return d.getTime();
  }
  return 0; // all
}

function inRange(ts, rangeId) {
  return ts >= rangeStart(rangeId);
}

// 余额始终是「全部」的累计
function totalBalance(list) {
  return list.reduce((s, t) => s + (t.type === 'income' ? t.amount : -t.amount), 0);
}

function summarize(list, rangeId) {
  let income = 0, expense = 0;
  list.forEach((t) => {
    if (!inRange(t.ts, rangeId)) return;
    if (t.type === 'income') income += t.amount;
    else expense += t.amount;
  });
  return { income, expense, net: income - expense };
}

// 友好时间
function timeLabel(ts) {
  const d = new Date(ts);
  const now = new Date();
  const sameDay = d.toDateString() === now.toDateString();
  const y = new Date(now); y.setDate(now.getDate() - 1);
  const isYest = d.toDateString() === y.toDateString();
  const hm = `${d.getHours().toString().padStart(2, '0')}:${d.getMinutes().toString().padStart(2, '0')}`;
  if (sameDay) return `今天 ${hm}`;
  if (isYest) return `昨天 ${hm}`;
  return `${d.getMonth() + 1}月${d.getDate()}日`;
}

Object.assign(window, {
  CATS, INCOME_CATS, EXPENSE_CATS, RANGES,
  TIERS, TIER_ORDER, WISH_COST, MAX_SLOTS,
  loadTx, saveTx, newId,
  loadWishes, saveWishes, wishDaysKept, activeWishes,
  apiFetchTx, apiAddTx, apiLogin,
  apiFetchWishes, apiAddWish, apiUpdateWish, apiReleaseWish, apiReorderWishes,
  apiListConvs, apiCreateConv, apiGetMessages, apiAddMessage, apiDeleteConv,
  totalBalance, summarize, inRange, timeLabel,
});
