"use client";

import { FormEvent, useEffect, useRef, useState } from "react";
import {
  TONE_OPTIONS,
  askForNotifications,
  hasPushSubscription,
  notificationState,
  playAlert,
  previewAlert,
  pushSupported,
  rememberSoundChoice,
  showLocalNotification,
  storedSoundChoice,
  subscribeToPush,
  unlockAudio,
  unsubscribeFromPush,
  type SoundSettings,
  type ToneName,
} from "./alerts";
import Combobox from "./Combobox";

// "staff" is the operations queue screen, "waiter" the phone app, "bills" the
// manager's active-bills and history page.
type Surface = "login" | "admin" | "guest" | "staff" | "waiter" | "bills";
type AdminSection = "overview" | "menu" | "tables" | "branding" | "languages" | "settings";
type TicketKind = "order" | "call";
type TicketStatus = "open" | "done";

type FontFace = { url: string; weight: number; style: "normal" | "italic" };

type Brand = {
  primary: string;
  accent: string;
  background: string;
  text: string;
  radius: number;
  // "custom" means the restaurant picked a font uploaded into storage/fonts;
  // fontFamily then holds its name and fontFaces the files to declare.
  font: "modern" | "classic" | "soft" | "custom";
  fontFamily?: string;
  fontFaces?: FontFace[];
};

type Settings = {
  confirmBeforeResolve: boolean;
  soundEnabled: boolean;
  orderingEnabled: boolean;
  waiterCallEnabled: boolean;
  // Whether the guest confirms a waiter call with a second tap, or the request
  // fires the moment they pick a reason.
  callConfirmEnabled: boolean;
  orderingPaused: boolean;
  showTotal: boolean;
  // Which tone plays for an order and for a waiter call, how loud, and the
  // uploaded file when the restaurant brought its own.
  soundOrder: ToneName;
  soundCall: ToneName;
  soundVolume: number;
  soundOrderAssetId: string | null;
  soundCallAssetId: string | null;
  // The two chips the restaurant used to be stuck with. Both can be renamed
  // or hidden, like any other category button on the guest page.
  showAllChip: boolean;
  allChipLabel: string;
  showFeaturedChip: boolean;
  featuredChipLabel: string;
  posWebhookUrl?: string;
  posWebhookSecret?: string;
  enabledLocales?: string[];
};

type PosApiKey = {
  id: string;
  label: string;
  createdAt: string;
  lastUsedAt: string | null;
  isActive: boolean;
};

type MenuItem = {
  id: string;
  categoryId?: string;
  variantId?: string;
  variants?: MenuVariant[];
  imageAssetId?: string;
  persisted?: boolean;
  translations?: Record<string, { name?: string; description?: string }>;
  name: string;
  description: string;
  category: string;
  portion: string;
  price: number;
  available: boolean;
  featured?: boolean;
  image?: string;
  tags: string[];
  allergens?: string[];
};

type MenuVariant = {
  id?: string;
  label: string;
  price: number;
  portionAmount?: number;
  portionUnit?: "ml" | "g" | "piece" | "portion";
  available?: boolean;
};

type MenuCategory = {
  id: string;
  name: string;
  active: boolean;
  sortOrder: number;
};

// Who is on a table: the manager's default assignment, unless a waiter has
// claimed it for this shift.
type TableWaiter = { email: string; name: string };

type DiningTable = {
  id: string;
  number: number;
  label: string;
  zone: string;
  seats: number;
  token: string;
  active: boolean;
  assignedEmail?: string | null;
  claimedEmail?: string | null;
  waiter?: TableWaiter | null;
};

type RestaurantZone = {
  id: string;
  name: string;
  sortOrder: number;
};

type TeamMemberSummary = {
  email: string;
  displayName: string;
  role: string;
};

type TicketItem = {
  id?: string;
  name: string;
  quantity: number;
  portion?: string;
  price: number;
  note?: string;
};

type Ticket = {
  id: string;
  kind: TicketKind;
  status: TicketStatus;
  tableNumber: number;
  zone: string;
  createdAt: string;
  resolvedAt?: string;
  total?: number;
  callReason?: string;
  paymentMethod?: string;
  note?: string;
  items?: TicketItem[];
  source?: "guest" | "staff";
  createdBy?: string;
  tableId?: string;
  waiter?: TableWaiter | null;
};

// One consolidated bill line for a table: the same item/variant ordered across
// several tickets is merged, with guest-sent and waiter-entered kept apart.
type VisitItem = {
  name: string;
  variantLabel: string;
  quantity: number;
  price: number;
  lineTotal: number;
  source: "guest" | "staff";
};

type TableVisit = {
  id: string;
  tableId: string;
  tableNumber: number;
  tableLabel: string;
  zone: string;
  status: "open" | "closed";
  openedAt: string;
  closedAt?: string;
  closedBy?: string;
  orderCount: number;
  total: number;
  openTicketCount: number;
  lastOrderAt?: string;
  waiter?: TableWaiter | null;
  items: VisitItem[];
};

type RestaurantState = {
  restaurant: {
    id: string;
    slug: string;
    name: string;
    location: string;
    plan: "trial" | "active" | "basic";
    hasFullAccess: boolean;
    trialDaysLeft: number | null;
  // The restaurant own contact details, editable in Podešavanja and shown to
  // MeniFlow support in the platform panel.
  ownerEmail?: string | null;
  contactPhone?: string;
  address?: string;
  city?: string;
  postalCode?: string;
    // Everything below comes from the server snapshot; the built-in demo
    // fallbacks leave it out and count as an unlimited active plan.
    locked?: boolean;
    lockNote?: string | null;
    planTone?: "locked" | "trial" | "ok" | "soon" | "urgent" | "grace" | "basic";
    planExpiresAt?: string | null;
    planDaysLeft?: number | null;
    planUnlimited?: boolean;
    graceDaysLeft?: number | null;
    defaultLocale: string;
    currency: string;
    logoAssetId?: string;
    logoUrl?: string;
    brand: Brand;
    settings: Settings;
  };
  categories: string[];
  categoryDetails?: MenuCategory[];
  items: MenuItem[];
  tables: DiningTable[];
  zones: RestaurantZone[];
  guestCopy: Record<string, Record<string, string>>;
  team: TeamMemberSummary[];
  visits: TableVisit[];
  tickets: Ticket[];
  posApiKeys?: PosApiKey[];
};

type CartLine = MenuItem & { quantity: number; note?: string };

// Kept in step with AuthRole in db/auth.ts: "staff" is a waiter, "queue" is a
// screen account that only watches the request queue.
type AuthRole = "owner" | "manager" | "staff" | "queue";

type AuthRestaurant = {
  slug: string;
  name: string;
  currency: string;
  role: AuthRole;
};

type AuthUser = {
  email: string;
  displayName: string;
  // Only the account that ran /setup may add or remove shared resources such as
  // the photo library and the uploaded fonts.
  isPlatformAdmin?: boolean;
};

type TeamMember = {
  id: string;
  email: string;
  displayName: string;
  role: AuthRole;
  hasLogin: boolean;
  isActive: boolean;
  createdAt: string;
};

type GuestCopy = {
  greeting: string;
  whatToEat: string;
  kitchenHours: string;
  callWaiter: string;
  helpOrBill: string;
  pausedTitle: string;
  pausedBody: string;
  search: string;
  fromKitchen: string;
  itemsLabel: string;
  noResults: string;
  yourOrder: string;
  oneItem: string;
  manyItems: string;
  allergens: string;
  poweredBy: string;
  kitchenNote: string;
  noteExample: string;
  add: string;
  checkItems: string;
  subtotal: string;
  service: string;
  included: string;
  total: string;
  payAtRestaurant: string;
  payBody: string;
  sendOrder: string;
  callTitle: string;
  callBody: string;
  reasons: string[];
  payQuestion: string;
  card: string;
  cash: string;
  notify: string;
  orderSent: string;
  orderSentBody: string;
  prepTime: string;
  continueMenu: string;
};

// Built-in wording MeniFlow ships. A restaurant may override any of these per
// language, and may add languages we do not ship — those fall back to the
// restaurant default until the owner translates them.
const GUEST_COPY: Record<string, GuestCopy> = {
  "sr-Latn": {
    greeting: "Dobro veče 👋", whatToEat: "Šta vam se jede?", kitchenHours: "Kuhinja radi do 22:30 · priprema 15–25 min",
    callWaiter: "Pozovi konobara", helpOrBill: "Pomoć ili račun", pausedTitle: "Poručivanje je trenutno pauzirano.",
    pausedBody: "Meni možete pregledati, a konobara pozvati u svakom trenutku.", search: "Pretražite meni…", fromKitchen: "Iz naše kuhinje",
    itemsLabel: "artikala", noResults: "Nema rezultata za ovu pretragu.", yourOrder: "Vaša porudžbina", oneItem: "stavka", manyItems: "stavke",
    allergens: "Alergeni dostupni", poweredBy: "Digitalni meni pokreće MeniFlow", kitchenNote: "Napomena za kuhinju", noteExample: "Npr. bez luka",
    add: "Dodaj", checkItems: "Proverite stavke pre slanja.", subtotal: "Međuzbir", service: "Usluga", included: "Uključena", total: "Ukupno",
    payAtRestaurant: "Plaćanje u restoranu", payBody: "Porudžbinu plaćate konobaru, karticom ili gotovinom.", sendOrder: "Pošalji porudžbinu",
    callTitle: "Pozovite konobara", callBody: "Recite nam kako možemo da pomognemo.", reasons: ["Potrebna mi je pomoć", "Želim da poručim", "Donesite račun", "Drugo"],
    payQuestion: "Kako želite da platite?", card: "Kartica", cash: "Gotovina", notify: "Obavesti konobara", orderSent: "Porudžbina je poslata",
    orderSentBody: "Osoblje je primilo vaš zahtev. Prijatno!", prepTime: "Procenjeno vreme pripreme", continueMenu: "Nastavi pregled menija",
  },
  en: {
    greeting: "Good evening 👋", whatToEat: "What are you in the mood for?", kitchenHours: "Kitchen open until 10:30 PM · 15–25 min preparation",
    callWaiter: "Call a waiter", helpOrBill: "Help or the bill", pausedTitle: "Ordering is currently paused.",
    pausedBody: "You can still browse the menu or call a waiter at any time.", search: "Search the menu…", fromKitchen: "From our kitchen",
    itemsLabel: "items", noResults: "No items match your search.", yourOrder: "Your order", oneItem: "item", manyItems: "items",
    allergens: "Allergen info available", poweredBy: "Digital menu powered by MeniFlow", kitchenNote: "Note for the kitchen", noteExample: "E.g. no onions",
    add: "Add", checkItems: "Review your items before sending.", subtotal: "Subtotal", service: "Service", included: "Included", total: "Total",
    payAtRestaurant: "Pay at the restaurant", payBody: "Pay your waiter by card or cash.", sendOrder: "Send order",
    callTitle: "Call a waiter", callBody: "Tell us how we can help.", reasons: ["I need help", "I would like to order", "Bring the bill", "Other"],
    payQuestion: "How would you like to pay?", card: "Card", cash: "Cash", notify: "Notify waiter", orderSent: "Order sent",
    orderSentBody: "The team has received your request. Enjoy!", prepTime: "Estimated preparation time", continueMenu: "Continue browsing",
  },
  de: {
    greeting: "Guten Abend 👋", whatToEat: "Worauf haben Sie Appetit?", kitchenHours: "Küche bis 22:30 Uhr · Zubereitung 15–25 Min.",
    callWaiter: "Service rufen", helpOrBill: "Hilfe oder Rechnung", pausedTitle: "Bestellungen sind momentan pausiert.",
    pausedBody: "Sie können die Speisekarte ansehen oder jederzeit den Service rufen.", search: "Speisekarte durchsuchen…", fromKitchen: "Aus unserer Küche",
    itemsLabel: "Gerichte", noResults: "Keine passenden Gerichte gefunden.", yourOrder: "Ihre Bestellung", oneItem: "Artikel", manyItems: "Artikel",
    allergens: "Allergeninfo verfügbar", poweredBy: "Digitale Speisekarte von MeniFlow", kitchenNote: "Hinweis an die Küche", noteExample: "Z. B. ohne Zwiebeln",
    add: "Hinzufügen", checkItems: "Bitte prüfen Sie Ihre Auswahl.", subtotal: "Zwischensumme", service: "Service", included: "Inklusive", total: "Gesamt",
    payAtRestaurant: "Zahlung im Restaurant", payBody: "Zahlen Sie beim Service mit Karte oder bar.", sendOrder: "Bestellung senden",
    callTitle: "Service rufen", callBody: "Wie können wir helfen?", reasons: ["Ich brauche Hilfe", "Ich möchte bestellen", "Die Rechnung bitte", "Sonstiges"],
    payQuestion: "Wie möchten Sie zahlen?", card: "Karte", cash: "Bar", notify: "Service benachrichtigen", orderSent: "Bestellung gesendet",
    orderSentBody: "Das Team hat Ihre Anfrage erhalten. Guten Appetit!", prepTime: "Voraussichtliche Zubereitungszeit", continueMenu: "Weiter zur Speisekarte",
  },
};

const DEFAULT_LOCALE = "sr-Latn";

// Languages a restaurant can switch on. The first three ship with a full
// translation; the rest start from the restaurant default and are translated
// by the owner in Admin > Jezici.
const LOCALES: Array<{ code: string; short: string; native: string; continueLabel: string }> = [
  { code: "sr-Latn", short: "SR", native: "Srpski", continueLabel: "Nastavite na srpskom" },
  { code: "en", short: "EN", native: "English", continueLabel: "Continue in English" },
  { code: "de", short: "DE", native: "Deutsch", continueLabel: "Auf Deutsch fortfahren" },
  { code: "ru", short: "RU", native: "Русский", continueLabel: "Продолжить на русском" },
  { code: "it", short: "IT", native: "Italiano", continueLabel: "Continua in italiano" },
  { code: "fr", short: "FR", native: "Français", continueLabel: "Continuer en français" },
  { code: "es", short: "ES", native: "Español", continueLabel: "Continuar en español" },
  { code: "hu", short: "HU", native: "Magyar", continueLabel: "Folytatás magyarul" },
  { code: "tr", short: "TR", native: "Türkçe", continueLabel: "Türkçe devam et" },
  { code: "el", short: "EL", native: "Ελληνικά", continueLabel: "Συνέχεια στα ελληνικά" },
  { code: "zh", short: "ZH", native: "中文", continueLabel: "继续使用中文" },
  { code: "ar", short: "AR", native: "العربية", continueLabel: "المتابعة بالعربية" },
];

function localeInfo(code: string) {
  return LOCALES.find((entry) => entry.code === code)
    ?? { code, short: code.slice(0, 2).toUpperCase(), native: code, continueLabel: "" };
}

// Built-in translation first, then anything the restaurant wrote itself.
function guestCopyFor(data: RestaurantState, locale: string): GuestCopy {
  const base = GUEST_COPY[locale] ?? GUEST_COPY[data.restaurant.defaultLocale] ?? GUEST_COPY[DEFAULT_LOCALE];
  const overrides = data.guestCopy?.[locale];
  if (!overrides) return base;
  const merged: GuestCopy = { ...base };
  for (const [key, value] of Object.entries(overrides)) {
    if (!value) continue;
    if (key === "reasons") {
      const list = value.split("|").map((entry) => entry.trim()).filter(Boolean);
      if (list.length) merged.reasons = list;
      continue;
    }
    if (key in merged) (merged as unknown as Record<string, string>)[key] = value;
  }
  return merged;
}

// A restaurant that speaks one language should not ask the guest to pick it.
function enabledLocalesFor(data: RestaurantState) {
  const list = (data.restaurant.settings.enabledLocales ?? []).filter(Boolean);
  return list.length ? list : [data.restaurant.defaultLocale || DEFAULT_LOCALE];
}

const FOOD_IMAGES = {
  grill:
    "https://images.unsplash.com/photo-1544025162-d76694265947?auto=format&fit=crop&w=900&q=80",
  salad:
    "https://images.unsplash.com/photo-1540189549336-e6e99c3679fe?auto=format&fit=crop&w=900&q=80",
  pasta:
    "https://images.unsplash.com/photo-1473093295043-cdd812d0e601?auto=format&fit=crop&w=900&q=80",
  dessert:
    "https://images.unsplash.com/photo-1571877227200-a0d98ea607e9?auto=format&fit=crop&w=900&q=80",
};

const bistroItems: MenuItem[] = [
  {
    id: "item-cevapi",
    name: "Ćevapi u somunu",
    description: "Domaći juneći ćevapi, somun, luk i kajmak.",
    category: "Glavna jela",
    portion: "10 kom · 300 g",
    price: 890,
    available: true,
    featured: true,
    image: FOOD_IMAGES.grill,
    tags: ["Preporuka kuće"],
    allergens: ["Gluten", "Mleko"],
  },
  {
    id: "item-pasta",
    name: "Pasta sa tartufima",
    description: "Sveža pasta, krem od parmezana i crni tartuf.",
    category: "Glavna jela",
    portion: "320 g",
    price: 1190,
    available: true,
    featured: true,
    image: FOOD_IMAGES.pasta,
    tags: ["Vegetarijansko"],
    allergens: ["Gluten", "Mleko", "Jaja"],
  },
  {
    id: "item-salata",
    name: "Dunavska salata",
    description: "Zeleniš, pečena paprika, feta, orasi i preliv od nara.",
    category: "Salate",
    portion: "280 g",
    price: 690,
    available: true,
    featured: true,
    image: FOOD_IMAGES.salad,
    tags: ["Vegetarijansko", "Bez glutena"],
    allergens: ["Mleko", "Orašasti plodovi"],
  },
  {
    id: "item-tiramisu",
    name: "Tiramisu",
    description: "Maskarpone, espresso i kakao — naš kućni klasik.",
    category: "Dezerti",
    portion: "160 g",
    price: 490,
    available: true,
    image: FOOD_IMAGES.dessert,
    tags: ["Omiljeno"],
    allergens: ["Mleko", "Jaja", "Gluten"],
  },
  {
    id: "item-limunada",
    name: "Domaća limunada",
    description: "Limun, nana i malo meda. Bez veštačkih dodataka.",
    category: "Bezalkoholna pića",
    portion: "0,4 l",
    price: 360,
    available: true,
    tags: ["Domaće"],
  },
  {
    id: "item-voda",
    name: "Mineralna voda",
    description: "Rosa gazirana ili negazirana.",
    category: "Bezalkoholna pića",
    portion: "0,33 l",
    price: 220,
    available: true,
    tags: [],
  },
  {
    id: "item-rakija",
    name: "Šljivovica Reserve",
    description: "Odležala domaća rakija od požegače.",
    category: "Alkoholna pića",
    portion: "0,03 l",
    price: 390,
    available: false,
    tags: ["18+"],
  },
];

function makeTables(count = 14): DiningTable[] {
  return Array.from({ length: count }, (_, index) => ({
    id: `table-${index + 1}`,
    number: index + 1,
    label: `Sto ${index + 1}`,
    zone: index < 8 ? "Sala" : index < 12 ? "Terasa" : "Bašta",
    seats: 4,
    token: `bd-${(index + 1).toString().padStart(2, "0")}-7f3a`,
    active: true,
  }));
}

const initialTickets: Ticket[] = [
  {
    id: "ticket-1",
    kind: "call",
    status: "open",
    tableNumber: 7,
    zone: "Sala",
    createdAt: new Date(Date.now() - 11 * 60_000).toISOString(),
    callReason: "Donesite račun",
    paymentMethod: "Kartica",
  },
  {
    id: "ticket-2",
    kind: "order",
    status: "open",
    tableNumber: 12,
    zone: "Terasa",
    createdAt: new Date(Date.now() - 5 * 60_000).toISOString(),
    total: 2040,
    items: [
      { name: "Ćevapi u somunu", quantity: 2, price: 890, note: "Bez luka" },
      { name: "Mineralna voda", quantity: 1, price: 220, portion: "0,33 l" },
      { name: "Domaća limunada", quantity: 1, price: 360, portion: "0,4 l" },
    ],
  },
  {
    id: "ticket-3",
    kind: "call",
    status: "open",
    tableNumber: 4,
    zone: "Sala",
    createdAt: new Date(Date.now() - 2 * 60_000).toISOString(),
    callReason: "Potrebna mi je pomoć",
  },
  {
    id: "ticket-4",
    kind: "order",
    status: "open",
    tableNumber: 9,
    zone: "Terasa",
    createdAt: new Date(Date.now() - 45_000).toISOString(),
    total: 1050,
    items: [
      { name: "Dunavska salata", quantity: 1, price: 690 },
      { name: "Domaća limunada", quantity: 1, price: 360 },
    ],
  },
  {
    id: "ticket-history",
    kind: "order",
    status: "done",
    tableNumber: 2,
    zone: "Sala",
    createdAt: new Date(Date.now() - 42 * 60_000).toISOString(),
    resolvedAt: new Date(Date.now() - 31 * 60_000).toISOString(),
    total: 1380,
    items: [{ name: "Dunavska salata", quantity: 2, price: 690 }],
  },
];

const bistroFallback: RestaurantState = {
  restaurant: {
    id: "restaurant-bistro-dunav",
    slug: "bistro-dunav",
    name: "Bistro Dunav",
    location: "Dorćol · Beograd",
    currency: "RSD",
    plan: "active" as const,
    hasFullAccess: true,
    trialDaysLeft: null,
    defaultLocale: "sr-Latn",
    brand: {
      primary: "#1F5A4A",
      accent: "#D77A5B",
      background: "#FFF9F3",
      text: "#24211F",
      radius: 22,
      font: "modern",
    },
    settings: {
      confirmBeforeResolve: true,
      soundEnabled: true,
      soundOrder: "zvono" as const,
      soundCall: "dvoton" as const,
      soundVolume: 70,
      soundOrderAssetId: null,
      soundCallAssetId: null,
      showAllChip: true,
      allChipLabel: "Sve",
      showFeaturedChip: true,
      featuredChipLabel: "Preporučujemo",
      orderingEnabled: true,
      waiterCallEnabled: true,
      callConfirmEnabled: true,
      orderingPaused: false,
      showTotal: true,
    },
  },
  categories: [
    "Sve",
    "Preporučujemo",
    "Glavna jela",
    "Salate",
    "Dezerti",
    "Bezalkoholna pića",
    "Alkoholna pića",
  ],
  items: bistroItems,
  tables: makeTables(),
  zones: [],
  guestCopy: {},
  team: [],
  visits: [],
  tickets: initialTickets,
};

const casaFallback: RestaurantState = {
  restaurant: {
    id: "restaurant-casa-verde",
    slug: "casa-verde",
    name: "Casa Verde",
    location: "Vračar · Beograd",
    currency: "RSD",
    plan: "active" as const,
    hasFullAccess: true,
    trialDaysLeft: null,
    defaultLocale: "sr-Latn",
    brand: {
      primary: "#305B38",
      accent: "#E5A93F",
      background: "#FBF7EA",
      text: "#27312A",
      radius: 14,
      font: "classic",
    },
    settings: {
      confirmBeforeResolve: false,
      soundEnabled: true,
      soundOrder: "zvono" as const,
      soundCall: "dvoton" as const,
      soundVolume: 70,
      soundOrderAssetId: null,
      soundCallAssetId: null,
      showAllChip: true,
      allChipLabel: "Sve",
      showFeaturedChip: true,
      featuredChipLabel: "Preporučujemo",
      orderingEnabled: true,
      waiterCallEnabled: true,
      callConfirmEnabled: true,
      orderingPaused: false,
      showTotal: true,
    },
  },
  categories: ["Sve", "Preporučujemo", "Paste", "Salate", "Dezerti", "Pića"],
  items: [
    { ...bistroItems[1], id: "casa-pasta", category: "Paste", price: 1290 },
    { ...bistroItems[2], id: "casa-salata", name: "Verde salata", price: 740 },
    { ...bistroItems[3], id: "casa-tiramisu", price: 520 },
  ],
  tables: makeTables(10).map((table) => ({ ...table, token: `cv-${table.number}-4b2e` })),
  zones: [],
  guestCopy: {},
  team: [],
  visits: [],
  tickets: initialTickets.slice(2),
};

function fallbackFor(slug: string) {
  return structuredClone(slug === "casa-verde" ? casaFallback : bistroFallback);
}

function money(value: number, currency = "RSD") {
  return `${new Intl.NumberFormat("sr-RS").format(value)} ${currency}`;
}

function portionValues(label: string) {
  const normalized = label.trim().toLowerCase().replace(",", ".");
  const amount = Number.parseFloat(normalized);
  if (/\bml\b/.test(normalized)) return { portionAmount: Math.round(amount), portionUnit: "ml" as const };
  if (/\bl\b/.test(normalized)) return { portionAmount: Math.round(amount * 1000), portionUnit: "ml" as const };
  if (/\bkg\b/.test(normalized)) return { portionAmount: Math.round(amount * 1000), portionUnit: "g" as const };
  if (/\bg\b/.test(normalized)) return { portionAmount: Math.round(amount), portionUnit: "g" as const };
  if (/kom|piece/.test(normalized)) return { portionAmount: Math.max(1, Math.round(amount || 1)), portionUnit: "piece" as const };
  return { portionAmount: Number.isFinite(amount) && amount >= 1 ? Math.round(amount) : undefined, portionUnit: "portion" as const };
}

// SQLite's CURRENT_TIMESTAMP returns UTC as "YYYY-MM-DD HH:MM:SS" with no
// timezone marker. Browsers parse that space-separated form as LOCAL time,
// silently shifting every server timestamp by the viewer's UTC offset (e.g.
// showing a brand-new order as "pre 120 min" in Belgrade's UTC+2 summer
// time). Treat any timestamp without an explicit offset as UTC.
function parseServerDate(value: string) {
  return new Date(/[Zz]|[+-]\d\d:?\d\d$/.test(value) ? value : `${value.replace(" ", "T")}Z`);
}

function timeAgo(value: string) {
  const minutes = Math.max(0, Math.floor((Date.now() - parseServerDate(value).getTime()) / 60_000));
  if (minutes < 1) return "upravo sada";
  if (minutes === 1) return "pre 1 min";
  return `pre ${minutes} min`;
}

// Every category the restaurant has, read from the database rather than from
// the guest chip row: chips can be hidden or renamed, but an item still has to
// be filed under a real category.
// What a chip in the guest row means. The two built-in chips can be renamed,
// so their label is never compared directly outside this function.
function chipKind(data: RestaurantState, name: string): "all" | "featured" | "category" {
  const settings = data.restaurant.settings;
  if (name === settings.allChipLabel || name === "Sve") return "all";
  if (name === settings.featuredChipLabel || name === "Preporučujemo") return "featured";
  return "category";
}

// The chip a screen should start on: the featured one when the restaurant kept
// it, otherwise the first chip it does show.
function defaultChip(data: RestaurantState) {
  const settings = data.restaurant.settings;
  if (settings.showFeaturedChip && data.categories.includes(settings.featuredChipLabel)) {
    return settings.featuredChipLabel;
  }
  return data.categories[0] ?? "";
}

function chipMatches(data: RestaurantState, chip: string, item: MenuItem) {
  const kind = chipKind(data, chip);
  if (kind === "all") return true;
  if (kind === "featured") return item.featured;
  return item.category === chip;
}

function menuCategoryNames(data: RestaurantState) {
  const fromDatabase = (data.categoryDetails ?? []).map((entry) => entry.name);
  if (fromDatabase.length) return fromDatabase;
  // The built-in demo data has no category rows, only the chip row.
  return data.categories.filter(
    (entry) => entry !== "Sve" && !entry.toLowerCase().includes("preporu"),
  );
}

// Zones a table can be put in: the managed list in its own order, then anything
// a table still carries that the list does not know about.
function knownZoneNames(data: RestaurantState) {
  const managed = [...data.zones]
    .sort((left, right) => left.sortOrder - right.sortOrder || left.name.localeCompare(right.name, "sr"))
    .map((zone) => zone.name);
  const extra = [...new Set(data.tables.map((table) => table.zone))].filter(
    (name) => name && !managed.includes(name),
  );
  return [...managed, ...extra.sort((left, right) => left.localeCompare(right, "sr"))];
}

function initials(value: string) {
  return value
    .split(" ")
    .map((part) => part[0])
    .join("")
    .slice(0, 2)
    .toUpperCase();
}

function BrandMark({ restaurant, className = "" }: { restaurant: RestaurantState["restaurant"]; className?: string }) {
  return <span className={className}>{restaurant.logoUrl ? <img src={restaurant.logoUrl} alt={`Logo ${restaurant.name}`} /> : initials(restaurant.name)}</span>;
}

// The stored value is "sans", "serif", or "font:Family" for something uploaded
// into storage/fonts. Everything the page needs to render that font comes with
// the snapshot, so there is no second request and no flash of the fallback.
function brandFont(
  branding: Record<string, unknown>,
  fallback: Brand,
): Pick<Brand, "font" | "fontFamily" | "fontFaces"> {
  const stored = String(branding.fontFamily ?? "");
  if (stored.startsWith("font:")) {
    const family = stored.slice("font:".length);
    const faces = Array.isArray(branding.fontFaces)
      ? (branding.fontFaces as Record<string, unknown>[]).map((face) => ({
          url: String(face.url),
          weight: Number(face.weight ?? 400),
          style: face.style === "italic" ? ("italic" as const) : ("normal" as const),
        }))
      : [];
    // A family whose files were deleted from the server falls back rather than
    // leaving the page with a font name that resolves to nothing.
    if (family && faces.length) return { font: "custom", fontFamily: family, fontFaces: faces };
  }
  if (stored === "serif") return { font: "classic" };
  return { font: stored === "sans" ? "modern" : fallback.font };
}

// Declared once per page. Only the faces of the chosen family are emitted.
function BrandFontFaces({ brand }: { brand: Brand }) {
  if (brand.font !== "custom" || !brand.fontFamily || !brand.fontFaces?.length) return null;
  const css = brand.fontFaces
    .map((face) => `@font-face{font-family:"${brand.fontFamily}";src:url("${face.url}");font-weight:${face.weight};font-style:${face.style};font-display:swap;}`)
    .join("");
  return <style>{css}</style>;
}

function normalizeWaiter(value: unknown): TableWaiter | null {
  if (!value || typeof value !== "object") return null;
  const entry = value as Record<string, unknown>;
  if (!entry.email) return null;
  const email = String(entry.email);
  return { email, name: String(entry.name || email) };
}

function zoneFromLabel(value: unknown) {
  const raw = String(value ?? "Sala").split("·").at(-1)?.trim() || "Sala";
  return raw.charAt(0).toUpperCase() + raw.slice(1);
}

