import type { ActionFunctionArgs, LoaderFunctionArgs } from "react-router";
import { useLoaderData, useSubmit, useNavigation, useNavigate } from "react-router";
import {
  Page,
  Layout,
  Card,
  Text,
  BlockStack,
  InlineStack,
  Button,
  Modal,
  FormLayout,
  TextField,
  Select,
  Badge,
  Tooltip,
  Box,
  Divider,
} from "@shopify/polaris";
import { useState, useCallback, useRef, useEffect } from "react";
import { useFetcher } from "react-router";
import { authenticate } from "../shopify.server";
import prisma from "../db.server";

const DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
const HOURS = Array.from({ length: 24 }, (_, i) => i); // 00:00–23:00
const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
const MAX_WEEKS_AHEAD = 8; // ~2 months

// 2 px per minute → each hour row is 120 px tall.
// Increasing this makes sub-hour deal cards taller and more readable.
const MINUTE_PX = 2;
const ROW_H = 60 * MINUTE_PX; // 120 px per hour row

/** Format a Date as "YYYY-MM-DD" (local/browser time) */
function formatDateKey(date: Date): string {
  const y = date.getFullYear();
  const m = String(date.getMonth() + 1).padStart(2, "0");
  const d = String(date.getDate()).padStart(2, "0");
  return `${y}-${m}-${d}`;
}

/** Parse a "YYYY-MM-DD" string back to a local Date */
function parseDateKey(key: string): Date {
  const [y, m, d] = key.split("-").map(Number);
  return new Date(y, m - 1, d);
}

/** Returns the 7 Date objects for the week at the given offset from current week (Sun→Sat) */
function getWeekDates(weekOffset: number): Date[] {
  const now = new Date();
  const sunday = new Date(now);
  sunday.setDate(now.getDate() - now.getDay() + weekOffset * 7);
  sunday.setHours(0, 0, 0, 0);
  return Array.from({ length: 7 }, (_, i) => {
    const d = new Date(sunday);
    d.setDate(sunday.getDate() + i);
    return d;
  });
}

function formatShortDate(date: Date): string {
  return `${date.getDate()} ${MONTHS[date.getMonth()]}`;
}

function normalizeProductGid(input: string) {
  const raw = (input ?? "").trim();
  if (!raw) return raw;
  if (raw.startsWith("gid://shopify/Product/")) return raw;
  if (/^\d+$/.test(raw)) return `gid://shopify/Product/${raw}`;
  return raw;
}

function displayProductId(input: string) {
  const raw = (input ?? "").trim();
  if (raw.startsWith("gid://shopify/Product/")) return raw.replace("gid://shopify/Product/", "");
  return raw;
}

async function searchProducts(admin: any, query: string) {
  if (!admin?.graphql || !query.trim()) return [];
  const gql = `#graphql
    query SearchProducts($query: String!) {
      products(first: 8, query: $query) {
        nodes {
          id
          title
          featuredImage { url }
          variants(first: 1) {
            nodes { sku price inventoryQuantity }
          }
        }
      }
    }
  `;
  try {
    const resp = await admin.graphql(gql, { variables: { query } });
    const json = await resp.json();
    return (json?.data?.products?.nodes ?? []).map((p: any) => ({
      gid: p.id,
      numericId: p.id.replace("gid://shopify/Product/", ""),
      title: p.title ?? "",
      image: p.featuredImage?.url ?? "",
      sku: p.variants?.nodes?.[0]?.sku ?? "",
      price: p.variants?.nodes?.[0]?.price != null ? Number(p.variants.nodes[0].price) : 0,
      inventoryQuantity: p.variants?.nodes?.[0]?.inventoryQuantity ?? null,
    }));
  } catch {
    return [];
  }
}

async function fetchProductDetails(admin: any, productGid: string) {
  if (!admin?.graphql) return null;

  const query = `#graphql
    query DealProduct($id: ID!) {
      product(id: $id) {
        title
        featuredImage { url }
        variants(first: 1) {
          nodes {
            sku
            price
          }
        }
      }
    }
  `;

  const response = await admin.graphql(query, { variables: { id: productGid } });
  const json = await response.json();
  const product = json?.data?.product;
  if (!product) return null;

  const variant = product.variants?.nodes?.[0];

  return {
    title: product.title ?? "",
    imageUrl: product.featuredImage?.url ?? "",
    sku: variant?.sku ?? "",
    price: variant?.price != null ? Number(variant.price) : null,
  };
}

type DealSlot = {
  id: string;
  specificDate: string | null;
  dayOfWeek: number;
  startHour: number;
  startMinute: number;
  durationMinutes: number;
  productTitle: string;
  productId: string;
  productSku: string;
  discountType: string;
  discountValue: number;
  originalPrice: number;
  promotionQuantity: number | null;
  isActive: boolean;
};

export const loader = async ({ request }: LoaderFunctionArgs) => {
  const { session } = await authenticate.admin(request);
  const shop = session.shop;

  const deals = await prisma.dealSlot.findMany({
    where: { shop, isActive: true },
    orderBy: [{ dayOfWeek: "asc" }, { startHour: "asc" }],
  });

  const settings = await prisma.appSettings.findUnique({ where: { shop } });

  return { deals, timezone: settings?.timezone ?? "UTC", intervalMinutes: settings?.intervalMinutes ?? 60 };
};

