import { useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { useQueryClient } from "@tanstack/react-query";
import {
  useUpdateNotificationPreferences,
  getGetMeQueryKey,
  type AuthUser,
  type NotificationSoundId,
} from "@workspace/api-client-react";
import { Bell, BellOff, Check, Volume2, VolumeX, Play } from "lucide-react";
import {
  isIosSafariNonStandalone,
  usePushSubscription,
} from "@/hooks/use-push-subscription";
import { useToast } from "@/hooks/use-toast";
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/components/ui/popover";
import { ALL_SOUND_IDS, notificationPlayer } from "@/lib/notification-sounds";
import { useAuth } from "@/contexts/AuthContext";

type PrefsPatch = {
  notificationSoundOrder?: NotificationSoundId;
  notificationSoundMeeting?: NotificationSoundId;
  notificationSoundNote?: NotificationSoundId;
  notifyOrdersEnabled?: boolean;
  notifyMeetingsEnabled?: boolean;
  notifyNotesEnabled?: boolean;
  vibrationEnabledOrder?: boolean;
  vibrationEnabledMeeting?: boolean;
  vibrationEnabledNote?: boolean;
  notificationsMuted?: boolean;
};

function applyOptimistic(prev: AuthUser | undefined, patch: PrefsPatch): AuthUser | undefined {
  if (!prev) return prev;
  return { ...prev, ...patch };
}

export function useUpdatePrefs() {
  const queryClient = useQueryClient();
  const mutation = useUpdateNotificationPreferences();
  // Monotonic counter so out-of-order responses from rapid clicks (or a
  // dragged slider) cannot overwrite newer optimistic state.
  const seqRef = useRef(0);
  const lastAppliedRef = useRef(0);
  return (patch: PrefsPatch) => {
    const seq = ++seqRef.current;
    queryClient.cancelQueries({ queryKey: getGetMeQueryKey() }).catch(() => {});
    const previous = queryClient.getQueryData<AuthUser>(getGetMeQueryKey());
    const next = applyOptimistic(previous, patch);
    if (next) queryClient.setQueryData<AuthUser>(getGetMeQueryKey(), next);
    mutation.mutate(
      { data: patch },
      {
        onSuccess: (data) => {
          // Only the latest in-flight mutation may publish server state,
          // otherwise a slow earlier response would clobber a newer one.
          if (seq < lastAppliedRef.current) return;
          lastAppliedRef.current = seq;
          if (seq === seqRef.current) {
            queryClient.setQueryData(getGetMeQueryKey(), data);
          }
        },
        onError: () => {
          // Only roll back if no newer write has been initiated; otherwise
          // the newer optimistic state should stand.
          if (seq !== seqRef.current) return;
          if (previous) queryClient.setQueryData(getGetMeQueryKey(), previous);
          else queryClient.invalidateQueries({ queryKey: getGetMeQueryKey() });
        },
      },
    );
  };
}

type Slot = "order" | "meeting" | "note";

const SLOT_KEYS: Record<
  Slot,
  {
    enabledKey: keyof Pick<
      AuthUser,
      "notifyOrdersEnabled" | "notifyMeetingsEnabled" | "notifyNotesEnabled"
    >;
    soundKey: keyof Pick<
      AuthUser,
      | "notificationSoundOrder"
      | "notificationSoundMeeting"
      | "notificationSoundNote"
    >;
    vibrateKey: keyof Pick<
      AuthUser,
      "vibrationEnabledOrder" | "vibrationEnabledMeeting" | "vibrationEnabledNote"
    >;
    enabledLabel: string;
    vibrateLabel: string;
  }
> = {
  order: {
    enabledKey: "notifyOrdersEnabled",
    soundKey: "notificationSoundOrder",
    vibrateKey: "vibrationEnabledOrder",
    enabledLabel: "notifSettings.ordersEnabled",
    vibrateLabel: "notifSettings.vibrationOrder",
  },
  meeting: {
    enabledKey: "notifyMeetingsEnabled",
    soundKey: "notificationSoundMeeting",
    vibrateKey: "vibrationEnabledMeeting",
    enabledLabel: "notifSettings.meetingsEnabled",
    vibrateLabel: "notifSettings.vibrationMeeting",
  },
  note: {
    enabledKey: "notifyNotesEnabled",
    soundKey: "notificationSoundNote",
    vibrateKey: "vibrationEnabledNote",
    enabledLabel: "notifSettings.notesEnabled",
    vibrateLabel: "notifSettings.vibrationNote",
  },
};

function SoundList({
  current,
  onPick,
}: {
  current: NotificationSoundId;
  onPick: (id: NotificationSoundId) => void;
}) {
  const { t } = useTranslation();
  return (
    <div className="flex flex-col gap-0.5 max-h-56 overflow-y-auto">
      {ALL_SOUND_IDS.map((id) => {
        const isActive = id === current;
        return (
          <div
            key={id}
            className={`flex items-center justify-between gap-2 px-2 py-1.5 rounded-lg ${
              isActive ? "bg-primary/10" : "hover:bg-foreground/5"
            }`}
          >
            <button
              type="button"
              onClick={() => onPick(id)}
              className="flex-1 flex items-center gap-2 text-start min-w-0"
            >
              {isActive ? (
                <Check size={14} className="text-primary shrink-0" />
              ) : (
                <span className="w-3.5 shrink-0" />
              )}
              <span className="text-xs text-foreground font-medium truncate">
                {t(`notifSettings.sounds.${id}`)}
              </span>
            </button>
            <button
              type="button"
              onClick={() => {
                // #441: testPlay bypasses the 3s burst-coalescing
                // throttle (so users can audition several sounds in a
                // row from settings) and force-unlocks the audio
                // context inside this gesture handler.
                notificationPlayer.testPlay(id);
              }}
              aria-label={t("notifSettings.preview")}
              className="p-1 rounded hover:bg-foreground/10 text-muted-foreground"
            >
              <Play size={12} />
            </button>
          </div>
        );
      })}
    </div>
  );
}

function Toggle({
  active,
  onClick,
  label,
}: {
  active: boolean;
  onClick: () => void;
  label: string;
}) {
  return (
    <button
      type="button"
      onClick={onClick}
      className="w-full flex items-center justify-between gap-3 px-2 py-2 rounded-lg text-start hover:bg-foreground/5 transition-colors"
    >
      <span className="text-xs text-foreground font-medium truncate">{label}</span>
      <span
        className={`relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors ${
          active ? "bg-primary" : "bg-foreground/15"
        }`}
        aria-hidden="true"
      >
        <span
          className={`inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform ${
            active
              ? "translate-x-[18px] rtl:translate-x-0.5"
              : "translate-x-0.5 rtl:translate-x-[18px]"
          }`}
        />
      </span>
    </button>
  );
}

/**
 * One-tap mute toggle for the topbar (separate from the settings popover
 * trigger). Shows a confirmation toast so the user knows the change took
 * effect even though there's no visual focus shift.
 */
export function QuickMuteButton() {
  const { t } = useTranslation();
  const { user } = useAuth();
  const { toast } = useToast();
  const apply = useUpdatePrefs();
  if (!user) return null;
  const muted = user.notificationsMuted;
  return (
    <button
      type="button"
      aria-label={t(muted ? "notifSettings.quickUnmute" : "notifSettings.quickMute")}
      aria-pressed={muted}
      data-testid="notif-quick-mute"
      onClick={() => {
        const next = !muted;
        apply({ notificationsMuted: next });
        toast({
          title: t(next ? "notifSettings.mutedToast" : "notifSettings.unmutedToast"),
        });
      }}
      className="text-muted-foreground hover:text-foreground transition-colors p-1.5 md:p-2.5 rounded-lg md:rounded-xl hover:bg-foreground/5"
    >
      {muted ? (
        <VolumeX className="w-[18px] h-[18px] md:w-6 md:h-6" />
      ) : (
        <Volume2 className="w-[18px] h-[18px] md:w-6 md:h-6" />
      )}
    </button>
  );
}

/**
 * Inner content of the notification settings popover, extracted so the
 * Settings panel can render the same form without
 * re-implementing the toggles, slot tabs, and sound picker. The
 * standalone `NotificationSettingsPicker` below wraps it in a popover
 * trigger + content; the unified Settings panel mounts this content
 * directly inside its own surface.
 */
export function NotificationSettingsContent() {
  const { t } = useTranslation();
  const { user } = useAuth();
  const [slot, setSlot] = useState<Slot>("order");
  const apply = useUpdatePrefs();

  if (!user) return null;
  const muted = user.notificationsMuted;
  const cfg = SLOT_KEYS[slot];

  return (
    <div>
      <div className="px-2 py-1.5 text-xs font-semibold uppercase tracking-wider text-muted-foreground">
        {t("notifSettings.title")}
      </div>

      <Toggle
        active={!muted}
        onClick={() => apply({ notificationsMuted: !muted })}
        label={t("notifSettings.globalSound")}
      />

      <div className="h-px bg-foreground/10 mx-1 my-1" />

      <div
        className="grid grid-cols-3 gap-1 mx-1 mb-1 p-1 rounded-lg bg-foreground/5"
        role="group"
      >
        {(["order", "meeting", "note"] as const).map((s) => {
          const isActive = s === slot;
          return (
            <button
              key={s}
              type="button"
              onClick={() => setSlot(s)}
              aria-pressed={isActive}
              className={`text-xs font-medium px-2 py-1.5 rounded-md transition-colors ${
                isActive
                  ? "bg-background text-foreground shadow-sm"
                  : "text-muted-foreground hover:text-foreground"
              }`}
            >
              {t(`notifSettings.slot.${s}`)}
            </button>
          );
        })}
      </div>

      <Toggle
        active={user[cfg.enabledKey]}
        onClick={() =>
          apply({ [cfg.enabledKey]: !user[cfg.enabledKey] } as PrefsPatch)
        }
        label={t(cfg.enabledLabel)}
      />
      <Toggle
        active={user[cfg.vibrateKey]}
        onClick={() =>
          apply({ [cfg.vibrateKey]: !user[cfg.vibrateKey] } as PrefsPatch)
        }
        label={t(cfg.vibrateLabel)}
      />

      <div className="h-px bg-foreground/10 mx-1 my-1" />

      <PushToggleRow />

      <div className="h-px bg-foreground/10 mx-1 my-1" />

      <SoundList
        current={user[cfg.soundKey]}
        onPick={(id) =>
          apply({ [cfg.soundKey]: id } as PrefsPatch)
        }
      />
    </div>
  );
}

function PushToggleRow() {
  const { t } = useTranslation();
  const { toast } = useToast();
  const { status, busy, enable, disable } = usePushSubscription();
  const subscribed = status === "subscribed";
  // iOS Safari outside Home Screen exposes the Push APIs but
  // `subscribe()` always fails — treat it as a first-class "needs PWA
  // install" state so the toggle doesn't tease the user with an
  // Enable affordance that can't succeed.
  const needsIosInstall = isIosSafariNonStandalone() && !subscribed;
  const disabled =
    busy ||
    status === "unsupported" ||
    status === "denied" ||
    needsIosInstall;
  const subtitle = needsIosInstall
    ? t("notifSettings.push.errors.unsupported_ios_safari")
    : status === "unsupported"
      ? t("notifSettings.push.unsupported")
      : status === "denied"
        ? t("notifSettings.push.denied")
        : subscribed
          ? t("notifSettings.push.enabled")
          : t("notifSettings.push.desc");

  return (
    <div className="px-2 py-2">
      <div className="flex items-center justify-between gap-3">
        <div className="min-w-0 flex-1">
          <div className="text-xs font-semibold text-foreground truncate">
            {t("notifSettings.push.title")}
          </div>
          <div className="text-[11px] text-muted-foreground mt-0.5 leading-snug">
            {subtitle}
          </div>
        </div>
        <button
          type="button"
          disabled={disabled}
          data-testid="notif-push-toggle"
          onClick={async () => {
            if (subscribed) {
              await disable();
            } else {
              const res = await enable();
              if (res.ok) {
                toast({ title: t("notifSettings.push.enableSuccess") });
              } else {
                // #576: show the specific failure reason (iOS-needs-PWA,
                // permission denied, VAPID unreachable, subscribe failed,
                // server rejected) instead of a generic toast.
                const key = `notifSettings.push.errors.${res.reason}`;
                const msg = t(key);
                toast({
                  title:
                    msg && msg !== key
                      ? msg
                      : t("notifSettings.push.enableFailed"),
                });
              }
            }
          }}
          className={`relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors ${
            subscribed ? "bg-primary" : "bg-foreground/15"
          } ${disabled ? "opacity-50" : ""}`}
          aria-pressed={subscribed}
          aria-label={
            subscribed
              ? t("notifSettings.push.disable")
              : t("notifSettings.push.enable")
          }
        >
          <span
            className={`inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform ${
              subscribed
                ? "translate-x-[18px] rtl:translate-x-0.5"
                : "translate-x-0.5 rtl:translate-x-[18px]"
            }`}
          />
        </button>
      </div>
    </div>
  );
}

export function NotificationSettingsPicker() {
  const { t } = useTranslation();
  const { user } = useAuth();
  const [open, setOpen] = useState(false);

  if (!user) return null;
  const muted = user.notificationsMuted;

  return (
    <Popover open={open} onOpenChange={setOpen}>
      <PopoverTrigger asChild>
        <button
          type="button"
          aria-label={t("notifSettings.title")}
          aria-haspopup="dialog"
          className="text-muted-foreground hover:text-foreground transition-colors p-1.5 md:p-2.5 rounded-lg md:rounded-xl hover:bg-foreground/5 relative"
        >
          {muted ? (
            <BellOff className="w-[18px] h-[18px] md:w-6 md:h-6" />
          ) : (
            <Bell className="w-[18px] h-[18px] md:w-6 md:h-6" />
          )}
        </button>
      </PopoverTrigger>
      <PopoverContent className="w-80 p-2" align="end">
        <NotificationSettingsContent />
      </PopoverContent>
    </Popover>
  );
}
