import { useMemo } from "react";
import {
  Box,
  Button,
  Center,
  Group,
  Modal,
  Stack,
  Text,
  ThemeIcon,
} from "@mantine/core";
import {
  Download,
  ExternalLink,
  FileArchive,
  FileQuestion,
} from "lucide-react";

/** The minimal file shape the viewer needs. */
export interface ViewableFile {
  /** Display name (used for the title + extension fallback). */
  name: string;
  /** Direct URL to the file content. A signed URL is preferred when present. */
  url: string;
  /** MIME type when known (e.g. "application/pdf", "image/png"). */
  mimeType?: string | null;
}

export interface FileViewerModalProps {
  /** Whether the modal is open. */
  open: boolean;
  /** The file to display, or null when nothing is selected. */
  file: ViewableFile | null;
  /** Close handler. */
  onClose: () => void;
}

type ViewerKind =
  | "image"
  | "video"
  | "audio"
  | "pdf"
  | "office"
  | "text"
  | "unsupported";

const EXT_KIND: Record<string, ViewerKind> = {
  // images
  png: "image",
  jpg: "image",
  jpeg: "image",
  gif: "image",
  webp: "image",
  bmp: "image",
  svg: "image",
  // video
  mp4: "video",
  webm: "video",
  ogv: "video",
  mov: "video",
  m4v: "video",
  // audio
  mp3: "audio",
  wav: "audio",
  ogg: "audio",
  m4a: "audio",
  // documents
  pdf: "pdf",
  // office — rendered via the Microsoft Office online viewer
  doc: "office",
  docx: "office",
  xls: "office",
  xlsx: "office",
  ppt: "office",
  pptx: "office",
  // text
  txt: "text",
  csv: "text",
  json: "text",
  log: "text",
  md: "text",
};

/** Archives / binaries we deliberately do NOT try to render inline. */
const UNVIEWABLE_EXT = new Set([
  "zip",
  "rar",
  "7z",
  "tar",
  "gz",
  "bz2",
  "exe",
  "dmg",
  "iso",
  "bin",
]);

function extOf(name: string): string {
  const dot = name.lastIndexOf(".");
  return dot >= 0 ? name.slice(dot + 1).toLowerCase() : "";
}

/** Decide how to render a file from its MIME type, falling back to extension. */
export function resolveViewerKind(file: ViewableFile): ViewerKind {
  const mime = (file.mimeType ?? "").toLowerCase();
  const ext = extOf(file.name);

  if (UNVIEWABLE_EXT.has(ext)) return "unsupported";

  if (mime.startsWith("image/")) return "image";
  if (mime.startsWith("video/")) return "video";
  if (mime.startsWith("audio/")) return "audio";
  if (mime === "application/pdf") return "pdf";
  if (
    mime.includes("word") ||
    mime.includes("excel") ||
    mime.includes("spreadsheet") ||
    mime.includes("powerpoint") ||
    mime.includes("presentation") ||
    mime.includes("officedocument")
  ) {
    return "office";
  }
  if (mime.startsWith("text/") || mime === "application/json") return "text";

  // Fall back to the file extension when the MIME type is missing/generic.
  return EXT_KIND[ext] ?? "unsupported";
}

/** True when a file can be previewed inline (not an archive/binary). */
export function isViewable(file: ViewableFile): boolean {
  return resolveViewerKind(file) !== "unsupported";
}

/**
 * A wide modal that renders the content of common document types inline —
 * images, video, audio, PDFs, Office documents (via the Microsoft online
 * viewer) and plain text. Archives and other binaries fall back to a download
 * prompt. Use the {@link isViewable} / {@link resolveViewerKind} helpers to gate
 * a "view" affordance in the caller.
 */
