'use client';

/**
 * ImageViewer - Image viewer with zoom, pan, rotate, flip, gallery navigation
 *
 * Features:
 * - Zoom with mouse wheel and presets
 * - Pan with drag
 * - Rotate 90°
 * - Flip horizontal/vertical
 * - Fullscreen dialog
 * - Keyboard shortcuts (+/-, 0, r, ←/→ for gallery)
 * - Gallery mode: pass images[] with multiple items
 */

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 } 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 { useImageTransform, useImageLoading } from '../hooks';
import { KEYBOARD_PAN_STEP } from '../utils';
import type { ImageViewerProps } from '../types';

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

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

  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);

  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'),
  }), [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), []);

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

  const next = useCallback(() =>
    setCurrentIndex((i) => (i + 1) % images.length),
    [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;
  }, []);

  // 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':
          if (!e.metaKey && !e.ctrlKey) { e.preventDefault(); rotate(); }
          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]);

  // 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}
      className={cn(
        'flex-1 h-full relative overflow-hidden outline-none',
        'focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset',
        '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]'
      )}
    >
      {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-blue-400 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={0.1}
        maxScale={8}
        centerOnInit
        centerZoomedOut
        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; }}
        wheel={{ step: 0.1 }}
        doubleClick={{ mode: 'toggle', step: 2 }}
        panning={{ velocityDisabled: false }}
      >
        <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="max-w-full max-h-full object-contain select-none"
                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>

      {/* Gallery navigation */}
      {hasMultiple && (
        <>
          <button
            type="button"
            onClick={prev}
            aria-label={labels.prev}
            title={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>
          <button
            type="button"
            onClick={next}
            aria-label={labels.next}
            title={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>
          <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>
        </>
      )}

      {/* Fullscreen dialog */}
      {!inDialog && (
        <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
          <DialogContent className="max-w-[95vw] max-h-[95vh] w-[95vw] h-[95vh] p-0 overflow-hidden [&>button]:hidden flex flex-col">
            <DialogTitle className="sr-only">{current.file.name}</DialogTitle>
            <div className="flex items-center justify-between px-4 py-2 border-b shrink-0">
              <span className="text-sm font-medium truncate">{current.file.name}</span>
            </div>
            <div className="flex-1 min-h-0">
              <ImageViewer images={images} initialIndex={currentIndex} inDialog />
            </div>
          </DialogContent>
        </Dialog>
      )}
    </div>
  );
}
