import { useRef, useEffect, useCallback, useState, useMemo } from 'react';
import { useEditorStore } from '../store/editorStore';
import { AlertTriangle } from 'lucide-react';
import { extractThumbnails } from '../lib/thumbnails';

const RULER_H = 20; // px reserved at top of canvas for the time ruler
const COLLAPSED_CUT_DISPLAY_SECONDS = 0.08;

type WaveformData = {
  samples: Float32Array;
  sampleRate: number;
  duration: number;
};

function parsePcm16Wav(arrayBuffer: ArrayBuffer): WaveformData {
  const view = new DataView(arrayBuffer);
  if (view.byteLength < 44) {
    throw new Error('WAV file too small');
  }

  const text = (offset: number, length: number) => {
    let s = '';
    for (let i = 0; i < length; i++) {
      s += String.fromCharCode(view.getUint8(offset + i));
    }
    return s;
  };

  if (text(0, 4) !== 'RIFF' || text(8, 4) !== 'WAVE') {
    throw new Error('Not a RIFF/WAVE file');
  }

  let fmtOffset = -1;
  let dataOffset = -1;
  let dataSize = 0;
  let offset = 12;

  while (offset + 8 <= view.byteLength) {
    const chunkId = text(offset, 4);
    const chunkSize = view.getUint32(offset + 4, true);
    const chunkDataStart = offset + 8;

    if (chunkId === 'fmt ') {
      fmtOffset = chunkDataStart;
    } else if (chunkId === 'data') {
      dataOffset = chunkDataStart;
      dataSize = chunkSize;
      break;
    }

    offset = chunkDataStart + chunkSize + (chunkSize % 2);
  }

  if (fmtOffset < 0 || dataOffset < 0) {
    throw new Error('Missing WAV fmt/data chunk');
  }

  const audioFormat = view.getUint16(fmtOffset, true);
  const channels = view.getUint16(fmtOffset + 2, true);
  const sampleRate = view.getUint32(fmtOffset + 4, true);
  const bitsPerSample = view.getUint16(fmtOffset + 14, true);

  if (audioFormat !== 1 || bitsPerSample !== 16) {
    throw new Error(`Unsupported WAV format (format=${audioFormat}, bits=${bitsPerSample})`);
  }
  if (channels < 1) {
    throw new Error('Invalid channel count in WAV');
  }

  const bytesPerSample = bitsPerSample / 8;
  const frameCount = Math.floor(dataSize / (channels * bytesPerSample));
  const samples = new Float32Array(frameCount);

  let p = dataOffset;
  for (let i = 0; i < frameCount; i++) {
    const sample = view.getInt16(p, true);
    samples[i] = sample / 32768;
    p += channels * bytesPerSample;
  }

  return {
    samples,
    sampleRate,
    duration: frameCount / sampleRate,
  };
}

function formatTime(secs: number): string {
  const m = Math.floor(secs / 60);
  const s = secs % 60;
  if (m > 0) return `${m}:${String(Math.floor(s)).padStart(2, '0')}.${Math.floor((s % 1) * 10)}`;
  return `${s.toFixed(1)}s`;
}

function pickInterval(pxPerSec: number): { major: number; minor: number } {
  const NICE = [0.05, 0.1, 0.25, 0.5, 1, 2, 5, 10, 15, 30, 60, 120, 300, 600];
  let major = NICE[NICE.length - 1];
  for (const n of NICE) {
    if (n * pxPerSec >= 70) { major = n; break; }
  }
  let minor = major;
  for (const n of NICE) {
    if (n * pxPerSec >= 6 && n < major) { minor = n; }
  }
  return { major, minor };
}

type TimelineSegment = {
  kind: 'keep' | 'cut';
  sourceStart: number;
  sourceEnd: number;
  displayStart: number;
  displayEnd: number;
};

function mergeCutRanges(ranges: Array<{ start: number; end: number }>, maxDuration: number) {
  const sorted = ranges
    .map((range) => ({
      start: Math.max(0, Math.min(maxDuration, range.start)),
      end: Math.max(0, Math.min(maxDuration, range.end)),
    }))
    .filter((range) => range.end > range.start)
    .sort((a, b) => a.start - b.start);

  const merged: Array<{ start: number; end: number }> = [];
  for (const range of sorted) {
    const last = merged[merged.length - 1];
    if (!last || range.start > last.end) {
      merged.push({ ...range });
    } else {
      last.end = Math.max(last.end, range.end);
    }
  }
  return merged;
}

function buildTimelineSegments(sourceDuration: number, cutRanges: Array<{ start: number; end: number }>) {
  if (sourceDuration <= 0) {
    return { segments: [] as TimelineSegment[], displayDuration: 0 };
  }

  const mergedCuts = mergeCutRanges(cutRanges, sourceDuration);
  const segments: TimelineSegment[] = [];
  let sourceCursor = 0;
  let displayCursor = 0;

  for (const cutRange of mergedCuts) {
    if (cutRange.start > sourceCursor) {
      const keepDuration = cutRange.start - sourceCursor;
      segments.push({
        kind: 'keep',
        sourceStart: sourceCursor,
        sourceEnd: cutRange.start,
        displayStart: displayCursor,
        displayEnd: displayCursor + keepDuration,
      });
      displayCursor += keepDuration;
    }

    const cutDisplayDuration = Math.min(cutRange.end - cutRange.start, COLLAPSED_CUT_DISPLAY_SECONDS);
    segments.push({
      kind: 'cut',
      sourceStart: cutRange.start,
      sourceEnd: cutRange.end,
      displayStart: displayCursor,
      displayEnd: displayCursor + cutDisplayDuration,
    });
    displayCursor += cutDisplayDuration;
    sourceCursor = cutRange.end;
  }

  if (sourceCursor < sourceDuration) {
    const keepDuration = sourceDuration - sourceCursor;
    segments.push({
      kind: 'keep',
      sourceStart: sourceCursor,
      sourceEnd: sourceDuration,
      displayStart: displayCursor,
      displayEnd: displayCursor + keepDuration,
    });
    displayCursor += keepDuration;
  }

  return { segments, displayDuration: displayCursor };
}

function sourceToDisplayTime(time: number, segments: TimelineSegment[], sourceDuration: number) {
  if (segments.length === 0) return Math.max(0, Math.min(sourceDuration, time));
  const clampedTime = Math.max(0, Math.min(sourceDuration, time));

  for (const segment of segments) {
    if (clampedTime <= segment.sourceEnd || segment === segments[segments.length - 1]) {
      const sourceSpan = Math.max(segment.sourceEnd - segment.sourceStart, 1e-6);
      const displaySpan = segment.displayEnd - segment.displayStart;
      const ratio = (clampedTime - segment.sourceStart) / sourceSpan;
      return segment.displayStart + ratio * displaySpan;
    }
  }

  return segments[segments.length - 1].displayEnd;
}

