// app-state.jsx — local-first app data and helpers


const CURRENCIES = [
  { code: "GBP", label: "British Pound", symbol: "£" },
  { code: "USD", label: "US Dollar", symbol: "$" },
  { code: "EUR", label: "Euro", symbol: "€" },
  { code: "CAD", label: "Canadian Dollar", symbol: "C$" },
  { code: "AUD", label: "Australian Dollar", symbol: "A$" },
];

const BUSINESS_TYPES = [
  { id: "classes", label: "Classes", desc: "Group classes with limited spaces per session, term bookings, waivers, and live capacity tracking." },
  { id: "pool_hire", label: "Private Pool Hire", desc: "Hourly private pool slots, rules, payments, and blocked maintenance times." },
  { id: "beauty", label: "Beautician", desc: "Facials, massage, brows, lashes, deposits, and treatment questions." },
  { id: "nails", label: "Nail Bar", desc: "Manicures, pedicures, nail art, staff-ready service durations and prices." },
];

function uid(prefix = "id") {
  return `${prefix}-${Math.random().toString(36).slice(2, 9)}-${Date.now().toString(36)}`;
}

function todayIso() {
  return new Date().toISOString().slice(0, 10);
}

function datePartsToIso(d) {
  const yyyy = String(d.year);
  const mm = String(d.month + 1).padStart(2, "0");
  const dd = String(d.day).padStart(2, "0");
  return `${yyyy}-${mm}-${dd}`;
}

function isoToDateParts(iso) {
  const [year, month, day] = iso.split("-").map(Number);
  return { year, month: month - 1, day };
}

function minutesToTime(total) {
  const h24 = Math.floor(total / 60);
  const mins = total % 60;
  const suffix = h24 >= 12 ? "PM" : "AM";
  const h12 = ((h24 + 11) % 12) + 1;
  return `${h12}:${String(mins).padStart(2, "0")} ${suffix}`;
}

function timeToMinutes(time) {
  if (!time) return 0;
  if (time.includes(":") && !time.includes(" ")) {
    const [h, m] = time.split(":").map(Number);
    return h * 60 + m;
  }
  const [, hh, mm, suffix] = time.match(/(\d+):(\d+)\s*(AM|PM)/i) || [];
  let h = Number(hh || 0);
  const m = Number(mm || 0);
  if (suffix?.toUpperCase() === "PM" && h !== 12) h += 12;
  if (suffix?.toUpperCase() === "AM" && h === 12) h = 0;
  return h * 60 + m;
}

function formatIsoDate(iso, opts = {}) {
  if (!iso) return "—";
  const date = new Date(`${iso}T12:00:00`);
  return date.toLocaleDateString("en-US", {
    weekday: opts.weekday || undefined,
    month: "short",
    day: "numeric",
    year: opts.year ? "numeric" : undefined,
  });
}

function normalizeHex(value, fallback) {
  const hex = String(value || "").trim();
  return /^#[0-9a-f]{6}$/i.test(hex) ? hex : fallback;
}

function hexToRgb(hex) {
  const clean = normalizeHex(hex, "#efa403").slice(1);
  return {
    r: parseInt(clean.slice(0, 2), 16),
    g: parseInt(clean.slice(2, 4), 16),
    b: parseInt(clean.slice(4, 6), 16),
  };
}

function shadeHex(hex, amount = 18) {
  const { r, g, b } = hexToRgb(hex);
  const channel = (value) => Math.max(0, Math.min(255, value + amount)).toString(16).padStart(2, "0");
  return `#${channel(r)}${channel(g)}${channel(b)}`;
}

function getLogoText(profile = {}) {
  return String(profile.logoText || profile.initials || profile.brandName?.slice(0, 1) || "N").slice(0, 3).toUpperCase();
}

function getBrandStyle(profile = {}) {
  const accent = normalizeHex(profile.accent, "#efa403");
  const logoBg = normalizeHex(profile.logoBg, accent);
  const logoColor = profile.logoColor || "#ffffff";
  const rgb = hexToRgb(accent);
  return {
    "--accent": accent,
    "--accent-hover": shadeHex(accent, 18),
    "--accent-soft": `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.16)`,
    "--brand-logo-bg": logoBg,
    "--brand-logo-fg": logoColor,
  };
}

