/**
 * `@ump/shared/video-viewer` — a 2×2 video dataset quad-viewer.
 *
 * Q1 video gốc (master clock) · Q2 bản đồ độ sâu · Q3 mặt nạ phân vùng (base video
 * + segmentation overlay composited via mixBlendMode:'screen') · Q4 tái tạo 3D
 * (placeholder). Double-click a quad to expand it; the other three stack on the
 * right. A controls bar below the grid drives synced playback across the
 * time-based panes.
 *
 * 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, useMemo, useRef, useState } from 'react';
import type { CSSProperties } from 'react';
import type {
  VideoQuadViewerProps,
  QuadIndex,
  ExpandedView,
  VideoSource,
  DepthSource,
  SegmentationSource,
} from './types';
import { QUAD_LABELS, formatTimecode, expandedOrder } from './playbackModel';
import { usePlaybackSync } from './usePlaybackSync';
import { VideoQuad } from './VideoQuad';

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

/**
 * Resolve a playable URL from a {url?, file?} source. When a File is given, create
 * an object URL and revoke it when the file identity changes or on unmount. A plain
 * `url` is returned as-is.
 *
 * The create + revoke MUST live in the same effect: under React 18 StrictMode the
 * mount→cleanup→remount cycle would otherwise revoke the URL right after mount
 * (leaving a dead `<video src>`). Here cleanup revokes and the re-run recreates.
 */
function useSourceUrl(
  source: VideoSource | DepthSource | SegmentationSource | undefined,
): string | undefined {
  const file = source?.file;
  const url = source?.url;
  const [objectUrl, setObjectUrl] = useState<string | undefined>(undefined);
  useEffect(() => {
    if (!file) {
      setObjectUrl(undefined);
      return;
    }
    const created = URL.createObjectURL(file);
    setObjectUrl(created);
    return () => URL.revokeObjectURL(created);
  }, [file]);
  return objectUrl ?? url;
}

const PLACEHOLDER_TEXT = 'text-xs text-white/50 px-2 text-center';

