/* global React */
/* ============================================================
   CHANTELLE — shared site store
   One source of truth in localStorage that BOTH the public site
   (homepage, booking, fitness panel) and the back office read/write.
   Inbox stays in its own key (chantelle_inbox_v1) — see chantelle-modal.jsx.
   ============================================================ */
const { useState: useStateS, useEffect: useEffectS, useCallback: useCallbackS } = React;

const SITE_KEY = "chantelle_site_v1";

/* ---- font catalogue (all preloaded in each page <head>) ---- */
const DISPLAY_FONTS = ["Bodoni Moda", "Playfair Display", "Cormorant Garamond", "DM Serif Display"];
const SANS_FONTS = ["Jost", "Work Sans", "Outfit"];

/* ---- accent palette presets ---- */
const ACCENTS = ["#EC4A12", "#0B0B0C", "#1F8A5B", "#2A6FDB", "#B11226", "#C08A2D"];
const PAPERS = ["#F3F1ED", "#F6F4EF", "#EFEDE7", "#F2EEE9"];

/* ---- placeholder gradients for new items ---- */
const SWATCHES = [
  "linear-gradient(150deg,#2c2925,#0b0b0c)",
  "linear-gradient(150deg,var(--orange-bright),#b8330a)",
  "linear-gradient(150deg,#e9e6e1,#cdc9c2)",
  "linear-gradient(150deg,#dbd7d1,#9a958c)",
];

/* ---- journal categories (label + cover gradient when no image) ---- */
const JOURNAL_CATS = [
  { key: "training", label: "Training",     grad: "linear-gradient(150deg,#2c2925,#0b0b0c)" },
  { key: "recipes",  label: "Recipes",      grad: "linear-gradient(150deg,#e9e6e1,#cdc9c2)" },
  { key: "shoots",   label: "On set", grad: "linear-gradient(150deg,#dbd7d1,#9a958c)" },
  { key: "mindset",  label: "Mindset",      grad: "linear-gradient(150deg,#3a2418,#0b0b0c)" },
  { key: "results",  label: "Results",      grad: "linear-gradient(150deg,var(--orange-bright),#b8330a)" },
  { key: "news",     label: "Announcements",grad: "linear-gradient(150deg,#1f2a26,#0b0b0c)" },
];
const catMeta = (key) => JOURNAL_CATS.find((c) => c.key === key) || JOURNAL_CATS[0];