function bookingDateTimeLabel(booking) {
  return `${formatIsoDate(booking.date, { year: true })} ${booking.time}`;
}

function makeAvailability(overrides = {}) {
  const base = [
    { name: "Monday", on: false, start: "09:00", end: "17:00" },
    { name: "Tuesday", on: false, start: "09:00", end: "17:00" },
    { name: "Wednesday", on: false, start: "09:00", end: "17:00" },
    { name: "Thursday", on: false, start: "09:00", end: "17:00" },
    { name: "Friday", on: false, start: "09:00", end: "17:00" },
    { name: "Saturday", on: false, start: "09:00", end: "17:00" },
    { name: "Sunday", on: false, start: "09:00", end: "17:00" },
  ];
  return base.map((day) => ({ ...day, ...(overrides[day.name] || {}) }));
}

function makeEvent(config) {
  return normalizeEvent({
    id: uid("evt"),
    enabled: true,
    paymentMode: Number(config.price || 0) > 0 ? "full" : "none",
    depositAmount: "",
    questions: [],
    requireTerms: false,
    termsText: "",
    useCustomAvailability: false,
    customAvailability: [],
    ...config,
  });
}

function getBusinessTemplate(type) {
  const sharedPoolQuestions = [
    { id: uid("q"), label: "How many guests/swimmers are attending?", type: "text", required: true },
    { id: uid("q"), label: "Any children, accessibility needs, or important notes?", type: "textarea", required: false },
    { id: uid("q"), label: "Emergency contact number", type: "text", required: true },
  ];
  const classQuestions = [
    { id: uid("q"), label: "What is your experience level?", type: "text", required: true },
    { id: uid("q"), label: "Any injuries, medical conditions, or anything we should know?", type: "textarea", required: false },
    { id: uid("q"), label: "Emergency contact name and number", type: "text", required: true },
  ];
  const treatmentQuestions = [
    { id: uid("q"), label: "Any allergies, sensitivities, or medical notes?", type: "textarea", required: false },
  ];
  const templates = {
    classes: {
      profile: {
        businessType: "classes",
        setupComplete: true,
        brandName: "",
        logoText: "",
        logoBg: "#efa403",
        logoColor: "#ffffff",
        accent: "#efa403",
        title: "",
        bio: "",
        bookingUrl: "",
      },
      availability: makeAvailability({
        Monday:    { on: true, start: "06:00", end: "20:00" },
        Tuesday:   { on: true, start: "06:00", end: "20:00" },
        Wednesday: { on: true, start: "06:00", end: "20:00" },
        Thursday:  { on: true, start: "06:00", end: "20:00" },
        Friday:    { on: true, start: "06:00", end: "20:00" },
        Saturday:  { on: true, start: "08:00", end: "14:00" },
      }),
      events: [],
    },
    pool_hire: {
      profile: {
        businessType: "pool_hire",
        setupComplete: true,
        brandName: "",
        logoText: "",
        logoBg: "#0070BA",
        logoColor: "#ffffff",
        accent: "#0070BA",
        title: "",
        bio: "",
        bookingUrl: "",
      },
      availability: makeAvailability({
        Monday: { on: true, start: "08:00", end: "20:00" },
        Tuesday: { on: true, start: "08:00", end: "20:00" },
        Wednesday: { on: true, start: "08:00", end: "20:00" },
        Thursday: { on: true, start: "08:00", end: "20:00" },
        Friday: { on: true, start: "08:00", end: "20:00" },
        Saturday: { on: true, start: "09:00", end: "18:00" },
        Sunday: { on: true, start: "09:00", end: "18:00" },
      }),
      events: [],
    },
    beauty: {
      profile: {
        businessType: "beauty",
        setupComplete: true,
        brandName: "",
        logoText: "",
        logoBg: "#f59e9e",
        logoColor: "#171923",
        accent: "#f59e9e",
        title: "",
        bio: "",
        bookingUrl: "",
      },
      availability: makeAvailability({ Tuesday: { on: true, start: "10:00", end: "18:00" }, Wednesday: { on: true, start: "10:00", end: "18:00" }, Thursday: { on: true, start: "10:00", end: "20:00" }, Friday: { on: true, start: "10:00", end: "18:00" }, Saturday: { on: true, start: "09:00", end: "16:00" } }),
      events: [],
    },
    nails: {
      profile: {
        businessType: "nails",
        setupComplete: true,
        brandName: "",
        logoText: "",
        logoBg: "#a78bfa",
        logoColor: "#ffffff",
        accent: "#a78bfa",
        title: "",
        bio: "",
        bookingUrl: "",
      },
      availability: makeAvailability({ Monday: { on: true, start: "09:30", end: "17:30" }, Tuesday: { on: true, start: "09:30", end: "17:30" }, Wednesday: { on: true, start: "09:30", end: "17:30" }, Thursday: { on: true, start: "09:30", end: "19:00" }, Friday: { on: true, start: "09:30", end: "17:30" }, Saturday: { on: true, start: "09:00", end: "16:00" } }),
      events: [],
    },
  };
  return templates[type] || templates.pool_hire;
}

