// app-store.jsx — Live data store backed by the Flask/MongoDB backend.
// Keeps the exact same public surface as the original in-memory store so every
// screen keeps working unchanged:
//   window.useAppStore()  -> { stores, users, assignments, leaves, currentUserId, today, session, ready }
//   window.appActions     -> login/logout/setStorePrice/addAssignment/removeAssignment/setLeaveStatus + submit*
//   window.getStoreById / getUserById / computeEarnings / faNumS / fmtToman / SLOTS

const { useSyncExternalStore } = React;

const faNumS = (n) => String(n).replace(/\d/g, d => '۰۱۲۳۴۵۶۷۸۹'[d]);
const fmtToman = (n) => faNumS(n.toLocaleString('en-US')) + ' تومان';

// time slots per day (kept client-side; matches the original)
const SLOTS = ['۰۹:۰۰', '۱۰:۳۰', '۱۲:۰۰', '۱۴:۰۰', '۱۵:۳۰'];

// ---------- low-level fetch helper ----------
async function api(path, { method = 'GET', json, form } = {}) {
  const opts = { method, headers: {}, credentials: 'same-origin' };
  if (json !== undefined) {
    opts.headers['Content-Type'] = 'application/json';
    opts.body = JSON.stringify(json);
  } else if (form !== undefined) {
    opts.body = form; // FormData; browser sets the boundary
  }
  const res = await fetch(path, opts);
  let data = {};
  try { data = await res.json(); } catch { /* empty body */ }
  return { ok: res.ok, status: res.status, data };
}

// ---------- store state ----------
let state = {
  stores: [],
  products: [],         // sample-product catalogue (admin-managed)
  inventoryProducts: [], // in-stock product catalogue (admin-managed)
  projects: [],         // named projects with their assigned supervisors (manager+)
  users: [],
  assignments: {},      // userId -> { day -> [ {storeId,time,status} ] }
  leaves: [],
  inbox: [],            // messages received by the current user
  sentMessages: [],     // messages sent by the current user
  currentUserId: null,
  today: 2,
  session: null,        // { userId, role } | null
  ready: false,         // true once /api/me has resolved
};
const listeners = new Set();
const notify = () => listeners.forEach(l => l());
const subscribe = (l) => { listeners.add(l); return () => listeners.delete(l); };
const getSnapshot = () => state;
const set = (patch) => { state = { ...state, ...patch }; notify(); };

// ---------- hydration ----------
async function hydrate() {
  const boot = await api('/api/bootstrap');
  if (!boot.ok) return false;
  const d = boot.data;
  set({
    stores: d.stores || [],
    products: d.products || [],
    inventoryProducts: d.inventoryProducts || [],
    projects: d.projects || [],
    users: d.users || [],
    assignments: d.assignments || {},
    leaves: d.leaves || [],
    currentUserId: d.currentUserId,
    today: d.today ?? 2,
    session: d.session || null,
  });
  loadMessages();   // fire-and-forget; updates state when it resolves
  return true;
}

// Pull the current user's inbox + sent list into the store.
async function loadMessages() {
  const r = await api('/api/messages');
  if (r.ok) set({ inbox: r.data.inbox || [], sentMessages: r.data.sent || [] });
}

async function init() {
  const me = await api('/api/me');
  if (me.ok && me.data.user) {
    await hydrate();
    const u = me.data.user;
    set({ session: { userId: u.id, role: u.role, mustSetCredentials: u.mustSetCredentials }, ready: true });
  } else {
    set({ session: null, ready: true });
  }
}

