"use client";

import {
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
  type MouseEvent as ReactMouseEvent,
  type ReactNode
} from "react";
import type { Locale } from "@/config/i18n";
import type { Dictionary } from "@allai/i18n/server";
import { TopNav } from "@/components/layout/TopNav";
import { Icon } from "@/components/ui/Icon";
import styles from "./NanoBananaWorkbench.module.css";

type WorkbenchProps = {
  locale: Locale;
  dictionary: Dictionary;
};

type ModelKey = "nano-banana" | "seedream" | "veo3" | "sora" | "runway" | "kling";
type AspectRatio =
  | "1:1"
  | "3:2"
  | "2:3"
  | "4:3"
  | "3:4"
  | "4:5"
  | "5:4"
  | "16:9"
  | "21:9"
  | "9:16"
  | "auto";
type SeedreamImageSize =
  | "square"
  | "square_hd"
  | "portrait_4_3"
  | "portrait_3_2"
  | "portrait_16_9"
  | "landscape_4_3"
  | "landscape_3_2"
  | "landscape_16_9"
  | "landscape_21_9";
type SeedreamResolution = "1K" | "2K" | "4K";
type SeedreamMaxImages = "1" | "2" | "3" | "4" | "5" | "6";

type PreviewImage = {
  id: string;
  file: File;
  url: string;
  name: string;
  size: number;
  uploadedUrl: string | null;
};

type NanoHistoryStatus = "generating" | "success" | "failed";

type NanoHistoryItem = {
  id: string;
  taskId: string;
  prompt: string;
  createdAt: number;
  status: NanoHistoryStatus;
  progress: number;
  urls?: string[];
  error?: string | null;
};

type TaskState = "waiting" | "queuing" | "generating" | "success" | "fail";

type PlaceholderResult = {
  id: string;
  image: string;
  prompt: string;
  author: string;
};

const MODEL_ORDER: ModelKey[] = ["nano-banana", "seedream", "veo3", "sora", "runway", "kling"];
const IMAGE_MODELS: ModelKey[] = ["nano-banana", "seedream"];
const VIDEO_MODELS: ModelKey[] = ["veo3", "sora", "runway", "kling"];

const MAX_REFERENCE_IMAGES = 9;
const POLLING_INTERVAL_MS = 2500;
const MAX_POLL_ATTEMPTS = 60;
const VISIBLE_BATCH_SIZE = 5;

const placeholderResults: PlaceholderResult[] = [
  {
    id: "placeholder-1",
    image: "https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?w=800&q=80",
    prompt: "Cinematic portrait with soft rim light",
    author: "Lia"
  },
  {
    id: "placeholder-2",
    image: "https://images.unsplash.com/photo-1518837695005-2083093ee35b?w=800&q=80",
    prompt: "Digital painting in pastel tones",
    author: "Makoto"
  },
  {
    id: "placeholder-3",
    image: "https://images.unsplash.com/photo-1520607162513-77705c0f0d4a?w=800&q=80",
    prompt: "Minimalist office interior at sunrise",
    author: "Jo"
  },
  {
    id: "placeholder-4",
    image: "https://images.unsplash.com/photo-1493558103817-58b2924bce98?w=800&q=80",
    prompt: "Tropical shore after the storm",
    author: "Sasha"
  },
  {
    id: "placeholder-5",
    image: "https://images.unsplash.com/photo-1500534303141-436a1ffdb8eb?w=800&q=80",
    prompt: "Fantasy portrait with glowing accents",
    author: "Eren"
  }
];

async function readJsonSafely<T>(response: Response): Promise<T | null> {
  const raw = await response.text();
  if (!raw) {
    return null;
  }
  try {
    return JSON.parse(raw) as T;
  } catch (error) {
    console.error("Failed to parse JSON from response", error, raw);
    return null;
  }
}

function createPreview(file: File): PreviewImage {
  return {
    id: crypto.randomUUID(),
    file,
    url: URL.createObjectURL(file),
    name: file.name,
    size: file.size,
    uploadedUrl: null
  };
}

function formatBytes(bytes: number) {
  if (bytes < 1024) {
    return `${bytes} B`;
  }
  if (bytes < 1024 * 1024) {
    return `${Math.round(bytes / 1024)} KB`;
  }
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}