function getInitialData() {
  const seed = PRESETS.generic;
  return {
    profile: {
      name: "Nora Singh",
      initials: "NS",
      title: "Owner & Lead",
      email: "nora@apexadvisors.com",
      phone: "+44 7700 900000",
      bio: "Helping clients book in for the services they love. Slots fill up — book early!",
      bookingUrl: "nexus.com/nora-singh",
      brandName: "Nexus Booking",
      accent: "#efa403",
      logoText: "N",
      logoUrl: "",
      logoBg: "#efa403",
      logoColor: "#131419",
      currency: "GBP",
      theme: "dark",
      whiteLabel: false,
      businessType: "",
      setupComplete: false,
      timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "Europe/London",
    },
    events: seed.events.map((event, index) => ({
      id: uid("evt"),
      name: event.name,
      duration: event.duration,
      color: event.color,
      price: [0, 75, 120, 45][index] || 0,
      slotInterval: event.duration >= 60 ? 60 : 30,
      location: index === 0 ? "Google Meet" : "Zoom",
      description: [
        "A relaxed first appointment to get to know what you need.",
        "A focused working session with clear next steps.",
        "A longer appointment for onboarding and setup.",
        "A short check-in for existing clients.",
      ][index],
      questions: [],
      requireTerms: false,
      termsText: "",
      useCustomAvailability: false,
      customAvailability: [],
      enabled: true,
    })),
    availability: [
      { name: "Monday", on: true, start: "09:00", end: "17:00" },
      { name: "Tuesday", on: true, start: "09:00", end: "17:00" },
      { name: "Wednesday", on: true, start: "09:00", end: "17:00" },
      { name: "Thursday", on: true, start: "09:00", end: "17:00" },
      { name: "Friday", on: true, start: "09:00", end: "15:00" },
      { name: "Saturday", on: false, start: "10:00", end: "14:00" },
      { name: "Sunday", on: false, start: "10:00", end: "14:00" },
    ],
    blockedDates: [
      { id: uid("block"), date: "2026-05-27", reason: "Personal — Long weekend" },
      { id: uid("block"), date: "2026-06-12", reason: "Training day" },
    ],
    blockedTimes: [
      { id: uid("blocktime"), date: "2026-05-15", start: "12:00", end: "14:00", reason: "Pool maintenance" },
    ],
    bookings: seed.upcomingBookings.map((b, index) => ({
      id: uid("book"),
      date: ["2026-05-12", "2026-05-12", "2026-05-13", "2026-05-13", "2026-05-14"][index],
      time: ["10:00 AM", "1:30 PM", "9:00 AM", "3:00 PM", "11:00 AM"][index],
      eventName: b.event,
      eventId: null,
      guestName: b.guest,
      guestEmail: `${b.guest.toLowerCase().replace(/\s+/g, ".")}@example.com`,
      guestPhone: "",
      notes: "",
      status: b.status,
      source: "Seed",
    })),
    integrations: {
      "Google Calendar": { connected: false, account: "" },
      Stripe: { connected: false, account: "" },
      PayPal: { connected: false, account: "" },
      "Apple Calendar": { connected: false, account: "" },
      WhatsApp: { connected: false, account: "" },
      Square: { connected: false, account: "" },
    },
    settings: {
      notifications: {
        bookEmail: true,
        bookSms: true,
        cancelEmail: true,
        reminders: true,
        reviews: false,
        marketing: false,
      },
      plan: "Professional",
    },
    platform: {
      customers: [
        { id: uid("cust"), business: "Bluewater Private Pool", owner: "Graham Ellis", email: "graham@bluewaterpool.co.uk", plan: "Professional", status: "Active", revenue: 1860, users: 3, joined: "2026-02-14" },
        { id: uid("cust"), business: "Apex Advisors", owner: "Nora Singh", email: "nora@apexadvisors.com", plan: "Professional", status: "Active", revenue: 4320, users: 5, joined: "2026-01-08" },
        { id: uid("cust"), business: "Glow Beauty Studio", owner: "Lena Ortiz", email: "lena@glowstudio.com", plan: "Business", status: "Active", revenue: 4910, users: 7, joined: "2026-03-02" },
        { id: uid("cust"), business: "Northlane Nails", owner: "Priya Shah", email: "priya@northlanenails.com", plan: "Starter", status: "Trial", revenue: 620, users: 2, joined: "2026-05-01" },
        { id: uid("cust"), business: "Harbor Swim School", owner: "Miles Carter", email: "miles@harborswim.co.uk", plan: "Professional", status: "Past due", revenue: 2180, users: 4, joined: "2026-04-11" },
      ],
      users: [
        { id: uid("usr"), name: "Justin Bamforth", email: "justin@nexusbooking.app", role: "Owner", status: "Active", lastSeen: "Today" },
        { id: uid("usr"), name: "Nora Singh", email: "nora@apexadvisors.com", role: "Customer admin", status: "Active", lastSeen: "Today" },
        { id: uid("usr"), name: "Graham Ellis", email: "graham@bluewaterpool.co.uk", role: "Customer admin", status: "Active", lastSeen: "Yesterday" },
        { id: uid("usr"), name: "Maya Reyes", email: "maya@glowstudio.com", role: "Team member", status: "Invited", lastSeen: "Never" },
      ],
      billing: [
        { id: "NB-2026-1005", customer: "Bluewater Private Pool", date: "2026-05-01", amount: 29, status: "Paid" },
        { id: "NB-2026-1004", customer: "Apex Advisors", date: "2026-05-01", amount: 29, status: "Paid" },
        { id: "NB-2026-1003", customer: "Glow Beauty Studio", date: "2026-05-01", amount: 79, status: "Paid" },
        { id: "NB-2026-1002", customer: "Harbor Swim School", date: "2026-05-01", amount: 29, status: "Past due" },
      ],
      announcements: [
        { id: uid("ann"), title: "New booking reminders", message: "SMS reminders can now be enabled per customer workspace.", audience: "All customers", status: "Draft", date: "2026-05-09" },
        { id: uid("ann"), title: "Pool booking templates", message: "New hourly booking presets are available for private pool hire businesses.", audience: "Pool customers", status: "Published", date: "2026-05-06" },
      ],
      settings: {
        supportEmail: "support@nexusbooking.app",
        platformCurrency: "GBP",
        allowTrials: true,
        trialDays: 14,
      },
    },
    activity: [
      { id: uid("act"), text: "Google Calendar sync is active.", at: "Today" },
      { id: uid("act"), text: "Stripe payments are ready.", at: "Today" },
    ],
  };
}