// ---------- mutations / actions ----------
const actions = {
  async login(username, password) {
    const r = await api('/api/auth/login', { method: 'POST', json: { username, password } });
    if (!r.ok) return { ok: false, error: r.data.error || 'خطا در ورود' };
    const u = r.data.user;
    await hydrate();
    set({ session: { userId: u.id, role: u.role, mustSetCredentials: u.mustSetCredentials }, currentUserId: u.id, ready: true });
    return { ok: true, user: u };
  },

  // ----- sign-up (phone + OTP) -----
  async register({ name, phone, nationalCode }) {
    const r = await api('/api/auth/register', {
      method: 'POST', json: { name, phone, nationalCode },
    });
    if (!r.ok) return { ok: false, error: r.data.error || 'خطا در ثبت‌نام' };
    return { ok: true, phone: r.data.phone, devCode: r.data.devCode, ttl: r.data.ttl };
  },

  async verifyOtp({ phone, code }) {
    const r = await api('/api/auth/verify', { method: 'POST', json: { phone, code } });
    if (!r.ok) return { ok: false, error: r.data.error || 'خطا در تایید کد' };
    const u = r.data.user;
    await hydrate();
    set({ session: { userId: u.id, role: u.role, mustSetCredentials: u.mustSetCredentials }, currentUserId: u.id, ready: true });
    return { ok: true, user: u };
  },

  // ----- first-login: set username + password -----
  async setupCredentials({ username, password }) {
    const r = await api('/api/auth/setup-credentials', { method: 'POST', json: { username, password } });
    if (!r.ok) return { ok: false, error: r.data.error || 'خطا در ثبت اطلاعات' };
    await hydrate();
    set({ session: { ...state.session, mustSetCredentials: false } });
    return { ok: true, user: r.data.user };
  },

  async logout() {
    await api('/api/auth/logout', { method: 'POST' });
    set({
      session: null, currentUserId: null,
      stores: [], products: [], inventoryProducts: [], projects: [], users: [], assignments: {}, leaves: [],
      inbox: [], sentMessages: [],
    });
  },

  // ----- messages -----
  // Refresh inbox + sent from the server.
  reloadMessages() {
    return loadMessages();
  },

  // Who the current user is allowed to message (+ whether group send is allowed).
  async fetchRecipients() {
    const r = await api('/api/messages/recipients');
    if (!r.ok) return { recipients: [], canGroup: false };
    return { recipients: r.data.recipients || [], canGroup: !!r.data.canGroup };
  },

  // Send to specific users (toUserIds) or broadcast to everyone allowed (all: true).
  async sendMessage({ toUserIds, all, subject, body }) {
    const r = await api('/api/messages', {
      method: 'POST', json: { toUserIds, all, subject, body },
    });
    if (r.ok) await loadMessages();
    return r.ok ? { ok: true, count: r.data.count } : { ok: false, error: r.data.error || 'خطا در ارسال پیام' };
  },

  // Mark a received message as read (optimistic).
  markMessageRead(msgId) {
    set({ inbox: state.inbox.map(m => m.id === msgId ? { ...m, read: true } : m) });
    return api(`/api/messages/${msgId}/read`, { method: 'POST' })
      .then(r => { if (!r.ok) loadMessages(); return r; });
  },

  addStore(fields) {
    return api('/api/stores', { method: 'POST', json: fields }).then(r => {
      if (r.ok && r.data.store) set({ stores: [...state.stores, r.data.store] });
      else hydrate();
      return r;
    });
  },

  updateStore(storeId, patch) {
    // optimistic update, then persist
    set({ stores: state.stores.map(s => s.id === storeId ? { ...s, ...patch } : s) });
    return api(`/api/stores/${storeId}`, { method: 'PATCH', json: patch })
      .then(r => { if (!r.ok) hydrate(); return r; });
  },

  deleteStore(storeId) {
    set({ stores: state.stores.filter(s => s.id !== storeId) });
    return api(`/api/stores/${storeId}`, { method: 'DELETE' })
      .then(r => { if (!r.ok) hydrate(); return r; });
  },

  setStorePrice(storeId, price) {
    return actions.updateStore(storeId, { price });
  },

  // ----- sample-product catalogue (admin) -----
  addProduct(fields) {
    return api('/api/products', { method: 'POST', json: fields }).then(r => {
      if (r.ok && r.data.product) set({ products: [...state.products, r.data.product] });
      else hydrate();
      return r;
    });
  },

  updateProduct(productId, patch) {
    set({ products: state.products.map(p => p.id === productId ? { ...p, ...patch } : p) });
    return api(`/api/products/${productId}`, { method: 'PATCH', json: patch })
      .then(r => { if (!r.ok) hydrate(); return r; });
  },

  deleteProduct(productId) {
    set({ products: state.products.filter(p => p.id !== productId) });
    return api(`/api/products/${productId}`, { method: 'DELETE' })
      .then(r => { if (!r.ok) hydrate(); return r; });
  },

  // ----- in-stock product catalogue (admin) -----
  addInventoryProduct(fields) {
    return api('/api/inventory-products', { method: 'POST', json: fields }).then(r => {
      if (r.ok && r.data.product) set({ inventoryProducts: [...state.inventoryProducts, r.data.product] });
      else hydrate();
      return r;
    });
  },

  updateInventoryProduct(productId, patch) {
    set({ inventoryProducts: state.inventoryProducts.map(p => p.id === productId ? { ...p, ...patch } : p) });
    return api(`/api/inventory-products/${productId}`, { method: 'PATCH', json: patch })
      .then(r => { if (!r.ok) hydrate(); return r; });
  },

  deleteInventoryProduct(productId) {
    set({ inventoryProducts: state.inventoryProducts.filter(p => p.id !== productId) });
    return api(`/api/inventory-products/${productId}`, { method: 'DELETE' })
      .then(r => { if (!r.ok) hydrate(); return r; });
  },

  // ----- projects (manager+): a name with its assigned supervisors -----
  addProject(fields) {
    return api('/api/projects', { method: 'POST', json: fields }).then(r => {
      if (r.ok && r.data.project) set({ projects: [...state.projects, r.data.project] });
      else hydrate();
      return r;
    });
  },

  updateProject(projectId, patch) {
    return api(`/api/projects/${projectId}`, { method: 'PATCH', json: patch }).then(r => {
      if (r.ok && r.data.project) {
        set({ projects: state.projects.map(p => p.id === projectId ? r.data.project : p) });
      } else hydrate();
      return r;
    });
  },

  deleteProject(projectId) {
    set({ projects: state.projects.filter(p => p.id !== projectId) });
    return api(`/api/projects/${projectId}`, { method: 'DELETE' })
      .then(r => { if (!r.ok) hydrate(); return r; });
  },

  // ----- user management (full-access roles) -----
  async createUser(fields) {
    const r = await api('/api/users', { method: 'POST', json: fields });
    if (r.ok) await hydrate();
    return r.ok ? { ok: true, user: r.data.user } : { ok: false, error: r.data.error || 'خطا در ساخت کاربر' };
  },

  async updateUser(userId, patch) {
    const r = await api(`/api/users/${userId}`, { method: 'PATCH', json: patch });
    if (r.ok) await hydrate();
    return r.ok ? { ok: true, user: r.data.user } : { ok: false, error: r.data.error || 'خطا در ویرایش کاربر' };
  },

  async resetUserPassword(userId, password) {
    const r = await api(`/api/users/${userId}/password`, { method: 'POST', json: { password } });
    return r.ok ? { ok: true } : { ok: false, error: r.data.error || 'خطا در تغییر گذرواژه' };
  },

  async deleteUser(userId) {
    const r = await api(`/api/users/${userId}`, { method: 'DELETE' });
    if (r.ok) await hydrate();
    return r.ok ? { ok: true } : { ok: false, error: r.data.error || 'خطا در حذف کاربر' };
  },

  addAssignment(userId, day, storeId, time) {
    const dayList = state.assignments[userId]?.[day] || [];
    if (dayList.some(a => a.storeId === storeId)) return;
    const status = day === state.today ? 'today' : 'upcoming';
    set({
      assignments: {
        ...state.assignments,
        [userId]: {
          ...(state.assignments[userId] || {}),
          [day]: [...dayList, { storeId, time, status }].sort((a, b) => a.time.localeCompare(b.time)),
        },
      },
    });
    api('/api/assignments', { method: 'POST', json: { userId, day, storeId, time } })
      .then(r => { if (!r.ok) hydrate(); });
  },

  removeAssignment(userId, day, storeId) {
    const dayList = state.assignments[userId]?.[day] || [];
    const filtered = dayList.filter(a => a.storeId !== storeId);
    const newUser = { ...(state.assignments[userId] || {}) };
    if (filtered.length === 0) delete newUser[day];
    else newUser[day] = filtered;
    set({ assignments: { ...state.assignments, [userId]: newUser } });
    api('/api/assignments', { method: 'DELETE', json: { userId, day, storeId } })
      .then(r => { if (!r.ok) hydrate(); });
  },

  setLeaveStatus(leaveId, status) {
    set({ leaves: state.leaves.map(l => l.id === leaveId ? { ...l, status } : l) });
    api(`/api/leaves/${leaveId}/status`, { method: 'POST', json: { status } })
      .then(r => { if (!r.ok) hydrate(); });
  },

  // ----- field-user submissions -----
  submitLeave({ range, leaveType, reason }) {
    return api('/api/leaves', {
      method: 'POST',
      json: { start: range.start, end: range.end ?? range.start, type: leaveType, reason },
    }).then(r => {
      if (r.ok && r.data.leave) set({ leaves: [r.data.leave, ...state.leaves] });
      return r;
    });
  },

  submitSamples(counts, storeId) {
    return api('/api/samples', { method: 'POST', json: { counts, storeId } });
  },

  submitInventory(counts, storeId) {
    return api('/api/inventory', { method: 'POST', json: { counts, storeId } });
  },

  submitDocuments({ idFront, idBack, iban }) {
    const fd = new FormData();
    if (iban) fd.append('iban', iban);
    if (idFront) fd.append('idFront', idFront);
    if (idBack) fd.append('idBack', idBack);
    return api('/api/documents', { method: 'POST', form: fd });
  },

  recordAttendance(kind, file) {
    if (file) {
      const fd = new FormData();
      fd.append('kind', kind);
      fd.append('photo', file);
      return api('/api/attendance', { method: 'POST', form: fd });
    }
    return api('/api/attendance', { method: 'POST', json: { kind } });
  },

  // Performance detail for one user (work-day sessions + sample/inventory tallies).
  fetchPerformance(userId) {
    return api(`/api/performance/${userId}`).then(r => (r.ok ? r.data : null));
  },

  recordBreak(event, durationSec, overThreshold) {
    return api('/api/breaks', { method: 'POST', json: { event, durationSec, overThreshold } });
  },
};