function normalizeApiState(payload: Partial<RestaurantState> & Record<string, unknown>, fallback: RestaurantState): RestaurantState {
  const source = payload as Record<string, any>;
  const restaurantSource = source.restaurant ?? {};
  const brandingSource = source.branding ?? restaurantSource.brand ?? {};
  const settingsSource = source.settings ?? restaurantSource.settings ?? {};
  const categoryRows = Array.isArray(source.categories) ? source.categories : [];
  const categoryNameById = new Map<string, string>(
    categoryRows
      .filter((entry: any) => entry && typeof entry === "object")
      .map((entry: any) => [String(entry.id), String(entry.name)]),
  );
  // The guest chip row: the two built-in chips only appear if the restaurant
  // kept them, and they carry whatever label it gave them.
  const categories = categoryRows.length && typeof categoryRows[0] === "object"
    ? [
        ...(settingsSource.showAllChip === false ? [] : [String(settingsSource.allChipLabel ?? "Sve")]),
        ...(settingsSource.showFeaturedChip === false ? [] : [String(settingsSource.featuredChipLabel ?? "Preporučujemo")]),
        ...categoryRows.map((entry: any) => String(entry.name)),
      ]
    : source.categories;
  const categoryDetails = categoryRows.length && typeof categoryRows[0] === "object"
    ? categoryRows.map((entry: any) => ({
        id: String(entry.id),
        name: String(entry.name),
        active: Boolean(entry.isActive ?? true),
        sortOrder: Number(entry.sortOrder ?? 0),
      } satisfies MenuCategory))
    : fallback.categoryDetails;
  const apiItems = Array.isArray(source.items) && source.items.some((entry: any) => Array.isArray(entry?.variants))
      ? source.items.map((entry: any) => {
        const variant = entry.variants?.find((candidate: any) => candidate.isAvailable) ?? entry.variants?.[0] ?? {};
        const normalizedVariants: MenuVariant[] = (entry.variants ?? []).map((candidate: any) => ({
          id: candidate.id ? String(candidate.id) : undefined,
          label: String(candidate.label ?? "Porcija"),
          price: Number(candidate.priceMinor ?? 0) / 100,
          portionAmount: candidate.portionAmount == null ? undefined : Number(candidate.portionAmount),
          portionUnit: candidate.portionUnit ?? undefined,
          available: Boolean(candidate.isAvailable ?? true),
        }));
        const fallbackMatch = fallback.items.find((candidate) => candidate.name === entry.name);
        const portionAmount = variant.portionAmount ?? "";
        const portionUnit = variant.portionUnit === "piece" ? "kom" : variant.portionUnit === "portion" ? "porcija" : variant.portionUnit ?? "";
        return {
          id: String(entry.id),
          categoryId: String(entry.categoryId),
          variantId: variant.id ? String(variant.id) : undefined,
          variants: normalizedVariants,
          imageAssetId: entry.imageAssetId ? String(entry.imageAssetId) : undefined,
          persisted: true,
          translations: entry.translations && typeof entry.translations === "object" ? entry.translations : undefined,
          name: String(entry.name),
          description: String(entry.description ?? ""),
          category: categoryNameById.get(String(entry.categoryId)) ?? "Ostalo",
          portion: [variant.label, portionAmount && portionUnit ? `${portionAmount} ${portionUnit}` : ""].filter(Boolean).join(" · "),
          price: Number(variant.priceMinor ?? 0) / 100,
          available: Boolean(entry.isAvailable && (variant.isAvailable ?? true)),
          featured: Boolean(entry.isFeatured),
          image: entry.imageAssetId ? `/api/demo/media/${encodeURIComponent(String(entry.imageAssetId))}` : fallbackMatch?.image,
          tags: Array.isArray(entry.dietaryTags) ? entry.dietaryTags : [],
          allergens: fallbackMatch?.allergens ?? [],
        } satisfies MenuItem;
      })
    : source.items;
  const apiTables = Array.isArray(source.tables) && source.tables.some((entry: any) => "tableNumber" in entry)
    ? source.tables.map((entry: any) => ({
        id: String(entry.id),
        number: Number(entry.tableNumber),
        label: String(entry.label ?? `Sto ${entry.tableNumber}`),
        zone: String(entry.zone ?? zoneFromLabel(entry.label)),
        seats: Number(entry.seats ?? 4),
        token: String(entry.qrToken),
        active: Boolean(entry.isActive),
        assignedEmail: entry.assignedEmail ? String(entry.assignedEmail) : null,
        claimedEmail: entry.claimedEmail ? String(entry.claimedEmail) : null,
        waiter: normalizeWaiter(entry.waiter),
      } satisfies DiningTable))
    : source.tables;
  const apiZones = Array.isArray(source.zones)
    ? source.zones.map((entry: any) => ({
        id: String(entry.id),
        name: String(entry.name),
        sortOrder: Number(entry.sortOrder ?? 0),
      } satisfies RestaurantZone))
    : fallback.zones;
  const apiTeam = Array.isArray(source.team)
    ? source.team.map((entry: any) => ({
        email: String(entry.email),
        displayName: String(entry.displayName ?? ""),
        role: String(entry.role ?? "staff"),
      } satisfies TeamMemberSummary))
    : fallback.team;
  const ticketRows = source.tickets && !Array.isArray(source.tickets)
    ? [...(source.tickets.open ?? []), ...(source.tickets.history ?? [])]
    : source.tickets;
  const apiTickets = Array.isArray(ticketRows) && ticketRows.some((entry: any) => entry.type)
    ? ticketRows.map((entry: any) => ({
        id: String(entry.id),
        kind: entry.type === "waiter_call" ? "call" : "order",
        status: entry.status === "resolved" ? "done" : "open",
        tableNumber: Number(entry.tableNumber),
        tableId: entry.tableId ? String(entry.tableId) : undefined,
        // The server now sends the table's real zone; the label parse is only
        // a fallback for older payloads.
        zone: entry.zone ? String(entry.zone) : zoneFromLabel(entry.tableLabel),
        waiter: normalizeWaiter(entry.waiter),
        createdAt: String(entry.createdAt),
        resolvedAt: entry.resolvedAt ? String(entry.resolvedAt) : undefined,
        total: Number(entry.totalMinor ?? 0) / 100,
        callReason: entry.type === "waiter_call" ? String(entry.note ?? "Potrebna mi je pomoć") : undefined,
        note: entry.type === "order" && entry.note ? String(entry.note) : undefined,
        items: Array.isArray(entry.items) ? entry.items.map((line: any) => ({
          id: String(line.id), name: String(line.itemName), quantity: Number(line.quantity),
          portion: String(line.variantLabel ?? ""), price: Number(line.unitPriceMinor ?? 0) / 100,
        })) : [],
        source: entry.source === "staff" ? "staff" : "guest",
        createdBy: entry.createdBy ? String(entry.createdBy) : undefined,
      } satisfies Ticket))
    : ticketRows;
  const visitRows = source.visits && !Array.isArray(source.visits)
    ? [...(source.visits.open ?? []), ...(source.visits.history ?? [])]
    : source.visits;
  const apiVisits = Array.isArray(visitRows)
    ? visitRows.map((entry: any) => ({
        id: String(entry.id),
        tableId: String(entry.tableId),
        tableNumber: Number(entry.tableNumber),
        tableLabel: String(entry.tableLabel ?? `Sto ${entry.tableNumber}`),
        zone: String(entry.zone ?? zoneFromLabel(entry.tableLabel)),
        status: entry.status === "closed" ? "closed" : "open",
        openedAt: String(entry.openedAt),
        closedAt: entry.closedAt ? String(entry.closedAt) : undefined,
        closedBy: entry.closedBy ? String(entry.closedBy) : undefined,
        orderCount: Number(entry.orderCount ?? 0),
        total: Number(entry.totalMinor ?? 0) / 100,
        openTicketCount: Number(entry.openTicketCount ?? 0),
        lastOrderAt: entry.lastOrderAt ? String(entry.lastOrderAt) : undefined,
        waiter: normalizeWaiter(entry.waiter),
        items: Array.isArray(entry.items) ? entry.items.map((line: any) => ({
          name: String(line.name),
          variantLabel: String(line.variantLabel ?? ""),
          quantity: Number(line.quantity ?? 0),
          price: Number(line.unitPriceMinor ?? 0) / 100,
          lineTotal: Number(line.lineTotalMinor ?? 0) / 100,
          source: line.source === "staff" ? "staff" : "guest",
        } satisfies VisitItem)) : [],
      } satisfies TableVisit))
    : fallback.visits;

  return {
    restaurant: source.restaurant
      ? {
          ...fallback.restaurant,
          id: String(restaurantSource.id ?? fallback.restaurant.id),
          slug: String(restaurantSource.slug ?? fallback.restaurant.slug),
          name: String(brandingSource.displayName ?? restaurantSource.name ?? fallback.restaurant.name),
          currency: String(restaurantSource.currency ?? fallback.restaurant.currency),
          plan: restaurantSource.plan === "trial" ? "trial" : restaurantSource.plan === "basic" ? "basic" : "active",
          hasFullAccess: restaurantSource.hasFullAccess !== false,
          locked: restaurantSource.locked === true,
          lockNote: restaurantSource.lockNote == null ? null : String(restaurantSource.lockNote),
          planTone: restaurantSource.planTone as RestaurantState["restaurant"]["planTone"],
          trialDaysLeft: restaurantSource.trialDaysLeft == null ? null : Number(restaurantSource.trialDaysLeft),
          planExpiresAt: restaurantSource.planExpiresAt == null ? null : String(restaurantSource.planExpiresAt),
          planDaysLeft: restaurantSource.planDaysLeft == null ? null : Number(restaurantSource.planDaysLeft),
          planUnlimited: restaurantSource.planUnlimited === true,
          graceDaysLeft: restaurantSource.graceDaysLeft == null ? null : Number(restaurantSource.graceDaysLeft),
          ownerEmail: restaurantSource.ownerEmail == null ? null : String(restaurantSource.ownerEmail),
          contactPhone: String(restaurantSource.contactPhone ?? ""),
          address: String(restaurantSource.address ?? ""),
          city: String(restaurantSource.city ?? ""),
          postalCode: String(restaurantSource.postalCode ?? ""),
          location: [String(restaurantSource.city ?? ""), String(restaurantSource.address ?? "")]
            .filter(Boolean)
            .join(" · ") || fallback.restaurant.location,
          defaultLocale: String(restaurantSource.defaultLocale ?? fallback.restaurant.defaultLocale),
          logoAssetId: brandingSource.logoAssetId ? String(brandingSource.logoAssetId) : undefined,
          logoUrl: brandingSource.logoAssetId
            ? `/api/demo/media/${encodeURIComponent(String(brandingSource.logoAssetId))}`
            : undefined,
          brand: {
            ...fallback.restaurant.brand,
            primary: brandingSource.primaryColor ?? brandingSource.primary ?? fallback.restaurant.brand.primary,
            accent: brandingSource.accentColor ?? brandingSource.accent ?? fallback.restaurant.brand.accent,
            background: brandingSource.backgroundColor ?? brandingSource.background ?? fallback.restaurant.brand.background,
            text: brandingSource.textColor ?? brandingSource.text ?? fallback.restaurant.brand.text,
            ...brandFont(brandingSource, fallback.restaurant.brand),
          },
          settings: {
            ...fallback.restaurant.settings,
            confirmBeforeResolve: settingsSource.confirmBeforeResolve ?? fallback.restaurant.settings.confirmBeforeResolve,
            orderingEnabled: settingsSource.acceptOrders ?? settingsSource.orderingEnabled ?? fallback.restaurant.settings.orderingEnabled,
            orderingPaused: settingsSource.acceptOrders === false
              ? true
              : settingsSource.orderingPaused ?? fallback.restaurant.settings.orderingPaused,
            waiterCallEnabled: settingsSource.waiterCallEnabled ?? fallback.restaurant.settings.waiterCallEnabled,
            callConfirmEnabled: settingsSource.callConfirmEnabled ?? fallback.restaurant.settings.callConfirmEnabled,
            soundEnabled: settingsSource.soundEnabled ?? fallback.restaurant.settings.soundEnabled,
            soundOrder: (settingsSource.soundOrder as ToneName) ?? fallback.restaurant.settings.soundOrder,
            soundCall: (settingsSource.soundCall as ToneName) ?? fallback.restaurant.settings.soundCall,
            soundVolume: settingsSource.soundVolume == null
              ? fallback.restaurant.settings.soundVolume
              : Number(settingsSource.soundVolume),
            soundOrderAssetId: settingsSource.soundOrderAssetId
              ? String(settingsSource.soundOrderAssetId)
              : null,
            soundCallAssetId: settingsSource.soundCallAssetId
              ? String(settingsSource.soundCallAssetId)
              : null,
            showTotal: settingsSource.showTotal ?? fallback.restaurant.settings.showTotal,
            showAllChip: settingsSource.showAllChip ?? fallback.restaurant.settings.showAllChip,
            allChipLabel: String(settingsSource.allChipLabel ?? fallback.restaurant.settings.allChipLabel),
            showFeaturedChip: settingsSource.showFeaturedChip ?? fallback.restaurant.settings.showFeaturedChip,
            featuredChipLabel: String(settingsSource.featuredChipLabel ?? fallback.restaurant.settings.featuredChipLabel),
            posWebhookUrl: settingsSource.posWebhookUrl ?? fallback.restaurant.settings.posWebhookUrl ?? "",
            posWebhookSecret: settingsSource.posWebhookSecret ?? fallback.restaurant.settings.posWebhookSecret ?? "",
            enabledLocales: Array.isArray(settingsSource.enabledLocales) && settingsSource.enabledLocales.length
              ? settingsSource.enabledLocales.map((entry: unknown) => String(entry))
              : undefined,
          },
        }
      : fallback.restaurant,
    categories: Array.isArray(categories) ? categories : fallback.categories,
    categoryDetails,
    items: Array.isArray(apiItems) ? apiItems : fallback.items,
    tables: Array.isArray(apiTables) ? apiTables : fallback.tables,
    zones: apiZones,
    guestCopy: (source.guestCopy && typeof source.guestCopy === "object") ? source.guestCopy as Record<string, Record<string, string>> : fallback.guestCopy,
    team: apiTeam,
    visits: apiVisits,
    tickets: Array.isArray(apiTickets) ? apiTickets : fallback.tickets,
    posApiKeys: Array.isArray(source.posApiKeys)
      ? source.posApiKeys.map((entry: any) => ({
          id: String(entry.id),
          label: String(entry.label),
          createdAt: String(entry.createdAt),
          lastUsedAt: entry.lastUsedAt ? String(entry.lastUsedAt) : null,
          isActive: Boolean(entry.isActive),
        } satisfies PosApiKey))
      : fallback.posApiKeys,
  };
}

export default function RestaurantDemo({ initialSurface = "admin", initialSlug = "bistro-dunav", guestToken }: { initialSurface?: Surface; initialSlug?: string; guestToken?: string }) {
  const [surface, setSurface] = useState<Surface>(initialSurface);
  const [section, setSection] = useState<AdminSection>("overview");
  const [slug, setSlug] = useState(initialSlug);
  const [data, setData] = useState<RestaurantState>(() => fallbackFor(initialSlug));
  const [loading, setLoading] = useState(false);
  const [toast, setToast] = useState<string | null>(null);
  const [restaurants, setRestaurants] = useState<AuthRestaurant[]>([]);
  const [account, setAccount] = useState<AuthUser | null>(null);
  const [loadError, setLoadError] = useState<string | null>(null);
  const [stateReady, setStateReady] = useState(initialSurface === "login");
  const [authReady, setAuthReady] = useState(
    initialSurface === "guest" || initialSurface === "login",
  );

  useEffect(() => {
    if (initialSurface === "guest" || initialSurface === "login") return;
    let active = true;
    fetch("/api/auth/session", { cache: "no-store", credentials: "same-origin" })
      .then(async (response) => {
        if (response.status === 401) {
          const next = surfacePath(initialSurface);
          window.location.assign(`/login?next=${encodeURIComponent(next)}`);
          return null;
        }
        if (!response.ok) throw new Error("session_unavailable");
        return response.json();
      })
      .then((payload) => {
        if (!active || !payload?.session?.restaurants) return;
        const memberships = payload.session.restaurants as AuthRestaurant[];
        // Only the memberships whose role may open this screen. Somebody with
        // no such membership is sent to the screen their role does allow.
        const permitted = SURFACE_ROLES[initialSurface as keyof typeof SURFACE_ROLES];
        const allowed = permitted
          ? memberships.filter((entry) => permitted.includes(entry.role))
          : memberships;
        if (permitted && allowed.length === 0) {
          window.location.assign(homeFor(memberships.map((entry) => entry.role)));
          return;
        }
        setAccount(payload.session.user as AuthUser);
        setRestaurants(allowed);
        setAuthReady(true);
        if (allowed.length && !allowed.some((entry) => entry.slug === slug)) {
          setSlug(allowed[0].slug);
        }
      })
      .catch(() => {
        if (active) {
          setLoadError("Prijava trenutno ne može da se proveri. Pokušajte ponovo.");
          setAuthReady(true);
        }
      });
    return () => { active = false; };
  }, [initialSurface, slug]);

  useEffect(() => {
    if (initialSurface === "login") return;
    let active = true;
    const fallback = fallbackFor(slug);
    setData(fallback);
    setStateReady(false);
    setLoading(true);
    setLoadError(null);
    const endpoint = initialSurface === "guest"
      ? `/api/demo/public?slug=${encodeURIComponent(slug)}&token=${encodeURIComponent(guestToken ?? "")}`
      : `/api/demo/state?slug=${encodeURIComponent(slug)}`;
    fetch(endpoint, { cache: "no-store", credentials: "same-origin" })
      .then(async (response) => {
        if (response.status === 401 && initialSurface !== "guest") {
          const next = surfacePath(initialSurface);
          window.location.assign(`/login?next=${encodeURIComponent(next)}`);
          throw new Error("auth_required");
        }
        if (!response.ok) throw new Error(response.status === 404 ? "not_found" : "load_failed");
        return response.json();
      })
      .then((payload) => {
        if (active) {
          setData(normalizeApiState(payload.state ?? payload, fallback));
          setStateReady(true);
        }
      })
      .catch((error: Error) => {
        if (active) {
          setLoadError(
            initialSurface === "guest" && error.message === "not_found"
              ? "Ovaj QR kod nije važeći ili je sto trenutno isključen."
              : "Podaci trenutno nisu dostupni. Proverite vezu i pokušajte ponovo.",
          );
        }
      })
      .finally(() => active && setLoading(false));
    return () => {
      active = false;
    };
  }, [slug, initialSurface, guestToken]);

  useEffect(() => {
    if (initialSurface !== "staff" && initialSurface !== "waiter" && initialSurface !== "bills") {
      return;
    }
    const timer = window.setInterval(() => {
      fetch(`/api/demo/state?slug=${encodeURIComponent(slug)}`, { cache: "no-store", credentials: "same-origin" })
        .then((response) => response.ok ? response.json() : null)
        .then((payload) => {
          if (payload?.state) setData((current) => normalizeApiState(payload.state, current));
        })
        .catch(() => undefined);
    }, 4000);
    return () => window.clearInterval(timer);
  }, [initialSurface, slug]);

  useEffect(() => {
    if (!toast) return;
    const timer = window.setTimeout(() => setToast(null), 4200);
    return () => window.clearTimeout(timer);
  }, [toast]);

  async function postAction(action: string, payload: Record<string, unknown>) {
    try {
      let actionPayload: Record<string, unknown> = payload;
      if (action === "toggleItem") {
        actionPayload = { itemId: payload.itemId, isAvailable: payload.available };
      } else if (action === "addMenuItem") {
        const item = payload.item as MenuItem;
        const variants = item.variants?.length
          ? item.variants
          : [{ id: item.variantId, label: item.portion, price: item.price }];
        actionPayload = {
          item: {
            itemId: item.persisted ? item.id : undefined,
            categoryId: item.categoryId ?? data.items.find((entry) => entry.category === item.category)?.categoryId,
            name: item.name,
            description: item.description,
            imageAssetId: item.imageAssetId,
            isAvailable: item.available,
            isFeatured: item.featured,
            dietaryTags: item.tags,
            variants: variants.map((variant) => ({
              label: variant.label,
              ...portionValues(variant.label),
              priceMinor: Math.round(variant.price * 100),
            })),
          },
        };
      } else if (action === "updateBranding") {
        const brand = payload.brand as Brand;
        actionPayload = { branding: { displayName: payload.displayName, logoAssetId: payload.logoAssetId, primaryColor: brand.primary, accentColor: brand.accent, backgroundColor: brand.background, textColor: brand.text, fontFamily: brand.font === "custom" && brand.fontFamily ? `font:${brand.fontFamily}` : brand.font === "classic" ? "serif" : "sans" } };
      } else if (action === "updateSettings") {
        // Only the fields the caller actually set are sent, so a partial save
        // (chips, languages) cannot clobber unrelated settings server-side.
        const settings = payload.settings as Partial<Settings>;
        const mapped: Record<string, unknown> = {};
        const pass = (key: string, value: unknown) => { if (value !== undefined) mapped[key] = value; };
        pass("confirmBeforeResolve", settings.confirmBeforeResolve);
        if (settings.orderingEnabled !== undefined || settings.orderingPaused !== undefined) {
          mapped.acceptOrders = Boolean(settings.orderingEnabled) && !settings.orderingPaused;
        }
        pass("waiterCallEnabled", settings.waiterCallEnabled);
        pass("callConfirmEnabled", settings.callConfirmEnabled);
        pass("showTotal", settings.showTotal);
        pass("soundEnabled", settings.soundEnabled);
        pass("soundOrder", settings.soundOrder);
        pass("soundCall", settings.soundCall);
        pass("soundVolume", settings.soundVolume);
        if (settings.soundOrderAssetId !== undefined) mapped.soundOrderAssetId = settings.soundOrderAssetId ?? "";
        if (settings.soundCallAssetId !== undefined) mapped.soundCallAssetId = settings.soundCallAssetId ?? "";
        pass("showAllChip", settings.showAllChip);
        pass("showFeaturedChip", settings.showFeaturedChip);
        if (settings.allChipLabel?.trim()) mapped.allChipLabel = settings.allChipLabel.trim();
        if (settings.featuredChipLabel?.trim()) mapped.featuredChipLabel = settings.featuredChipLabel.trim();
        pass("enabledLocales", settings.enabledLocales);
        actionPayload = { settings: mapped };
      } else if (action === "createTable") {
        const table = payload.table as DiningTable;
        actionPayload = { table: { tableNumber: table.number, label: table.label, zone: table.zone, seats: table.seats } };
      } else if (action === "updateTable") {
        const table = payload.table as DiningTable;
        actionPayload = { table: { tableId: table.id, tableNumber: table.number, label: table.label, zone: table.zone, seats: table.seats, isActive: table.active } };
      } else if (action === "createOrder") {
        const items = payload.items as Array<{ itemId: string; variantId?: string; quantity: number; note?: string }>;
        actionPayload = {
          qrToken: payload.tableToken,
          clientRequestId: typeof payload.clientRequestId === "string"
            ? payload.clientRequestId
            : `web-${Date.now()}-${Math.random().toString(16).slice(2)}`,
          note: items
            .filter((item) => item.note)
            .map((item) => `${data.items.find((entry) => entry.id === item.itemId)?.name ?? "Stavka"}: ${item.note}`)
            .join(" · ")
            .slice(0, 300),
          items: items.map((item) => ({ itemId: item.itemId, variantId: item.variantId ?? data.items.find((entry) => entry.id === item.itemId)?.variantId, quantity: item.quantity })),
        };
      } else if (action === "createWaiterCall") {
        actionPayload = { qrToken: payload.tableToken, note: [payload.reason, payload.paymentMethod].filter(Boolean).join(" · ") };
      }
      const response = await fetch("/api/demo/action", {
        method: "POST",
        headers: { "content-type": "application/json" },
        credentials: "same-origin",
        body: JSON.stringify({ slug, action, ...actionPayload }),
      });
      if (response.status === 401) {
        window.location.assign(`/login?next=${encodeURIComponent(window.location.pathname)}`);
        return false;
      }
      if (!response.ok) {
        const failure = await response.json().catch(() => null);
        setToast(failure?.error?.message ?? "Izmena nije sačuvana. Pokušajte ponovo.");
        return false;
      }
      const result = await response.json();
      if (result.state) setData((current) => normalizeApiState(result.state, current));
      return result;
    } catch {
      setToast("Server trenutno nije dostupan. Pokušajte ponovo.");
      return false;
    }
  }

  const sharedProps = { data, setData, postAction, setToast };

  if (!authReady) {
    return <main className="language-screen"><div className="language-card"><div className="platform-mark">M</div><p className="eyebrow">MeniFlow</p><h2>Otvaramo vaš portal…</h2><p>Proveravamo nalog i restorane kojima imate pristup.</p></div></main>;
  }

  if (initialSurface !== "login" && !stateReady && !loadError) {
    return <main className="language-screen"><div className="language-card"><div className="platform-mark">M</div><p className="eyebrow">MeniFlow</p><h2>Učitavamo podatke…</h2><p>Još samo trenutak.</p></div></main>;
  }

  if (loadError) {
    return <main className="language-screen"><div className="language-card"><div className="platform-mark">M</div><p className="eyebrow">MeniFlow</p><h2>{initialSurface === "guest" ? "QR kod nije dostupan" : "Podaci nisu dostupni"}</h2><p>{loadError}</p><button className="primary-button wide" onClick={() => window.location.reload()}>Pokušaj ponovo</button>{initialSurface === "guest" && <small className="language-footnote">Zamolite osoblje za važeći QR kod stola.</small>}</div></main>;
  }

  async function logout() {
    await fetch("/api/auth/logout", { method: "POST", credentials: "same-origin" }).catch(() => undefined);
    window.location.assign("/login");
  }

  function openGuestPreview() {
    const table = data.tables.find((entry) => entry.active) ?? data.tables[0];
    if (!table) return;
    window.open(`/r/${encodeURIComponent(slug)}/t/${encodeURIComponent(table.token)}`, "_blank", "noopener,noreferrer");
  }

  return (
    <div
      className={`restaurant-demo font-${data.restaurant.brand.font}`}
      style={
        {
          "--brand-primary": data.restaurant.brand.primary,
          "--brand-accent": data.restaurant.brand.accent,
          "--brand-bg": data.restaurant.brand.background,
          "--brand-text": data.restaurant.brand.text,
          "--brand-radius": `${data.restaurant.brand.radius}px`,
          "--brand-font": data.restaurant.brand.fontFamily
            ? `"${data.restaurant.brand.fontFamily}"`
            : undefined,
        } as React.CSSProperties
      }
    >
      <BrandFontFaces brand={data.restaurant.brand} />
      {surface === "login" && <LoginScreen />}
      {surface === "admin" && (
        <AdminApp
          {...sharedProps}
          section={section}
          setSection={setSection}
          slug={slug}
          setSlug={setSlug}
          restaurants={restaurants}
          account={account}
          loading={loading}
          onOpenBills={() => window.location.assign(`/racuni?restaurant=${encodeURIComponent(slug)}`)}
          onOpenStaff={() => window.location.assign(`/osoblje?restaurant=${encodeURIComponent(slug)}`)}
          onOpenWaiter={() => window.open(`/moj-sto?restaurant=${encodeURIComponent(slug)}`, "_blank", "noopener,noreferrer")}
          onOpenGuest={openGuestPreview}
          onLogout={() => { void logout(); }}
        />
      )}
      {surface === "guest" && <LocalizedGuestApp {...sharedProps} guestToken={guestToken} />}
      {/* The operational screens have nothing to show on a locked account: no
          order or waiter call can arrive. The admin panel stays reachable so
          the owner can read the reason and switch restaurants. */}
      {surface === "staff" && (data.restaurant.locked
        ? <LockedNotice restaurant={data.restaurant} onLogout={() => { void logout(); }} />
        : <StaffApp {...sharedProps} account={account} onLogout={() => { void logout(); }} />)}
      {surface === "waiter" && (data.restaurant.locked
        ? <LockedNotice restaurant={data.restaurant} onLogout={() => { void logout(); }} />
        : <WaiterApp {...sharedProps} account={account} slug={slug} onLogout={() => { void logout(); }} />)}
      {surface === "bills" && (data.restaurant.locked
        ? <LockedNotice restaurant={data.restaurant} onLogout={() => { void logout(); }} />
        : <BillsApp
            {...sharedProps}
            account={account}
            onLogout={() => { void logout(); }}
            slug={slug}
            setSlug={setSlug}
            restaurants={restaurants}
          />)}

      {toast && <div className="global-toast" role="status"><span>✓</span>{toast}</div>}
    </div>
  );
}

function LoginScreen() {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [working, setWorking] = useState(false);
  const [error, setError] = useState("");

  async function submit(event: FormEvent) {
    event.preventDefault();
    if (!email || !password || working) return;
    setWorking(true);
    setError("");
    try {
      const response = await fetch("/api/auth/login", {
        method: "POST",
        headers: { "content-type": "application/json" },
        credentials: "same-origin",
        body: JSON.stringify({ email, password }),
      });
      const payload = await response.json().catch(() => null);
      if (!response.ok) throw new Error(payload?.error?.message ?? "Prijava nije uspela.");
      window.location.assign(payload?.redirect ?? "/admin");
    } catch (loginError) {
      setError(loginError instanceof Error ? loginError.message : "Prijava nije uspela.");
      setWorking(false);
    }
  }

  return (
    <main className="login-screen">
      <section className="login-story">
        <div className="platform-mark light">M</div>
        <p className="eyebrow light-text">MeniFlow za restorane</p>
        <h1>Vaš meni. Vaš brend.<br />Bolja usluga.</h1>
        <p className="login-lead">Od QR menija do porudžbine i poziva konobaru — sve na jednom mestu.</p>
        <div className="login-proof">
          <span>01</span><p><strong>Jednostavan meni</strong>Menjajte ponudu i dostupnost za nekoliko sekundi.</p>
          <span>02</span><p><strong>Brža usluga</strong>Svaki zahtev odmah stiže pravoj osobi.</p>
          <span>03</span><p><strong>Vaš identitet</strong>Boje, logo i stil svakog restorana.</p>
        </div>
      </section>
      <section className="login-form-wrap">
        <form className="login-card" onSubmit={submit}>
          <div className="platform-mark">M</div>
          <p className="eyebrow">Portal za restorane</p>
          <h2>Dobro došli nazad</h2>
          <p>Prijavite se da upravljate svojim restoranom.</p>
          <label>Email adresa<input type="email" value={email} onChange={(event) => setEmail(event.target.value)} /></label>
          <label>Lozinka<input type="password" value={password} onChange={(event) => setPassword(event.target.value)} /></label>
          <a className="forgot-password-link" href="/zaboravljena-lozinka">Zaboravili ste lozinku?</a>
          {error && <div className="auth-error" role="alert">{error}</div>}
          <button className="primary-button wide" type="submit" disabled={working}>{working ? "Prijavljivanje…" : "Prijavi se"} <span>→</span></button>
          <div className="demo-note"><span>Nemate nalog?</span> <a href="/registracija">Registrujte svoj restoran</a>.</div>
        </form>
      </section>
    </main>
  );
}

type SharedProps = {
  data: RestaurantState;
  setData: React.Dispatch<React.SetStateAction<RestaurantState>>;
  postAction: (
    action: string,
    payload: Record<string, unknown>,
  ) => Promise<{ result?: Record<string, unknown>; state?: unknown } | false>;
  setToast: React.Dispatch<React.SetStateAction<string | null>>;
};

function AdminApp({
  data,
  setData,
  postAction,
  setToast,
  section,
  setSection,
  slug,
  setSlug,
  restaurants,
  account,
  loading,
  onOpenBills,
  onOpenStaff,
  onOpenWaiter,
  onOpenGuest,
  onLogout,
}: SharedProps & {
  section: AdminSection;
  setSection: (section: AdminSection) => void;
  slug: string;
  setSlug: (slug: string) => void;
  restaurants: AuthRestaurant[];
  account: AuthUser | null;
  loading: boolean;
  onOpenBills: () => void;
  onOpenStaff: () => void;
  onOpenWaiter: () => void;
  onOpenGuest: () => void;
  onLogout: () => void;
}) {
  const [showRestaurantForm, setShowRestaurantForm] = useState(false);
  const currentMembership = restaurants.find((entry) => entry.slug === slug);
  const nav: { id: AdminSection; label: string; icon: string }[] = [
    { id: "overview", label: "Pregled", icon: "⌂" },
    { id: "menu", label: "Meni", icon: "≡" },
    { id: "tables", label: "Stolovi i QR", icon: "▦" },
    { id: "languages", label: "Jezici", icon: "⛭" },
    { id: "branding", label: "Brendiranje", icon: "◐" },
    { id: "settings", label: "Podešavanja", icon: "⚙" },
  ];

  return (
    <div className="admin-shell">
      <aside className="admin-sidebar">
        <div className="admin-logo"><span>M</span><div><strong>MeniFlow</strong><small>Restaurant OS</small></div></div>
        <div className="restaurant-switch-label">Restoran</div>
        <RestaurantSwitcher
          restaurants={restaurants.length ? restaurants : [{ slug: data.restaurant.slug, name: data.restaurant.name, currency: data.restaurant.currency, role: "owner" as const }]}
          slug={slug}
          currentName={loading ? "Učitavanje…" : data.restaurant.name}
          currentLocation={data.restaurant.location}
          onSelect={setSlug}
        />
        {currentMembership?.role === "owner" && <button className="add-restaurant-button" onClick={() => setShowRestaurantForm(true)}>＋ Dodaj restoran</button>}
        <nav className="admin-nav" aria-label="Admin navigacija">
          {nav.map((item) => <button key={item.id} className={section === item.id ? "active" : ""} onClick={() => setSection(item.id)}><span>{item.icon}</span>{item.label}{item.id === "overview" && data.tickets.filter((ticket) => ticket.status === "open").length > 0 && <b>{data.tickets.filter((ticket) => ticket.status === "open").length}</b>}</button>)}
          <button onClick={onOpenBills}><span>↗</span>Računi i zahtevi<i>Menadžer</i></button>
          <button onClick={onOpenStaff}><span>↗</span>Red zahteva<i>Ekran</i></button>
          <button onClick={onOpenWaiter}><span>↗</span>Moji stolovi<i>Konobari</i></button>
        </nav>
        <SidebarSalesContact />
        <button className="user-card" onClick={onLogout} title="Odjavi se"><span>{initials(account?.displayName || "Admin")}</span><p><strong>{account?.displayName || "Administrator"}</strong><small>{account?.email || "Vlasnički nalog"}</small></p><i>↪</i></button>
      </aside>
      <main className="admin-main">
        <header className="admin-topbar">
          <div className="mobile-brand"><span>M</span>MeniFlow</div>
          <div className="topbar-actions"><button className="icon-button" aria-label="Obaveštenja">◔<i /></button><button className="secondary-button" onClick={onOpenGuest}>Pregled stranice gosta <span>↗</span></button></div>
        </header>
        <div className="admin-content">
          <PlanBanner restaurant={data.restaurant} />
          {section === "overview" && <AdminOverview data={data} setSection={setSection} onOpenStaff={onOpenStaff} onOpenGuest={onOpenGuest} displayName={account?.displayName} />}
          {section === "menu" && <MenuManager {...{ data, setData, postAction, setToast }} />}
          {section === "tables" && <TablesManager {...{ data, setData, postAction, setToast }} />}
          {section === "branding" && <BrandingManager {...{ data, setData, postAction, setToast }} account={account} />}
          {section === "languages" && <LanguageManager {...{ data, postAction, setToast }} />}
          {section === "settings" && <SettingsManager {...{ data, setData, postAction, setToast }} canManageTeam={currentMembership?.role === "owner"} />}
        </div>
      </main>
      {showRestaurantForm && <CreateRestaurantModal onClose={() => setShowRestaurantForm(false)} onCreated={(createdSlug) => window.location.assign(`/admin?restaurant=${encodeURIComponent(createdSlug)}`)} />}
    </div>
  );
}

const ROLE_LABEL: Record<AuthRole, string> = {
  owner: "Vlasnik",
  manager: "Menadžer",
  staff: "Konobar",
  queue: "Ekran",
};

// The bottom of the admin sidebar: who to call about the subscription. The
// details are whatever the installation owner typed on /platforma; while
// nothing is entered there the old hosting note keeps the spot.
function SidebarSalesContact() {
  const [contact, setContact] = useState<SalesContact | null>(null);

  useEffect(() => {
    let active = true;
    fetch("/api/sales-contact", { cache: "no-store", credentials: "same-origin" })
      .then((response) => response.json())
      .then((payload) => {
        if (active && payload?.ok) setContact(payload.contact as SalesContact);
      })
      .catch(() => undefined);
    return () => { active = false; };
  }, []);

  if (!contact || (!contact.name && !contact.phone && !contact.email)) {
    return <div className="sidebar-promo"><span>✓</span><strong>Podaci su na vašem hostingu</strong><p>Meni, porudžbine i fotografije čuvaju se u vašoj cPanel aplikaciji.</p></div>;
  }

  return <div className="sidebar-promo sidebar-sales">
    <span>◔</span>
    <strong>{contact.name || "Prodaja i podrška"}</strong>
    <p>Pretplata, nove opcije i pomoć pri radu.</p>
    {contact.phone && <a href={`tel:${contact.phone.replace(/\s/g, "")}`}>{contact.phone}</a>}
    {contact.email && <a href={`mailto:${contact.email}`}>{contact.email}</a>}
  </div>;
}

// Shown instead of the waiter and operations screens while an account is
// locked, so staff are not left tapping buttons that answer with an error.
function LockedNotice({
  restaurant,
  onLogout,
}: {
  restaurant: RestaurantState["restaurant"];
  onLogout: () => void;
}) {
  return <main className="language-screen">
    <div className="language-card">
      <div className="platform-mark">M</div>
      <p className="eyebrow">{restaurant.name}</p>
      <h2>Nalog je zaključan</h2>
      <p>
        {restaurant.lockNote?.trim()
          ? restaurant.lockNote
          : "Pristup je privremeno zaustavljen. Ništa nije obrisano — meni, stolovi i nalozi vas čekaju."}
      </p>
      <p>Za otključavanje se obratite MeniFlow podršci.</p>
      <button className="primary-button wide" onClick={onLogout}>Odjavi se</button>
    </div>
  </main>;
}

function dayWord(days: number) {
  return days === 1 ? "dan" : "dana";
}

function planEndDate(value: string | null | undefined) {
  if (!value) return "";
  const parsed = new Date(String(value).replace(" ", "T") + (/[zZ]|[+-]\d\d:?\d\d$/.test(String(value)) ? "" : "Z"));
  if (Number.isNaN(parsed.getTime())) return "";
  return parsed.toLocaleDateString("sr-Latn-RS", { day: "numeric", month: "long", year: "numeric" });
}

// Countdown for whichever clock is running — the trial, or the paid month — and
// a clear explanation of what stopped working once it runs out.
function PlanBanner({ restaurant }: { restaurant: RestaurantState["restaurant"] }) {
  if (restaurant.locked) {
    return <div className="plan-banner locked" role="status">
      <span>⊘</span>
      <p>
        <strong>Nalog je zaključan.</strong>
        {restaurant.lockNote ? ` ${restaurant.lockNote} ` : " "}
        Meni, poručivanje i poziv konobaru su zaustavljeni. Ništa nije obrisano — obratite se MeniFlow podršci.
      </p>
    </div>;
  }

  // A lapsed subscription keeps working for three working days, and that is
  // exactly when the client most needs to see why.
  if (restaurant.planTone === "grace") {
    const left = Math.max(0, restaurant.graceDaysLeft ?? 0);
    return <div className="plan-banner expired" role="status">
      <span>!</span>
      <p>
        {/* The Serbian locale already ends a date with a full stop. */}
        <strong>Pretplata je istekla {planEndDate(restaurant.planExpiresAt)}</strong>
        Sve još radi {left ? `${left} ${dayWord(left)}` : "danas"}, a onda nalog prelazi na osnovni nivo — ostaje samo poziv konobaru. Obnovite pretplatu da ne dođe do prekida.
      </p>
    </div>;
  }

  if (!restaurant.hasFullAccess) {
    return <div className="plan-banner expired" role="status">
      <span>!</span>
      <p>
        <strong>{restaurant.plan === "trial" ? "Probni period je istekao." : "Nalog je na osnovnom nivou."}</strong>
        Gost i dalje može da pozove konobara, ali meni, poručivanje i računi po stolu su zaustavljeni.
        Vaši podaci nisu obrisani — sve se vraća čim aktivirate pretplatu.
      </p>
    </div>;
  }

  if (restaurant.plan === "trial") {
    const daysLeft = restaurant.trialDaysLeft ?? 0;
    return <div className={`plan-banner trial ${daysLeft <= 7 ? "ending" : ""}`} role="status">
      <span>◔</span>
      <p>
        <strong>{daysLeft === 1 ? "Poslednji dan probnog perioda." : `Probni period — još ${daysLeft} ${dayWord(daysLeft)}.`}</strong>
        Sve opcije rade. Kad period istekne, ostaje samo poziv konobaru dok ne aktivirate pretplatu.
      </p>
    </div>;
  }

  // A paid plan says nothing until the end of the term is close enough to act
  // on, so the banner does not become wallpaper.
  const daysLeft = restaurant.planDaysLeft;
  if (restaurant.planUnlimited || daysLeft == null || daysLeft > 10) return null;
  return <div className={`plan-banner ${daysLeft <= 5 ? "urgent" : "ending"}`} role="status">
    <span>◔</span>
    <p>
      <strong>Pretplata ističe {daysLeft <= 1 ? "danas" : `za ${daysLeft} ${dayWord(daysLeft)}`}, {planEndDate(restaurant.planExpiresAt)}</strong>
      Posle isteka imate još 3 radna dana, a onda nalog prelazi na osnovni nivo.
    </p>
  </div>;
}