export const action = async ({ request }: ActionFunctionArgs) => {
  const { session, admin } = await authenticate.admin(request);
  const shop = session.shop;
  const formData = await request.formData();
  const intent = formData.get("intent") as string;

  if (intent === "searchProducts") {
    const query = String(formData.get("query") ?? "");
    const products = await searchProducts(admin, query);
    return { products };
  }

  // Returns live inventory + how many units are already allocated to other deals for this product
  if (intent === "getProductInfo") {
    const productGid = normalizeProductGid(String(formData.get("productId") ?? ""));
    const excludeId = String(formData.get("excludeId") ?? "");

    // Get shop timezone so we can compute today's date + current hour correctly
    const shopSettings = await prisma.appSettings.findUnique({ where: { shop }, select: { timezone: true } });
    const shopTz = shopSettings?.timezone ?? "UTC";
    const now = new Date();
    const todayKey = new Intl.DateTimeFormat("en-CA", {
      timeZone: shopTz, year: "numeric", month: "2-digit", day: "2-digit",
    }).format(now);
    const currentHour = parseInt(
      new Intl.DateTimeFormat("en-US", { timeZone: shopTz, hour: "2-digit", hourCycle: "h23" })
        .formatToParts(now).find((p) => p.type === "hour")?.value ?? "0", 10
    );

    let inventoryQuantity: number | null = null;
    try {
      const resp = await admin.graphql(`#graphql
        query ProductInv($id: ID!) {
          product(id: $id) {
            variants(first: 1) { nodes { inventoryQuantity } }
          }
        }
      `, { variables: { id: productGid } });
      const json = await resp.json();
      const raw = json?.data?.product?.variants?.nodes?.[0]?.inventoryQuantity;
      if (raw != null) inventoryQuantity = Number(raw);
    } catch { }

    // Only count truly FUTURE deals:
    // - specificDate > today → always future
    // - specificDate = today AND startHour > currentHour → today but not yet started
    // - specificDate = null (recurring) → count conservatively
    // Deals from today that already started/ended are excluded — their effect
    // is already reflected in the live Shopify inventory figure.
    const futureFilter = {
      shop,
      productId: productGid,
      isActive: true,
      ...(excludeId ? { id: { not: excludeId } } : {}),
      OR: [
        { specificDate: null },                               // recurring
        { specificDate: { gt: todayKey } },                   // future dates
        { specificDate: todayKey, startHour: { gt: currentHour } }, // today, not yet started
      ],
    };

    // Limited-qty deals: sum up their allocations
    const limitedDeals = await prisma.dealSlot.findMany({
      where: { ...futureFilter, promotionQuantity: { not: null } },
      select: { id: true, promotionQuantity: true },
    });
    const allocatedQty = limitedDeals.reduce((sum, d) => sum + (d.promotionQuantity ?? 0), 0);

    // Entire-stock deals: count how many exist (promotionQuantity = null)
    const entireStockCount = await prisma.dealSlot.count({
      where: { ...futureFilter, promotionQuantity: null },
    });

    return { inventoryQuantity, allocatedQty, entireStockCount };
  }

  if (intent === "create" || intent === "update") {
    const productIdInput = String(formData.get("productId") ?? "");
    const productGid = normalizeProductGid(productIdInput);

    const specificDateRaw = String(formData.get("specificDate") ?? "").trim();
    const dayOfWeek = specificDateRaw
      ? new Date(specificDateRaw + "T12:00:00").getDay()
      : Number(formData.get("dayOfWeek"));

    const startHour = Number(formData.get("startHour"));
    const startMinute = Number(formData.get("startMinute") ?? 0);
    const durationMinutes = Number(formData.get("durationMinutes") ?? 60);
    const discountType = String(formData.get("discountType"));
    const discountValue = Number(formData.get("discountValue"));
    const originalPrice = Number(formData.get("originalPrice") ?? 0);
    const promotionQtyRaw = formData.get("promotionQuantity");
    const promotionQuantity = promotionQtyRaw ? Number(promotionQtyRaw) : null;

    // ── Server-side validation ──────────────────────────────────────────────
    const errors: string[] = [];

    if (!productGid) errors.push("A product must be selected.");
    if (!Number.isInteger(startHour) || startHour < 0 || startHour > 23)
      errors.push("Start hour must be between 0 and 23.");
    if (!Number.isInteger(startMinute) || startMinute < 0 || startMinute > 59)
      errors.push("Start minute must be between 0 and 59.");
    if (!Number.isInteger(durationMinutes) || durationMinutes <= 0)
      errors.push("Duration must be greater than 0 minutes.");
    if (!["percentage", "fixed"].includes(discountType))
      errors.push("Discount type must be 'percentage' or 'fixed'.");
    if (discountType === "percentage" && (discountValue <= 0 || discountValue > 100))
      errors.push("Percentage discount must be between 1 and 100.");
    if (discountType === "fixed" && discountValue <= 0)
      errors.push("Fixed discount must be greater than 0.");
    if (promotionQuantity !== null && (!Number.isInteger(promotionQuantity) || promotionQuantity <= 0))
      errors.push("Promotion quantity must be a positive whole number.");

    // Validate timezone from settings (fetch from DB)
    const shopSettings = await prisma.appSettings.findUnique({ where: { shop }, select: { timezone: true } });
    const timezone = shopSettings?.timezone ?? "UTC";
    try { Intl.DateTimeFormat(undefined, { timeZone: timezone }); }
    catch { errors.push("The configured timezone is invalid. Please update it in Settings."); }

    if (errors.length > 0) {
      return { ok: false, errors };
    }

    // ── Overlap check (handles multi-hour deals + recurring vs one-time) ─────
    // For a one-time deal: check same specific-date AND any recurring deal on
    //   the same dayOfWeek (recurring runs every week, so it conflicts too).
    // For a recurring deal: check other recurring deals on same dayOfWeek.
    const currentId = intent === "update" ? String(formData.get("id") ?? "") : "";
    const sameDayDeals = await prisma.dealSlot.findMany({
      where: {
        shop,
        isActive: true,
        ...(specificDateRaw
          ? { OR: [{ specificDate: specificDateRaw }, { dayOfWeek, specificDate: null }] }
          : { dayOfWeek, specificDate: null }),
        ...(currentId ? { id: { not: currentId } } : {}),
      },
      select: { id: true, startHour: true, startMinute: true, durationMinutes: true, productTitle: true },
    });

    const newStartMin = startHour * 60 + startMinute;
    const newEndMin = newStartMin + durationMinutes;

    const overlapping = sameDayDeals.find((d) => {
      const exStart = d.startHour * 60 + (d.startMinute ?? 0);
      const exEnd = exStart + d.durationMinutes;
      return exStart < newEndMin && newStartMin < exEnd;
    });

    if (overlapping) {
      const dayName = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][dayOfWeek];
      const fmtMin = (m: number) =>
        `${String(Math.floor(m / 60)).padStart(2, "0")}:${String(m % 60).padStart(2, "0")}`;
      const exStart = overlapping.startHour * 60 + (overlapping.startMinute ?? 0);
      return {
        ok: false,
        errors: [
          `"${overlapping.productTitle}" runs ${fmtMin(exStart)}–${fmtMin(exStart + overlapping.durationMinutes)} on ${dayName}. ` +
          `Choose a non-overlapping time slot.`,
        ],
      };
    }

    // ── 60-min slot capacity check ─────────────────────────────────────────
    // All deals sharing the same start hour on the same day must not exceed 60 min total.
    const sameHourUsed = sameDayDeals
      .filter((d) => d.startHour === startHour)
      .reduce((sum, d) => sum + d.durationMinutes, 0);
    if (sameHourUsed + durationMinutes > 60) {
      const remaining = 60 - sameHourUsed;
      const dayName = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][dayOfWeek];
      return {
        ok: false,
        errors: [
          remaining > 0
            ? `Only ${remaining} min remaining in the ${String(startHour).padStart(2, "0")}:00 slot on ${dayName}. Choose a duration of ${remaining} min or less.`
            : `The ${String(startHour).padStart(2, "0")}:00 slot on ${dayName} is full (60/60 min used). No more deals can be added.`,
        ],
      };
    }
    // ──────────────────────────────────────────────────────────────────────

    const data = {
      shop,
      specificDate: specificDateRaw || null,
      dayOfWeek,
      startHour,
      startMinute,
      durationMinutes,
      productId: productGid,
      productTitle: String(formData.get("productTitle")),
      productSku: String(formData.get("productSku") ?? ""),
      productImage: String(formData.get("productImage") ?? ""),
      discountType,
      discountValue,
      originalPrice,
      promotionQuantity,
    };

    try {
      const needsHydration =
        !data.productTitle ||
        !data.productSku ||
        !data.productImage ||
        !data.originalPrice ||
        data.originalPrice <= 0;

      if (needsHydration && data.productId) {
        const product = await fetchProductDetails(admin, data.productId);
        if (product) {
          if (!data.productTitle) data.productTitle = product.title;
          if (!data.productSku) data.productSku = product.sku;
          if (!data.productImage) data.productImage = product.imageUrl;
          if (!data.originalPrice || data.originalPrice <= 0) {
            if (product.price != null && !Number.isNaN(product.price)) {
              data.originalPrice = product.price;
            }
          }
        }
      }
    } catch {
      // ignore product hydration failures and proceed with provided form data
    }


    if (intent === "create") {
      await prisma.dealSlot.create({ data });
    } else {
      const id = String(formData.get("id"));
      await prisma.dealSlot.update({ where: { id, shop }, data });
    }
  }

  if (intent === "delete") {
    const id = String(formData.get("id"));
    await prisma.dealSlot.update({ where: { id, shop }, data: { isActive: false } });
  }

  return { ok: true };
};

function getNowInTz(tz: string): { day: number; hour: number } {
  const parts = new Intl.DateTimeFormat("en-US", {
    timeZone: tz,
    hour: "2-digit",
    hourCycle: "h23",
    weekday: "short",
  }).formatToParts(new Date());
  const get = (type: string) => parts.find((p) => p.type === type)?.value ?? "0";
  const day = ({ Sun: 0, Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6 } as Record<string, number>)[get("weekday")] ?? 0;
  return { day, hour: parseInt(get("hour"), 10) };
}

function getNowInTzWithMinute(tz: string): { day: number; hour: number; minute: number } {
  const parts = new Intl.DateTimeFormat("en-US", {
    timeZone: tz,
    hour: "2-digit",
    minute: "2-digit",
    hourCycle: "h23",
    weekday: "short",
  }).formatToParts(new Date());
  const get = (type: string) => parts.find((p) => p.type === type)?.value ?? "0";
  const day = ({ Sun: 0, Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6 } as Record<string, number>)[get("weekday")] ?? 0;
  return { day, hour: parseInt(get("hour"), 10), minute: parseInt(get("minute"), 10) };
}