export function NanoBananaWorkbench({ locale, dictionary }: WorkbenchProps) {
  const nanoDict = dictionary.nanoWorkbench;
  const [activeModel, setActiveModel] = useState<ModelKey>("nano-banana");
  const [prompt, setPrompt] = useState("");
  const promptRef = useRef<HTMLTextAreaElement | null>(null);
  const activePromptRef = useRef<string>("");

  const [aspect, setAspect] = useState<AspectRatio>("4:3");
  const [seedreamSize, setSeedreamSize] = useState<SeedreamImageSize>("square");
  const [seedreamResolution, setSeedreamResolution] = useState<SeedreamResolution>("1K");
  const [seedreamMaxImages, setSeedreamMaxImages] = useState<SeedreamMaxImages>("1");
  const [seedreamSeed, setSeedreamSeed] = useState("");

  const [primaryImage, setPrimaryImage] = useState<PreviewImage | null>(null);
  const [referenceImages, setReferenceImages] = useState<Array<PreviewImage | null>>(
    Array(MAX_REFERENCE_IMAGES).fill(null)
  );

  const [isGeneratingImage, setIsGeneratingImage] = useState(false);
  const [nanoStatusMessage, setNanoStatusMessage] = useState<string | null>(null);
  const [nanoError, setNanoError] = useState<string | null>(null);
  const [nanoTaskId, setNanoTaskId] = useState<string | null>(null);

  const [nanoHistory, setNanoHistory] = useState<NanoHistoryItem[]>([]);
  const [visibleHistoryCount, setVisibleHistoryCount] = useState(VISIBLE_BATCH_SIZE);

  const [previewItem, setPreviewItem] = useState<NanoHistoryItem | null>(null);
  const [previewIndex, setPreviewIndex] = useState(0);
  const [shareItem, setShareItem] = useState<NanoHistoryItem | null>(null);
  const [animateItem, setAnimateItem] = useState<NanoHistoryItem | null>(null);
  const [deleteItem, setDeleteItem] = useState<NanoHistoryItem | null>(null);
  const [animateSelectedModel, setAnimateSelectedModel] = useState<ModelKey>(VIDEO_MODELS[0]);
  const [shareFeedback, setShareFeedback] = useState<string | null>(null);

  const [actionMenuOpenId, setActionMenuOpenId] = useState<string | null>(null);

  const loadMoreRef = useRef<HTMLDivElement | null>(null);
  const pollTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const pollAttemptsRef = useRef(0);

  const isSeedream = activeModel === "seedream";
  const isImageModel = IMAGE_MODELS.includes(activeModel);

  const modelOptions = useMemo(
    () =>
      MODEL_ORDER.map((key) => ({
        key,
        label: nanoDict.modelLabels[key] ?? key
      })),
    [nanoDict.modelLabels]
  );

  const relativeTimeFormatter = useMemo(
    () => new Intl.RelativeTimeFormat(locale, { numeric: "auto" }),
    [locale]
  );

  const hasPendingUploads = useMemo(
    () =>
      Boolean(
        (primaryImage && !primaryImage.uploadedUrl) ||
          referenceImages.some((image) => image && !image.uploadedUrl)
      ),
    [primaryImage, referenceImages]
  );

  const uploadImageFile = useCallback(async (file: File) => {
    const formData = new FormData();
    formData.append("file", file);

    const response = await fetch("/api/nano-banana/uploads", {
      method: "POST",
      body: formData,
      cache: "no-store"
    });

    const payload = (await response.json()) as { url?: string; message?: string };

    if (!response.ok || !payload.url) {
      throw new Error(payload.message || nanoDict.historyStatus.failed);
    }

    return payload.url;
  }, [nanoDict.historyStatus.failed]);

  const displayedHistory = useMemo(() => {
    if (!nanoHistory.length) return [];
    const sorted = [...nanoHistory].sort((a, b) => b.createdAt - a.createdAt);
    return sorted.slice(0, Math.min(sorted.length, visibleHistoryCount));
  }, [nanoHistory, visibleHistoryCount]);

  const hasMoreHistory = nanoHistory.length > displayedHistory.length;
  const previewImages = useMemo(() => {
    if (!previewItem) return [];
    if (previewItem.urls && previewItem.urls.length > 0) return previewItem.urls;
    return [];
  }, [previewItem]);

  const currentPreviewImage = previewImages[previewIndex] ?? null;

  const seedreamSizeOptions: Array<{ value: SeedreamImageSize; label: string }> = useMemo(
    () => [
      { value: "square", label: "Square" },
      { value: "square_hd", label: "Square HD" },
      { value: "portrait_4_3", label: "Portrait 3:4" },
      { value: "portrait_3_2", label: "Portrait 2:3" },
      { value: "portrait_16_9", label: "Portrait 9:16" },
      { value: "landscape_4_3", label: "Landscape 4:3" },
      { value: "landscape_3_2", label: "Landscape 3:2" },
      { value: "landscape_16_9", label: "Landscape 16:9" },
      { value: "landscape_21_9", label: "Landscape 21:9" }
    ],
    []
  );

  const seedreamResolutionOptions: Array<{ value: SeedreamResolution; label: string }> = useMemo(
    () => [
      { value: "1K", label: "1K" },
      { value: "2K", label: "2K" },
      { value: "4K", label: "4K" }
    ],
    []
  );

  const seedreamMaxImagesOptions: SeedreamMaxImages[] = useMemo(() => ["1", "2", "3", "4", "5", "6"], []);

  const seedreamPayload = useCallback(() => {
    const seedInt = parseInt(seedreamSeed, 10);
    return {
      prompt: prompt.trim(),
      output_format: "png",
      image_size: seedreamSize,
      image_resolution: seedreamResolution,
      max_images: Number(seedreamMaxImages),
      ...(Number.isNaN(seedInt) ? {} : { seed: seedInt })
    };
  }, [prompt, seedreamMaxImages, seedreamResolution, seedreamSeed, seedreamSize]);

  const nanoPayload = useCallback(
    () => ({
      prompt: prompt.trim(),
      output_format: "png",
      image_size: aspect
    }),
    [aspect, prompt]
  );

  const toAbsoluteUrl = useCallback((url: string) => {
    if (typeof window === "undefined") {
      return url;
    }

    try {
      return new URL(url, window.location.origin).toString();
    } catch {
      return url;
    }
  }, []);

  const createTaskPayload = useCallback(() => {
    if (isSeedream) {
      return {
        model: "bytedance/seedream-v4-text-to-image",
        input: seedreamPayload()
      };
    }

    const primaryUrl = primaryImage?.uploadedUrl
      ? toAbsoluteUrl(primaryImage.uploadedUrl)
      : null;
    const referenceUrls = referenceImages
      .map((ref) => (ref?.uploadedUrl ? toAbsoluteUrl(ref.uploadedUrl) : null))
      .filter((url): url is string => Boolean(url));

    if (primaryUrl || referenceUrls.length > 0) {
      const imageUrls = primaryUrl ? [primaryUrl, ...referenceUrls] : referenceUrls;
      return {
        model: "google/nano-banana-edit",
        input: {
          ...nanoPayload(),
          image_urls: imageUrls
        }
      };
    }

    return {
      model: "google/nano-banana",
      input: nanoPayload()
    };
  }, [isSeedream, nanoPayload, primaryImage?.uploadedUrl, referenceImages, seedreamPayload, toAbsoluteUrl]);

  const clearPollTimer = useCallback(() => {
    if (pollTimeoutRef.current) {
      clearTimeout(pollTimeoutRef.current);
      pollTimeoutRef.current = null;
    }
  }, []);

  const resetNanoJobState = useCallback(
    (options?: { preserveHistory?: boolean }) => {
      clearPollTimer();
      pollAttemptsRef.current = 0;
      setNanoTaskId(null);
      setNanoStatusMessage(null);
      setIsGeneratingImage(false);
      if (!options?.preserveHistory) {
        setNanoHistory([]);
        setVisibleHistoryCount(VISIBLE_BATCH_SIZE);
      }
    },
    [clearPollTimer]
  );

  const updateNanoHistoryItem = useCallback((taskId: string, updater: (item: NanoHistoryItem) => NanoHistoryItem) => {
    setNanoHistory((prev) => prev.map((item) => (item.taskId === taskId ? updater(item) : item)));
  }, []);

  const bumpHistoryProgress = useCallback(
    (taskId: string, state: TaskState) => {
      updateNanoHistoryItem(taskId, (item) => {
        if (item.status !== "generating") return item;
        const base =
          state === "waiting" ? 7 : state === "queuing" ? 24 : state === "generating" ? 55 : item.progress;
        const jitter = Math.random() * 10 + 5;
        return {
          ...item,
          progress: Math.min(95, Math.max(base, item.progress + jitter))
        };
      });
    },
    [updateNanoHistoryItem]
  );

  const formatRelativeTime = useCallback(
    (timestamp: number) => {
      const diff = timestamp - Date.now();
      const absolute = Math.abs(diff);
      const minute = 60 * 1000;
      const hour = 60 * minute;
      const day = 24 * hour;
      if (absolute < minute) {
        return relativeTimeFormatter.format(Math.round(diff / 1000), "second");
      }
      if (absolute < hour) {
        return relativeTimeFormatter.format(Math.round(diff / minute), "minute");
      }
      if (absolute < day) {
        return relativeTimeFormatter.format(Math.round(diff / hour), "hour");
      }
      return relativeTimeFormatter.format(Math.round(diff / day), "day");
    },
    [relativeTimeFormatter]
  );

  const pollNanoTask = useCallback(
    async (taskId: string) => {
      if (!taskId) return;
      pollAttemptsRef.current += 1;
      if (pollAttemptsRef.current > MAX_POLL_ATTEMPTS) {
        clearPollTimer();
        setNanoStatusMessage(null);
        setNanoError(nanoDict.historyStatus.failed);
        updateNanoHistoryItem(taskId, (item) => ({
          ...item,
          status: "failed",
          progress: 100,
          error: nanoDict.historyStatus.failed
        }));
        setIsGeneratingImage(false);
        return;
      }

      try {
        const response = await fetch(`/api/nano-banana/tasks?taskId=${encodeURIComponent(taskId)}`, {
          cache: "no-store"
        }).catch((error) => {
          console.error("Failed to poll Nano Banana task", error);
          return null;
        });

        if (!response) {
          clearPollTimer();
          pollTimeoutRef.current = setTimeout(() => {
            void pollNanoTask(taskId);
          }, POLLING_INTERVAL_MS);
          return;
        }

        const payload = await readJsonSafely<{
          data?: { state?: TaskState; resultJson?: string; failMsg?: string };
          message?: string;
        }>(response);

        if (!response.ok) {
          console.error("Upstream status not OK", response.status, payload);
          clearPollTimer();
          pollTimeoutRef.current = setTimeout(() => {
            void pollNanoTask(taskId);
          }, POLLING_INTERVAL_MS);
          return;
        }

        const state = payload?.data?.state;
        if (!state) {
          clearPollTimer();
          pollTimeoutRef.current = setTimeout(() => {
            void pollNanoTask(taskId);
          }, POLLING_INTERVAL_MS);
          return;
        }

        if (state === "success") {
          pollAttemptsRef.current = 0;
          clearPollTimer();

          const urls = (() => {
            if (!payload?.data?.resultJson) return [] as string[];
            try {
              const parsed = JSON.parse(payload.data.resultJson) as { resultUrls?: string[] };
              return parsed.resultUrls ?? [];
            } catch (error) {
              console.error("Failed to parse resultJson", error, payload?.data?.resultJson);
              return [];
            }
          })();

          if (!urls.length) {
            const message = nanoDict.historyStatus.noResult;
            setNanoError(message);
            updateNanoHistoryItem(taskId, (item) => ({
              ...item,
              status: "failed",
              progress: 100,
              error: message
            }));
          } else {
            setNanoError(null);
            setNanoStatusMessage(nanoDict.historyStatus.completed);
            updateNanoHistoryItem(taskId, (item) => ({
              ...item,
              status: "success",
              progress: 100,
              urls
            }));
          }

          setIsGeneratingImage(false);
          activePromptRef.current = "";
          return;
        }

        if (state === "fail") {
          clearPollTimer();
          const message = payload?.data?.failMsg || payload?.message || nanoDict.historyStatus.failed;
          setNanoStatusMessage(null);
          setNanoError(message);
          updateNanoHistoryItem(taskId, (item) => ({
            ...item,
            status: "failed",
            progress: 100,
            error: message
          }));
          setIsGeneratingImage(false);
          activePromptRef.current = "";
          return;
        }

        bumpHistoryProgress(taskId, state);
        const statusMessage =
          state === "waiting"
            ? nanoDict.historyStatus.waiting
            : state === "queuing"
              ? nanoDict.historyStatus.queuing
              : nanoDict.historyStatus.generating;
        setNanoStatusMessage(statusMessage);
        clearPollTimer();
        pollTimeoutRef.current = setTimeout(() => {
          void pollNanoTask(taskId);
        }, POLLING_INTERVAL_MS);
      } catch (error) {
        console.error(error);
        if (pollAttemptsRef.current < MAX_POLL_ATTEMPTS) {
          clearPollTimer();
          pollTimeoutRef.current = setTimeout(() => {
            void pollNanoTask(taskId);
          }, POLLING_INTERVAL_MS);
          return;
        }
        clearPollTimer();
        const message = error instanceof Error ? error.message : nanoDict.historyStatus.failed;
        setNanoStatusMessage(null);
        setNanoError(message);
        updateNanoHistoryItem(taskId, (item) => ({
          ...item,
          status: "failed",
          progress: 100,
          error: message
        }));
        setIsGeneratingImage(false);
        activePromptRef.current = "";
      }
    },
    [
      bumpHistoryProgress,
      clearPollTimer,
      nanoDict.historyStatus.completed,
      nanoDict.historyStatus.failed,
      nanoDict.historyStatus.generating,
      nanoDict.historyStatus.noResult,
      nanoDict.historyStatus.queuing,
      nanoDict.historyStatus.waiting,
      updateNanoHistoryItem
    ]
  );

  const handlePromptChange = useCallback((event: React.ChangeEvent<HTMLTextAreaElement>) => {
    setPrompt(event.target.value);
  }, []);

  const handlePrimaryFiles = useCallback((files: FileList | null) => {
    if (!files || !files.length) return;
    const [first] = files;
    const preview = createPreview(first);
    setPrimaryImage((prev) => {
      if (prev) URL.revokeObjectURL(prev.url);
      return preview;
    });

    uploadImageFile(first)
      .then((uploadedUrl) => {
        setPrimaryImage((current) => {
          if (!current || current.id !== preview.id) return current;
          return { ...current, uploadedUrl };
        });
      })
      .catch((error) => {
        console.error(error);
        setNanoError(error instanceof Error ? error.message : nanoDict.historyStatus.failed);
        setPrimaryImage((current) => {
          if (current && current.id === preview.id) {
            URL.revokeObjectURL(current.url);
            return null;
          }
          return current;
        });
      });
  }, [nanoDict.historyStatus.failed, setNanoError, uploadImageFile]);

  const handleReferenceFiles = useCallback((index: number, files: FileList | null) => {
    if (!files || !files.length) return;
    const file = files[0];
    const preview = createPreview(file);
    setReferenceImages((prev) => {
      const copy = [...prev];
      if (copy[index]) URL.revokeObjectURL(copy[index]!.url);
      copy[index] = preview;
      return copy;
    });

    uploadImageFile(file)
      .then((uploadedUrl) => {
        setReferenceImages((prev) => {
          const copy = [...prev];
          const current = copy[index];
          if (current && current.id === preview.id) {
            copy[index] = { ...current, uploadedUrl };
          }
          return copy;
        });
      })
      .catch((error) => {
        console.error(error);
        setNanoError(error instanceof Error ? error.message : nanoDict.historyStatus.failed);
        setReferenceImages((prev) => {
          const copy = [...prev];
          const current = copy[index];
          if (current && current.id === preview.id) {
            URL.revokeObjectURL(current.url);
            copy[index] = null;
          }
          return copy;
        });
      });
  }, [nanoDict.historyStatus.failed, setNanoError, uploadImageFile]);

  const removeReference = useCallback((index: number, event?: ReactMouseEvent<HTMLButtonElement>) => {
    event?.preventDefault();
    setReferenceImages((prev) => {
      const copy = [...prev];
      if (copy[index]) URL.revokeObjectURL(copy[index]!.url);
      copy[index] = null;
      return copy;
    });
  }, []);

  const handleCopyPrompt = useCallback(
    async (item: NanoHistoryItem) => {
      const text = item.prompt || nanoDict.promptPlaceholder;
      try {
        await navigator.clipboard.writeText(text);
        setNanoError(null);
        setNanoStatusMessage(nanoDict.historyStatus.promptCopied);
      } catch {
        setNanoError(nanoDict.historyStatus.promptCopyError);
      }
    },
    [nanoDict.historyStatus.promptCopied, nanoDict.historyStatus.promptCopyError, nanoDict.promptPlaceholder]
  );

  const handleDownload = useCallback(async (item: NanoHistoryItem) => {
    const url = item.urls?.[0];
    if (!url) return;

    try {
      const response = await fetch(url, { cache: "no-store" });
      if (!response.ok) {
        throw new Error("Download failed");
      }
      const blob = await response.blob();
      const extension = blob.type.includes("png") ? "png" : blob.type.includes("jpeg") ? "jpg" : "png";
      const objectUrl = URL.createObjectURL(blob);
      const link = document.createElement("a");
      link.href = objectUrl;
      link.download = `nano-banana-${item.taskId}.${extension}`;
      document.body.appendChild(link);
      link.click();
      link.remove();
      URL.revokeObjectURL(objectUrl);
    } catch (error) {
      console.error(error);
      window.open(url, "_blank", "noopener");
    }
  }, []);

  const handleShareLink = useCallback(async (item: NanoHistoryItem) => {
    const url = item.urls?.[0];
    if (!url) return;
    try {
      await navigator.clipboard.writeText(url);
      setShareFeedback(nanoDict.historyModals.shareCopied);
    } catch {
      setShareFeedback(nanoDict.historyModals.shareCopyLink);
    }
  }, [nanoDict.historyModals.shareCopied, nanoDict.historyModals.shareCopyLink]);

  const handleShare = useCallback((item: NanoHistoryItem, channel: "email" | "telegram" | "whatsapp") => {
    const url = item.urls?.[0];
    if (!url) return;
    const promptText = item.prompt || nanoDict.promptPlaceholder;
    switch (channel) {
      case "email": {
        const subject = encodeURIComponent(promptText.slice(0, 60) || "Generated image");
        const body = encodeURIComponent(`${promptText}\n\n${url}`);
        window.open(`mailto:?subject=${subject}&body=${body}`, "_blank", "noopener");
        break;
      }
      case "telegram": {
        window.open(
          `https://t.me/share/url?url=${encodeURIComponent(url)}&text=${encodeURIComponent(promptText)}`,
          "_blank",
          "noopener"
        );
        break;
      }
      case "whatsapp": {
        window.open(
          `https://api.whatsapp.com/send?text=${encodeURIComponent(`${promptText}\n${url}`)}`,
          "_blank",
          "noopener"
        );
        break;
      }
    }
  }, [nanoDict.promptPlaceholder]);

  const handleDeleteConfirmed = useCallback(() => {
    if (!deleteItem) return;
    setNanoHistory((prev) => prev.filter((item) => item.id !== deleteItem.id));
    setVisibleHistoryCount((count) => Math.max(VISIBLE_BATCH_SIZE, Math.min(count, nanoHistory.length - 1)));
    setDeleteItem(null);
  }, [deleteItem, nanoHistory.length]);

  const handleAnimateConfirm = useCallback(() => {
    if (!animateItem) return;
    setNanoStatusMessage(
      `${nanoDict.historyModals.animateTitle}: ${nanoDict.modelLabels[animateSelectedModel] ?? animateSelectedModel}`
    );
    setAnimateItem(null);
  }, [animateItem, animateSelectedModel, nanoDict.historyModals.animateTitle, nanoDict.modelLabels]);

  const handleNanoSubmit = useCallback(async () => {
    const trimmedPrompt = prompt.trim();
    if (!trimmedPrompt) {
      setNanoError("Enter a prompt to start generating.");
      return;
    }

    if (hasPendingUploads) {
      setNanoError("Please wait for images to finish uploading before generating.");
      return;
    }

    setNanoError(null);
    setNanoStatusMessage(nanoDict.historyStatus.submitting);
    setIsGeneratingImage(true);
    activePromptRef.current = trimmedPrompt;

    try {
      const response = await fetch("/api/nano-banana/tasks", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(createTaskPayload()),
        cache: "no-store"
      });

      const payload = await readJsonSafely<{ data?: { taskId?: string }; message?: string }>(response);
      if (!response.ok || !payload?.data?.taskId) {
        throw new Error(payload?.message || nanoDict.historyStatus.failed);
      }

      const taskId = payload?.data?.taskId as string;
      const createdAt = Date.now();
      const placeholder: NanoHistoryItem = {
        id: `${taskId}-${createdAt}`,
        taskId,
        prompt: trimmedPrompt,
        createdAt,
        status: "generating",
        progress: 5
      };

      setNanoHistory((prev) => [placeholder, ...prev]);
      setVisibleHistoryCount((count) => Math.max(count, VISIBLE_BATCH_SIZE));
      setNanoTaskId(taskId);
      setNanoStatusMessage(nanoDict.historyStatus.waiting);
      pollAttemptsRef.current = 0;
      clearPollTimer();
      pollTimeoutRef.current = setTimeout(() => {
        void pollNanoTask(taskId);
      }, POLLING_INTERVAL_MS);
    } catch (error) {
      console.error(error);
      setNanoError(error instanceof Error ? error.message : nanoDict.historyStatus.failed);
      setNanoStatusMessage(null);
      setIsGeneratingImage(false);
      activePromptRef.current = "";
    }
  }, [
    createTaskPayload,
    nanoDict.historyStatus.failed,
    nanoDict.historyStatus.submitting,
    nanoDict.historyStatus.waiting,
    prompt,
    hasPendingUploads,
    clearPollTimer,
    pollNanoTask
  ]);

  const handleSubmit = useCallback(
    (event: React.FormEvent<HTMLFormElement>) => {
      event.preventDefault();
      if (!IMAGE_MODELS.includes(activeModel)) return;
      void handleNanoSubmit();
    },
    [activeModel, handleNanoSubmit]
  );

  useEffect(() => {
    const textarea = promptRef.current;
    if (!textarea) return;
    textarea.style.height = "auto";
    textarea.style.height = `${textarea.scrollHeight}px`;
  }, [prompt]);

  useEffect(() => {
    return () => {
      clearPollTimer();
      if (primaryImage) URL.revokeObjectURL(primaryImage.url);
      referenceImages.forEach((image) => image && URL.revokeObjectURL(image.url));
    };
  }, [clearPollTimer, primaryImage, referenceImages]);

  useEffect(() => {
    const target = loadMoreRef.current;
    if (!target) return;
    if (!hasMoreHistory) return;
    const observer = new IntersectionObserver(
      (entries) => {
        const [entry] = entries;
        if (entry?.isIntersecting) {
          setVisibleHistoryCount((count) => Math.min(nanoHistory.length, count + VISIBLE_BATCH_SIZE));
        }
      },
      { root: null, rootMargin: "0px 0px 180px 0px", threshold: 0 }
    );
    observer.observe(target);
    return () => observer.disconnect();
  }, [hasMoreHistory, nanoHistory.length]);

  useEffect(() => {
    if (previewItem) {
      setPreviewIndex(0);
    }
  }, [previewItem?.id]);

  useEffect(() => {
    return () => {
      clearPollTimer();
    };
  }, [clearPollTimer]);

  const filledReferences = useMemo(() => referenceImages.filter(Boolean).length, [referenceImages]);
  const visibleReferenceSlots = Math.min(referenceImages.length, filledReferences + 1);

  const promo = nanoDict.promos[activeModel] ?? nanoDict.promos["nano-banana"];
  const promoPanel = promo ? (
    <article className={styles.promoCard}>
      <span className={styles.contentTag}>{promo.tag}</span>
      <h3>{promo.title}</h3>
      <p>{promo.description}</p>
      <ul>
        {promo.bulletPoints.map((point) => (
          <li key={point}>{point}</li>
        ))}
      </ul>
    </article>
  ) : null;

  const renderHistoryCard = (item: NanoHistoryItem) => {
    const isGenerating = item.status === "generating";
    const isFailed = item.status === "failed";
    const isSuccess = item.status === "success";
    const mainImageUrl = item.urls?.[0];
    const progressValue = Math.min(99, Math.max(0, Math.round(item.progress)));

    return (
      <article key={item.id} className={styles.historyItem} data-status={item.status}>
        <div className={styles.historyMedia}>
          {isGenerating ? (
            <div className={styles.historyPlaceholder} role="status">
              <div className={styles.historyProgressCircle}>
                <span>{progressValue}%</span>
              </div>
              <p className={styles.historyPlaceholderTitle}>{nanoDict.historyStatus.generating}</p>
              <div className={styles.historyProgressBar}>
                <div className={styles.historyProgressFill} style={{ width: `${Math.min(100, progressValue)}%` }} />
              </div>
            </div>
          ) : isFailed ? (
            <div className={styles.historyPlaceholder}>
              <Icon name="AlertTriangle" className={styles.historyErrorIcon} aria-hidden />
              <p className={styles.historyPlaceholderTitle}>{nanoDict.historyStatus.failed}</p>
              {item.error ? <span className={styles.historyErrorMessage}>{item.error}</span> : null}
            </div>
          ) : mainImageUrl ? (
            <button
              type="button"
              className={styles.historyPreviewButton}
              onClick={() => setPreviewItem(item)}
              aria-label={nanoDict.historyActions.preview}
            >
              <img
                src={mainImageUrl}
                alt={item.prompt || nanoDict.promptPlaceholder}
                className={styles.historyImage}
                loading="lazy"
              />
            </button>
          ) : null}
        </div>
        <div className={styles.historyMeta}>
          {isGenerating ? (
            <div className={styles.historyProgress}>
              <div className={styles.historyProgressLabelRow}>
                <span>{nanoDict.historyStatus.progressLabel}</span>
                <span>{progressValue}%</span>
              </div>
              <div className={styles.historyProgressBar}>
                <div className={styles.historyProgressFill} style={{ width: `${Math.min(100, progressValue)}%` }} />
              </div>
            </div>
          ) : null}
          {isFailed && item.error ? <p className={styles.historyErrorMessage}>{item.error}</p> : null}
          <div className={styles.historyMetaActions}>
            <HistoryItemMenu
              item={item}
              isOpen={actionMenuOpenId === item.id}
              onToggle={() => setActionMenuOpenId((current) => (current === item.id ? null : item.id))}
              onClose={() => setActionMenuOpenId(null)}
              labels={nanoDict.historyActions}
              onPreview={(entry) => {
                setActionMenuOpenId(null);
                setPreviewItem(entry);
              }}
              onDownload={(entry) => {
                setActionMenuOpenId(null);
                void handleDownload(entry);
              }}
              onCopyPrompt={(entry) => {
                setActionMenuOpenId(null);
                void handleCopyPrompt(entry);
              }}
              onAnimate={(entry) => {
                setActionMenuOpenId(null);
                setAnimateItem(entry);
                setAnimateSelectedModel(VIDEO_MODELS[0]);
              }}
              onShare={(entry) => {
                setActionMenuOpenId(null);
                setShareItem(entry);
                setShareFeedback(null);
              }}
              onDelete={(entry) => {
                setActionMenuOpenId(null);
                setDeleteItem(entry);
              }}
              disabled={isGenerating}
            />
          </div>
        </div>
      </article>
    );
  };

  const renderHistory = () => {
    if (displayedHistory.length) {
      return (
        <>
          <div className={styles.historyGrid}>
            {displayedHistory.map((item) => renderHistoryCard(item))}
            {hasMoreHistory ? <div ref={loadMoreRef} className={styles.loadMoreTrigger} aria-hidden="true" /> : null}
          </div>
        </>
      );
    }

    return (
      <div className={styles.historyGrid} data-empty="true">
        {placeholderResults.map((result, index) => {
          const placeholderItem: NanoHistoryItem = {
            id: `${result.id}-placeholder`,
            taskId: `${result.id}-placeholder`,
            prompt: result.prompt,
            createdAt: Date.now() - index * 1000,
            status: "success",
            progress: 100,
            urls: [`${result.image}&auto=format&fit=crop&q=80`],
            error: null
          };
          return renderHistoryCard(placeholderItem);
        })}
      </div>
    );
  };

  return (
    <div className={styles.page}>
      <TopNav dictionary={dictionary} locale={locale} />
      <div className={styles.main}>
        <aside className={styles.sidebar}>
          <p className={styles.sidebarTitle}>{nanoDict.menuTitle}</p>
          <nav className={styles.menu}>
            <span className={styles.menuSection}>{nanoDict.imageCategoryLabel}</span>
            {modelOptions
              .filter((option) => IMAGE_MODELS.includes(option.key))
              .map((option) => (
                <button
                  key={option.key}
                  type="button"
                  className={`${styles.menuItem} ${activeModel === option.key ? styles.menuItemActive : ""}`}
                  onClick={() => setActiveModel(option.key)}
                >
                  {option.label}
                </button>
              ))}
            <span className={styles.menuSection}>{nanoDict.videoCategoryLabel}</span>
            {modelOptions
              .filter((option) => VIDEO_MODELS.includes(option.key))
              .map((option) => (
                <button
                  key={option.key}
                  type="button"
                  className={`${styles.menuItem} ${activeModel === option.key ? styles.menuItemActive : ""}`}
                  onClick={() => setActiveModel(option.key)}
                >
                  {option.label}
                </button>
              ))}
          </nav>
        </aside>

        <div className={styles.generatorColumn}>
          <form className={styles.modelPanel} onSubmit={handleSubmit}>
              <div className={styles.formGroup}>
                <label htmlFor="prompt">{nanoDict.promptLabel}</label>
                <textarea
                  id="prompt"
                  ref={promptRef}
                  className={styles.textarea}
                  placeholder={nanoDict.promptPlaceholder}
                  value={prompt}
                  onChange={handlePromptChange}
                  maxLength={8000}
                  disabled={!IMAGE_MODELS.includes(activeModel)}
                />
                <span className={styles.promptHint}>{nanoDict.promptHint}</span>
              </div>

              <div className={styles.formLayout}>
                <div className={styles.formPrimary}>
                  <div className={styles.fieldStack}>
                    <div className={styles.formGroup}>
                      <label htmlFor="aspect">{nanoDict.formatLabel}</label>
                      <select
                        id="aspect"
                        className={styles.select}
                        value={aspect}
                        onChange={(event) => setAspect(event.target.value as AspectRatio)}
                      >
                        {nanoDict.formatOptions.map((option) => (
                          <option key={option.value} value={option.value}>
                            {option.label}
                          </option>
                        ))}
                      </select>
                    </div>

                    {isSeedream ? (
                      <>
                        <div className={styles.seedreamGrid}>
                          <div className={styles.formGroup}>
                            <label htmlFor="seedream-size">Size</label>
                            <select
                              id="seedream-size"
                              className={styles.select}
                              value={seedreamSize}
                              onChange={(event) => setSeedreamSize(event.target.value as SeedreamImageSize)}
                            >
                              {seedreamSizeOptions.map((option) => (
                                <option key={option.value} value={option.value}>
                                  {option.label}
                                </option>
                              ))}
                            </select>
                          </div>
                          <div className={styles.formGroup}>
                            <label htmlFor="seedream-resolution">Resolution</label>
                            <select
                              id="seedream-resolution"
                              className={styles.select}
                              value={seedreamResolution}
                              onChange={(event) => setSeedreamResolution(event.target.value as SeedreamResolution)}
                            >
                              {seedreamResolutionOptions.map((option) => (
                                <option key={option.value} value={option.value}>
                                  {option.label}
                                </option>
                              ))}
                            </select>
                          </div>
                        </div>
                        <div className={styles.seedreamGridSecondary}>
                          <div className={styles.formGroup}>
                            <label htmlFor="seedream-max">Images</label>
                            <select
                              id="seedream-max"
                              className={styles.select}
                              value={seedreamMaxImages}
                              onChange={(event) => setSeedreamMaxImages(event.target.value as SeedreamMaxImages)}
                            >
                              {seedreamMaxImagesOptions.map((option) => (
                                <option key={option} value={option}>
                                  {option}
                                </option>
                              ))}
                            </select>
                          </div>
                          <div className={styles.formGroup}>
                            <label htmlFor="seedream-seed" className={styles.labelWithInfo}>
                              Seed
                              <span className={styles.tooltipTrigger} tabIndex={0}>
                                ?
                                <span className={styles.tooltipBubble}>
                                  Enter a numeric seed to reproduce results later. Leave empty to randomise the outcome.
                                </span>
                              </span>
                            </label>
                            <input
                              id="seedream-seed"
                              className={styles.input}
                              value={seedreamSeed}
                              onChange={(event) => setSeedreamSeed(event.target.value)}
                            />
                          </div>
                        </div>
                      </>
                    ) : null}

                  </div>

                  <div className={styles.formGroup}>
                    <label>{nanoDict.primaryLabel}</label>
                    <label className={styles.uploadCell} data-filled={primaryImage ? "true" : "false"}>
                      <input type="file" accept="image/*" onChange={(event) => handlePrimaryFiles(event.target.files)} />
                      {primaryImage ? (
                        <div className={styles.previewWrapper}>
                          <img className={styles.previewImage} src={primaryImage.url} alt={primaryImage.name} />
                          <div className={styles.previewMeta}>
                            <span>{primaryImage.name}</span>
                            <span>{formatBytes(primaryImage.size)}</span>
                          </div>
                        </div>
                      ) : (
                        <div className={styles.uploadHint}>
                          <strong>{nanoDict.primaryEmptyTitle}</strong>
                          <span>{nanoDict.primaryEmptyHint}</span>
                        </div>
                      )}
                    </label>
                  </div>

                  <div className={styles.formGroup}>
                    <label>{nanoDict.referencesLabel}</label>
                    <div className={styles.uploadList}>
                      {referenceImages.slice(0, visibleReferenceSlots).map((ref, index) => (
                        <label key={index} className={styles.uploadCell} data-filled={ref ? "true" : "false"}>
                          <input
                            type="file"
                            accept="image/*"
                            onChange={(event) => handleReferenceFiles(index, event.target.files)}
                          />
                          {ref ? (
                            <div className={styles.previewWrapper}>
                              <img className={styles.previewImage} src={ref.url} alt={ref.name} />
                              <div className={styles.previewMeta}>
                                <span>{ref.name}</span>
                                <span>{formatBytes(ref.size)}</span>
                              </div>
                              <div className={styles.previewActions}>
                                <button type="button" className={styles.removeButton} onClick={(event) => removeReference(index, event)}>
                                  {nanoDict.removeLabel}
                                </button>
                              </div>
                            </div>
                          ) : (
                            <div className={styles.uploadHint}>
                              <strong>{nanoDict.referenceEmptyTitle}</strong>
                              <span>{nanoDict.referenceEmptyHint}</span>
                            </div>
                          )}
                        </label>
                      ))}
                    </div>
                  </div>

                  <button
                    type="submit"
                    className={styles.generateButton}
                    disabled={!isImageModel || isGeneratingImage || hasPendingUploads}
                  >
                    {isGeneratingImage
                      ? nanoDict.historyStatus.generating
                      : hasPendingUploads
                        ? "Uploading..."
                        : nanoDict.generate}
                  </button>

                  {nanoStatusMessage ? <p className={styles.statusMessage}>{nanoStatusMessage}</p> : null}
                  {nanoError ? <p className={styles.errorMessage}>{nanoError}</p> : null}
                </div>
              </div>
            </form>
          </div>

        <div className={styles.promoColumn}>
          {promoPanel ?? <div className={styles.promoPlaceholder} aria-hidden="true" />}
        </div>

        <aside className={styles.historyColumn}>
          <section className={styles.historySection}>{renderHistory()}</section>
        </aside>
      </div>

      {previewItem ? (
        <OverlayModal
          title={nanoDict.historyModals.previewTitle}
          description={previewItem.prompt || nanoDict.promptPlaceholder}
          onClose={() => setPreviewItem(null)}
          footer={
            <div className={styles.modalFooterActions}>
              <button type="button" className={styles.modalSecondaryButton} onClick={() => setPreviewItem(null)}>
                {nanoDict.historyModals.cancel}
              </button>
              {currentPreviewImage ? (
                <button
                  type="button"
                  className={styles.modalPrimaryButton}
                  onClick={() => previewItem && void handleDownload(previewItem)}
                >
                  <Icon name="Download" aria-hidden />
                  {nanoDict.historyActions.download}
                </button>
              ) : null}
            </div>
          }
        >
          <div className={styles.previewModalContent}>
            {currentPreviewImage ? (
              <img
                src={currentPreviewImage}
                alt={previewItem.prompt || nanoDict.promptPlaceholder}
                className={styles.previewImageLarge}
              />
            ) : (
              <div className={styles.previewEmpty}>{nanoDict.historyStatus.noResult}</div>
            )}
            {previewImages.length > 1 ? (
              <div className={styles.previewThumbnails}>
                {previewImages.map((url, index) => (
                  <button
                    key={`${previewItem.id}-${index}`}
                    type="button"
                    className={`${styles.previewThumbButton} ${
                      index === previewIndex ? styles.previewThumbActive : ""
                    }`}
                    onClick={() => setPreviewIndex(index)}
                    aria-label={`${nanoDict.historyModals.previewTitle} ${index + 1}`}
                  >
                    <img src={url} alt="" />
                  </button>
                ))}
              </div>
            ) : null}
          </div>
        </OverlayModal>
      ) : null}

      {shareItem ? (
        <OverlayModal
          title={nanoDict.historyModals.shareTitle}
          description={nanoDict.historyModals.shareSubtitle}
          onClose={() => setShareItem(null)}
          footer={
            <div className={styles.modalFooterActions}>
              <button type="button" className={styles.modalSecondaryButton} onClick={() => setShareItem(null)}>
                {nanoDict.historyModals.cancel}
              </button>
            </div>
          }
        >
          <div className={styles.shareList}>
            <button type="button" className={styles.shareOption} onClick={() => handleShare(shareItem, "email")}>
              <Icon name="Mail" aria-hidden />
              {nanoDict.historyModals.shareEmail}
            </button>
            <button type="button" className={styles.shareOption} onClick={() => handleShare(shareItem, "telegram")}>
              <Icon name="Send" aria-hidden />
              {nanoDict.historyModals.shareTelegram}
            </button>
            <button type="button" className={styles.shareOption} onClick={() => handleShare(shareItem, "whatsapp")}>
              <Icon name="MessageCircle" aria-hidden />
              {nanoDict.historyModals.shareWhatsapp}
            </button>
            <button type="button" className={styles.shareOption} onClick={() => handleShareLink(shareItem)}>
              <Icon name="Link" aria-hidden />
              {shareFeedback ?? nanoDict.historyModals.shareCopyLink}
            </button>
          </div>
          {shareItem.urls?.[0] ? (
            <div className={styles.sharePreview}>
              <img src={shareItem.urls[0]} alt={shareItem.prompt || nanoDict.promptPlaceholder} />
            </div>
          ) : null}
        </OverlayModal>
      ) : null}

      {animateItem ? (
        <OverlayModal
          title={nanoDict.historyModals.animateTitle}
          description={nanoDict.historyModals.animateDescription}
          onClose={() => setAnimateItem(null)}
          footer={
            <div className={styles.modalFooterActions}>
              <button type="button" className={styles.modalSecondaryButton} onClick={() => setAnimateItem(null)}>
                {nanoDict.historyModals.cancel}
              </button>
              <button type="button" className={styles.modalPrimaryButton} onClick={handleAnimateConfirm}>
                {nanoDict.historyModals.animateConfirm}
              </button>
            </div>
          }
        >
          <label className={styles.modalLabel}>
            {nanoDict.historyModals.animateSelectLabel}
            <select
              className={styles.modalSelect}
              value={animateSelectedModel}
              onChange={(event) => setAnimateSelectedModel(event.target.value as ModelKey)}
            >
              {VIDEO_MODELS.map((model) => (
                <option key={model} value={model}>
                  {nanoDict.modelLabels[model] ?? model}
                </option>
              ))}
            </select>
          </label>
        </OverlayModal>
      ) : null}

      {deleteItem ? (
        <OverlayModal
          title={nanoDict.historyModals.deleteTitle}
          description={nanoDict.historyModals.deleteDescription}
          onClose={() => setDeleteItem(null)}
          footer={
            <div className={styles.modalFooterActions}>
              <button type="button" className={styles.modalSecondaryButton} onClick={() => setDeleteItem(null)}>
                {nanoDict.historyModals.cancel}
              </button>
              <button type="button" className={styles.modalDangerButton} onClick={handleDeleteConfirmed}>
                {nanoDict.historyModals.deleteConfirm}
              </button>
            </div>
          }
        >
          <p className={styles.modalBodyText}>{deleteItem.prompt || nanoDict.promptPlaceholder}</p>
        </OverlayModal>
      ) : null}
    </div>
  );
}