function RestaurantSwitcher({
  restaurants,
  slug,
  currentName,
  currentLocation,
  onSelect,
}: {
  restaurants: AuthRestaurant[];
  slug: string;
  currentName: string;
  currentLocation?: string;
  onSelect: (slug: string) => void;
}) {
  const [open, setOpen] = useState(false);
  const [query, setQuery] = useState("");
  const containerRef = useRef<HTMLDivElement>(null);
  const current = restaurants.find((entry) => entry.slug === slug);
  const filtered = query.trim()
    ? restaurants.filter((entry) => entry.name.toLowerCase().includes(query.trim().toLowerCase()))
    : restaurants;

  useEffect(() => {
    if (!open) return;
    function onDocClick(event: MouseEvent) {
      if (containerRef.current && !containerRef.current.contains(event.target as Node)) setOpen(false);
    }
    function onKey(event: KeyboardEvent) {
      if (event.key === "Escape") setOpen(false);
    }
    document.addEventListener("mousedown", onDocClick);
    document.addEventListener("keydown", onKey);
    return () => {
      document.removeEventListener("mousedown", onDocClick);
      document.removeEventListener("keydown", onKey);
    };
  }, [open]);

  return (
    <div className="restaurant-switch" ref={containerRef}>
      <span className="restaurant-avatar">{initials(currentName)}</span>
      <div className="restaurant-switch-control">
        <button
          type="button"
          className="restaurant-switch-trigger"
          onClick={() => setOpen((value) => !value)}
          aria-haspopup="listbox"
          aria-expanded={open}
        >
          <span className="restaurant-switch-name">
            <strong>{currentName}</strong>
            {currentLocation && <small>{currentLocation}</small>}
          </span>
          {current && <span className="status-chip role-chip">{ROLE_LABEL[current.role]}</span>}
          <i>{open ? "▲" : "▼"}</i>
        </button>
        <select
          aria-label="Promenite restoran"
          className="sr-only"
          value={slug}
          onChange={(event) => onSelect(event.target.value)}
        >
          {restaurants.map((entry) => (
            <option key={entry.slug} value={entry.slug}>{entry.name}</option>
          ))}
        </select>
        {open && (
          <div className="restaurant-switch-panel" role="listbox">
            {restaurants.length > 6 && (
              <input
                autoFocus
                className="restaurant-switch-search"
                placeholder="Pretraži restorane…"
                value={query}
                onChange={(event) => setQuery(event.target.value)}
              />
            )}
            <div className="restaurant-switch-list">
              {filtered.map((entry) => (
                <button
                  type="button"
                  key={entry.slug}
                  role="option"
                  aria-selected={entry.slug === slug}
                  className={`restaurant-switch-option ${entry.slug === slug ? "active" : ""}`}
                  onClick={() => {
                    onSelect(entry.slug);
                    setOpen(false);
                    setQuery("");
                  }}
                >
                  <span className="restaurant-avatar small">{initials(entry.name)}</span>
                  <span className="restaurant-switch-option-main">
                    <strong>{entry.name}</strong>
                    <small>{entry.currency}</small>
                  </span>
                  <span className="status-chip role-chip">{ROLE_LABEL[entry.role]}</span>
                  {entry.slug === slug && <i>✓</i>}
                </button>
              ))}
              {filtered.length === 0 && <div className="empty-small">Nema restorana koji se poklapaju.</div>}
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

function slugFromName(value: string) {
  return value
    .toLocaleLowerCase("sr-Latn")
    .replaceAll("đ", "dj")
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-+|-+$/g, "")
    .slice(0, 63);
}

function CreateRestaurantModal({ onClose, onCreated }: { onClose: () => void; onCreated: (slug: string) => void }) {
  const [name, setName] = useState("");
  const [slug, setSlug] = useState("");
  const [slugEdited, setSlugEdited] = useState(false);
  const [currency, setCurrency] = useState<"RSD" | "EUR">("RSD");
  const [working, setWorking] = useState(false);
  const [error, setError] = useState("");

  async function submit(event: FormEvent) {
    event.preventDefault();
    if (working) return;
    setWorking(true);
    setError("");
    try {
      const response = await fetch("/api/admin/restaurants", {
        method: "POST",
        credentials: "same-origin",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ name, slug, currency }),
      });
      const payload = await response.json().catch(() => null);
      if (!response.ok) throw new Error(payload?.error?.message ?? "Restoran nije napravljen.");
      onCreated(payload.restaurant.slug);
    } catch (requestError) {
      setError(requestError instanceof Error ? requestError.message : "Restoran nije napravljen.");
      setWorking(false);
    }
  }

  return <div className="modal-backdrop" role="presentation" onMouseDown={(event) => event.target === event.currentTarget && onClose()}><form className="compact-modal restaurant-modal" onSubmit={submit}><div className="modal-heading"><div><p className="eyebrow">Novi prostor</p><h2>Dodajte restoran</h2><p>Dobićete odvojen meni, stolove, brending i naloge.</p></div><button type="button" onClick={onClose}>×</button></div><div className="form-grid"><label className="full">Naziv restorana<input autoFocus value={name} maxLength={100} required onChange={(event) => { const next = event.target.value; setName(next); if (!slugEdited) setSlug(slugFromName(next)); }} placeholder="npr. Restoran Dunav" /></label><label className="full">Adresa u linku<input value={slug} minLength={3} maxLength={63} pattern="[a-z0-9]+(?:-[a-z0-9]+)*" required onChange={(event) => { setSlugEdited(true); setSlug(event.target.value.toLowerCase()); }} placeholder="restoran-dunav" /><small>Biće deo QR linka. Koristite mala slova, brojeve i crtice.</small></label><label className="full">Valuta<select value={currency} onChange={(event) => setCurrency(event.target.value as "RSD" | "EUR")}><option value="RSD">RSD — dinar</option><option value="EUR">EUR — evro</option></select></label></div>{error && <div className="auth-error" role="alert">{error}</div>}<footer className="modal-actions"><button type="button" className="secondary-button" onClick={onClose} disabled={working}>Otkaži</button><button className="primary-button" type="submit" disabled={working}>{working ? "Kreiranje…" : "Napravi restoran"}</button></footer></form></div>;
}

function PageHeading({ eyebrow, title, description, action }: { eyebrow?: string; title: string; description?: string; action?: React.ReactNode }) {
  return <div className="page-heading"><div>{eyebrow && <p className="eyebrow">{eyebrow}</p>}<h1>{title}</h1>{description && <p>{description}</p>}</div>{action}</div>;
}

function AdminOverview({ data, setSection, onOpenStaff, onOpenGuest, displayName }: { data: RestaurantState; setSection: (value: AdminSection) => void; onOpenStaff: () => void; onOpenGuest: () => void; displayName?: string }) {
  const open = data.tickets.filter((ticket) => ticket.status === "open");
  const orders = open.filter((ticket) => ticket.kind === "order");
  const unavailable = data.items.filter((item) => !item.available).length;
  const recent = [...open].sort((a, b) => parseServerDate(a.createdAt).getTime() - parseServerDate(b.createdAt).getTime()).slice(0, 3);
  return (
    <>
      <PageHeading eyebrow={new Date().toLocaleDateString("sr-RS", { weekday: "long", day: "numeric", month: "long" })} title={`Dobro došli${displayName ? `, ${displayName.split(" ")[0]}` : ""}`} description="Evo šta se trenutno dešava u vašem restoranu." action={<button className="primary-button" onClick={onOpenStaff}><span className="live-dot" /> Otvori operativni ekran</button>} />
      <section className="metric-grid">
        <article><span className="metric-icon green">↗</span><div><small>Aktivni zahtevi</small><strong>{open.length}</strong><p><b>{open.filter((ticket) => ticket.kind === "call").length}</b> poziva · <b>{orders.length}</b> porudžbine</p></div><em className="metric-trend">Uživo</em></article>
        <article><span className="metric-icon coral">≡</span><div><small>Artikli u meniju</small><strong>{data.items.length}</strong><p><b>{data.items.filter((item) => item.available).length}</b> dostupno danas</p></div><button onClick={() => setSection("menu")}>Uredi →</button></article>
        <article><span className="metric-icon gold">!</span><div><small>Nedostupno</small><strong>{unavailable}</strong><p>Brzo vratite u ponudu</p></div><button onClick={() => setSection("menu")}>Pregled →</button></article>
        <article><span className="metric-icon blue">▦</span><div><small>Aktivni QR stolovi</small><strong>{data.tables.filter((table) => table.active).length}</strong><p>{new Set(data.tables.map((table) => table.zone)).size} zone restorana</p></div><button onClick={() => setSection("tables")}>Stolovi →</button></article>
      </section>
      <section className="overview-grid">
        <article className="panel live-requests">
          <div className="panel-heading"><div><h2>Aktivni zahtevi</h2><p>Najstariji zahtevi su prvi.</p></div><button onClick={onOpenStaff}>Prikaži sve <span>→</span></button></div>
          <div className="request-list">
            {recent.map((ticket) => <div className="request-row" key={ticket.id}><div className={`request-type ${ticket.kind}`}><span>{ticket.kind === "call" ? "◔" : "≡"}</span></div><div className="request-main"><strong>Sto {ticket.tableNumber}</strong><p>{ticket.kind === "call" ? ticket.callReason : `${ticket.items?.reduce((sum, item) => sum + item.quantity, 0)} stavke · ${money(ticket.total ?? 0)}`}</p></div><span className={`age ${Date.now() - parseServerDate(ticket.createdAt).getTime() > 10 * 60_000 ? "urgent" : ""}`}>{timeAgo(ticket.createdAt)}</span></div>)}
          </div>
          {recent.length === 0 && <div className="empty-small">Sve je rešeno — nema aktivnih zahteva.</div>}
        </article>
        <article className="panel rhythm-card">
          <div className="panel-heading"><div><h2>Spremnost restorana</h2><p>Najvažnije stavke za rad QR menija</p></div><span className="status-chip">Uživo</span></div>
          <div className="rhythm-total"><strong>{data.items.filter((item) => item.available).length}</strong><span>aktivnih artikala<br /><b>{data.tables.filter((table) => table.active).length} QR stolova spremno</b></span></div>
          <div className="request-list"><div className="request-row"><div className="request-type call"><span>✓</span></div><div className="request-main"><strong>Poručivanje</strong><p>{data.restaurant.settings.orderingEnabled && !data.restaurant.settings.orderingPaused ? "Gost može da pošalje porudžbinu" : "Trenutno je pauzirano"}</p></div></div><div className="request-row"><div className="request-type order"><span>✓</span></div><div className="request-main"><strong>Poziv konobaru</strong><p>{data.restaurant.settings.waiterCallEnabled ? "Dostupan gostima" : "Isključen u podešavanjima"}</p></div></div></div>
        </article>
      </section>
      <section className="quick-actions"><button onClick={() => setSection("menu")}><span>＋</span><p><strong>Dodaj artikal</strong><small>Proširite svoj meni</small></p><i>→</i></button><button onClick={() => setSection("tables")}><span>▦</span><p><strong>Kreiraj QR za sto</strong><small>Dodajte novi sto</small></p><i>→</i></button><button onClick={onOpenGuest}><span>◉</span><p><strong>Pogled gosta</strong><small>Proverite kako sve izgleda</small></p><i>→</i></button></section>
    </>
  );
}

function MenuManager({ data, setData, postAction, setToast }: SharedProps) {
  const [query, setQuery] = useState("");
  const [category, setCategory] = useState(() => data.categories[0] ?? "");
  const [showForm, setShowForm] = useState(false);
  const [showCategories, setShowCategories] = useState(false);
  const [editing, setEditing] = useState<MenuItem | null>(null);
  const filtered = data.items.filter((item) => chipMatches(data, category, item) && item.name.toLowerCase().includes(query.toLowerCase()));

  async function toggle(item: MenuItem) {
    if (await postAction("toggleItem", { itemId: item.id, available: !item.available })) {
      setToast(item.available ? `${item.name} je označen kao nedostupan.` : `${item.name} je ponovo dostupan.`);
    }
  }

  return (
    <>
      <PageHeading eyebrow="Ponuda restorana" title="Meni" description="Upravljajte jelima, pićima, cenama i dostupnošću." action={<button className="primary-button" onClick={() => { setEditing(null); setShowForm(true); }}>＋ Dodaj artikal</button>} />
      <div className="menu-toolbar"><label className="search-box"><span>⌕</span><input placeholder="Pretražite naziv artikla…" value={query} onChange={(event) => setQuery(event.target.value)} /></label><button className="secondary-button">Filteri <span>⌄</span></button><button className="secondary-button">Redosled <span>↕</span></button></div>
      <div className="category-tabs">{data.categories.map((name) => <button key={name} className={category === name ? "active" : ""} onClick={() => setCategory(name)}>{name}{chipKind(data, name) === "all" && <span>{data.items.length}</span>}</button>)}<button className="manage-categories" onClick={() => setShowCategories(true)}>⚙ Uredi kategorije</button></div>
      <section className="panel menu-panel">
        <div className="menu-table-head"><span>Artikal</span><span>Kategorija</span><span>Porcija</span><span>Cena</span><span>Dostupno</span><span /></div>
        <div className="menu-rows">
          {filtered.map((item) => <article className={`menu-row ${!item.available ? "unavailable" : ""}`} key={item.id}>
            <div className="menu-item-info"><FoodThumb item={item} /><div><strong>{item.name}</strong><p>{item.description}</p><div className="tag-row">{item.tags.slice(0, 2).map((tag) => <span key={tag}>{tag}</span>)}{item.allergens?.length ? <em>i</em> : null}</div></div></div>
            <span className="table-category">{item.category}</span><span>{item.portion}</span><strong className="table-price">{money(item.price, data.restaurant.currency)}</strong>
            <button className={`toggle ${item.available ? "on" : ""}`} onClick={() => toggle(item)} aria-label={`Promenite dostupnost za ${item.name}`}><span /></button>
            <div className="row-actions"><button onClick={() => { setEditing(item); setShowForm(true); }}>Uredi</button><button aria-label="Više opcija">•••</button></div>
          </article>)}
        </div>
        <footer className="table-footer">Prikazano {filtered.length} od {data.items.length} artikala <span><button disabled>←</button><b>1</b><button disabled>→</button></span></footer>
      </section>
      {showForm && <MenuItemModal data={data} existing={editing} onClose={() => setShowForm(false)} postAction={postAction} setToast={setToast} onSave={async (item, file) => {
        let savedItem = item;
        if (file) {
          if (file.size > 5 * 1024 * 1024) {
            setToast("Fotografija može imati najviše 5 MB.");
            return false;
          }
          try {
            const form = new FormData();
            form.append("slug", data.restaurant.slug);
            form.append("file", file);
            const response = await fetch("/api/demo/upload", { method: "POST", body: form });
            const result = await response.json();
            if (!response.ok || !result.asset) throw new Error(result?.error?.message ?? "Upload nije uspeo.");
            savedItem = { ...item, imageAssetId: result.asset.id, image: result.asset.url };
          } catch {
            setToast("Fotografija nije sačuvana. Pokušajte ponovo.");
            return false;
          }
        }
        if (!(await postAction("addMenuItem", { item: savedItem }))) return false;
        setShowForm(false);
        setToast(editing ? "Izmene artikla su sačuvane." : `${savedItem.name} je dodat u meni.`);
        return true;
      }} />}
      {showCategories && <CategoryManagerModal data={data} postAction={postAction} setToast={setToast} onClose={() => setShowCategories(false)} />}
    </>
  );
}

function CategoryManagerModal({ data, postAction, setToast, onClose }: Pick<SharedProps, "data" | "postAction" | "setToast"> & { onClose: () => void }) {
  const [rows, setRows] = useState<MenuCategory[]>(() => data.categoryDetails ?? []);
  const [newName, setNewName] = useState("");
  const [workingId, setWorkingId] = useState<string | null>(null);

  useEffect(() => {
    setRows(data.categoryDetails ?? []);
  }, [data.categoryDetails]);

  async function saveRow(row: MenuCategory) {
    if (!row.name.trim()) return;
    setWorkingId(row.id);
    const saved = await postAction("saveCategory", {
      category: { categoryId: row.id, name: row.name.trim(), isActive: row.active },
    });
    setWorkingId(null);
    if (saved) setToast(`Kategorija „${row.name.trim()}“ je sačuvana.`);
  }

  async function addCategory(event: FormEvent) {
    event.preventDefault();
    const name = newName.trim();
    if (!name) return;
    setWorkingId("new");
    const saved = await postAction("saveCategory", { category: { name, isActive: true } });
    setWorkingId(null);
    if (saved) {
      setNewName("");
      setToast(`Kategorija „${name}“ je dodata.`);
    }
  }

  async function moveRow(row: MenuCategory, direction: "up" | "down") {
    setWorkingId(row.id);
    await postAction("reorderCategory", { categoryId: row.id, direction });
    setWorkingId(null);
  }

  async function deleteRow(row: MenuCategory) {
    if (!window.confirm(`Obrisati kategoriju „${row.name}“?`)) return;
    setWorkingId(row.id);
    const deleted = await postAction("deleteCategory", { categoryId: row.id });
    setWorkingId(null);
    if (deleted) setToast(`Kategorija „${row.name}“ je obrisana.`);
  }

  return <div className="modal-backdrop" role="presentation" onMouseDown={(event) => event.target === event.currentTarget && onClose()}><div className="compact-modal category-modal"><div className="modal-heading"><div><p className="eyebrow">Organizacija menija</p><h2>Kategorije jela i pića</h2><p>Preimenujte, obrišite ili promenite redosled kategorija.</p></div><button type="button" onClick={onClose}>×</button></div><div className="category-editor-list">{rows.map((row, index) => <div className="category-editor-row" key={row.id}><div className="category-reorder"><button type="button" aria-label="Pomeri gore" disabled={index === 0 || workingId === row.id} onClick={() => moveRow(row, "up")}>▲</button><button type="button" aria-label="Pomeri dole" disabled={index === rows.length - 1 || workingId === row.id} onClick={() => moveRow(row, "down")}>▼</button></div><input value={row.name} maxLength={60} onChange={(event) => setRows((current) => current.map((entry) => entry.id === row.id ? { ...entry, name: event.target.value } : entry))} /><label className="category-active"><input type="checkbox" checked={row.active} onChange={(event) => setRows((current) => current.map((entry) => entry.id === row.id ? { ...entry, active: event.target.checked } : entry))} /><span>{row.active ? "Vidljiva" : "Skrivena"}</span></label><button className="secondary-button" onClick={() => saveRow(row)} disabled={workingId === row.id}>{workingId === row.id ? "Čuvanje…" : "Sačuvaj"}</button><button className="danger-button" onClick={() => deleteRow(row)} disabled={workingId === row.id}>Obriši</button></div>)}</div><form className="category-add-row" onSubmit={addCategory}><label>Nova kategorija<input value={newName} maxLength={60} onChange={(event) => setNewName(event.target.value)} placeholder="npr. Pića, Žestina, Predjelo" /></label><button className="primary-button" disabled={workingId === "new"}>{workingId === "new" ? "Dodavanje…" : "Dodaj kategoriju"}</button></form></div></div>;
}

function FoodThumb({ item, large = false }: { item: MenuItem; large?: boolean }) {
  return <div className={`food-thumb ${large ? "large" : ""}`}>{item.image ? <img src={item.image} alt="" /> : <span>{item.category.includes("pića") ? "0,3" : initials(item.name).slice(0, 1)}</span>}</div>;
}

function MenuItemModal({ data, existing, onClose, onSave, postAction, setToast }: { data: RestaurantState; existing: MenuItem | null; onClose: () => void; onSave: (item: MenuItem, file: File | null) => Promise<boolean>; postAction: SharedProps["postAction"]; setToast: SharedProps["setToast"] }) {
  const [name, setName] = useState(existing?.name ?? "");
  const [category, setCategory] = useState(existing?.category ?? data.categoryDetails?.[0]?.name ?? "Glavna jela");
  const [description, setDescription] = useState(existing?.description ?? "");
  const [variants, setVariants] = useState<MenuVariant[]>(() => existing?.variants?.length ? existing.variants.map((entry) => ({ ...entry })) : [{ id: existing?.variantId, label: existing?.portion ?? "Porcija", price: existing?.price ?? 0 }]);
  const [tags, setTags] = useState(existing?.tags.join(", ") ?? "");
  const [available, setAvailable] = useState(existing?.available ?? true);
  const [featured, setFeatured] = useState(existing?.featured ?? false);
  const [selectedFile, setSelectedFile] = useState<File | null>(null);
  const [libraryOpen, setLibraryOpen] = useState(false);
  // Set when the manager picks a library photo instead of uploading their own.
  const [libraryPick, setLibraryPick] = useState<{ id: string; name: string; url: string } | null>(null);
  const [saving, setSaving] = useState(false);

  function updateVariant(index: number, patch: Partial<MenuVariant>) {
    setVariants((current) => current.map((entry, entryIndex) => entryIndex === index ? { ...entry, ...patch } : entry));
  }

  async function submit(event: FormEvent) {
    event.preventDefault();
    const cleanVariants = variants.filter((entry) => entry.label.trim() && entry.price > 0).map((entry) => ({ ...entry, label: entry.label.trim() }));
    if (!name.trim() || !cleanVariants.length) return;
    setSaving(true);
    const primary = cleanVariants[0];
    const saved = await onSave({
      id: existing?.id ?? `item-${Date.now()}`,
      name: name.trim(),
      category,
      description: description.trim(),
      portion: primary.label,
      price: primary.price,
      variants: cleanVariants,
      available,
      featured,
      image: existing?.image,
      imageAssetId: libraryPick?.id ?? existing?.imageAssetId,
      persisted: existing?.persisted,
      tags: tags.split(",").map((tag) => tag.trim()).filter(Boolean),
      allergens: existing?.allergens ?? [],
      categoryId: existing?.categoryId ?? data.categoryDetails?.find((entry) => entry.name === category)?.id ?? data.items.find((item) => item.category === category)?.categoryId,
      variantId: primary.id ?? existing?.variantId,
    }, selectedFile);
    if (!saved) setSaving(false);
  }

  // Copying happens server-side; we only need the resulting asset id.
  async function pickFromLibrary(image: { id: string; name: string; url: string }) {
    const result = await postAction("useLibraryImage", { libraryId: image.id });
    if (!result) return;
    const assetId = (result.result as { assetId?: string } | undefined)?.assetId;
    if (!assetId) return;
    setLibraryPick({ id: assetId, name: image.name, url: `/api/demo/media/${assetId}` });
    setSelectedFile(null);
    setLibraryOpen(false);
    setToast(`Fotografija „${image.name}" je preuzeta iz biblioteke.`);
  }

  return <div className="modal-backdrop" role="presentation" onMouseDown={(event) => event.target === event.currentTarget && onClose()}><form className="side-modal item-form flexible-item-form" onSubmit={submit}><div className="modal-heading"><div><p className="eyebrow">{existing ? "Izmena ponude" : "Novi artikal"}</p><h2>{existing ? "Uredi artikal i cene" : "Dodajte novi artikal"}</h2><p>Dodajte jednu ili više veličina, gramaža i cena.</p></div><button type="button" onClick={onClose}>×</button></div><div className="form-grid"><label className="full">Naziv artikla *<input autoFocus value={name} onChange={(event) => setName(event.target.value)} placeholder="npr. Teleći medaljoni" required /></label><label>Kategorija *<select value={category} onChange={(event) => setCategory(event.target.value)}>{menuCategoryNames(data).map((entry) => <option key={entry}>{entry}</option>)}</select></label><label>Oznake<input value={tags} onChange={(event) => setTags(event.target.value)} placeholder="npr. Ljuto, Domaće, Bez glutena" /><small>Slobodan tekst, razdvojen zapetom.</small></label><label className="full">Opis<textarea value={description} onChange={(event) => setDescription(event.target.value)} placeholder="Kratak, privlačan opis…" rows={3} /></label></div><div className="variant-editor"><div className="variant-editor-heading"><span><strong>Veličine, količine i cene</strong><small>Primeri: 0,3 l, 0,5 l, 250 g, mala, velika, porcija</small></span><button className="secondary-button" type="button" onClick={() => setVariants((current) => [...current, { label: "", price: 0 }])}>＋ Dodaj varijantu</button></div>{variants.map((variant, index) => <div className="variant-editor-row" key={variant.id ?? index}><label>Naziv / količina<input value={variant.label} maxLength={40} onChange={(event) => updateVariant(index, { label: event.target.value })} placeholder="npr. 0,5 l" required /></label><label>Cena ({data.restaurant.currency})<input inputMode="decimal" value={variant.price || ""} onChange={(event) => updateVariant(index, { price: Number(event.target.value.replace(",", ".")) })} placeholder="890" required /></label><button type="button" className="variant-remove" disabled={variants.length === 1} onClick={() => setVariants((current) => current.filter((_, entryIndex) => entryIndex !== index))}>Ukloni</button></div>)}</div><label className="full upload-zone"><input type="file" accept="image/png,image/jpeg,image/webp" onChange={(event) => setSelectedFile(event.target.files?.[0] ?? null)} /><span>＋</span><strong>{selectedFile?.name || (existing?.image ? "Zamenite fotografiju" : "Otpremite fotografiju")}</strong><small>PNG, JPG ili WebP · do 5 MB · opciono</small></label><div className="library-pick-row"><button type="button" className="secondary-button" onClick={() => setLibraryOpen(true)}>▤ Izaberi iz biblioteke</button>{libraryPick && <span className="library-pick-chosen"><img src={libraryPick.url} alt="" /><small>{libraryPick.name}</small><button type="button" onClick={() => setLibraryPick(null)}>Ukloni</button></span>}</div><div className="form-toggles"><label><span><strong>Dostupno za poručivanje</strong><small>Gost može da doda artikal u porudžbinu.</small></span><input type="checkbox" checked={available} onChange={(event) => setAvailable(event.target.checked)} /></label><label><span><strong>Istaknut artikal</strong><small>Prikaži u sekciji „Preporučujemo“.</small></span><input type="checkbox" checked={featured} onChange={(event) => setFeatured(event.target.checked)} /></label></div><footer><button type="button" className="secondary-button" onClick={onClose} disabled={saving}>Otkaži</button><button className="primary-button" type="submit" disabled={saving}>{saving ? "Čuvanje…" : existing ? "Sačuvaj izmene" : "Dodaj u meni"}</button></footer>{libraryOpen && <LibraryPicker onPick={pickFromLibrary} onClose={() => setLibraryOpen(false)} />}</form></div>;
}

type LibraryImage = { id: string; name: string; tags: string; url: string };

// Photo bank kept by the platform owner: standard products such as a Heineken
// bottle are uploaded once centrally instead of by every restaurant.
function LibraryPicker({ onPick, onClose }: { onPick: (image: LibraryImage) => Promise<void>; onClose: () => void }) {
  const [images, setImages] = useState<LibraryImage[] | null>(null);
  const [query, setQuery] = useState("");
  const [suggestions, setSuggestions] = useState<string[]>([]);
  const [busyId, setBusyId] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);

  // Product names and tags the platform owner has already used, offered as a
  // dropdown so nobody has to guess how a photo was labelled.
  useEffect(() => {
    let active = true;
    fetch("/api/library?facets=1", { cache: "no-store", credentials: "same-origin" })
      .then((response) => response.json())
      .then((payload) => {
        if (!active || !payload?.ok) return;
        setSuggestions([
          ...(payload.names as string[] ?? []),
          ...(payload.tags as string[] ?? []),
        ]);
      })
      .catch(() => undefined);
    return () => { active = false; };
  }, []);

  useEffect(() => {
    let active = true;
    const timer = window.setTimeout(() => {
      fetch(`/api/library?q=${encodeURIComponent(query)}`, { cache: "no-store", credentials: "same-origin" })
        .then((response) => response.json())
        .then((payload) => {
          if (!active) return;
          if (!payload?.ok) { setError(payload?.error?.message ?? "Biblioteka nije dostupna."); setImages([]); return; }
          setImages(payload.images);
        })
        .catch(() => { if (active) { setError("Biblioteka nije dostupna."); setImages([]); } });
    }, query ? 220 : 0);
    return () => { active = false; window.clearTimeout(timer); };
  }, [query]);

  async function choose(image: LibraryImage) {
    setBusyId(image.id);
    await onPick(image);
    setBusyId(null);
  }

  return <div className="modal-backdrop library-backdrop" role="presentation" onMouseDown={(event) => event.target === event.currentTarget && onClose()}>
    <div className="library-modal">
      <div className="modal-heading">
        <div><p className="eyebrow">Zajednička biblioteka</p><h2>Izaberite fotografiju</h2><p>Standardni proizvodi — pića i flaširana roba — spremni za upotrebu. Za svoja jela otpremite sopstvenu fotografiju.</p></div>
        <button type="button" onClick={onClose} aria-label="Zatvori">×</button>
      </div>
      <div className="library-search"><Combobox label="Pretraga" value={query} options={suggestions} onChange={setQuery} maxLength={60} placeholder="npr. Heineken, kola, pivo" /></div>
      {error && <div className="auth-error" role="alert">{error}</div>}
      {images === null
        ? <p className="staff-order-empty">Učitavamo biblioteku…</p>
        : images.length
          ? <div className="library-grid">{images.map((image) => <button type="button" key={image.id} disabled={busyId !== null} onClick={() => void choose(image)}>
              <img src={image.url} alt={image.name} loading="lazy" />
              <strong>{busyId === image.id ? "Preuzimamo…" : image.name}</strong>
              {image.tags && <small>{image.tags}</small>}
            </button>)}</div>
          : <p className="staff-order-empty">{query ? `Nema rezultata za „${query}".` : "Biblioteka je još prazna."}</p>}
    </div>
  </div>;
}

// Every guest-facing string, grouped so the owner is not staring at a wall of
// 37 inputs. Labels describe where each one shows up.
const COPY_FIELDS: Array<{ group: string; keys: Array<[keyof GuestCopy, string]> }> = [
  {
    group: "Početak menija",
    keys: [
      ["greeting", "Pozdrav"],
      ["whatToEat", "Naslov"],
      ["kitchenHours", "Radno vreme kuhinje"],
      ["search", "Polje za pretragu"],
      ["fromKitchen", "Nadnaslov sekcije"],
      ["itemsLabel", "Brojač artikala"],
      ["noResults", "Nema rezultata"],
    ],
  },
  {
    group: "Poziv konobaru",
    keys: [
      ["callWaiter", "Dugme"],
      ["helpOrBill", "Podnaslov dugmeta"],
      ["callTitle", "Naslov prozora"],
      ["callBody", "Opis prozora"],
      ["reasons", "Razlozi (razdvojiti znakom |)"],
      ["payQuestion", "Pitanje o plaćanju"],
      ["card", "Kartica"],
      ["cash", "Gotovina"],
      ["notify", "Dugme za slanje"],
    ],
  },
  {
    group: "Artikal i korpa",
    keys: [
      ["allergens", "Alergeni"],
      ["kitchenNote", "Napomena kuhinji"],
      ["noteExample", "Primer napomene"],
      ["add", "Dodaj u porudžbinu"],
      ["yourOrder", "Vaša porudžbina"],
      ["oneItem", "Jedna stavka"],
      ["manyItems", "Više stavki"],
      ["checkItems", "Naslov korpe"],
      ["subtotal", "Međuzbir"],
      ["service", "Usluga"],
      ["included", "Uračunato"],
      ["total", "Ukupno"],
      ["payAtRestaurant", "Plaćanje u restoranu"],
      ["payBody", "Opis plaćanja"],
      ["sendOrder", "Pošalji porudžbinu"],
    ],
  },
  {
    group: "Posle slanja i pauza",
    keys: [
      ["orderSent", "Porudžbina poslata"],
      ["orderSentBody", "Poruka posle slanja"],
      ["prepTime", "Vreme pripreme"],
      ["continueMenu", "Nastavi pregled"],
      ["pausedTitle", "Pauza — naslov"],
      ["pausedBody", "Pauza — opis"],
      ["poweredBy", "Podnožje"],
    ],
  },
];

function LanguageManager({ data, postAction, setToast }: Pick<SharedProps, "data" | "postAction" | "setToast">) {
  const enabled = enabledLocalesFor(data);
  const [editing, setEditing] = useState<string>(enabled[0] ?? DEFAULT_LOCALE);
  const [draft, setDraft] = useState<Record<string, string>>({});
  const [adding, setAdding] = useState("");
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const available = LOCALES.filter((entry) => !enabled.includes(entry.code));
  const builtIn = GUEST_COPY[editing] ?? GUEST_COPY[data.restaurant.defaultLocale] ?? GUEST_COPY[DEFAULT_LOCALE];
  const saved = data.guestCopy?.[editing] ?? {};

  function placeholderFor(key: keyof GuestCopy) {
    const value = builtIn[key];
    return Array.isArray(value) ? value.join(" | ") : String(value ?? "");
  }
  function valueFor(key: keyof GuestCopy) {
    return draft[key] ?? saved[key] ?? "";
  }

  async function addLocale(code: string) {
    if (!code) return;
    setBusy(true);
    setError(null);
    const ok = await postAction("updateSettings", { settings: { enabledLocales: [...enabled, code] } });
    setBusy(false);
    if (!ok) { setError("Jezik nije dodat. Pokušajte ponovo."); return; }
    setAdding("");
    setEditing(code);
    setToast(`Jezik ${localeInfo(code).native} je dodat.`);
  }

  async function removeLocale(code: string) {
    setBusy(true);
    setError(null);
    const ok = await postAction("removeGuestLocale", { locale: code });
    setBusy(false);
    if (!ok) { setError("Jezik nije uklonjen."); return; }
    if (editing === code) setEditing(enabled.filter((entry) => entry !== code)[0] ?? DEFAULT_LOCALE);
    setToast(`Jezik ${localeInfo(code).native} je uklonjen.`);
  }

  async function save() {
    if (!Object.keys(draft).length) { setToast("Nema izmena za čuvanje."); return; }
    setBusy(true);
    setError(null);
    const ok = await postAction("saveGuestCopy", { locale: editing, entries: draft });
    setBusy(false);
    if (!ok) { setError("Prevod nije sačuvan."); return; }
    setDraft({});
    setToast(`Prevod za ${localeInfo(editing).native} je sačuvan.`);
  }

  return <div className="language-manager">
    <PageHeading
      eyebrow="Jezici gostiju"
      title="Jezici i prevodi"
      description="Sa jednim jezikom gost ne vidi izbornik — meni se otvara odmah. Čim dodate drugi, gost prvo bira jezik."
      action={<div className="button-group">
        <select value={adding} disabled={busy || !available.length} onChange={(event) => void addLocale(event.target.value)}>
          <option value="">{available.length ? "＋ Dodaj jezik" : "Svi jezici su dodati"}</option>
          {available.map((entry) => <option key={entry.code} value={entry.code}>{entry.native}</option>)}
        </select>
        <button className="primary-button" disabled={busy || !Object.keys(draft).length} onClick={() => void save()}>
          {busy ? "Čuvanje…" : "Sačuvaj prevod"}
        </button>
      </div>}
    />

    <div className="language-tabs">
      {enabled.map((code) => <button
        key={code}
        className={editing === code ? "active" : ""}
        onClick={() => { setEditing(code); setDraft({}); }}
      >
        <strong>{localeInfo(code).native}</strong>
        <small>{localeInfo(code).short}{GUEST_COPY[code] ? "" : " · bez ugrađenog prevoda"}</small>
        {enabled.length > 1 && <em role="button" tabIndex={0} title="Ukloni jezik"
          onClick={(event) => { event.stopPropagation(); void removeLocale(code); }}
          onKeyDown={(event) => { if (event.key === "Enter") { event.stopPropagation(); void removeLocale(code); } }}
        >×</em>}
      </button>)}
    </div>

    {error && <div className="auth-error" role="alert">{error}</div>}
    <p className="language-hint">
      Prazno polje znači da se koristi tekst ispod njega. {GUEST_COPY[editing]
        ? "MeniFlow već ima prevod za ovaj jezik — menjajte samo ono što želite drugačije."
        : `Za ovaj jezik MeniFlow nema ugrađen prevod, pa se prikazuje ${localeInfo(data.restaurant.defaultLocale).native} dok ne unesete svoj.`}
    </p>

    {COPY_FIELDS.map((section) => <article className="panel language-group" key={section.group}>
      <header><h2>{section.group}</h2></header>
      <div className="language-fields">
        {section.keys.map(([key, label]) => <label key={String(key)}>
          <span>{label}</span>
          <input
            value={valueFor(key)}
            maxLength={400}
            placeholder={placeholderFor(key)}
            onChange={(event) => setDraft((current) => ({ ...current, [key]: event.target.value }))}
          />
        </label>)}
      </div>
    </article>)}
  </div>;
}

function LegacyMenuItemModal({ data, existing, onClose, onSave }: { data: RestaurantState; existing: MenuItem | null; onClose: () => void; onSave: (item: MenuItem, file: File | null) => Promise<boolean> }) {
  const [name, setName] = useState(existing?.name ?? "");
  const [category, setCategory] = useState(existing?.category ?? "Glavna jela");
  const [description, setDescription] = useState(existing?.description ?? "");
  const [portion, setPortion] = useState(existing?.portion ?? "300 g");
  const [price, setPrice] = useState(existing?.price.toString() ?? "");
  const [tags, setTags] = useState(existing?.tags.join(", ") ?? "");
  const [available, setAvailable] = useState(existing?.available ?? true);
  const [featured, setFeatured] = useState(existing?.featured ?? false);
  const [selectedFile, setSelectedFile] = useState<File | null>(null);
  const [saving, setSaving] = useState(false);

  async function submit(event: FormEvent) {
    event.preventDefault();
    if (!name.trim() || !Number(price)) return;
    setSaving(true);
    const saved = await onSave({
      id: existing?.id ?? `item-${Date.now()}`,
      name: name.trim(), category, description: description.trim(), portion: portion.trim(),
      price: Number(price), available, featured, image: existing?.image, imageAssetId: existing?.imageAssetId, persisted: existing?.persisted,
      tags: tags.split(",").map((tag) => tag.trim()).filter(Boolean), allergens: existing?.allergens ?? [],
      categoryId: existing?.categoryId ?? data.categoryDetails?.find((entry) => entry.name === category)?.id ?? data.items.find((item) => item.category === category)?.categoryId,
      variantId: existing?.variantId,
    }, selectedFile);
    if (!saved) setSaving(false);
  }

  return <div className="modal-backdrop" role="presentation" onMouseDown={(event) => event.target === event.currentTarget && onClose()}><form className="side-modal item-form" onSubmit={submit}><div className="modal-heading"><div><p className="eyebrow">{existing ? "Izmena ponude" : "Novi artikal"}</p><h2>{existing ? "Uredi artikal" : "Dodajte novi artikal"}</h2><p>Fotografija i dodatne oznake nisu obavezne.</p></div><button type="button" onClick={onClose}>×</button></div><div className="form-grid"><label className="full">Naziv artikla *<input autoFocus value={name} onChange={(event) => setName(event.target.value)} placeholder="npr. Teleći medaljoni" /></label><label>Kategorija *<select value={category} onChange={(event) => setCategory(event.target.value)}>{data.categories.filter((entry) => entry !== "Sve" && entry !== "Preporučujemo").map((entry) => <option key={entry}>{entry}</option>)}</select></label><label>Cena ({data.restaurant.currency}) *<input inputMode="decimal" value={price} onChange={(event) => setPrice(event.target.value)} placeholder="890" /></label><label className="full">Opis<textarea value={description} onChange={(event) => setDescription(event.target.value)} placeholder="Kratak, privlačan opis…" rows={3} /></label><label>Porcija / količina<input value={portion} onChange={(event) => setPortion(event.target.value)} placeholder="300 g ili 0,33 l" /></label><label>Oznake<input value={tags} onChange={(event) => setTags(event.target.value)} placeholder="Ljuto, Bez glutena" /></label><label className="full upload-zone"><input type="file" accept="image/png,image/jpeg,image/webp" onChange={(event) => setSelectedFile(event.target.files?.[0] ?? null)} /><span>＋</span><strong>{selectedFile?.name || (existing?.image ? "Zamenite fotografiju" : "Otpremite fotografiju")}</strong><small>PNG, JPG ili WebP · do 5 MB · opciono</small></label></div><div className="form-toggles"><label><span><strong>Dostupno za poručivanje</strong><small>Gost može da doda artikal u porudžbinu.</small></span><input type="checkbox" checked={available} onChange={(event) => setAvailable(event.target.checked)} /></label><label><span><strong>Istaknut artikal</strong><small>Prikaži u sekciji „Preporučujemo“.</small></span><input type="checkbox" checked={featured} onChange={(event) => setFeatured(event.target.checked)} /></label></div><footer><button type="button" className="secondary-button" onClick={onClose} disabled={saving}>Otkaži</button><button className="primary-button" type="submit" disabled={saving}>{saving ? "Čuvanje…" : existing ? "Sačuvaj izmene" : "Dodaj u meni"}</button></footer></form></div>;
}

