'use client';

import {
  useMemo,
  useState,
  useCallback,
  useRef,
  useReducer,
  useEffect,
} from 'react';
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from '@/components/ui/card';
import { Film } from 'lucide-react';

import {
  DetectionData,
  DetectionType,
  DetectionCounts,
  DetectionLogEntry,
} from '@/types';
import { useGpsMap } from '@/hooks/use-gps-map';
import { useFrameDetectionMap } from '@/hooks/use-frame-detection-map';
import { useCumulativeCounts } from '@/hooks/use-cumulative-counts';
import { useVideoDetectionLoop } from '@/hooks/use-video-detection-loop';

import VideoCanvasPlayer, {
  VideoCanvasPlayerRef,
} from './video/video-canvas-player';
import DetectionStatsBar from './video/detection-stats-bar';
import DetectionLogs from './video/detection-logs';
import SummarySection from './video/summary-section';
import DetailedSummarySection from './video/detailed-summary-section';
import {
  getDetectionModeConfig,
  getEnabledDetectionTypes,
} from '@/constants/detectionModeConfig';

type VideoPlayerSectionProps = {
  data: DetectionData;
  videoId: string;
  videoFile: File | null;
  detectionType: string;
  projectId?: string;
};

const MAX_LOGS = 50;

type LogAction =
  | { type: 'ADD_LOG'; payload: DetectionLogEntry }
  | { type: 'CLEAR' };

function logsReducer(
  state: DetectionLogEntry[],
  action: LogAction,
): DetectionLogEntry[] {
  switch (action.type) {
    case 'ADD_LOG':
      // Move to top if already exists, or just add to top
      const filtered = state.filter(
        (log) => log.frame !== action.payload.frame,
      );
      return [action.payload, ...filtered].slice(0, MAX_LOGS);
    case 'CLEAR':
      return [];
    default:
      return state;
  }
}

const formatVideoTime = (frame: number, fps: number): string => {
  const seconds = frame / fps;
  const minutes = Math.floor(seconds / 60);
  const secs = Math.floor(seconds % 60);
  return `${minutes}:${secs.toString().padStart(2, '0')}`;
};

