'use client';

/**
 * ImageViewer - Image viewer with zoom, pan, rotate, flip, gallery navigation
 *
 * Two presentations share one engine (react-zoom-pan-pinch):
 * - Embedded (default): checkerboard canvas + editing toolbar (zoom presets,
 *   rotate, flip, expand-to-fullscreen).
 * - Lightbox (`lightbox` / `inDialog`): macOS Quick-Look-grade fullscreen —
 *   clean dark backdrop, idle-fading floating chrome, no rotate/flip slab.
 *
 * Gesture model (macOS-native):
 * - Trackpad pinch arrives as wheel events with `ctrlKey=true`; the library's
 *   `smooth` path maps `|deltaY|` to the zoom magnitude so scale tracks the
 *   gesture 1:1. We raise `smoothStep` (see WHEEL_SMOOTH_STEP) so a real pinch
 *   reaches a meaningful zoom in one motion.
 * - Zoom is anchored to the FOCAL POINT (cursor / pinch midpoint) — the point
 *   under the fingers stays put — handled natively by the library.
 * - Pan by drag when zoomed; double-click/tap toggles fit ↔ zoom toward the
 *   point; keyboard +/-/0 and arrows (pan when zoomed, gallery nav at fit).
 */

import { useEffect, useState, useRef, useCallback, useMemo } from 'react';
import { ImageIcon, AlertCircle, ChevronLeft, ChevronRight, Loader2 } from 'lucide-react';
import {
  TransformWrapper,
  TransformComponent,
  type ReactZoomPanPinchRef,
} from 'react-zoom-pan-pinch';
import { cn, Dialog, DialogContent, DialogTitle, Alert, AlertDescription, Tooltip, TooltipContent, TooltipTrigger } from '@djangocfg/ui-core';
import { useAppT } from '@djangocfg/i18n';
import { useHotkey } from '@djangocfg/ui-core/hooks';

import { ImageToolbar } from './ImageToolbar';
import { ImageInfo } from './ImageInfo';
import { LightboxChrome } from './LightboxChrome';
import { useImageTransform, useImageLoading, useIdleChrome } from '../hooks';
import {
  KEYBOARD_PAN_STEP,
  MIN_ZOOM,
  MAX_ZOOM,
  WHEEL_SMOOTH_STEP,
  PINCH_STEP,
  DOUBLE_CLICK_STEP,
} from '../utils';
import type { ImageViewerProps } from '../types';

// =============================================================================
// COMPONENT
// =============================================================================

