// Local-first data store for the BBQ na Biso prototype.
// This keeps the app functional before a production backend is chosen.

const BBQ_LOCAL_CONFIG = window.BBQ_LOCAL_CONFIG || {};

const BBQ_EVENT = {
  title: 'BBQ na Biso 2026',
  dateLabel: 'Samedi 27 juin 2026',
  dateISO: '2026-06-27T14:00:00+02:00',
  endISO: '2026-06-27T21:00:00+02:00',
  startTime: '14h00',
  endTime: '21h00',
  rsvpDeadline: '13 juin 2026',
  reminderDates: {
    deadline: '13 juin 2026',
    event: '25 juin 2026',
    dayBefore: '26 juin 2026',
    dayOf: '27 juin 2026',
  },
  locationName: 'Maison familiale Jules',
  address: 'Avenue de la Clairière 9, 1000 Bruxelles',
  city: 'Bruxelles',
  contributionPerAdult: 20,
  freeAgeLimit: 12,
  familyPassword: (BBQ_LOCAL_CONFIG.familyPassword || '').trim().toLowerCase(),
  adminPassword: (BBQ_LOCAL_CONFIG.adminPassword || '').trim(),
  organizer: {
    name: 'Chantal',
    phone: '+32 999 999 999',
    email: 'Jules@Musoko.com',
    chatLabel: 'Groupe WhatsApp familial',
  },
  payment: {
    recommended: 'Leetchi · Cagnotte famille',
    sepa: 'IBAN envoyé après RSVP',
    paypal: 'PayPal entre proches',
  },
};

const BBQ_STORAGE_KEY = 'bbq-na-biso-local-data-v2';
const BBQ_API_SESSION_PASSWORD_KEY = 'bbq-na-biso-family-key';
const FOOD_RESET_VERSION = 1;
const VOLUNTEER_RESET_VERSION = 1;
const ANNOUNCEMENT_RESET_VERSION = 5;
const BBQ_API_TIMEOUT = 4500;
const BBQ_ACTIVITY_LIMIT = 500;

const BBQ_PAGE_LABELS = {
  home: 'Accueil',
  rsvp: 'Inscription',
  announcements: 'Annonces',
  food: 'Repas',
  photos: 'Photos',
  schedule: 'Programme',
  kids: 'Enfants',
  volunteers: 'Bénévoles',
  faq: 'FAQ',
  admin: 'Admin',
};

const BBQ_PRODUCTION_NOTES = [
  {
    title: 'Real private access',
    detail: 'Replace the shared demo password with real authentication or a private invitation link model.',
  },
  {
    title: 'Payments',
    detail: 'Add final Leetchi, SEPA, and PayPal details plus payment status tracking if needed.',
  },
  {
    title: 'Notifications',
    detail: 'Pick email/SMS/WhatsApp providers and decide which reminders are automatic.',
  },
  {
    title: 'Photo storage & moderation',
    detail: 'Use real file storage, upload limits, moderation rules, and album permissions.',
  },
  {
    title: 'Admin permissions',
    detail: 'Decide who can view exports, allergies, payments, photos, announcements, and guest contact data.',
  },
];

function bbqTodayLabel() {
  return new Date().toLocaleDateString('fr-BE', {
    day: 'numeric',
    month: 'long',
    year: 'numeric',
  });
}

function bbqId(prefix) {
  return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
}

function cloneBBQ(value) {
  return JSON.parse(JSON.stringify(value));
}