/* ---- defaults (mirror the original hardcoded content) ---- */
const DEFAULT_SITE = {
  appearance: {
    accent: "#EC4A12",
    paper: "#F3F1ED",
    display: "Bodoni Moda",
    sans: "Jost",
  },
  text: {
    wordmark: "Chantelle",
    modeling:    { kicker: "Bookings", title: "Modeling", cta: "Modeling inquiries" },
    fitness:     { kicker: "Training", title: "Fitness", cta: "Fitness inquiries" },
    fitnessPanel:{ kicker: "Training · Digital + 1:1", title: "Fitness",
                   lede: "Train with Chantelle — download a program, or book a session in person or online." },
    note: "I like shoots that feel calm, collaborative and run on time. When you request a date, tell me a little about the concept — mood, location and the look you're after — and I'll confirm within 24 hours.",
  },
  products: [
    { id: "p1", fmt: "bundle", fmtLabel: "PDF + Video", title: "12-Week Strength", price: 89,
      desc: "Progressive lifting plan with demo videos for every movement.", color: SWATCHES[0], image: "" },
    { id: "p2", fmt: "pdf", fmtLabel: "PDF Guide", title: "Lean Kitchen", price: 39,
      desc: "60-page nutrition guide, macros and 40 simple recipes.", color: SWATCHES[2], image: "" },
    { id: "p3", fmt: "video", fmtLabel: "Video Series", title: "Mobility Reset", price: 49,
      desc: "Eight follow-along sessions to move better and recover faster.", color: SWATCHES[1], image: "" },
    { id: "p4", fmt: "spark", fmtLabel: "Built for you", title: "Custom Plan", price: 250,
      desc: "A bespoke training & nutrition plan made around your goals.", color: SWATCHES[3], image: "" },
  ],
  sessions: [
    { id: "s1", mode: "inperson", title: "In-person 1:1", dur: "60 min", price: 120 },
    { id: "s2", mode: "virtual",  title: "Virtual coaching", dur: "45 min", price: 75 },
  ],
  availability: {
    days: [false, true, true, true, true, true, false], // Su..Sa
    slots: ["7 AM", "12 PM", "5 PM", "7 PM"],
  },
  socials: {
    instagram: "",
    tiktok: "",
    youtube: "",
  },
  journal: {
    kicker: "The Journal",
    title: "Notes from the gym, the kitchen & set",
    lede: "Training I actually use, food that travels, and the occasional look behind the camera. New entries most weeks.",
    subscribeTitle: "Get new entries by email",
    subscribeSub: "One quiet note when something new goes up. No spam, unsubscribe anytime.",
  },
  posts: [
    { id: "po1", status: "published", featured: true, category: "training",
      title: "The five lifts I'd keep if I could only do five",
      slug: "five-lifts",
      excerpt: "Strip a program back to its bones and this is what's left — the movements that carry everything else.",
      cover: "", date: Date.parse("2026-05-28"),
      body: [
        { t: "p", x: "Every few months I do the same thought experiment: if my whole week collapsed to five movements, which would survive? It keeps me honest. It's easy to collect exercises; it's harder to admit most of them are decoration." },
        { t: "h", x: "Pick the lift that pays rent" },
        { t: "p", x: "A good lift earns its place by carrying over into the rest of your life — picking things up, climbing, holding a position under load. The squat, the hinge, a press, a pull, and a loaded carry cover almost everything a body is asked to do." },
        { t: "quote", x: "If a movement doesn't show up somewhere in your real life, it's a hobby — not a priority." },
        { t: "p", x: "None of this means variety is bad. It means variety is a luxury you add on top of the essentials, not a substitute for them. Build the five first. Everything else is seasoning." },
      ] },
    { id: "po2", status: "published", featured: false, category: "recipes",
      title: "The protein breakfast I make in four minutes",
      slug: "four-minute-breakfast",
      excerpt: "No blender, no cleanup, genuinely fast — the thing I eat on shoot mornings.",
      cover: "", date: Date.parse("2026-05-19"),
      body: [
        { t: "p", x: "Most of my mornings start before the light is good, which means breakfast has to be quick or it doesn't happen. This is the one I keep coming back to." },
        { t: "h", x: "What goes in" },
        { t: "p", x: "Greek yoghurt, a scoop of vanilla protein stirred through, berries, and a spoon of nut butter for staying power. That's it. Thirty grams of protein, no pan to wash." },
        { t: "quote", x: "Fast food, in the original sense: food you can actually make fast." },
        { t: "p", x: "If I have an extra minute I toast a little oats-and-seed mix for crunch. If I don't, I eat it as is, in the car, and nobody is the wiser." },
      ] },
    { id: "po3", status: "published", featured: false, category: "mindset",
      title: "On training when you don't feel like it",
      slug: "training-when-you-dont",
      excerpt: "Motivation is a guest, not a landlord. Here's the deal I make with myself on the flat days.",
      cover: "", date: Date.parse("2026-05-08"),
      body: [
        { t: "p", x: "I am not a naturally disciplined person. What I have instead is a small set of bargains I've made with myself, and the most useful one is this: I'm allowed to do the warm-up and then quit." },
        { t: "p", x: "I almost never quit. By the time the first set is done the resistance is gone, because the resistance was never about the workout — it was about starting. Lower the cost of starting and most days take care of themselves." },
        { t: "quote", x: "Discipline is mostly the art of making the first five minutes very, very cheap." },
      ] },
    { id: "po4", status: "published", featured: false, category: "shoots",
      title: "A day on set, from call time to wrap",
      slug: "day-on-set",
      excerpt: "The unglamorous, oddly lovely rhythm of a shoot day — and why I train the way I do because of it.",
      cover: "", date: Date.parse("2026-04-24"),
      body: [
        { t: "p", x: "People imagine shoot days as glamour. Mostly they are standing, waiting, and small adjustments repeated until the light agrees with everyone. Stamina matters more than anyone tells you." },
        { t: "h", x: "Why I train for endurance, not just shape" },
        { t: "p", x: "Holding a position for a camera is deceptively physical. The work I do in the gym — the carries, the unglamorous mobility — is what lets me look effortless for nine hours. The aesthetic is a side effect of the function." },
      ] },
    { id: "po5", status: "draft", featured: false, category: "news",
      title: "Something new is coming this summer",
      slug: "summer-news",
      excerpt: "A draft — not live yet. A little announcement I'm still writing.",
      cover: "", date: Date.parse("2026-06-02"),
      body: [
        { t: "p", x: "Still drafting this one. Come back soon." },
      ] },
  ],
  subscribers: [
    { id: "sub1", email: "amara@example.com", date: Date.parse("2026-05-30"), source: "Journal" },
    { id: "sub2", email: "jordan@example.com", date: Date.parse("2026-05-21"), source: "Lead magnet" },
  ],
  about: {
    kicker: "About",
    title: "Chantelle",
    lede: "Model and coach. I split my life between set and the gym, and I've learned the two feed each other.",
    body: "I started training to last longer on shoot days and fell for it completely. Now I coach the same way I work — calm, consistent, and built around real life rather than a fantasy of it.\n\nWhen I'm not on set you'll find me lifting heavy-ish, cooking something simple, or outside walking off a long day. I believe in plans you can keep and standards you can hold without hating your week.",
    portrait: "",
    stats: [
      { n: "8", l: "Years coaching" },
      { n: "120+", l: "Clients trained" },
      { n: "4", l: "Continents shot on" },
    ],
  },
  testimonials: [
    { id: "t1", quote: "Chantelle made training feel like the calmest part of my week. I've never been this consistent.", name: "Amara O.", role: "1:1 coaching client" },
    { id: "t2", quote: "The 12-week program is the first one I've ever actually finished. The videos make every lift obvious.", name: "Jordan P.", role: "Strength program" },
    { id: "t3", quote: "Easy to work with, completely professional on set, and the photos were exactly the mood we wanted.", name: "Studio Vela", role: "Campaign client" },
  ],
  faqs: [
    { id: "f1", q: "Do I need a gym for the programs?", a: "Most are written for a standard commercial gym, but each program lists the kit it needs up front, and several have a home-friendly variation." },
    { id: "f2", q: "How do bookings work?", a: "Request a date and time and it lands in my inbox. I confirm within 24 hours by email, then we lock it in." },
    { id: "f3", q: "Can I get a refund on a digital product?", a: "Because files are delivered instantly they're non-refundable, but if something's wrong with your download just message me and I'll sort it." },
    { id: "f4", q: "Do you travel for shoots?", a: "Yes — travel and rates depend on the project, so send the details through the modeling enquiry and I'll come back with specifics." },
  ],
  orders: [
    { id: "o1", productId: "p1", productTitle: "12-Week Strength", customer: "Amara O.", email: "amara@example.com", amount: 89, date: Date.parse("2026-05-31T09:12:00") },
    { id: "o2", productId: "p2", productTitle: "Lean Kitchen", customer: "Jordan P.", email: "jordan@example.com", amount: 39, date: Date.parse("2026-05-29T17:40:00") },
    { id: "o3", productId: "p3", productTitle: "Mobility Reset", customer: "Sam R.", email: "sam@example.com", amount: 49, date: Date.parse("2026-05-22T12:05:00") },
    { id: "o4", productId: "p1", productTitle: "12-Week Strength", customer: "Lena K.", email: "lena@example.com", amount: 89, date: Date.parse("2026-05-14T20:18:00") },
  ],
  bookings: [],
  promos: [
    { id: "pr1", code: "WELCOME10", kind: "percent", value: 10, active: true },
  ],
  photos: {
    home: [],     // dataURLs — first is the hero; empties fall back to placeholders
    fitness: [],  // dataURLs for the fitness gallery (smaller tiles)
    fitnessFeature: "", // optional dedicated featured image (static) for the fitness hero
    fitnessVideo: "", // optional YouTube / TikTok URL — features a video as the fitness hero
    booking: "",  // single dataURL for the booking page image
  },
};