export function ImageViewer({
  images,
  initialIndex = 0,
  inDialog = false,
  lightbox = false,
  onRequestClose,
  autoFocus = false,
}: ImageViewerProps) {
  const t = useAppT();

  // `lightbox` implies the in-dialog presentation.
  const isLightbox = lightbox || inDialog;

  const [currentIndex, setCurrentIndex] = useState(() =>
    Math.max(0, Math.min(initialIndex, images.length - 1))
  );

  // Reset index when initialIndex changes (e.g. opening different image)
  useEffect(() => {
    setCurrentIndex(Math.max(0, Math.min(initialIndex, images.length - 1)));
  }, [initialIndex, images.length]);

  const current = images[currentIndex];
  const hasMultiple = images.length > 1;

  const [scale, setScale] = useState(1);
  const [dialogOpen, setDialogOpen] = useState(false);
  const [loadError, setLoadError] = useState(false);
  const [imgDecoded, setImgDecoded] = useState(false);
  const containerRef = useRef<HTMLDivElement>(null);
  const controlsRef = useRef<ReactZoomPanPinchRef | null>(null);

  // Idle-fading chrome (lightbox only). Pointer movement over the canvas keeps
  // the controls visible; resting for CHROME_IDLE_MS fades them out.
  const { active: chromeActive, onActivity, setPinned } = useIdleChrome(isLightbox);

  const labels = useMemo(() => ({
    noImage: t('tools.image.noImage'),
    failedToLoad: t('tools.image.failedToLoad'),
    loading: t('ui.form.loading'),
    prev: t('tools.gallery.previous'),
    next: t('tools.gallery.next'),
    close: t('tools.gallery.close'),
  }), [t]);

  const {
    src,
    lqip,
    isFullyLoaded,
    useProgressiveLoading,
    error,
    hasContent,
  } = useImageLoading({
    content: current?.content ?? '',
    mimeType: current?.file.mimeType,
    src: current?.src,
  });

  // Reset per-source load flags whenever the source changes
  useEffect(() => {
    setLoadError(false);
    setImgDecoded(false);
  }, [src]);

  const { transform, rotate, flipH, flipV, transformStyle } = useImageTransform({
    resetKey: current?.file.path ?? '',
  });

  const handleZoomPreset = useCallback((value: number | 'fit') => {
    const controls = controlsRef.current;
    if (!controls) return;
    if (value === 'fit') {
      controls.resetTransform();
      return;
    }
    // Anchor preset zoom to the canvas center instead of the top-left origin
    const el = controls.instance.wrapperComponent;
    if (el) {
      const cx = el.offsetWidth / 2;
      const cy = el.offsetHeight / 2;
      const x = cx - cx * value;
      const y = cy - cy * value;
      controls.setTransform(x, y, value);
    } else {
      controls.setTransform(0, 0, value);
    }
  }, []);

  const handleExpand = useCallback(() => setDialogOpen(true), []);

  // Reset zoom/pan to fit whenever the gallery image changes — a fresh image
  // should never inherit the previous one's pan offset.
  const goTo = useCallback((next: number) => {
    setCurrentIndex(next);
    controlsRef.current?.resetTransform();
  }, []);

  const prev = useCallback(() =>
    goTo((currentIndex - 1 + images.length) % images.length),
    [goTo, currentIndex, images.length]
  );

  const next = useCallback(() =>
    goTo((currentIndex + 1) % images.length),
    [goTo, currentIndex, images.length]
  );

  // Pan the image by a fixed offset (arrow keys)
  const panBy = useCallback((dx: number, dy: number) => {
    const controls = controlsRef.current;
    if (!controls) return false;
    const { positionX, positionY, scale: s } = controls.instance.transformState;
    // Only pan when the image is zoomed in — otherwise it cannot move
    if (s <= 1) return false;
    controls.setTransform(positionX + dx, positionY + dy, s);
    return true;
  }, []);

  // Declarative autoFocus: focus the container once on mount. Pair with
  // `key={src}` upstream for per-source focus reset.
  useEffect(() => {
    if (!autoFocus) return;
    containerRef.current?.focus({ preventScroll: true });
  }, [autoFocus]);

  // Keyboard: zoom / rotate / pan (only when container focused)
  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      if (!containerRef.current?.contains(document.activeElement) &&
          document.activeElement !== containerRef.current) return;
      const controls = controlsRef.current;
      if (!controls) return;
      switch (e.key) {
        case '+': case '=': e.preventDefault(); controls.zoomIn(); break;
        case '-': case '_': e.preventDefault(); controls.zoomOut(); break;
        case '0': e.preventDefault(); controls.resetTransform(); break;
        case 'r': case 'R':
          // Rotate is an editing affordance — embedded mode only.
          if (!isLightbox && !e.metaKey && !e.ctrlKey) { e.preventDefault(); rotate(); }
          break;
        case 'Escape':
          if (isLightbox && onRequestClose) { e.preventDefault(); onRequestClose(); }
          break;
        // Arrow keys pan only when zoomed in. While at fit scale they
        // fall through to gallery navigation (handled by useHotkey).
        case 'ArrowUp': if (panBy(0, KEYBOARD_PAN_STEP)) e.preventDefault(); break;
        case 'ArrowDown': if (panBy(0, -KEYBOARD_PAN_STEP)) e.preventDefault(); break;
        case 'ArrowLeft': if (panBy(KEYBOARD_PAN_STEP, 0)) e.preventDefault(); break;
        case 'ArrowRight': if (panBy(-KEYBOARD_PAN_STEP, 0)) e.preventDefault(); break;
      }
    };
    window.addEventListener('keydown', handleKeyDown);
    return () => window.removeEventListener('keydown', handleKeyDown);
  }, [rotate, panBy, isLightbox, onRequestClose]);

  // Keyboard: gallery navigation (global, only while not zoomed in so it
  // never competes with arrow-key panning)
  useHotkey('ArrowLeft', prev, {
    enabled: hasMultiple && scale <= 1,
    preventDefault: true,
  });
  useHotkey('ArrowRight', next, {
    enabled: hasMultiple && scale <= 1,
    preventDefault: true,
  });

  if (!current) {
    return (
      <div className="flex-1 flex flex-col items-center justify-center gap-2 bg-muted/30">
        <ImageIcon className="w-12 h-12 text-muted-foreground/50" />
        <p className="text-sm text-muted-foreground">{labels.noImage}</p>
      </div>
    );
  }

  if (error || loadError) {
    return (
      <div className="flex-1 flex flex-col items-center justify-center gap-3 bg-muted/30 p-4">
        <AlertCircle className="w-12 h-12 text-destructive/70" />
        <Alert variant="destructive" className="max-w-md">
          <AlertCircle className="h-4 w-4" />
          <AlertDescription>{error || labels.failedToLoad}</AlertDescription>
        </Alert>
      </div>
    );
  }

  if (!hasContent) {
    return (
      <div className="flex-1 flex flex-col items-center justify-center gap-2 bg-muted/30">
        <ImageIcon className="w-12 h-12 text-muted-foreground/50" />
        <p className="text-sm text-muted-foreground">{labels.noImage}</p>
      </div>
    );
  }

  return (
    <div
      ref={containerRef}
      tabIndex={0}
      role="img"
      aria-label={current.file.name}
      onMouseMove={isLightbox ? onActivity : undefined}
      className={cn(
        'flex-1 h-full relative overflow-hidden outline-none',
        'focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset',
        isLightbox
          // Lightbox: a deep, slightly translucent vibrancy backdrop. The
          // saturate+blur is the macOS Quick Look recipe — the page behind
          // the dialog's scrim bleeds through softly so the image floats.
          ? cn(
              '[background-color:color-mix(in_oklab,#000_82%,transparent)]',
              'backdrop-blur-2xl backdrop-saturate-150',
            )
          // Embedded: the classic transparency checkerboard.
          : cn(
              'bg-[length:16px_16px]',
              '[background-color:color-mix(in_oklab,var(--muted)_20%,transparent)]',
              '[background-image:linear-gradient(45deg,color-mix(in_oklab,var(--muted)_40%,transparent)_25%,transparent_25%),linear-gradient(-45deg,color-mix(in_oklab,var(--muted)_40%,transparent)_25%,transparent_25%),linear-gradient(45deg,transparent_75%,color-mix(in_oklab,var(--muted)_40%,transparent)_75%),linear-gradient(-45deg,transparent_75%,color-mix(in_oklab,var(--muted)_40%,transparent)_75%)]',
              '[background-position:0_0,0_8px,8px_-8px,-8px_0px]',
            ),
      )}
    >
      {/* Dimensions badge — embedded only (lightbox keeps chrome minimal). */}
      {!isLightbox && src && <ImageInfo src={src} />}

      {useProgressiveLoading && !isFullyLoaded && (
        <div className="absolute top-3 left-3 z-10 px-2 py-1 bg-black/60 backdrop-blur-sm border border-white/10 rounded text-[10px] text-white/80 font-mono flex items-center gap-1.5">
          <div className="w-2 h-2 bg-primary rounded-full animate-pulse" />
          {labels.loading}
        </div>
      )}

      {/* Spinner while a non-progressive image is still decoding */}
      {!useProgressiveLoading && !imgDecoded && !loadError && (
        <div className="absolute inset-0 z-10 flex flex-col items-center justify-center gap-2 pointer-events-none">
          <Loader2 className="w-8 h-8 text-muted-foreground/60 animate-spin" />
          <span className="text-xs text-muted-foreground">{labels.loading}</span>
        </div>
      )}

      <TransformWrapper
        initialScale={1}
        minScale={MIN_ZOOM}
        maxScale={MAX_ZOOM}
        centerOnInit
        centerZoomedOut
        // `smooth` (default) makes the wheel/trackpad zoom magnitude scale with
        // `|deltaY|`, i.e. 1:1 with the gesture. The raised `smoothStep` gives
        // a macOS trackpad pinch real reach in a single motion (the library's
        // 0.001 default felt dead). Focal-point anchoring is handled natively.
        smooth
        wheel={{ smoothStep: WHEEL_SMOOTH_STEP }}
        pinch={{ step: PINCH_STEP }}
        doubleClick={{ mode: 'toggle', step: DOUBLE_CLICK_STEP }}
        panning={{ velocityDisabled: false }}
        onTransformed={(ref, state) => {
          controlsRef.current = ref;
          // Avoid a re-render on every pan frame — only when zoom changes
          setScale((prev) => (prev === state.scale ? prev : state.scale));
        }}
        onInit={(ref) => { controlsRef.current = ref; }}
      >
        {/* Embedded editing toolbar (rotate/flip/expand) — not in the lightbox. */}
        {!isLightbox && (
          <ImageToolbar
            scale={scale}
            transform={transform}
            onRotate={rotate}
            onFlipH={flipH}
            onFlipV={flipV}
            onZoomPreset={handleZoomPreset}
            onExpand={!inDialog ? handleExpand : undefined}
          />
        )}

        <TransformComponent
          wrapperClass="!w-full !h-full cursor-grab active:cursor-grabbing"
          contentClass="!w-full !h-full flex items-center justify-center"
        >
          {/*
            Fill the TransformComponent content box so the children's
            `max-w-full / max-h-full` resolve against the actual viewport
            instead of the image's natural box. Without `w-full h-full`
            this wrapper shrink-fits the image, which collapses the
            max-* constraints and renders the image at intrinsic size —
            visible as cropping / half-height in tall containers.
          */}
          <div className="relative w-full h-full flex items-center justify-center">
            {useProgressiveLoading && lqip && !isFullyLoaded && (
              <img
                src={lqip}
                alt=""
                aria-hidden="true"
                className="absolute max-w-full max-h-full object-contain select-none"
                style={{ transform: transformStyle, filter: 'blur(20px)', transition: 'opacity 0.3s ease-out', opacity: isFullyLoaded ? 0 : 1 }}
                draggable={false}
              />
            )}
            {src && (
              <img
                src={src}
                alt={current.file.name}
                className={cn(
                  'max-w-full max-h-full object-contain select-none',
                  // A soft drop shadow lifts the image off the dark lightbox
                  // backdrop (Preview / Photos do the same).
                  isLightbox && 'drop-shadow-[0_8px_40px_rgba(0,0,0,0.55)]',
                )}
                style={{
                  transform: transformStyle,
                  transition: 'transform 0.15s ease-out, opacity 0.3s ease-out',
                  opacity:
                    (useProgressiveLoading && !isFullyLoaded) || !imgDecoded ? 0 : 1,
                }}
                draggable={false}
                onLoad={() => setImgDecoded(true)}
                onError={() => setLoadError(true)}
              />
            )}
          </div>
        </TransformComponent>
      </TransformWrapper>

      {/* Lightbox: restrained macOS-grade chrome that idle-fades. */}
      {isLightbox ? (
        <LightboxChrome
          active={chromeActive}
          index={currentIndex + 1}
          total={images.length}
          scale={scale}
          fileName={current.file.name}
          onPrev={prev}
          onNext={next}
          onClose={() => onRequestClose?.()}
          onPinChange={setPinned}
          labels={{ prev: labels.prev, next: labels.next, close: labels.close }}
        />
      ) : (
        hasMultiple && (
          // Embedded gallery nav (checkerboard canvas).
          <>
            <Tooltip>
              <TooltipTrigger asChild>
                <button
                  type="button"
                  onClick={prev}
                  aria-label={labels.prev}
                  className="absolute left-2 top-1/2 -translate-y-1/2 z-10 bg-black/50 hover:bg-black/70 text-white rounded-full p-1.5 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white"
                >
                  <ChevronLeft className="h-5 w-5" />
                </button>
              </TooltipTrigger>
              <TooltipContent side="right">{labels.prev}</TooltipContent>
            </Tooltip>
            <Tooltip>
              <TooltipTrigger asChild>
                <button
                  type="button"
                  onClick={next}
                  aria-label={labels.next}
                  className="absolute right-2 top-1/2 -translate-y-1/2 z-10 bg-black/50 hover:bg-black/70 text-white rounded-full p-1.5 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white"
                >
                  <ChevronRight className="h-5 w-5" />
                </button>
              </TooltipTrigger>
              <TooltipContent side="left">{labels.next}</TooltipContent>
            </Tooltip>
            <div className="absolute top-3 left-1/2 -translate-x-1/2 z-10 bg-black/60 backdrop-blur-sm border border-white/10 text-white/80 text-xs font-mono px-2 py-0.5 rounded-full pointer-events-none">
              {currentIndex + 1} / {images.length}
            </div>
          </>
        )
      )}

      {/* Embedded expand-to-fullscreen dialog (uses the lightbox presentation). */}
      {!inDialog && !lightbox && (
        <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
          <DialogContent
            fullscreen
            closeButton={false}
            className="border-0 bg-transparent p-0 shadow-none"
          >
            <DialogTitle className="sr-only">{current.file.name}</DialogTitle>
            {dialogOpen && (
              <ImageViewer
                images={images}
                initialIndex={currentIndex}
                lightbox
                autoFocus
                onRequestClose={() => setDialogOpen(false)}
              />
            )}
          </DialogContent>
        </Dialog>
      )}
    </div>
  );
}