function TablesManager({ data, postAction, setToast }: SharedProps) {
  const [selectedId, setSelectedId] = useState(data.tables[0]?.id);
  const [editing, setEditing] = useState<DiningTable | null | undefined>(undefined);
  const [query, setQuery] = useState("");
  const [printMode, setPrintMode] = useState<"single" | "all" | null>(null);
  const [showZones, setShowZones] = useState(false);
  const selected = data.tables.find((table) => table.id === selectedId) ?? data.tables[0];
  const visibleTables = data.tables.filter((table) => `${table.number} ${table.label} ${table.zone} ${table.waiter?.name ?? ""}`.toLowerCase().includes(query.toLowerCase()));
  // Managed zones first and in their configured order; anything a table still
  // carries but the zone list does not know about is appended so it stays visible.
  const managedZones = [...data.zones].sort((a, b) => a.sortOrder - b.sortOrder || a.name.localeCompare(b.name, "sr")).map((zone) => zone.name);
  const zones = [
    ...managedZones.filter((name) => visibleTables.some((table) => table.zone === name)),
    ...[...new Set(visibleTables.map((table) => table.zone))].filter((name) => !managedZones.includes(name)).sort((a, b) => a.localeCompare(b, "sr")),
  ];
  const activeTables = data.tables.filter((table) => table.active);

  useEffect(() => {
    if (!printMode) return;
    const reset = () => setPrintMode(null);
    window.addEventListener("afterprint", reset, { once: true });
    const timer = window.setTimeout(() => window.print(), 80);
    return () => {
      window.clearTimeout(timer);
      window.removeEventListener("afterprint", reset);
    };
  }, [printMode]);

  function qrUrl(table: DiningTable) {
    return `/api/demo/qr?slug=${encodeURIComponent(data.restaurant.slug)}&tableId=${encodeURIComponent(table.id)}`;
  }

  return <div className={`tables-manager print-${printMode ?? "none"}`}>
    <PageHeading eyebrow="Fizički prostor" title="Stolovi, lokacije i QR kodovi" description="Uredite svaki sto, njegovu lokaciju i status, pa odštampajte sve QR kodove na A4." action={<div className="button-group"><button className="secondary-button" onClick={() => setShowZones(true)}>Uredi rejone</button><button className="secondary-button" onClick={() => setPrintMode("all")} disabled={!activeTables.length}>Odštampaj sve na A4</button><button className="primary-button" onClick={() => setEditing(null)}>＋ Novi sto</button></div>} />
    <div className="table-stats"><span><b>{data.tables.length}</b> ukupno stolova</span><span><b>{activeTables.length}</b> aktivnih QR kodova</span><span><b>{new Set(data.tables.map((table) => table.zone)).size}</b> lokacija</span></div>
    <section className="tables-layout">
      <article className="panel tables-list"><div className="panel-heading"><div><h2>Svi stolovi</h2><p>Lokacije mogu biti Sala, Terasa, Bašta, Bar, VIP ili bilo koji vaš naziv.</p></div><label className="small-search">⌕ <input value={query} onChange={(event) => setQuery(event.target.value)} placeholder="Pronađi sto ili lokaciju" /></label></div>{zones.map((zone) => <div key={zone}><div className="zone-label">{zone} <span>{visibleTables.filter((table) => table.zone === zone).length}</span></div><div className="table-card-grid">{visibleTables.filter((table) => table.zone === zone).map((table) => <EditableTableCard key={table.id} table={table} selected={selected?.id === table.id} onSelect={() => setSelectedId(table.id)} onEdit={() => setEditing(table)} />)}</div></div>)}{!visibleTables.length && <div className="guest-empty">Nema stolova za ovu pretragu.</div>}</article>
      {selected && <aside className="panel qr-preview"><div className="qr-header"><span>Pregled kartice</span><button onClick={() => setEditing(selected)}>Uredi sto</button></div><QrPrintCard restaurant={data.restaurant} table={selected} qrUrl={qrUrl(selected)} /><div className="qr-actions"><button className="primary-button" onClick={() => setPrintMode("single")}>Odštampaj karticu</button><a className="secondary-button" href={qrUrl(selected)} download={`qr-sto-${selected.number}.png`} target="_blank" rel="noreferrer">Preuzmi PNG</a></div><p className="secure-note"><span>✓</span> Kod koristi siguran token. Isključivanjem stola QR odmah prestaje da prima porudžbine.</p></aside>}
    </section>
    <section className="qr-print-sheet" aria-hidden="true">{activeTables.map((table) => <QrPrintCard key={table.id} restaurant={data.restaurant} table={table} qrUrl={qrUrl(table)} compact />)}</section>
    {selected && <section className="single-print-sheet" aria-hidden="true"><QrPrintCard restaurant={data.restaurant} table={selected} qrUrl={qrUrl(selected)} /></section>}
    {showZones && <ZoneManagerModal data={data} postAction={postAction} setToast={setToast} onClose={() => setShowZones(false)} />}
    {editing !== undefined && <TableEditorModal data={data} existing={editing} postAction={postAction} setToast={setToast} onClose={() => setEditing(undefined)} />}
  </div>;
}

// Rejoni: balkon, sprat, bašta… Tables carry the zone by name, so a rename here
// travels to every table in that zone (handled server-side).
function ZoneManagerModal({ data, postAction, setToast, onClose }: Pick<SharedProps, "data" | "postAction" | "setToast"> & { onClose: () => void }) {
  const [name, setName] = useState("");
  const [editingId, setEditingId] = useState<string | null>(null);
  const [editingName, setEditingName] = useState("");
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const zones = [...data.zones].sort((a, b) => a.sortOrder - b.sortOrder || a.name.localeCompare(b.name, "sr"));

  function tableCount(zoneName: string) {
    return data.tables.filter((table) => table.zone === zoneName).length;
  }

  async function run(action: string, payload: Record<string, unknown>, okMessage: string) {
    setBusy(true);
    setError(null);
    const result = await postAction(action, payload);
    setBusy(false);
    if (!result) {
      setError("Izmena nije sačuvana. Pokušajte ponovo.");
      return false;
    }
    setToast(okMessage);
    return true;
  }

  async function addZone() {
    const value = name.trim();
    if (value.length < 2) { setError("Naziv rejona mora imati bar 2 znaka."); return; }
    if (await run("saveZone", { name: value }, `Rejon „${value}" je dodat.`)) setName("");
  }

  async function renameZone(zoneId: string) {
    const value = editingName.trim();
    if (value.length < 2) { setError("Naziv rejona mora imati bar 2 znaka."); return; }
    if (await run("saveZone", { zoneId, name: value }, `Rejon je preimenovan u „${value}".`)) {
      setEditingId(null);
    }
  }

  return <div className="modal-backdrop">
    <div className="side-modal zone-modal">
      <div className="modal-heading">
        <div><p className="eyebrow">Fizički prostor</p><h2>Rejoni</h2><p>Balkon, sprat, bašta, bar… Svaki sto pripada jednom rejonu, a osoblje po njima filtrira zahteve.</p></div>
        <button onClick={onClose} aria-label="Zatvori">×</button>
      </div>

      <div className="zone-add">
        <input value={name} onChange={(event) => setName(event.target.value)} placeholder="Naziv novog rejona" onKeyDown={(event) => { if (event.key === "Enter") { event.preventDefault(); void addZone(); } }} />
        <button className="primary-button" onClick={() => void addZone()} disabled={busy}>＋ Dodaj</button>
      </div>
      {error && <div className="auth-error" role="alert">{error}</div>}

      <div className="zone-list">
        {zones.map((zone, index) => <div key={zone.id}>
          {editingId === zone.id
            ? <>
                <input value={editingName} onChange={(event) => setEditingName(event.target.value)} autoFocus />
                <button className="primary-button" disabled={busy} onClick={() => void renameZone(zone.id)}>Sačuvaj</button>
                <button className="secondary-button" disabled={busy} onClick={() => setEditingId(null)}>Otkaži</button>
              </>
            : <>
                <span><strong>{zone.name}</strong><small>{tableCount(zone.name)} {tableCount(zone.name) === 1 ? "sto" : "stolova"}</small></span>
                <div className="zone-order">
                  <button disabled={busy || index === 0} onClick={() => void run("reorderZone", { zoneId: zone.id, direction: "up" }, "Redosled rejona je promenjen.")} aria-label="Gore">▲</button>
                  <button disabled={busy || index === zones.length - 1} onClick={() => void run("reorderZone", { zoneId: zone.id, direction: "down" }, "Redosled rejona je promenjen.")} aria-label="Dole">▼</button>
                </div>
                <button className="secondary-button" disabled={busy} onClick={() => { setEditingId(zone.id); setEditingName(zone.name); setError(null); }}>Preimenuj</button>
                <button className="danger-button" disabled={busy || tableCount(zone.name) > 0} title={tableCount(zone.name) > 0 ? "Prvo prebacite stolove u drugi rejon" : "Obriši rejon"} onClick={() => void run("deleteZone", { zoneId: zone.id }, `Rejon „${zone.name}" je obrisan.`)}>Obriši</button>
              </>}
        </div>)}
        {!zones.length && <p className="staff-order-empty">Još nema rejona. Dodajte prvi iznad.</p>}
      </div>

      <p className="secure-note"><span>i</span> Preimenovanje rejona automatski prebacuje sve njegove stolove. Rejon sa stolovima ne može da se obriše dok se stolovi ne prebace.</p>
    </div>
  </div>;
}

function EditableTableCard({ table, selected, onSelect, onEdit }: { table: DiningTable; selected: boolean; onSelect: () => void; onEdit: () => void }) {
  return <article className={`table-card editable ${selected ? "selected" : ""}`}><button className="table-card-main" onClick={onSelect}><span className="table-number">{table.number.toString().padStart(2, "0")}</span><span><strong>{table.label || `Sto ${table.number}`}</strong><small><i className={table.active ? "active" : ""} /> {table.active ? "QR aktivan" : "Pauziran"} · {table.seats} mesta</small>{table.waiter && <em className={`table-waiter ${table.claimedEmail ? "claimed" : ""}`}>{table.claimedEmail ? "preuzeo" : "konobar"}: {table.waiter.name}</em>}</span></button><button className="table-edit-button" onClick={onEdit}>Uredi</button></article>;
}

function TableEditorModal({ data, existing, postAction, setToast, onClose }: { data: RestaurantState; existing: DiningTable | null; postAction: SharedProps["postAction"]; setToast: SharedProps["setToast"]; onClose: () => void }) {
  const nextNumber = Math.max(0, ...data.tables.map((table) => table.number)) + 1;
  const [number, setNumber] = useState(existing?.number ?? nextNumber);
  const [label, setLabel] = useState(existing?.label ?? `Sto ${nextNumber}`);
  const [zone, setZone] = useState(existing?.zone ?? "Sala");
  const [seats, setSeats] = useState(existing?.seats ?? 4);
  const [active, setActive] = useState(existing?.active ?? true);
  const [waiterEmail, setWaiterEmail] = useState(existing?.assignedEmail ?? "");
  const [saving, setSaving] = useState(false);
  const knownZones = knownZoneNames(data);
  // A brand-new zone can still be created from here, but the normal path is
  // picking one that already exists.
  const [newZone, setNewZone] = useState(false);

  async function submit(event: FormEvent) {
    event.preventDefault();
    if (!label.trim() || !zone.trim()) return;
    setSaving(true);
    const table: DiningTable = {
      id: existing?.id ?? `table-${Date.now()}`,
      number,
      label: label.trim(),
      zone: zone.trim(),
      seats,
      token: existing?.token ?? "",
      active,
    };
    const saved = await postAction(existing ? "updateTable" : "createTable", { table });
    if (!saved) { setSaving(false); return; }
    // createTable mints its own id, so read the assignment target back from the
    // refreshed state rather than trusting the placeholder id above.
    const savedId = existing?.id
      ?? (saved.state as any)?.tables?.find((entry: any) => Number(entry.tableNumber) === number)?.id;
    if (savedId && (waiterEmail || existing?.assignedEmail)) {
      await postAction("assignTable", { tableId: savedId, email: waiterEmail || null });
    }
    setSaving(false);
    setToast(existing ? `Sto ${number} je sačuvan.` : `Sto ${number} i njegov QR kod su kreirani.`);
    onClose();
  }

  return <div className="modal-backdrop" role="presentation" onMouseDown={(event) => event.target === event.currentTarget && onClose()}><form className="compact-modal table-editor-modal" onSubmit={submit}><div className="modal-heading"><div><p className="eyebrow">{existing ? "Izmena stola" : "Novi sto"}</p><h2>{existing ? `Uredi sto ${existing.number}` : "Dodajte sto"}</h2><p>Rejon birate iz spiska, a nov možete dodati kroz „＋ Nov rejon“.</p></div><button type="button" onClick={onClose}>×</button></div><div className="form-grid"><label>Broj stola<input type="number" min="1" max="999" value={number} onChange={(event) => setNumber(Number(event.target.value))} required /></label><label>Broj mesta<input type="number" min="1" max="30" value={seats} onChange={(event) => setSeats(Number(event.target.value))} required /></label><label className="full">Naziv koji vidi osoblje<input value={label} maxLength={80} onChange={(event) => setLabel(event.target.value)} placeholder="npr. Sto 7 pored prozora" required /></label><label className="full">Rejon{knownZones.length > 0 && !newZone ? <><select value={zone} onChange={(event) => { if (event.target.value === "__new") { setNewZone(true); setZone(""); } else setZone(event.target.value); }} required>{!knownZones.includes(zone) && <option value="">Izaberite rejon</option>}{knownZones.map((entry) => <option key={entry} value={entry}>{entry}</option>)}<option value="__new">＋ Nov rejon…</option></select></> : <><input value={zone} maxLength={60} onChange={(event) => setZone(event.target.value)} placeholder="npr. Bašta" autoFocus={newZone} required />{knownZones.length > 0 && <button type="button" className="text-button" onClick={() => { setNewZone(false); setZone(knownZones[0]); }}>Izaberi postojeći rejon</button>}</>}</label><label className="full">Konobar<select value={waiterEmail} onChange={(event) => setWaiterEmail(event.target.value)}><option value="">Bez zaduženog konobara</option>{data.team.map((member) => <option key={member.email} value={member.email}>{member.displayName || member.email}{member.role === "staff" ? "" : member.role === "owner" ? " (vlasnik)" : " (menadžer)"}</option>)}</select></label></div><label className="table-active-row"><span><strong>QR kod je aktivan</strong><small>Isključite ga ako sto privremeno ne prima zahteve.</small></span><input type="checkbox" checked={active} onChange={(event) => setActive(event.target.checked)} /></label><footer className="modal-actions"><button type="button" className="secondary-button" onClick={onClose} disabled={saving}>Otkaži</button><button className="primary-button" disabled={saving}>{saving ? "Čuvanje…" : "Sačuvaj sto"}</button></footer></form></div>;
}

function QrPrintCard({ restaurant, table, qrUrl, compact = false }: { restaurant: RestaurantState["restaurant"]; table: DiningTable; qrUrl: string; compact?: boolean }) {
  return <div className={`print-card ${compact ? "compact" : ""}`}><BrandMark restaurant={restaurant} className="mini-logo" /><h3>{restaurant.name}</h3><p>Skenirajte za meni<br />i poručivanje</p><div className="qr-image"><img src={qrUrl} alt={`QR kod za sto ${table.number}`} /></div><small>VAŠ STO</small><strong>{table.number.toString().padStart(2, "0")}</strong><em>{table.zone}</em></div>;
}

function LegacyTablesManager({ data, setData, postAction, setToast }: SharedProps) {
  const [selectedId, setSelectedId] = useState(data.tables.find((table) => table.number === 12)?.id ?? data.tables[0]?.id);
  const [showAdd, setShowAdd] = useState(false);
  const selected = data.tables.find((table) => table.id === selectedId) ?? data.tables[0];
  const qrUrl = selected
    ? `/api/demo/qr?slug=${encodeURIComponent(data.restaurant.slug)}&tableId=${encodeURIComponent(selected.id)}`
    : "";

  async function addTable(zone: string) {
    const number = Math.max(0, ...data.tables.map((table) => table.number)) + 1;
    const table: DiningTable = { id: `table-${Date.now()}`, number, label: `Sto ${number}`, zone, seats: 4, token: `${data.restaurant.slug.slice(0, 2)}-${number}-${Math.random().toString(16).slice(2, 6)}`, active: true };
    if (await postAction("createTable", { table })) {
      setShowAdd(false);
      setToast(`Sto ${number} i njegov QR kod su kreirani.`);
    }
  }

  return <>
    <PageHeading eyebrow="Fizički prostor" title="Stolovi i QR kodovi" description="Kreirajte siguran QR kod za svaki sto i zonu restorana." action={<div className="button-group"><button className="secondary-button">Preuzmi sve <span>↓</span></button><button className="primary-button" onClick={() => setShowAdd(true)}>＋ Novi sto</button></div>} />
    <div className="table-stats"><span><b>{data.tables.length}</b> ukupno stolova</span><span><b>{data.tables.filter((table) => table.active).length}</b> aktivnih QR kodova</span><span><b>{new Set(data.tables.map((table) => table.zone)).size}</b> zone</span></div>
    <section className="tables-layout">
      <article className="panel tables-list"><div className="panel-heading"><div><h2>Svi stolovi</h2><p>Kliknite na sto za pregled QR koda.</p></div><label className="small-search">⌕ <input placeholder="Pronađi sto" /></label></div><div className="zone-label">Sala <span>{data.tables.filter((table) => table.zone === "Sala").length}</span></div><div className="table-card-grid">{data.tables.filter((table) => table.zone === "Sala").map((table) => <TableCard key={table.id} table={table} selected={selected?.id === table.id} onClick={() => setSelectedId(table.id)} />)}</div>{["Terasa", "Bašta"].map((zone) => data.tables.some((table) => table.zone === zone) && <div key={zone}><div className="zone-label">{zone} <span>{data.tables.filter((table) => table.zone === zone).length}</span></div><div className="table-card-grid">{data.tables.filter((table) => table.zone === zone).map((table) => <TableCard key={table.id} table={table} selected={selected?.id === table.id} onClick={() => setSelectedId(table.id)} />)}</div></div>)}</article>
      {selected && <aside className="panel qr-preview"><div className="qr-header"><span>Pregled kartice</span><button>•••</button></div><div className="print-card"><div className="mini-logo">{initials(data.restaurant.name)}</div><h3>{data.restaurant.name}</h3><p>Skenirajte za meni<br />i poručivanje</p><div className="qr-image"><img src={qrUrl} alt={`QR kod za sto ${selected.number}`} /></div><small>VAŠ STO</small><strong>{selected.number.toString().padStart(2, "0")}</strong><em>{selected.zone}</em></div><div className="qr-actions"><button className="primary-button" onClick={() => window.print()}>Odštampaj karticu</button><a className="secondary-button" href={qrUrl} download={`qr-sto-${selected.number}.png`} target="_blank" rel="noreferrer" onClick={() => setToast("QR kod je otvoren za preuzimanje.")}>Preuzmi PNG</a></div><p className="secure-note"><span>✓</span> Kod koristi siguran token. Broj stola nije izložen u linku.</p></aside>}
    </section>
    {showAdd && <div className="modal-backdrop"><div className="compact-modal"><div className="modal-heading"><div><p className="eyebrow">Novi sto</p><h2>Izaberite zonu</h2></div><button onClick={() => setShowAdd(false)}>×</button></div><div className="zone-choice">{["Sala", "Terasa", "Bašta", "VIP"].map((zone) => <button key={zone} onClick={() => addTable(zone)}><span>▦</span>{zone}<small>Sto {Math.max(0, ...data.tables.map((table) => table.number)) + 1}</small></button>)}</div></div></div>}
  </>;
}

function TableCard({ table, selected, onClick }: { table: DiningTable; selected: boolean; onClick: () => void }) {
  return <button className={`table-card ${selected ? "selected" : ""}`} onClick={onClick}><span className="table-number">{table.number.toString().padStart(2, "0")}</span><span><strong>Sto {table.number}</strong><small><i className={table.active ? "active" : ""} /> {table.active ? "QR aktivan" : "Pauziran"}</small></span><b>›</b></button>;
}

function BrandingManager({ data, setData, postAction, setToast, account }: SharedProps & { account: AuthUser | null }) {
  const brand = data.restaurant.brand;
  const [logoFile, setLogoFile] = useState<File | null>(null);
  const [logoRemoved, setLogoRemoved] = useState(false);
  const [saving, setSaving] = useState(false);
  function update(patch: Partial<Brand>) { setData((current) => ({ ...current, restaurant: { ...current.restaurant, brand: { ...current.restaurant.brand, ...patch } } })); }
  function updateName(name: string) { setData((current) => ({ ...current, restaurant: { ...current.restaurant, name } })); }

  async function save() {
    if (saving) return;
    setSaving(true);
    let logoAssetId: string | null | undefined = logoRemoved ? null : data.restaurant.logoAssetId;
    if (logoFile) {
      if (logoFile.size > 5 * 1024 * 1024) {
        setToast("Logo može imati najviše 5 MB.");
        setSaving(false);
        return;
      }
      try {
        const form = new FormData();
        form.append("slug", data.restaurant.slug);
        form.append("kind", "logo");
        form.append("altText", `Logo ${data.restaurant.name}`);
        form.append("file", logoFile);
        const response = await fetch("/api/demo/upload", { method: "POST", body: form, credentials: "same-origin" });
        const result = await response.json().catch(() => null);
        if (!response.ok || !result?.asset) throw new Error("upload_failed");
        logoAssetId = result.asset.id;
        setData((current) => ({ ...current, restaurant: { ...current.restaurant, logoAssetId: result.asset.id, logoUrl: result.asset.url } }));
      } catch {
        setToast("Logo nije sačuvan. Proverite format i pokušajte ponovo.");
        setSaving(false);
        return;
      }
    }
    const saved = await postAction("updateBranding", { brand: data.restaurant.brand, displayName: data.restaurant.name, logoAssetId });
    setSaving(false);
    if (saved) {
      setLogoFile(null);
      setLogoRemoved(false);
      setToast("Brending i logo su objavljeni na stranici gosta.");
    }
  }

  function removeLogo() {
    setLogoFile(null);
    setLogoRemoved(true);
    setData((current) => ({ ...current, restaurant: { ...current.restaurant, logoAssetId: undefined, logoUrl: undefined } }));
  }

  return <><PageHeading eyebrow="Vizuelni identitet" title="Brendiranje" description="Prilagodite boje, tipografiju i logo koji gosti vide na telefonu i QR karticama." action={<div className="button-group"><button className="secondary-button" onClick={() => update(bistroFallback.restaurant.brand)}>Vrati boje</button><button className="primary-button" onClick={save} disabled={saving}>{saving ? "Objavljivanje…" : "Objavi izmene"}</button></div>} /><section className="branding-layout"><article className="panel brand-controls"><div className="brand-section"><h2>Logo i naziv restorana</h2><p>Možete koristiti PNG, JPG ili WebP fotografiju do 5 MB.</p><div className="brand-logo-row"><BrandMark restaurant={data.restaurant} className="brand-logo-preview" /><div className="brand-logo-actions"><label className="secondary-button">{logoFile ? logoFile.name : "Izaberi logo"}<input type="file" accept="image/png,image/jpeg,image/webp" onChange={(event) => { setLogoFile(event.target.files?.[0] ?? null); setLogoRemoved(false); }} /></label>{(data.restaurant.logoUrl || logoFile) && <button className="text-button danger" type="button" onClick={removeLogo}>Ukloni logo</button>}</div></div><div className="brand-name-row"><BrandMark restaurant={data.restaurant} /><label>Prikazani naziv<input value={data.restaurant.name} minLength={2} maxLength={80} onChange={(event) => updateName(event.target.value)} /></label></div></div><div className="brand-section"><h2>Paleta boja</h2><p>Izaberite boje koje će gost videti.</p><div className="color-grid"><ColorField label="Primarna" value={brand.primary} onChange={(value) => update({ primary: value })} /><ColorField label="Akcentna" value={brand.accent} onChange={(value) => update({ accent: value })} /><ColorField label="Pozadina" value={brand.background} onChange={(value) => update({ background: value })} /><ColorField label="Tekst" value={brand.text} onChange={(value) => update({ text: value })} /></div><div className="contrast-ok"><span>✓</span><p><strong>Pregled kontrasta</strong>Proverite mobilni izgled sa desne strane pre objave.</p></div></div><div className="brand-section"><h2>Stil interfejsa</h2><div className="style-options"><FontPicker brand={brand} update={update} canManage={Boolean(account?.isPlatformAdmin)} /><label>Zaobljenost kartica<input type="range" min="8" max="28" value={brand.radius} onChange={(event) => update({ radius: Number(event.target.value) })} /><span>{brand.radius}px</span></label></div></div></article><PhonePreview data={data} /></section></>;
}

type UploadedFont = { family: string; faces: FontFace[] };

// Typography picker: the three built-in stacks, plus every font found in
// storage/fonts on the server. The owner of the installation can drop a file in
// from here as well as through cPanel File Manager — both land in the same
// folder, and that folder survives every update.
function FontPicker({
  brand,
  update,
  canManage,
}: {
  brand: Brand;
  update: (changes: Partial<Brand>) => void;
  canManage: boolean;
}) {
  const [fonts, setFonts] = useState<UploadedFont[]>([]);
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    let active = true;
    fetch("/api/fonts", { cache: "no-store", credentials: "same-origin" })
      .then((response) => response.json())
      .then((payload) => {
        if (active && payload?.ok) setFonts(payload.families as UploadedFont[]);
      })
      .catch(() => undefined);
    return () => { active = false; };
  }, []);

  function choose(value: string) {
    if (value === "modern" || value === "classic" || value === "soft") {
      update({ font: value, fontFamily: undefined, fontFaces: undefined });
      return;
    }
    const found = fonts.find((entry) => entry.family === value);
    if (found) update({ font: "custom", fontFamily: found.family, fontFaces: found.faces });
  }

  async function upload(file: File) {
    setBusy(true);
    setError(null);
    const form = new FormData();
    form.append("file", file);
    const response = await fetch("/api/fonts", { method: "POST", credentials: "same-origin", body: form });
    const payload = await response.json().catch(() => null);
    setBusy(false);
    if (!response.ok || !payload?.ok) {
      setError(payload?.error?.message ?? "Otpremanje fonta nije uspelo.");
      return;
    }
    const families = payload.families as UploadedFont[];
    setFonts(families);
    // Pick it straight away: uploading a font and then having to find it in the
    // list would be a pointless second step.
    const added = families.find((entry) => entry.family === payload.family);
    if (added) update({ font: "custom", fontFamily: added.family, fontFaces: added.faces });
  }

  async function remove(family: string) {
    if (!window.confirm(`Obrisati font „${family}" sa servera? Restorani koji ga koriste vraćaju se na podrazumevani font.`)) return;
    setBusy(true);
    setError(null);
    const response = await fetch("/api/fonts", {
      method: "DELETE",
      credentials: "same-origin",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ family }),
    });
    const payload = await response.json().catch(() => null);
    setBusy(false);
    if (!response.ok || !payload?.ok) {
      setError(payload?.error?.message ?? "Brisanje fonta nije uspelo.");
      return;
    }
    setFonts(payload.families as UploadedFont[]);
    if (brand.fontFamily === family) {
      update({ font: "modern", fontFamily: undefined, fontFaces: undefined });
    }
  }

  return <div className="font-picker">
    <label>Tipografija
      <select
        value={brand.font === "custom" ? brand.fontFamily ?? "modern" : brand.font}
        onChange={(event) => choose(event.target.value)}
      >
        <option value="modern">Moderan sans</option>
        <option value="classic">Klasičan serif</option>
        <option value="soft">Mekani sans</option>
        {fonts.length > 0 && <optgroup label="Vaši fontovi">
          {fonts.map((entry) => <option key={entry.family} value={entry.family}>{entry.family} ({entry.faces.length})</option>)}
        </optgroup>}
      </select>
    </label>
    {brand.font === "custom" && brand.fontFamily && <p
      className="font-sample"
      style={{ fontFamily: `"${brand.fontFamily}", system-ui, sans-serif` }}
    >
      Šnjegav đačić 0123 · <strong>podebljano</strong>
    </p>}
    {canManage && <div className="font-manage">
      <label className="font-upload">
        <input
          type="file"
          accept=".woff2,.woff,.ttf,.otf,font/woff2,font/woff,font/ttf,font/otf"
          onChange={(event) => {
            const file = event.target.files?.[0];
            if (file) void upload(file);
          }}
        />
        <span>{busy ? "Otpremanje…" : "＋ Otpremi font"}</span>
      </label>
      {fonts.length > 0 && <ul>
        {fonts.map((entry) => <li key={entry.family}>
          <span>{entry.family}</span>
          <button type="button" className="text-button danger" disabled={busy} onClick={() => void remove(entry.family)}>Obriši</button>
        </li>)}
      </ul>}
      <small>
        Fontovi se čuvaju u <code>storage/fonts</code> i ostaju posle ažuriranja.
        Možete ih otpremiti i preko cPanel File Manager-a: jedan fajl po familiji
        (<code>Poppins.woff2</code>) ili folder sa svim rezovima
        (<code>Lora/Lora-Regular.woff2</code>, <code>Lora/Lora-Bold.woff2</code>).
        WOFF2, WOFF, TTF ili OTF, do 2 MB.
      </small>
    </div>}
    {error && <div className="auth-error" role="alert">{error}</div>}
  </div>;
}