function normalizeEvent(event) {
  return {
    slotInterval: event.duration >= 60 ? 60 : 30,
    questions: [],
    requireTerms: false,
    termsText: "",
    paymentMode: Number(event.price || 0) > 0 ? "full" : "none",
    depositAmount: "",
    termWeeks: 0,
    classPrice: 0,
    useCustomAvailability: false,
    customAvailability: [],
    maxSpaces: 0,
    ...event,
  };
}

function normalizeData(data) {
  return {
    ...data,
    profile: {
      currency: "GBP",
      theme: "dark",
      whiteLabel: false,
      businessType: "",
      setupComplete: false,
      logoText: data.profile?.initials || "N",
      logoUrl: "",
      logoBg: data.profile?.accent || "#efa403",
      logoColor: "#ffffff",
      ...data.profile,
    },
    events: (data.events || []).map(normalizeEvent),
    blockedTimes: data.blockedTimes || [],
    platform: data.platform || getInitialData().platform,
  };
}

function useAppData() {
  const [data, setData]         = React.useState(null);   // null = loading
  const [authUser, setAuthUser] = React.useState(undefined); // undefined = checking
  const userIdRef               = React.useRef(null);

  // ── Auth listener ─────────────────────────────────────────────
  React.useEffect(() => {
    sb.auth.getSession().then(({ data: { session } }) => {
      const u = session?.user ?? null;
      setAuthUser(u);
      userIdRef.current = u?.id ?? null;
      if (u) fetchUserData(u.id).then(setData);
      else    setData(null);
    });

    const { data: { subscription } } = sb.auth.onAuthStateChange(async (event, session) => {
      const u = session?.user ?? null;
      setAuthUser(u);
      userIdRef.current = u?.id ?? null;
      if (u && (event === "SIGNED_IN" || event === "TOKEN_REFRESHED")) {
        fetchUserData(u.id).then(setData);
      }
      if (event === "SIGNED_OUT") setData(null);
    });

    return () => subscription.unsubscribe();
  }, []);

  // ── Real-time: new bookings arrive instantly ──────────────────
  React.useEffect(() => {
    const uid_val = authUser?.id;
    if (!uid_val) return;
    const channel = sb
      .channel("bookings-rt-" + uid_val)
      .on("postgres_changes", { event: "INSERT", schema: "public", table: "bookings", filter: `user_id=eq.${uid_val}` },
        (payload) => {
          setData(d => d ? ({
            ...d,
            bookings:  [dbToBooking(payload.new), ...d.bookings],
            activity:  [{ id: uid("act"), text: `${payload.new.guest_name} booked ${payload.new.event_name}.`, at: "Just now" }, ...d.activity].slice(0, 8),
          }) : d);
        })
      .subscribe();
    return () => sb.removeChannel(channel);
  }, [authUser?.id]);

  const ownerId = () => userIdRef.current;

  // ── Helpers ───────────────────────────────────────────────────
  const addActivity = React.useCallback((text) => {
    const id = uid("act");
    setData(d => d ? ({ ...d, activity: [{ id, text, at: "Just now" }, ...d.activity].slice(0, 8) }) : d);
    const o = ownerId();
    if (o) sb.from("activity").insert({ id, user_id: o, text });
  }, []);

  // ── Actions ───────────────────────────────────────────────────

  const addBooking = React.useCallback((booking) => {
    const events  = data?.events || [];
    const event   = events.find(e => e.id === booking.eventId) || events[0];
    if (!event) return null;
    const bookId     = uid("book");
    const manageToken = (typeof crypto !== "undefined" && crypto.randomUUID) ? crypto.randomUUID() : uid("tok");
    const next = {
      id: bookId, date: booking.date, time: booking.time,
      eventId: event.id, eventName: event.name,
      guestName: booking.name, guestEmail: booking.email,
      guestPhone: booking.phone || "", notes: booking.notes || "",
      answers: booking.answers || {}, location: event.location || "",
      paymentStatus: booking.paymentStatus || (Number(event.price || 0) > 0 ? "Paid" : "Not required"),
      paymentProvider: booking.paymentProvider || "",
      paymentAmount: booking.paymentAmount ?? Number(event.price || 0),
      status: "Confirmed", source: "Public page",
      manageToken,
    };
    setData(d => d ? ({
      ...d,
      bookings: [next, ...d.bookings],
      activity: [{ id: uid("act"), text: `${booking.name} booked ${event.name}.`, at: "Just now" }, ...d.activity].slice(0, 8),
    }) : d);
    const o = ownerId() || data?.userId;
    if (o) sb.from("bookings").insert({
      id: bookId, user_id: o,
      event_id: event.id, event_name: event.name,
      date: next.date, time: next.time,
      guest_name: next.guestName, guest_email: next.guestEmail, guest_phone: next.guestPhone,
      notes: next.notes, answers: next.answers, location: next.location,
      status: next.status, payment_status: next.paymentStatus,
      payment_provider: next.paymentProvider, payment_amount: next.paymentAmount, source: next.source,
      manage_token: manageToken,
    });
    return next;
  }, [data]);

  const updateBookingStatus = React.useCallback((id, status) => {
    setData(d => d ? ({ ...d, bookings: d.bookings.map(b => b.id === id ? { ...b, status } : b) }) : d);
    const o = ownerId();
    if (o) sb.from("bookings").update({ status }).eq("id", id);
  }, []);

  const rescheduleBooking = React.useCallback((id, patch) => {
    // patch: { date, time, status, notes }
    setData(d => d ? ({ ...d, bookings: d.bookings.map(b => b.id === id ? { ...b, ...patch } : b) }) : d);
    const o = ownerId();
    if (o) sb.from("bookings").update({
      date: patch.date, time: patch.time,
      status: patch.status, notes: patch.notes,
    }).eq("id", id).eq("user_id", o);
  }, []);

  const saveEvent = React.useCallback((event) => {
    const clean = normalizeEvent({
      ...event,
      duration: Number(event.duration),
      slotInterval: Number(event.slotInterval || event.duration || 30),
      price: Number(event.price || 0),
      maxSpaces: Number(event.maxSpaces || 0),
      termWeeks: Number(event.termWeeks || 0),
      classPrice: Number(event.classPrice || 0),
      depositAmount: event.depositAmount === "" ? "" : Number(event.depositAmount || 0),
      useCustomAvailability: !!event.useCustomAvailability,
      customAvailability: (event.customAvailability || []).map(day => ({ ...day, on: !!day.on })),
      questions: (event.questions || []).filter(q => q.label?.trim()).map(q => ({
        ...q, id: q.id || uid("q"), label: q.label.trim(), required: !!q.required,
      })),
    });
    setData(d => {
      if (!d) return d;
      const exists = d.events.some(e => e.id === clean.id);
      return { ...d, events: exists ? d.events.map(e => e.id === clean.id ? clean : e) : [...d.events, clean] };
    });
    const o = ownerId();
    if (o) sb.from("events").upsert(eventToDb(clean, o));
    addActivity(event.id ? `${clean.name} was updated.` : `${clean.name} was added.`);
  }, [addActivity]);

  const toggleEvent = React.useCallback((id) => {
    let next;
    setData(d => {
      if (!d) return d;
      const events = d.events.map(e => { if (e.id === id) { next = !e.enabled; return { ...e, enabled: next }; } return e; });
      return { ...d, events };
    });
    const o = ownerId();
    if (o) setTimeout(() => { if (next !== undefined) sb.from("events").update({ enabled: next }).eq("id", id); }, 0);
  }, []);

  const deleteEvent = React.useCallback((id) => {
    setData(d => d ? ({ ...d, events: d.events.filter(e => e.id !== id) }) : d);
    const o = ownerId();
    if (o) sb.from("events").delete().eq("id", id);
  }, []);

  const addPoolTemplate = React.useCallback(() => {
    const poolEvent = makeEvent({
      name: "Private Pool Hire", duration: 60, slotInterval: 60, color: "#7aa7ff", price: 40,
      location: "Your Pool Address", paymentMode: "full",
      description: "One-hour private hire of the pool for your group.",
      questions: [
        { id: uid("q"), label: "How many swimmers are attending?", type: "text", required: true },
        { id: uid("q"), label: "Emergency contact number", type: "text", required: true },
      ],
      requireTerms: true, termsText: "I agree to the pool hire safety rules and cancellation terms.",
    });
    setData(d => d ? ({
      ...d,
      events: [...d.events, poolEvent],
      activity: [{ id: uid("act"), text: "Private Pool Hire template was added.", at: "Just now" }, ...d.activity].slice(0, 8),
    }) : d);
    const o = ownerId();
    if (o) sb.from("events").insert(eventToDb(poolEvent, o));
  }, []);

  const saveAvailability = React.useCallback((availability) => {
    setData(d => d ? ({ ...d, availability }) : d);
    const o = ownerId();
    if (o) sb.from("availability").upsert(
      availability.map(day => ({ user_id: o, day_name: day.name, enabled: day.on, start_time: day.start, end_time: day.end }))
    );
    addActivity("Availability was updated.");
  }, [addActivity]);

  const addBlockedTime = React.useCallback((exception) => {
    const id = uid("blocktime");
    const item = { id, ...exception };
    setData(d => d ? ({ ...d, blockedTimes: [...(d.blockedTimes || []), item] }) : d);
    const o = ownerId();
    if (o) sb.from("blocked_times").insert({ id, user_id: o, date: exception.date, start_time: exception.start, end_time: exception.end, reason: exception.reason });
  }, []);

  const removeBlockedTime = React.useCallback((id) => {
    setData(d => d ? ({ ...d, blockedTimes: (d.blockedTimes || []).filter(b => b.id !== id) }) : d);
    const o = ownerId();
    if (o) sb.from("blocked_times").delete().eq("id", id);
  }, []);

  const addBlockedDate = React.useCallback((date, reason) => {
    addBlockedTime({ date, start: "00:00", end: "23:59", reason });
  }, [addBlockedTime]);

  const removeBlockedDate = React.useCallback((id) => {
    removeBlockedTime(id);
  }, [removeBlockedTime]);

  const connectIntegration = React.useCallback((name) => {
    setData(d => d ? ({ ...d, integrations: { ...d.integrations, [name]: { connected: true } } }) : d);
    addActivity(`${name} was connected.`);
  }, [addActivity]);

  const disconnectIntegration = React.useCallback((name) => {
    setData(d => d ? ({ ...d, integrations: { ...d.integrations, [name]: { connected: false } } }) : d);
  }, []);

  const updateProfile = React.useCallback((patch) => {
    let updated;
    setData(d => { if (!d) return d; updated = { ...d.profile, ...patch }; return { ...d, profile: updated }; });
    const o = ownerId();
    if (o) setTimeout(() => { if (updated) sb.from("profiles").upsert(profileToDb(updated, o)); }, 0);
    addActivity("Profile was updated.");
  }, [addActivity]);

  const updateNotifications = React.useCallback((notifications) => {
    setData(d => d ? ({ ...d, notifications }) : d);
    const o = ownerId();
    if (o) sb.from("profiles").update({ notifications }).eq("id", o);
  }, []);

  const applyBusinessType = React.useCallback((type) => {
    const template = getBusinessTemplate(type);
    const o = ownerId();
    setData(d => d ? ({
      ...d,
      profile: { ...d.profile, ...template.profile },
      events: template.events || [],
      availability: template.availability || d.availability,
      blockedTimes: template.blockedTimes || [],
      blockedDates: [],
      bookings: [],
      activity: [{ id: uid("act"), text: `${BUSINESS_TYPES.find(b => b.id === type)?.label || "Business"} setup applied.`, at: "Just now" }],
    }) : d);
    if (o) {
      sb.from("profiles").upsert(profileToDb(template.profile, o));
      sb.from("events").delete().eq("user_id", o).then(() => {
        if (template.events?.length) sb.from("events").insert(template.events.map(e => eventToDb(e, o)));
      });
      if (template.availability?.length) sb.from("availability").upsert(
        template.availability.map(day => ({ user_id: o, day_name: day.name, enabled: day.on, start_time: day.start, end_time: day.end }))
      );
    }
  }, []);

  const resetDemoData = React.useCallback(() => {
    if (data?.profile?.businessType) applyBusinessType(data.profile.businessType);
  }, [data, applyBusinessType]);

  const addAnnouncement = React.useCallback((announcement) => {
    setData(d => d ? ({
      ...d,
      platform: { ...d.platform, announcements: [{ id: uid("ann"), date: todayIso(), status: "Draft", ...announcement }, ...d.platform.announcements] },
    }) : d);
  }, []);

  const updateCustomerStatus = React.useCallback((id, status) => {
    setData(d => d ? ({
      ...d,
      platform: { ...d.platform, customers: d.platform.customers.map(c => c.id === id ? { ...c, status } : c) },
    }) : d);
  }, []);

  const updatePlatformSettings = React.useCallback((settings) => {
    setData(d => d ? ({ ...d, platform: { ...d.platform, settings: { ...d.platform.settings, ...settings } } }) : d);
  }, []);

  return {
    data,
    authUser,
    actions: {
      addActivity, addBooking, updateBookingStatus, rescheduleBooking,
      saveEvent, toggleEvent, deleteEvent, addPoolTemplate,
      saveAvailability, addBlockedDate, removeBlockedDate, addBlockedTime, removeBlockedTime,
      connectIntegration, disconnectIntegration,
      updateProfile, updateNotifications, resetDemoData, applyBusinessType,
      addAnnouncement, updateCustomerStatus, updatePlatformSettings,
    },
  };
}

