import { useState, useEffect, useMemo, useRef } from "react";
import { useTranslation } from "react-i18next";
import { useLocation } from "wouter";
import {
  useListApps,
  getListAppsQueryKey,
  useGetHomeStats,
  getGetHomeStatsQueryKey,
  useListNotifications,
  getListNotificationsQueryKey,
  useListIncomingServiceOrders,
  getListIncomingServiceOrdersQueryKey,
  useLogout,
  useUpdateMyAppOrder,
  logAppOpen,
  getGetMeQueryKey,
  type App,
} from "@workspace/api-client-react";
import { useQueryClient } from "@tanstack/react-query";
import { useAuth } from "@/contexts/AuthContext";
import { usePushSubscription } from "@/hooks/use-push-subscription";
import { useReceivedNotes } from "@/lib/notes-api";
import { useIncomingNotePopup } from "@/contexts/IncomingNotePopupContext";
import {
  Bell,
  Inbox,
  LogOut,
  Grid2X2,
  Coffee,
  BellRing,
  Sparkles,
} from "lucide-react";
import * as LucideIcons from "lucide-react";
import type { LucideIcon } from "lucide-react";
import {
  DndContext,
  PointerSensor,
  TouchSensor,
  useSensor,
  useSensors,
  closestCenter,
  type DragEndEvent,
} from "@dnd-kit/core";
import {
  SortableContext,
  arrayMove,
  rectSortingStrategy,
  useSortable,
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import {
  formatDate,
  formatTime,
  formatNumber,
} from "@/lib/i18n-format";
import { resolveServiceImageUrl } from "@/lib/image-url";
import {
  Clock,
  useNow,
  AnalogClockWidget,
  useHomeClockVisibility,
  useTopbarClockVisibility,
  useHomeClockSize,
  useHomeClockPosition,
  type HomeClockSize,
} from "@/components/clock";
import { SettingsPanel } from "@/components/settings-panel";

type IconName = keyof typeof LucideIcons;
function isIconName(x: string): x is IconName {
  return x in LucideIcons;
}
function resolveIcon(name: string): LucideIcon {
  if (isIconName(name)) {
    const Candidate = LucideIcons[name] as unknown;
    if (typeof Candidate === "function" || typeof Candidate === "object") {
      return Candidate as LucideIcon;
    }
  }
  return LucideIcons.Grid2X2;
}

function gradientForColor(color: string): string {
  const c = (color ?? "").toLowerCase();
  // #603: each built-in app now ships with a distinct hue so the four
  // default tiles (Notes/Services/Meetings/Admin) read as four different
  // colours instead of three blues + one orange. New patterns added for
  // yellow (#eab308 / facc15) and red (#ef4444 / dc2626), and the green
  // branch already accepts the new #22c55e Meetings colour. The purple
  // branch stays for any admin who manually picks a purple via the
  // Apps editor, but no seeded row uses it any more.
  if (c.includes("ef4444") || c.includes("dc2626") || c.includes("red")) return "icon-tile-red";
  if (c.includes("eab308") || c.includes("facc15") || c.includes("yellow")) return "icon-tile-yellow";
  if (c.includes("a855f7") || c.includes("8b5cf6") || c.includes("purple") || c.includes("violet")) return "icon-tile-purple";
  if (c.includes("10b981") || c.includes("22c55e") || c.includes("green") || c.includes("emerald")) return "icon-tile-green";
  if (c.includes("f59e0b") || c.includes("f97316") || c.includes("orange") || c.includes("amber")) return "icon-tile-orange";
  if (c.includes("ec4899") || c.includes("pink") || c.includes("rose")) return "icon-tile-pink";
  if (c.includes("14b8a6") || c.includes("06b6d4") || c.includes("teal") || c.includes("cyan")) return "icon-tile-teal";
  return "icon-tile-blue";
}

function AppIconContent({
  app,
  badgeCount = 0,
  pulse = false,
}: {
  app: { nameAr: string; nameEn: string; iconName: string; color: string; imageUrl?: string | null };
  badgeCount?: number;
  pulse?: boolean;
}) {
  const { i18n: i18nInstance } = useTranslation();
  const lang = i18nInstance.language;
  const name = lang === "ar" ? app.nameAr : app.nameEn;

  const Icon = resolveIcon(app.iconName);
  // when an admin uploads a custom image, render it in place
  // of the Lucide icon. The image fills the tile (object-cover) so the
  // gradient background still shows through transparent PNGs.
  const resolvedImage = resolveServiceImageUrl(app.imageUrl ?? null);

  return (
    <>
      <div className={`relative icon-tile w-[68px] h-[68px] sm:w-[72px] sm:h-[72px] ${gradientForColor(app.color)} group-hover:scale-105 group-active:scale-95`}>
        {pulse && (
          <span
            data-testid={`app-icon-pulse-${app.iconName}`}
            className="absolute inset-0 rounded-[inherit] ring-4 ring-amber-400/70 animate-ping pointer-events-none"
            aria-hidden="true"
          />
        )}
        {resolvedImage ? (
          <img
            src={resolvedImage}
            alt=""
            className="absolute inset-0 w-full h-full object-cover rounded-[inherit]"
            onError={(e) => { (e.currentTarget as HTMLImageElement).style.display = "none"; }}
          />
        ) : (
          <Icon size={30} color="#FFFFFF" />
        )}
        {badgeCount > 0 && (
          <span className="absolute -top-1 -right-1 bg-destructive text-destructive-foreground text-[10px] rounded-full min-w-[18px] h-[18px] px-1 flex items-center justify-center font-bold tabular-nums shadow">
            {badgeCount > 9 ? "9+" : badgeCount}
          </span>
        )}
      </div>
      <span
        lang={lang === "ar" ? "ar" : "en"}
        dir={lang === "ar" ? "rtl" : "ltr"}
        className="text-[11px] text-foreground/85 font-medium text-center max-w-[78px] block"
        style={{
          // Pin an Arabic-capable face so iOS Safari doesn't fall back
          // to a Latin font (which drops the dots under final ي in
          // labels like "خدماتي"). Falls back to the Latin face for
          // English labels via the second family in the stack.
          fontFamily:
            lang === "ar"
              ? '"DIN Next LT Arabic", "Helvetica Neue LT Arabic", "Tajawal", sans-serif'
              : '"Helvetica Neue", system-ui, sans-serif',
          lineHeight: 1.6,
          paddingTop: 2,
          overflow: "hidden",
          textOverflow: "ellipsis",
          whiteSpace: "nowrap",
        }}
      >
        {name}
      </span>
    </>
  );
}

function SortableAppIcon({
  app,
  onClick,
  badgeCount = 0,
  pulse = false,
}: {
  app: App;
  onClick: () => void;
  badgeCount?: number;
  pulse?: boolean;
}) {
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: `app-${app.id}` });
  const style: React.CSSProperties = {
    transform: CSS.Transform.toString(transform),
    transition,
    opacity: isDragging ? 0.6 : 1,
    zIndex: isDragging ? 20 : "auto",
    touchAction: "none",
  };
  return (
    <button
      type="button"
      ref={setNodeRef}
      style={style}
      onClick={onClick}
      className="flex flex-col items-center gap-2 group"
      {...attributes}
      {...listeners}
    >
      <AppIconContent app={app} badgeCount={badgeCount} pulse={pulse} />
    </button>
  );
}