function ColorField({ label, value, onChange }: { label: string; value: string; onChange: (value: string) => void }) {
  return <label>{label}<span className="color-input"><input type="color" value={value} onChange={(event) => onChange(event.target.value)} /><input value={value.toUpperCase()} onChange={(event) => /^#[0-9A-Fa-f]{0,6}$/.test(event.target.value) && onChange(event.target.value)} /></span></label>;
}

function PhonePreview({ data }: { data: RestaurantState }) {
  return <aside className="brand-preview"><div className="preview-label"><span>Pregled uživo</span><em>Mobilni prikaz</em></div><div className="phone-frame"><div className="phone-speaker" /><div className="preview-app"><header><BrandMark restaurant={data.restaurant} /><div><strong>{data.restaurant.name}</strong><small>Sto 12 · SR</small></div></header><div className="preview-hero"><p>Dobro veče</p><h3>Šta vam se jede?</h3><label>⌕ Pretražite meni</label></div><div className="preview-pills"><b>Preporuka</b><span>Glavna jela</span><span>Pića</span></div><div className="preview-food-grid">{data.items.filter((item) => item.available).slice(0, 2).map((item) => <div key={item.id}><FoodThumb item={item} large /><strong>{item.name}</strong><p>{item.portion}</p><span>{money(item.price, data.restaurant.currency)}</span><button>＋</button></div>)}</div><button className="preview-call"><span>◔</span><b>Pozovi konobara<small>Pomoć ili račun</small></b></button></div></div><p>Izmene se prikazuju odmah. Gost vidi samo objavljenu verziju.</p></aside>;
}

function LegacyPhonePreview({ data }: { data: RestaurantState }) {
  return <aside className="brand-preview"><div className="preview-label"><span>Pregled uživo</span><em>Mobilni prikaz</em></div><div className="phone-frame"><div className="phone-speaker" /><div className="preview-app"><header><span>{initials(data.restaurant.name)}</span><div><strong>{data.restaurant.name}</strong><small>Sto 12 · SR⌄</small></div><button>◔</button></header><div className="preview-hero"><p>Dobro veče</p><h3>Šta vam se jede?</h3><label>⌕ Pretražite meni</label></div><div className="preview-pills"><b>Preporuka</b><span>Glavna jela</span><span>Salate</span></div><div className="preview-food-grid">{data.items.filter((item) => item.available).slice(0, 2).map((item) => <div key={item.id}><FoodThumb item={item} large /><strong>{item.name}</strong><p>{item.portion}</p><span>{money(item.price, data.restaurant.currency)}</span><button>＋</button></div>)}</div><button className="preview-cart">Korpa · 2 stavke <strong>{money(1250, data.restaurant.currency)}</strong></button></div></div><p>Izmene se prikazuju odmah. Gost vidi samo objavljenu verziju.</p></aside>;
}

function SettingsManager({ data, setData, postAction, setToast, canManageTeam }: SharedProps & { canManageTeam: boolean }) {
  const settings = data.restaurant.settings;
  function update(key: keyof Settings, value: boolean) { setData((current) => ({ ...current, restaurant: { ...current.restaurant, settings: { ...current.restaurant.settings, [key]: value } } })); }
  async function save() { if (await postAction("updateSettings", { settings })) setToast("Podešavanja restorana su sačuvana."); }
  return <>
    <PageHeading eyebrow="Način rada" title="Podešavanja" description="Kontrolišite poručivanje, red zahteva i naloge osoblja." action={<button className="primary-button" onClick={save}>Sačuvaj izmene</button>} />
    <section className="settings-layout">
      <div className="settings-nav"><button className="active" onClick={() => document.getElementById("operating-settings")?.scrollIntoView({ behavior: "smooth" })}>Rad restorana</button><button onClick={() => document.getElementById("info-settings")?.scrollIntoView({ behavior: "smooth" })}>Podaci o restoranu</button><button onClick={() => document.getElementById("sales-settings")?.scrollIntoView({ behavior: "smooth" })}>Kontakt prodaje</button><button onClick={() => document.getElementById("pos-settings")?.scrollIntoView({ behavior: "smooth" })}>Kasa / API</button><button onClick={() => document.getElementById("team-settings")?.scrollIntoView({ behavior: "smooth" })}>Nalozi i uloge</button>{canManageTeam && <button onClick={() => document.getElementById("danger-settings")?.scrollIntoView({ behavior: "smooth" })}>Opasna zona</button>}</div>
      <div className="settings-sections" id="operating-settings">
        <SettingsSection title="Poručivanje preko QR menija" description="Odredite koje akcije su dostupne gostu za stolom."><SettingRow title="Dozvoli poručivanje" description="Gost može da pošalje porudžbinu direktno iz menija." checked={settings.orderingEnabled} onChange={(value) => update("orderingEnabled", value)} /><SettingRow title="Poziv konobaru" description="Gost može da zatraži pomoć ili račun." checked={settings.waiterCallEnabled} onChange={(value) => update("waiterCallEnabled", value)} /><SettingRow title="Potvrda pre poziva konobara" description="Uključeno: gost bira razlog pa potvrđuje dugmetom „Obavesti konobara“. Isključeno: zahtev se šalje odmah klikom na razlog." checked={settings.callConfirmEnabled} onChange={(value) => update("callConfirmEnabled", value)} /><SettingRow title="Privremeno pauziraj nove porudžbine" description="Meni ostaje vidljiv, ali poručivanje nije dostupno." checked={settings.orderingPaused} onChange={(value) => update("orderingPaused", value)} warning /></SettingsSection>
        <SettingsSection title="Ekran za osoblje" description="Podesite ponašanje reda zahteva."><SettingRow title="Potvrda pre uklanjanja zahteva" description="Pre završavanja zahteva pitaj „Da li ste sigurni?“" checked={settings.confirmBeforeResolve} onChange={(value) => update("confirmBeforeResolve", value)} featured /><SettingRow title="Prikaži ukupnu cenu" description="Cena porudžbine je vidljiva na kartici zahteva." checked={settings.showTotal} onChange={(value) => update("showTotal", value)} /></SettingsSection>
        <SoundSettingsSection data={data} setData={setData} postAction={postAction} setToast={setToast} />
        <GuestChipsSection data={data} setData={setData} postAction={postAction} setToast={setToast} />
        <RestaurantInfoSection data={data} postAction={postAction} setToast={setToast} />
        <SalesContactCard />
        <div id="pos-settings"><PosIntegrationSection data={data} postAction={postAction} setToast={setToast} /></div>
        <TeamManager slug={data.restaurant.slug} enabled={canManageTeam} setToast={setToast} />
        {canManageTeam && <div id="danger-settings"><DangerZoneSection data={data} /></div>}
      </div>
    </section>
  </>;
}

// The restaurant's own details. The platform panel writes the same columns, so
// whatever the owner types here is what MeniFlow support sees.
function RestaurantInfoSection({ data, postAction, setToast }: Pick<SharedProps, "data" | "postAction" | "setToast">) {
  const restaurant = data.restaurant;
  const [form, setForm] = useState({
    ownerEmail: restaurant.ownerEmail ?? "",
    contactPhone: restaurant.contactPhone ?? "",
    address: restaurant.address ?? "",
    city: restaurant.city ?? "",
    postalCode: restaurant.postalCode ?? "",
  });
  const [saving, setSaving] = useState(false);
  const [saved, setSaved] = useState(false);

  async function save() {
    setSaving(true);
    setSaved(false);
    const ok = await postAction("updateRestaurantInfo", { info: form });
    setSaving(false);
    if (ok) {
      setSaved(true);
      setToast("Podaci o restoranu su sačuvani.");
    }
  }

  return <article className="panel settings-card" id="info-settings">
    <header>
      <h2>Podaci o restoranu</h2>
      <p>Adresa i telefon koje MeniFlow koristi za podršku i račun. Gost ih ne vidi.</p>
    </header>
    <div className="info-grid">
      <label className="wide">Kontakt email
        <input type="email" value={form.ownerEmail} maxLength={160}
          onChange={(event) => setForm((current) => ({ ...current, ownerEmail: event.target.value }))} />
      </label>
      <label>Telefon restorana
        <input value={form.contactPhone} maxLength={30} placeholder="011 234 5678"
          onChange={(event) => setForm((current) => ({ ...current, contactPhone: event.target.value }))} />
      </label>
      <label className="wide">Adresa
        <input value={form.address} maxLength={160} placeholder="Cara Dušana 12"
          onChange={(event) => setForm((current) => ({ ...current, address: event.target.value }))} />
      </label>
      <label>Grad
        <input value={form.city} maxLength={80} placeholder="Beograd"
          onChange={(event) => setForm((current) => ({ ...current, city: event.target.value }))} />
      </label>
      <label>Poštanski broj
        <input value={form.postalCode} maxLength={20} placeholder="11000"
          onChange={(event) => setForm((current) => ({ ...current, postalCode: event.target.value }))} />
      </label>
    </div>
    <div className="info-actions">
      <button className="primary-button" onClick={() => void save()} disabled={saving}>
        {saving ? "Čuvamo…" : "Sačuvaj podatke"}
      </button>
      {saved && <span className="info-saved">Sačuvano.</span>}
    </div>
  </article>;
}

type SalesContact = { name: string; phone: string; email: string; url: string; note: string };

// Filled in once by the owner of the installation on /platforma, read by every
// restaurant. A client on the basic tier should never have to hunt for who to
// call to unlock the rest.
function SalesContactCard() {
  const [contact, setContact] = useState<SalesContact | null>(null);

  useEffect(() => {
    let active = true;
    fetch("/api/sales-contact", { cache: "no-store", credentials: "same-origin" })
      .then((response) => response.json())
      .then((payload) => {
        if (active && payload?.ok) setContact(payload.contact as SalesContact);
      })
      .catch(() => undefined);
    return () => { active = false; };
  }, []);

  if (!contact) return null;
  const empty = !contact.phone && !contact.email && !contact.url;

  return <article className="panel settings-card" id="sales-settings">
    <header>
      <h2>Kontakt prodaje MeniFlow</h2>
      <p>Za nove opcije, produženje pretplate ili pomoć pri postavljanju.</p>
    </header>
    <div className="sales-card">
      {empty
        ? <p className="sales-empty">Vlasnik MeniFlow instalacije još nije uneo kontakt prodaje.</p>
        : <>
            {contact.name && <div className="sales-row"><small>Kontakt osoba</small><span>{contact.name}</span></div>}
            {contact.phone && <div className="sales-row"><small>Telefon</small><a href={`tel:${contact.phone.replace(/\s/g, "")}`}>{contact.phone}</a></div>}
            {contact.email && <div className="sales-row"><small>Email</small><a href={`mailto:${contact.email}`}>{contact.email}</a></div>}
            {contact.url && <div className="sales-row"><small>Sajt</small><a href={contact.url} target="_blank" rel="noreferrer">{contact.url}</a></div>}
            {contact.note && <p>{contact.note}</p>}
          </>}
    </div>
  </article>;
}

// Which buttons stand above the guest menu. The two built-in ones used to be
// fixed; now they can be renamed or dropped like any category.
function GuestChipsSection({ data, setData, postAction, setToast }: Pick<SharedProps, "data" | "setData" | "postAction" | "setToast">) {
  const settings = data.restaurant.settings;

  function patch(changes: Partial<Settings>) {
    setData((current) => ({
      ...current,
      restaurant: { ...current.restaurant, settings: { ...current.restaurant.settings, ...changes } },
    }));
  }

  async function save(changes: Partial<Settings>) {
    patch(changes);
    if (await postAction("updateSettings", { settings: { ...settings, ...changes } })) {
      setToast("Dugmići u meniju gosta su sačuvani.");
    }
  }

  return <article className="panel settings-card" id="chips-settings">
    <header>
      <h2>Dugmići u meniju gosta</h2>
      <p>
        Iznad menija gost vidi red dugmića. Prva dva su ugrađena — možete ih
        preimenovati ili sakriti. Ostala dugmića su vaše kategorije, koje
        dodajete i menjate u Meni &gt; Uredi kategorije.
      </p>
    </header>
    <div className="chip-rows">
      <div className="chip-row">
        <label>Naziv dugmeta za ceo meni
          <input
            type="text"
            value={settings.allChipLabel}
            maxLength={30}
            onChange={(event) => patch({ allChipLabel: event.target.value })}
            onBlur={() => { if (settings.allChipLabel.trim()) void save({ allChipLabel: settings.allChipLabel.trim() }); }}
          />
        </label>
        <label className="chip-row-toggle">
          <span>Prikaži</span>
          <input type="checkbox" checked={settings.showAllChip} onChange={(event) => void save({ showAllChip: event.target.checked })} />
        </label>
      </div>
      <div className="chip-row">
        <label>Naziv dugmeta za istaknuto
          <input
            type="text"
            value={settings.featuredChipLabel}
            maxLength={30}
            onChange={(event) => patch({ featuredChipLabel: event.target.value })}
            onBlur={() => { if (settings.featuredChipLabel.trim()) void save({ featuredChipLabel: settings.featuredChipLabel.trim() }); }}
          />
        </label>
        <label className="chip-row-toggle">
          <span>Prikaži</span>
          <input type="checkbox" checked={settings.showFeaturedChip} onChange={(event) => void save({ showFeaturedChip: event.target.checked })} />
        </label>
      </div>
      <p className="sound-hint">
        Trenutni red: {data.categories.length ? data.categories.join(" · ") : "nema dugmića"}
      </p>
    </div>
  </article>;
}

type Shift = {
  id: string;
  name: string;
  shiftDate: string;
  startsAt: string;
  endsAt: string;
  note: string;
  appliedAt: string | null;
  // A waiter's own startsAt/endsAt override the shift's span when set — that is
  // how a 6-hour person sits inside a 10-hour shift.
  waiters: { email: string; name: string; tableCount: number; startsAt: string | null; endsAt: string | null }[];
  tables: { tableId: string; tableNumber: number; label: string; zone: string; email: string }[];
};

type ShiftTemplate = { id: string; name: string; startsAt: string; endsAt: string };

// Serbian counts nouns in three buckets: 1 sto, 2-4 stola, 5+ stolova, with
// the teens always taking the last form.
function tablesWord(count: number) {
  const lastTwo = count % 100;
  const last = count % 10;
  if (last === 1 && lastTwo !== 11) return `${count} sto`;
  if (last >= 2 && last <= 4 && (lastTwo < 12 || lastTwo > 14)) return `${count} stola`;
  return `${count} stolova`;
}

function todayIso(offsetDays = 0) {
  return new Date(Date.now() + offsetDays * 86_400_000).toISOString().slice(0, 10);
}

function shiftDayLabel(date: string) {
  const parsed = new Date(`${date}T12:00:00Z`);
  if (Number.isNaN(parsed.getTime())) return date;
  return parsed.toLocaleDateString("sr-Latn-RS", { weekday: "long", day: "numeric", month: "short" });
}

// Waiter colors for the calendar: stable per email, so Marko is the same green
// on every screen and every week.
const WAITER_COLORS = [
  "#2f7d5d", "#7c5cd6", "#d97b2e", "#2b6cb0", "#c2482b", "#0d9488",
  "#a8741a", "#9d2ec2", "#5d7d2f", "#c2185b", "#455a64", "#8d6e63",
];

function waiterColor(email: string) {
  let hash = 0;
  for (let index = 0; index < email.length; index += 1) {
    hash = (hash * 31 + email.charCodeAt(index)) >>> 0;
  }
  return WAITER_COLORS[hash % WAITER_COLORS.length];
}

function timeToMinutes(value: string) {
  const [hours, minutes] = value.split(":").map(Number);
  return (hours || 0) * 60 + (minutes || 0);
}

function minutesToTime(value: number) {
  const clamped = Math.max(0, Math.min(24 * 60, value));
  return `${String(Math.floor(clamped / 60)).padStart(2, "0")}:${String(clamped % 60).padStart(2, "0")}`;
}

function addDaysIso(dateIso: string, days: number) {
  const date = new Date(`${dateIso}T12:00:00Z`);
  date.setUTCDate(date.getUTCDate() + days);
  return date.toISOString().slice(0, 10);
}

function mondayOf(dateIso: string) {
  const date = new Date(`${dateIso}T12:00:00Z`);
  return addDaysIso(dateIso, -((date.getUTCDay() + 6) % 7));
}

function shortDayLabel(dateIso: string) {
  const parsed = new Date(`${dateIso}T12:00:00Z`);
  if (Number.isNaN(parsed.getTime())) return dateIso;
  return parsed.toLocaleDateString("sr-Latn-RS", { weekday: "short", day: "numeric", month: "numeric" });
}

// One rendered rectangle on the calendar: a waiter's hours inside a shift, or
// the shift itself while nobody is on it yet (dashed, so it begs to be filled).
type CalendarBlock = {
  key: string;
  shift: Shift;
  name: string;
  color: string | null;
  start: number;
  end: number;
  overnight: boolean;
  lane: number;
  lanes: number;
};

function blocksForDay(shifts: Shift[], day: string): CalendarBlock[] {
  const blocks: Omit<CalendarBlock, "lane" | "lanes">[] = [];
  for (const shift of shifts) {
    if (shift.shiftDate !== day) continue;
    const shiftStart = timeToMinutes(shift.startsAt);
    const shiftEndRaw = timeToMinutes(shift.endsAt);
    const entries = shift.waiters.length
      ? shift.waiters.map((waiter) => ({
          key: `${shift.id}:${waiter.email}`,
          name: waiter.name,
          color: waiterColor(waiter.email),
          start: waiter.startsAt ? timeToMinutes(waiter.startsAt) : shiftStart,
          endRaw: waiter.endsAt ? timeToMinutes(waiter.endsAt) : shiftEndRaw,
        }))
      : [{ key: shift.id, name: shift.name, color: null, start: shiftStart, endRaw: shiftEndRaw }];
    for (const entry of entries) {
      // An overnight span (22:00-06:00) is drawn to midnight and flagged.
      const overnight = entry.endRaw <= entry.start;
      blocks.push({
        shift,
        key: entry.key,
        name: entry.name,
        color: entry.color,
        start: entry.start,
        end: overnight ? 24 * 60 : entry.endRaw,
        overnight,
      });
    }
  }
  blocks.sort((a, b) => a.start - b.start || a.end - b.end);
  // Greedy lane packing: overlapping blocks sit side by side, like Google
  // Calendar does with parallel events.
  const laneEnds: number[] = [];
  const placed = blocks.map((block) => {
    let lane = laneEnds.findIndex((end) => end <= block.start);
    if (lane === -1) {
      lane = laneEnds.length;
      laneEnds.push(block.end);
    } else {
      laneEnds[lane] = block.end;
    }
    return { ...block, lane, lanes: 1 };
  });
  const laneCount = Math.max(1, laneEnds.length);
  return placed.map((block) => ({ ...block, lanes: laneCount }));
}

const CAL_HOUR_PX = 42;

// The shift calendar: recurring shifts are defined once as templates, dropped
// onto dates, and every waiter shows up as their own colored block — daily and
// weekly views, flexible hours per person.
function ShiftBoard({ data, slug, setToast }: { data: RestaurantState; slug: string; setToast: (value: string) => void }) {
  const [shifts, setShifts] = useState<Shift[] | null>(null);
  const [templates, setTemplates] = useState<ShiftTemplate[]>([]);
  const [view, setView] = useState<"day" | "week" | "list">("week");
  const [anchor, setAnchor] = useState(todayIso());
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [detailId, setDetailId] = useState<string | null>(null);
  const [createDate, setCreateDate] = useState<string | null>(null);
  const [showTemplates, setShowTemplates] = useState(false);

  const waiters = data.team.filter((member) => member.role === "staff" || member.role === "manager" || member.role === "owner");
  const zones = knownZoneNames(data);

  const from = view === "day" ? anchor : view === "week" ? mondayOf(anchor) : anchor;
  const to = view === "day" ? anchor : view === "week" ? addDaysIso(mondayOf(anchor), 6) : addDaysIso(anchor, 13);
  const days: string[] = [];
  for (let day = from; day <= to; day = addDaysIso(day, 1)) days.push(day);

  async function load() {
    setError(null);
    const params = new URLSearchParams({ slug, from, to });
    const response = await fetch(`/api/shifts?${params.toString()}`, {
      cache: "no-store",
      credentials: "same-origin",
    });
    const payload = await response.json().catch(() => null);
    if (!response.ok || !payload?.ok) {
      setError(payload?.error?.message ?? "Smene nisu dostupne.");
      setShifts([]);
      return;
    }
    setShifts(payload.shifts ?? []);
    setTemplates(payload.templates ?? []);
  }

  useEffect(() => {
    void load();
    // from/to are derived from view + anchor, which are the dependencies here.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [slug, view, anchor]);

  async function send(body: Record<string, unknown>, method: "POST" | "DELETE" = "POST") {
    setBusy(true);
    setError(null);
    const response = await fetch("/api/shifts", {
      method,
      credentials: "same-origin",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ slug, ...body }),
    });
    const payload = await response.json().catch(() => null);
    setBusy(false);
    if (!response.ok || !payload?.ok) {
      setError(payload?.error?.message ?? "Radnja nije uspela.");
      return null;
    }
    await load();
    return payload;
  }

  const detail = shifts?.find((shift) => shift.id === detailId) ?? null;
  const dayBlocks = new Map(days.map((day) => [day, blocksForDay(shifts ?? [], day)]));
  const allBlocks = [...dayBlocks.values()].flat();
  const minHour = Math.min(8, ...allBlocks.map((block) => Math.floor(block.start / 60)));
  const maxHour = Math.max(22, ...allBlocks.map((block) => Math.ceil(block.end / 60)));
  const hourCount = Math.max(1, maxHour - minHour);
  const legendEmails = new Map<string, string>();
  for (const shift of shifts ?? []) {
    for (const waiter of shift.waiters) legendEmails.set(waiter.email, waiter.name);
  }
  const rangeLabel = view === "day"
    ? shiftDayLabel(anchor)
    : `${shortDayLabel(from)} – ${shortDayLabel(to)}`;
  const step = view === "day" ? 1 : view === "week" ? 7 : 14;

  function calendarColumn(day: string, blocks: CalendarBlock[]) {
    return <div
      key={day}
      className={`shift-cal-day ${day === todayIso() ? "today" : ""}`}
      style={{ height: hourCount * CAL_HOUR_PX }}
    >
      {blocks.map((block) => {
        const top = ((block.start / 60) - minHour) * CAL_HOUR_PX;
        const height = Math.max(24, ((block.end - block.start) / 60) * CAL_HOUR_PX);
        const width = 100 / block.lanes;
        return <button
          key={block.key}
          type="button"
          className={`shift-block ${block.color ? "" : "ghost"}`}
          style={{
            top,
            height,
            left: `${block.lane * width}%`,
            width: `calc(${width}% - 3px)`,
            ...(block.color ? { background: block.color } : {}),
          }}
          title={`${block.shift.name} · ${block.name}`}
          onClick={() => setDetailId(block.shift.id)}
        >
          <strong>{block.name}</strong>
          <small>{minutesToTime(block.start)}–{block.overnight ? `${block.shift.endsAt} +1` : minutesToTime(block.end)}</small>
        </button>;
      })}
    </div>;
  }

  return <section className="shift-panel">
    <div className="shift-cal-toolbar">
      <div className="shift-cal-nav">
        <button type="button" className="secondary-button" onClick={() => setAnchor(addDaysIso(anchor, -step))} aria-label="Prethodni period">‹</button>
        <button type="button" className="secondary-button" onClick={() => setAnchor(todayIso())}>Danas</button>
        <button type="button" className="secondary-button" onClick={() => setAnchor(addDaysIso(anchor, step))} aria-label="Sledeći period">›</button>
        <strong className="shift-cal-range">{rangeLabel}</strong>
      </div>
      <div className="shift-cal-views">
        <button type="button" className={view === "day" ? "active" : ""} onClick={() => setView("day")}>Dan</button>
        <button type="button" className={view === "week" ? "active" : ""} onClick={() => setView("week")}>Nedelja</button>
        <button type="button" className={view === "list" ? "active" : ""} onClick={() => setView("list")}>Lista</button>
      </div>
      <div className="shift-cal-actions">
        <button type="button" className="secondary-button" onClick={() => setShowTemplates(!showTemplates)}>⚙ Definiši smene</button>
        <button type="button" className="primary-button" disabled={busy} onClick={() => setCreateDate(view === "day" ? anchor : todayIso())}>＋ Nova smena</button>
      </div>
    </div>

    {showTemplates && <ShiftTemplateManager templates={templates} busy={busy} send={send} setToast={setToast} />}

    {error && <div className="auth-error" role="alert">{error}</div>}

    {shifts === null
      ? <p className="bills-empty">Učitavamo smene…</p>
      : view === "list"
        ? <div className="shift-agenda">
            {days.filter((day) => (dayBlocks.get(day) ?? []).length).map((day) => <div key={day} className={`shift-agenda-day ${day === todayIso() ? "today" : ""}`}>
              <h3>{shiftDayLabel(day)}</h3>
              {(shifts ?? []).filter((shift) => shift.shiftDate === day).map((shift) => <button key={shift.id} type="button" className="shift-agenda-row" onClick={() => setDetailId(shift.id)}>
                <strong>{shift.name}</strong>
                <span>{shift.startsAt}–{shift.endsAt}</span>
                <em>
                  {shift.waiters.length
                    ? shift.waiters.map((waiter) => <i key={waiter.email} style={{ background: waiterColor(waiter.email) }}>{waiter.name}{waiter.startsAt ? ` ${waiter.startsAt}–${waiter.endsAt}` : ""}</i>)
                    : <i className="empty">bez konobara</i>}
                </em>
                {shift.appliedAt && <b className="applied">✓ na sali</b>}
              </button>)}
            </div>)}
            {!allBlocks.length && <p className="bills-empty">Nema smena u ovom periodu. Dodajte prvu preko „＋ Nova smena”.</p>}
          </div>
        : <div className={`shift-cal ${view === "day" ? "single" : ""}`}>
            <div className="shift-cal-head">
              <span className="shift-cal-corner" />
              {days.map((day) => <div key={day} className={day === todayIso() ? "today" : ""}>
                <button type="button" onClick={() => { setAnchor(day); setView("day"); }}>{view === "day" ? shiftDayLabel(day) : shortDayLabel(day)}</button>
                <button type="button" className="shift-cal-add" title="Dodaj smenu za ovaj dan" onClick={() => setCreateDate(day)}>＋</button>
              </div>)}
            </div>
            <div className="shift-cal-body">
              <div className="shift-cal-times" style={{ height: hourCount * CAL_HOUR_PX }}>
                {Array.from({ length: hourCount }, (_, index) => <span key={index} style={{ top: index * CAL_HOUR_PX }}>{String(minHour + index).padStart(2, "0")}h</span>)}
              </div>
              {days.map((day) => calendarColumn(day, dayBlocks.get(day) ?? []))}
            </div>
          </div>}

    {legendEmails.size > 0 && <div className="shift-legend">
      {[...legendEmails.entries()].map(([email, name]) => <span key={email}><i style={{ background: waiterColor(email) }} />{name}</span>)}
    </div>}

    {createDate && <ShiftCreateModal
      date={createDate}
      templates={templates}
      busy={busy}
      onClose={() => setCreateDate(null)}
      onCreate={async (draft) => {
        const payload = await send({ action: "save", ...draft });
        if (payload) {
          setToast(`Smena „${draft.name}” je dodata.`);
          setCreateDate(null);
          setDetailId(String(payload.shiftId));
        }
      }}
    />}

    {detail && <ShiftDetailModal
      shift={detail}
      data={data}
      waiters={waiters}
      zones={zones}
      busy={busy}
      send={send}
      setToast={setToast}
      onClose={() => setDetailId(null)}
    />}
  </section>;
}

// The named shifts a restaurant runs (Jutarnja, Večernja…), managed in one
// place and offered whenever a shift is dropped onto a date.
function ShiftTemplateManager({ templates, busy, send, setToast }: {
  templates: ShiftTemplate[];
  busy: boolean;
  send: (body: Record<string, unknown>, method?: "POST" | "DELETE") => Promise<Record<string, unknown> | null>;
  setToast: (value: string) => void;
}) {
  const [draft, setDraft] = useState({ name: "", startsAt: "08:00", endsAt: "16:00" });

  return <div className="shift-templates">
    <p><strong>Definisane smene</strong> — dodajte ih jednom, pa ih birate pri pravljenju rasporeda.</p>
    <div className="shift-template-chips">
      {templates.map((template) => <span key={template.id} className="shift-template-chip">
        <strong>{template.name}</strong> {template.startsAt}–{template.endsAt}
        <button
          type="button"
          disabled={busy}
          title="Obriši definiciju smene"
          onClick={() => {
            if (!window.confirm(`Obrisati definiciju smene „${template.name}”? Već raspoređene smene ostaju.`)) return;
            void send({ templateId: template.id }, "DELETE");
          }}
        >×</button>
      </span>)}
      {!templates.length && <span className="shift-template-empty">Još nema definisanih smena.</span>}
    </div>
    <div className="shift-template-form">
      <label>Naziv<input value={draft.name} maxLength={60} placeholder="npr. Jutarnja" onChange={(event) => setDraft({ ...draft, name: event.target.value })} /></label>
      <label>Od<input type="time" value={draft.startsAt} onChange={(event) => setDraft({ ...draft, startsAt: event.target.value })} /></label>
      <label>Do<input type="time" value={draft.endsAt} onChange={(event) => setDraft({ ...draft, endsAt: event.target.value })} /></label>
      <button
        type="button"
        className="primary-button"
        disabled={busy || draft.name.trim().length < 2}
        onClick={() => void send({ action: "template", ...draft }).then((payload) => {
          if (payload) {
            setToast(`Smena „${draft.name}” je definisana.`);
            setDraft({ name: "", startsAt: "08:00", endsAt: "16:00" });
          }
        })}
      >Sačuvaj smenu</button>
    </div>
  </div>;
}

// Drops a shift onto a date: pick one of the defined shifts (or type a custom
// one), confirm the date, done — the detail window then takes the roster.
function ShiftCreateModal({ date, templates, busy, onClose, onCreate }: {
  date: string;
  templates: ShiftTemplate[];
  busy: boolean;
  onClose: () => void;
  onCreate: (draft: { name: string; shiftDate: string; startsAt: string; endsAt: string }) => Promise<void>;
}) {
  const first = templates[0] ?? null;
  const [templateId, setTemplateId] = useState(first?.id ?? "custom");
  const [draft, setDraft] = useState({
    name: first?.name ?? "Jutarnja",
    shiftDate: date,
    startsAt: first?.startsAt ?? "08:00",
    endsAt: first?.endsAt ?? "16:00",
  });

  function pickTemplate(id: string) {
    setTemplateId(id);
    const template = templates.find((entry) => entry.id === id);
    if (template) setDraft((current) => ({ ...current, name: template.name, startsAt: template.startsAt, endsAt: template.endsAt }));
  }

  return <div className="modal-backdrop" onClick={onClose}>
    <div className="shift-modal" onClick={(event) => event.stopPropagation()}>
      <header>
        <div><p className="eyebrow">{shiftDayLabel(draft.shiftDate)}</p><h2>Nova smena</h2></div>
        <button type="button" onClick={onClose}>×</button>
      </header>
      {templates.length > 0 && <label className="shift-modal-field">Smena
        <select value={templateId} onChange={(event) => pickTemplate(event.target.value)}>
          {templates.map((template) => <option key={template.id} value={template.id}>{template.name} ({template.startsAt}–{template.endsAt})</option>)}
          <option value="custom">Prilagođena…</option>
        </select>
      </label>}
      <div className="shift-modal-grid">
        <label>Naziv<input value={draft.name} maxLength={60} onChange={(event) => { setTemplateId("custom"); setDraft({ ...draft, name: event.target.value }); }} /></label>
        <label>Datum<input type="date" value={draft.shiftDate} onChange={(event) => setDraft({ ...draft, shiftDate: event.target.value })} /></label>
        <label>Od<input type="time" value={draft.startsAt} onChange={(event) => { setTemplateId("custom"); setDraft({ ...draft, startsAt: event.target.value }); }} /></label>
        <label>Do<input type="time" value={draft.endsAt} onChange={(event) => { setTemplateId("custom"); setDraft({ ...draft, endsAt: event.target.value }); }} /></label>
      </div>
      <footer>
        <button type="button" className="secondary-button" onClick={onClose} disabled={busy}>Otkaži</button>
        <button type="button" className="primary-button" disabled={busy || draft.name.trim().length < 2} onClick={() => void onCreate(draft)}>Dodaj smenu</button>
      </footer>
    </div>
  </div>;
}

// One waiter's row inside the shift window: personal hours (defaulting to the
// shift's span), their tables, and the × that takes them off the roster.
function ShiftWaiterRow({ shift, member, busy, send, data, zones }: {
  shift: Shift;
  member: Shift["waiters"][number];
  busy: boolean;
  send: (body: Record<string, unknown>) => Promise<Record<string, unknown> | null>;
  data: RestaurantState;
  zones: string[];
}) {
  const [startsAt, setStartsAt] = useState(member.startsAt ?? shift.startsAt);
  const [endsAt, setEndsAt] = useState(member.endsAt ?? shift.endsAt);
  useEffect(() => {
    setStartsAt(member.startsAt ?? shift.startsAt);
    setEndsAt(member.endsAt ?? shift.endsAt);
  }, [member.startsAt, member.endsAt, shift.startsAt, shift.endsAt]);
  const dirty = startsAt !== (member.startsAt ?? shift.startsAt) || endsAt !== (member.endsAt ?? shift.endsAt);
  const custom = member.startsAt != null || member.endsAt != null;
  const mine = shift.tables.filter((table) => table.email === member.email);

  return <div className="shift-waiter" style={{ borderLeft: `4px solid ${waiterColor(member.email)}` }}>
    <header>
      <span><strong>{member.name}</strong><em> {tablesWord(mine.length)}</em></span>
      <button
        disabled={busy}
        title="Skini sa smene"
        onClick={() => void send({ action: "waiter", shiftId: shift.id, email: member.email, member: false })}
      >
        ×
      </button>
    </header>
    <div className="shift-hours">
      <label>Od<input type="time" value={startsAt} disabled={busy} onChange={(event) => setStartsAt(event.target.value)} /></label>
      <label>Do<input type="time" value={endsAt} disabled={busy} onChange={(event) => setEndsAt(event.target.value)} /></label>
      {dirty && <button
        type="button"
        className="primary-button"
        disabled={busy}
        onClick={() => void send({ action: "waiter", shiftId: shift.id, email: member.email, member: true, startsAt, endsAt })}
      >✓</button>}
      {custom && !dirty && <button
        type="button"
        className="text-button"
        disabled={busy}
        title="Vrati na pune sate smene"
        onClick={() => void send({ action: "waiter", shiftId: shift.id, email: member.email, member: true })}
      >ceo termin</button>}
    </div>
    <div className="shift-tables">
      {mine.length
        ? mine.map((table) => <button
            key={table.tableId}
            disabled={busy}
            title="Ukloni sto iz dodele"
            onClick={() => void send({ action: "tables", shiftId: shift.id, email: member.email, tableIds: [table.tableId], assign: false })}
          >
            {table.tableNumber} ×
          </button>)
        : <span className="empty">Bez stolova</span>}
    </div>
    <div className="shift-add">
      <select
        value=""
        disabled={busy}
        onChange={(event) => {
          const value = event.target.value;
          if (!value) return;
          const body = value.startsWith("zone:")
            ? { action: "tables", shiftId: shift.id, email: member.email, zone: value.slice(5), assign: true }
            : { action: "tables", shiftId: shift.id, email: member.email, tableIds: [value], assign: true };
          void send(body);
        }}
      >
        <option value="">＋ Dodaj sto ili rejon…</option>
        {zones.map((zone) => <option key={`zone:${zone}`} value={`zone:${zone}`}>Ceo rejon: {zone}</option>)}
        {data.tables.filter((table) => table.active).map((table) => <option key={table.id} value={table.id}>
          Sto {table.number} ({table.zone})
        </option>)}
      </select>
    </div>
  </div>;
}

// Everything about one shift: basics, the roster with per-person hours and
// tables, applying the plan to the floor, and deletion.
function ShiftDetailModal({ shift, data, waiters, zones, busy, send, setToast, onClose }: {
  shift: Shift;
  data: RestaurantState;
  waiters: TeamMemberSummary[];
  zones: string[];
  busy: boolean;
  send: (body: Record<string, unknown>, method?: "POST" | "DELETE") => Promise<Record<string, unknown> | null>;
  setToast: (value: string) => void;
  onClose: () => void;
}) {
  const [basics, setBasics] = useState({ name: shift.name, shiftDate: shift.shiftDate, startsAt: shift.startsAt, endsAt: shift.endsAt });
  useEffect(() => {
    setBasics({ name: shift.name, shiftDate: shift.shiftDate, startsAt: shift.startsAt, endsAt: shift.endsAt });
  }, [shift.id, shift.name, shift.shiftDate, shift.startsAt, shift.endsAt]);
  const basicsDirty = basics.name !== shift.name || basics.shiftDate !== shift.shiftDate || basics.startsAt !== shift.startsAt || basics.endsAt !== shift.endsAt;
  const free = waiters.filter((member) => !shift.waiters.some((entry) => entry.email === member.email));

  return <div className="modal-backdrop" onClick={onClose}>
    <div className="shift-modal shift-detail" onClick={(event) => event.stopPropagation()}>
      <header>
        <div>
          <p className="eyebrow">{shiftDayLabel(shift.shiftDate)} · {shift.startsAt}–{shift.endsAt}</p>
          <h2>{shift.name}</h2>
          {shift.appliedAt && <p className="shift-applied-note">✓ Primenjena na salu</p>}
        </div>
        <button type="button" onClick={onClose}>×</button>
      </header>

      <div className="shift-modal-grid">
        <label>Naziv<input value={basics.name} maxLength={60} disabled={busy} onChange={(event) => setBasics({ ...basics, name: event.target.value })} /></label>
        <label>Datum<input type="date" value={basics.shiftDate} disabled={busy} onChange={(event) => setBasics({ ...basics, shiftDate: event.target.value })} /></label>
        <label>Od<input type="time" value={basics.startsAt} disabled={busy} onChange={(event) => setBasics({ ...basics, startsAt: event.target.value })} /></label>
        <label>Do<input type="time" value={basics.endsAt} disabled={busy} onChange={(event) => setBasics({ ...basics, endsAt: event.target.value })} /></label>
      </div>
      {basicsDirty && <button
        type="button"
        className="secondary-button"
        disabled={busy || basics.name.trim().length < 2}
        onClick={() => void send({ action: "save", shiftId: shift.id, ...basics }).then((payload) => {
          if (payload) setToast("Smena je izmenjena.");
        })}
      >Sačuvaj izmene smene</button>}

      <div className="shift-board">
        {shift.waiters.map((member) => <ShiftWaiterRow key={member.email} shift={shift} member={member} busy={busy} send={send} data={data} zones={zones} />)}
        {!shift.waiters.length && <p className="shift-empty">Još nema konobara na ovoj smeni. Dodajte ih ispod.</p>}
      </div>

      {free.length > 0 && <div className="shift-add" style={{ marginTop: 10 }}>
        <select
          value=""
          disabled={busy}
          onChange={(event) => {
            if (!event.target.value) return;
            void send({ action: "waiter", shiftId: shift.id, email: event.target.value, member: true });
          }}
        >
          <option value="">＋ Dodaj konobara na smenu…</option>
          {free.map((member) => <option key={member.email} value={member.email}>
            {member.displayName || member.email}
          </option>)}
        </select>
      </div>}

      <footer>
        <button
          className="danger-link"
          disabled={busy}
          onClick={() => {
            if (!window.confirm(`Obrisati smenu „${shift.name}” (${shiftDayLabel(shift.shiftDate)})?`)) return;
            void send({ shiftId: shift.id }, "DELETE").then((payload) => {
              if (payload) onClose();
            });
          }}
        >
          Obriši smenu
        </button>
        <button
          className="primary-button"
          disabled={busy || !shift.tables.length}
          title={shift.tables.length ? "Prepiši dodele na salu" : "Prvo dodelite stolove"}
          onClick={() => void send({ action: "apply", shiftId: shift.id }).then((payload) => {
            if (payload) setToast(`Smena je primenjena na ${tablesWord(Number(payload.applied))}.`);
          })}
        >
          Primeni na salu
        </button>
      </footer>
    </div>
  </div>;
}

type WaiterShift = {
  id: string;
  name: string;
  shiftDate: string;
  startsAt: string;
  endsAt: string;
  note: string;
  tableCount: number;
  tableNumbers: string;
};

// Read-only on the phone: a waiter checks when they work and which tables are
// theirs, and changes nothing.
function WaiterShifts({ slug }: { slug: string }) {
  const [shifts, setShifts] = useState<WaiterShift[] | null>(null);

  useEffect(() => {
    let active = true;
    const params = new URLSearchParams({ slug, from: todayIso(-1), to: todayIso(14) });
    fetch(`/api/shifts?${params.toString()}`, { cache: "no-store", credentials: "same-origin" })
      .then((response) => response.json())
      .then((payload) => {
        if (active && payload?.ok) setShifts((payload.mine ?? []) as WaiterShift[]);
      })
      .catch(() => { if (active) setShifts([]); });
    return () => { active = false; };
  }, [slug]);

  if (shifts === null || !shifts.length) return null;
  const today = todayIso();

  return <section className="waiter-shifts">
    <h2>Moje smene</h2>
    <ul>
      {shifts.map((shift) => <li key={shift.id} className={shift.shiftDate === today ? "today" : ""}>
        <strong>{shift.name} · {shift.startsAt}–{shift.endsAt}</strong>
        <small>
          {shift.shiftDate === today ? "danas" : shiftDayLabel(shift.shiftDate)}
          {shift.tableCount ? ` · stolovi ${shift.tableNumbers}` : " · bez dodeljenih stolova"}
        </small>
        {shift.note && <small>{shift.note}</small>}
      </li>)}
    </ul>
  </section>;
}

function SettingsSection({ title, description, children }: { title: string; description: string; children: React.ReactNode }) { return <article className="panel settings-card"><header><h2>{title}</h2><p>{description}</p></header>{children}</article>; }

// Sound is saved on the server from this version on, so every screen in the
// restaurant plays the same thing. Each device still needs one click on "Zvuk"
// before a browser will make any sound at all.
function SoundSettingsSection({ data, setData, postAction, setToast }: Pick<SharedProps, "data" | "setData" | "postAction" | "setToast">) {
  const settings = data.restaurant.settings;
  const sound = soundSettingsFrom(data.restaurant);
  const [busy, setBusy] = useState<"order" | "call" | null>(null);
  const [error, setError] = useState<string | null>(null);

  function patch(changes: Partial<Settings>) {
    setData((current) => ({
      ...current,
      restaurant: { ...current.restaurant, settings: { ...current.restaurant.settings, ...changes } },
    }));
  }

  async function save(changes: Partial<Settings>) {
    patch(changes);
    if (await postAction("updateSettings", { settings: { ...settings, ...changes } })) {
      setToast("Zvuk je sačuvan.");
    }
  }

  // The file goes through the ordinary media upload, tagged as a sound, so it
  // lives in storage/ next to the photos and is served the same way.
  async function upload(kind: "order" | "call", file: File) {
    setBusy(kind);
    setError(null);
    const form = new FormData();
    form.append("slug", data.restaurant.slug);
    form.append("kind", "sound");
    form.append("file", file);
    const response = await fetch("/api/demo/upload", { method: "POST", credentials: "same-origin", body: form });
    const payload = await response.json().catch(() => null);
    setBusy(null);
    if (!response.ok || !payload?.ok) {
      setError(payload?.error?.message ?? "Otpremanje zvuka nije uspelo.");
      return;
    }
    const assetId = String(payload.asset.id);
    await save(
      kind === "order"
        ? { soundOrderAssetId: assetId, soundOrder: "custom" }
        : { soundCallAssetId: assetId, soundCall: "custom" },
    );
  }

  function picker(kind: "order" | "call") {
    const tone = kind === "order" ? settings.soundOrder : settings.soundCall;
    const assetId = kind === "order" ? settings.soundOrderAssetId : settings.soundCallAssetId;
    return <div className="sound-row">
      <div className="sound-row-head">
        <strong>{kind === "order" ? "Nova porudžbina" : "Poziv konobaru"}</strong>
        <button type="button" className="secondary-button" onClick={() => previewAlert(kind, sound)}>▶ Testiraj</button>
      </div>
      <select value={tone} onChange={(event) => void save(
        kind === "order"
          ? { soundOrder: event.target.value as ToneName }
          : { soundCall: event.target.value as ToneName },
      )}>
        {TONE_OPTIONS.map((option) => <option
          key={option.value}
          value={option.value}
          disabled={option.value === "custom" && !assetId}
        >{option.label}</option>)}
      </select>
      <label className="sound-upload">
        <input
          type="file"
          accept="audio/mpeg,audio/wav,audio/ogg"
          onChange={(event) => {
            const file = event.target.files?.[0];
            if (file) void upload(kind, file);
          }}
        />
        <span>{busy === kind ? "Otpremanje…" : assetId ? "Zameni moj zvuk" : "＋ Otpremi svoj zvuk"}</span>
      </label>
      {assetId && <button type="button" className="text-button" onClick={() => void save(
        kind === "order"
          ? { soundOrderAssetId: null, soundOrder: "zvono" }
          : { soundCallAssetId: null, soundCall: "dvoton" },
      )}>Ukloni moj zvuk</button>}
    </div>;
  }

  return <SettingsSection
    title="Zvuk za nov zahtev"
    description="Bira se po restoranu i važi i za operativni ekran i za konobarske telefone. Na svakom uređaju je potrebno jednom kliknuti „Uključi zvuk“ — to je pravilo pretraživača."
  >
    <SettingRow
      title="Zvuk je uključen"
      description="Kada je isključeno, nijedan ekran ne pušta zvuk."
      checked={settings.soundEnabled}
      onChange={(value) => void save({ soundEnabled: value })}
      featured
    />
    <label className="sound-volume">
      <span><strong>Jačina</strong><small>{settings.soundVolume}%</small></span>
      <input
        type="range"
        min={0}
        max={100}
        step={5}
        value={settings.soundVolume}
        onChange={(event) => patch({ soundVolume: Number(event.target.value) })}
        onMouseUp={() => void save({ soundVolume: settings.soundVolume })}
        onTouchEnd={() => void save({ soundVolume: settings.soundVolume })}
      />
    </label>
    {picker("order")}
    {picker("call")}
    {error && <div className="auth-error" role="alert">{error}</div>}
    <p className="sound-hint">
      Svoj zvuk: MP3, WAV ili OGG do 1 MB. Najbolje radi kratak signal od 1–2
      sekunde. Fajl se čuva u <code>storage/media</code> na serveru.
    </p>
  </SettingsSection>;
}
function SettingRow({ title, description, checked, onChange, warning, featured }: { title: string; description: string; checked: boolean; onChange: (value: boolean) => void; warning?: boolean; featured?: boolean }) { return <label className={`setting-row ${featured ? "featured" : ""}`}><span><strong>{title}{warning && <em>Pauza</em>}</strong><small>{description}</small></span><input type="checkbox" checked={checked} onChange={(event) => onChange(event.target.checked)} /></label>; }

function DangerZoneSection({ data }: { data: RestaurantState }) {
  const [confirmName, setConfirmName] = useState("");
  const [deleting, setDeleting] = useState(false);
  const [error, setError] = useState("");
  const restaurantName = data.restaurant.name;
  const matches = confirmName.trim() === restaurantName;

  useEffect(() => {
    setConfirmName("");
    setError("");
  }, [data.restaurant.slug]);

  async function handleDelete() {
    if (!matches || deleting) return;
    if (
      !window.confirm(
        `Ovo NEPOVRATNO briše „${restaurantName}“ — meni, stolove, QR kodove, porudžbine i fotografije. Nastaviti?`,
      )
    ) {
      return;
    }
    setDeleting(true);
    setError("");
    try {
      const response = await fetch(`/api/admin/restaurants?slug=${encodeURIComponent(data.restaurant.slug)}`, {
        method: "DELETE",
        credentials: "same-origin",
      });
      const payload = await response.json().catch(() => null);
      if (!response.ok) throw new Error(payload?.error?.message ?? "Restoran nije obrisan.");
      window.location.assign("/admin");
    } catch (requestError) {
      setError(requestError instanceof Error ? requestError.message : "Restoran nije obrisan.");
      setDeleting(false);
    }
  }

  return (
    <SettingsSection title="Opasna zona" description="Nepovratne akcije — koristite pažljivo.">
      <div className="danger-zone-row">
        <p>
          Brisanje restorana <strong>{restaurantName}</strong> zauvek uklanja meni, stolove, QR
          kodove, porudžbine, fotografije i pristup naloga vezanih samo za ovaj restoran. Ne može
          se poništiti.
        </p>
        <label>
          Upišite naziv restorana da potvrdite
          <input value={confirmName} onChange={(event) => setConfirmName(event.target.value)} placeholder={restaurantName} />
        </label>
        {error && <div className="auth-error" role="alert">{error}</div>}
        <button type="button" className="danger-button" disabled={!matches || deleting} onClick={handleDelete}>
          {deleting ? "Brisanje…" : `Obriši ${restaurantName}`}
        </button>
      </div>
    </SettingsSection>
  );
}

function PosIntegrationSection({ data, postAction, setToast }: Pick<SharedProps, "data" | "postAction" | "setToast">) {
  const [webhookUrl, setWebhookUrl] = useState(data.restaurant.settings.posWebhookUrl ?? "");
  const [savingWebhook, setSavingWebhook] = useState(false);
  const [newLabel, setNewLabel] = useState("");
  const [creatingKey, setCreatingKey] = useState(false);
  const [revealedToken, setRevealedToken] = useState<{ label: string; token: string } | null>(null);
  const [workingKeyId, setWorkingKeyId] = useState<string | null>(null);

  useEffect(() => {
    setWebhookUrl(data.restaurant.settings.posWebhookUrl ?? "");
  }, [data.restaurant.settings.posWebhookUrl]);

  async function saveWebhook(event: FormEvent) {
    event.preventDefault();
    setSavingWebhook(true);
    const trimmed = webhookUrl.trim();
    const saved = await postAction("updatePosWebhook", { url: trimmed });
    setSavingWebhook(false);
    if (saved) setToast(trimmed ? "Webhook adresa je sačuvana." : "Webhook je isključen.");
  }

  async function createKey(event: FormEvent) {
    event.preventDefault();
    const label = newLabel.trim();
    if (!label) return;
    setCreatingKey(true);
    const saved = await postAction("generatePosApiKey", { label });
    setCreatingKey(false);
    const rawToken = saved ? saved.result?.token : undefined;
    const token = typeof rawToken === "string" ? rawToken : null;
    if (token) {
      setRevealedToken({ label, token });
      setNewLabel("");
    }
  }

  async function revokeKey(key: PosApiKey) {
    if (!window.confirm(`Opozvati ključ „${key.label}“? Sistemi koji ga koriste odmah gube pristup.`)) return;
    setWorkingKeyId(key.id);
    const revoked = await postAction("revokePosApiKey", { keyId: key.id });
    setWorkingKeyId(null);
    if (revoked) setToast(`Ključ „${key.label}“ je opozvan.`);
  }

  const activeKeys = (data.posApiKeys ?? []).filter((key) => key.isActive);

  return (
    <SettingsSection
      title="Kasa / API integracija"
      description="Povežite eksterni fiskalni ili POS sistem — preko API ključa (POS periodično čita porudžbine na /api/pos/tickets) ili webhook-a (MeniFlow odmah šalje novu porudžbinu)."
    >
      {revealedToken && (
        <div className="pos-key-reveal">
          <strong>Ključ za „{revealedToken.label}“ — sačuvajte ga sada, više se neće prikazati:</strong>
          <code>{revealedToken.token}</code>
          <div className="pos-key-reveal-actions">
            <button
              type="button"
              className="secondary-button"
              onClick={() => {
                navigator.clipboard?.writeText(revealedToken.token);
                setToast("Ključ je kopiran.");
              }}
            >
              Kopiraj
            </button>
            <button type="button" className="secondary-button" onClick={() => setRevealedToken(null)}>Zatvori</button>
          </div>
        </div>
      )}
      <div className="pos-key-list">
        {activeKeys.length === 0 && <p className="empty-small">Još nema aktivnih API ključeva.</p>}
        {activeKeys.map((key) => (
          <div className="pos-key-row" key={key.id}>
            <div>
              <strong>{key.label}</strong>
              <small>
                Napravljen {parseServerDate(key.createdAt).toLocaleDateString("sr-RS")}
                {key.lastUsedAt ? ` · korišćen ${timeAgo(key.lastUsedAt)}` : " · nikad korišćen"}
              </small>
            </div>
            <button type="button" className="danger-button" disabled={workingKeyId === key.id} onClick={() => revokeKey(key)}>
              Opozovi
            </button>
          </div>
        ))}
      </div>
      <form className="pos-key-add-row" onSubmit={createKey}>
        <label>Naziv ključa<input value={newLabel} maxLength={60} onChange={(event) => setNewLabel(event.target.value)} placeholder="npr. Kasa 1, Aurus POS" /></label>
        <button className="primary-button" type="submit" disabled={creatingKey}>{creatingKey ? "Generisanje…" : "Generiši ključ"}</button>
      </form>
      <form className="pos-webhook-row" onSubmit={saveWebhook}>
        <label>Webhook URL (opciono)<input type="url" value={webhookUrl} maxLength={500} onChange={(event) => setWebhookUrl(event.target.value)} placeholder="https://vas-pos-sistem.rs/webhook/meniflow" /></label>
        <button className="secondary-button" type="submit" disabled={savingWebhook}>{savingWebhook ? "Čuvanje…" : "Sačuvaj webhook"}</button>
      </form>
      {data.restaurant.settings.posWebhookSecret && (
        <p className="pos-webhook-secret">
          Webhook potpis (HMAC-SHA256 secret) za provalu autentičnosti poziva: <code>{data.restaurant.settings.posWebhookSecret}</code>
        </p>
      )}
    </SettingsSection>
  );
}

function TeamManager({ slug, enabled, setToast }: { slug: string; enabled: boolean; setToast: React.Dispatch<React.SetStateAction<string | null>> }) {
  const [members, setMembers] = useState<TeamMember[]>([]);
  const [loading, setLoading] = useState(enabled);
  const [email, setEmail] = useState("");
  const [displayName, setDisplayName] = useState("");
  const [password, setPassword] = useState("");
  const [role, setRole] = useState<TeamMember["role"]>("staff");
  const [working, setWorking] = useState(false);
  const [error, setError] = useState("");

  async function loadTeam() {
    if (!enabled) return;
    setLoading(true);
    try {
      const response = await fetch(`/api/admin/team?slug=${encodeURIComponent(slug)}`, {
        cache: "no-store",
        credentials: "same-origin",
      });
      const payload = await response.json().catch(() => null);
      if (!response.ok) throw new Error(payload?.error?.message ?? "Tim nije dostupan.");
      setMembers(Array.isArray(payload.members) ? payload.members : []);
      setError("");
    } catch (requestError) {
      setError(requestError instanceof Error ? requestError.message : "Tim nije dostupan.");
    } finally {
      setLoading(false);
    }
  }

  useEffect(() => {
    void loadTeam();
  }, [slug, enabled]);

  async function addMember(event: FormEvent) {
    event.preventDefault();
    if (working) return;
    setWorking(true);
    setError("");
    try {
      const response = await fetch("/api/admin/team", {
        method: "POST",
        credentials: "same-origin",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ slug, email, displayName, password, role }),
      });
      const payload = await response.json().catch(() => null);
      if (!response.ok) throw new Error(payload?.error?.message ?? "Nalog nije dodat.");
      setEmail(""); setDisplayName(""); setPassword(""); setRole("staff");
      await loadTeam();
      const person = payload.member.displayName || payload.member.email;
      if (payload.invited) {
        setToast(`${person} je dodat — link za aktivaciju je poslat na ${payload.member.email}.`);
      } else if (payload.hadAccount) {
        setToast(`${person} je dodat — prijavljuje se postojećom MeniFlow lozinkom.`);
      } else {
        setToast(`${person} je dodat u tim.`);
      }
    } catch (requestError) {
      setError(requestError instanceof Error ? requestError.message : "Nalog nije dodat.");
    } finally {
      setWorking(false);
    }
  }

  // Re-sends the set-password (activation) email for one member.
  async function sendInvite(member: TeamMember) {
    setWorking(true);
    try {
      const response = await fetch("/api/admin/team", {
        method: "PATCH",
        credentials: "same-origin",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ slug, memberId: member.id }),
      });
      const payload = await response.json().catch(() => null);
      if (!response.ok) throw new Error(payload?.error?.message ?? "Link nije poslat.");
      setToast(`Link za aktivaciju je poslat na ${member.email}. Važi 1 sat.`);
    } catch (requestError) {
      setToast(requestError instanceof Error ? requestError.message : "Link nije poslat.");
    } finally {
      setWorking(false);
    }
  }

  async function removeMember(member: TeamMember) {
    if (!window.confirm(`Ukloniti ${member.displayName || member.email} sa ovog restorana?`)) return;
    setWorking(true);
    try {
      const response = await fetch("/api/admin/team", {
        method: "DELETE",
        credentials: "same-origin",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ slug, memberId: member.id }),
      });
      const payload = await response.json().catch(() => null);
      if (!response.ok) throw new Error(payload?.error?.message ?? "Nalog nije uklonjen.");
      await loadTeam();
      setToast(`${member.displayName || member.email} je uklonjen sa restorana.`);
    } catch (requestError) {
      setToast(requestError instanceof Error ? requestError.message : "Nalog nije uklonjen.");
    } finally {
      setWorking(false);
    }
  }

  if (!enabled) {
    return <article className="panel settings-card" id="team-settings"><header><h2>Nalozi i uloge</h2><p>Samo vlasnik restorana može da dodaje i pregleda članove tima.</p></header><div className="team-empty">Obratite se vlasniku restorana za promenu pristupa.</div></article>;
  }

  return <article className="panel settings-card team-card" id="team-settings"><header><h2>Nalozi i uloge</h2><p>Svaka osoba dobija svoj login i vidi samo restorane kojima pripada.</p></header><div className="team-waiter-note"><span>◔</span><p><strong>Gde se ko prijavljuje</strong>Nalog „Konobar" na svom telefonu vidi samo svoje stolove, na adresi ispod. Nalog „Ekran" služi za veliki ekran u kuhinji ili za šankom i vidi samo red zahteva na <code>/osoblje</code>. Menadžer i vlasnik vide sve, uključujući račune na <code>/racuni</code>.</p></div><div className="team-waiter-link"><code>{typeof window !== "undefined" ? window.location.origin : ""}/moj-sto</code><button type="button" className="text-button" onClick={() => { const url = `${window.location.origin}/moj-sto`; navigator.clipboard?.writeText(url).then(() => setToast("Link je kopiran.")).catch(() => setToast(url)); }}>Kopiraj link</button></div><div className="team-list">{loading ? <div className="team-empty">Učitavanje članova…</div> : members.map((member) => <div className="team-member" key={member.id}><span>{initials(member.displayName || member.email)}</span><p><strong>{member.displayName || "Nalog u pripremi"}</strong><small>{member.email}</small></p><em>{ROLE_LABEL[member.role] ?? member.role}</em><i className={member.isActive ? "active" : ""}>{member.hasLogin ? "Login aktivan" : "Bez naloga"}</i><button type="button" className="text-button" disabled={working || !member.hasLogin || !member.isActive} title="Šalje email sa linkom za postavljanje lozinke" onClick={() => void sendInvite(member)}>Pošalji link</button><button type="button" className="text-button danger" disabled={working} onClick={() => removeMember(member)}>Ukloni</button></div>)}</div><form className="team-form" onSubmit={addMember}><h3>Dodajte člana tima</h3><div className="form-grid"><label>Ime i prezime<input value={displayName} maxLength={100} onChange={(event) => setDisplayName(event.target.value)} placeholder="npr. Marko Marković" /></label><label>Email<input type="email" value={email} maxLength={160} required onChange={(event) => setEmail(event.target.value)} placeholder="marko@restoran.rs" /></label><label>{role === "queue" ? "Lozinka ekrana (obavezno)" : "Privremena lozinka (opciono)"}<input type="password" value={password} required={role === "queue"} minLength={role === "queue" ? 12 : undefined} maxLength={200} onChange={(event) => setPassword(event.target.value)} placeholder={role === "queue" ? "Najmanje 12 znakova, slovo i broj" : "Prazno = link za aktivaciju"} /><small>{role === "queue" ? "Ekran nalog ne dobija email — ovom lozinkom se prijavljujete na ekranu (/osoblje)." : "Ostavite prazno i osoba dobija email sa linkom da sama postavi lozinku."}</small></label><label>Uloga<select value={role} onChange={(event) => setRole(event.target.value as TeamMember["role"])}><option value="staff">Konobar — samo „Moji stolovi" na telefonu</option><option value="queue">Ekran — samo red zahteva na /osoblje</option><option value="manager">Menadžer — meni i podešavanja</option><option value="owner">Vlasnik — puna kontrola</option></select></label></div>{error && <div className="auth-error" role="alert">{error}</div>}<button className="primary-button" type="submit" disabled={working}>{working ? "Dodavanje…" : "Dodaj nalog"}</button></form></article>;
}