type HistoryItemMenuProps = {
  item: NanoHistoryItem;
  isOpen: boolean;
  disabled?: boolean;
  labels: Dictionary["nanoWorkbench"]["historyActions"];
  onToggle: () => void;
  onClose: () => void;
  onPreview: (item: NanoHistoryItem) => void;
  onDownload: (item: NanoHistoryItem) => void;
  onCopyPrompt: (item: NanoHistoryItem) => void;
  onAnimate: (item: NanoHistoryItem) => void;
  onShare: (item: NanoHistoryItem) => void;
  onDelete: (item: NanoHistoryItem) => void;
};

function HistoryItemMenu({
  item,
  isOpen,
  disabled,
  labels,
  onToggle,
  onClose,
  onPreview,
  onDownload,
  onCopyPrompt,
  onAnimate,
  onShare,
  onDelete
}: HistoryItemMenuProps) {
  const containerRef = useRef<HTMLDivElement | null>(null);

  useEffect(() => {
    if (!isOpen) return;

    const handleClick = (event: globalThis.MouseEvent) => {
      if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
        onClose();
      }
    };

    const handleEscape = (event: KeyboardEvent) => {
      if (event.key === "Escape") onClose();
    };

    document.addEventListener("click", handleClick);
    document.addEventListener("keydown", handleEscape);
    return () => {
      document.removeEventListener("click", handleClick);
      document.removeEventListener("keydown", handleEscape);
    };
  }, [isOpen, onClose]);

  return (
    <div className={styles.historyMenu} ref={containerRef}>
      <button
        type="button"
        className={styles.historyMenuTrigger}
        onClick={onToggle}
        aria-haspopup="menu"
        aria-expanded={isOpen}
        aria-label={labels.menu}
        disabled={disabled}
      >
        <Icon name="MoreHorizontal" aria-hidden />
      </button>
      {isOpen ? (
        <div className={styles.historyMenuList} role="menu">
          <button
            type="button"
            className={styles.historyMenuItem}
            onClick={() => {
              onClose();
              onPreview(item);
            }}
          >
            <Icon name="Eye" aria-hidden />
            <span>{labels.preview}</span>
          </button>
          <button
            type="button"
            className={styles.historyMenuItem}
            onClick={() => {
              onClose();
              onDownload(item);
            }}
          >
            <Icon name="Download" aria-hidden />
            <span>{labels.download}</span>
          </button>
          <button
            type="button"
            className={styles.historyMenuItem}
            onClick={() => {
              onClose();
              onCopyPrompt(item);
            }}
          >
            <Icon name="Clipboard" aria-hidden />
            <span>{labels.copyPrompt}</span>
          </button>
          <button
            type="button"
            className={styles.historyMenuItem}
            onClick={() => {
              onClose();
              onAnimate(item);
            }}
          >
            <Icon name="Sparkles" aria-hidden />
            <span>{labels.animate}</span>
          </button>
          <button
            type="button"
            className={styles.historyMenuItem}
            onClick={() => {
              onClose();
              onShare(item);
            }}
          >
            <Icon name="Share2" aria-hidden />
            <span>{labels.share}</span>
          </button>
          <button
            type="button"
            className={`${styles.historyMenuItem} ${styles.historyMenuItemDanger}`}
            onClick={() => {
              onClose();
              onDelete(item);
            }}
          >
            <Icon name="Trash2" aria-hidden />
            <span>{labels.delete}</span>
          </button>
        </div>
      ) : null}
    </div>
  );
}