function bbqBrowserInfo() {
  const ua = navigator.userAgent || '';
  let browser = 'Navigateur inconnu';
  if (/Edg\//.test(ua)) browser = 'Microsoft Edge';
  else if (/OPR\//.test(ua)) browser = 'Opera';
  else if (/Firefox\//.test(ua)) browser = 'Firefox';
  else if (/Chrome\//.test(ua)) browser = 'Chrome';
  else if (/Safari\//.test(ua)) browser = 'Safari';

  let os = 'OS inconnu';
  if (/iPhone|iPad|iPod/.test(ua)) os = 'iOS';
  else if (/Android/.test(ua)) os = 'Android';
  else if (/Mac OS X|Macintosh/.test(ua)) os = 'macOS';
  else if (/Windows NT/.test(ua)) os = 'Windows';
  else if (/Linux/.test(ua)) os = 'Linux';

  return { browser, os, userAgent: ua };
}

async function bbqFetchLoginLocation() {
  const fallback = { ip: '—', country: '—' };
  const endpoints = [
    'https://ipapi.co/json/',
    'https://ipwho.is/',
  ];

  for (const url of endpoints) {
    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), 2200);
    try {
      const response = await fetch(url, { signal: controller.signal });
      if (!response.ok) continue;
      const data = await response.json();
      const ip = data.ip || data.query || '—';
      const country = data.country_name || data.country || '—';
      if (ip !== '—' || country !== '—') return { ip, country };
    } catch (err) {
      // Best-effort client-side lookup; production should capture this server-side.
    } finally {
      clearTimeout(timeout);
    }
  }
  return fallback;
}

async function bbqBuildLoginRecord(name, access = 'Site') {
  const now = new Date();
  const { browser, os, userAgent } = bbqBrowserInfo();
  const location = await bbqFetchLoginLocation();
  return {
    id: bbqId('login'),
    name: (name || 'Inconnu').trim() || 'Inconnu',
    access,
    date: now.toLocaleDateString('fr-BE'),
    time: now.toLocaleTimeString('fr-BE', { hour: '2-digit', minute: '2-digit', second: '2-digit' }),
    timestamp: now.toISOString(),
    ip: location.ip,
    country: location.country,
    browser,
    os,
    userAgent,
  };
}

function bbqBuildActivityRecord(name, type, label, details = {}) {
  const now = new Date();
  const { browser, os, userAgent } = bbqBrowserInfo();
  const page = details.page || 'home';
  return {
    id: bbqId('activity'),
    name: (name || 'Inconnu').trim() || 'Inconnu',
    type,
    label,
    page,
    pageLabel: details.pageLabel || BBQ_PAGE_LABELS[page] || page,
    detail: details.detail || '',
    date: now.toLocaleDateString('fr-BE'),
    time: now.toLocaleTimeString('fr-BE', { hour: '2-digit', minute: '2-digit', second: '2-digit' }),
    timestamp: now.toISOString(),
    browser,
    os,
    userAgent,
  };
}

function pushActivityRecord(draft, record) {
  draft.activityRecords = [record, ...(draft.activityRecords || [])]
    .filter((item, index, all) => all.findIndex((candidate) => candidate.id === item.id) === index)
    .sort((a, b) => new Date(b.timestamp || 0) - new Date(a.timestamp || 0))
    .slice(0, BBQ_ACTIVITY_LIMIT);
}

function activityRecordFromLoginRecord(record) {
  const adminAccess = record.access === 'Admin';
  const page = adminAccess ? 'admin' : 'home';
  return {
    id: `activity-${record.id}`,
    sourceLoginId: record.id,
    name: record.name || 'Inconnu',
    type: adminAccess ? 'admin_access' : 'login',
    label: adminAccess ? 'Accès admin validé' : 'Connexion au site',
    page,
    pageLabel: BBQ_PAGE_LABELS[page] || page,
    detail: adminAccess ? 'Code organisateur accepté' : `Accès ${record.access || 'Site'}`,
    date: record.date,
    time: record.time,
    timestamp: record.timestamp,
    ip: record.ip,
    country: record.country,
    browser: record.browser,
    os: record.os,
    userAgent: record.userAgent,
  };
}

function backfillLoginActivityRecords(loginRecords = [], activityRecords = []) {
  const existingIds = new Set((activityRecords || []).map((record) => record.id).filter(Boolean));
  const existingLoginIds = new Set((activityRecords || []).map((record) => record.sourceLoginId).filter(Boolean));
  const missing = (loginRecords || [])
    .filter((record) => record.id && !record.retiredAt && !existingIds.has(`activity-${record.id}`) && !existingLoginIds.has(record.id))
    .map(activityRecordFromLoginRecord);
  return [...(activityRecords || []), ...missing]
    .filter((item, index, all) => all.findIndex((candidate) => candidate.id === item.id) === index)
    .sort((a, b) => new Date(b.timestamp || 0) - new Date(a.timestamp || 0))
    .slice(0, BBQ_ACTIVITY_LIMIT);
}

function activityDetail(parts) {
  return parts.filter(Boolean).join(' · ');
}

function bbqApiHeaders(familyPassword = BBQ_EVENT.familyPassword) {
  return {
    'Content-Type': 'application/json',
    'X-BBQ-Family-Key': familyPassword || '',
  };
}

async function bbqApiJson(path, options = {}) {
  const { familyPassword, timeout, ...fetchOptions } = options;
  const controller = new AbortController();
  const timeoutHandle = setTimeout(() => controller.abort(), timeout || BBQ_API_TIMEOUT);
  try {
    const response = await fetch(path, {
      ...fetchOptions,
      headers: {
        ...bbqApiHeaders(familyPassword),
        ...(options.headers || {}),
      },
      signal: controller.signal,
    });
    if (response.status === 404) return null;
    let payload = null;
    try {
      payload = await response.json();
    } catch (err) {
      payload = null;
    }
    if (!response.ok || payload?.ok === false) return null;
    return payload;
  } catch (err) {
    return null;
  } finally {
    clearTimeout(timeoutHandle);
  }
}

function bbqPrepareRemoteState(state) {
  const copy = cloneBBQ(state);
  delete copy.myVotes;
  delete copy.likedAnnouncementIds;
  copy.uploadedMemories = (copy.uploadedMemories || []).map((memory) => {
    const next = { ...memory };
    delete next.dataUrl;
    return next;
  });
  copy.loginRecords = (copy.loginRecords || []).slice(0, 200);
  copy.activityRecords = (copy.activityRecords || []).slice(0, BBQ_ACTIVITY_LIMIT);
  return copy;
}

function bbqMergeRemoteState(localState, remoteState) {
  return normalizeState({
    ...localState,
    ...(remoteState || {}),
    myVotes: localState.myVotes || {},
    likedAnnouncementIds: localState.likedAnnouncementIds || [],
  });
}

async function bbqLoadRemoteState(familyPassword = BBQ_EVENT.familyPassword) {
  return bbqApiJson('/api/bbq-state', { method: 'GET', familyPassword });
}

async function bbqSaveRemoteState(state, familyPassword = BBQ_EVENT.familyPassword) {
  return bbqApiJson('/api/bbq-state', {
    method: 'POST',
    familyPassword,
    body: JSON.stringify({ state: bbqPrepareRemoteState(state) }),
  });
}

async function bbqRecordRemoteLogin(name, access, familyPassword = BBQ_EVENT.familyPassword) {
  const { browser, os, userAgent } = bbqBrowserInfo();
  return bbqApiJson('/api/bbq-login', {
    method: 'POST',
    familyPassword,
    body: JSON.stringify({ name, access, browser, os, userAgent }),
  });
}

async function bbqRecordRemoteAdminLogin(name, adminPassword) {
  const { browser, os, userAgent } = bbqBrowserInfo();
  return bbqApiJson('/api/bbq-admin-login', {
    method: 'POST',
    headers: { 'X-BBQ-Admin-Key': adminPassword },
    body: JSON.stringify({ name, browser, os, userAgent }),
  });
}

async function bbqRecordRemoteActivity(record, familyPassword = BBQ_EVENT.familyPassword) {
  return bbqApiJson('/api/bbq-activity', {
    method: 'POST',
    familyPassword,
    body: JSON.stringify(record),
  });
}

async function bbqUploadRemoteMemory(memory, familyPassword = BBQ_EVENT.familyPassword) {
  if (!memory.dataUrl) return null;
  return bbqApiJson('/api/bbq-upload', {
    method: 'POST',
    familyPassword,
    timeout: 15000,
    body: JSON.stringify({
      id: memory.id,
      title: memory.title,
      label: memory.label,
      kind: memory.kind,
      dataUrl: memory.dataUrl,
    }),
  });
}

function blankRSVP() {
  return {
    lastName: '',
    address: '',
    email: '',
    phone: '',
    adults: 1,
    teens: 0,
    kids: 0,
    participantAdults: [''],
    participantTeens: [],
    participantKids: [],
    kidAges: '',
    allergies: '',
    dietary: 'Aucune restriction',
    mealPref: 'Aucune préférence',
    potluckType: 'Accompagnement',
    potluckDish: '',
    potluckServings: '8',
    needHelp: false,
    volunteer: false,
    volRole: 'Équipe Installation',
    volTime: '13h00 – 15h00',
    contactEmail: true,
    contactSMS: true,
    contactPhone: false,
    updatesEvent: true,
    updatesPotluck: true,
  };
}

function defaultState() {
  return {
    rsvps: [],
    potluck: [],
    foodTallies: {
      meat: {},
      side: {},
      sauce: {},
      dessert: {},
    },
    myVotes: {},
    foodResetVersion: FOOD_RESET_VERSION,
    announcements: [
      {
        id: 'pinned-recurring-bbq',
        name: 'Richard, Chantal et Jules',
        date: '27 avril 2026',
        text: "Notre BBQ familial annuel est confirmé pour le samedi 27 juin 2026. C'est notre tradition, un moment précieux pour nous retrouver, partager, rire et profiter ensemble une fois dans l'année. Venez avec votre bonne humeur, votre joie et l'envie de passer une belle journée en famille. La contribution est fixée à 20 € par adulte/ado. Gratuit pour nos enfants.",
        likes: 0,
        replies: 0,
        pinned: true,
      },
    ],
    likedAnnouncementIds: [],
    announcementResetVersion: ANNOUNCEMENT_RESET_VERSION,
    uploadedMemories: [],
    loginRecords: [],
    activityRecords: [],
    volunteerRoles: [
      { id: 'setup', role: 'Équipe Installation', need: 6, filled: 0, time: '13h00 – 15h00', desc: 'Tables, tentes, boissons, panneaux.' },
      { id: 'grill', role: 'Maître du grill', need: 3, filled: 0, time: '14h00 – 18h00', desc: 'Pinces, tabliers et savoir-faire requis.' },
      { id: 'kids', role: 'Animateur jeux enfants', need: 4, filled: 0, time: '15h00 – 18h00', desc: 'Chasse au trésor, arts créatifs et coin lecture.' },
      { id: 'music', role: 'Musique & Ambiance', need: 2, filled: 0, time: 'Toute la journée', desc: 'Gérer la playlist et les enceintes.' },
      { id: 'photo', role: 'Photographe', need: 2, filled: 0, time: 'Toute la journée', desc: 'Capturer des moments — un téléphone suffit !' },
      { id: 'cleanup', role: 'Équipe Rangement', need: 8, filled: 0, time: '20h00 – 21h00', desc: "Plus on est, plus c'est facile." },
    ],
    volunteerSignups: [],
    volunteerResetVersion: VOLUNTEER_RESET_VERSION,
    reminderPrefs: {
      deadline: true,
      event: true,
      dayBefore: true,
      dayOf: false,
      email: true,
      whatsapp: true,
    },
  };
}

function normalizeState(saved) {
  const base = defaultState();
  if (!saved || typeof saved !== 'object') return base;
  const shouldResetFood = saved.foodResetVersion !== FOOD_RESET_VERSION;
  const shouldResetVolunteers = saved.volunteerResetVersion !== VOLUNTEER_RESET_VERSION;
  const shouldResetAnnouncements = saved.announcementResetVersion !== ANNOUNCEMENT_RESET_VERSION;
  const savedVolunteerRoles = Array.isArray(saved.volunteerRoles) && saved.volunteerRoles.length > 0
    ? saved.volunteerRoles
    : cloneBBQ(base.volunteerRoles);
  const normalized = {
    ...base,
    ...saved,
    potluck: shouldResetFood ? [] : saved.potluck || [],
    foodTallies: shouldResetFood ? cloneBBQ(base.foodTallies) : {
      ...base.foodTallies,
      ...(saved.foodTallies || {}),
    },
    myVotes: shouldResetFood ? {} : saved.myVotes || {},
    foodResetVersion: FOOD_RESET_VERSION,
    volunteerRoles: shouldResetVolunteers ? cloneBBQ(base.volunteerRoles) : savedVolunteerRoles,
    volunteerSignups: shouldResetVolunteers ? [] : saved.volunteerSignups || [],
    volunteerResetVersion: VOLUNTEER_RESET_VERSION,
    announcements: shouldResetAnnouncements ? cloneBBQ(base.announcements) : saved.announcements || base.announcements,
    likedAnnouncementIds: shouldResetAnnouncements ? [] : saved.likedAnnouncementIds || [],
    announcementResetVersion: ANNOUNCEMENT_RESET_VERSION,
    loginRecords: saved.loginRecords || [],
    activityRecords: saved.activityRecords || [],
    reminderPrefs: {
      ...base.reminderPrefs,
      ...(saved.reminderPrefs || {}),
    },
  };
  let pinnedSeen = false;
  normalized.announcements = (normalized.announcements || []).map((item) => {
    const next = item.id === 'update-rain' ? { ...item, kind: 'rain' } : item;
    if (next.retiredAt) return { ...next, pinned: false };
    if (!next.pinned) return next;
    if (pinnedSeen) return { ...next, pinned: false };
    pinnedSeen = true;
    return next;
  });
  normalized.rsvps = (normalized.rsvps || []).map((rsvp) => ({
    ...rsvp,
    contributionTotal: contributionTotal(rsvp),
  }));
  normalized.activityRecords = backfillLoginActivityRecords(normalized.loginRecords, normalized.activityRecords);
  normalized.volunteerRoles = recomputeVolunteerRolesFromSignups(normalized.volunteerRoles, normalized.volunteerSignups);
  return normalized;
}

function loadBBQState() {
  try {
    return normalizeState(JSON.parse(localStorage.getItem(BBQ_STORAGE_KEY)));
  } catch (err) {
    return defaultState();
  }
}

function saveBBQState(state) {
  localStorage.setItem(BBQ_STORAGE_KEY, JSON.stringify(state));
}

function contributionTotal(data) {
  const adults = parseInt(data.adults, 10) || 0;
  const teens = parseInt(data.teens, 10) || 0;
  return (adults + teens) * BBQ_EVENT.contributionPerAdult;
}

function participantNamesForCount(list, count) {
  return Array.from({ length: Math.max(0, parseInt(count, 10) || 0) }, (_, index) => String((list || [])[index] || '').trim());
}

function recomputeVolunteerRolesFromSignups(roles = [], signups = []) {
  const activeSignups = (signups || []).filter((signup) => !signup.retiredAt);
  return (roles || []).map((role) => ({
    ...role,
    filled: activeSignups.filter((signup) => signup.role === role.role || signup.roleId === role.id).length,
  }));
}

function useBBQStore() {
  const [state, setState] = React.useState(() => loadBBQState());
  const [backendStatus, setBackendStatus] = React.useState('checking');
  const remoteAvailableRef = React.useRef(null);
  const activeFamilyPasswordRef = React.useRef(sessionStorage.getItem(BBQ_API_SESSION_PASSWORD_KEY) || BBQ_EVENT.familyPassword);
  const remoteSaveTimerRef = React.useRef(null);

  const scheduleRemoteSave = React.useCallback((nextState) => {
    if (remoteAvailableRef.current === false) return;
    if (remoteSaveTimerRef.current) clearTimeout(remoteSaveTimerRef.current);
    remoteSaveTimerRef.current = setTimeout(async () => {
      const payload = await bbqSaveRemoteState(nextState, activeFamilyPasswordRef.current);
      if (payload?.configured) {
        remoteAvailableRef.current = true;
        setBackendStatus('synced');
      } else {
        remoteAvailableRef.current = false;
        setBackendStatus('local');
      }
    }, 500);
  }, []);

  React.useEffect(() => {
    let live = true;
    bbqLoadRemoteState(activeFamilyPasswordRef.current).then((payload) => {
      if (!live) return;
      if (payload?.configured) {
        remoteAvailableRef.current = true;
        setBackendStatus('synced');
        if (payload.state) {
          setState((prev) => {
            const next = bbqMergeRemoteState(prev, payload.state);
            saveBBQState(next);
            return next;
          });
        }
      } else {
        remoteAvailableRef.current = false;
        setBackendStatus('local');
      }
    });
    return () => {
      live = false;
      if (remoteSaveTimerRef.current) clearTimeout(remoteSaveTimerRef.current);
    };
  }, []);

  const commit = React.useCallback((updater, options = {}) => {
    let nextState = null;
    setState((prev) => {
      const draft = typeof updater === 'function' ? updater(cloneBBQ(prev)) : updater;
      const next = normalizeState(draft);
      saveBBQState(next);
      nextState = next;
      return next;
    });
    if (nextState && options.remote !== false) scheduleRemoteSave(nextState);
  }, [scheduleRemoteSave]);

  const actions = React.useMemo(() => ({
    async verifyFamilyAccess(name, password) {
      const remote = await bbqRecordRemoteLogin(name, 'Site', password);
      if (remote?.record) {
        activeFamilyPasswordRef.current = password;
        sessionStorage.setItem(BBQ_API_SESSION_PASSWORD_KEY, password);
        remoteAvailableRef.current = !!remote.configured;
        setBackendStatus(remote.configured ? 'synced' : 'local');
        commit((draft) => {
          if (remote.state) draft = bbqMergeRemoteState(draft, remote.state);
          draft.loginRecords = [remote.record, ...(draft.loginRecords || [])]
            .filter((record, index, all) => all.findIndex((item) => item.id === record.id) === index)
            .slice(0, 200);
          pushActivityRecord(draft, remote.activityRecord || bbqBuildActivityRecord(name, 'login', 'Connexion au site', { page: 'home', detail: 'Accès famille validé' }));
          return draft;
        }, { remote: false });
        return true;
      }
      return false;
    },

    async verifyAdminAccess(name, password) {
      const remote = await bbqRecordRemoteAdminLogin(name, password);
      if (remote?.record) {
        remoteAvailableRef.current = !!remote.configured;
        setBackendStatus(remote.configured ? 'synced' : 'local');
        commit((draft) => {
          if (remote.state) draft = bbqMergeRemoteState(draft, remote.state);
          draft.loginRecords = [remote.record, ...(draft.loginRecords || [])]
            .filter((record, index, all) => all.findIndex((item) => item.id === record.id) === index)
            .slice(0, 200);
          pushActivityRecord(draft, remote.activityRecord || bbqBuildActivityRecord(name, 'admin_access', 'Accès admin validé', { page: 'admin', detail: 'Code organisateur accepté' }));
          return draft;
        }, { remote: false });
        return true;
      }
      return false;
    },

    submitRSVP(data) {
      let saved;
      commit((draft) => {
        const email = (data.email || '').trim().toLowerCase();
        const adults = parseInt(data.adults, 10) || 0;
        const teens = parseInt(data.teens, 10) || 0;
        const kids = parseInt(data.kids, 10) || 0;
        const guests = adults + teens + kids;
        const participantAdults = participantNamesForCount(data.participantAdults, adults);
        const participantTeens = participantNamesForCount(data.participantTeens, teens);
        const participantKids = participantNamesForCount(data.participantKids, kids);
        const payload = {
          ...data,
          id: data.id || bbqId('rsvp'),
          email: data.email.trim(),
          lastName: data.lastName.trim(),
          participantAdults,
          participantTeens,
          participantKids,
          submittedAt: new Date().toISOString(),
          totalGuests: guests,
          contributionTotal: contributionTotal(data),
          cancelledAt: null,
          cancelledBy: null,
        };
        const existingIndex = draft.rsvps.findIndex((r) => (r.email || '').toLowerCase() === email);
        if (existingIndex >= 0) {
          payload.id = draft.rsvps[existingIndex].id;
          draft.rsvps[existingIndex] = { ...draft.rsvps[existingIndex], ...payload };
        } else {
          draft.rsvps.push(payload);
        }

        const potluckDish = (data.potluckDish || '').trim();
        if (potluckDish) {
          const now = new Date().toISOString();
          const potluckPayload = {
            id: bbqId('potluck'),
            who: data.lastName.trim(),
            dish: potluckDish,
            kind: data.potluckType,
            servings: data.potluckServings,
            source: 'rsvp',
            rsvpId: payload.id,
            updatedAt: now,
            retiredAt: null,
            retiredBy: null,
          };
          const potluckIndex = draft.potluck.findIndex((p) => p.rsvpId === payload.id);
          if (potluckIndex >= 0) draft.potluck[potluckIndex] = { ...draft.potluck[potluckIndex], ...potluckPayload, id: draft.potluck[potluckIndex].id };
          else draft.potluck.push(potluckPayload);
        }

        if (data.volunteer) {
          const now = new Date().toISOString();
          const signup = {
            id: bbqId('volunteer'),
            name: data.lastName.trim(),
            role: data.volRole,
            time: data.volTime,
            source: 'rsvp',
            rsvpId: payload.id,
            updatedAt: now,
            retiredAt: null,
            retiredBy: null,
          };
          const signupIndex = draft.volunteerSignups.findIndex((v) => v.rsvpId === payload.id);
          const previousSignup = signupIndex >= 0 ? draft.volunteerSignups[signupIndex] : null;
          if (signupIndex >= 0) draft.volunteerSignups[signupIndex] = { ...draft.volunteerSignups[signupIndex], ...signup, id: draft.volunteerSignups[signupIndex].id };
          else draft.volunteerSignups.push(signup);
          if (!previousSignup || previousSignup.role !== data.volRole) {
            if (previousSignup) {
              const oldRole = draft.volunteerRoles.find((r) => r.role === previousSignup.role);
              if (oldRole) oldRole.filled = Math.max(0, oldRole.filled - 1);
            }
            const role = draft.volunteerRoles.find((r) => r.role === data.volRole);
            if (role && role.filled < role.need) role.filled += 1;
          }
        }

        draft.reminderPrefs.email = !!data.contactEmail;
        draft.reminderPrefs.whatsapp = !!data.contactSMS;
        draft.reminderPrefs.deadline = !!data.updatesEvent;
        draft.reminderPrefs.event = !!data.updatesEvent;
        pushActivityRecord(draft, bbqBuildActivityRecord(payload.lastName, 'rsvp_submit', 'Inscription BBQ envoyée', {
          page: 'rsvp',
          detail: activityDetail([
            `${guests} invité${guests === 1 ? '' : 's'}`,
            payload.email,
            potluckDish ? `plat: ${potluckDish}` : '',
            data.volunteer ? `bénévole: ${data.volRole}` : '',
          ]),
        }));
        if (potluckDish) {
          pushActivityRecord(draft, bbqBuildActivityRecord(payload.lastName, 'potluck_reserve', 'Plat réservé', {
            page: 'rsvp',
            detail: `${potluckDish} · ${data.potluckType} · pour ${data.potluckServings}`,
          }));
        }
        if (data.volunteer) {
          pushActivityRecord(draft, bbqBuildActivityRecord(payload.lastName, 'volunteer_signup', 'Inscription bénévole', {
            page: 'rsvp',
            detail: `${data.volRole} · ${data.volTime}`,
          }));
        }
        saved = payload;
        return draft;
      });
      return saved;
    },

    cancelRSVP(id, name = '') {
      commit((draft) => {
        const item = (draft.rsvps || []).find((rsvp) => rsvp.id === id);
        if (!item || item.cancelledAt) return draft;
        const now = new Date().toISOString();
        const cancelledBy = (name || 'Admin').trim() || 'Admin';
        draft.rsvps = (draft.rsvps || []).map((rsvp) =>
          rsvp.id === id ? { ...rsvp, cancelledAt: now, cancelledBy } : rsvp
        );
        draft.potluck = (draft.potluck || []).map((entry) =>
          entry.rsvpId === id && !entry.retiredAt ? { ...entry, retiredAt: now, retiredBy: cancelledBy } : entry
        );
        draft.volunteerSignups = (draft.volunteerSignups || []).map((signup) =>
          signup.rsvpId === id && !signup.retiredAt ? { ...signup, retiredAt: now, retiredBy: cancelledBy } : signup
        );
        draft.volunteerRoles = recomputeVolunteerRolesFromSignups(draft.volunteerRoles, draft.volunteerSignups);
        pushActivityRecord(draft, bbqBuildActivityRecord(name, 'rsvp_cancel', 'Inscription annulée', {
          page: 'admin',
          detail: activityDetail([
            item.lastName || 'Foyer',
            item.email || '',
            `${item.totalGuests || 0} invité${Number(item.totalGuests) === 1 ? '' : 's'}`,
          ]),
        }));
        return draft;
      });
    },

    saveVotes(votes, name = '') {
      commit((draft) => {
        const previous = draft.myVotes || {};
        Object.entries(votes).forEach(([group, choice]) => {
          if (!choice) return;
          draft.foodTallies[group] = draft.foodTallies[group] || {};
          if (previous[group] && draft.foodTallies[group][previous[group]]) {
            draft.foodTallies[group][previous[group]] = Math.max(0, draft.foodTallies[group][previous[group]] - 1);
          }
          draft.foodTallies[group][choice] = (draft.foodTallies[group][choice] || 0) + 1;
        });
        draft.myVotes = { ...votes };
        draft.lastVoteAt = new Date().toISOString();
        pushActivityRecord(draft, bbqBuildActivityRecord(name, 'food_vote', 'Votes repas enregistrés', {
          page: 'food',
          detail: Object.entries(votes)
            .filter(([, choice]) => !!choice)
            .map(([group, choice]) => `${group}: ${choice}`)
            .join(' · '),
        }));
        return draft;
      });
    },

    reservePotluck(form) {
      commit((draft) => {
        draft.potluck.push({ id: bbqId('potluck'), ...form, source: 'manual', updatedAt: new Date().toISOString() });
        pushActivityRecord(draft, bbqBuildActivityRecord(form.who, 'potluck_reserve', 'Plat réservé', {
          page: 'food',
          detail: `${form.dish} · ${form.kind} · pour ${form.servings}`,
        }));
        return draft;
      });
    },

    signVolunteer(roleId, name = 'Vous') {
      commit((draft) => {
        const role = draft.volunteerRoles.find((r) => r.id === roleId);
        if (!role || role.filled >= role.need) return draft;
        const cleanName = (name || '').trim() || 'Vous';
        const alreadySigned = (draft.volunteerSignups || []).some((signup) =>
          !signup.retiredAt && signup.role === role.role && (signup.name || '').trim().toLowerCase() === cleanName.toLowerCase()
        );
        if (alreadySigned) return draft;
        role.filled += 1;
        draft.volunteerSignups.push({ id: bbqId('volunteer'), name: cleanName, role: role.role, roleId, time: role.time, source: 'manual', updatedAt: new Date().toISOString() });
        pushActivityRecord(draft, bbqBuildActivityRecord(cleanName, 'volunteer_signup', 'Inscription bénévole', {
          page: 'volunteers',
          detail: `${role.role} · ${role.time}`,
        }));
        return draft;
      });
    },

    claimAnonymousVolunteerSignups(name) {
      const cleanName = (name || '').trim();
      if (!cleanName) return;
      commit((draft) => {
        (draft.volunteerSignups || []).forEach((signup) => {
          if (signup.source === 'manual' && (!signup.name || signup.name === 'Vous')) {
            signup.name = cleanName;
          }
        });
        return draft;
      });
    },

    async recordLogin(name, access = 'Site') {
      if (remoteAvailableRef.current !== false) {
        const remote = await bbqRecordRemoteLogin(name, access, activeFamilyPasswordRef.current);
        if (remote?.record) {
          remoteAvailableRef.current = !!remote.configured;
          setBackendStatus(remote.configured ? 'synced' : 'local');
          commit((draft) => {
            if (remote.state) draft = bbqMergeRemoteState(draft, remote.state);
            draft.loginRecords = [remote.record, ...(draft.loginRecords || [])]
              .filter((record, index, all) => all.findIndex((item) => item.id === record.id) === index)
              .slice(0, 200);
            pushActivityRecord(draft, remote.activityRecord || bbqBuildActivityRecord(name, 'login', 'Connexion au site', { page: 'home', detail: `Accès ${access}` }));
            return draft;
          }, { remote: false });
          return remote.record;
        }
      }
      const record = await bbqBuildLoginRecord(name, access);
      commit((draft) => {
        draft.loginRecords = [record, ...(draft.loginRecords || [])].slice(0, 100);
        pushActivityRecord(draft, bbqBuildActivityRecord(name, 'login', 'Connexion au site', { page: 'home', detail: `Accès ${access}` }));
        return draft;
      });
      return record;
    },

    addAnnouncement(form) {
      commit((draft) => {
        draft.announcements.unshift({
          id: bbqId('announcement'),
          name: form.name.trim(),
          date: bbqTodayLabel(),
          text: form.text.trim(),
          likes: 0,
          replies: 0,
          kind: form.kind || null,
          label: form.label || null,
          pinned: false,
        });
        pushActivityRecord(draft, bbqBuildActivityRecord(form.name, 'announcement_post', 'Annonce publiée', {
          page: 'announcements',
          detail: form.text.trim().slice(0, 120),
        }));
        return draft;
      });
    },

    setPinnedAnnouncement(id, pinned, name = '') {
      commit((draft) => {
        const item = (draft.announcements || []).find((announcement) => announcement.id === id && !announcement.retiredAt);
        if (!item) return draft;
        draft.announcements = (draft.announcements || []).map((announcement) => {
          let nextPinned = !!announcement.pinned;
          if (pinned) nextPinned = announcement.id === id;
          else if (announcement.id === id) nextPinned = false;
          return { ...announcement, pinned: nextPinned };
        });
        pushActivityRecord(draft, bbqBuildActivityRecord(name, pinned ? 'announcement_pin' : 'announcement_unpin', pinned ? 'Annonce épinglée' : 'Annonce désépinglée', {
          page: 'admin',
          detail: item.text ? item.text.slice(0, 120) : item.name,
        }));
        return draft;
      });
    },

    retireAnnouncement(id, name = '') {
      commit((draft) => {
        const item = (draft.announcements || []).find((announcement) => announcement.id === id);
        if (!item) return draft;
        draft.announcements = (draft.announcements || []).map((announcement) =>
          announcement.id === id
            ? { ...announcement, pinned: false, retiredAt: new Date().toISOString(), retiredBy: (name || 'Admin').trim() || 'Admin' }
            : announcement
        );
        draft.likedAnnouncementIds = (draft.likedAnnouncementIds || []).filter((likedId) => likedId !== id);
        pushActivityRecord(draft, bbqBuildActivityRecord(name, 'announcement_retire', 'Annonce supprimée', {
          page: 'admin',
          detail: item.text ? item.text.slice(0, 120) : item.name,
        }));
        return draft;
      });
    },

    retireLogs(kind, name = '', ids = []) {
      if (!['loginRecords', 'activityRecords'].includes(kind)) return;
      const targetIds = new Set((ids || []).filter(Boolean));
      if (targetIds.size === 0) return;
      commit((draft) => {
        const now = new Date().toISOString();
        const retiredBy = (name || 'Admin').trim() || 'Admin';
        const activeRows = (draft[kind] || []).filter((record) => record.id && targetIds.has(record.id) && !record.retiredAt);
        draft[kind] = (draft[kind] || []).map((record) =>
          record.id && targetIds.has(record.id) && !record.retiredAt
            ? { ...record, retiredAt: now, retiredBy }
            : record
        );
        if (kind !== 'activityRecords') {
          pushActivityRecord(draft, bbqBuildActivityRecord(name, 'logs_retire', 'Journal des connexions supprimé', {
            page: 'admin',
            detail: `${activeRows.length} entrée${activeRows.length === 1 ? '' : 's'}`,
          }));
        }
        return draft;
      });
    },

    toggleAnnouncementLike(id, name = '') {
      commit((draft) => {
        const liked = new Set(draft.likedAnnouncementIds || []);
        const item = draft.announcements.find((a) => a.id === id);
        if (!item) return draft;
        let label = 'Annonce aimée';
        if (liked.has(id)) {
          liked.delete(id);
          item.likes = Math.max(0, item.likes - 1);
          label = 'Like retiré';
        } else {
          liked.add(id);
          item.likes += 1;
        }
        draft.likedAnnouncementIds = Array.from(liked);
        pushActivityRecord(draft, bbqBuildActivityRecord(name, 'announcement_like', label, {
          page: 'announcements',
          detail: item.text ? item.text.slice(0, 120) : item.name,
        }));
        return draft;
      });
    },

    saveReminderPrefs(prefs, name = '') {
      commit((draft) => {
        draft.reminderPrefs = { ...draft.reminderPrefs, ...prefs };
        pushActivityRecord(draft, bbqBuildActivityRecord(name, 'reminder_pref', 'Préférences de rappel modifiées', {
          page: 'announcements',
          detail: Object.entries(prefs).map(([key, value]) => `${key}: ${value ? 'oui' : 'non'}`).join(' · '),
        }));
        return draft;
      });
    },

    addMemory(memory = {}, name = '') {
      const localMemory = {
        id: memory.id || bbqId('memory'),
        title: memory.title || 'Souvenir ajouté',
        date: bbqTodayLabel(),
        kind: memory.kind || 'warm',
        label: memory.label || 'Photo famille BBQ',
        dataUrl: memory.dataUrl || null,
        src: memory.src || null,
        storagePath: memory.storagePath || null,
        createdAt: new Date().toISOString(),
      };
      commit((draft) => {
        draft.uploadedMemories.unshift(localMemory);
        pushActivityRecord(draft, bbqBuildActivityRecord(name, 'photo_upload', 'Photo ajoutée', {
          page: 'photos',
          detail: localMemory.title || localMemory.label,
        }));
        return draft;
      });
      if (localMemory.dataUrl && remoteAvailableRef.current !== false) {
        bbqUploadRemoteMemory(localMemory, activeFamilyPasswordRef.current).then((payload) => {
          if (!payload?.memory) return;
          remoteAvailableRef.current = true;
          setBackendStatus('synced');
          commit((draft) => {
            const index = (draft.uploadedMemories || []).findIndex((item) => item.id === localMemory.id);
            if (index >= 0) {
              draft.uploadedMemories[index] = {
                ...draft.uploadedMemories[index],
                ...payload.memory,
              };
            }
            return draft;
          });
        });
      }
    },

    recordActivity(name, type, label, details = {}) {
      const record = bbqBuildActivityRecord(name, type, label, details);
      commit((draft) => {
        pushActivityRecord(draft, record);
        return draft;
      }, { remote: false });
      if (remoteAvailableRef.current !== false) {
        bbqRecordRemoteActivity(record, activeFamilyPasswordRef.current).then((payload) => {
          if (!payload?.record) return;
          remoteAvailableRef.current = !!payload.configured;
          setBackendStatus(payload.configured ? 'synced' : 'local');
          commit((draft) => {
            pushActivityRecord(draft, payload.record);
            return draft;
          }, { remote: false });
        });
      }
      return record;
    },

    resetLocalData() {
      const next = defaultState();
      saveBBQState(next);
      setState(next);
    },
  }), [commit]);

  return { state, actions, backendStatus };
}

window.BBQ_EVENT = BBQ_EVENT;
window.BBQ_PRODUCTION_NOTES = BBQ_PRODUCTION_NOTES;
window.BBQStore = {
  blankRSVP,
  contributionTotal,
  defaultState,
  load: loadBBQState,
  save: saveBBQState,
};
window.useBBQStore = useBBQStore;