function GuestApp({ data, setData, postAction, setToast, guestToken }: SharedProps & { onBackToAdmin: () => void; guestToken?: string }) {
  const [language, setLanguage] = useState<string | null>(null);
  const [category, setCategory] = useState(() => defaultChip(data));
  const [query, setQuery] = useState("");
  const [cart, setCart] = useState<CartLine[]>([]);
  const [selectedItem, setSelectedItem] = useState<MenuItem | null>(null);
  const [cartOpen, setCartOpen] = useState(false);
  const [callOpen, setCallOpen] = useState(false);
  const [sent, setSent] = useState(false);
  const table = data.tables.find((entry) => guestToken && entry.token === guestToken) ?? data.tables.find((entry) => entry.number === 12) ?? data.tables[0];
  const visibleItems = data.items.filter((item) => item.available && item.name.toLowerCase().includes(query.toLowerCase()) && chipMatches(data, category, item));
  const total = cart.reduce((sum, item) => sum + item.price * item.quantity, 0);
  const itemCount = cart.reduce((sum, item) => sum + item.quantity, 0);

  function addToCart(item: MenuItem, quantity = 1, note = "") {
    if (!data.restaurant.settings.orderingEnabled || data.restaurant.settings.orderingPaused) {
      setToast("Poručivanje je trenutno pauzirano.");
      return;
    }
    setCart((current) => {
      const existing = current.find((entry) => entry.id === item.id && entry.variantId === item.variantId && entry.note === note);
      if (existing) return current.map((entry) => entry === existing ? { ...entry, quantity: entry.quantity + quantity } : entry);
      return [...current, { ...item, quantity, note }];
    });
    setSelectedItem(null); setToast(`${item.name} je dodat u porudžbinu.`);
  }

  function sendOrder() {
    if (!cart.length || !table) return;
    const ticket: Ticket = { id: `ticket-${Date.now()}`, kind: "order", status: "open", tableNumber: table.number, zone: table.zone, createdAt: new Date().toISOString(), total, items: cart.map((item) => ({ name: item.name, quantity: item.quantity, price: item.price, portion: item.portion, note: item.note })) };
    setData((current) => ({ ...current, tickets: [ticket, ...current.tickets] }));
    void postAction("createOrder", { tableToken: table.token, items: cart.map((item) => ({ itemId: item.id, quantity: item.quantity, note: item.note })) });
    setCart([]); setCartOpen(false); setSent(true);
  }

  function sendCall(reason: string, paymentMethod?: string) {
    if (!table) return;
    const duplicate = data.tickets.some((ticket) => ticket.status === "open" && ticket.kind === "call" && ticket.tableNumber === table.number && ticket.callReason === reason);
    if (duplicate) { setCallOpen(false); setToast("Konobar je već obavešten za ovaj sto."); return; }
    const ticket: Ticket = { id: `ticket-${Date.now()}`, kind: "call", status: "open", tableNumber: table.number, zone: table.zone, createdAt: new Date().toISOString(), callReason: reason, paymentMethod };
    setData((current) => ({ ...current, tickets: [ticket, ...current.tickets] }));
    void postAction("createWaiterCall", { tableToken: table.token, reason, paymentMethod });
    setCallOpen(false); setToast(`Konobar je obavešten · Sto ${table.number}`);
  }

  if (!language) return <LanguageScreen data={data} locales={enabledLocalesFor(data)} tableNumber={table?.number ?? 12} onChoose={setLanguage} />;

  return <div className="guest-page"><header className="guest-header"><div className="guest-brand"><span>{initials(data.restaurant.name)}</span><div><strong>{data.restaurant.name}</strong><small>{data.restaurant.location}</small></div></div><div className="guest-header-actions">{enabledLocalesFor(data).length > 1 && <button onClick={() => setLanguage(null)}>{localeInfo(language ?? DEFAULT_LOCALE).short} <span>⌄</span></button>}<span className="guest-table">Sto <b>{table?.number ?? 12}</b></span></div></header><main className="guest-main"><section className="guest-intro"><div><p>Dobro veče 👋</p><h1>Šta vam se jede?</h1><span>Kuhinja radi do 22:30 · priprema 15–25 min</span></div>{data.restaurant.settings.waiterCallEnabled && <button className="call-waiter" onClick={() => setCallOpen(true)}><span>◔</span><strong>Pozovi konobara</strong><small>Pomoć ili račun</small></button>}</section>{data.restaurant.settings.orderingPaused && <div className="guest-pause"><span>i</span><p><strong>Poručivanje je trenutno pauzirano.</strong>Meni možete pregledati, a konobara pozvati u svakom trenutku.</p></div>}<label className="guest-search"><span>⌕</span><input value={query} onChange={(event) => setQuery(event.target.value)} placeholder="Pretražite meni…" /></label><div className="guest-categories">{data.categories.map((name) => <button key={name} className={category === name ? "active" : ""} onClick={() => setCategory(name)}>{name}</button>)}</div><section className="guest-menu-section"><div className="guest-section-heading"><div><p className="eyebrow">Iz naše kuhinje</p><h2>{category}</h2></div><span>{visibleItems.length} artikala</span></div><div className="guest-food-grid">{visibleItems.map((item) => <article className="guest-food-card" key={item.id} onClick={() => setSelectedItem(item)}><FoodThumb item={item} large /><div className="guest-food-copy"><div className="tag-row">{item.tags.slice(0, 1).map((tag) => <span key={tag}>{tag}</span>)}</div><h3>{item.name}</h3><p>{item.description}</p><small>{item.portion}{item.allergens?.length ? " · Alergeni dostupni" : ""}</small><div><strong>{money(item.price, data.restaurant.currency)}</strong><button onClick={(event) => { event.stopPropagation(); addToCart(item); }} disabled={data.restaurant.settings.orderingPaused}>＋</button></div></div></article>)}</div>{visibleItems.length === 0 && <div className="guest-empty">Nema rezultata za ovu pretragu.</div>}</section></main>{cart.length > 0 && <button className="guest-cart-bar" onClick={() => setCartOpen(true)}><span className="cart-count">{itemCount}</span><span><small>Vaša porudžbina</small><strong>{itemCount} {itemCount === 1 ? "stavka" : "stavke"}</strong></span><b>{money(total, data.restaurant.currency)} <i>→</i></b></button>}{selectedItem && <ProductSheet item={selectedItem} currency={data.restaurant.currency} onClose={() => setSelectedItem(null)} onAdd={addToCart} />}{cartOpen && <CartSheet tableNumber={table?.number ?? 12} data={data} cart={cart} setCart={setCart} total={total} onClose={() => setCartOpen(false)} onSend={sendOrder} />}{callOpen && <WaiterCallSheet tableNumber={table?.number ?? 12} onClose={() => setCallOpen(false)} onSend={sendCall} />}{sent && <div className="modal-backdrop"><div className="success-modal"><span>✓</span><p className="eyebrow">Sto {table?.number ?? 12}</p><h2>Porudžbina je poslata</h2><p>Osoblje je primilo vaš zahtev. Prijatno!</p><div className="prep-time"><b>15–25 min</b><small>Procenjeno vreme pripreme</small></div><button className="primary-button wide" onClick={() => setSent(false)}>Nastavi pregled menija</button></div></div>}<footer className="guest-footer"><div className="guest-brand"><span>{initials(data.restaurant.name)}</span><strong>{data.restaurant.name}</strong></div><p>Digitalni meni pokreće MeniFlow</p></footer></div>;
}

function categoryLabel(name: string, locale: string) {
  const labels: Record<string, [string, string]> = {
    "Sve": ["All", "Alle"], "Preporučujemo": ["Recommended", "Empfohlen"], "Glavna jela": ["Main courses", "Hauptgerichte"],
    "Salate": ["Salads", "Salate"], "Dezerti": ["Desserts", "Desserts"], "Bezalkoholna pića": ["Soft drinks", "Alkoholfreie Getränke"],
    "Alkoholna pića": ["Alcoholic drinks", "Alkoholische Getränke"], "Predjela": ["Starters", "Vorspeisen"], "Pića": ["Drinks", "Getränke"],
  };
  if (locale === "en") return labels[name]?.[0] ?? name;
  if (locale === "de") return labels[name]?.[1] ?? name;
  return name;
}

function localizeMenuItem(item: MenuItem, locale: string) {
  const translation = item.translations?.[locale] ?? item.translations?.[locale.split("-")[0]];
  return translation ? { ...item, name: translation.name || item.name, description: translation.description || item.description } : item;
}

function LocalizedGuestApp({ data, setData, postAction, setToast, guestToken }: SharedProps & { guestToken?: string }) {
  const locales = enabledLocalesFor(data);
  // One language means there is nothing to choose — go straight to the menu.
  const [language, setLanguage] = useState<string | null>(locales.length > 1 ? null : locales[0]);
  const [category, setCategory] = useState(() => defaultChip(data));
  const [query, setQuery] = useState("");
  const [cart, setCart] = useState<CartLine[]>([]);
  const [selectedItem, setSelectedItem] = useState<MenuItem | null>(null);
  const [cartOpen, setCartOpen] = useState(false);
  const [callOpen, setCallOpen] = useState(false);
  const [sent, setSent] = useState(false);
  const [sending, setSending] = useState(false);
  const pendingOrder = useRef<{ id: string; fingerprint: string } | null>(null);
  const table = data.tables.find((entry) => guestToken && entry.token === guestToken) ?? data.tables.find((entry) => entry.number === 12) ?? data.tables[0];
  const activeLocale = language ?? locales[0] ?? DEFAULT_LOCALE;
  const copy = guestCopyFor(data, activeLocale);
  const visibleItems = data.items
    .filter((item) => item.available && chipMatches(data, category, item))
    .map((item) => localizeMenuItem(item, activeLocale))
    .filter((item) => `${item.name} ${item.description}`.toLowerCase().includes(query.toLowerCase()));
  const total = cart.reduce((sum, item) => sum + item.price * item.quantity, 0);
  const itemCount = cart.reduce((sum, item) => sum + item.quantity, 0);

  function addToCart(item: MenuItem, quantity = 1, note = "") {
    if (!data.restaurant.settings.orderingEnabled || data.restaurant.settings.orderingPaused) {
      setToast(language === "EN" ? "Ordering is currently paused." : language === "DE" ? "Bestellungen sind derzeit pausiert." : "Poručivanje je trenutno pauzirano.");
      return;
    }
    setCart((current) => {
      const existing = current.find((entry) => entry.id === item.id && entry.note === note);
      if (existing) return current.map((entry) => entry === existing ? { ...entry, quantity: entry.quantity + quantity } : entry);
      return [...current, { ...item, quantity, note }];
    });
    setSelectedItem(null);
    setToast(language === "EN" ? `${item.name} added to your order.` : language === "DE" ? `${item.name} wurde hinzugefügt.` : `${item.name} je dodat u porudžbinu.`);
  }

  async function sendOrder() {
    if (!cart.length || !table || sending) return;
    if (!data.restaurant.settings.orderingEnabled || data.restaurant.settings.orderingPaused) {
      setToast("Poručivanje je trenutno pauzirano.");
      return;
    }
    const orderItems = cart.map((item) => ({ itemId: item.id, variantId: item.variantId, quantity: item.quantity, note: item.note }));
    const fingerprint = JSON.stringify(orderItems);
    if (!pendingOrder.current || pendingOrder.current.fingerprint !== fingerprint) {
      pendingOrder.current = {
        id: `web-${crypto.randomUUID()}`,
        fingerprint,
      };
    }
    setSending(true);
    const saved = await postAction("createOrder", {
      tableToken: table.token,
      clientRequestId: pendingOrder.current.id,
      items: orderItems,
    });
    setSending(false);
    if (!saved) return;
    pendingOrder.current = null;
    setCart([]);
    setCartOpen(false);
    setSent(true);
  }

  async function sendCall(reason: string, paymentMethod?: string) {
    if (!table || sending) return;
    const duplicate = data.tickets.some((ticket) => ticket.status === "open" && ticket.kind === "call" && ticket.tableNumber === table.number);
    if (duplicate) {
      setCallOpen(false);
      setToast(language === "EN" ? "A waiter has already been notified." : language === "DE" ? "Der Service wurde bereits benachrichtigt." : "Konobar je već obavešten za ovaj sto.");
      return;
    }
    setSending(true);
    const saved = await postAction("createWaiterCall", { tableToken: table.token, reason, paymentMethod });
    setSending(false);
    if (!saved) return;
    setCallOpen(false);
    setToast(language === "EN" ? `Waiter notified · Table ${table.number}` : language === "DE" ? `Service benachrichtigt · Tisch ${table.number}` : `Konobar je obavešten · Sto ${table.number}`);
  }

  if (!language) return <LanguageScreen data={data} locales={locales} tableNumber={table?.number ?? 12} onChoose={setLanguage} />;

  // A locked restaurant serves nothing at all, so the QR code leads to a plain
  // notice instead of a screen with buttons that cannot work.
  if (data.restaurant.locked) {
    return <main className="language-screen">
      <div className="language-card">
        <div className="platform-mark">M</div>
        <p className="eyebrow">{data.restaurant.name}</p>
        <h2>{language === "EN" ? "Temporarily unavailable" : language === "DE" ? "Vorübergehend nicht verfügbar" : "Trenutno nedostupno"}</h2>
        <p>
          {language === "EN"
            ? "Digital ordering is switched off at the moment. Please ask the staff directly."
            : language === "DE"
              ? "Die digitale Bestellung ist derzeit deaktiviert. Bitte wenden Sie sich an das Personal."
              : "Digitalni meni je trenutno isključen. Obratite se osoblju restorana."}
        </p>
      </div>
    </main>;
  }

  // On the basic tier the guest gets the call-waiter screen only. The server
  // already withholds the menu; this is the matching guest-facing view.
  if (!data.restaurant.hasFullAccess) {
    return <TrialGuestApp
      data={data}
      language={language}
      tableNumber={table?.number ?? 1}
      onCall={(reason) => { void sendCall(reason); }}
      sending={sending}
    />;
  }

  return (
    <div className="guest-page">
      <header className="guest-header">
        <div className="guest-brand"><BrandMark restaurant={data.restaurant} /><div><strong>{data.restaurant.name}</strong><small>{data.restaurant.location}</small></div></div>
        <div className="guest-header-actions">{locales.length > 1 && <button onClick={() => setLanguage(null)}>{localeInfo(activeLocale).short} <span>⌄</span></button>}<span className="guest-table">{activeLocale === "en" ? "Table" : activeLocale === "de" ? "Tisch" : "Sto"} <b>{table?.number ?? 12}</b></span></div>
      </header>
      <main className="guest-main">
        <section className="guest-intro">
          <div><p>{copy.greeting}</p><h1>{copy.whatToEat}</h1><span>{copy.kitchenHours}</span></div>
          {data.restaurant.settings.waiterCallEnabled && <button className="call-waiter" onClick={() => setCallOpen(true)}><span>◔</span><strong>{copy.callWaiter}</strong><small>{copy.helpOrBill}</small></button>}
        </section>
        {(!data.restaurant.settings.orderingEnabled || data.restaurant.settings.orderingPaused) && <div className="guest-pause"><span>i</span><p><strong>{copy.pausedTitle}</strong>{copy.pausedBody}</p></div>}
        <label className="guest-search"><span>⌕</span><input value={query} onChange={(event) => setQuery(event.target.value)} placeholder={copy.search} /></label>
        <div className="guest-categories">{data.categories.map((name) => <button key={name} className={category === name ? "active" : ""} onClick={() => setCategory(name)}>{categoryLabel(name, language)}</button>)}</div>
        <section className="guest-menu-section">
          <div className="guest-section-heading"><div><p className="eyebrow">{copy.fromKitchen}</p><h2>{categoryLabel(category, language)}</h2></div><span>{visibleItems.length} {copy.itemsLabel}</span></div>
          <div className="guest-food-grid">{visibleItems.map((item) => <article className="guest-food-card" key={item.id} onClick={() => setSelectedItem(item)}><FoodThumb item={item} large /><div className="guest-food-copy"><div className="tag-row">{item.tags.slice(0, 1).map((tag) => <span key={tag}>{tag}</span>)}</div><h3>{item.name}</h3><p>{item.description}</p><small>{item.portion}{item.allergens?.length ? ` · ${copy.allergens}` : ""}</small><div><strong>{money(item.price, data.restaurant.currency)}</strong><button onClick={(event) => { event.stopPropagation(); addToCart(item); }} disabled={!data.restaurant.settings.orderingEnabled || data.restaurant.settings.orderingPaused}>＋</button></div></div></article>)}</div>
          {visibleItems.length === 0 && <div className="guest-empty">{copy.noResults}</div>}
        </section>
      </main>
      {(data.restaurant.settings.waiterCallEnabled || cart.length > 0) && <div className={`guest-mobile-dock ${cart.length > 0 ? "has-cart" : ""}`}>
        {data.restaurant.settings.waiterCallEnabled && <button className="mobile-waiter-action" onClick={() => setCallOpen(true)}><span>◔</span><b><strong>{copy.callWaiter}</strong><small>{copy.helpOrBill}</small></b></button>}
        {cart.length > 0 && <button className="mobile-cart-action" onClick={() => setCartOpen(true)}><span>{itemCount}</span><b><strong>{copy.yourOrder}</strong><small>{money(total, data.restaurant.currency)}</small></b><i>→</i></button>}
      </div>}
      {cart.length > 0 && <button className="guest-cart-bar" onClick={() => setCartOpen(true)}><span className="cart-count">{itemCount}</span><span><small>{copy.yourOrder}</small><strong>{itemCount} {itemCount === 1 ? copy.oneItem : copy.manyItems}</strong></span><b>{money(total, data.restaurant.currency)} <i>→</i></b></button>}
      {selectedItem && <LocalizedProductSheet item={selectedItem} currency={data.restaurant.currency} copy={copy} onClose={() => setSelectedItem(null)} onAdd={addToCart} />}
      {cartOpen && <LocalizedCartSheet tableNumber={table?.number ?? 12} data={data} cart={cart} setCart={setCart} total={total} copy={copy} sending={sending} onClose={() => setCartOpen(false)} onSend={sendOrder} />}
      {callOpen && <LocalizedWaiterCallSheet tableNumber={table?.number ?? 12} copy={copy} sending={sending} confirm={data.restaurant.settings.callConfirmEnabled} onClose={() => setCallOpen(false)} onSend={sendCall} />}
      {sent && <div className="modal-backdrop"><div className="success-modal"><span>✓</span><p className="eyebrow">{language === "EN" ? "Table" : language === "DE" ? "Tisch" : "Sto"} {table?.number ?? 12}</p><h2>{copy.orderSent}</h2><p>{copy.orderSentBody}</p><div className="prep-time"><b>15–25 min</b><small>{copy.prepTime}</small></div><button className="primary-button wide" onClick={() => setSent(false)}>{copy.continueMenu}</button></div></div>}
      <footer className="guest-footer"><div className="guest-brand"><BrandMark restaurant={data.restaurant} /><strong>{data.restaurant.name}</strong></div><p>{copy.poweredBy}</p></footer>
    </div>
  );
}

// Guest view for a restaurant still on the trial plan: the waiter call works,
// the menu does not exist yet as far as this guest is concerned.
function TrialGuestApp({ data, language, tableNumber, onCall, sending }: { data: RestaurantState; language: string; tableNumber: number; onCall: (reason: string) => void; sending: boolean }) {
  const strings: Record<string, { title: string; lede: string; table: string; reasons: string[]; sending: string; soon: string }> = {
    "sr-Latn": {
      title: "Pozovite konobara",
      lede: "Skenirali ste QR kod ovog stola. Dodirnite dugme i konobar odmah dobija poruku.",
      table: "Sto",
      reasons: ["Potrebna mi je pomoć", "Želim da poručim", "Donesite račun"],
      sending: "Šaljemo…",
      soon: "Digitalni meni ovog restorana biće dostupan uskoro.",
    },
    en: {
      title: "Call the waiter",
      lede: "You scanned this table's QR code. Tap a button and a waiter is notified right away.",
      table: "Table",
      reasons: ["I need help", "I would like to order", "Bring the bill"],
      sending: "Sending…",
      soon: "This restaurant's digital menu is coming soon.",
    },
    de: {
      title: "Kellner rufen",
      lede: "Sie haben den QR-Code dieses Tisches gescannt. Tippen Sie, und der Kellner wird sofort benachrichtigt.",
      table: "Tisch",
      reasons: ["Ich brauche Hilfe", "Ich möchte bestellen", "Die Rechnung, bitte"],
      sending: "Wird gesendet…",
      soon: "Die digitale Speisekarte dieses Restaurants folgt in Kürze.",
    },
  };
  // A language we do not ship falls back to the restaurant's own default.
  const copy = strings[language] ?? strings[data.restaurant.defaultLocale] ?? strings[DEFAULT_LOCALE];

  return <div className="guest-page trial-guest">
    <header className="guest-header">
      <div className="guest-brand"><BrandMark restaurant={data.restaurant} /><div><strong>{data.restaurant.name}</strong><small>{data.restaurant.location}</small></div></div>
      <span className="guest-table">{copy.table} <b>{tableNumber}</b></span>
    </header>
    <main className="trial-guest-main">
      <div className="trial-guest-mark">◔</div>
      <h1>{copy.title}</h1>
      <p>{copy.lede}</p>
      <div className="trial-guest-actions">
        {copy.reasons.map((reason) => <button key={reason} type="button" disabled={sending} onClick={() => onCall(reason)}>
          {sending ? copy.sending : reason}
        </button>)}
      </div>
      <small>{copy.soon}</small>
    </main>
  </div>;
}