/* ---- deep-ish merge so older saved blobs gain new fields ---- */
function mergeDefaults(saved){
  const out = JSON.parse(JSON.stringify(DEFAULT_SITE));
  if (!saved || typeof saved !== "object") return out;
  for (const k of Object.keys(DEFAULT_SITE)){
    const d = DEFAULT_SITE[k], s = saved[k];
    if (s == null) continue;
    if (Array.isArray(d)) out[k] = Array.isArray(s) ? s : d;
    else if (typeof d === "object"){
      out[k] = { ...d, ...s };
      for (const kk of Object.keys(d)){
        if (d[kk] && typeof d[kk] === "object" && !Array.isArray(d[kk]))
          out[k][kk] = { ...d[kk], ...(s[kk] || {}) };
      }
    } else out[k] = s;
  }
  return out;
}

function readSite(){
  try { return mergeDefaults(JSON.parse(localStorage.getItem(SITE_KEY) || "null")); }
  catch(e){ return mergeDefaults(null); }
}
function writeSite(site){
  try { localStorage.setItem(SITE_KEY, JSON.stringify(site)); } catch(e){ /* quota */ }
  window.dispatchEvent(new Event("cw-site"));
}
function resetSite(){ try { localStorage.removeItem(SITE_KEY); } catch(e){} window.dispatchEvent(new Event("cw-site")); }

