import { XIcon } from '@phosphor-icons/react'
import React, { useEffect, useRef } from 'react'

import { VIEWER_ACTION_CLASS } from './viewerChromeClasses'

export interface ViewerShellProps {
  open: boolean
  onClose: () => void
  /**
   * Accessible label for the dialog — usually the media's filename. We
   * intentionally don't render a visible title bar (the chrome is a
   * single close button + optional actions so the media stays the
   * focus), so this is the only way assistive tech gets a name for
   * the modal.
   */
  ariaLabel?: string
  /**
   * Optional counter rendered in the top-left chrome (e.g. `2 / 6`).
   * Carousel-aware viewers set this when their items array has more
   * than one entry; omit it for single-item viewers.
   */
  counter?: React.ReactNode
  /**
   * Extra buttons rendered alongside the close button in the top-right
   * chrome (e.g. a download action), laid out as one consistent button group.
   */
  actions?: React.ReactNode
  children: React.ReactNode
  'data-testid'?: string
}

/**
 * Full-viewport lightbox shell shared by `ImageViewer`, `VideoViewer`,
 * and `PdfViewer`. Built on a native `<dialog>` so the package ships
 * without a Tailwind dependency for the load-bearing chrome:
 *
 *   - Top-layer + body-scroll lock come for free from `showModal()`.
 *   - `Escape` to close is wired by the platform.
 *   - Focus management (move-in on open, restore on close) is handled
 *     by the browser; we explicitly focus the close button as a
 *     fallback for environments where `showModal` is unavailable
 *     (jsdom under tests).
 *
 * The `<dialog>` root, backdrop, and open/close transitions live in
 * `styles.css` under `.mes-media-viewer` (they use `::backdrop` /
 * `@starting-style`, which Tailwind can't express). The chrome inside is
 * plain Tailwind on the JSX, like the rest of the package.
 */
const ViewerShell: React.FC<ViewerShellProps> = ({
  open,
  onClose,
  ariaLabel,
  counter,
  actions,
  children,
  'data-testid': dataTestId,
}) => {
  const dialogRef = useRef<HTMLDialogElement | null>(null)
  const closeButtonRef = useRef<HTMLButtonElement | null>(null)

  // Drive `<dialog>` open/close imperatively from the React `open` prop.
  // We guard `showModal` / `close` because jsdom 23 ships the element
  // without those methods — falling back to the `[open]` attribute
  // keeps tests (and any other non-DOM environment) functional even if
  // the true top-layer modal behavior isn't available there.
  useEffect(() => {
    const dialog = dialogRef.current
    if (!dialog) return undefined

    if (open) {
      if (!dialog.open) {
        if (typeof dialog.showModal === 'function') {
          try {
            dialog.showModal()
          } catch {
            dialog.setAttribute('open', '')
          }
        } else {
          dialog.setAttribute('open', '')
        }
      }

      const previouslyFocused =
        typeof document !== 'undefined'
          ? (document.activeElement as HTMLElement | null)
          : null

      // Explicitly focus the close button. In a real browser
      // `showModal()` would already focus the first sequentially
      // focusable child (which is the close button), but doing it
      // ourselves guarantees the same behavior in jsdom and in any
      // browser where focus heuristics differ.
      closeButtonRef.current?.focus()

      return () => {
        if (dialog.open) {
          if (typeof dialog.close === 'function') {
            try {
              dialog.close()
            } catch {
              dialog.removeAttribute('open')
            }
          } else {
            dialog.removeAttribute('open')
          }
        }
        if (previouslyFocused && document.body.contains(previouslyFocused)) {
          previouslyFocused.focus()
        }
      }
    }

    if (dialog.open) {
      if (typeof dialog.close === 'function') {
        try {
          dialog.close()
        } catch {
          dialog.removeAttribute('open')
        }
      } else {
        dialog.removeAttribute('open')
      }
    }
    return undefined
  }, [open])

  // The platform fires `close` when the user dismisses via Escape or
  // any other native path. Mirror that into React state so the parent's
  // `open` flag stays in sync — otherwise the dialog would close
  // visually but our effect would re-open it on the next render.
  const handleDialogClose = () => {
    onClose()
  }

  // Backdrop click closes the viewer. The click lands on the `<dialog>`
  // itself (not its children) because the body sits inside an inner
  // wrapper — the easiest reliable check.
  const handleDialogClick = (e: React.MouseEvent<HTMLDialogElement>) => {
    if (e.target === dialogRef.current) onClose()
  }

  return (
    // The native `<dialog>` element ships its own keyboard dismissal
    // (Escape, handled by the platform and surfaced through `onClose`),
    // so the `onClick` on the backdrop doesn't need a paired keyboard
    // listener — that's the whole point of using `<dialog>`. The
    // `jsx-a11y` rules below don't model `<dialog>` as interactive, so
    // we silence them locally with the rationale captured here.
    // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions
    <dialog
      ref={dialogRef}
      className="mes-media-viewer"
      aria-label={ariaLabel ?? 'Attachment viewer'}
      data-testid={dataTestId}
      onClose={handleDialogClose}
      onClick={handleDialogClick}
    >
      <div className="pointer-events-none absolute inset-x-3 top-3 z-[1] flex items-center justify-between gap-3">
        {counter ? (
          <span className="pointer-events-auto inline-flex items-center rounded-full bg-white/[0.12] px-3 py-1.5 text-[13px] font-medium leading-none tabular-nums text-white">
            {counter}
          </span>
        ) : null}
        <div className="pointer-events-auto ml-auto inline-flex items-center gap-2">
          {actions}
          <button
            ref={closeButtonRef}
            type="button"
            onClick={onClose}
            aria-label="Close viewer"
            className={VIEWER_ACTION_CLASS}
          >
            <XIcon size={20} weight="bold" aria-hidden />
          </button>
        </div>
      </div>
      <div className="box-border flex h-full w-full items-center justify-center px-6 py-16">
        {children}
      </div>
    </dialog>
  )
}

export default ViewerShell