function LocalizedProductSheet({ item, currency, copy, onClose, onAdd }: { item: MenuItem; currency: string; copy: GuestCopy; onClose: () => void; onAdd: (item: MenuItem, quantity: number, note: string) => void }) {
  const variants = item.variants?.filter((variant) => variant.available !== false) ?? [];
  const [selectedIndex, setSelectedIndex] = useState(0);
  const selectedVariant = variants[selectedIndex];
  const selectedItem = selectedVariant
    ? { ...item, variantId: selectedVariant.id, portion: selectedVariant.label, price: selectedVariant.price }
    : item;
  const [quantity, setQuantity] = useState(1);
  const [note, setNote] = useState("");
  return <div className="modal-backdrop sheet-backdrop" onMouseDown={(event) => event.target === event.currentTarget && onClose()}><div className="guest-sheet product-sheet"><button className="sheet-close" onClick={onClose}>×</button><FoodThumb item={item} large /><div className="product-copy"><div className="tag-row">{item.tags.map((tag) => <span key={tag}>{tag}</span>)}</div><h2>{item.name}</h2><p>{item.description}</p>{variants.length > 1 && <div className="variant-options">{variants.map((variant, index) => <button className={selectedIndex === index ? "active" : ""} key={variant.id ?? `${variant.label}-${index}`} onClick={() => setSelectedIndex(index)}><span>{variant.label}</span><strong>{money(variant.price, currency)}</strong></button>)}</div>}<div className="product-meta"><span>{selectedItem.portion}</span><strong>{money(selectedItem.price, currency)}</strong></div>{item.allergens?.length ? <small>{copy.allergens}: {item.allergens.join(", ")}</small> : null}<label>{copy.kitchenNote}<textarea rows={2} placeholder={copy.noteExample} value={note} onChange={(event) => setNote(event.target.value)} /></label><div className="product-add"><div className="quantity-control"><button onClick={() => setQuantity(Math.max(1, quantity - 1))}>−</button><b>{quantity}</b><button onClick={() => setQuantity(quantity + 1)}>＋</button></div><button className="primary-button" onClick={() => onAdd(selectedItem, quantity, note)}>{copy.add} · {money(selectedItem.price * quantity, currency)}</button></div></div></div></div>;
}

function LegacyLocalizedProductSheet({ item, currency, copy, onClose, onAdd }: { item: MenuItem; currency: string; copy: GuestCopy; onClose: () => void; onAdd: (item: MenuItem, quantity: number, note: string) => void }) {
  const [quantity, setQuantity] = useState(1);
  const [note, setNote] = useState("");
  return <div className="modal-backdrop sheet-backdrop" onMouseDown={(event) => event.target === event.currentTarget && onClose()}><div className="guest-sheet product-sheet"><button className="sheet-close" onClick={onClose}>×</button><FoodThumb item={item} large /><div className="product-copy"><div className="tag-row">{item.tags.map((tag) => <span key={tag}>{tag}</span>)}</div><h2>{item.name}</h2><p>{item.description}</p><div className="product-meta"><span>{item.portion}</span><strong>{money(item.price, currency)}</strong></div>{item.allergens?.length ? <small>{copy.allergens}: {item.allergens.join(", ")}</small> : null}<label>{copy.kitchenNote}<textarea rows={2} placeholder={copy.noteExample} value={note} onChange={(event) => setNote(event.target.value)} /></label><div className="product-add"><div className="quantity-control"><button onClick={() => setQuantity(Math.max(1, quantity - 1))}>−</button><b>{quantity}</b><button onClick={() => setQuantity(quantity + 1)}>＋</button></div><button className="primary-button" onClick={() => onAdd(item, quantity, note)}>{copy.add} · {money(item.price * quantity, currency)}</button></div></div></div></div>;
}

function LocalizedCartSheet({ data, cart, setCart, total, onClose, onSend, tableNumber, copy, sending }: { data: RestaurantState; cart: CartLine[]; setCart: React.Dispatch<React.SetStateAction<CartLine[]>>; total: number; onClose: () => void; onSend: () => void; tableNumber: number; copy: GuestCopy; sending: boolean }) {
  function change(item: CartLine, delta: number) { setCart((current) => current.flatMap((entry) => entry === item ? entry.quantity + delta <= 0 ? [] : [{ ...entry, quantity: entry.quantity + delta }] : [entry])); }
  return <div className="modal-backdrop sheet-backdrop"><div className="guest-sheet cart-sheet"><div className="modal-heading"><div><p className="eyebrow">{tableNumber}</p><h2>{copy.yourOrder}</h2><p>{copy.checkItems}</p></div><button onClick={onClose} disabled={sending}>×</button></div><div className="cart-lines">{cart.map((item) => <div className="cart-line" key={`${item.id}-${item.variantId ?? "default"}-${item.note ?? ""}`}><FoodThumb item={item} /><div><strong>{item.name}</strong><p>{item.portion}{item.note ? ` · ${item.note}` : ""}</p><span>{money(item.price * item.quantity, data.restaurant.currency)}</span></div><div className="quantity-control small"><button onClick={() => change(item, -1)} disabled={sending}>−</button><b>{item.quantity}</b><button onClick={() => change(item, 1)} disabled={sending}>＋</button></div></div>)}</div><div className="cart-summary"><span>{copy.subtotal} <b>{money(total, data.restaurant.currency)}</b></span><span>{copy.service} <b>{copy.included}</b></span><strong>{copy.total} <b>{money(total, data.restaurant.currency)}</b></strong></div><div className="pay-note"><span>i</span><p><strong>{copy.payAtRestaurant}</strong>{copy.payBody}</p></div><button className="primary-button wide send-order" onClick={onSend} disabled={sending}>{sending ? "Slanje…" : copy.sendOrder} · {money(total, data.restaurant.currency)} <span>→</span></button></div></div>;
}

function LegacyLocalizedCartSheet({ data, cart, setCart, total, onClose, onSend, tableNumber, copy, sending }: { data: RestaurantState; cart: CartLine[]; setCart: React.Dispatch<React.SetStateAction<CartLine[]>>; total: number; onClose: () => void; onSend: () => void; tableNumber: number; copy: GuestCopy; sending: boolean }) {
  function change(item: CartLine, delta: number) { setCart((current) => current.flatMap((entry) => entry === item ? entry.quantity + delta <= 0 ? [] : [{ ...entry, quantity: entry.quantity + delta }] : [entry])); }
  return <div className="modal-backdrop sheet-backdrop"><div className="guest-sheet cart-sheet"><div className="modal-heading"><div><p className="eyebrow">{tableNumber}</p><h2>{copy.yourOrder}</h2><p>{copy.checkItems}</p></div><button onClick={onClose} disabled={sending}>×</button></div><div className="cart-lines">{cart.map((item) => <div className="cart-line" key={`${item.id}-${item.note}`}><FoodThumb item={item} /><div><strong>{item.name}</strong><p>{item.portion}{item.note ? ` · ${item.note}` : ""}</p><span>{money(item.price * item.quantity, data.restaurant.currency)}</span></div><div className="quantity-control small"><button onClick={() => change(item, -1)} disabled={sending}>−</button><b>{item.quantity}</b><button onClick={() => change(item, 1)} disabled={sending}>＋</button></div></div>)}</div><div className="cart-summary"><span>{copy.subtotal} <b>{money(total, data.restaurant.currency)}</b></span><span>{copy.service} <b>{copy.included}</b></span><strong>{copy.total} <b>{money(total, data.restaurant.currency)}</b></strong></div><div className="pay-note"><span>i</span><p><strong>{copy.payAtRestaurant}</strong>{copy.payBody}</p></div><button className="primary-button wide send-order" onClick={onSend} disabled={sending}>{sending ? "Slanje…" : copy.sendOrder} · {money(total, data.restaurant.currency)} <span>→</span></button></div></div>;
}

// With `confirm` off the request fires the moment the guest taps a reason; the
// bill reason still waits for the one extra tap that picks card or cash.
function LocalizedWaiterCallSheet({ onClose, onSend, tableNumber, copy, sending, confirm }: { onClose: () => void; onSend: (reason: string, payment?: string) => void; tableNumber: number; copy: GuestCopy; sending: boolean; confirm: boolean }) {
  const [reason, setReason] = useState("");
  const [payment, setPayment] = useState(copy.card);
  const billReason = copy.reasons[2];
  function pickReason(entry: string) {
    if (!confirm && entry !== billReason) {
      onSend(entry);
      return;
    }
    setReason(entry);
  }
  function pickPayment(method: string) {
    setPayment(method);
    if (!confirm) onSend(reason, method);
  }
  return <div className="modal-backdrop sheet-backdrop"><div className="guest-sheet waiter-sheet"><div className="modal-heading"><div><p className="eyebrow">{tableNumber}</p><h2>{copy.callTitle}</h2><p>{copy.callBody}</p></div><button onClick={onClose} disabled={sending}>×</button></div><div className="reason-list">{copy.reasons.map((entry, index) => <button key={entry} disabled={sending} className={reason === entry ? "selected" : ""} onClick={() => pickReason(entry)}><span>{index === 2 ? "▤" : index === 1 ? "≡" : "◔"}</span>{entry}<b>{confirm || entry === billReason ? "✓" : "→"}</b></button>)}</div>{reason === billReason && <div className="payment-choice"><p>{copy.payQuestion}</p><button disabled={sending} className={payment === copy.card ? "active" : ""} onClick={() => pickPayment(copy.card)}>{copy.card}</button><button disabled={sending} className={payment === copy.cash ? "active" : ""} onClick={() => pickPayment(copy.cash)}>{copy.cash}</button></div>}{confirm && <button disabled={!reason || sending} className="primary-button wide" onClick={() => onSend(reason, reason === billReason ? payment : undefined)}>{sending ? "Slanje…" : copy.notify} <span>→</span></button>}</div></div>;
}

function LanguageScreen({ data, locales, tableNumber, onChoose }: { data: RestaurantState; locales: string[]; tableNumber: number; onChoose: (language: string) => void }) {
  const options = locales.map(localeInfo);
  const subtitle = options.filter((entry) => entry.code !== DEFAULT_LOCALE).map((entry) => entry.continueLabel).filter(Boolean).slice(0, 2).join(" · ");
  return <main className="language-screen"><div className="language-art"><div className="language-orb one" /><div className="language-orb two" /><BrandMark restaurant={data.restaurant} className="language-logo" /><p>Dobro došli u</p><h1>{data.restaurant.name}</h1><span>Sto {tableNumber}</span></div><div className="language-card"><p className="eyebrow">Digitalni meni</p><h2>Izaberite jezik</h2>{subtitle && <p>{subtitle}</p>}<div className="language-list">{options.map((entry) => <button key={entry.code} onClick={() => onChoose(entry.code)}><span>{entry.short}</span><p><strong>{entry.native}</strong><small>{entry.continueLabel}</small></p><b>→</b></button>)}</div><small className="language-footnote">Jezik možete promeniti u svakom trenutku.</small></div></main>;
}

function LegacyLanguageScreen({ data, tableNumber, onChoose }: { data: RestaurantState; tableNumber: number; onChoose: (language: string) => void }) {
  return <main className="language-screen"><div className="language-art"><div className="language-orb one" /><div className="language-orb two" /><div className="language-logo">{initials(data.restaurant.name)}</div><p>Dobro došli u</p><h1>{data.restaurant.name}</h1><span>Sto {tableNumber}</span></div><div className="language-card"><p className="eyebrow">Digitalni meni</p><h2>Izaberite jezik</h2><p>Choose your language · Sprache wählen</p><div className="language-list"><button onClick={() => onChoose("SR")}><span>SR</span><p><strong>Srpski</strong><small>Nastavite na srpskom</small></p><b>→</b></button><button onClick={() => onChoose("EN")}><span>EN</span><p><strong>English</strong><small>Continue in English</small></p><b>→</b></button><button onClick={() => onChoose("DE")}><span>DE</span><p><strong>Deutsch</strong><small>Auf Deutsch fortfahren</small></p><b>→</b></button></div><small className="language-footnote">Jezik možete promeniti u svakom trenutku.</small></div></main>;
}

function ProductSheet({ item, currency, onClose, onAdd }: { item: MenuItem; currency: string; onClose: () => void; onAdd: (item: MenuItem, quantity: number, note: string) => void }) {
  const [quantity, setQuantity] = useState(1); const [note, setNote] = useState("");
  return <div className="modal-backdrop sheet-backdrop" onMouseDown={(event) => event.target === event.currentTarget && onClose()}><div className="guest-sheet product-sheet"><button className="sheet-close" onClick={onClose}>×</button><FoodThumb item={item} large /><div className="product-copy"><div className="tag-row">{item.tags.map((tag) => <span key={tag}>{tag}</span>)}</div><h2>{item.name}</h2><p>{item.description}</p><div className="product-meta"><span>{item.portion}</span><strong>{money(item.price, currency)}</strong></div>{item.allergens?.length ? <small>Alergeni: {item.allergens.join(", ")}</small> : null}<label>Napomena za kuhinju <textarea rows={2} placeholder="Npr. bez luka" value={note} onChange={(event) => setNote(event.target.value)} /></label><div className="product-add"><div className="quantity-control"><button onClick={() => setQuantity(Math.max(1, quantity - 1))}>−</button><b>{quantity}</b><button onClick={() => setQuantity(quantity + 1)}>＋</button></div><button className="primary-button" onClick={() => onAdd(item, quantity, note)}>Dodaj · {money(item.price * quantity, currency)}</button></div></div></div></div>;
}

function CartSheet({ data, cart, setCart, total, onClose, onSend, tableNumber }: { data: RestaurantState; cart: CartLine[]; setCart: React.Dispatch<React.SetStateAction<CartLine[]>>; total: number; onClose: () => void; onSend: () => void; tableNumber: number }) {
  function change(item: CartLine, delta: number) { setCart((current) => current.flatMap((entry) => entry === item ? entry.quantity + delta <= 0 ? [] : [{ ...entry, quantity: entry.quantity + delta }] : [entry])); }
  return <div className="modal-backdrop sheet-backdrop"><div className="guest-sheet cart-sheet"><div className="modal-heading"><div><p className="eyebrow">Sto {tableNumber}</p><h2>Vaša porudžbina</h2><p>Proverite stavke pre slanja.</p></div><button onClick={onClose}>×</button></div><div className="cart-lines">{cart.map((item) => <div className="cart-line" key={`${item.id}-${item.note}`}><FoodThumb item={item} /><div><strong>{item.name}</strong><p>{item.portion}{item.note ? ` · ${item.note}` : ""}</p><span>{money(item.price * item.quantity, data.restaurant.currency)}</span></div><div className="quantity-control small"><button onClick={() => change(item, -1)}>−</button><b>{item.quantity}</b><button onClick={() => change(item, 1)}>＋</button></div></div>)}</div><div className="cart-summary"><span>Međuzbir <b>{money(total, data.restaurant.currency)}</b></span><span>Usluga <b>Uključena</b></span><strong>Ukupno <b>{money(total, data.restaurant.currency)}</b></strong></div><div className="pay-note"><span>i</span><p><strong>Plaćanje u restoranu</strong>Porudžbinu plaćate konobaru, karticom ili gotovinom.</p></div><button className="primary-button wide send-order" onClick={onSend}>Pošalji porudžbinu · {money(total, data.restaurant.currency)} <span>→</span></button></div></div>;
}

function WaiterCallSheet({ onClose, onSend, tableNumber }: { onClose: () => void; onSend: (reason: string, payment?: string) => void; tableNumber: number }) {
  const [reason, setReason] = useState(""); const [payment, setPayment] = useState("Kartica");
  const reasons = ["Potrebna mi je pomoć", "Želim da poručim", "Donesite račun", "Drugo"];
  return <div className="modal-backdrop sheet-backdrop"><div className="guest-sheet waiter-sheet"><div className="modal-heading"><div><p className="eyebrow">Sto {tableNumber}</p><h2>Pozovite konobara</h2><p>Recite nam kako možemo da pomognemo.</p></div><button onClick={onClose}>×</button></div><div className="reason-list">{reasons.map((entry) => <button key={entry} className={reason === entry ? "selected" : ""} onClick={() => setReason(entry)}><span>{entry === "Donesite račun" ? "▤" : entry === "Želim da poručim" ? "≡" : "◔"}</span>{entry}<b>✓</b></button>)}</div>{reason === "Donesite račun" && <div className="payment-choice"><p>Kako želite da platite?</p><button className={payment === "Kartica" ? "active" : ""} onClick={() => setPayment("Kartica")}>Kartica</button><button className={payment === "Gotovina" ? "active" : ""} onClick={() => setPayment("Gotovina")}>Gotovina</button></div>}<button disabled={!reason} className="primary-button wide" onClick={() => onSend(reason, reason === "Donesite račun" ? payment : undefined)}>Obavesti konobara <span>→</span></button></div></div>;
}

// Where a surface lives, used both to send an unauthenticated visitor to the
// login page and to send somebody back to the screen their role allows.
function surfacePath(surface: Surface) {
  if (surface === "staff") return "/osoblje";
  if (surface === "waiter") return "/moj-sto";
  if (surface === "bills") return "/racuni";
  return "/admin";
}

// Which roles may open which screen. A waiter has no business on the wall
// screen and a screen account none on the bills page, so each is sent to its
// own home instead of being shown an empty page.
const SURFACE_ROLES: Record<"admin" | "staff" | "waiter" | "bills", AuthRole[]> = {
  admin: ["owner", "manager"],
  bills: ["owner", "manager"],
  staff: ["owner", "manager", "queue"],
  waiter: ["owner", "manager", "staff"],
};

function homeFor(roles: AuthRole[]) {
  if (roles.includes("owner") || roles.includes("manager")) return "/admin";
  if (roles.includes("queue")) return "/osoblje";
  return "/moj-sto";
}

function soundSettingsFrom(restaurant: RestaurantState["restaurant"]): SoundSettings {
  const url = (assetId: string | null) =>
    assetId ? `/api/demo/media/${encodeURIComponent(assetId)}` : null;
  return {
    soundEnabled: restaurant.settings.soundEnabled,
    soundOrder: restaurant.settings.soundOrder,
    soundCall: restaurant.settings.soundCall,
    soundVolume: restaurant.settings.soundVolume,
    soundOrderUrl: url(restaurant.settings.soundOrderAssetId),
    soundCallUrl: url(restaurant.settings.soundCallAssetId),
  };
}

// Rings and, when allowed, raises a notification for every request that was not
// in the list on the previous poll. The very first load is never announced —
// otherwise opening the screen would sound like ten new orders at once.
function useRequestAlerts(input: {
  tickets: Ticket[];
  settings: SoundSettings;
  soundOn: boolean;
  notify: boolean;
}) {
  const seen = useRef<Set<string> | null>(null);
  const { tickets, settings, soundOn, notify } = input;
  useEffect(() => {
    const open = tickets.filter((ticket) => ticket.status === "open");
    if (seen.current === null) {
      seen.current = new Set(open.map((ticket) => ticket.id));
      return;
    }
    const known = seen.current;
    const fresh = open.filter((ticket) => !known.has(ticket.id));
    for (const ticket of open) known.add(ticket.id);
    if (!fresh.length) return;
    // One sound for a burst, so three orders at once do not overlap into noise.
    if (soundOn) {
      playAlert(fresh.some((ticket) => ticket.kind === "order") ? "order" : "call", settings);
    }
    if (!notify) return;
    for (const ticket of fresh.slice(0, 3)) {
      void showLocalNotification(
        ticket.kind === "order"
          ? `Nova porudžbina · Sto ${ticket.tableNumber}`
          : `Poziv konobaru · Sto ${ticket.tableNumber}`,
        ticket.kind === "order"
          ? (ticket.items ?? []).map((line) => `${line.quantity}× ${line.name}`).join(", ") || "Nova porudžbina"
          : ticket.callReason || "Gost traži konobara",
        `${ticket.kind}-${ticket.tableId ?? ticket.id}`,
      );
    }
  }, [tickets, settings, soundOn, notify]);
}

// The control that makes sound possible at all: browsers stay silent until
// somebody clicks. The choice is remembered per device, but a fresh page load
// still needs one click — that is a browser rule, not ours.
function SoundToggle({
  settings,
  soundOn,
  onChange,
}: {
  settings: SoundSettings;
  soundOn: boolean;
  onChange: (value: boolean) => void;
}) {
  async function toggle() {
    if (soundOn) {
      onChange(false);
      rememberSoundChoice(false);
      return;
    }
    const ready = await unlockAudio();
    onChange(ready);
    rememberSoundChoice(ready);
    // Play it straight away, so it is obvious the click worked.
    if (ready) playAlert("order", { ...settings, soundEnabled: true });
  }

  return <button
    className={soundOn ? "active" : "needs-click"}
    onClick={() => void toggle()}
    title={soundOn ? "Zvuk je uključen" : "Kliknite da uključite zvuk na ovom uređaju"}
  >
    {soundOn ? "◕ Zvuk" : "◔ Uključi zvuk"}
  </button>;
}

// Waiter-only surface: phone-sized, shows just the tables this account covers.
function WaiterApp({ data, postAction, setToast, account, slug, onLogout }: SharedProps & { account: AuthUser | null; slug: string; onLogout: () => void }) {
  const [confirmVisit, setConfirmVisit] = useState<TableVisit | null>(null);
  const [orderVisit, setOrderVisit] = useState<TableVisit | null>(null);
  const [showFree, setShowFree] = useState(false);
  const [busyTableId, setBusyTableId] = useState<string | null>(null);
  const [confirmTicket, setConfirmTicket] = useState<Ticket | null>(null);
  const [detailTicket, setDetailTicket] = useState<Ticket | null>(null);
  const [expandedBills, setExpandedBills] = useState<Set<string>>(new Set());
  const [confirmReleaseAll, setConfirmReleaseAll] = useState(false);
  const [soundOn, setSoundOn] = useState(false);
  const currency = data.restaurant.currency;
  const myEmail = (account?.email ?? "").toLowerCase();
  const sound = soundSettingsFrom(data.restaurant);

  const mine = data.tables.filter((table) => table.active && table.waiter?.email.toLowerCase() === myEmail);
  const mineIds = new Set(mine.map((table) => table.id));
  const free = data.tables.filter((table) => table.active && !table.waiter);
  const myVisits = data.visits.filter((visit) => visit.status === "open" && mineIds.has(visit.tableId));
  const myTickets = data.tickets.filter((ticket) => ticket.status === "open" && ticket.tableId && mineIds.has(ticket.tableId));
  const myTotal = myVisits.reduce((sum, visit) => sum + visit.total, 0);

  useEffect(() => {
    setSoundOn(storedSoundChoice());
  }, []);

  // Only my own tables ring. Somebody else's order is not my problem, and a
  // phone that buzzes for the whole restaurant gets silenced within a day.
  useRequestAlerts({
    tickets: data.tickets.filter((ticket) => ticket.tableId && mineIds.has(ticket.tableId)),
    settings: sound,
    soundOn: soundOn && data.restaurant.settings.soundEnabled,
    notify: true,
  });

  async function releaseAll() {
    setConfirmReleaseAll(false);
    const ok = await postAction("releaseAllTables", {});
    if (ok) setToast("Svi stolovi su uklonjeni sa vašeg spiska.");
  }

  function ticketsFor(tableId: string) {
    return myTickets.filter((ticket) => ticket.tableId === tableId);
  }
  function visitFor(tableId: string) {
    return myVisits.find((visit) => visit.tableId === tableId) ?? null;
  }

  async function claim(table: DiningTable) {
    setBusyTableId(table.id);
    const ok = await postAction("claimTable", { tableId: table.id });
    setBusyTableId(null);
    if (ok) setToast(`Preuzeli ste sto ${table.number}.`);
  }

  async function release(table: DiningTable) {
    setBusyTableId(table.id);
    const ok = await postAction("releaseTable", { tableId: table.id });
    setBusyTableId(null);
    if (ok) setToast(`Sto ${table.number} više nije vaš.`);
  }

  async function resolve(ticket: Ticket) {
    if (!(await postAction("resolveTicket", { ticketId: ticket.id }))) return;
    setConfirmTicket(null);
    setToast("Zahtev je završen.");
  }

  function requestResolve(ticket: Ticket) {
    if (data.restaurant.settings.confirmBeforeResolve) setConfirmTicket(ticket);
    else void resolve(ticket);
  }

  async function closeVisit(visit: TableVisit) {
    setBusyTableId(visit.tableId);
    const ok = await postAction("closeTableVisit", { visitId: visit.id });
    setBusyTableId(null);
    if (!ok) return;
    setConfirmVisit(null);
    setToast(`Sto ${visit.tableNumber}: račun je zatvoren.`);
  }

  return <div className="waiter-page">
    <header className="waiter-header">
      <div className="staff-brand"><BrandMark restaurant={data.restaurant} /><div><strong>{account?.displayName || "Moji stolovi"}</strong><small>{data.restaurant.name}</small></div></div>
      <div className="waiter-header-actions">
        <SoundToggle settings={sound} soundOn={soundOn} onChange={setSoundOn} />
        <NotificationButton slug={slug} setToast={setToast} />
        <button onClick={onLogout} title="Odjavi se">{initials(account?.displayName || "Konobar")}</button>
      </div>
    </header>

    <WaiterShifts slug={slug} />

    <div className="waiter-summary">
      <div><b>{mine.length}</b><span>mojih stolova</span></div>
      <div><b>{myTickets.length}</b><span>otvorenih zahteva</span></div>
      <div><b>{money(myTotal, currency)}</b><span>na mojim računima</span></div>
    </div>

    <main className="waiter-main">
      {mine.length === 0 && <div className="waiter-empty">
        <h2>Nemate dodeljenih stolova</h2>
        <p>Menadžer vam može dodeliti stolove, ili ih preuzmite sami ispod.</p>
      </div>}

      {mine.map((table) => {
        const visit = visitFor(table.id);
        const tickets = ticketsFor(table.id);
        return <article className={`waiter-table ${tickets.length ? "has-requests" : ""}`} key={table.id}>
          <header>
            <div><strong>Sto {table.number}</strong><small>{table.zone} · {table.seats} mesta</small></div>
            <div className="waiter-table-total">{visit ? money(visit.total, currency) : <em>slobodan</em>}</div>
          </header>

          {tickets.length > 0 && <div className="waiter-requests">
            {tickets.map((ticket) => <div
              key={ticket.id}
              className={`${ticket.kind} openable`}
              role="button"
              tabIndex={0}
              title="Otvori detalje zahteva"
              onClick={() => setDetailTicket(ticket)}
              onKeyDown={(event) => { if (event.key === "Enter" || event.key === " ") setDetailTicket(ticket); }}
            >
              <span>{ticket.kind === "call" ? "◔" : "≡"}</span>
              <p><strong>{ticket.kind === "call" ? (ticket.callReason || "Poziv konobaru") : `Porudžbina · ${money(ticket.total ?? 0, currency)}`}</strong><small>{timeAgo(ticket.createdAt)}{ticket.kind === "order" && ticket.items?.length ? ` · ${ticket.items.length} ${ticket.items.length === 1 ? "stavka" : "stavki"} — dodirnite za sve` : ticket.note ? ` · ${ticket.note}` : ""}</small></p>
              <button type="button" onClick={(event) => { event.stopPropagation(); requestResolve(ticket); }}>✓</button>
            </div>)}
          </div>}

          {visit && visit.items.length > 0 && <div className="waiter-items">
            {(expandedBills.has(visit.id) ? visit.items : visit.items.slice(0, 3)).map((line, index) => <div key={`${line.name}-${index}`} className={line.source === "staff" ? "staff-entered" : ""}>
              <b>{line.quantity}×</b><span>{line.name}{line.variantLabel ? ` · ${line.variantLabel}` : ""}</span><em>{money(line.lineTotal, currency)}</em>
            </div>)}
            {visit.items.length > 3 && <button
              type="button"
              className="waiter-items-more"
              onClick={() => setExpandedBills((current) => {
                const next = new Set(current);
                if (next.has(visit.id)) next.delete(visit.id);
                else next.add(visit.id);
                return next;
              })}
            >
              {expandedBills.has(visit.id) ? "▴ Sakrij" : `▾ Još ${visit.items.length - 3} ${visit.items.length - 3 === 1 ? "stavka" : "stavki"} — prikaži sve`}
            </button>}
          </div>}

          <div className="waiter-table-actions">
            {visit
              ? <>
                  <button type="button" className="ghost" onClick={() => setOrderVisit(visit)}>＋ Stavka</button>
                  <button type="button" disabled={busyTableId === table.id} onClick={() => setConfirmVisit(visit)}>✓ Gost je otišao</button>
                </>
              : <p className="waiter-idle">Račun se otvara čim gost poruči ili pozove.</p>}
            {table.claimedEmail?.toLowerCase() === myEmail && <button type="button" className="text-button" disabled={busyTableId === table.id} onClick={() => void release(table)}>Otpusti sto</button>}
          </div>
        </article>;
      })}

      <section className="waiter-free">
        <button type="button" className="waiter-free-toggle" onClick={() => setShowFree(!showFree)}>
          {showFree ? "▲ Sakrij slobodne stolove" : `▼ Dodeli sebi sto (${free.length} slobodnih)`}
        </button>
        {showFree && (free.length > 0
          ? <div className="waiter-free-grid">{free.map((table) => <button type="button" key={table.id} disabled={busyTableId === table.id} onClick={() => void claim(table)}>
              <strong>Sto {table.number}</strong><small>{table.zone}</small>
            </button>)}</div>
          : <p className="waiter-idle">Svi stolovi već imaju konobara.</p>)}
        <p className="waiter-idle">Dodeljeni sto ostaje vaš i posle odjave, dok ga ne uklonite vi ili menadžer.</p>
      </section>

      {mine.length > 0 && <button type="button" className="waiter-release-all" onClick={() => setConfirmReleaseAll(true)}>
        Ukloni sve moje stolove ({mine.length})
      </button>}
    </main>

    {orderVisit && <StaffOrderModal data={data} visit={orderVisit} postAction={postAction} setToast={setToast} onClose={() => setOrderVisit(null)} />}
    {detailTicket && <WaiterTicketModal
      ticket={detailTicket}
      visit={detailTicket.tableId ? visitFor(detailTicket.tableId) : null}
      currency={currency}
      onClose={() => setDetailTicket(null)}
      onResolve={detailTicket.status === "open" ? () => { setDetailTicket(null); requestResolve(detailTicket); } : undefined}
    />}
    {confirmTicket && <div className="modal-backdrop"><div className="confirm-modal"><span>?</span><p className="eyebrow">Potvrda</p><h2>Završiti ovaj zahtev?</h2><p>Zahtev za Sto {confirmTicket.tableNumber} biće uklonjen iz reda, ali račun ostaje otvoren.</p><div><button className="secondary-button" onClick={() => setConfirmTicket(null)}>Otkaži</button><button className="primary-button" onClick={() => void resolve(confirmTicket)}>Da, završi</button></div></div></div>}
    {confirmVisit && <div className="modal-backdrop"><div className="confirm-modal visit-confirm"><span>✓</span><p className="eyebrow">Zatvaranje računa</p><h2>Gost sa stola {confirmVisit.tableNumber} je otišao?</h2><p>Zatvara se račun od <strong>{money(confirmVisit.total, currency)}</strong>. Sledeća porudžbina sa ovog QR koda otvara nov račun.</p><div><button className="secondary-button" disabled={busyTableId === confirmVisit.tableId} onClick={() => setConfirmVisit(null)}>Otkaži</button><button className="primary-button" disabled={busyTableId === confirmVisit.tableId} onClick={() => void closeVisit(confirmVisit)}>Da, gost je otišao</button></div></div></div>}
    {confirmReleaseAll && <div className="modal-backdrop"><div className="confirm-modal"><span>?</span><p className="eyebrow">Potvrda</p><h2>Ukloniti sve vaše stolove?</h2><p>Svi vaši stolovi ({mine.length}) postaju slobodni za druge konobare. Otvoreni računi ostaju otvoreni. Uklanjaju se i stolovi koje vam je dodelio menadžer.</p><div><button className="secondary-button" onClick={() => setConfirmReleaseAll(false)}>Otkaži</button><button className="primary-button" onClick={() => void releaseAll()}>Da, ukloni sve</button></div></div></div>}
  </div>;
}

// Everything the waiter needs before touching a table: the full request (every
// item, note and payment choice) plus the table's whole running bill, with the
// items from this request being the newest. Opened by tapping a request row.
function WaiterTicketModal({ ticket, visit, currency, onClose, onResolve }: {
  ticket: Ticket;
  visit: TableVisit | null;
  currency: string;
  onClose: () => void;
  onResolve?: () => void;
}) {
  const lines = ticket.items ?? [];
  return <div className="modal-backdrop sheet-backdrop" onClick={onClose}>
    <div className="waiter-ticket-modal" onClick={(event) => event.stopPropagation()}>
      <header>
        <div>
          <p className="eyebrow">Sto {ticket.tableNumber}{ticket.zone ? ` · ${ticket.zone}` : ""}</p>
          <h2>{ticket.kind === "call" ? "Poziv konobaru" : "Porudžbina"}</h2>
          <small>{timeAgo(ticket.createdAt)} · {parseServerDate(ticket.createdAt).toLocaleTimeString("sr-RS", { hour: "2-digit", minute: "2-digit" })}{ticket.source === "staff" && ticket.createdBy ? ` · uneo ${ticket.createdBy}` : ""}</small>
        </div>
        <button type="button" onClick={onClose}>×</button>
      </header>

      {ticket.kind === "call"
        ? <div className="waiter-ticket-call">
            <p><strong>{ticket.callReason || "Gost traži konobara"}</strong></p>
            {ticket.paymentMethod && <p>Plaćanje: <strong>{ticket.paymentMethod}</strong></p>}
          </div>
        : <section>
            <h3>U ovom zahtevu {lines.length ? <span>{lines.length}</span> : null}</h3>
            {lines.length
              ? <ul className="waiter-ticket-items">
                  {lines.map((line, index) => <li key={`${line.name}-${index}`}>
                    <b>{line.quantity}×</b>
                    <span>{line.name}{line.portion ? ` · ${line.portion}` : ""}{line.note && <small>Napomena: {line.note}</small>}</span>
                    <em>{money((line.price ?? 0) * line.quantity, currency)}</em>
                  </li>)}
                </ul>
              : <p className="waiter-idle">Porudžbina bez pojedinačnih stavki.</p>}
            {typeof ticket.total === "number" && <p className="waiter-ticket-total">Ovaj zahtev: <strong>{money(ticket.total, currency)}</strong></p>}
          </section>}
      {ticket.note && ticket.note !== ticket.callReason && <p className="waiter-ticket-note">✎ {ticket.note}</p>}

      {visit && visit.items.length > 0 && <section>
        <h3>Sve što je sto poručio <span>{visit.items.length}</span></h3>
        <ul className="waiter-ticket-items bill">
          {visit.items.map((line, index) => <li key={`${line.name}-${line.variantLabel}-${index}`} className={line.source === "staff" ? "staff-entered" : ""}>
            <b>{line.quantity}×</b>
            <span>{line.name}{line.variantLabel ? ` · ${line.variantLabel}` : ""}</span>
            <em>{money(line.lineTotal, currency)}</em>
          </li>)}
        </ul>
        <p className="waiter-ticket-total">Račun stola ukupno: <strong>{money(visit.total, currency)}</strong>{visit.openTicketCount > 1 ? ` · još ${visit.openTicketCount - 1} otvoren zahtev` : ""}</p>
      </section>}

      <footer>
        <button type="button" className="secondary-button" onClick={onClose}>Zatvori</button>
        {onResolve && <button type="button" className="primary-button" onClick={onResolve}>✓ Završi zahtev</button>}
      </footer>
    </div>
  </div>;
}

// Web push is per device, so each waiter switches it on once per phone. It is a
// single bell in the header rather than a banner: on a phone, every row of
// explanation is a table the waiter cannot see.
function NotificationButton({ slug, setToast }: { slug: string; setToast: (value: string) => void }) {
  const [state, setState] = useState<"loading" | "off" | "on" | "blocked" | "unavailable">("loading");
  const [publicKey, setPublicKey] = useState("");
  const [busy, setBusy] = useState(false);
  const [open, setOpen] = useState(false);

  useEffect(() => {
    let active = true;
    async function check() {
      if (!pushSupported()) {
        if (active) setState("unavailable");
        return;
      }
      const response = await fetch(`/api/push?slug=${encodeURIComponent(slug)}`, {
        cache: "no-store",
        credentials: "same-origin",
      }).catch(() => null);
      const payload = await response?.json().catch(() => null);
      if (!active) return;
      if (!payload?.ok || !payload.available) {
        setState("unavailable");
        return;
      }
      setPublicKey(String(payload.publicKey));
      if (notificationState() === "denied") {
        setState("blocked");
        return;
      }
      setState((await hasPushSubscription()) ? "on" : "off");
    }
    void check();
    return () => { active = false; };
  }, [slug]);

  async function enable() {
    setBusy(true);
    const result = await subscribeToPush(slug, publicKey);
    setBusy(false);
    if (result.ok) {
      setState("on");
      setToast("Notifikacije su uključene na ovom telefonu.");
      return;
    }
    if (result.reason === "denied") {
      setState("blocked");
      setToast("Notifikacije su blokirane u pretraživaču za ovaj sajt.");
      return;
    }
    setToast(
      typeof result.reason === "string" && result.reason.length > 12
        ? result.reason
        : "Notifikacije nisu uključene. Pokušajte ponovo.",
    );
  }

  async function disable() {
    setBusy(true);
    await unsubscribeFromPush(slug);
    setBusy(false);
    setState("off");
    setOpen(false);
    setToast("Notifikacije su isključene na ovom telefonu.");
  }

  async function test() {
    setBusy(true);
    const response = await fetch("/api/push", {
      method: "POST",
      credentials: "same-origin",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ action: "test", slug }),
    });
    const payload = await response.json().catch(() => null);
    setBusy(false);
    setOpen(false);
    setToast(
      response.ok && payload?.ok
        ? "Probna notifikacija je poslata."
        : payload?.error?.message ?? "Proba nije uspela.",
    );
  }

  if (state === "loading" || state === "unavailable") return null;

  return <span className="waiter-bell">
    <button
      type="button"
      className={state === "on" ? "active" : state === "blocked" ? "blocked" : ""}
      disabled={busy}
      title={state === "on"
        ? "Notifikacije su uključene"
        : state === "blocked"
          ? "Notifikacije su blokirane u pretraživaču"
          : "Uključi notifikacije"}
      aria-label="Notifikacije"
      onClick={() => {
        if (state === "off") void enable();
        else if (state === "blocked") setToast("Dozvolite notifikacije u podešavanjima pretraživača za ovaj sajt, pa osvežite stranicu.");
        else setOpen(!open);
      }}
    >
      {state === "on" ? "◉" : state === "blocked" ? "⃠" : "◎"}
    </button>
    {open && state === "on" && <div className="waiter-bell-menu">
      <button type="button" disabled={busy} onClick={() => void test()}>Probna notifikacija</button>
      <button type="button" disabled={busy} onClick={() => void disable()}>Isključi notifikacije</button>
    </div>}
  </span>;
}

