/**
 * `@ump/shared/video-viewer` — a 2×2 viewer for 2D PNG image SEQUENCES.
 *
 * Q1 Ảnh gốc · Q2 Bản đồ độ sâu · Q3 Phủ phân vùng (original + segmentation overlay
 * composited via mixBlendMode:'screen') · Q4 Tái tạo 3D (placeholder). A top toolbar
 * adds brightness/contrast (CSS filters). A single frame index — driven by the controls-bar
 * "video slider" — indexes all channels at once. Double-click a quad to expand it;
 * the other three stack on the right.
 *
 * Sibling of `VideoQuadViewer`: same grid skeleton (one stable CSS-grid so panes
 * never remount on expand) and the same StrictMode-safe object-URL discipline, but
 * `<img>` frames + a frame index replace `<video>` + currentTime.
 *
 * Deliberately VTK-free and dependency-free beyond react: it never imports VTK,
 * three.js, or the @ump/shared barrel, so this subpath stays light.
 */
import { useEffect, useState } from 'react';
import type { CSSProperties } from 'react';
import type {
  ImageSequenceViewerProps,
  ImageSequenceSource,
  QuadIndex,
  ExpandedView,
} from './types';
import { expandedOrder } from './playbackModel';
import {
  IMAGE_QUAD_LABELS,
  clampFrame,
  frameLabel,
  naturalSortFiles,
} from './imageSequenceModel';
import { VideoQuad } from './VideoQuad';
// AnnotationOverlay + its types are VTK-free (React + types only), so reusing them
// here keeps the drawing tools DRY without pulling VTK into this light subpath.
import { AnnotationOverlay } from '../viewer/AnnotationOverlay';
import type { Annotation, AnnotationTool } from '../viewer/types';

// Inline class join — keep this package barrel-free (no `cn` from @ump/shared).
function join(...parts: Array<string | false | null | undefined>): string {
  return parts.filter(Boolean).join(' ');
}

const FPS_OPTIONS = [5, 10, 15, 30] as const;
const DEFAULT_FPS = 10;
const PLACEHOLDER_TEXT = 'text-xs text-white/50 px-2 text-center';
const ANNO_COLOR = '#22d3ee';

// Annotation tools — mirrors the 3D viewer's palette. 'none' = select (no drawing).
const TOOLS: { tool: AnnotationTool; label: string }[] = [
  { tool: 'none', label: 'Chọn' },
  { tool: 'bbox', label: 'Khung' },
  { tool: 'points', label: 'Điểm' },
  { tool: 'pen', label: 'Bút' },
  { tool: 'brush', label: 'Cọ' },
  { tool: 'polygon', label: 'Đa giác' },
];
const TOOL_LABEL: Record<AnnotationTool, string> = {
  none: 'Chọn',
  bbox: 'Khung',
  points: 'Điểm',
  pen: 'Bút',
  brush: 'Cọ',
  polygon: 'Đa giác',
};

/**
 * Resolve a frame-URL array from an {urls?, files?} source. A pre-resolved `urls`
 * array is returned as-is. For `files`, sort naturally then create one object URL
 * per frame.
 *
 * The create + revoke MUST live in the same effect: under React 18 StrictMode the
 * mount→cleanup→remount cycle would otherwise revoke the URLs right after mount
 * (leaving dead `<img src>`s). Here cleanup revokes every created URL and the
 * re-run recreates the whole array.
 */
function useSequenceUrls(source: ImageSequenceSource | undefined): string[] {
  const urls = source?.urls;
  const files = source?.files;
  const [objectUrls, setObjectUrls] = useState<string[]>([]);

  useEffect(() => {
    if (!files || files.length === 0) {
      setObjectUrls([]);
      return;
    }
    const created = naturalSortFiles(files).map((f) => URL.createObjectURL(f));
    setObjectUrls(created);
    return () => {
      for (const u of created) URL.revokeObjectURL(u);
    };
  }, [files]);

  if (urls && urls.length > 0) return urls;
  return objectUrls;
}