/* React hook: [site, save] with cross-tab + same-tab sync */
function useSite(){
  const [site, setSite] = useStateS(readSite);
  useEffectS(() => {
    const h = () => setSite(readSite());
    window.addEventListener("cw-site", h);
    window.addEventListener("storage", h);
    return () => { window.removeEventListener("cw-site", h); window.removeEventListener("storage", h); };
  }, []);
  const save = useCallbackS((next) => { writeSite(next); setSite(next); }, []);
  return [site, save];
}

/* ---- apply colours + fonts to the document ---- */
function applyTheme(site){
  const a = site.appearance || DEFAULT_SITE.appearance;
  const r = document.documentElement.style;
  r.setProperty("--orange", a.accent);
  // a brighter + deeper sibling for gradients
  r.setProperty("--orange-bright", lighten(a.accent, 0.12));
  r.setProperty("--orange-deep", lighten(a.accent, -0.28));
  r.setProperty("--paper", a.paper);
  r.setProperty("--serif", `"${a.display}", "Didot", Georgia, serif`);
  r.setProperty("--sans", `"${a.sans}", "Helvetica Neue", Arial, sans-serif`);
}
/* tiny hex lighten (negative amt = darken) */
function lighten(hex, amt){
  try {
    const n = parseInt(hex.slice(1), 16);
    let R = (n >> 16) & 255, G = (n >> 8) & 255, B = n & 255;
    if (amt >= 0){
      R = Math.round(R + (255 - R) * amt); G = Math.round(G + (255 - G) * amt); B = Math.round(B + (255 - B) * amt);
    } else {
      const f = 1 + amt; R = Math.round(R * f); G = Math.round(G * f); B = Math.round(B * f);
    }
    R = Math.max(0, Math.min(255, R)); G = Math.max(0, Math.min(255, G)); B = Math.max(0, Math.min(255, B));
    return "#" + ((1 << 24) + (R << 16) + (G << 8) + B).toString(16).slice(1);
  } catch(e){ return hex; }
}