function displayToSourceTime(time: number, segments: TimelineSegment[], displayDuration: number, sourceDuration: number) {
  if (segments.length === 0) return Math.max(0, Math.min(sourceDuration, time));
  const clampedTime = Math.max(0, Math.min(displayDuration, time));

  for (const segment of segments) {
    if (clampedTime <= segment.displayEnd || segment === segments[segments.length - 1]) {
      const displaySpan = Math.max(segment.displayEnd - segment.displayStart, 1e-6);
      const sourceSpan = segment.sourceEnd - segment.sourceStart;
      const ratio = (clampedTime - segment.displayStart) / displaySpan;
      return segment.sourceStart + ratio * sourceSpan;
    }
  }

  return sourceDuration;
}

export default function WaveformTimeline({
  cutMode,
  muteMode,
  gainMode,
  gainModeDb,
  speedMode,
  speedModeValue,
}: {
  cutMode: boolean;
  muteMode: boolean;
  gainMode: boolean;
  gainModeDb: number;
  speedMode: boolean;
  speedModeValue: number;
}) {
  const waveCanvasRef = useRef<HTMLCanvasElement>(null);
  const headCanvasRef = useRef<HTMLCanvasElement>(null);
  const containerRef = useRef<HTMLDivElement>(null);
  const [audioError, setAudioError] = useState<string | null>(null);

  const videoUrl = useEditorStore((s) => s.videoUrl);
  const videoPath = useEditorStore((s) => s.videoPath);
  const backendUrl = useEditorStore((s) => s.backendUrl);
  const duration = useEditorStore((s) => s.duration);
  const setCurrentTime = useEditorStore((s) => s.setCurrentTime);
  const cutRanges = useEditorStore((s) => s.cutRanges);
  const muteRanges = useEditorStore((s) => s.muteRanges);
  const gainRanges = useEditorStore((s) => s.gainRanges);
  const speedRanges = useEditorStore((s) => s.speedRanges);
  const timelineMarkers = useEditorStore((s) => s.timelineMarkers);
  const markInTime = useEditorStore((s) => s.markInTime);
  const markOutTime = useEditorStore((s) => s.markOutTime);
  const [showThumbnails, setShowThumbnails] = useState(() => typeof window !== 'undefined' && localStorage.getItem('talkedit:showThumbnails') === 'true');
  const [thumbnailFrames, setThumbnailFrames] = useState<Map<number, string>>(new Map());
  void setShowThumbnails;
  const thumbnailContainerRef = useRef<HTMLDivElement>(null);
  const addCutRange = useEditorStore((s) => s.addCutRange);
  const addMuteRange = useEditorStore((s) => s.addMuteRange);
  const addGainRange = useEditorStore((s) => s.addGainRange);
  const addSpeedRange = useEditorStore((s) => s.addSpeedRange);
  const updateCutRange = useEditorStore((s) => s.updateCutRange);
  const updateMuteRange = useEditorStore((s) => s.updateMuteRange);
  const updateGainRangeBounds = useEditorStore((s) => s.updateGainRangeBounds);
  const updateSpeedRangeBounds = useEditorStore((s) => s.updateSpeedRangeBounds);
  const removeCutRange = useEditorStore((s) => s.removeCutRange);
  const removeMuteRange = useEditorStore((s) => s.removeMuteRange);
  const removeGainRange = useEditorStore((s) => s.removeGainRange);
  const removeSpeedRange = useEditorStore((s) => s.removeSpeedRange);

  const waveformDataRef = useRef<WaveformData | null>(null);
  const zoomRef = useRef(1);       // 1 = show all, >1 = zoomed in
  const scrollSecsRef = useRef(0); // seconds scrolled from left
  const rafRef = useRef(0);
  // Ref so the RAF loop can call drawStaticWaveform without a stale closure
  const drawStaticWaveformRef = useRef<() => void>(() => {});
  const isDraggingRef = useRef(false);
  const [isDragging, setIsDragging] = useState(false);
  const selectionStartRef = useRef<number | null>(null);
  const selectionEndRef = useRef<number | null>(null);
  const [selectionStart, setSelectionStart] = useState<number | null>(null);
  const [selectionEnd, setSelectionEnd] = useState<number | null>(null);
  const [selectedZone, setSelectedZone] = useState<{type: 'cut' | 'mute' | 'gain' | 'speed', id: string} | null>(null);
  const [hoverCursor, setHoverCursor] = useState<string>('crosshair');
  const editingZoneRef = useRef<{type: 'cut' | 'mute' | 'gain' | 'speed', id: string, edge: 'start' | 'end' | 'move'} | null>(null);
  const [showCutZones, setShowCutZones] = useState(true);
  const [showMuteZones, setShowMuteZones] = useState(true);
  const [showGainZones, setShowGainZones] = useState(true);
  const [showSpeedZones, setShowSpeedZones] = useState(true);
  const [showAdjustedTimeline, setShowAdjustedTimeline] = useState(false);

  const sourceDuration = duration || waveformDataRef.current?.duration || 0;
  const timelineCutRanges = showAdjustedTimeline ? cutRanges : [];
  const { segments: timelineSegments, displayDuration } = useMemo(
    () => buildTimelineSegments(sourceDuration, timelineCutRanges),
    [sourceDuration, timelineCutRanges],
  );

  useEffect(() => {
    if (!videoUrl || !videoPath) return;
    setAudioError(null);

    let cancelled = false;
    const controller = new AbortController();
    const loadAudio = async () => {
      const requestId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
      try {
        const waveformUrl = `${backendUrl}/audio/waveform?path=${encodeURIComponent(videoPath!)}`;
        console.log('[WaveformTimeline] req=', requestId, 'backendUrl=', backendUrl, 'videoPath=', videoPath);
        console.log('[WaveformTimeline] req=', requestId, 'fetching=', waveformUrl);

        const startedAt = performance.now();
        const response = await fetch(waveformUrl, { signal: controller.signal });
        if (cancelled) return;
        const elapsedMs = Math.round(performance.now() - startedAt);
        if (!response.ok) {
          const body = await response.text().catch(() => '');
          console.error(
            `[WaveformTimeline] req=${requestId} fetch failed — HTTP ${response.status} ${response.statusText}`,
            {
              url: waveformUrl,
              decodedPath: videoPath,
              elapsedMs,
              body,
            }
          );
          throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }

        const contentType = response.headers.get('content-type') ?? 'unknown';
        const contentLength = response.headers.get('content-length');
        console.log(
          `[WaveformTimeline] req=${requestId} fetch ok — content-type: ${contentType}, size: ${contentLength ?? 'unknown'} bytes, elapsed: ${elapsedMs}ms`
        );

        const arrayBuffer = await response.arrayBuffer();
        if (cancelled) return;
        console.log(`[WaveformTimeline] req=${requestId} arrayBuffer size: ${arrayBuffer.byteLength} bytes`);

        if (arrayBuffer.byteLength === 0) {
          throw new Error('Server returned an empty file');
        }

        const waveformData = parsePcm16Wav(arrayBuffer);

        console.log(
          `[WaveformTimeline] req=${requestId} parsed wav ok — duration: ${waveformData.duration.toFixed(2)}s, ` +
          `sampleRate: ${waveformData.sampleRate}Hz, samples: ${waveformData.samples.length}`
        );
        if (cancelled) return;
        waveformDataRef.current = waveformData;
        drawStaticWaveformRef.current();
      } catch (err) {
        if (cancelled || (err instanceof DOMException && err.name === 'AbortError')) {
          console.log('[WaveformTimeline] req=', requestId, 'aborted/cancelled');
          return;
        }
        console.error('[WaveformTimeline] waveform load failed', {
          requestId,
          error: err,
          videoPath,
          backendUrl,
          encodedPath: encodeURIComponent(videoPath ?? ''),
        });
        const waveformUrl2 = `${backendUrl}/audio/waveform?path=${encodeURIComponent(videoPath ?? '')}`;
        const errMessage = err instanceof Error ? err.message : 'audio could not be decoded';
        const userMessage = errMessage === 'Load failed'
          ? `Cannot reach backend at ${backendUrl}. Start the backend server and reload the project.`
          : errMessage;
        setAudioError(`Waveform unavailable — ${userMessage} [URL: ${waveformUrl2}]`);
      }
    };

    loadAudio();

    return () => {
      cancelled = true;
      controller.abort();
    };
  }, [videoUrl, videoPath, backendUrl]);

  const drawStaticWaveform = useCallback(() => {
    const canvas = waveCanvasRef.current;
    const waveformData = waveformDataRef.current;
    if (!canvas || !waveformData) return;

    const ctx = canvas.getContext('2d');
    if (!ctx) return;

    const dpr = window.devicePixelRatio || 1;
    const rect = canvas.getBoundingClientRect();
    canvas.width = rect.width * dpr;
    canvas.height = rect.height * dpr;
    ctx.scale(dpr, dpr);

    const width = rect.width;
    const height = rect.height;
    const dur = waveformData.duration;
    const timelineDur = displayDuration || dur;
    const zoom = zoomRef.current;
    const scroll = scrollSecsRef.current;
    const pxPerSec = (width * zoom) / timelineDur;
    const sampleRate = waveformData.sampleRate;
    const channelData = waveformData.samples;

    ctx.clearRect(0, 0, width, height);

    // --- Ruler background ---
    ctx.fillStyle = '#13141f';
    ctx.fillRect(0, 0, width, RULER_H);

    // Separator line
    ctx.strokeStyle = '#2a2d3e';
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.moveTo(0, RULER_H);
    ctx.lineTo(width, RULER_H);
    ctx.stroke();

    // --- Ruler ticks & labels ---
    const { major, minor } = pickInterval(pxPerSec);
    const visibleDur = width / pxPerSec;

    // Minor ticks
    const minorStart = Math.floor(scroll / minor) * minor;
    ctx.strokeStyle = '#3a3d52';
    ctx.lineWidth = 1;
    for (let t = minorStart; t <= scroll + visibleDur + minor; t = Math.round((t + minor) * 1e6) / 1e6) {
      const x = (t - scroll) * pxPerSec;
      if (x < 0 || x > width) continue;
      ctx.beginPath();
      ctx.moveTo(x, RULER_H);
      ctx.lineTo(x, RULER_H * 0.45);
      ctx.stroke();
    }

    // Major ticks + labels
    const majorStart = Math.floor(scroll / major) * major;
    ctx.lineWidth = 1;
    ctx.font = `9px "JetBrains Mono", "Courier New", monospace`;
    ctx.textBaseline = 'top';
    for (let t = majorStart; t <= scroll + visibleDur + major; t = Math.round((t + major) * 1e6) / 1e6) {
      const x = (t - scroll) * pxPerSec;
      if (x < -50 || x > width + 50) continue;
      ctx.strokeStyle = '#4a4f6a';
      ctx.beginPath();
      ctx.moveTo(x, RULER_H);
      ctx.lineTo(x, 0);
      ctx.stroke();
      if (x >= 2 && x < width - 2) {
        ctx.fillStyle = '#6b7280';
        ctx.fillText(formatTime(t), x + 3, 2);
      }
    }

    // --- Waveform ---
    const waveTop = RULER_H + 1;
    const waveH = height - waveTop;

    // Draw cut ranges (red overlays)
    for (const range of showCutZones ? cutRanges : []) {
      const x1 = (sourceToDisplayTime(range.start, timelineSegments, dur) - scroll) * pxPerSec;
      const x2 = (sourceToDisplayTime(range.end, timelineSegments, dur) - scroll) * pxPerSec;
      const isSelected = selectedZone?.type === 'cut' && selectedZone.id === range.id;

      ctx.fillStyle = isSelected ? 'rgba(239, 68, 68, 0.62)' : 'rgba(239, 68, 68, 0.46)';
      ctx.fillRect(x1, waveTop, x2 - x1, waveH);

      // Keep cut ranges clearly visible even when not selected.
      ctx.strokeStyle = isSelected ? '#ef4444' : 'rgba(239, 68, 68, 0.9)';
      ctx.lineWidth = isSelected ? 2.8 : 1.8;
      ctx.strokeRect(x1, waveTop, x2 - x1, waveH);
      
      if (isSelected) {
        // Draw resize handles
        ctx.fillStyle = '#ef4444';
        ctx.beginPath();
        ctx.arc(x1, waveTop + waveH / 2, 4, 0, 2 * Math.PI);
        ctx.fill();
        ctx.beginPath();
        ctx.arc(x2, waveTop + waveH / 2, 4, 0, 2 * Math.PI);
        ctx.fill();
      }
    }

    // Draw mute ranges (blue overlays)
    for (const range of showMuteZones ? muteRanges : []) {
      const x1 = (sourceToDisplayTime(range.start, timelineSegments, dur) - scroll) * pxPerSec;
      const x2 = (sourceToDisplayTime(range.end, timelineSegments, dur) - scroll) * pxPerSec;
      const isSelected = selectedZone?.type === 'mute' && selectedZone.id === range.id;
      
      ctx.fillStyle = isSelected ? 'rgba(59, 130, 246, 0.5)' : 'rgba(59, 130, 246, 0.3)';
      ctx.fillRect(x1, waveTop, x2 - x1, waveH);
      
      if (isSelected) {
        ctx.strokeStyle = '#3b82f6';
        ctx.lineWidth = 2;
        ctx.strokeRect(x1, waveTop, x2 - x1, waveH);
        
        // Draw resize handles
        ctx.fillStyle = '#3b82f6';
        ctx.beginPath();
        ctx.arc(x1, waveTop + waveH / 2, 4, 0, 2 * Math.PI);
        ctx.fill();
        ctx.beginPath();
        ctx.arc(x2, waveTop + waveH / 2, 4, 0, 2 * Math.PI);
        ctx.fill();
      }
    }

    // Draw gain ranges (amber overlays)
    for (const range of showGainZones ? gainRanges : []) {
      const x1 = (sourceToDisplayTime(range.start, timelineSegments, dur) - scroll) * pxPerSec;
      const x2 = (sourceToDisplayTime(range.end, timelineSegments, dur) - scroll) * pxPerSec;
      const isSelected = selectedZone?.type === 'gain' && selectedZone.id === range.id;

      ctx.fillStyle = isSelected ? 'rgba(245, 158, 11, 0.55)' : 'rgba(245, 158, 11, 0.35)';
      ctx.fillRect(x1, waveTop, x2 - x1, waveH);

      if (isSelected) {
        ctx.strokeStyle = '#f59e0b';
        ctx.lineWidth = 2;
        ctx.strokeRect(x1, waveTop, x2 - x1, waveH);

        ctx.fillStyle = '#f59e0b';
        ctx.beginPath();
        ctx.arc(x1, waveTop + waveH / 2, 4, 0, 2 * Math.PI);
        ctx.fill();
        ctx.beginPath();
        ctx.arc(x2, waveTop + waveH / 2, 4, 0, 2 * Math.PI);
        ctx.fill();
      }
    }

    // Draw speed ranges (emerald overlays)
    for (const range of showSpeedZones ? speedRanges : []) {
      const x1 = (sourceToDisplayTime(range.start, timelineSegments, dur) - scroll) * pxPerSec;
      const x2 = (sourceToDisplayTime(range.end, timelineSegments, dur) - scroll) * pxPerSec;
      const isSelected = selectedZone?.type === 'speed' && selectedZone.id === range.id;

      ctx.fillStyle = isSelected ? 'rgba(16, 185, 129, 0.55)' : 'rgba(16, 185, 129, 0.35)';
      ctx.fillRect(x1, waveTop, x2 - x1, waveH);

      if (isSelected) {
        ctx.strokeStyle = '#10b981';
        ctx.lineWidth = 2;
        ctx.strokeRect(x1, waveTop, x2 - x1, waveH);

        ctx.fillStyle = '#10b981';
        ctx.beginPath();
        ctx.arc(x1, waveTop + waveH / 2, 4, 0, 2 * Math.PI);
        ctx.fill();
        ctx.beginPath();
        ctx.arc(x2, waveTop + waveH / 2, 4, 0, 2 * Math.PI);
        ctx.fill();
      }

      const centerX = (x1 + x2) / 2;
      ctx.fillStyle = '#d1fae5';
      ctx.font = '10px sans-serif';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'top';
      if (centerX > 12 && centerX < width - 12) {
        ctx.fillText(`${range.speed.toFixed(2)}x`, centerX, waveTop + 4);
      }
      ctx.textAlign = 'start';
      ctx.textBaseline = 'alphabetic';
    }

    // Draw selection overlay (when in zone mode)
    if ((cutMode || muteMode || gainMode || speedMode) && selectionStart !== null && selectionEnd !== null) {
      const x1 = (sourceToDisplayTime(Math.min(selectionStart, selectionEnd), timelineSegments, dur) - scroll) * pxPerSec;
      const x2 = (sourceToDisplayTime(Math.max(selectionStart, selectionEnd), timelineSegments, dur) - scroll) * pxPerSec;
      const fillColor = cutMode
        ? 'rgba(239, 68, 68, 0.5)'
        : muteMode
          ? 'rgba(59, 130, 246, 0.5)'
          : gainMode
            ? 'rgba(245, 158, 11, 0.5)'
            : 'rgba(16, 185, 129, 0.5)';
      const strokeColor = cutMode ? '#ef4444' : muteMode ? '#3b82f6' : gainMode ? '#f59e0b' : '#10b981';
      ctx.fillStyle = fillColor;
      ctx.fillRect(x1, waveTop, x2 - x1, waveH);
      
      // Add border
      ctx.strokeStyle = strokeColor;
      ctx.lineWidth = 2;
      ctx.strokeRect(x1, waveTop, x2 - x1, waveH);
    }

    // Draw mark in/out range and markers
    if (markInTime !== null && markOutTime !== null) {
      const x1 = (sourceToDisplayTime(Math.min(markInTime, markOutTime), timelineSegments, dur) - scroll) * pxPerSec;
      const x2 = (sourceToDisplayTime(Math.max(markInTime, markOutTime), timelineSegments, dur) - scroll) * pxPerSec;
      ctx.fillStyle = 'rgba(250, 204, 21, 0.14)';
      ctx.fillRect(x1, waveTop, x2 - x1, waveH);
    }

    const drawMarkLine = (time: number, label: string) => {
      const x = (sourceToDisplayTime(time, timelineSegments, dur) - scroll) * pxPerSec;
      if (x < -4 || x > width + 4) return;
      ctx.strokeStyle = '#facc15';
      ctx.lineWidth = 1.5;
      ctx.beginPath();
      ctx.moveTo(x, 0);
      ctx.lineTo(x, height);
      ctx.stroke();
      ctx.fillStyle = '#facc15';
      ctx.font = '9px sans-serif';
      ctx.textBaseline = 'top';
      ctx.fillText(label, Math.min(width - 12, Math.max(2, x + 2)), 2);
      ctx.textBaseline = 'alphabetic';
    };

    if (markInTime !== null) drawMarkLine(markInTime, 'I');
    if (markOutTime !== null) drawMarkLine(markOutTime, 'O');

    // Draw timeline markers (colored pins)
    for (const marker of timelineMarkers) {
      const x = (sourceToDisplayTime(marker.time, timelineSegments, dur) - scroll) * pxPerSec;
      if (x < -4 || x > width + 4) continue;
      // Draw a pin triangle
      ctx.beginPath();
      ctx.moveTo(x, waveTop - 8);
      ctx.lineTo(x - 4, waveTop - 2);
      ctx.lineTo(x + 4, waveTop - 2);
      ctx.closePath();
      ctx.fillStyle = marker.color;
      ctx.fill();
      ctx.strokeStyle = marker.color;
      ctx.lineWidth = 1;
      // Draw a thin vertical line
      ctx.beginPath();
      ctx.moveTo(x, waveTop);
      ctx.lineTo(x, waveTop + waveH);
      ctx.stroke();
      // Label
      ctx.fillStyle = marker.color;
      ctx.font = '9px sans-serif';
      ctx.textBaseline = 'bottom';
      ctx.fillText(marker.label, Math.min(width - 50, Math.max(2, x + 5)), waveTop - 2);
      ctx.textBaseline = 'alphabetic';
    }

    const mid = waveTop + waveH / 2;
    ctx.beginPath();
    ctx.strokeStyle = '#4a4d5e';
    ctx.lineWidth = 1;

    for (let x = 0; x < width; x++) {
      const tStart = displayToSourceTime(scroll + x / pxPerSec, timelineSegments, timelineDur, dur);
      const tEnd = displayToSourceTime(scroll + (x + 1) / pxPerSec, timelineSegments, timelineDur, dur);
      const sStart = Math.floor(tStart * sampleRate);
      const sEnd = Math.min(Math.ceil(tEnd * sampleRate), channelData.length);
      if (sStart >= channelData.length) break;

      let min = 0, max = 0;
      for (let i = sStart; i < sEnd; i++) {
        if (channelData[i] < min) min = channelData[i];
        if (channelData[i] > max) max = channelData[i];
      }

      const amp = (waveH / 2) * 0.9;
      ctx.moveTo(x, mid + min * amp);
      ctx.lineTo(x, mid + max * amp);
    }
    ctx.stroke();
  }, [
    cutRanges,
    muteRanges,
    gainRanges,
    speedRanges,
    selectionStart,
    selectionEnd,
    cutMode,
    muteMode,
    gainMode,
    speedMode,
    selectedZone,
    showAdjustedTimeline,
    markInTime,
    markOutTime,
    displayDuration,
    showCutZones,
    showMuteZones,
    showGainZones,
    showSpeedZones,
    timelineSegments,
  ]);

  // Keep the ref in sync with the latest drawStaticWaveform closure
  useEffect(() => {
    drawStaticWaveformRef.current = drawStaticWaveform;
  }, [drawStaticWaveform]);

  // Redraw static layer when cutRanges change
  useEffect(() => {
    drawStaticWaveform();
  }, [drawStaticWaveform]);

  // Lightweight RAF loop for playhead only -- reads video.currentTime directly,
  // never triggers React re-renders
  useEffect(() => {
    const headCanvas = headCanvasRef.current;
    const waveCanvas = waveCanvasRef.current;
    if (!headCanvas || !waveCanvas) return;

    const tick = () => {
      const ctx = headCanvas.getContext('2d');
      if (!ctx) { rafRef.current = requestAnimationFrame(tick); return; }

      const video = document.querySelector('video') as HTMLVideoElement | null;
      const dur = waveformDataRef.current?.duration ?? 0;
      const timelineDur = displayDuration || dur;

      const dpr = window.devicePixelRatio || 1;
      const rect = headCanvas.getBoundingClientRect();
      if (headCanvas.width !== waveCanvas.width || headCanvas.height !== waveCanvas.height) {
        headCanvas.width = rect.width * dpr;
        headCanvas.height = rect.height * dpr;
      }
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);

      const width = rect.width;
      const height = rect.height;
      ctx.clearRect(0, 0, width, height);

      if (dur > 0 && video) {
        const pxPerSec = (width * zoomRef.current) / timelineDur;
        const displayTime = sourceToDisplayTime(video.currentTime, timelineSegments, dur);
        let px = (displayTime - scrollSecsRef.current) * pxPerSec;

        // If the playhead is off-screen (e.g. after a seek from the transcript),
        // scroll so it's centered and redraw the static waveform layer.
        if (px < 0 || px > width) {
          const visibleSecs = width / pxPerSec;
          const maxScroll = Math.max(0, timelineDur - visibleSecs);
          scrollSecsRef.current = Math.max(0, Math.min(maxScroll, displayTime - visibleSecs / 2));
          drawStaticWaveformRef.current();
          px = (displayTime - scrollSecsRef.current) * pxPerSec;
        }

        ctx.beginPath();
        ctx.strokeStyle = '#6366f1';
        ctx.lineWidth = 2;
        ctx.moveTo(px, 0);
        ctx.lineTo(px, height);
        ctx.stroke();
      }

      rafRef.current = requestAnimationFrame(tick);
    };

    rafRef.current = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(rafRef.current);
  }, [videoUrl, displayDuration, timelineSegments]);

  useEffect(() => {
    const observer = new ResizeObserver(() => {
      drawStaticWaveform();
    });
    if (containerRef.current) observer.observe(containerRef.current);
    return () => observer.disconnect();
  }, [drawStaticWaveform]);

  const handleWheel = useCallback((e: React.WheelEvent) => {
    e.preventDefault();
    const canvas = waveCanvasRef.current;
    if (!canvas) return;
    const dur = waveformDataRef.current?.duration;
    if (!dur) return;
    const width = canvas.getBoundingClientRect().width;
    const timelineDur = displayDuration || dur;

    if (e.ctrlKey || e.metaKey) {
      // Zoom around the cursor position
      const mouseX = e.clientX - canvas.getBoundingClientRect().left;
      const pxPerSecBefore = (width * zoomRef.current) / timelineDur;
      const timeCursor = scrollSecsRef.current + mouseX / pxPerSecBefore;
      const factor = e.deltaY < 0 ? 1.25 : 1 / 1.25;
      zoomRef.current = Math.max(1, Math.min(100, zoomRef.current * factor));
      const pxPerSecAfter = (width * zoomRef.current) / timelineDur;
      scrollSecsRef.current = timeCursor - mouseX / pxPerSecAfter;
    } else {
      // Scroll horizontally
      const pxPerSec = (width * zoomRef.current) / timelineDur;
      scrollSecsRef.current += (e.deltaY || e.deltaX) / pxPerSec * 1.5;
    }

    // Clamp scroll
    const pxPerSec = (width * zoomRef.current) / timelineDur;
    const maxScroll = Math.max(0, timelineDur - width / pxPerSec);
    scrollSecsRef.current = Math.max(0, Math.min(scrollSecsRef.current, maxScroll));
    drawStaticWaveform();
  }, [displayDuration, drawStaticWaveform]);

  const seekToClientX = useCallback((clientX: number) => {
    const canvas = headCanvasRef.current;
    const dur = waveformDataRef.current?.duration;
    if (!canvas || !dur) return;
    const rect = canvas.getBoundingClientRect();
    const x = clientX - rect.left;
    const timelineDur = displayDuration || dur;
    const pxPerSec = (rect.width * zoomRef.current) / timelineDur;
    const displayTime = Math.max(0, Math.min(timelineDur, scrollSecsRef.current + x / pxPerSec));
    const newTime = displayToSourceTime(displayTime, timelineSegments, timelineDur, dur);
    setCurrentTime(newTime);
    const video = document.querySelector('video') as HTMLVideoElement | null;
    if (video) video.currentTime = newTime;
  }, [displayDuration, setCurrentTime, timelineSegments]);

  const clientXToTime = useCallback((clientX: number): number => {
    const canvas = headCanvasRef.current;
    const dur = waveformDataRef.current?.duration;
    if (!canvas || !dur) return 0;
    const rect = canvas.getBoundingClientRect();
    const x = clientX - rect.left;
    const timelineDur = displayDuration || dur;
    const pxPerSec = (rect.width * zoomRef.current) / timelineDur;
    const displayTime = Math.max(0, Math.min(timelineDur, scrollSecsRef.current + x / pxPerSec));
    return displayToSourceTime(displayTime, timelineSegments, timelineDur, dur);
  }, [displayDuration, timelineSegments]);

  const getZoneAtPosition = useCallback((clientX: number, clientY: number, forHover: boolean = false) => {
    const dur = waveformDataRef.current?.duration;
    const canvas = waveCanvasRef.current;
    if (!canvas || !dur) return null;

    const rect = canvas.getBoundingClientRect();
    const x = clientX - rect.left;
    const y = clientY - rect.top;
    const timelineDur = displayDuration || dur;
    const pxPerSec = (rect.width * zoomRef.current) / timelineDur;
    const scroll = scrollSecsRef.current;
    const waveTop = RULER_H + 1;
    const waveH = canvas.height - waveTop;

    // Check if click is in waveform area
    if (y < waveTop || y > waveTop + waveH) return null;

    const handleSize = forHover ? 6 : 8; // Smaller hit area for hover, larger for click

    // Check cut ranges
    for (const range of showCutZones ? cutRanges : []) {
      const rangeX1 = (sourceToDisplayTime(range.start, timelineSegments, dur) - scroll) * pxPerSec;
      const rangeX2 = (sourceToDisplayTime(range.end, timelineSegments, dur) - scroll) * pxPerSec;
      const isSelected = selectedZone?.type === 'cut' && selectedZone.id === range.id;

      if (forHover && isSelected) {
        // For hover on selected zones, check edges
        if (Math.abs(x - rangeX1) <= handleSize) {
          return { type: 'cut' as const, id: range.id, edge: 'start' as const };
        }
        if (Math.abs(x - rangeX2) <= handleSize) {
          return { type: 'cut' as const, id: range.id, edge: 'end' as const };
        }
      } else if (!forHover) {
        // For click detection, check handles and body
        if (isSelected) {
          // For selected zones, allow clicking on edges for resizing
          if (Math.abs(x - rangeX1) <= handleSize) {
            return { type: 'cut' as const, id: range.id, edge: 'start' as const };
          }
          if (Math.abs(x - rangeX2) <= handleSize) {
            return { type: 'cut' as const, id: range.id, edge: 'end' as const };
          }
        } else {
          // For unselected zones, check small handle circles
          // Check start handle
          if (Math.abs(x - rangeX1) <= handleSize && Math.abs(y - (waveTop + waveH / 2)) <= handleSize) {
            return { type: 'cut' as const, id: range.id, edge: 'start' as const };
          }
          // Check end handle
          if (Math.abs(x - rangeX2) <= handleSize && Math.abs(y - (waveTop + waveH / 2)) <= handleSize) {
            return { type: 'cut' as const, id: range.id, edge: 'end' as const };
          }
        }
        // Check range body
        if (x >= rangeX1 && x <= rangeX2) {
          return { type: 'cut' as const, id: range.id, edge: 'move' as const };
        }
      }
    }

    // Check mute ranges
    for (const range of showMuteZones ? muteRanges : []) {
      const rangeX1 = (sourceToDisplayTime(range.start, timelineSegments, dur) - scroll) * pxPerSec;
      const rangeX2 = (sourceToDisplayTime(range.end, timelineSegments, dur) - scroll) * pxPerSec;
      const isSelected = selectedZone?.type === 'mute' && selectedZone.id === range.id;

      if (forHover && isSelected) {
        // For hover on selected zones, check edges
        if (Math.abs(x - rangeX1) <= handleSize) {
          return { type: 'mute' as const, id: range.id, edge: 'start' as const };
        }
        if (Math.abs(x - rangeX2) <= handleSize) {
          return { type: 'mute' as const, id: range.id, edge: 'end' as const };
        }
      } else if (!forHover) {
        // For click detection, check handles and body
        if (isSelected) {
          // For selected zones, allow clicking on edges for resizing
          if (Math.abs(x - rangeX1) <= handleSize) {
            return { type: 'mute' as const, id: range.id, edge: 'start' as const };
          }
          if (Math.abs(x - rangeX2) <= handleSize) {
            return { type: 'mute' as const, id: range.id, edge: 'end' as const };
          }
        } else {
          // For unselected zones, check small handle circles
          // Check start handle
          if (Math.abs(x - rangeX1) <= handleSize && Math.abs(y - (waveTop + waveH / 2)) <= handleSize) {
            return { type: 'mute' as const, id: range.id, edge: 'start' as const };
          }
          // Check end handle
          if (Math.abs(x - rangeX2) <= handleSize && Math.abs(y - (waveTop + waveH / 2)) <= handleSize) {
            return { type: 'mute' as const, id: range.id, edge: 'end' as const };
          }
        }
        // Check range body
        if (x >= rangeX1 && x <= rangeX2) {
          return { type: 'mute' as const, id: range.id, edge: 'move' as const };
        }
      }
    }

    // Check gain ranges
    for (const range of showGainZones ? gainRanges : []) {
      const rangeX1 = (sourceToDisplayTime(range.start, timelineSegments, dur) - scroll) * pxPerSec;
      const rangeX2 = (sourceToDisplayTime(range.end, timelineSegments, dur) - scroll) * pxPerSec;
      const isSelected = selectedZone?.type === 'gain' && selectedZone.id === range.id;

      if (forHover && isSelected) {
        if (Math.abs(x - rangeX1) <= handleSize) {
          return { type: 'gain' as const, id: range.id, edge: 'start' as const };
        }
        if (Math.abs(x - rangeX2) <= handleSize) {
          return { type: 'gain' as const, id: range.id, edge: 'end' as const };
        }
      } else if (!forHover) {
        if (isSelected) {
          if (Math.abs(x - rangeX1) <= handleSize) {
            return { type: 'gain' as const, id: range.id, edge: 'start' as const };
          }
          if (Math.abs(x - rangeX2) <= handleSize) {
            return { type: 'gain' as const, id: range.id, edge: 'end' as const };
          }
        } else {
          if (Math.abs(x - rangeX1) <= handleSize && Math.abs(y - (waveTop + waveH / 2)) <= handleSize) {
            return { type: 'gain' as const, id: range.id, edge: 'start' as const };
          }
          if (Math.abs(x - rangeX2) <= handleSize && Math.abs(y - (waveTop + waveH / 2)) <= handleSize) {
            return { type: 'gain' as const, id: range.id, edge: 'end' as const };
          }
        }
        if (x >= rangeX1 && x <= rangeX2) {
          return { type: 'gain' as const, id: range.id, edge: 'move' as const };
        }
      }
    }

    // Check speed ranges
    for (const range of showSpeedZones ? speedRanges : []) {
      const rangeX1 = (sourceToDisplayTime(range.start, timelineSegments, dur) - scroll) * pxPerSec;
      const rangeX2 = (sourceToDisplayTime(range.end, timelineSegments, dur) - scroll) * pxPerSec;
      const isSelected = selectedZone?.type === 'speed' && selectedZone.id === range.id;

      if (forHover && isSelected) {
        if (Math.abs(x - rangeX1) <= handleSize) {
          return { type: 'speed' as const, id: range.id, edge: 'start' as const };
        }
        if (Math.abs(x - rangeX2) <= handleSize) {
          return { type: 'speed' as const, id: range.id, edge: 'end' as const };
        }
      } else if (!forHover) {
        if (isSelected) {
          if (Math.abs(x - rangeX1) <= handleSize) {
            return { type: 'speed' as const, id: range.id, edge: 'start' as const };
          }
          if (Math.abs(x - rangeX2) <= handleSize) {
            return { type: 'speed' as const, id: range.id, edge: 'end' as const };
          }
        } else {
          if (Math.abs(x - rangeX1) <= handleSize && Math.abs(y - (waveTop + waveH / 2)) <= handleSize) {
            return { type: 'speed' as const, id: range.id, edge: 'start' as const };
          }
          if (Math.abs(x - rangeX2) <= handleSize && Math.abs(y - (waveTop + waveH / 2)) <= handleSize) {
            return { type: 'speed' as const, id: range.id, edge: 'end' as const };
          }
        }
        if (x >= rangeX1 && x <= rangeX2) {
          return { type: 'speed' as const, id: range.id, edge: 'move' as const };
        }
      }
    }

    return null;
  }, [cutRanges, muteRanges, gainRanges, speedRanges, selectedZone, showCutZones, showMuteZones, showGainZones, showSpeedZones, displayDuration, timelineSegments]);

  const handleMouseMove = useCallback((e: React.MouseEvent<HTMLCanvasElement>) => {
    if (isDragging) return; // Don't change cursor while dragging

    const zoneHit = getZoneAtPosition(e.clientX, e.clientY, true);
    if (zoneHit && selectedZone && zoneHit.id === selectedZone.id) {
      if (zoneHit.edge === 'start' || zoneHit.edge === 'end') {
        setHoverCursor('ew-resize');
        return;
      }
    }
    setHoverCursor('crosshair');
  }, [isDragging, getZoneAtPosition, selectedZone]);

  const handleMouseDown = useCallback(
    (e: React.MouseEvent<HTMLCanvasElement>) => {
      e.preventDefault();
      
      // Check if clicking on a zone
      const zoneHit = getZoneAtPosition(e.clientX, e.clientY);
      if (zoneHit) {
        if (zoneHit.edge === 'move') {
          setSelectedZone({ type: zoneHit.type, id: zoneHit.id });
        } else {
          setSelectedZone({ type: zoneHit.type, id: zoneHit.id });
          editingZoneRef.current = zoneHit;
        }
        isDraggingRef.current = true;
        setIsDragging(true);

        const startTime = clientXToTime(e.clientX);
        const originalRange = zoneHit.type === 'cut'
          ? cutRanges.find(r => r.id === zoneHit.id)
          : zoneHit.type === 'mute'
            ? muteRanges.find(r => r.id === zoneHit.id)
            : zoneHit.type === 'gain'
              ? gainRanges.find(r => r.id === zoneHit.id)
              : speedRanges.find(r => r.id === zoneHit.id);
        
        if (!originalRange) return;

        const onMove = (ev: MouseEvent) => {
          if (!isDraggingRef.current || !editingZoneRef.current) return;
          const currentTime = clientXToTime(ev.clientX);
          const delta = currentTime - startTime;
          const minZoneDuration = 0.05;
          
          let newStart = originalRange.start;
          let newEnd = originalRange.end;
          
          if (editingZoneRef.current.edge === 'start') {
            newStart = Math.max(0, Math.min(originalRange.end - minZoneDuration, originalRange.start + delta));
            newEnd = originalRange.end; // Keep end fixed when dragging start
          } else if (editingZoneRef.current.edge === 'end') {
            newStart = originalRange.start; // Keep start fixed when dragging end
            newEnd = Math.min(duration, Math.max(newStart + minZoneDuration, originalRange.end + delta));
          } else if (editingZoneRef.current.edge === 'move') {
            const zoneDuration = originalRange.end - originalRange.start;
            const maxStart = Math.max(0, duration - zoneDuration);
            newStart = Math.max(0, Math.min(maxStart, originalRange.start + delta));
            newEnd = newStart + zoneDuration;
          }
          
          // Ensure valid range
          if (newStart < newEnd) {
            if (editingZoneRef.current.type === 'cut') {
              updateCutRange(editingZoneRef.current.id, newStart, newEnd);
            } else if (editingZoneRef.current.type === 'mute') {
              updateMuteRange(editingZoneRef.current.id, newStart, newEnd);
            } else if (editingZoneRef.current.type === 'gain') {
              updateGainRangeBounds(editingZoneRef.current.id, newStart, newEnd);
            } else {
              updateSpeedRangeBounds(editingZoneRef.current.id, newStart, newEnd);
            }
          }
        };
        
        const onUp = () => {
          isDraggingRef.current = false;
          setIsDragging(false);
          editingZoneRef.current = null;
          window.removeEventListener('mousemove', onMove);
          window.removeEventListener('mouseup', onUp);
        };
        
        window.addEventListener('mousemove', onMove);
        window.addEventListener('mouseup', onUp);
        return;
      }

      // Clear selection if clicking elsewhere
      setSelectedZone(null);
      
      if (cutMode || muteMode || gainMode || speedMode) {
        // Range selection mode
        const startTime = clientXToTime(e.clientX);
        selectionStartRef.current = startTime;
        selectionEndRef.current = startTime;
        setSelectionStart(startTime);
        setSelectionEnd(startTime);
        isDraggingRef.current = true;
        setIsDragging(true);

        const onMove = (ev: MouseEvent) => {
          if (!isDraggingRef.current) return;
          const currentTime = clientXToTime(ev.clientX);
          selectionEndRef.current = currentTime;
          setSelectionEnd(currentTime);
        };
        
        const onUp = () => {
          isDraggingRef.current = false;
          setIsDragging(false);
          
          if (selectionStartRef.current !== null && selectionEndRef.current !== null) {
            const start = Math.min(selectionStartRef.current, selectionEndRef.current);
            const end = Math.max(selectionStartRef.current, selectionEndRef.current);
            const minDuration = 0.01;
            
            if (end - start >= minDuration && cutMode) {
              addCutRange(start, end);
            } else if (end - start >= minDuration && muteMode) {
              addMuteRange(start, end);
            } else if (end - start >= minDuration && gainMode) {
              addGainRange(start, end, gainModeDb);
            } else if (end - start >= minDuration && speedMode) {
              addSpeedRange(start, end, speedModeValue);
            }
          }
          
          // Reset selection
          selectionStartRef.current = null;
          selectionEndRef.current = null;
          setSelectionStart(null);
          setSelectionEnd(null);
          
          window.removeEventListener('mousemove', onMove);
          window.removeEventListener('mouseup', onUp);
        };
        
        window.addEventListener('mousemove', onMove);
        window.addEventListener('mouseup', onUp);
      } else {
        // Normal seek mode
        isDraggingRef.current = true;
        setIsDragging(true);
        seekToClientX(e.clientX);

        const onMove = (ev: MouseEvent) => {
          if (!isDraggingRef.current) return;
          seekToClientX(ev.clientX);
        };
        const onUp = () => {
          isDraggingRef.current = false;
          setIsDragging(false);
          window.removeEventListener('mousemove', onMove);
          window.removeEventListener('mouseup', onUp);
        };
        window.addEventListener('mousemove', onMove);
        window.addEventListener('mouseup', onUp);
      }
    },
    [cutMode, muteMode, gainMode, gainModeDb, speedMode, speedModeValue, clientXToTime, seekToClientX, addCutRange, addMuteRange, addGainRange, addSpeedRange, getZoneAtPosition, cutRanges, muteRanges, gainRanges, speedRanges, duration, updateCutRange, updateMuteRange, updateGainRangeBounds, updateSpeedRangeBounds],
  );

  // Handle keyboard shortcuts for zone editing
  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      const target = e.target as HTMLElement | null;
      if (target && (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.tagName === 'SELECT')) {
        return;
      }

      if (e.key === 'Escape') {
        setSelectedZone(null);
        editingZoneRef.current = null;
      } else if (e.key === 'Delete' || e.key === 'Backspace') {
        if (selectedZone) {
          e.preventDefault();
          e.stopPropagation();
          e.stopImmediatePropagation();
          if (selectedZone.type === 'cut') {
            removeCutRange(selectedZone.id);
          } else if (selectedZone.type === 'mute') {
            removeMuteRange(selectedZone.id);
          } else if (selectedZone.type === 'gain') {
            removeGainRange(selectedZone.id);
          } else {
            removeSpeedRange(selectedZone.id);
          }
          setSelectedZone(null);
          editingZoneRef.current = null;
        }
      }
    };

    // Capture phase ensures zone delete runs before app-level bubble shortcuts.
    window.addEventListener('keydown', handleKeyDown, { capture: true });
    return () => window.removeEventListener('keydown', handleKeyDown, { capture: true });
  }, [selectedZone, removeCutRange, removeMuteRange, removeGainRange, removeSpeedRange]);

  useEffect(() => {
    if (!selectedZone) return;
    if (selectedZone.type === 'cut' && !showCutZones) setSelectedZone(null);
    if (selectedZone.type === 'mute' && !showMuteZones) setSelectedZone(null);
    if (selectedZone.type === 'gain' && !showGainZones) setSelectedZone(null);
    if (selectedZone.type === 'speed' && !showSpeedZones) setSelectedZone(null);
  }, [selectedZone, showCutZones, showMuteZones, showGainZones, showSpeedZones]);

  // Capture thumbnail frames when enabled
  useEffect(() => {
    if (!showThumbnails) { setThumbnailFrames(new Map()); return; }
    const dur = displayDuration || waveformDataRef.current?.duration || 0;
    if (dur <= 0) return;
    const video = document.querySelector('video') as HTMLVideoElement | null;
    if (!video) return;

    const interval = 10;
    const times: number[] = [];
    for (let t = 0; t < dur; t += interval) times.push(t);

    let cancelled = false;
    extractThumbnails(video, times).then((frames) => {
      if (!cancelled) setThumbnailFrames(frames);
    });

    return () => { cancelled = true; };
  }, [showThumbnails, videoUrl, displayDuration]);

  if (!videoUrl) {
    return (
      <div className="w-full h-full flex items-center justify-center text-editor-text-muted text-xs">
        Load a video to see the waveform
      </div>
    );
  }

  return (
    <div ref={containerRef} className="w-full h-full flex flex-col">
      <div className="flex items-center justify-between px-3 py-1 shrink-0 gap-3">
        <div className="flex items-center gap-2">
          <span className="text-[10px] text-editor-text-muted font-medium uppercase tracking-wider">
            Timeline
          </span>
          {cutMode && <span className="text-[10px] text-red-400">Cut mode</span>}
          {muteMode && <span className="text-[10px] text-blue-400">Mute mode</span>}
          {gainMode && <span className="text-[10px] text-amber-400">Gain mode ({gainModeDb.toFixed(1)} dB)</span>}
          {speedMode && <span className="text-[10px] text-emerald-400">Speed mode ({speedModeValue.toFixed(2)}x)</span>}
          {markInTime !== null && <span className="text-[10px] text-yellow-300">I {markInTime.toFixed(2)}s</span>}
          {markOutTime !== null && <span className="text-[10px] text-yellow-300">O {markOutTime.toFixed(2)}s</span>}
        </div>
        <div className="flex items-center gap-2">
          <label className="flex items-center gap-1 text-[10px] text-editor-text-muted select-none">
            <input
              type="checkbox"
              checked={showAdjustedTimeline}
              onChange={(e) => setShowAdjustedTimeline(e.target.checked)}
              className="h-3 w-3 rounded border-editor-border bg-editor-surface"
            />
            Show adjusted timeline
          </label>
          <div className="flex items-center gap-2 pl-2 ml-1 border-l border-editor-border/80">
            <span className="text-[10px] text-editor-text-muted">Show zones</span>
            <button
              onClick={() => setShowCutZones((v) => !v)}
              className={`px-1.5 py-0.5 rounded text-[10px] border ${showCutZones ? 'border-red-500/60 text-red-300 bg-red-500/10' : 'border-editor-border text-editor-text-muted'}`}
              title="Toggle cut zones"
            >
              Cut
            </button>
            <button
              onClick={() => setShowMuteZones((v) => !v)}
              className={`px-1.5 py-0.5 rounded text-[10px] border ${showMuteZones ? 'border-blue-500/60 text-blue-300 bg-blue-500/10' : 'border-editor-border text-editor-text-muted'}`}
              title="Toggle mute zones"
            >
              Mute
            </button>
            <button
              onClick={() => setShowGainZones((v) => !v)}
              className={`px-1.5 py-0.5 rounded text-[10px] border ${showGainZones ? 'border-amber-500/60 text-amber-300 bg-amber-500/10' : 'border-editor-border text-editor-text-muted'}`}
              title="Toggle gain zones"
            >
              Gain
            </button>
            <button
              onClick={() => setShowSpeedZones((v) => !v)}
              className={`px-1.5 py-0.5 rounded text-[10px] border ${showSpeedZones ? 'border-emerald-500/60 text-emerald-300 bg-emerald-500/10' : 'border-editor-border text-editor-text-muted'}`}
              title="Toggle speed zones"
            >
              Speed
            </button>
          </div>
          <span className="text-[10px] text-editor-text-muted pl-2 ml-1 border-l border-editor-border/80">
            Scroll · Ctrl+Scroll to zoom
          </span>
        </div>
      </div>
      {audioError ? (
        <div className="flex-1 flex items-start justify-center gap-2 text-editor-text-muted text-xs p-3 overflow-auto">
          <AlertTriangle className="w-4 h-4 text-yellow-500 mt-0.5 shrink-0" />
          <pre
            className="select-text cursor-text whitespace-pre-wrap break-all leading-relaxed"
            title="Highlight this text to copy"
          >
            {audioError}
          </pre>
        </div>
      ) : (
        <div className="flex-1 relative flex flex-col">
          {showThumbnails && thumbnailFrames.size > 0 && (
            <div
              ref={thumbnailContainerRef}
              className="h-14 shrink-0 overflow-x-auto border-b border-editor-border/60"
              style={{ scrollbarWidth: 'thin' }}
            >
              <div className="relative h-full" style={{ width: '100%', minHeight: 0 }}>
                {Array.from(thumbnailFrames.entries()).map(([time, dataUrl]) => {
                  const dur = displayDuration || waveformDataRef.current?.duration || 1;
                  const pct = (time / dur) * 100;
                  return (
                    <img
                      key={time}
                      src={dataUrl}
                      alt={`Thumbnail at ${time.toFixed(0)}s`}
                      className="absolute top-1 rounded border border-editor-border/40 object-cover cursor-pointer"
                      style={{ left: `${pct}%`, width: 80, height: 45, transform: 'translateX(-50%)' }}
                      title={`${time.toFixed(0)}s`}
                      onClick={() => {
                        const video = document.querySelector('video') as HTMLVideoElement | null;
                        if (video) { video.currentTime = time; setCurrentTime(time); }
                      }}
                    />
                  );
                })}
              </div>
            </div>
          )}
          <div className="flex-1 relative">
            <canvas ref={waveCanvasRef} className="absolute inset-0 w-full h-full" />
            <canvas
              ref={headCanvasRef}
              className="absolute inset-0 w-full h-full"
              style={{ cursor: isDragging ? 'grabbing' : hoverCursor }}
              onMouseDown={handleMouseDown}
              onMouseMove={handleMouseMove}
              onWheel={handleWheel}
            />
          </div>
        </div>
      )}
    </div>
  );
}