export function ImageSequenceViewer({
  original,
  depth,
  segmentation,
  fps: fpsProp,
  className,
  initialExpanded = null,
}: ImageSequenceViewerProps) {
  const originalUrls = useSequenceUrls(original);
  const depthUrls = useSequenceUrls(depth);
  const segUrls = useSequenceUrls(segmentation);

  const frameCount = Math.max(
    originalUrls.length,
    depthUrls.length,
    segUrls.length,
  );

  const [frame, setFrame] = useState(0);
  const [playing, setPlaying] = useState(false);
  const [fps, setFps] = useState(fpsProp ?? DEFAULT_FPS);
  const [expandedView, setExpandedView] = useState<ExpandedView>(initialExpanded ?? null);
  // CT window width/level have no meaning for flat 2D rasters; brightness + contrast
  // (CSS filters on the <img> panes) are the 2D-image equivalents.
  const [brightness, setBrightness] = useState(1);
  const [contrast, setContrast] = useState(1);
  const imgFilter = `brightness(${brightness}) contrast(${contrast})`;

  // Segmentation overlay controls (the explicit ask: adjust mask opacity) + the
  // annotation tools, mirroring the 3D viewer's right sidebar.
  const [maskVisible, setMaskVisible] = useState(true);
  const [maskOpacity, setMaskOpacity] = useState(segmentation?.opacity ?? 0.6);
  const [activeTool, setActiveTool] = useState<AnnotationTool>('none');
  const [annotations, setAnnotations] = useState<Annotation[]>([]);

  const toggleExpand = (i: QuadIndex) =>
    setExpandedView((prev) => (prev === i ? null : i));

  // Keep the current frame in range when the sequence length shrinks.
  useEffect(() => {
    setFrame((f) => clampFrame(f, frameCount));
  }, [frameCount]);

  // Honour a controlled `fps` prop (kept as state so the fps <select> can also drive it).
  useEffect(() => {
    if (fpsProp != null) setFps(fpsProp);
  }, [fpsProp]);

  // Surface a frame decode/load failure (broken PNG) on the original pane instead
  // of a silent black quad. Reset whenever the frame or sequence changes.
  const [imageError, setImageError] = useState(false);
  useEffect(() => setImageError(false), [frame, originalUrls]);

  // Play loop: advance the frame on an interval while playing. Drive the advance
  // through the functional updater so `frame` is NOT an effect dependency — were it
  // one, the interval would be torn down and recreated every tick (resetting the
  // cadence). It re-runs only on play/pause, fps, or frameCount changes.
  useEffect(() => {
    if (!playing || frameCount <= 0) return;
    const id = setInterval(() => {
      setFrame((f) => (f + 1) % frameCount);
    }, 1000 / fps);
    return () => clearInterval(id);
  }, [playing, fps, frameCount]);

  const hasFrames = frameCount > 0;
  const togglePlay = () => {
    if (!hasFrames) return;
    setPlaying((p) => !p);
  };

  const info = frameLabel(frame, frameCount);
  const frameAnnotations = annotations.filter((a) => a.sliceIndex === frame);

  // ---- Per-quad media render ------------------------------------------------
  const renderQuadMedia = (q: QuadIndex) => {
    switch (q) {
      case 0: {
        if (originalUrls.length === 0) {
          return (
            <div className="absolute inset-0 flex items-center justify-center">
              <span className={PLACEHOLDER_TEXT}>Chưa có ảnh — tải lên để xem</span>
            </div>
          );
        }
        const idx = clampFrame(frame, originalUrls.length);
        return (
          <>
            <img
              src={originalUrls[idx]}
              alt="Ảnh gốc"
              className="w-full h-full object-contain bg-black"
              style={{ filter: imgFilter }}
              onError={() => setImageError(true)}
            />
            {imageError && (
              <div className="absolute inset-0 flex items-center justify-center bg-black/70 p-3">
                <span className={PLACEHOLDER_TEXT}>Không tải được ảnh.</span>
              </div>
            )}
            <AnnotationOverlay
              view="axial"
              sliceIndex={frame}
              tool={activeTool}
              annotations={annotations}
              onCommit={(a) => setAnnotations((prev) => [...prev, a])}
              color={ANNO_COLOR}
              onRequestExpand={() => toggleExpand(0)}
            />
          </>
        );
      }
      case 1: {
        if (depthUrls.length === 0) {
          return (
            <div className="absolute inset-0 flex items-center justify-center">
              <span className={PLACEHOLDER_TEXT}>Không có bản đồ độ sâu</span>
            </div>
          );
        }
        const idx = clampFrame(frame, depthUrls.length);
        return (
          <img
            src={depthUrls[idx]}
            alt="Bản đồ độ sâu"
            className="w-full h-full object-contain bg-black"
            style={{ filter: `grayscale(1) ${imgFilter}` }}
          />
        );
      }
      case 2: {
        // Segmentation shown as an overlay over the original (the raw b/w mask pane
        // was dropped — the mask appears only as this useful overlay).
        const baseUrls = originalUrls.length > 0 ? originalUrls : segUrls;
        if (baseUrls.length === 0) {
          return (
            <div className="absolute inset-0 flex items-center justify-center">
              <span className={PLACEHOLDER_TEXT}>Không có dữ liệu phủ phân vùng</span>
            </div>
          );
        }
        const baseIdx = clampFrame(frame, baseUrls.length);
        const hasSeg = segUrls.length > 0;
        return (
          <div className="relative w-full h-full">
            <img
              src={baseUrls[baseIdx]}
              alt="Ảnh gốc"
              className="w-full h-full object-contain bg-black"
              style={{ filter: imgFilter }}
            />
            {hasSeg ? (
              <img
                src={segUrls[clampFrame(frame, segUrls.length)]}
                alt="Phủ phân vùng"
                className="absolute inset-0 w-full h-full object-contain pointer-events-none"
                style={{ mixBlendMode: 'screen', opacity: maskVisible ? maskOpacity : 0 }}
              />
            ) : (
              <div className="absolute bottom-2 right-2">
                <span className={PLACEHOLDER_TEXT}>Không có mặt nạ phân vùng</span>
              </div>
            )}
          </div>
        );
      }
      case 3:
        // 3D reconstruction placeholder — populated later (e.g. a point cloud from depth).
        return (
          <div className="absolute inset-0 flex items-center justify-center bg-white/[0.03]">
            <span className={PLACEHOLDER_TEXT}>Tái tạo 3D — chưa có dữ liệu</span>
          </div>
        );
      default:
        return null;
    }
  };

  const renderQuad = (q: QuadIndex, cellClassName?: string) => (
    <VideoQuad
      key={q}
      index={q}
      label={IMAGE_QUAD_LABELS[q]}
      info={hasFrames ? info : undefined}
      expanded={expandedView === q}
      onToggleExpand={toggleExpand}
      className={cellClassName}
    >
      {renderQuadMedia(q)}
    </VideoQuad>
  );

  // ---- Single CSS-grid tree -------------------------------------------------
  // Layout switches via grid-template-areas so the four cells are NEVER remounted
  // on expand (keeps each `<img>` and its load state stable). One grid, four stable
  // keyed cells in fixed order; expand only re-targets their gridArea.
  const areaFor = (q: QuadIndex): string => {
    if (expandedView === null) return `q${q}`;
    const order = expandedOrder(expandedView); // [main, t0, t1, t2]
    if (q === order[0]) return 'main';
    return `t${order.indexOf(q) - 1}`;
  };
  const gridStyle: CSSProperties =
    expandedView === null
      ? {
          gridTemplateColumns: '1fr 1fr',
          gridTemplateRows: '1fr 1fr',
          gridTemplateAreas: '"q0 q1" "q2 q3"',
        }
      : {
          gridTemplateColumns: '3fr 1fr',
          gridTemplateRows: '1fr 1fr 1fr',
          gridTemplateAreas: '"main t0" "main t1" "main t2"',
        };
  const grid = (
    <div className="grid gap-1 w-full h-full" style={gridStyle}>
      {([0, 1, 2, 3] as QuadIndex[]).map((q) => (
        <div key={q} className="relative min-h-0 min-w-0" style={{ gridArea: areaFor(q) }}>
          {renderQuad(q, 'w-full h-full')}
        </div>
      ))}
    </div>
  );

  return (
    <div className={join('flex flex-col w-full h-full bg-black', className)}>
      {/* Top toolbar — brightness/contrast (the 2D equivalent of CT window width/level). */}
      <div className="shrink-0 flex flex-wrap items-center gap-x-6 gap-y-2 px-3 py-2 border-b border-white/10 text-white/80">
        <label className="flex items-center gap-2 text-xs">
          <span className="whitespace-nowrap">Độ sáng</span>
          <input
            type="range"
            min={0.2}
            max={2}
            step={0.05}
            value={brightness}
            onChange={(e) => setBrightness(Number(e.target.value))}
            className="w-28"
            aria-label="Độ sáng"
          />
          <span className="w-9 tabular-nums">{brightness.toFixed(2)}</span>
        </label>
        <label className="flex items-center gap-2 text-xs">
          <span className="whitespace-nowrap">Tương phản</span>
          <input
            type="range"
            min={0.2}
            max={2}
            step={0.05}
            value={contrast}
            onChange={(e) => setContrast(Number(e.target.value))}
            className="w-28"
            aria-label="Tương phản"
          />
          <span className="w-9 tabular-nums">{contrast.toFixed(2)}</span>
        </label>
        <button
          type="button"
          onClick={() => {
            setBrightness(1);
            setContrast(1);
          }}
          className="text-xs text-white/50 underline-offset-2 hover:underline"
        >
          Đặt lại
        </button>
      </div>

      <div className="flex flex-1 min-h-0">
        <div className="flex-1 min-w-0 bg-black">{grid}</div>

        {/* Right sidebar — mirrors the 3D viewer: overlay control + annotation tools + list. */}
        <aside className="w-72 shrink-0 overflow-y-auto border-l border-border bg-background text-foreground">
          {/* Lớp phủ — segmentation overlay show/hide + opacity (the explicit ask). */}
          <div className="border-b border-border p-4">
            <h3 className="font-serif text-base font-semibold">Lớp phủ</h3>
            <p className="text-xs text-muted-foreground">Điều chỉnh mặt nạ phân vùng.</p>
            <div className="mt-3 rounded-md border border-border p-3">
              <div className="flex items-center justify-between gap-2">
                <span className="flex items-center gap-2 text-sm">
                  <span
                    className="h-3 w-3 rounded-full"
                    style={{ background: segmentation?.color ?? '#34d399' }}
                  />
                  Mặt nạ phân vùng
                </span>
                <button
                  type="button"
                  onClick={() => setMaskVisible((v) => !v)}
                  className="rounded px-2 py-0.5 text-xs text-muted-foreground hover:bg-muted"
                  title={maskVisible ? 'Ẩn lớp phủ' : 'Hiện lớp phủ'}
                >
                  {maskVisible ? 'Ẩn' : 'Hiện'}
                </button>
              </div>
              <div className="mt-3 flex items-center gap-2">
                <span className="text-xs text-muted-foreground">Độ mờ</span>
                <input
                  type="range"
                  min={0}
                  max={1}
                  step={0.05}
                  value={maskOpacity}
                  onChange={(e) => setMaskOpacity(Number(e.target.value))}
                  disabled={!maskVisible}
                  className="flex-1 disabled:opacity-40"
                  aria-label="Độ mờ mặt nạ"
                />
                <span className="w-9 text-right text-xs tabular-nums">
                  {Math.round(maskOpacity * 100)}%
                </span>
              </div>
            </div>
          </div>

          {/* Công cụ chú thích — drawing tools (draw on the Ảnh gốc pane). */}
          <div className="border-b border-border p-4">
            <h3 className="font-serif text-base font-semibold">Công cụ chú thích</h3>
            <p className="text-xs text-muted-foreground">Vẽ trên ảnh gốc.</p>
            <div className="mt-3 grid grid-cols-3 gap-2">
              {TOOLS.map((t) => (
                <button
                  key={t.tool}
                  type="button"
                  onClick={() => setActiveTool(t.tool)}
                  className={join(
                    'rounded-lg border px-2 py-3 text-xs font-medium transition',
                    activeTool === t.tool
                      ? 'border-primary bg-primary/10 text-primary'
                      : 'border-border text-foreground hover:bg-muted',
                  )}
                >
                  {t.label}
                </button>
              ))}
            </div>
          </div>

          {/* Chú thích — list for the current frame. */}
          <div className="p-4">
            <h3 className="font-serif text-base font-semibold">
              Chú thích ({frameAnnotations.length})
            </h3>
            {frameAnnotations.length === 0 ? (
              <p className="mt-4 text-center text-xs text-muted-foreground">
                Chọn một công cụ rồi vẽ trên ảnh gốc.
                <br />
                (Đa giác: bấm từng đỉnh, nhấn đúp để đóng.)
              </p>
            ) : (
              <ul className="mt-3 space-y-1">
                {frameAnnotations.map((a) => (
                  <li
                    key={a.id}
                    className="flex items-center justify-between rounded border border-border px-2 py-1 text-xs"
                  >
                    <span className="flex items-center gap-2">
                      <span className="h-2.5 w-2.5 rounded-full" style={{ background: a.color }} />
                      {TOOL_LABEL[a.tool]}
                    </span>
                    <button
                      type="button"
                      onClick={() => setAnnotations((prev) => prev.filter((x) => x.id !== a.id))}
                      className="text-destructive hover:underline"
                    >
                      Xoá
                    </button>
                  </li>
                ))}
              </ul>
            )}
          </div>
        </aside>
      </div>

      {/* Controls bar — the "video slider". */}
      <div className="shrink-0 flex items-center gap-3 px-3 py-2 border-t border-white/10 text-white/80">
        <button
          type="button"
          onClick={togglePlay}
          disabled={!hasFrames}
          className="shrink-0 px-2 py-1 text-xs rounded border border-white/20 hover:bg-white/10 disabled:opacity-40 disabled:cursor-not-allowed"
          aria-label={playing ? 'Tạm dừng' : 'Phát'}
          title={playing ? 'Tạm dừng' : 'Phát'}
        >
          {playing ? '❚❚' : '▶'}
        </button>

        <input
          type="range"
          min={0}
          max={Math.max(0, frameCount - 1)}
          step={1}
          value={frame}
          onChange={(e) => setFrame(clampFrame(Number(e.target.value), frameCount))}
          disabled={!hasFrames}
          className="flex-1 min-w-0 disabled:opacity-40"
          aria-label="Thanh khung hình"
        />

        <span className="shrink-0 text-[11px] font-mono tabular-nums">{info}</span>

        <select
          value={fps}
          onChange={(e) => setFps(Number(e.target.value))}
          disabled={!hasFrames}
          className="shrink-0 text-xs rounded border border-white/20 bg-black/40 text-white/80 px-1 py-1 disabled:opacity-40"
          aria-label="Tốc độ khung hình"
        >
          {FPS_OPTIONS.map((f) => (
            <option key={f} value={f} className="text-black">
              {f} fps
            </option>
          ))}
        </select>
      </div>
    </div>
  );
}

export default ImageSequenceViewer;