// ---------- hooks/selectors ----------
function useStore() { return useSyncExternalStore(subscribe, getSnapshot); }

function getStoreById(id) { return state.stores.find(s => s.id === id); }
function getUserById(id) { return state.users.find(u => u.id === id); }

// Earnings: sum agreed price for each completed/today visit (unchanged logic).
function computeEarnings(userId) {
  const ua = state.assignments[userId] || {};
  let earnedDone = 0, earnedToday = 0, upcoming = 0;
  let visitsDone = 0, visitsToday = 0, visitsUpcoming = 0;
  const perStore = {};
  const perDay = {};

  Object.entries(ua).forEach(([day, list]) => {
    list.forEach(a => {
      const store = getStoreById(a.storeId);
      if (!store) return;
      const p = store.price;
      perStore[a.storeId] = perStore[a.storeId] || { storeId: a.storeId, count: 0, total: 0 };
      perStore[a.storeId].count++;
      perStore[a.storeId].total += p;
      perDay[day] = perDay[day] || { day: +day, count: 0, total: 0 };
      perDay[day].count++;
      perDay[day].total += p;

      if (a.status === 'done')      { earnedDone += p;  visitsDone++; }
      else if (a.status === 'today'){ earnedToday += p; visitsToday++; }
      else                          { upcoming += p;    visitsUpcoming++; }
    });
  });

  return {
    earnedDone, earnedToday, upcoming,
    earnedTotal: earnedDone + earnedToday,
    visitsDone, visitsToday, visitsUpcoming,
    perStore: Object.values(perStore).sort((a, b) => b.total - a.total),
    perDay:   Object.values(perDay).sort((a, b) => a.day - b.day),
  };
}

Object.assign(window, {
  AppStore: { subscribe, getSnapshot, actions, useStore },
  useAppStore: useStore,
  appActions: actions,
  getStoreById, getUserById, computeEarnings,
  faNumS, fmtToman, SLOTS,
});

// kick off session check + hydration
init();