function getDealStatus(deal: DealSlot, tz: string, weekOffset: number): "past" | "current" | "upcoming" {
  if (weekOffset < 0) return "past";
  if (weekOffset > 0) return "upcoming";
  const { day: currentDay, hour: currentHour, minute: currentMinute } = getNowInTzWithMinute(tz);
  if (deal.dayOfWeek < currentDay) return "past";
  if (deal.dayOfWeek > currentDay) return "upcoming";
  // Compare in total minutes to account for startMinute correctly
  const currentTotalMinutes = currentHour * 60 + currentMinute;
  const dealStartMinutes = deal.startHour * 60 + deal.startMinute;
  const dealEndMinutes = dealStartMinutes + deal.durationMinutes;
  if (currentTotalMinutes >= dealEndMinutes) return "past";
  if (currentTotalMinutes >= dealStartMinutes) return "current";
  return "upcoming";
}

const statusTone: Record<string, "success" | "attention" | "subdued"> = {
  current: "success",
  upcoming: "attention",
  past: "subdued",
};

type ProductResult = {
  gid: string;
  numericId: string;
  title: string;
  image: string;
  sku: string;
  price: number;
  inventoryQuantity: number | null;
};

function ProductPicker({
  selectedTitle,
  onSelect,
}: {
  selectedTitle: string;
  onSelect: (p: ProductResult) => void;
}) {
  const fetcher = useFetcher<{ products: ProductResult[] }>();
  const [query, setQuery] = useState(selectedTitle || "");
  const [open, setOpen] = useState(false);
  const wrapRef = useRef<HTMLDivElement>(null);
  const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);

  // Sync label when parent changes (edit mode)
  useEffect(() => { setQuery(selectedTitle || ""); }, [selectedTitle]);

  const handleChange = (val: string) => {
    setQuery(val);
    setOpen(true);
    if (debounceRef.current) clearTimeout(debounceRef.current);
    debounceRef.current = setTimeout(() => {
      if (val.trim().length >= 1) {
        const fd = new FormData();
        fd.append("intent", "searchProducts");
        fd.append("query", val.trim());
        fetcher.submit(fd, { method: "post" });
      }
    }, 300);
  };

  const handleSelect = (p: ProductResult) => {
    setQuery(p.title);
    setOpen(false);
    onSelect(p);
  };

  // Close on outside click
  useEffect(() => {
    const handler = (e: MouseEvent) => {
      if (wrapRef.current && !wrapRef.current.contains(e.target as Node)) setOpen(false);
    };
    document.addEventListener("mousedown", handler);
    return () => document.removeEventListener("mousedown", handler);
  }, []);

  const results: ProductResult[] = fetcher.data?.products ?? [];
  const loading = fetcher.state === "submitting";

  return (
    <div ref={wrapRef} style={{ position: "relative" }}>
      <TextField
        label="Product"
        value={query}
        onChange={handleChange}
        onFocus={() => { if (results.length > 0) setOpen(true); }}
        placeholder="Search by title or paste product ID…"
        autoComplete="off"
        loading={loading}
        clearButton
        onClearButtonClick={() => { setQuery(""); setOpen(false); }}
      />
      {open && results.length > 0 && (
        <div style={{
          position: "absolute",
          top: "100%",
          left: 0,
          right: 0,
          zIndex: 9999,
          background: "#fff",
          border: "1px solid #e1e3e5",
          borderRadius: 8,
          boxShadow: "0 4px 16px rgba(0,0,0,0.12)",
          marginTop: 4,
          maxHeight: 280,
          overflowY: "auto",
        }}>
          {results.map((p) => (
            <div
              key={p.gid}
              onMouseDown={() => handleSelect(p)}
              style={{
                display: "flex",
                alignItems: "center",
                gap: 12,
                padding: "10px 14px",
                cursor: "pointer",
                borderBottom: "1px solid #f1f1f1",
              }}
              onMouseEnter={(e) => (e.currentTarget.style.background = "#f6f6f7")}
              onMouseLeave={(e) => (e.currentTarget.style.background = "transparent")}
            >
              {p.image ? (
                <img src={p.image} alt={p.title} style={{ width: 40, height: 40, objectFit: "cover", borderRadius: 4, flexShrink: 0 }} />
              ) : (
                <div style={{ width: 40, height: 40, background: "#e1e3e5", borderRadius: 4, flexShrink: 0 }} />
              )}
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontWeight: 600, fontSize: 13, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{p.title}</div>
                <div style={{ fontSize: 12, color: "#6d7175", marginTop: 2 }}>
                  {p.sku ? `SKU: ${p.sku}  ·  ` : ""}${p.price.toFixed(2)}
                </div>
              </div>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

export default function Schedule() {
  const { deals, timezone, intervalMinutes } = useLoaderData<typeof loader>();
  const submit = useSubmit();
  const navigation = useNavigation();
  const navigate = useNavigate();

  // Inventory info for the currently selected product in the modal
  const [productInventory, setProductInventory] = useState<number | null>(null);
  const [allocatedQty, setAllocatedQty] = useState<number>(0);
  const [entireStockCount, setEntireStockCount] = useState<number>(0);
  const [saveError, setSaveError] = useState<string | null>(null);
  const infoFetcher = useFetcher<{ inventoryQuantity: number | null; allocatedQty: number; entireStockCount: number }>();
  const saveFetcher = useFetcher<{ ok: boolean; errors?: string[] }>();

  // Close modal on successful save; surface error if slot conflict
  useEffect(() => {
    if (!saveFetcher.data) return;
    if (saveFetcher.data.ok) {
      setModalOpen(false);
    } else {
      setSaveError(saveFetcher.data.errors?.[0] ?? "An error occurred. Please try again.");
    }
  }, [saveFetcher.data]);

  // When infoFetcher returns data, update state
  useEffect(() => {
    if (infoFetcher.data) {
      setProductInventory(infoFetcher.data.inventoryQuantity ?? null);
      setAllocatedQty(infoFetcher.data.allocatedQty ?? 0);
      setEntireStockCount(infoFetcher.data.entireStockCount ?? 0);
    }
  }, [infoFetcher.data]);

  const [weekOffset, setWeekOffset] = useState(0);
  const weekDates = getWeekDates(weekOffset);

  // Label for the week range shown in the header, e.g. "19 May – 25 May 2025"
  const weekRangeLabel = (() => {
    const start = weekDates[0];
    const end = weekDates[6];
    const year = end.getFullYear();
    return `${formatShortDate(start)} – ${formatShortDate(end)} ${year}`;
  })();

  const [modalOpen, setModalOpen] = useState(false);
  const [editing, setEditing] = useState<DealSlot | null>(null);
  const [editingIsPast, setEditingIsPast] = useState(false);
  const [isLimitedQty, setIsLimitedQty] = useState(false);
  const [repeatWeekly, setRepeatWeekly] = useState(false);
  const [dragDeal, setDragDeal] = useState<DealSlot | null>(null);
  const [dragOverCell, setDragOverCell] = useState<string | null>(null); // "dayIdx-hour"
  const [form, setForm] = useState({
    specificDate: formatDateKey(weekDates[1]), // default Mon of current week
    dayOfWeek: "1",
    startHour: "9",
    startMinute: "0",
    durationMinutes: "60",
    productId: "",
    productTitle: "",
    productSku: "",
    discountType: "percentage",
    discountValue: "10",
    originalPrice: "0",
    promotionQuantity: "",   // empty = no cap (entire stock)
  });

  const fetchProductInfo = useCallback((productGid: string, excludeId?: string) => {
    if (!productGid) return;
    const fd = new FormData();
    fd.append("intent", "getProductInfo");
    fd.append("productId", productGid);
    if (excludeId) fd.append("excludeId", excludeId);
    infoFetcher.submit(fd, { method: "post" });
  }, [infoFetcher]);

  const openNew = (dayIdx?: number, hour?: number, specificDate?: string, prefill?: DealSlot) => {
    setEditing(null);
    setEditingIsPast(false);
    setProductInventory(null);
    setAllocatedQty(0);
    setEntireStockCount(0);
    setSaveError(null);
    const date = specificDate ?? formatDateKey(weekDates[dayIdx ?? 1]);
    const actualDayIdx = dayIdx ?? parseDateKey(date).getDay();
    const limited = prefill?.promotionQuantity != null;

    // Smart auto-fill:
    // - startMinute: always placed right after the last existing deal in that slot
    // - duration: 1 hour for future slots; remaining time in slot for the current running hour
    let smartStartMinute = 0;
    let smartDuration = 60; // default 1 hour
    if (!prefill && dayIdx != null && hour != null) {
      // Advance startMinute past any existing deals in this slot.
      // Filter directly from the deals array to avoid any key-mismatch with dealsMap.
      const targetDate = weekDateKeys[dayIdx] ?? null;
      const cellDeals = (deals as DealSlot[]).filter((d) => {
        const dayMatch = d.specificDate
          ? d.specificDate === targetDate
          : d.dayOfWeek === dayIdx;
        return dayMatch && d.startHour === hour;
      });
      if (cellDeals.length > 0) {
        const latestEndMin = cellDeals.reduce(
          (max, d) => Math.max(max, d.startMinute + d.durationMinutes), 0
        );
        if (latestEndMin > 0 && latestEndMin < 60) {
          smartStartMinute = Math.min(55, Math.ceil(latestEndMin / 5) * 5);
        }
      }
      // For the current running hour: start must be ≥ current clock minute,
      // and duration is capped to whatever time remains before the hour ends.
      const { day: nowDay, hour: nowHour, minute: nowMin } = getNowInTzWithMinute(timezone);
      const isCurrentHour = weekOffset === 0 && dayIdx === nowDay && hour === nowHour;
      if (isCurrentHour) {
        // Push startMinute past the current clock position (snapped to next 5-min mark)
        const latestStart = Math.max(smartStartMinute, nowMin);
        const snappedStart = Math.min(55, Math.ceil(latestStart / 5) * 5);
        smartStartMinute = snappedStart;
        const remaining = 60 - snappedStart;
        if (remaining > 0) {
          const validDurations = [15, 20, 30, 60, 120, 360, 720, 1440];
          smartDuration = validDurations.filter((d) => d <= remaining).pop() ?? 15;
        }
      }
    }

    setForm({
      specificDate: date,
      dayOfWeek: String(actualDayIdx),
      startHour: String(hour ?? 9),
      startMinute: prefill ? String(prefill.startMinute) : String(smartStartMinute),
      durationMinutes: prefill ? String(prefill.durationMinutes) : String(smartDuration),
      productId: prefill ? displayProductId(prefill.productId) : "",
      productTitle: prefill?.productTitle ?? "",
      productSku: prefill?.productSku ?? "",
      discountType: prefill?.discountType ?? "percentage",
      discountValue: prefill ? String(prefill.discountValue) : "10",
      originalPrice: prefill ? String(prefill.originalPrice) : "0",
      promotionQuantity: prefill?.promotionQuantity != null ? String(prefill.promotionQuantity) : "",
    });
    setIsLimitedQty(limited);
    setRepeatWeekly(false);
    if (prefill?.productId) fetchProductInfo(normalizeProductGid(prefill.productId), undefined);
    setModalOpen(true);
  };

  const openEdit = (deal: DealSlot) => {
    setEditing(deal);
    setEditingIsPast(getDealStatus(deal, timezone, weekOffset) === "past");
    setProductInventory(null);
    setAllocatedQty(0);
    setEntireStockCount(0);
    setSaveError(null);
    setForm({
      specificDate: deal.specificDate ?? "",
      dayOfWeek: String(deal.dayOfWeek),
      startHour: String(deal.startHour),
      startMinute: String(deal.startMinute),
      durationMinutes: String(deal.durationMinutes),
      productId: displayProductId(deal.productId),
      productTitle: deal.productTitle,
      productSku: deal.productSku,
      discountType: deal.discountType,
      discountValue: String(deal.discountValue),
      originalPrice: String(deal.originalPrice),
      promotionQuantity: deal.promotionQuantity != null ? String(deal.promotionQuantity) : "",
    });
    setIsLimitedQty(deal.promotionQuantity != null);
    // A deal with no specificDate is already recurring
    setRepeatWeekly(!deal.specificDate);
    if (deal.productId) fetchProductInfo(deal.productId, deal.id);
    setModalOpen(true);
  };

  const handleSave = () => {
    setSaveError(null);

    const formToSave = { ...form };

    // Past deals: always create a new slot (leave the old record untouched).
    // Active deals: update in place.
    const intent = (editing && !editingIsPast) ? "update" : "create";

    if (editingIsPast) {
      // Re-schedule this deal to next week's occurrence of the same day.
      // e.g. past Mon deal opened on Wed → new specificDate = next Monday.
      const dayOfWeek = parseInt(form.dayOfWeek, 10);
      const now = new Date();
      const todayDow = now.getDay();
      let daysUntilNext = (dayOfWeek - todayDow + 7) % 7;
      if (daysUntilNext === 0) daysUntilNext = 7; // same day → next week
      const nextDate = new Date(now);
      nextDate.setDate(now.getDate() + daysUntilNext);
      formToSave.specificDate = formatDateKey(nextDate);
    } else if (repeatWeekly) {
      // "Repeat every week" → strip specificDate so the deal recurs every week.
      formToSave.specificDate = "";
    } else if (formToSave.specificDate) {
      const today = new Date();
      today.setHours(0, 0, 0, 0);
      const slotDate = new Date(formToSave.specificDate + "T00:00:00");
      // Only clear if STRICTLY in the past (not today).
      // Today is a valid one-time date — keep it.
      if (slotDate < today) formToSave.specificDate = "";
    }

    const fd = new FormData();
    fd.append("intent", intent);
    // Only send id when updating a non-past deal
    if (intent === "update" && editing) fd.append("id", editing.id);

    Object.entries(formToSave).forEach(([k, v]) => fd.append(k, v));
    // Ensure the submitted duration reflects the capacity-capped effective value
    fd.set("durationMinutes", effectiveDuration);
    // Auto-correct startMinute if saving for the current running hour and the
    // stored startMinute is already in the past (e.g. modal opened from a cell
    // click without smart-fill firing). Snap forward to next 5-min boundary.
    {
      const { day: nowD, hour: nowH, minute: nowM } = getNowInTzWithMinute(timezone);
      const savedDay = parseInt(formToSave.dayOfWeek, 10);
      const savedHour = parseInt(formToSave.startHour, 10);
      const savedMin = parseInt(formToSave.startMinute || "0", 10);
      if (
        weekOffset === 0 &&
        savedDay === nowD &&
        savedHour === nowH &&
        savedMin < nowM
      ) {
        fd.set("startMinute", String(Math.min(55, Math.ceil(nowM / 5) * 5)));
      }
    }
    // Use saveFetcher so we can read the response — modal stays open on error.
    saveFetcher.submit(fd, { method: "post" });
  };

  const handleDelete = (id: string) => {
    const fd = new FormData();
    fd.append("intent", "delete");
    fd.append("id", id);
    submit(fd, { method: "post" });
  };

  const { day: todayDay, hour: todayHour } = getNowInTz(timezone);
  // Only highlight today's column when viewing the current week
  const highlightDay = weekOffset === 0 ? todayDay : -1;
  const highlightHour = weekOffset === 0 ? todayHour : -1;

  // Is the currently selected form slot in the past?
  // Used to block Save and show a warning banner.
  const finalSlotIsInPast = (() => {
    if (weekOffset < 0) return true;
    if (weekOffset > 0) return false;
    const { day: cd, hour: ch, minute: cm } = getNowInTzWithMinute(timezone);
    const nh = parseInt(form.startHour, 10);
    const nm = parseInt(form.startMinute || "0", 10);
    if (form.specificDate && !repeatWeekly) {
      const todayMidnight = new Date();
      todayMidnight.setHours(0, 0, 0, 0);
      const slotDate = new Date(form.specificDate + "T00:00:00");
      // Definite future date → never past
      if (slotDate > todayMidnight) return false;
      // specificDate is today or earlier → falls through to dayOfWeek check
    }
    const nd = parseInt(form.dayOfWeek, 10);
    if (nd < cd) return true;
    if (nd === cd) {
      if (nh < ch) return true; // previous hours are past
      if (nh === ch) {
        // Current running hour: allow deal creation as long as there's capacity.
        // The slot-capacity check (remainingSlotMinutes) will block if the hour is full.
        // startMinute is auto-corrected on submit so we don't block here.
        return false;
      }
      // nh > ch: future hour, not past
    }
    return false;
  })();

  // Build dealsMap for the currently viewed week
  const weekDateKeys = weekDates.map(formatDateKey); // ["2026-05-17", ..., "2026-05-23"]

  const dealsMap: Record<string, Record<string, DealSlot[]>> = {};
  for (const deal of deals as DealSlot[]) {
    let dayIdx: number;
    if (deal.specificDate) {
      // Only show in the week that contains this specific date
      const colIdx = weekDateKeys.indexOf(deal.specificDate);
      if (colIdx === -1) continue; // not in this week — skip
      dayIdx = colIdx;
    } else {
      // Legacy recurring deal — show in every week under its dayOfWeek column
      dayIdx = deal.dayOfWeek;
    }
    const dk = String(dayIdx);
    const hk = String(deal.startHour);
    if (!dealsMap[dk]) dealsMap[dk] = {};
    if (!dealsMap[dk][hk]) dealsMap[dk][hk] = [];
    dealsMap[dk][hk].push(deal);
  }

  // continuationMap: cells covered by a multi-hour deal that didn't start there.
  // Key = "dayIdx-hour", value = the deal that covers it.
  const continuationMap: Record<string, DealSlot> = {};
  for (const dk of Object.keys(dealsMap)) {
    for (const hk of Object.keys(dealsMap[dk])) {
      for (const deal of dealsMap[dk][hk]) {
        const coveredHours = Math.ceil((deal.startMinute + deal.durationMinutes) / 60);
        for (let h = 1; h < coveredHours; h++) {
          const ch = parseInt(hk) + h;
          if (ch < 24) continuationMap[`${dk}-${ch}`] = deal;
        }
      }
    }
  }

  // ── 60-min slot capacity ─────────────────────────────────────────────────
  // Compute how many minutes are still available in the currently selected hour slot.
  // Used to filter the Duration dropdown and block Save when the slot is full.
  const remainingSlotMinutes = (() => {
    if (!modalOpen) return 60;
    const targetDate = !repeatWeekly ? (form.specificDate || null) : null;
    const slotHour = parseInt(form.startHour, 10);
    const slotDay = parseInt(form.dayOfWeek, 10);
    const slotDeals = (deals as DealSlot[]).filter((d) => {
      const dayMatch = d.specificDate
        ? d.specificDate === targetDate
        : d.dayOfWeek === slotDay;
      return dayMatch && d.startHour === slotHour && d.id !== (editing?.id ?? "");
    });
    const totalUsed = slotDeals.reduce((sum, d) => sum + d.durationMinutes, 0);

    // For the currently running hour, elapsed minutes are also "used" even if no deal
    // was scheduled there — you can't create a deal in the past part of the hour.
    const { day: nowDay, hour: nowHour, minute: nowMin } = getNowInTzWithMinute(timezone);
    const isCurrentHourSlot = weekOffset === 0 && slotDay === nowDay && slotHour === nowHour;
    const effectiveTotalUsed = isCurrentHourSlot ? Math.max(totalUsed, nowMin) : totalUsed;

    return Math.max(0, 60 - effectiveTotalUsed);
  })();

  // Duration options filtered to only those that fit within the remaining slot time.
  const ALL_DURATIONS = [
    { label: "15 min", value: "15" },
    { label: "20 min", value: "20" },
    { label: "30 min", value: "30" },
    { label: "1 hour", value: "60" },
    { label: "2 hours", value: "120" },
    { label: "6 hours", value: "360" },
    { label: "12 hours", value: "720" },
    { label: "1 day", value: "1440" },
  ];
  const availableDurations = ALL_DURATIONS.filter(
    (opt) => parseInt(opt.value) <= remainingSlotMinutes
  );
  // If the current form value no longer fits, show the largest option that does.
  const effectiveDuration = availableDurations.some((o) => o.value === form.durationMinutes)
    ? form.durationMinutes
    : String(availableDurations[availableDurations.length - 1]?.value ?? "60");
  // ─────────────────────────────────────────────────────────────────────────

  return (
    <Page
      title="Weekly Deal Schedule"
      subtitle={`Timezone: ${timezone}`}
      backAction={{ content: "Dashboard", onAction: () => navigate("/app") }}
      primaryAction={{ content: "+ Add Deal", onAction: () => openNew() }}
    >
      {/* ── Week navigation bar ───────────────────────────────────── */}
      <div style={{
        display: "flex",
        alignItems: "center",
        justifyContent: "space-between",
        padding: "10px 0 14px",
        gap: 12,
      }}>
        <Button onClick={() => setWeekOffset((o) => o - 1)}>← Previous week</Button>

        <div style={{ textAlign: "center" }}>
          <div style={{ fontWeight: 700, fontSize: 15 }}>{weekRangeLabel}</div>
          {weekOffset !== 0 && (
            <button
              onClick={() => setWeekOffset(0)}
              style={{ fontSize: 12, color: "#008060", background: "none", border: "none", cursor: "pointer", padding: "2px 0" }}
            >
              Back to current week
            </button>
          )}
        </div>

        <Button
          disabled={weekOffset >= MAX_WEEKS_AHEAD}
          onClick={() => setWeekOffset((o) => Math.min(o + 1, MAX_WEEKS_AHEAD))}
        >
          Next week →
        </Button>
      </div>

      <Card padding="0">
        <div style={{ overflowX: "auto" }}>
          <div style={{ minWidth: 780 }}>

            {/*
          ── Single flat CSS Grid ──────────────────────────────────────────
          Row 1        = day-name header
          Rows 2-25    = one row per hour (60px tall each)
          Columns 1    = hour label
          Columns 2-8  = Sun–Sat

          Deal cards use `gridRow: "startHour+2 / span N"` so multi-hour
          deals physically merge their rows — no fake continuation indicators.
        */}
            <div style={{
              display: "grid",
              gridTemplateColumns: "64px repeat(7, minmax(100px, 1fr))",
              gridTemplateRows: `48px repeat(24, ${ROW_H}px)`,
            }}>

              {/* ── Header: "Hour" label ── */}
              <div style={{
                gridColumn: 1, gridRow: 1,
                padding: "12px 8px",
                fontWeight: 600, fontSize: 12, color: "#6d7175",
                borderBottom: "1px solid #e1e3e5",
                background: "#fafafa",
              }} />

              {/* ── Header: day columns ── */}
              {DAYS.map((d, i) => {
                const isToday = i === highlightDay;
                return (
                  <div
                    key={d}
                    style={{
                      gridColumn: i + 2,
                      gridRow: 1,
                      padding: "10px 8px",
                      fontWeight: 600,
                      fontSize: 12,
                      color: isToday ? "#008060" : "#6d7175",
                      borderLeft: "1px solid #e1e3e5",
                      borderBottom: "1px solid #e1e3e5",
                      textAlign: "center",
                      background: isToday ? "#f1faf7" : "#fafafa",
                    }}
                  >
                    <div>{d}</div>
                    <div style={{ fontWeight: 400, fontSize: 11, marginTop: 2, opacity: 0.8 }}>
                      {formatShortDate(weekDates[i])}
                    </div>
                  </div>
                );
              })}

              {/* ── Hour labels (column 1, rows 2-25) ── */}
              {HOURS.map((hour) => (
                <div
                  key={`lbl-${hour}`}
                  style={{
                    gridColumn: 1,
                    gridRow: hour + 2,
                    padding: "6px 8px",
                    fontSize: 11,
                    color: "#6d7175",
                    fontWeight: 500,
                    borderBottom: hour < 23 ? "1px solid #e1e3e5" : undefined,
                    background: "#fafafa",
                    userSelect: "none",
                  }}
                >
                  {String(hour).padStart(2, "0")}:00
                </div>
              ))}

              {/* ── Background cells: one per day per hour ── */}
              {HOURS.map((hour) =>
                DAYS.map((_, dayIdx) => {
                  const cellKey = `${dayIdx}-${hour}`;
                  const isCovered = !!continuationMap[cellKey]; // under a spanning deal
                  const cellIsPast = (() => {
                    if (weekOffset < 0) return true;
                    if (weekOffset > 0) return false;
                    const { day: cd, hour: ch } = getNowInTz(timezone);
                    if (dayIdx < cd) return true;
                    if (dayIdx === cd && hour < ch) return true;
                    return false;
                  })();
                  const isDragOver = dragOverCell === cellKey && !cellIsPast && !isCovered && !!dragDeal;

                  return (
                    <div
                      key={cellKey}
                      style={{
                        gridColumn: dayIdx + 2,
                        gridRow: hour + 2,
                        borderLeft: "1px solid #e1e3e5",
                        borderBottom: hour < 23 ? "1px solid #e1e3e5" : undefined,
                        background: isDragOver
                          ? "#e6f4f0"
                          : dayIdx === highlightDay && hour === highlightHour
                            ? "#f1faf7"
                            : cellIsPast
                              ? "#fafafa"
                              : undefined,
                        outline: isDragOver ? "2px dashed #008060" : undefined,
                        cursor: cellIsPast || isCovered ? "default" : "pointer",
                        transition: "background 0.1s",
                      }}
                      onClick={() => {
                        if (isCovered) { openEdit(continuationMap[cellKey]); return; }
                        if (!cellIsPast) openNew(dayIdx, hour, weekDateKeys[dayIdx]);
                      }}
                      onDragOver={(e) => {
                        if (!cellIsPast && !isCovered && dragDeal) {
                          e.preventDefault();
                          setDragOverCell(cellKey);
                        }
                      }}
                      onDragLeave={() => setDragOverCell(null)}
                      onDrop={(e) => {
                        e.preventDefault();
                        setDragOverCell(null);
                        if (!cellIsPast && !isCovered && dragDeal) {
                          openNew(dayIdx, hour, weekDateKeys[dayIdx], dragDeal);
                          setDragDeal(null);
                        }
                      }}
                    />
                  );
                })
              )}

              {/* ── Deal cards: placed in the grid, spanning rows for multi-hour deals ── */}
              {(deals as DealSlot[]).map((deal) => {
                let dayIdx: number;
                if (deal.specificDate) {
                  const colIdx = weekDateKeys.indexOf(deal.specificDate);
                  if (colIdx === -1) return null;
                  dayIdx = colIdx;
                } else {
                  dayIdx = deal.dayOfWeek;
                }

                // Span enough rows to cover the full time range including the startMinute offset
                const spanRows = Math.max(1, Math.ceil((deal.startMinute + deal.durationMinutes) / 60));
                const status = getDealStatus(deal, timezone, weekOffset);

                // Colours per status
                const bgColor = status === "current"  ? "#008060"
                              : status === "upcoming" ? "#fff8ed"
                              :                        "#f4f4f4";
                const txtColor   = status === "current" ? "#fff" : "#202223";
                const accentColor = status === "current"  ? "rgba(255,255,255,0.35)"
                                  : status === "upcoming" ? "#f0a500"
                                  :                        "#c4c4c4";

                const endTotalMin = deal.startHour * 60 + deal.startMinute + deal.durationMinutes;
                const endH = Math.floor(endTotalMin / 60) % 24;
                const endM = endTotalMin % 60;
                const timeRange = `${String(deal.startHour).padStart(2, "0")}:${String(deal.startMinute).padStart(2, "0")} – ${String(endH).padStart(2, "0")}:${String(endM).padStart(2, "0")}`;
                const durationLabel = deal.durationMinutes >= 1440
                  ? "1 day"
                  : deal.durationMinutes >= 60
                    ? `${Math.round(deal.durationMinutes / 60)}h`
                    : `${deal.durationMinutes}m`;

                // Pixel dimensions using MINUTE_PX scale
                const cardTop    = deal.startMinute * MINUTE_PX;
                const cardHeight = deal.durationMinutes * MINUTE_PX;
                // Thresholds based on actual pixel height
                const showPills     = cardHeight >= 30;  // ≥ 15 min
                const showTimeRange = cardHeight >= 56;  // ≥ 28 min (comfortable two-row height)
                const largePadding  = cardHeight >= 56;

                return (
                  <div
                    key={deal.id}
                    style={{
                      gridColumn: dayIdx + 2,
                      gridRow: `${deal.startHour + 2} / span ${spanRows}`,
                      zIndex: 2,
                      pointerEvents: "none",
                      position: "relative",
                    }}
                  >
                    <div
                      draggable
                      onDragStart={(e) => { setDragDeal(deal); e.dataTransfer.effectAllowed = "copy"; }}
                      onDragEnd={() => { setDragDeal(null); setDragOverCell(null); }}
                      onClick={(e) => { e.stopPropagation(); openEdit(deal); }}
                      style={{
                        position: "absolute",
                        top: `${cardTop}px`,
                        left: "4px",
                        right: "4px",
                        height: `${cardHeight}px`,
                        background: bgColor,
                        color: txtColor,
                        // Left accent stripe instead of full border — cleaner at small sizes
                        borderLeft: `3px solid ${accentColor}`,
                        borderTop: status === "upcoming" ? `1px solid ${accentColor}` : "none",
                        borderRight: status === "upcoming" ? `1px solid ${accentColor}` : "none",
                        borderBottom: status === "upcoming" ? `1px solid ${accentColor}` : "none",
                        borderRadius: "0 5px 5px 0",
                        padding: largePadding ? "6px 8px" : "3px 6px",
                        boxSizing: "border-box",
                        cursor: "grab",
                        pointerEvents: "auto",
                        opacity: dragDeal?.id === deal.id ? 0.45 : 1,
                        overflow: "hidden",
                        display: "flex",
                        flexDirection: "column",
                        justifyContent: "center",
                        gap: 2,
                      }}
                    >
                      {/* Title row */}
                      <div style={{
                        fontWeight: 700,
                        fontSize: 11,
                        lineHeight: 1.2,
                        overflow: "hidden",
                        textOverflow: "ellipsis",
                        whiteSpace: "nowrap",
                      }}>
                        {deal.productTitle}
                      </div>

                      {/* Pills row — shown when card is tall enough */}
                      {showPills && (
                        <div style={{ display: "flex", gap: 4, alignItems: "center", flexWrap: "nowrap" }}>
                          <span style={{ fontSize: 10, opacity: 0.9, whiteSpace: "nowrap" }}>
                            {deal.discountType === "percentage"
                              ? `${deal.discountValue}% off`
                              : `$${deal.discountValue} off`}
                          </span>
                          <span style={{
                            fontSize: 9,
                            fontWeight: 700,
                            background: status === "current" ? "rgba(255,255,255,0.25)" : "rgba(0,0,0,0.08)",
                            borderRadius: 3,
                            padding: "1px 5px",
                            whiteSpace: "nowrap",
                          }}>
                            {durationLabel}
                          </span>
                          {!deal.specificDate && (
                            <span style={{
                              fontSize: 9,
                              fontWeight: 700,
                              background: status === "current" ? "rgba(255,255,255,0.25)" : "rgba(0,128,96,0.12)",
                              color: status === "current" ? "#fff" : "#007a5a",
                              borderRadius: 3,
                              padding: "1px 5px",
                              whiteSpace: "nowrap",
                            }}>
                              ↻
                            </span>
                          )}
                        </div>
                      )}

                      {/* Time range — shown when card has comfortable height */}
                      {showTimeRange && (
                        <div style={{
                          fontSize: 10,
                          opacity: 0.65,
                          whiteSpace: "nowrap",
                          marginTop: 1,
                        }}>
                          {timeRange}
                        </div>
                      )}
                    </div>
                  </div>
                );
              })}

            </div>{/* end flat grid */}
          </div>{/* end minWidth wrapper */}
        </div>{/* end overflowX wrapper */}
      </Card>

      {/* Add / Edit Modal */}
      <Modal
        open={modalOpen}
        onClose={() => setModalOpen(false)}
        title={editing ? (editingIsPast ? "Past Deal — Reschedule" : "Edit Deal Slot") : "Add Deal Slot"}
        primaryAction={(() => {
          // Past deal: always allow rescheduling to next week — bypass the past-slot block.
          if (editingIsPast) {
            const dayName = DAYS[parseInt(form.dayOfWeek, 10)];
            return {
              content: `Schedule for next ${dayName}`,
              onAction: handleSave,
              loading: saveFetcher.state === "submitting",
            };
          }
          // Block Save if the target slot is in the past.
          if (finalSlotIsInPast) return undefined;
          // Block Save if the slot is already full and this is a new deal.
          if (!editing && remainingSlotMinutes === 0) return undefined;
          // Block Save if the chosen duration exceeds remaining slot capacity.
          if (!editing && parseInt(effectiveDuration) > remainingSlotMinutes) return undefined;
          return { content: "Save", onAction: handleSave, loading: saveFetcher.state === "submitting" };
        })()}
        secondaryActions={[
          ...(editing ? [{ content: "Delete", destructive: true, onAction: () => { handleDelete(editing.id); setModalOpen(false); } }] : []),
          { content: "Cancel", onAction: () => setModalOpen(false) },
        ]}
      >
        <Modal.Section>
          <FormLayout>
            {/* ── Recurrence toggle ── */}
            <div style={{
              background: "rgba(128,128,128,0.05)",
              border: "1px solid rgba(128,128,128,0.15)",
              borderRadius: 8,
              padding: "12px 16px",
            }}>
              <div style={{ marginBottom: 10 }}>
                <Text as="p" variant="bodyMd" fontWeight="semibold">Schedule type</Text>
              </div>
              <div style={{ display: "flex", gap: 8 }}>
                {[
                  { label: "One-time deal", recurring: false },
                  { label: "↻  Repeat every week", recurring: true },
                ].map((opt) => {
                  const active = opt.recurring === repeatWeekly;
                  return (
                    <button
                      key={String(opt.recurring)}
                      type="button"
                      onClick={() => {
                        setRepeatWeekly(opt.recurring);
                        if (!opt.recurring) {
                          // Switching back to one-time: restore the correct date
                          // for the currently selected day in this week view.
                          const correctDate = weekDateKeys[parseInt(form.dayOfWeek, 10)] ?? form.specificDate;
                          setForm((f) => ({ ...f, specificDate: correctDate }));
                        }
                      }}
                      style={{
                        padding: "7px 16px",
                        borderRadius: 6,
                        border: active ? "2px solid #008060" : "1px solid #c9cccf",
                        background: active ? "#e6f4f0" : "#fff",
                        color: active ? "#007a5a" : "#202223",
                        fontWeight: active ? 700 : 400,
                        fontSize: 13,
                        cursor: "pointer",
                      }}
                    >
                      {opt.label}
                    </button>
                  );
                })}
              </div>

              {/* Date badge — only shown for one-time deals */}
              {!repeatWeekly && form.specificDate && (
                <div style={{
                  display: "inline-flex",
                  alignItems: "center",
                  gap: 8,
                  marginTop: 12,
                  background: "#f1faf7",
                  border: "1px solid #b5e3d0",
                  borderRadius: 8,
                  padding: "8px 14px",
                  fontSize: 13,
                  fontWeight: 600,
                  color: "#007a5a",
                }}>
                  <span>📅</span>
                  <span>
                    {(() => {
                      const d = parseDateKey(form.specificDate);
                      return `${DAYS[d.getDay()]}, ${d.getDate()} ${MONTHS[d.getMonth()]} ${d.getFullYear()}`;
                    })()}
                  </span>
                </div>
              )}

              {repeatWeekly && (
                <div style={{
                  display: "inline-flex",
                  alignItems: "center",
                  gap: 8,
                  marginTop: 12,
                  background: "#f1faf7",
                  border: "1px solid #b5e3d0",
                  borderRadius: 8,
                  padding: "8px 14px",
                  fontSize: 13,
                  color: "#007a5a",
                }}>
                  ↻ Runs every {DAYS[parseInt(form.dayOfWeek, 10)]} at {String(form.startHour).padStart(2, "0")}:{String(form.startMinute).padStart(2, "0")} — starts from next occurrence
                </div>
              )}
            </div>
            <FormLayout.Group>
              <Select
                label="Day"
                options={DAYS.map((d, i) => ({ label: d, value: String(i) }))}
                value={form.dayOfWeek}
                onChange={(v) => {
                  // Keep specificDate in sync with the chosen day so a stale
                  // past date never accidentally makes the deal recurring.
                  const newSpecificDate = !repeatWeekly
                    ? (weekDateKeys[parseInt(v, 10)] ?? form.specificDate)
                    : "";
                  setForm({ ...form, dayOfWeek: v, specificDate: newSpecificDate });
                }}
              />
              <Select
                label="Start Hour"
                options={HOURS.map((h) => ({ label: `${String(h).padStart(2, "0")}:00`, value: String(h) }))}
                value={form.startHour}
                onChange={(v) => setForm({ ...form, startHour: v })}
              />
              <div>
                <Select
                  label={`Duration${remainingSlotMinutes < 60 ? ` (${remainingSlotMinutes} min remaining)` : ""}`}
                  options={availableDurations.length > 0 ? availableDurations : [{ label: "Slot full", value: form.durationMinutes }]}
                  value={effectiveDuration}
                  onChange={(v) => setForm({ ...form, durationMinutes: v })}
                  disabled={remainingSlotMinutes === 0}
                />
                {/* Slot capacity bar */}
                {(() => {
                  const used = 60 - remainingSlotMinutes;
                  if (used === 0) return null;
                  const pct = Math.round((used / 60) * 100);
                  const full = remainingSlotMinutes === 0;
                  return (
                    <div style={{ marginTop: 6 }}>
                      <div style={{
                        display: "flex",
                        justifyContent: "space-between",
                        fontSize: 11,
                        color: full ? "#b91c1c" : "#6d7175",
                        marginBottom: 3,
                      }}>
                        <span>{used}/60 min used</span>
                        <span style={{ color: full ? "#b91c1c" : "#008060", fontWeight: 600 }}>
                          {full ? "Slot full" : `${remainingSlotMinutes} min left`}
                        </span>
                      </div>
                      <div style={{ height: 4, background: "#e1e3e5", borderRadius: 4 }}>
                        <div style={{
                          height: "100%",
                          width: `${pct}%`,
                          background: full ? "#b91c1c" : "#008060",
                          borderRadius: 4,
                          transition: "width 0.2s",
                        }} />
                      </div>
                    </div>
                  );
                })()}
              </div>
            </FormLayout.Group>
            <ProductPicker
              selectedTitle={form.productTitle}
              onSelect={(p) => {
                setForm({
                  ...form,
                  productId: p.numericId,
                  productTitle: p.title,
                  productSku: p.sku,
                  originalPrice: p.price > 0 ? String(p.price) : form.originalPrice,
                });
                setSaveError(null);
                // If inventoryQuantity already came back from search, use it immediately
                if (p.inventoryQuantity != null) {
                  setProductInventory(p.inventoryQuantity);
                } else {
                  setProductInventory(null);
                }
                setAllocatedQty(0);
                setEntireStockCount(0);
                // Also fetch allocated qty for this product (excluding current deal if editing)
                fetchProductInfo(p.gid, editing?.id);
              }}
            />
            {form.productTitle ? (
              <div style={{ fontSize: 12, color: "#6d7175", marginTop: -8 }}>
                ID: {form.productId} {form.productSku ? `· SKU: ${form.productSku}` : ""}
              </div>
            ) : null}
            <FormLayout.Group>
              <Select
                label="Discount Type"
                options={[
                  { label: "Percentage (%)", value: "percentage" },
                  { label: "Fixed Amount ($)", value: "fixed" },
                ]}
                value={form.discountType}
                onChange={(v) => setForm({ ...form, discountType: v })}
              />
              <TextField
                label={form.discountType === "percentage" ? "Discount %" : "Discount Amount ($)"}
                value={form.discountValue}
                onChange={(v) => setForm({ ...form, discountValue: v })}
                type="number"
                autoComplete="off"
              />
              <TextField
                label="Original Price ($)"
                value={form.originalPrice}
                onChange={(v) => setForm({ ...form, originalPrice: v })}
                type="number"
                autoComplete="off"
              />
            </FormLayout.Group>

            {/* ── Slot full warning ── */}
            {!editing && remainingSlotMinutes === 0 && (
              <div style={{
                background: "#fff4f4",
                border: "1px solid #ffc9c9",
                borderRadius: 8,
                padding: "12px 16px",
                color: "#b91c1c",
                fontSize: 13,
                fontWeight: 500,
              }}>
                🚫 This hour slot is full (60/60 min used). Change the hour or remove an existing deal to add another.
              </div>
            )}

            {/* ── Past-slot warning ── */}
            {finalSlotIsInPast && !editingIsPast && (
              <div style={{
                background: "#fff8ec",
                border: "1px solid #ffc453",
                borderRadius: 8,
                padding: "12px 16px",
                color: "#7d4e00",
                fontSize: 13,
                fontWeight: 500,
              }}>
                ⚠️ This time slot is in the past. Change the day or hour to a future time to save the deal.
              </div>
            )}

            {/* ── Save error banner ── */}
            {saveError && (
              <div style={{
                background: "#fff4f4",
                border: "1px solid #ffc9c9",
                borderRadius: 8,
                padding: "12px 16px",
                color: "#b91c1c",
                fontSize: 13,
                fontWeight: 500,
              }}>
                ⚠️ {saveError}
              </div>
            )}

            {/* ── Promotion quantity ── */}
            <div style={{
              background: "rgba(128,128,128,0.05)",
              border: "1px solid rgba(128,128,128,0.15)",
              borderRadius: 8,
              padding: "14px 16px",
            }}>
              <div style={{ marginBottom: 10 }}>
                <Text as="p" variant="bodyMd" fontWeight="semibold">Promotion Stock</Text>
                <Text as="p" variant="bodySm" tone="subdued">
                  Limit how many units are eligible for this deal, or offer your entire remaining stock.
                </Text>
              </div>

              {/* Inventory availability row */}
              {form.productTitle && (
                <div style={{
                  display: "flex",
                  gap: 16,
                  marginBottom: 12,
                  padding: "8px 12px",
                  background: "#f6f6f7",
                  borderRadius: 6,
                  fontSize: 13,
                }}>
                  {infoFetcher.state === "submitting" ? (
                    <span style={{ color: "#6d7175" }}>Checking inventory…</span>
                  ) : productInventory !== null ? (
                    <>
                      <span>
                        <strong>{productInventory}</strong>
                        <span style={{ color: "#6d7175" }}> total in stock</span>
                      </span>
                      {allocatedQty > 0 && (
                        <span>
                          <strong style={{ color: "#e97c1a" }}>{allocatedQty}</strong>
                          <span style={{ color: "#6d7175" }}> allocated to other deals</span>
                        </span>
                      )}
                      <span>
                        <strong style={{ color: allocatedQty > 0 ? "#008060" : "#202223" }}>
                          {productInventory - allocatedQty}
                        </strong>
                        <span style={{ color: "#6d7175" }}> available for this deal</span>
                      </span>
                    </>
                  ) : (
                    <span style={{ color: "#6d7175" }}>Select a product to see available inventory</span>
                  )}
                </div>
              )}

              {/* Overlap warnings — advisory only, never block */}
              {(() => {
                const available = productInventory !== null ? productInventory - allocatedQty : null;
                const warnings: string[] = [];
                if (entireStockCount > 0) {
                  warnings.push(
                    `${entireStockCount} other deal${entireStockCount > 1 ? "s" : ""} already claim${entireStockCount === 1 ? "s" : ""} the entire stock of this product.`
                  );
                }
                if (available !== null && available <= 0 && allocatedQty > 0) {
                  warnings.push(
                    `Other deals have already allocated all ${productInventory} units. You may still create this deal — stock may be available at deal runtime.`
                  );
                }
                if (warnings.length === 0) return null;
                return (
                  <div style={{
                    marginBottom: 12,
                    padding: "8px 12px",
                    background: "#fff8ec",
                    border: "1px solid #ffc453",
                    borderRadius: 6,
                    fontSize: 12,
                    color: "#7d4e00",
                  }}>
                    <strong>⚠️ Heads up</strong>
                    {warnings.map((w, i) => <div key={i} style={{ marginTop: 4 }}>{w}</div>)}
                  </div>
                );
              })()}

              {/* Toggle buttons */}
              <div style={{ display: "flex", gap: 8, marginBottom: isLimitedQty ? 12 : 0 }}>
                {[
                  { label: "Entire stock", value: "" },
                  { label: "Limited quantity", value: "cap" },
                ].map((opt) => {
                  const active = opt.value === "" ? !isLimitedQty : isLimitedQty;
                  return (
                    <button
                      key={opt.value}
                      type="button"
                      onClick={() => {
                        setSaveError(null);
                        const limited = opt.value !== "";
                        setIsLimitedQty(limited);
                        setForm({ ...form, promotionQuantity: limited ? (form.promotionQuantity || "1") : "" });
                      }}
                      style={{
                        padding: "7px 16px",
                        borderRadius: 6,
                        border: active ? "2px solid #008060" : "1px solid #c9cccf",
                        background: active ? "#e6f4f0" : "#fff",
                        color: active ? "#007a5a" : "#202223",
                        fontWeight: active ? 700 : 400,
                        fontSize: 13,
                        cursor: "pointer",
                      }}
                    >
                      {opt.label}
                    </button>
                  );
                })}
              </div>

              {/* Quantity input — only shown when "Limited quantity" is selected */}
              {isLimitedQty && (
                <div style={{ marginTop: 12 }}>
                  <TextField
                    label="Eligible units for this deal"
                    value={form.promotionQuantity}
                    onChange={(v) => {
                      setForm({ ...form, promotionQuantity: v });
                      setSaveError(null);
                    }}
                    type="number"
                    autoComplete="off"
                    min="1"
                    helpText="The deal ends automatically once this many units have been sold."
                  />
                  {/* Advisory warning — shown when planned qty exceeds available stock */}
                  {(() => {
                    const promoQty = Number(form.promotionQuantity);
                    if (!promoQty || productInventory === null) return null;
                    const available = productInventory - allocatedQty;
                    if (promoQty <= available) return null;
                    const fmt = (n: number) => n.toLocaleString("en-US");
                    return (
                      <div style={{
                        marginTop: 8,
                        padding: "10px 14px",
                        background: "#fff8ec",
                        border: "1px solid #ffc453",
                        borderRadius: 6,
                        fontSize: 13,
                        color: "#7d4e00",
                        fontWeight: 500,
                        lineHeight: 1.5,
                      }}>
                        ⚠️ Available stock is <strong>{fmt(available)}</strong>, you are planning <strong>{fmt(promoQty)}</strong> units.
                      </div>
                    );
                  })()}
                </div>
              )}
            </div>

          </FormLayout>
        </Modal.Section>
      </Modal>
    </Page>
  );
}