const CLOCK_TILE_ID = "__clock";

function useGridCols(): number {
  const compute = () => {
    if (typeof window === "undefined") return 8;
    const w = window.innerWidth;
    if (w >= 1024) return 8;
    if (w >= 768) return 7;
    if (w >= 640) return 6;
    return 4;
  };
  const [cols, setCols] = useState<number>(compute);
  useEffect(() => {
    const onResize = () => setCols(compute());
    window.addEventListener("resize", onResize);
    return () => window.removeEventListener("resize", onResize);
  }, []);
  return cols;
}

function SortableClockTile({
  time,
  lang,
  hour12,
  size,
  onToggleSize,
  pinColumn,
}: {
  time: Date;
  lang: string;
  hour12: boolean | null | undefined;
  size: HomeClockSize;
  onToggleSize: () => void;
  pinColumn?: number;
}) {
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
    id: CLOCK_TILE_ID,
  });
  const isLarge = size === "large";
  const px = isLarge ? 168 : 78;
  const effectivePinColumn =
    pinColumn && isLarge ? Math.max(pinColumn - 1, 1) : pinColumn;
  const style: React.CSSProperties = {
    transform: CSS.Transform.toString(transform),
    transition,
    opacity: isDragging ? 0.6 : 1,
    zIndex: isDragging ? 20 : "auto",
    touchAction: "none",
    ...(effectivePinColumn && !isDragging
      ? { gridColumnStart: effectivePinColumn, gridRowStart: 1 }
      : {}),
  };

  const longPressTimer = useRef<number | null>(null);
  const startPos = useRef<{ x: number; y: number } | null>(null);

  const clearTimer = () => {
    if (longPressTimer.current !== null) {
      window.clearTimeout(longPressTimer.current);
      longPressTimer.current = null;
    }
  };

  useEffect(() => {
    if (isDragging) clearTimer();
  }, [isDragging]);

  const dndPointerDown = listeners?.onPointerDown as
    | ((e: React.PointerEvent) => void)
    | undefined;

  const onPointerDown = (e: React.PointerEvent) => {
    startPos.current = { x: e.clientX, y: e.clientY };
    clearTimer();
    longPressTimer.current = window.setTimeout(() => {
      onToggleSize();
    }, 500);
    dndPointerDown?.(e);
  };
  const onPointerMove = (e: React.PointerEvent) => {
    if (!startPos.current) return;
    const dx = e.clientX - startPos.current.x;
    const dy = e.clientY - startPos.current.y;
    if (dx * dx + dy * dy > 64) clearTimer();
  };
  const onPointerUp = () => {
    clearTimer();
    startPos.current = null;
  };

  const { onPointerDown: _ignored, ...restListeners } = listeners ?? {};

  return (
    <div
      ref={setNodeRef}
      style={style}
      className={`flex flex-col items-center gap-2 group select-none ${isLarge ? "col-span-2 row-span-2" : ""}`}
      {...attributes}
      {...restListeners}
      onPointerDown={onPointerDown}
      onPointerMove={onPointerMove}
      onPointerUp={onPointerUp}
      onPointerCancel={onPointerUp}
    >
      <div
        className={`relative flex items-center justify-center text-foreground group-hover:scale-105 group-active:scale-95 transition-transform ${
          isLarge ? "w-[168px] h-[168px]" : "w-[78px] h-[78px]"
        }`}
        aria-label="Clock"
      >
        <AnalogClockWidget
          time={time}
          lang={lang}
          hour12={hour12}
          size={px - (isLarge ? 16 : 10)}
          showLabels={false}
        />
      </div>
      <span className="text-[11px] text-foreground/85 font-medium text-center leading-tight max-w-[78px] truncate">
        {formatTime(time, lang, {
          hour: "2-digit",
          minute: "2-digit",
          hour12: resolveClockHour12Bool(hour12),
        })}
      </span>
    </div>
  );
}