function getAvailableTimes({ data, event, dateIso }) {
  if (!event || !dateIso) return [];
  const date = new Date(`${dateIso}T12:00:00`);
  const today = new Date(`${todayIso()}T00:00:00`);
  if (date < today) return [];

  const blocked = data.blockedDates.some((b) => b.date === dateIso);
  if (blocked) return [];

  const schedule = event.useCustomAvailability && event.customAvailability?.length ? event.customAvailability : data.availability;
  const day = schedule[date.getDay() === 0 ? 6 : date.getDay() - 1];
  if (!day?.on) return [];

  const start = timeToMinutes(day.start);
  const end = timeToMinutes(day.end);
  const duration = Number(event.duration || 30);
  const slotInterval = Number(event.slotInterval || 30);
  const taken = new Set(
    data.bookings
      .filter((b) => b.date === dateIso && b.status !== "Cancelled")
      .map((b) => b.time)
  );
  const blockedRanges = (data.blockedTimes || [])
    .filter((b) => b.date === dateIso)
    .map((b) => ({
      start: timeToMinutes(b.start),
      end: timeToMinutes(b.end),
    }));
  const isClassMode = Number(event.maxSpaces || 0) > 0;
  const times = [];
  for (let t = start; t + duration <= end; t += slotInterval) {
    const label = minutesToTime(t);
    const slotEnd = t + duration;
    const overlapsBlockedRange = blockedRanges.some((range) => t < range.end && slotEnd > range.start);
    if (overlapsBlockedRange) continue;
    if (isClassMode) {
      // Classes: count bookings for this specific event + slot; allow up to maxSpaces
      const booked = data.bookings.filter(
        (b) => b.date === dateIso && b.eventId === event.id && b.time === label && b.status !== "Cancelled"
      ).length;
      if (booked < Number(event.maxSpaces)) times.push(label);
    } else {
      if (!taken.has(label)) times.push(label);
    }
  }
  return times;
}