export function VideoQuadViewer({
  video,
  depth,
  segmentation,
  recon,
  className,
  initialExpanded = null,
}: VideoQuadViewerProps) {
  const videoUrl = useSourceUrl(video);
  const depthUrl = useSourceUrl(depth);
  const segUrl = useSourceUrl(segmentation);

  const hasSegVideo = !!segUrl;

  // Refs to the time-based <video> elements.
  const videoRef = useRef<HTMLVideoElement>(null); // Q1 master
  const depthRef = useRef<HTMLVideoElement>(null); // Q2
  const segBaseRef = useRef<HTMLVideoElement>(null); // Q3 base (= original video again)
  const segOverlayRef = useRef<HTMLVideoElement>(null); // Q3 overlay (segmentation video)

  // Drive synced playback. Q1 is the master clock (index 0). Include the overlay
  // ref only when a segmentation video is present. The hook is null-safe so the
  // array always has stable length.
  const syncRefs = useMemo(
    () => [videoRef, depthRef, segBaseRef, segOverlayRef],
    [],
  );
  const { currentTime, duration, playing, rate, togglePlay, seek, setRate } =
    usePlaybackSync(syncRefs, 0, videoUrl);

  const [expandedView, setExpandedView] = useState<ExpandedView>(initialExpanded ?? null);
  const toggleExpand = (i: QuadIndex) =>
    setExpandedView((prev) => (prev === i ? null : i));

  // Surface a media decode failure (e.g. an unsupported codec such as MPEG-4
  // Part 2, which Chrome's <video> can't play) instead of a silent black quad.
  // Reset whenever the source changes.
  const [videoError, setVideoError] = useState(false);
  useEffect(() => setVideoError(false), [videoUrl]);

  const timecode = `${formatTimecode(currentTime)} / ${formatTimecode(duration)}`;

  // ---- Per-quad media render ------------------------------------------------
  const renderQuadMedia = (q: QuadIndex) => {
    switch (q) {
      case 0:
        if (!videoUrl) {
          return (
            <div className="absolute inset-0 flex items-center justify-center">
              <span className={PLACEHOLDER_TEXT}>Chưa có video — tải lên để xem</span>
            </div>
          );
        }
        return (
          <>
            <video
              ref={videoRef}
              src={videoUrl}
              className="w-full h-full object-contain bg-black"
              playsInline
              muted
              onError={() => setVideoError(true)}
            />
            {videoError && (
              <div className="absolute inset-0 flex items-center justify-center bg-black/70 p-3">
                <span className={PLACEHOLDER_TEXT}>
                  Không phát được video — trình duyệt không hỗ trợ codec này. Hãy chuyển sang
                  MP4 (H.264).
                </span>
              </div>
            )}
          </>
        );
      case 1:
        return depthUrl ? (
          <video
            ref={depthRef}
            src={depthUrl}
            className="w-full h-full object-contain bg-black grayscale"
            playsInline
            muted
          />
        ) : (
          <div className="absolute inset-0 flex items-center justify-center">
            <span className={PLACEHOLDER_TEXT}>Không có bản đồ độ sâu</span>
          </div>
        );
      case 2:
        return (
          <div className="relative w-full h-full">
            <video
              ref={segBaseRef}
              src={videoUrl}
              className="w-full h-full object-contain bg-black"
              playsInline
              muted
            />
            {hasSegVideo ? (
              <video
                ref={segOverlayRef}
                src={segUrl}
                className="absolute inset-0 w-full h-full object-contain pointer-events-none"
                style={{ mixBlendMode: 'screen', opacity: segmentation?.opacity ?? 0.6 }}
                playsInline
                muted
              />
            ) : (
              <div className="absolute inset-0 flex items-center justify-center">
                <span className={PLACEHOLDER_TEXT}>Không có mặt nạ phân vùng</span>
              </div>
            )}
          </div>
        );
      case 3:
        return (
          <div className="absolute inset-0 flex items-center justify-center bg-white/[0.03]">
            <span className={PLACEHOLDER_TEXT}>
              {recon?.placeholderText ?? 'Tái tạo 3D — chưa có dữ liệu'}
            </span>
          </div>
        );
      default:
        return null;
    }
  };

  const quadInfo = (q: QuadIndex): string | undefined =>
    // Timecode chip only on the time-based panes (0,1,2).
    q !== 3 ? timecode : undefined;

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

  // ---- Single CSS-grid tree -------------------------------------------------
  // Layout switches via grid-template-areas so the four <video> nodes are NEVER
  // remounted on expand. Remounting would drop usePlaybackSync's master-clock
  // listeners and reset every video to 0 — so the tree structure (one grid, four
  // stable keyed cells in fixed order) must stay identical across expand toggles.
  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', className)}>
      <div className="flex-1 min-h-0">{grid}</div>

      {/* Controls bar — plain HTML + Tailwind, no barrel imports. */}
      <div className="shrink-0 flex items-center gap-3 px-2 py-2 border-t border-border">
        <button
          type="button"
          onClick={togglePlay}
          className="shrink-0 px-2 py-1 text-xs rounded border border-border hover:bg-accent"
          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={duration || 0}
          step={0.01}
          value={currentTime}
          onChange={(e) => seek(Number(e.target.value))}
          className="flex-1 min-w-0"
          aria-label="Thanh thời gian"
        />

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

        <select
          value={rate}
          onChange={(e) => setRate(Number(e.target.value))}
          className="shrink-0 text-xs rounded border border-border bg-background px-1 py-1"
          aria-label="Tốc độ phát"
        >
          <option value={0.5}>0.5x</option>
          <option value={1}>1x</option>
          <option value={1.5}>1.5x</option>
          <option value={2}>2x</option>
        </select>
      </div>
    </div>
  );
}

export default VideoQuadViewer;