export default function VideoPlayerSection({
  data,
  videoId,
  videoFile,
  detectionType,
  projectId,
}: VideoPlayerSectionProps) {
  // Refs
  const playerRef = useRef<VideoCanvasPlayerRef>(null);
  const videoRef = useRef<HTMLVideoElement>(null);
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const loggedFrames = useRef<Set<number>>(new Set());

  // State
  const [currentFrame, setCurrentFrame] = useState(0);
  const [showSummary, setShowSummary] = useState(false);
  const [hasPlayedOnce, setHasPlayedOnce] = useState(false);
  const [videoError, setVideoError] = useState<string | null>(null);
  const [lastDetectedLat, setLastDetectedLat] = useState<number | null>(null);
  const [lastDetectedLng, setLastDetectedLng] = useState<number | null>(null);
  const detectionMode = data.detection_mode || detectionType;
  const modeConfig = getDetectionModeConfig(detectionMode);
  const enabledTypes = getEnabledDetectionTypes(detectionMode);

  const initialCounts = useMemo(() => {
    const counts: any = {};
    enabledTypes.forEach((type) => {
      counts[type.frameCountKey] = 0;
    });
    return counts as DetectionCounts;
  }, [enabledTypes]);

  const [currentFrameCounts, setCurrentFrameCounts] =
    useState<DetectionCounts>(initialCounts);

  // Logs Reducer
  const [logs, dispatchLogs] = useReducer(logsReducer, []);

  // Memoize a unified data object with frames generated if missing
  const normalizedData = useMemo(() => {
    if (data.frames && Array.isArray(data.frames) && data.frames.length > 0) {
      return data;
    }

    // Synthesize frames from lists if not present (specifically for gemini_video)
    // Other models like YOLO return data.frames directly
    const framesMap = new Map<number, any>();

    // Sort all detections by their first_detected_frame
    const allDetections: any[] = [];
    enabledTypes.forEach((type) => {
      const list = (data as any)[type.listKey];
      if (list && Array.isArray(list)) {
        list.forEach((item: any) => {
          allDetections.push({ ...item, _detType: type.id });
        });
      }
    });

    allDetections.sort(
      (a, b) => (a.first_detected_frame || 0) - (b.first_detected_frame || 0),
    );

    // Current counts for sticky stats
    const currentCounts: Record<string, number> = {};
    enabledTypes.forEach((t) => {
      currentCounts[t.frameCountKey] = 0;
    });

    allDetections.forEach((det) => {
      const frameId = det.first_detected_frame || det.frame_number || 0;

      // Spread detection across multiple frames so it stays visible (persistence)
      // Gemini detections are sparse, so showing them for ~1 second (30 frames) helps
      const persistenceFrames = 30;

      for (let i = 0; i < persistenceFrames; i++) {
        const targetFrame = frameId + i;

        if (!framesMap.has(targetFrame)) {
          framesMap.set(targetFrame, { frame_id: targetFrame, detections: [] });
        }

        const frameData = framesMap.get(targetFrame);

        // Update cumulative counts only on the first detected frame
        if (i === 0) {
          const typeConfig = enabledTypes.find((t) => t.id === det._detType);
          if (typeConfig) {
            currentCounts[typeConfig.frameCountKey] =
              (currentCounts[typeConfig.frameCountKey] || 0) + 1;
          }
        }

        const countCopy = { ...currentCounts };

        frameData.detections.push({
          ...det,
          type: det.type || det._detType,
          detection_id: det.detection_id || det.id,
          count: countCopy,
        });
      }
    });

    return {
      ...data,
      frames: Array.from(framesMap.values()).sort(
        (a, b) => (a.frame_id || 0) - (b.frame_id || 0),
      ),
    };
  }, [data, enabledTypes]);

  // Custom Hooks
  const gpsMap = useGpsMap(normalizedData);
  const { getNearestDetections, sortedDetectionIndices } = useFrameDetectionMap(
    normalizedData,
    detectionType,
  );
  const { getStickyCounts, sortedFrameIndices } = useCumulativeCounts(
    normalizedData.frames || [],
  );

  // Memoized video URL
  const videoUrl = useMemo(() => {
    if (videoFile) return URL.createObjectURL(videoFile);
    if (videoId) return `${process.env.NEXT_PUBLIC_API_URL}/video/${videoId}`;
    return '';
  }, [videoFile, videoId]);

  // Clean up Object URL
  useEffect(() => {
    return () => {
      if (videoFile && videoUrl) URL.revokeObjectURL(videoUrl);
    };
  }, [videoFile, videoUrl]);

  // Handle detection updates
  const handleFrameUpdate = useCallback(
    (frame: number) => {
      setCurrentFrame(frame);

      // Optimized lookup
      const dets = getNearestDetections(frame, sortedFrameIndices);
      const counts = getStickyCounts(frame);

      // Update visuals via ref to avoid state-induced slow re-renders in heavy loops
      playerRef.current?.drawDetections(dets || []);

      // Frame stats
      setCurrentFrameCounts(counts);

      // Logging logic
      if (dets && dets.length > 0) {
        const firstDetId =
          dets[0].pothole_id ?? dets[0].signboard_id ?? dets[0].detection_id;
        const coords = gpsMap.get(firstDetId);

        if (coords) {
          setLastDetectedLat(coords.lat);
          setLastDetectedLng(coords.lng);
        }

        dispatchLogs({
          type: 'ADD_LOG',
          payload: {
            frame,
            videoTime: formatVideoTime(frame, data.video_info.fps),
            detections: dets.map((d) => ({
              id: d.pothole_id ?? d.signboard_id ?? d.detection_id,
              type: d.type || d._detType,
              bbox: d.bbox,
              confidence: d.confidence,
              latitude: gpsMap.get(
                d.pothole_id ?? d.signboard_id ?? d.detection_id,
              )?.lat,
              longitude: gpsMap.get(
                d.pothole_id ?? d.signboard_id ?? d.detection_id,
              )?.lng,
            })),
          },
        });
      }
    },
    [
      data.video_info.fps,
      getNearestDetections,
      getStickyCounts,
      gpsMap,
      sortedDetectionIndices,
    ],
  );

  // Playback loop hook
  useVideoDetectionLoop(videoRef, data.video_info.fps, handleFrameUpdate);

  // Callbacks
  const handleLoadedData = useCallback(() => {
    playerRef.current?.resize();
  }, []);

  const handleVideoError = useCallback(() => {
    setVideoError('Failed to load video');
  }, []);

  const handleVideoEnded = useCallback(() => {
    if (!hasPlayedOnce) {
      setHasPlayedOnce(true);
      setShowSummary(true);
    }
  }, [hasPlayedOnce]);

  const handleVideoSeeked = useCallback(() => {
    const video = videoRef.current;
    if (!video) return;

    const fps = data.video_info.fps || 30;
    const currentFrame = Math.round(video.currentTime * fps);

    // Snap to the next available frame index that has detections
    const nextDetectionFrame = sortedDetectionIndices.find(
      (f) => f >= currentFrame,
    );

    if (
      nextDetectionFrame !== undefined &&
      nextDetectionFrame !== currentFrame
    ) {
      video.currentTime = nextDetectionFrame / fps;
      return;
    }

    handleFrameUpdate(currentFrame);
  }, [data.video_info.fps, handleFrameUpdate, sortedDetectionIndices]);

  const seekToFrame = useCallback(
    (frame: number) => {
      const video = videoRef.current;
      if (!video) return;
      video.currentTime = frame / data.video_info.fps;
    },
    [data.video_info.fps],
  );

  return (
    <div className="space-y-6">
      <Card className="overflow-hidden">
        <CardHeader className="pb-4 border-b">
          <div className="flex items-center gap-3">
            <div className="p-2 rounded bg-secondary">
              <Film className="h-5 w-5 text-primary" />
            </div>
            <div>
              <CardTitle className="text-lg font-bold">
                Detection Playback
              </CardTitle>
              <CardDescription className="text-xs">
                Real-time object detection analysis
              </CardDescription>
            </div>
          </div>
        </CardHeader>
        <CardContent className="pt-6">
          <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
            <div className="lg:col-span-2 space-y-6">
              <VideoCanvasPlayer
                ref={playerRef}
                videoRef={videoRef}
                canvasRef={canvasRef}
                videoUrl={videoUrl}
                videoWidth={data.video_info.width}
                videoHeight={data.video_info.height}
                videoError={videoError}
                onLoadedData={handleLoadedData}
                onEnded={handleVideoEnded}
                onSeeked={handleVideoSeeked}
                currentDetections={
                  getNearestDetections(currentFrame, sortedDetectionIndices) ||
                  []
                }
              />

              <DetectionStatsBar
                currentFrameCounts={currentFrameCounts}
                currentFrame={currentFrame}
                lastDetectedLat={lastDetectedLat}
                lastDetectedLng={lastDetectedLng}
                detectionMode={detectionMode}
              />
            </div>

            <DetectionLogs logs={logs} onSeek={seekToFrame} />
          </div>
        </CardContent>
      </Card>

      {showSummary && (
        <div className="space-y-6 pt-4 animate-in fade-in slide-in-from-bottom-4 duration-500">
          <SummarySection
            data={data}
            show={showSummary}
            detectionType={detectionType}
          />
          <DetailedSummarySection
            projectId={projectId || ''}
            videoId={videoId}
            show={showSummary}
            detectionType={detectionType}
          />
        </div>
      )}
    </div>
  );
}