function getSlotInfo({ data, event, dateIso, time }) {
  if (!event || !Number(event.maxSpaces || 0)) return null;
  const booked = data.bookings.filter(
    (b) => b.date === dateIso && b.eventId === event.id && b.time === time && b.status !== "Cancelled"
  ).length;
  const total = Number(event.maxSpaces);
  return { booked, total, available: total - booked };
}

function downloadCsv(filename, rows) {
  const csv = rows.map((row) => row.map((cell) => `"${String(cell ?? "").replaceAll('"', '""')}"`).join(",")).join("\n");
  const blob = new Blob([csv], { type: "text/csv;charset=utf-8" });
  const url = URL.createObjectURL(blob);
  const link = document.createElement("a");
  link.href = url;
  link.download = filename;
  link.click();
  URL.revokeObjectURL(url);
}

function getCurrency(code) {
  return CURRENCIES.find((c) => c.code === code) || CURRENCIES[0];
}

function formatMoney(amount, currencyCode) {
  const currency = getCurrency(currencyCode);
  return `${currency.symbol}${Number(amount || 0).toLocaleString()}`;
}

window.useAppData = useAppData;
window.datePartsToIso = datePartsToIso;
window.isoToDateParts = isoToDateParts;
window.formatIsoDate = formatIsoDate;
window.bookingDateTimeLabel = bookingDateTimeLabel;
window.getAvailableTimes = getAvailableTimes;
window.getSlotInfo = getSlotInfo;
window.downloadCsv = downloadCsv;
window.uid = uid;
window.todayIso = todayIso;
window.CURRENCIES = CURRENCIES;
window.getCurrency = getCurrency;
window.formatMoney = formatMoney;
window.normalizeEvent = normalizeEvent;
window.makeAvailability = makeAvailability;
window.BUSINESS_TYPES = BUSINESS_TYPES;
window.getBusinessTemplate = getBusinessTemplate;
window.getBrandStyle = getBrandStyle;
window.getLogoText = getLogoText;