function resolveClockHour12Bool(v: boolean | null | undefined): boolean {
  return v ?? false;
}

type StatCardProps = {
  label: string;
  value: string;
  Icon: typeof Grid2X2;
  iconClass: string;
};

function StatCard({ label, value, Icon, iconClass }: StatCardProps) {
  return (
    <div className="glass-panel rounded-2xl p-3 flex items-center gap-3">
      <div className={`shrink-0 w-10 h-10 rounded-xl flex items-center justify-center ${iconClass}`}>
        <Icon size={18} />
      </div>
      <div className="min-w-0">
        <div className="text-xl font-bold text-foreground leading-none tabular-nums">{value}</div>
        <div className="text-[11px] text-muted-foreground mt-1 truncate">{label}</div>
      </div>
    </div>
  );
}

export default function HomePage() {
  const { t, i18n: i18nInstance } = useTranslation();
  const [, setLocation] = useLocation();
  const { user } = useAuth();
  const queryClient = useQueryClient();
  const time = useNow();
  const lang = i18nInstance.language;

  const { data: apps } = useListApps({ query: { queryKey: getListAppsQueryKey() } });
  const updateOrder = useUpdateMyAppOrder();
  const [orderedApps, setOrderedApps] = useState<App[] | null>(null);

  useEffect(() => {
    if (!apps) return;
    const activeApps = apps.filter((a) => a.isActive);
    setOrderedApps((prev) => {
      if (!prev) return activeApps;
      const byId = new Map(activeApps.map((a) => [a.id, a]));
      const kept = prev
        .map((a) => byId.get(a.id))
        .filter((a): a is App => a !== undefined);
      const keptIds = new Set(kept.map((a) => a.id));
      const added = activeApps.filter((a) => !keptIds.has(a.id));
      return [...kept, ...added];
    });
  }, [apps]);

  const sensors = useSensors(
    useSensor(PointerSensor, { activationConstraint: { distance: 8 } }),
    useSensor(TouchSensor, { activationConstraint: { delay: 250, tolerance: 5 } }),
  );

  const gridCols = useGridCols();
  const [homeClockVisible, setHomeClockVisible] = useHomeClockVisibility();
  const [topbarClockVisible] = useTopbarClockVisibility();
  const [clockSize, setClockSize] = useHomeClockSize(user?.id);
  const [clockPos, setClockPos] = useHomeClockPosition(user?.id);
  const toggleClockSize = () => setClockSize(clockSize === "large" ? "compact" : "large");

  const appCount = (orderedApps ?? []).length;
  const isClockUnset = clockPos < 0;
  // Only meaningful in RTL: when the user drags the clock past the last
  // app, treat it as "pin to the absolute far-left cell" so they can
  // reach that cell even when there are fewer apps than columns.
  // In LTR the far-right is already reachable naturally so we don't
  // hijack the saved index there.
  const isRtlClockAtEnd = lang === "ar" && clockPos >= appCount;
  const shouldPinFarLeft = (isClockUnset || isRtlClockAtEnd) && lang === "ar" && homeClockVisible;
  const combinedIds = useMemo(() => {
    const appIds = (orderedApps ?? []).map((a) => `app-${a.id}`);
    if (!homeClockVisible) return appIds;
    if (isClockUnset) {
      // Default placement: LTR -> front of array (visually leftmost);
      // RTL -> appended to end and CSS-pinned to the last column of row 1.
      return lang === "ar" ? [...appIds, CLOCK_TILE_ID] : [CLOCK_TILE_ID, ...appIds];
    }
    if (isRtlClockAtEnd) {
      // RTL only: user dragged clock to / past the end -> pin far-left.
      return [...appIds, CLOCK_TILE_ID];
    }
    const safePos = Math.min(Math.max(clockPos, 0), appIds.length);
    const out: string[] = [...appIds];
    out.splice(safePos, 0, CLOCK_TILE_ID);
    return out;
  }, [orderedApps, clockPos, homeClockVisible, lang, isClockUnset, isRtlClockAtEnd]);
  const clockPinColumn = shouldPinFarLeft ? Math.max(gridCols, 1) : undefined;
  const sortableIds = combinedIds;

  const handleDragEnd = (event: DragEndEvent) => {
    const { active, over } = event;
    if (!over || active.id === over.id || !orderedApps) return;
    const oldIndex = combinedIds.findIndex((id) => id === active.id);
    const newIndex = combinedIds.findIndex((id) => id === over.id);
    if (oldIndex < 0 || newIndex < 0) return;
    const nextCombined = arrayMove(combinedIds, oldIndex, newIndex);
    const newClockPos = nextCombined.findIndex((id) => id === CLOCK_TILE_ID);
    if (newClockPos >= 0 && newClockPos !== clockPos) setClockPos(newClockPos);
    const newAppOrder = nextCombined
      .filter((id) => id !== CLOCK_TILE_ID)
      .map((id) => Number(String(id).slice(4)))
      .map((id) => orderedApps.find((a) => a.id === id))
      .filter((a): a is App => a !== undefined);
    const sameAppOrder =
      newAppOrder.length === orderedApps.length &&
      newAppOrder.every((a, i) => a.id === orderedApps[i].id);
    if (sameAppOrder) return;
    const previous = orderedApps;
    setOrderedApps(newAppOrder);
    updateOrder.mutate(
      { data: { order: newAppOrder.map((a) => a.id) } },
      {
        onSuccess: (data) => {
          setOrderedApps(data);
          queryClient.setQueryData(getListAppsQueryKey(), data);
        },
        onError: () => {
          setOrderedApps(previous);
          queryClient.invalidateQueries({ queryKey: getListAppsQueryKey() });
        },
      },
    );
  };

  const { data: stats } = useGetHomeStats({ query: { queryKey: getGetHomeStatsQueryKey() } });
  const { data: notifications } = useListNotifications({ query: { queryKey: getListNotificationsQueryKey() } });

  const isAdmin = user?.roles?.includes("admin") ?? false;
  const canReceiveOrders =
    isAdmin || (user?.roles?.includes("order_receiver") ?? false);
  const { data: incomingOrders } = useListIncomingServiceOrders({
    query: {
      queryKey: getListIncomingServiceOrdersQueryKey(),
      enabled: canReceiveOrders,
    },
  });
  const pendingOrdersCount =
    incomingOrders?.filter((o) => o.status === "pending" && !o.assignedTo).length ?? 0;

  // Unread Notes badge — drives the count shown on the Notes app tile (and
  // dock entry, if present). We reuse the existing /notes/received hook so
  // socket invalidations on note_received automatically refresh this.
  const { data: receivedNotes } = useReceivedNotes(false);
  const unreadNotesCount =
    receivedNotes?.filter((n) => n.status === "unread").length ?? 0;

  // Persistent visual cue while an incoming-note popup is queued: the Notes
  // tile pulses + shows the queue size as its badge so the user knows there's
  // an attention-grabbing note waiting even after they dismiss the modal or
  // navigate elsewhere on the home screen.
  const { queueLength: notePopupQueueLength } = useIncomingNotePopup();
  const notesBadgeCount = Math.max(unreadNotesCount, notePopupQueueLength);
  const notesPulse = notePopupQueueLength > 0;

  const logout = useLogout();
  const pushSub = usePushSubscription();

  const openApp = (app: App) => {
    // Fire-and-forget: keepalive ensures the request survives any
    // imminent navigation triggered by setLocation below.
    logAppOpen(app.id, { keepalive: true }).catch(() => {
      // Best-effort tracking; ignore failures so navigation isn't blocked.
    });
    // branch on the admin-configured open mode.
    //   external_tab    — open the externalUrl in a new tab
    //   external_iframe — navigate to /embedded/:id which renders externalUrl
    //                      inside an iframe shell with a back button
    //   internal (default) — navigate to the SPA route
    const mode = app.openMode ?? "internal";
    if (mode === "external_tab" && app.externalUrl) {
      window.open(app.externalUrl, "_blank", "noopener,noreferrer");
      return;
    }
    if (mode === "external_iframe" && app.externalUrl) {
      setLocation(`/embedded/${app.id}`);
      return;
    }
    setLocation(app.route);
  };

  const unreadCount = notifications?.filter((n) => !n.isRead).length ?? stats?.unreadNotifications ?? 0;

  const displayName = lang === "ar"
    ? (user?.displayNameAr ?? user?.username ?? "")
    : (user?.displayNameEn ?? user?.username ?? "");

  const handleLogout = async () => {
    // #626: tear down this browser's Web Push subscription BEFORE
    // hitting /auth/logout, so the local pushManager endpoint and the
    // server row both go away. The server-side logout also deletes
    // every push_subscriptions row for this user as a safety net, but
    // doing the browser unsubscribe first means the OS-level
    // subscription is gone too — otherwise the SW would keep an
    // orphan endpoint that the next signed-in user could inherit on
    // resubscribe.
    // Race the browser unsubscribe against a 1.5s timeout. `disable()`
    // awaits `navigator.serviceWorker.ready`, which can hang forever
    // if the SW never activates (rare but observed on first iOS PWA
    // launch). A stalled unsubscribe must not block the user from
    // signing out — the server-side delete in /auth/logout is the
    // authoritative cleanup, so a missed browser unsubscribe just
    // leaves an orphan pushManager endpoint that the OS will GC on
    // its own schedule.
    try {
      await Promise.race([
        pushSub.disable(),
        new Promise<void>((resolve) => setTimeout(resolve, 1500)),
      ]);
    } catch {
      // Best-effort: a failed unsubscribe must not block sign-out.
    }
    logout.mutate(undefined, {
      onSuccess: () => {
        queryClient.clear();
        setLocation("/login");
      },
    });
  };

  // #571: drop the Notifications tile from the bottom dock — the bell
  // icon in the top bar (with the same unread badge) is the canonical
  // entry point, so a second affordance for the same destination on
  // the home screen was redundant. The bell still routes to
  // `/notifications` (see the top-bar button below).
  const dockApps = (orderedApps ?? apps)?.filter((a) => ["services", "admin"].includes(a.slug)) ?? [];

  const initials = (displayName || "?").trim().slice(0, 1).toUpperCase();

  return (
    // removed `overflow-hidden` and switched to `min-h-[100dvh]`
    // so iOS Safari can collapse its URL bar on scroll, matching the
    // executive-meetings page behavior. `100dvh` (dynamic viewport
    // height) sizes against the visible area, so the layout reflows
    // cleanly when the URL bar shows/hides.
    <div className="min-h-[100dvh] os-bg flex flex-col relative">
      {/*
        Status Bar
        --------------------------------------------------------------
        Sizing scales by device class so the bar feels natural at each
        size — phones stay compact (the original design), iPad gets a
        noticeably taller bar with bigger tap targets so it doesn't
        look lost on a larger screen, and desktop matches iPad. We
        switch at the `md:` breakpoint (≥768px), which is the standard
        iPad-portrait threshold; mobile (<768px) renders identically
        to before this change.
      */}
      <div className="topbar-glass px-4 sm:px-6 md:px-8 py-3 md:py-4 flex items-center justify-between gap-3 md:gap-5 z-10 sticky top-0">
        {/* Identity */}
        <div className="flex items-center gap-2.5 md:gap-3.5 min-w-0 shrink basis-0 flex-1">
          <div className="w-9 h-9 md:w-12 md:h-12 rounded-full bg-primary/15 text-primary flex items-center justify-center font-bold text-sm md:text-lg shrink-0">
            {initials}
          </div>
          <div className="min-w-0 hidden sm:block">
            <div className="text-foreground font-semibold text-sm md:text-base truncate leading-tight">{displayName}</div>
            {isAdmin && (
              <div className="text-[10px] md:text-xs text-muted-foreground leading-tight">Admin</div>
            )}
          </div>
        </div>

        {/* Clock + dates */}
        {topbarClockVisible && (
          <div className="px-2 md:px-3 shrink-0">
            <Clock
              style={user?.clockStyle ?? null}
              hour12={user?.clockHour12 ?? null}
              time={time}
              lang={lang}
            />
          </div>
        )}

        {/*
          Controls — buttons grow on iPad+ via padding + icon size.
          Lucide icons size via `width`/`height` SVG attrs; we drop the
          numeric `size` prop and use Tailwind classes so the icon
          itself can scale at the `md:` breakpoint.
        */}
        {/*
: per-user UI preferences (language,
          notification sound + vibration, master sound, clock
          style/visibility/12-24h) live behind a single Settings
          (gear) entry that opens the unified panel. Logout stays
          on the topbar as a single-tap action ( reverted
          the brief move into the panel). The cluster now shows
          exactly Bell -> Inbox -> Settings -> Logout (Inbox is
          conditional on the user being able to receive incoming
          orders). The notification-center bell is the only bell on
          the page.
        */}
        <div className="flex items-center gap-1 md:gap-2 shrink basis-0 flex-1 justify-end">
          <button
            onClick={() => setLocation("/notifications")}
            aria-label={t("notifications.title")}
            className="relative p-1.5 md:p-2.5 rounded-lg md:rounded-xl hover:bg-foreground/5 text-muted-foreground hover:text-foreground transition-colors"
          >
            <Bell className="w-[18px] h-[18px] md:w-6 md:h-6" />
            {unreadCount > 0 && (
              <span aria-hidden="true" className="absolute -top-0.5 -right-0.5 bg-destructive text-destructive-foreground text-[10px] md:text-xs rounded-full min-w-[16px] md:min-w-[20px] h-4 md:h-5 px-1 flex items-center justify-center font-bold tabular-nums">
                {unreadCount > 9 ? "9+" : unreadCount}
              </span>
            )}
          </button>
          {canReceiveOrders && (
            <button
              onClick={() => setLocation("/orders/incoming")}
              aria-label={t("incomingOrders.linkLabel")}
              className="relative p-1.5 md:p-2.5 rounded-lg md:rounded-xl hover:bg-foreground/5 text-muted-foreground hover:text-foreground transition-colors"
            >
              <Inbox className="w-[18px] h-[18px] md:w-6 md:h-6" />
              {pendingOrdersCount > 0 && (
                <span aria-hidden="true" className="absolute -top-0.5 -right-0.5 bg-destructive text-destructive-foreground text-[10px] md:text-xs rounded-full min-w-[16px] md:min-w-[20px] h-4 md:h-5 px-1 flex items-center justify-center font-bold tabular-nums">
                  {pendingOrdersCount > 9 ? "9+" : pendingOrdersCount}
                </span>
              )}
            </button>
          )}
          <SettingsPanel />
          <button
            onClick={handleLogout}
            aria-label={t("nav.logout")}
            className="p-1.5 md:p-2.5 rounded-lg md:rounded-xl hover:bg-foreground/5 text-muted-foreground hover:text-foreground transition-colors"
          >
            <LogOut className="w-[18px] h-[18px] md:w-6 md:h-6" />
          </button>
        </div>
      </div>

      {/* Main Content */}
      <div className="flex-1 px-5 sm:px-8 pt-6 pb-32 overflow-y-auto">
        {/* Stats Row — admin only */}
        {isAdmin && stats && (
          <div className="grid grid-cols-2 sm:grid-cols-4 gap-3 mb-8 max-w-4xl">
            <StatCard
              label={t("home.stats.apps")}
              value={formatNumber(stats.totalApps, lang)}
              Icon={Grid2X2}
              iconClass="bg-primary/15 text-primary"
            />
            <StatCard
              label={t("home.stats.services")}
              value={formatNumber(stats.totalServices, lang)}
              Icon={Coffee}
              iconClass="bg-amber-100 text-amber-600"
            />
            <StatCard
              label={t("home.stats.alerts")}
              value={formatNumber(stats.unreadNotifications, lang)}
              Icon={BellRing}
              iconClass="bg-rose-100 text-rose-600"
            />
          </div>
        )}

        {/* Apps Grid */}
        <div className="mb-4">
          {orderedApps && orderedApps.length === 0 ? (
            <div className="glass-panel rounded-2xl p-8 text-center max-w-md mx-auto">
              <div className="mx-auto w-12 h-12 rounded-2xl bg-primary/10 text-primary flex items-center justify-center mb-3">
                <Sparkles size={22} />
              </div>
              <div className="font-semibold text-foreground">{t("home.noApps")}</div>
              <div className="text-sm text-muted-foreground mt-1">{t("home.noAppsHint")}</div>
            </div>
          ) : (
            <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
              <SortableContext items={sortableIds} strategy={rectSortingStrategy}>
                <div className="grid grid-cols-4 sm:grid-cols-6 md:grid-cols-7 lg:grid-cols-8 gap-x-3 gap-y-5 grid-flow-row-dense">
                  {combinedIds.map((id) => {
                    if (id === CLOCK_TILE_ID) {
                      if (!homeClockVisible) return null;
                      return (
                        <SortableClockTile
                          key={CLOCK_TILE_ID}
                          time={time}
                          lang={lang}
                          hour12={user?.clockHour12 ?? null}
                          size={clockSize}
                          onToggleSize={toggleClockSize}
                          pinColumn={clockPinColumn}
                        />
                      );
                    }
                    const appId = Number(String(id).slice(4));
                    const app = orderedApps?.find((a) => a.id === appId);
                    if (!app) return null;
                    return (
                      <SortableAppIcon
                        key={app.id}
                        app={app}
                        onClick={() => openApp(app)}
                        badgeCount={app.slug === "notes" ? notesBadgeCount : 0}
                        pulse={app.slug === "notes" && notesPulse}
                      />
                    );
                  })}
                </div>
              </SortableContext>
            </DndContext>
          )}
        </div>
      </div>

      {/* Bottom Dock */}
      <div className="fixed bottom-0 left-0 right-0 p-4 z-10 pointer-events-none">
        <div className="dock-glass rounded-3xl px-6 py-3 flex items-center justify-around max-w-sm mx-auto pointer-events-auto">
          {dockApps.slice(0, 4).map((app) => {
            const Icon = resolveIcon(app.iconName);
            const showBadge = app.slug === "notifications" && unreadCount > 0;
            return (
              <button
                key={app.id}
                onClick={() => openApp(app)}
                className="relative flex flex-col items-center gap-1 group"
              >
                <div
                  className="w-12 h-12 rounded-xl flex items-center justify-center group-hover:scale-110 transition-transform"
                  style={{ backgroundColor: `${app.color}25` }}
                >
                  <Icon size={22} color={app.color} />
                </div>
                {showBadge && (
                  <span className="absolute -top-1 -right-1 bg-destructive text-destructive-foreground text-[10px] rounded-full min-w-[16px] h-4 px-1 flex items-center justify-center font-bold tabular-nums">
                    {unreadCount > 9 ? "9+" : unreadCount}
                  </span>
                )}
              </button>
            );
          })}
        </div>
      </div>
    </div>
  );
}