export function FileViewerModal({ open, file, onClose }: FileViewerModalProps) {
  const kind = useMemo(
    () => (file ? resolveViewerKind(file) : "unsupported"),
    [file],
  );

  return (
    <Modal
      opened={open}
      onClose={onClose}
      title={
        <Text fw={700} fz={15} truncate>
          {file?.name ?? "Document"}
        </Text>
      }
      size="90%"
      radius="md"
      centered
      overlayProps={{ blur: 2, backgroundOpacity: 0.55 }}
      styles={{
        content: {
          height: "90vh",
          display: "flex",
          flexDirection: "column",
        },
        body: { flex: 1, minHeight: 0, display: "flex", padding: 0 },
        header: { paddingInline: 16 },
      }}
    >
      {file && (
        <Stack gap={0} style={{ flex: 1, minHeight: 0 }}>
          <Group
            justify="flex-end"
            gap="xs"
            px="md"
            py={8}
            style={{ borderBottom: "1px solid var(--mantine-color-gray-2)" }}
          >
            <Button
              component="a"
              href={file.url}
              target="_blank"
              rel="noopener noreferrer"
              size="compact-sm"
              variant="default"
              leftSection={<ExternalLink size={14} />}
            >
              Open in new tab
            </Button>
            <Button
              component="a"
              href={file.url}
              download={file.name}
              size="compact-sm"
              variant="light"
              color="edr-green"
              leftSection={<Download size={14} />}
            >
              Download
            </Button>
          </Group>
          <Box style={{ flex: 1, minHeight: 0, overflow: "auto" }}>
            <FileContent file={file} kind={kind} />
          </Box>
        </Stack>
      )}
    </Modal>
  );
}

function FileContent({
  file,
  kind,
}: {
  file: ViewableFile;
  kind: ViewerKind;
}) {
  switch (kind) {
    case "image":
      return (
        <Center p="md" style={{ minHeight: "100%" }}>
          <img
            src={file.url}
            alt={file.name}
            style={{ maxWidth: "100%", maxHeight: "100%", objectFit: "contain" }}
          />
        </Center>
      );
    case "video":
      return (
        <Center p="md" style={{ minHeight: "100%", background: "#000" }}>
          <video
            src={file.url}
            controls
            style={{ maxWidth: "100%", maxHeight: "100%" }}
          />
        </Center>
      );
    case "audio":
      return (
        <Center p="xl" style={{ minHeight: "100%" }}>
          <audio src={file.url} controls style={{ width: "100%", maxWidth: 480 }} />
        </Center>
      );
    case "pdf":
      return (
        <iframe
          src={file.url}
          title={file.name}
          style={{ width: "100%", height: "100%", border: "none" }}
        />
      );
    case "office":
      return (
        <iframe
          // The Microsoft Office online viewer requires a publicly reachable URL.
          src={`https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(
            file.url,
          )}`}
          title={file.name}
          style={{ width: "100%", height: "100%", border: "none" }}
        />
      );
    case "text":
      return (
        <iframe
          src={file.url}
          title={file.name}
          style={{ width: "100%", height: "100%", border: "none" }}
        />
      );
    default:
      return <UnsupportedNotice file={file} />;
  }
}

function UnsupportedNotice({ file }: { file: ViewableFile }) {
  const isArchive = UNVIEWABLE_EXT.has(extOf(file.name));
  return (
    <Center p="xl" style={{ minHeight: "100%" }}>
      <Stack align="center" gap="sm" maw={360} ta="center">
        <ThemeIcon variant="light" color="gray" radius="xl" size={56}>
          {isArchive ? <FileArchive size={26} /> : <FileQuestion size={26} />}
        </ThemeIcon>
        <Text fw={600}>This file type can’t be previewed</Text>
        <Text fz="sm" c="dimmed">
          {isArchive
            ? "Archives need to be downloaded and extracted on your computer."
            : "Download the file to open it in a compatible application."}
        </Text>
        <Button
          component="a"
          href={file.url}
          download={file.name}
          mt="xs"
          color="edr-green"
          leftSection={<Download size={16} />}
        >
          Download file
        </Button>
      </Stack>
    </Center>
  );
}

export default FileViewerModal;