type OverlayModalProps = {
  title: string;
  description?: string;
  children: ReactNode;
  footer?: ReactNode;
  onClose: () => void;
};

function OverlayModal({ title, description, children, footer, onClose }: OverlayModalProps) {
  const containerRef = useRef<HTMLDivElement | null>(null);

  useEffect(() => {
    const handleKeydown = (event: KeyboardEvent) => {
      if (event.key === "Escape") {
        onClose();
      }
    };
    document.addEventListener("keydown", handleKeydown);
    const previousOverflow = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    return () => {
      document.removeEventListener("keydown", handleKeydown);
      document.body.style.overflow = previousOverflow;
    };
  }, [onClose]);

  return (
    <div
      className={styles.modalOverlay}
      role="dialog"
      aria-modal="true"
      onClick={(event) => {
        if (event.target === event.currentTarget) {
          onClose();
        }
      }}
    >
      <div className={styles.modal} ref={containerRef}>
        <header className={styles.modalHeader}>
          <div>
            <h3>{title}</h3>
            {description ? <p>{description}</p> : null}
          </div>
          <button type="button" className={styles.modalCloseButton} onClick={onClose} aria-label="Close">
            <Icon name="X" aria-hidden />
          </button>
        </header>
        <div className={styles.modalBody}>{children}</div>
        {footer ? <footer className={styles.modalFooter}>{footer}</footer> : null}
      </div>
    </div>
  );
}