// The operations screen is a queue and nothing else: what is waiting, right
// now, big enough to read across a room. Bills, table totals and manual order
// entry moved to the manager's page at /racuni, because a wall screen that also
// tries to be a cash register is unreadable at both jobs.
function StaffApp({ data, postAction, setToast, account, onLogout }: SharedProps & { account: AuthUser | null; onLogout: () => void }) {
  const [tab, setTab] = useState<"active" | "history">("active");
  const [filter, setFilter] = useState<"all" | TicketKind>("all");
  const [zone, setZone] = useState("Sve zone");
  const [confirmTicket, setConfirmTicket] = useState<Ticket | null>(null);
  const [lastResolved, setLastResolved] = useState<Ticket | null>(null);
  const [soundOn, setSoundOn] = useState(false);
  const [now, setNow] = useState<Date | null>(null);
  const [isFullscreen, setIsFullscreen] = useState(false);
  const sound = soundSettingsFrom(data.restaurant);

  const open = data.tickets.filter((ticket) => ticket.status === "open");
  const history = data.tickets.filter((ticket) => ticket.status === "done");
  const zones = ["Sve zone", ...new Set(data.tables.map((table) => table.zone))];
  const shown = (tab === "active" ? open : history)
    .filter((ticket) => (filter === "all" || ticket.kind === filter) && (zone === "Sve zone" || ticket.zone === zone))
    .sort((a, b) => parseServerDate(a.createdAt).getTime() - parseServerDate(b.createdAt).getTime());

  useRequestAlerts({
    // A zone-filtered screen only rings for its own zone: that is the whole
    // point of splitting the floor across two screens.
    tickets: zone === "Sve zone" ? data.tickets : data.tickets.filter((ticket) => ticket.zone === zone),
    settings: sound,
    soundOn: soundOn && data.restaurant.settings.soundEnabled,
    notify: false,
  });

  // Rendered after mount so the server-side markup and the first client render
  // agree; a clock baked into HTML is wrong the moment it is sent.
  useEffect(() => {
    setNow(new Date());
    const timer = window.setInterval(() => setNow(new Date()), 20_000);
    return () => window.clearInterval(timer);
  }, []);

  // A wall screen should fill the display. Browsers refuse fullscreen without a
  // user gesture, so "immediately on open" becomes "on the very first touch" —
  // usually the click that turns the sound on anyway.
  useEffect(() => {
    function onChange() { setIsFullscreen(Boolean(document.fullscreenElement)); }
    function onFirstTap() {
      if (!document.fullscreenElement && document.fullscreenEnabled) {
        document.documentElement.requestFullscreen().catch(() => undefined);
      }
    }
    document.addEventListener("fullscreenchange", onChange);
    document.addEventListener("pointerdown", onFirstTap, { once: true });
    return () => {
      document.removeEventListener("fullscreenchange", onChange);
      document.removeEventListener("pointerdown", onFirstTap);
    };
  }, []);

  function toggleFullscreen() {
    if (document.fullscreenElement) void document.exitFullscreen().catch(() => undefined);
    else void document.documentElement.requestFullscreen().catch(() => undefined);
  }

  function requestResolve(ticket: Ticket) {
    if (data.restaurant.settings.confirmBeforeResolve) setConfirmTicket(ticket);
    else void resolve(ticket);
  }

  async function resolve(ticket: Ticket) {
    if (!(await postAction("resolveTicket", { ticketId: ticket.id }))) return;
    setConfirmTicket(null);
    setLastResolved(ticket);
    setToast("Zahtev je završen.");
  }

  async function undo() {
    if (!lastResolved) return;
    if (await postAction("undoTicket", { ticketId: lastResolved.id })) {
      setLastResolved(null);
      setToast("Zahtev je vraćen u red.");
    }
  }

  return <div className="staff-page queue-only">
    <header className="staff-header">
      <div className="staff-brand"><BrandMark restaurant={data.restaurant} /><div><strong>{data.restaurant.name}</strong><small>Red zahteva</small></div></div>
      <div className="staff-status">
        <span><i /> Uživo</span>
        <SoundToggle settings={sound} soundOn={soundOn} onChange={setSoundOn} />
        {now && <span className="queue-clock">{now.toLocaleDateString("sr-Latn-RS", { weekday: "short", day: "numeric", month: "short" })} · {now.toLocaleTimeString("sr-RS", { hour: "2-digit", minute: "2-digit" })}</span>}
        <button className="queue-fullscreen" onClick={toggleFullscreen} title={isFullscreen ? "Izađi iz celog ekrana" : "Ceo ekran"}>{isFullscreen ? "▣" : "⛶"}</button>
        <button onClick={onLogout} title="Odjavi se">{initials(account?.displayName || "Ekran")}</button>
      </div>
    </header>
    <main className="staff-main">
      <div className="staff-toolbar">
        <div className="staff-tabs">
          <button className={tab === "active" ? "active" : ""} onClick={() => setTab("active")}>Aktivno <span>{open.length}</span></button>
          <button className={tab === "history" ? "active" : ""} onClick={() => setTab("history")}>Istorija <span>{history.length}</span></button>
        </div>
        <div className="staff-filters">
          <button className={filter === "all" ? "active" : ""} onClick={() => setFilter("all")}>Sve</button>
          <button className={filter === "order" ? "active" : ""} onClick={() => setFilter("order")}>Porudžbine</button>
          <button className={filter === "call" ? "active" : ""} onClick={() => setFilter("call")}>Pozivi</button>
          <select value={zone} onChange={(event) => setZone(event.target.value)} aria-label="Rejon">
            {zones.map((entry) => <option key={entry}>{entry}</option>)}
          </select>
        </div>
      </div>
      {shown.length > 0
        ? <section className="queue-list">{shown.map((ticket) => <QueueRow key={ticket.id} ticket={ticket} currency={data.restaurant.currency} showTotal={data.restaurant.settings.showTotal} onResolve={() => requestResolve(ticket)} />)}</section>
        : <div className="empty-queue"><span>✓</span><h2>{tab === "active" ? "Sve je rešeno" : "Nema istorije"}</h2><p>{tab === "active" ? "Trenutno nema zahteva u redu." : "Ovde se prikazuju završeni zahtevi ove smene."}</p></div>}
    </main>
    {lastResolved && <div className="undo-bar"><span>✓ Zahtev za Sto {lastResolved.tableNumber} je završen.</span><button onClick={undo}>Vrati</button></div>}
    {confirmTicket && <div className="modal-backdrop"><div className="confirm-modal"><span>?</span><p className="eyebrow">Potvrda</p><h2>Završiti ovaj zahtev?</h2><p>Zahtev za Sto {confirmTicket.tableNumber} biće uklonjen iz reda.</p><div><button className="secondary-button" onClick={() => setConfirmTicket(null)}>Otkaži</button><button className="primary-button" onClick={() => void resolve(confirmTicket)}>Da, završi</button></div></div></div>}
  </div>;
}

// Manager view: every open bill and request in the restaurant, filtered by zone
// or by waiter, plus three months of history that can be exported.
function BillsApp({ data, postAction, setToast, account, onLogout, slug, restaurants, setSlug }: SharedProps & {
  account: AuthUser | null;
  onLogout: () => void;
  slug: string;
  restaurants: AuthRestaurant[];
  setSlug: (value: string) => void;
}) {
  const [tab, setTab] = useState<"active" | "history" | "shifts" | "team">("active");
  const [zone, setZone] = useState("Sve zone");
  const [waiter, setWaiter] = useState("Svi konobari");
  const isOwner = restaurants.find((entry) => entry.slug === slug)?.role === "owner";
  const [confirmVisit, setConfirmVisit] = useState<TableVisit | null>(null);
  const [confirmTicket, setConfirmTicket] = useState<Ticket | null>(null);
  const [orderVisit, setOrderVisit] = useState<TableVisit | null>(null);
  const [closingVisitId, setClosingVisitId] = useState<string | null>(null);
  const currency = data.restaurant.currency;

  const waiterFor = (tableId: string | null) => {
    const table = data.tables.find((entry) => entry.id === tableId);
    return table?.waiter?.name ?? "";
  };
  const zoneFor = (tableId: string | null) =>
    data.tables.find((entry) => entry.id === tableId)?.zone ?? "";

  const waiterNames = [
    "Svi konobari",
    "Bez konobara",
    ...new Set(data.tables.flatMap((table) => (table.waiter ? [table.waiter.name] : []))),
  ];
  const zones = ["Sve zone", ...new Set(data.tables.map((table) => table.zone))];

  function matches(tableId: string | null) {
    if (zone !== "Sve zone" && zoneFor(tableId) !== zone) return false;
    if (waiter === "Svi konobari") return true;
    const name = waiterFor(tableId);
    return waiter === "Bez konobara" ? name === "" : name === waiter;
  }

  const visits = data.visits
    .filter((visit) => visit.status === "open" && matches(visit.tableId))
    .sort((a, b) => parseServerDate(a.openedAt).getTime() - parseServerDate(b.openedAt).getTime());
  const openTickets = data.tickets
    .filter((ticket) => ticket.status === "open" && matches(ticket.tableId ?? null))
    .sort((a, b) => parseServerDate(a.createdAt).getTime() - parseServerDate(b.createdAt).getTime());
  const billsTotal = visits.reduce((sum, visit) => sum + visit.total, 0);

  async function resolve(ticket: Ticket) {
    if (!(await postAction("resolveTicket", { ticketId: ticket.id }))) return;
    setConfirmTicket(null);
    setToast("Zahtev je završen. Račun stola ostaje otvoren.");
  }

  async function closeVisit(visit: TableVisit) {
    setClosingVisitId(visit.id);
    const success = await postAction("closeTableVisit", { visitId: visit.id });
    setClosingVisitId(null);
    if (!success) return;
    setConfirmVisit(null);
    setToast(`Sto ${visit.tableNumber}: račun je zatvoren.`);
  }

  return <div className="bills-page">
    <header className="staff-header">
      <div className="staff-brand"><BrandMark restaurant={data.restaurant} /><div><strong>{data.restaurant.name}</strong><small>Računi i zahtevi</small></div></div>
      <div className="staff-status">
        {restaurants.length > 1 && <select value={slug} onChange={(event) => setSlug(event.target.value)} aria-label="Restoran">
          {restaurants.map((entry) => <option key={entry.slug} value={entry.slug}>{entry.name}</option>)}
        </select>}
        <a className="bills-link" href={`/admin?restaurant=${encodeURIComponent(slug)}`}>Admin panel</a>
        <button onClick={onLogout} title="Odjavi se">{initials(account?.displayName || "Menadžer")}</button>
      </div>
    </header>

    <main className="bills-main">
      <div className="bills-heading">
        <div><p className="eyebrow">Pregled sale</p><h1>Aktivni računi i zahtevi</h1><p>Sve što je trenutno otvoreno, po rejonu ili po konobaru. Porudžbine istog stola sabiraju se do odlaska gosta.</p></div>
        <div className="bills-totals">
          <div><b>{visits.length}</b><span>otvorenih računa</span></div>
          <div><b>{money(billsTotal, currency)}</b><span>ukupno otvoreno</span></div>
          <div><b>{openTickets.length}</b><span>zahteva u redu</span></div>
        </div>
      </div>

      <div className="bills-filters">
        <div className="staff-tabs">
          <button className={tab === "active" ? "active" : ""} onClick={() => setTab("active")}>Aktivno</button>
          <button className={tab === "history" ? "active" : ""} onClick={() => setTab("history")}>Istorija i izvoz</button>
          <button className={tab === "shifts" ? "active" : ""} onClick={() => setTab("shifts")}>Smene</button>
          <button className={tab === "team" ? "active" : ""} onClick={() => setTab("team")}>Nalozi</button>
        </div>
        {tab === "active" && <div className="bills-selects">
          <label>Rejon<select value={zone} onChange={(event) => setZone(event.target.value)}>{zones.map((entry) => <option key={entry}>{entry}</option>)}</select></label>
          <label>Konobar<select value={waiter} onChange={(event) => setWaiter(event.target.value)}>{waiterNames.map((entry) => <option key={entry}>{entry}</option>)}</select></label>
        </div>}
      </div>

      {tab === "team"
        ? <TeamManager slug={slug} enabled={isOwner} setToast={setToast} />
        : tab === "shifts"
        ? <ShiftBoard data={data} slug={slug} setToast={setToast} />
        : tab === "history"
        ? <HistoryPanel slug={slug} currency={currency} zones={zones.slice(1)} />
        : <>
            <section className="bills-section">
              <h2>Zahtevi u redu <span>{openTickets.length}</span></h2>
              {openTickets.length
                ? <div className="ticket-grid">{openTickets.map((ticket) => <TicketCard
                    key={ticket.id}
                    ticket={ticket}
                    currency={currency}
                    showTotal={data.restaurant.settings.showTotal}
                    onResolve={() => data.restaurant.settings.confirmBeforeResolve ? setConfirmTicket(ticket) : void resolve(ticket)}
                  />)}</div>
                : <p className="bills-empty">Nema zahteva za ovaj filter.</p>}
            </section>

            <section className="bills-section">
              <h2>Otvoreni računi <span>{visits.length}</span></h2>
              {visits.length
                ? <div className="open-bills-grid">{visits.map((visit) => <div key={visit.id} className="bills-card-wrap">
                    <OpenBillCard
                      visit={visit}
                      currency={currency}
                      onAddItem={() => setOrderVisit(visit)}
                      onCloseVisit={() => setConfirmVisit(visit)}
                    />
                    <small className="bills-card-meta">{zoneFor(visit.tableId) || "bez rejona"} · {waiterFor(visit.tableId) || "bez konobara"}</small>
                  </div>)}</div>
                : <p className="bills-empty">Nema otvorenih računa za ovaj filter.</p>}
            </section>
          </>}
    </main>

    {orderVisit && <StaffOrderModal data={data} visit={orderVisit} postAction={postAction} setToast={setToast} onClose={() => setOrderVisit(null)} />}
    {confirmTicket && <div className="modal-backdrop"><div className="confirm-modal"><span>?</span><p className="eyebrow">Potvrda</p><h2>Završiti ovaj zahtev?</h2><p>Zahtev za Sto {confirmTicket.tableNumber} biće uklonjen iz reda, ali račun ostaje otvoren.</p><div><button className="secondary-button" onClick={() => setConfirmTicket(null)}>Otkaži</button><button className="primary-button" onClick={() => void resolve(confirmTicket)}>Da, završi</button></div></div></div>}
    {confirmVisit && <div className="modal-backdrop"><div className="confirm-modal visit-confirm"><span>✓</span><p className="eyebrow">Zatvaranje računa</p><h2>Gost sa stola {confirmVisit.tableNumber} je otišao?</h2><p>Zatvara se ukupan račun od <strong>{money(confirmVisit.total, currency)}</strong>. Sledeća porudžbina sa tog QR koda otvara nov račun.</p><div><button className="secondary-button" disabled={closingVisitId === confirmVisit.id} onClick={() => setConfirmVisit(null)}>Otkaži</button><button className="primary-button" disabled={closingVisitId === confirmVisit.id} onClick={() => void closeVisit(confirmVisit)}>{closingVisitId === confirmVisit.id ? "Zatvaranje…" : "Da, gost je otišao"}</button></div></div></div>}
  </div>;
}

type HistoryRow = {
  id: string;
  createdAt: string;
  kind: "order" | "call";
  source: "guest" | "staff";
  status: "open" | "resolved";
  tableNumber: number;
  zone: string;
  waiterName: string;
  waiterEmail: string;
  resolvedBy: string;
  note: string;
  totalMinor: number;
  items: string;
};

function isoDay(offsetDays: number) {
  return new Date(Date.now() - offsetDays * 86_400_000).toISOString().slice(0, 10);
}

// Three months back, filtered the same way as the live view, with the same rows
// available as a CSV that opens straight in Excel.
function HistoryPanel({ slug, currency, zones }: { slug: string; currency: string; zones: string[] }) {
  const [from, setFrom] = useState(isoDay(30));
  const [to, setTo] = useState(isoDay(0));
  const [zone, setZone] = useState("");
  const [waiterEmail, setWaiterEmail] = useState("");
  const [kind, setKind] = useState("");
  const [rows, setRows] = useState<HistoryRow[] | null>(null);
  const [waiters, setWaiters] = useState<{ email: string; name: string }[]>([]);
  const [summary, setSummary] = useState<{ orderCount: number; callCount: number; revenueMinor: number } | null>(null);
  const [truncated, setTruncated] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [busy, setBusy] = useState(false);

  function query() {
    const params = new URLSearchParams({ slug, from, to });
    if (zone) params.set("zone", zone);
    if (waiterEmail) params.set("waiter", waiterEmail);
    if (kind) params.set("kind", kind);
    return params;
  }

  async function load() {
    setBusy(true);
    setError(null);
    try {
      const response = await fetch(`/api/reports/history?${query().toString()}`, {
        cache: "no-store",
        credentials: "same-origin",
      });
      const payload = await response.json().catch(() => null);
      if (!response.ok || !payload?.ok) {
        setError(payload?.error?.message ?? "Istorija nije dostupna.");
        setRows([]);
        return;
      }
      setRows(payload.rows);
      setWaiters(payload.waiters);
      setSummary(payload.summary);
      setTruncated(Boolean(payload.truncated));
    } catch {
      setError("Server trenutno nije dostupan.");
      setRows([]);
    } finally {
      setBusy(false);
    }
  }

  useEffect(() => {
    void load();
    // Refetching on every keystroke in a date field would hammer the server, so
    // the filters have an explicit button.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [slug]);

  return <section className="history-panel">
    <div className="history-filters">
      <label>Od<input type="date" value={from} max={to} onChange={(event) => setFrom(event.target.value)} /></label>
      <label>Do<input type="date" value={to} min={from} onChange={(event) => setTo(event.target.value)} /></label>
      <label>Rejon<select value={zone} onChange={(event) => setZone(event.target.value)}><option value="">Svi rejoni</option>{zones.map((entry) => <option key={entry} value={entry}>{entry}</option>)}</select></label>
      <label>Konobar<select value={waiterEmail} onChange={(event) => setWaiterEmail(event.target.value)}><option value="">Svi konobari</option>{waiters.map((entry) => <option key={entry.email} value={entry.email}>{entry.name}</option>)}</select></label>
      <label>Tip<select value={kind} onChange={(event) => setKind(event.target.value)}><option value="">Sve</option><option value="order">Porudžbine</option><option value="call">Pozivi konobaru</option></select></label>
      <div className="history-actions">
        <button className="primary-button" onClick={() => void load()} disabled={busy}>{busy ? "Tražimo…" : "Prikaži"}</button>
        <a
          className="secondary-button"
          href={`/api/reports/history?${query().toString()}&format=csv`}
          download
        >
          ⤓ Izvezi CSV
        </a>
      </div>
    </div>

    {error && <div className="auth-error" role="alert">{error}</div>}

    {summary && <div className="history-summary">
      <div><b>{summary.orderCount}</b><span>porudžbina</span></div>
      <div><b>{summary.callCount}</b><span>poziva konobaru</span></div>
      <div><b>{money(summary.revenueMinor / 100, currency)}</b><span>ukupno poručeno</span></div>
    </div>}
    {truncated && <p className="history-note">Prikazano je prvih 5000 zapisa. Suzite period ili filter za precizniji izvoz.</p>}

    {rows === null
      ? <p className="bills-empty">Učitavamo istoriju…</p>
      : rows.length
        ? <div className="history-table">
            <table>
              <thead><tr><th>Datum</th><th>Tip</th><th>Sto</th><th>Rejon</th><th>Konobar</th><th>Stavke</th><th>Iznos</th></tr></thead>
              <tbody>{rows.map((row) => <tr key={row.id}>
                <td>{new Date(row.createdAt.replace(" ", "T") + "Z").toLocaleString("sr-Latn-RS", { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" })}</td>
                <td>{row.kind === "order" ? (row.source === "staff" ? "Porudžbina (konobar)" : "Porudžbina") : "Poziv konobaru"}</td>
                <td>{row.tableNumber}</td>
                <td>{row.zone}</td>
                <td>{row.waiterName || "—"}</td>
                <td>{row.items || row.note || "—"}</td>
                <td>{row.kind === "order" ? money(row.totalMinor / 100, currency) : "—"}</td>
              </tr>)}</tbody>
            </table>
          </div>
        : <p className="bills-empty">Nema zapisa za izabrani period.</p>}
  </section>;
}

const VISIBLE_BILL_LINES = 4;

function OpenBillCard({ visit, currency, onAddItem, onCloseVisit }: { visit: TableVisit; currency: string; onAddItem: () => void; onCloseVisit: () => void }) {
  const [expanded, setExpanded] = useState(false);
  const lines = visit.items;
  const shown = expanded ? lines : lines.slice(0, VISIBLE_BILL_LINES);
  const hidden = lines.length - shown.length;

  return <article className="open-bill-card">
    <header><div><small>{visit.zone}</small><strong>Sto {visit.tableNumber}</strong><span>{visit.tableLabel}</span></div><em>Otvoren {timeAgo(visit.openedAt)}</em></header>
    <div className="open-bill-total"><span>Ukupan račun</span><strong>{money(visit.total, currency)}</strong></div>
    {lines.length > 0 ? <div className="open-bill-lines">
      {shown.map((line, index) => <div key={`${line.name}-${line.variantLabel}-${line.source}-${index}`} className={line.source === "staff" ? "staff-entered" : ""}>
        <b>{line.quantity}×</b>
        <span><strong>{line.name}</strong>{line.variantLabel && <small>{line.variantLabel}</small>}{line.source === "staff" && <i title="Uneo konobar">konobar</i>}</span>
        <em>{money(line.lineTotal, currency)}</em>
      </div>)}
      {hidden > 0 && <button type="button" className="open-bill-more" onClick={() => setExpanded(true)}>Prikaži još {hidden} {hidden === 1 ? "stavku" : "stavki"}</button>}
      {expanded && lines.length > VISIBLE_BILL_LINES && <button type="button" className="open-bill-more" onClick={() => setExpanded(false)}>Skupi listu</button>}
    </div> : <div className="open-bill-lines empty">Još nema poručenih stavki na ovom računu.</div>}
    <div className="open-bill-meta"><span><b>{visit.orderCount}</b> {visit.orderCount === 1 ? "porudžbina" : "porudžbine"}</span><span><b>{visit.openTicketCount}</b> otvorenih zahteva</span></div>
    <div className="open-bill-actions">
      <button type="button" className="ghost" onClick={onAddItem}><span>+</span> Dodaj stavku</button>
      <button type="button" onClick={onCloseVisit}><span>✓</span> Gost je otišao</button>
    </div>
  </article>;
}

type DraftLine = {
  key: string;
  itemId?: string;
  variantId?: string;
  name: string;
  variantLabel: string;
  price: number;
  quantity: number;
};

// Waiter-side order entry for anything the guest said out loud instead of
// sending through the QR menu.
function StaffOrderModal({ data, visit, postAction, setToast, onClose }: Pick<SharedProps, "data" | "postAction" | "setToast"> & { visit: TableVisit; onClose: () => void }) {
  const [lines, setLines] = useState<DraftLine[]>([]);
  const [search, setSearch] = useState("");
  const [pendingItem, setPendingItem] = useState<MenuItem | null>(null);
  const [customName, setCustomName] = useState("");
  const [customPrice, setCustomPrice] = useState("");
  const [note, setNote] = useState("");
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const currency = data.restaurant.currency;

  const query = search.trim().toLowerCase();
  const matches = data.items
    .filter((item) => !query || item.name.toLowerCase().includes(query) || item.category.toLowerCase().includes(query))
    .slice(0, 40);
  const total = lines.reduce((sum, line) => sum + line.price * line.quantity, 0);

  function addLine(next: Omit<DraftLine, "key" | "quantity">) {
    setError(null);
    setLines((current) => {
      const existing = current.findIndex((line) =>
        line.itemId === next.itemId && line.variantId === next.variantId && line.name === next.name && line.price === next.price);
      if (existing >= 0) {
        const copy = [...current];
        copy[existing] = { ...copy[existing], quantity: Math.min(20, copy[existing].quantity + 1) };
        return copy;
      }
      return [...current, { ...next, key: `${next.itemId ?? "custom"}-${next.variantId ?? next.name}-${current.length}`, quantity: 1 }];
    });
  }

  function pickItem(item: MenuItem) {
    const variants = item.variants ?? [];
    if (variants.length > 1) {
      setPendingItem(item);
      return;
    }
    const variant = variants[0];
    addLine({
      itemId: item.id,
      variantId: variant?.id ?? item.variantId,
      name: item.name,
      variantLabel: variant?.label ?? item.portion ?? "",
      price: variant?.price ?? item.price,
    });
  }

  function pickVariant(item: MenuItem, variant: MenuVariant) {
    addLine({ itemId: item.id, variantId: variant.id, name: item.name, variantLabel: variant.label, price: variant.price });
    setPendingItem(null);
  }

  function addCustom() {
    const name = customName.trim();
    const price = Number(customPrice.replace(",", "."));
    if (name.length < 2) { setError("Naziv stavke mora imati bar 2 znaka."); return; }
    if (!Number.isFinite(price) || price < 0) { setError("Unesite ispravnu cenu."); return; }
    addLine({ name, variantLabel: "", price });
    setCustomName("");
    setCustomPrice("");
  }

  function setQuantity(key: string, quantity: number) {
    setLines((current) => current
      .map((line) => (line.key === key ? { ...line, quantity: Math.max(0, Math.min(20, quantity)) } : line))
      .filter((line) => line.quantity > 0));
  }

  async function submit() {
    if (!lines.length) { setError("Dodajte bar jednu stavku."); return; }
    setSaving(true);
    setError(null);
    const success = await postAction("addStaffOrder", {
      tableId: visit.tableId,
      note: note.trim(),
      lines: lines.map((line) => line.itemId
        ? { itemId: line.itemId, variantId: line.variantId, quantity: line.quantity }
        : { name: line.name, variantLabel: line.variantLabel, unitPriceMinor: Math.round(line.price * 100), quantity: line.quantity }),
    });
    setSaving(false);
    if (!success) { setError("Stavke nisu sačuvane. Pokušajte ponovo."); return; }
    setToast(`Dodato na račun stola ${visit.tableNumber}: ${money(total, currency)}.`);
    onClose();
  }

  return <div className="modal-backdrop">
    <div className="staff-order-modal">
      <header>
        <div><p className="eyebrow">Ručni unos</p><h2>{visit.tableLabel}</h2><p>Stavke idu pravo na račun stola i ne pojavljuju se kao novi zahtev u redu.</p></div>
        <button type="button" className="icon-button" onClick={onClose} aria-label="Zatvori">✕</button>
      </header>

      <div className="staff-order-body">
        <section className="staff-order-picker">
          <label className="staff-order-search">Iz menija<input value={search} onChange={(event) => setSearch(event.target.value)} placeholder="Pretraga jela i pića…" /></label>
          <div className="staff-order-results">
            {matches.length > 0 ? matches.map((item) => <button type="button" key={item.id} onClick={() => pickItem(item)} className={item.available ? "" : "unavailable"}>
              <span><strong>{item.name}</strong><small>{item.category}{item.portion ? ` · ${item.portion}` : ""}{item.available ? "" : " · rasprodato"}</small></span>
              <em>{money(item.price, currency)}</em>
            </button>) : <p className="staff-order-empty">Nema rezultata za „{search}".</p>}
          </div>

          <div className="staff-order-custom">
            <p className="eyebrow">Ostalo (van menija)</p>
            <div>
              <input value={customName} onChange={(event) => setCustomName(event.target.value)} placeholder="Naziv stavke" />
              <input value={customPrice} onChange={(event) => setCustomPrice(event.target.value)} inputMode="decimal" placeholder={`Cena (${currency})`} />
              <button type="button" onClick={addCustom}>Dodaj</button>
            </div>
          </div>
        </section>

        <section className="staff-order-cart">
          <p className="eyebrow">Za dodavanje na račun</p>
          {lines.length > 0 ? <div className="staff-order-lines">{lines.map((line) => <div key={line.key}>
            <span><strong>{line.name}</strong>{line.variantLabel && <small>{line.variantLabel}</small>}</span>
            <div className="staff-order-qty">
              <button type="button" onClick={() => setQuantity(line.key, line.quantity - 1)} aria-label="Manje">−</button>
              <b>{line.quantity}</b>
              <button type="button" onClick={() => setQuantity(line.key, line.quantity + 1)} aria-label="Više">+</button>
            </div>
            <em>{money(line.price * line.quantity, currency)}</em>
          </div>)}</div> : <p className="staff-order-empty">Izaberite stavke levo ili ih unesite ručno.</p>}
          <label className="staff-order-note">Napomena (opciono)<input value={note} onChange={(event) => setNote(event.target.value)} placeholder="npr. bez leda, za poneti" /></label>
          <div className="staff-order-total"><span>Ukupno za dodavanje</span><strong>{money(total, currency)}</strong></div>
          {error && <div className="auth-error" role="alert">{error}</div>}
          <div className="staff-order-actions">
            <button type="button" className="secondary-button" onClick={onClose} disabled={saving}>Otkaži</button>
            <button type="button" className="primary-button" onClick={submit} disabled={saving || !lines.length}>{saving ? "Dodavanje…" : "Dodaj na račun"}</button>
          </div>
        </section>
      </div>

      {pendingItem && <div className="staff-order-variants">
        <p className="eyebrow">{pendingItem.name} — izaberite varijantu</p>
        <div>{(pendingItem.variants ?? []).map((variant) => <button type="button" key={variant.id ?? variant.label} onClick={() => pickVariant(pendingItem, variant)}>
          <strong>{variant.label}</strong><em>{money(variant.price, currency)}</em>
        </button>)}</div>
        <button type="button" className="secondary-button" onClick={() => setPendingItem(null)}>Otkaži</button>
      </div>}
    </div>
  </div>;
}

// What the queue says about a request. Deliberately the intent and nothing
// else: on a wall screen "Gost je tražio račun" is read across the room, while
// a list of ordered items is not, and the detail is one click away anyway.
function requestSummary(ticket: Ticket) {
  if (ticket.kind === "order") {
    return ticket.source === "staff" ? "Konobar je uneo porudžbinu" : "Hrana je poručena";
  }
  const reason = `${ticket.callReason ?? ""} ${ticket.note ?? ""}`.toLowerCase();
  if (reason.includes("račun") || reason.includes("racun")) {
    // The guest also picks card or cash, and that is worth the two extra words.
    const payment = /kartic/.test(reason) ? " · kartica" : /gotovin/.test(reason) ? " · gotovina" : "";
    return `Gost je tražio račun${payment}`;
  }
  if (reason.includes("poruč") || reason.includes("poruc")) return "Gost želi da poruči";
  return "Gost je tražio pomoć";
}

// One request, one line. The row gets tighter as the screen gets wider, so a
// wall-mounted screen shows the whole queue at once.
function QueueRow({
  ticket,
  currency,
  showTotal,
  onResolve,
}: {
  ticket: Ticket;
  currency: string;
  showTotal: boolean;
  onResolve: () => void;
}) {
  const [open, setOpen] = useState(false);
  const ageMinutes = Math.floor((Date.now() - parseServerDate(ticket.createdAt).getTime()) / 60_000);
  const lines = ticket.items ?? [];

  return <article
    className={`queue-row ${ticket.kind} ${ticket.status === "done" ? "done" : ""} ${ageMinutes >= 10 && ticket.status === "open" ? "urgent" : ""} ${open ? "open" : ""}`}
  >
    <div className="queue-line">
      <span className="queue-icon">{ticket.kind === "call" ? "◔" : ticket.source === "staff" ? "✎" : "≡"}</span>
      <strong className="queue-table">Sto {ticket.tableNumber}</strong>
      <span className="queue-what">{requestSummary(ticket)}</span>
      <span className="queue-zone">{ticket.zone}</span>
      {showTotal && ticket.kind === "order" && <span className="queue-total">{money(ticket.total ?? 0, currency)}</span>}
      <span className="queue-age">{timeAgo(ticket.createdAt)}</span>
      <span className="queue-actions">
        {ticket.status === "open" && <button type="button" className="queue-done" onClick={onResolve} title="Označi kao završeno">✓</button>}
        <button
          type="button"
          className="queue-more"
          aria-expanded={open}
          title={open ? "Sakrij detalje" : "Prikaži detalje"}
          onClick={() => setOpen(!open)}
        >
          •••
        </button>
      </span>
    </div>
    {open && <div className="queue-detail">
      {lines.length > 0 && <ul>
        {lines.map((line, index) => <li key={`${line.name}-${index}`}>
          <b>{line.quantity}×</b>
          <span>{line.name}{line.portion ? ` · ${line.portion}` : ""}</span>
          <em>{money((line.price ?? 0) * line.quantity, currency)}</em>
        </li>)}
      </ul>}
      {ticket.kind === "order" && !lines.length && <p>Porudžbina bez pojedinačnih stavki.</p>}
      {ticket.callReason && <p><small>Zahtev gosta:</small> {ticket.callReason}</p>}
      {ticket.note && ticket.note !== ticket.callReason && <p><small>Napomena:</small> {ticket.note}</p>}
      {ticket.waiter && <p><small>Konobar:</small> {ticket.waiter.name}</p>}
      {ticket.createdBy && <p><small>Uneo:</small> {ticket.createdBy}</p>}
      <p><small>Primljeno:</small> {parseServerDate(ticket.createdAt).toLocaleTimeString("sr-RS", { hour: "2-digit", minute: "2-digit" })}
        {ticket.status === "done" && ticket.resolvedAt ? ` · završeno ${parseServerDate(ticket.resolvedAt).toLocaleTimeString("sr-RS", { hour: "2-digit", minute: "2-digit" })}` : ""}
      </p>
    </div>}
  </article>;
}

function TicketCard({ ticket, currency, showTotal, onResolve }: { ticket: Ticket; currency: string; showTotal: boolean; onResolve: () => void }) {
  const ageMinutes = Math.floor((Date.now() - parseServerDate(ticket.createdAt).getTime()) / 60_000);
  return <article className={`ticket-card ${ticket.kind} ${ticket.source === "staff" ? "staff-entered" : ""} ${ageMinutes >= 10 && ticket.status === "open" ? "urgent" : ""} ${ticket.status === "done" ? "done" : ""}`}><header><div className="ticket-label"><span>{ticket.kind === "call" ? "◔" : ticket.source === "staff" ? "✎" : "≡"}</span><div><small>{ticket.kind === "call" ? "Poziv konobaru" : ticket.source === "staff" ? `Uneo konobar${ticket.createdBy ? ` · ${ticket.createdBy}` : ""}` : "Nova porudžbina"}</small><strong>Sto {ticket.tableNumber}</strong></div></div><div className="ticket-age"><span className={ageMinutes >= 10 ? "urgent" : ""}>{timeAgo(ticket.createdAt)}</span><small>{ticket.zone}</small></div></header><div className="ticket-body">{ticket.kind === "order" ? <div className="ticket-items">{ticket.items?.map((item, index) => <div key={`${item.name}-${index}`}><b>{item.quantity}×</b><span><strong>{item.name}</strong>{item.note && <small>Napomena: {item.note}</small>}</span>{showTotal && <em>{money(item.price * item.quantity, currency)}</em>}</div>)}</div> : <div className="call-reason"><span>{ticket.callReason === "Donesite račun" ? "▤" : "◔"}</span><p><small>Zahtev gosta</small><strong>{ticket.callReason}</strong>{ticket.paymentMethod && <em>Plaćanje: {ticket.paymentMethod}</em>}</p></div>}{ticket.kind === "order" && ticket.note && <div className="call-reason"><span>✎</span><p><small>Napomena gosta</small><strong>{ticket.note}</strong></p></div>}</div><footer>{ticket.kind === "order" && showTotal && <div className="ticket-total"><span>Ukupno</span><strong>{money(ticket.total ?? 0, currency)}</strong></div>}<div className="ticket-actions">{ticket.status === "open" ? <button onClick={onResolve}><span>✓</span> Označi kao završeno</button> : <span className="done-label">✓ Završeno {ticket.resolvedAt ? timeAgo(ticket.resolvedAt) : ""}</span>}<button className="more-button">•••</button></div></footer></article>;
}