/* ---- downscale an uploaded image to a storable dataURL ---- */
function resizeImage(file, maxDim = 1280, quality = 0.82){
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => {
      const img = new Image();
      img.onload = () => {
        let { width: w, height: h } = img;
        if (w > maxDim || h > maxDim){
          const s = maxDim / Math.max(w, h); w = Math.round(w * s); h = Math.round(h * s);
        }
        const c = document.createElement("canvas"); c.width = w; c.height = h;
        c.getContext("2d").drawImage(img, 0, 0, w, h);
        resolve(c.toDataURL("image/jpeg", quality));
      };
      img.onerror = reject;
      img.src = reader.result;
    };
    reader.onerror = reject;
    reader.readAsDataURL(file);
  });
}

/* ---- parse a YouTube / TikTok URL into an embeddable form ---- */
function parseVideo(url){
  if (!url || typeof url !== "string") return null;
  const u = url.trim();
  let m = u.match(/(?:youtube\.com\/(?:watch\?v=|shorts\/|embed\/|live\/)|youtu\.be\/)([\w-]{6,})/i);
  if (m) return { kind: "youtube", id: m[1], embed: "https://www.youtube-nocookie.com/embed/" + m[1] + "?rel=0&modestbranding=1&playsinline=1", thumb: "https://img.youtube.com/vi/" + m[1] + "/hqdefault.jpg" };
  m = u.match(/tiktok\.com\/(?:@[\w.-]+\/video\/|v\/|embed\/v2\/|player\/v1\/)?(\d{6,})/i);
  if (m) return { kind: "tiktok", id: m[1], embed: "https://www.tiktok.com/embed/v2/" + m[1], thumb: "" };
  return null;
}

const uid = (p = "x") => p + Date.now().toString(36) + Math.random().toString(36).slice(2, 6);

/* ---- journal helpers ---- */
const PLAIN_TYPES = { p: 1, h: 1, quote: 1 };
function readMinutes(post){
  if (!post || !Array.isArray(post.body)) return 1;
  const words = post.body.reduce((n, b) => n + (PLAIN_TYPES[b.t] && b.x ? b.x.trim().split(/\s+/).length : 0), 0);
  return Math.max(1, Math.round(words / 200));
}
function slugify(s){
  return (s || "").toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "post";
}
function fmtDate(ts){
  try { return new Date(ts).toLocaleDateString([], { month: "long", day: "numeric", year: "numeric" }); }
  catch(e){ return ""; }
}

/* ---- subscribers (email list) — own writes go through the site store ---- */
function addSubscriber(email, source){
  const e = (email || "").trim().toLowerCase();
  if (!e) return false;
  const site = readSite();
  if ((site.subscribers || []).some((s) => s.email.toLowerCase() === e)) return "exists";
  writeSite({ ...site, subscribers: [{ id: uid("sub"), email: e, date: Date.now(), source: source || "Site" }, ...(site.subscribers || [])] });
  return true;
}
/* ---- orders (sales) ---- */
function addOrder(order){
  const site = readSite();
  writeSite({ ...site, orders: [{ id: uid("o"), date: Date.now(), ...order }, ...(site.orders || [])] });
}
/* ---- confirm a booking request from the inbox → schedule entry ---- */
function addBooking(b){
  const site = readSite();
  if (b.msgId && (site.bookings || []).some((x) => x.msgId === b.msgId)) return;
  writeSite({ ...site, bookings: [{ id: uid("bk"), status: "confirmed", ...b }, ...(site.bookings || [])] });
}

Object.assign(window, {
  SITE_KEY, DEFAULT_SITE, DISPLAY_FONTS, SANS_FONTS, ACCENTS, PAPERS, SWATCHES,
  JOURNAL_CATS, catMeta, readMinutes, slugify, fmtDate, addSubscriber, addOrder, addBooking,
  readSite, writeSite, resetSite, useSite, applyTheme, resizeImage, parseVideo, uid,
});
