import { useState, useEffect, useRef, useMemo, useCallback } from 'react';

type ApiError = { response?: { data?: { detail?: string } } };
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useParams, useNavigate } from 'react-router-dom';
import { useJob, useApproveEnglish, useRejectJob, useJobVttContent, useUpdateJobVtt, useJobDownloads, useAdjustVttTiming, useUpdateTTSPreferences, useUpdateJob } from '../../hooks/useJob';
import {
  useAccessibleVideoEditState,
  useUpdatePausePoint,
  useQueueTTSRegeneration,
  useRemoveTTSRegeneration,
  useRerenderAccessibleVideo,
} from '../../hooks/useAccessibleVideoEdit';
import { StatusBadge } from '../../components/StatusBadge';
import { VttEditor } from '../../components/VttEditor/VttEditor';
import { VttDiffView } from '../../components/VttEditor/VttDiffView';
import { VideoWithCaptions } from '../../components/VideoWithCaptions';
import { VoiceSelector } from '../../components/VoiceSelector';
import { TimelinePreview } from '../../components/TimelinePreview';
import { RerenderControls } from '../../components/RerenderControls';
import { useToastContext } from '../../contexts/ToastContext';
import { useAuthStore } from '../../lib/auth';
import { apiClient } from '../../lib/api';
import type { TTSPreferences, VideoSegmentMetadata, PausePointData, LanguageQCStatus, LanguageQCComment } from '../../types/api';
import { useJobStatusWebSocket } from '../../hooks/useJobStatusWebSocket';
import type { WebSocketMessage } from '../../hooks/useJobStatusWebSocket';
import { VTTParser } from '../../lib/vtt';

// ── Status display helpers ────────────────────────────────────────────────────

const LANG_QC_BADGE: Record<LanguageQCStatus, string> = {
  pending: 'bg-gray-100 text-gray-500',
  in_progress: 'bg-yellow-100 text-yellow-700',
  pending_review: 'bg-orange-100 text-orange-700',
  in_review: 'bg-blue-100 text-blue-700',
  approved: 'bg-green-100 text-green-700',
  rejected: 'bg-red-100 text-red-700',
};
const LANG_QC_LABEL: Record<LanguageQCStatus, string> = {
  pending: 'Pending', in_progress: 'In progress', pending_review: 'Pending review',
  in_review: 'In review', approved: 'Approved', rejected: 'Rejected',
};
const LANG_QC_ICON: Record<LanguageQCStatus, string> = {
  pending: '⏳', in_progress: '✏️', pending_review: '📤', in_review: '🔍', approved: '✓', rejected: '✕',
};

export function QCDetail() {
  const { id } = useParams<{ id: string }>();
  const navigate = useNavigate();
  const toast = useToastContext();
  const { user: authUser } = useAuthStore();
  const { data: job, isLoading, error } = useJob(id!);
  // Get source language from job (default to 'en' for backwards compatibility)
  const sourceLanguage = job?.source?.language || 'en';

  // All languages for QC: source always first, then requested targets
  const availableLanguages = [
    sourceLanguage,
    ...((job?.requested_outputs?.languages ?? []).filter(l => l !== sourceLanguage)),
  ].filter((l, i, a) => a.indexOf(l) === i);

  // Language selection for QC review
  const [selectedLanguage, setSelectedLanguage] = useState<string>(sourceLanguage);

  // Video mode: original video or accessible video with AD
  const [videoMode, setVideoMode] = useState<'original' | 'accessible'>('original');

  // Track video current time for timeline
  const [currentVideoTimeMs, setCurrentVideoTimeMs] = useState(0);
  const videoRef = useRef<HTMLVideoElement | null>(null);

  // Local state for pending regenerations (cleared on successful re-render)
  const [pendingRegenerations, setPendingRegenerations] = useState<number[]>([]);
  const [pausePointsModified, setPausePointsModified] = useState(false);

  // Track AD cue insertions to queue TTS for shifted cues
  const [lastInsertedAdCue, setLastInsertedAdCue] = useState<{
    insertIndex: number;
    totalCues: number;
  } | null>(null);

  // Track AD cue deletions to queue TTS for shifted cues
  const [lastDeletedAdCue, setLastDeletedAdCue] = useState<{
    deletedIndex: number;
    totalCuesAfterDelete: number;
  } | null>(null);


  // Fetch VTT content for selected language
  const { data: vttContent, isLoading: vttLoading } = useJobVttContent(id!, selectedLanguage);
  const { data: downloads } = useJobDownloads(id!, job?.status);

  // Fetch accessible video edit state for selected language
  const { data: editState, isLoading: editStateLoading } = useAccessibleVideoEditState(id!, selectedLanguage);

  // Mutations for accessible video editing
  const updatePausePointMutation = useUpdatePausePoint();
  const queueTTSRegenerationMutation = useQueueTTSRegeneration();
  const removeTTSRegenerationMutation = useRemoveTTSRegeneration();
  const rerenderMutation = useRerenderAccessibleVideo();

  const approveEnglishMutation = useApproveEnglish();
  const rejectJobMutation = useRejectJob();
  const updateVttMutation = useUpdateJobVtt();
  const adjustTimingMutation = useAdjustVttTiming();
  const updateTTSPreferencesMutation = useUpdateTTSPreferences();
  const updateJobMutation = useUpdateJob();

  const [reviewNotes, setReviewNotes] = useState('');
  const [costProjectId, setCostProjectId] = useState('');
  const [adInsertAtTimeMs, setAdInsertAtTimeMs] = useState<number | null>(null);

  // Per-language QC state
  const { data: langQcData, refetch: refetchLangQc } = useQuery({
    queryKey: ['language-qc', id],
    queryFn: () => apiClient.getLanguageQC(id!),
    enabled: !!id && !!job,
    refetchInterval: 15_000,
  });
  const langQcMap = langQcData?.language_qc ?? {};
  const isSourceApproved = (langQcMap[sourceLanguage]?.status ?? 'pending') === 'approved';

  // Glossary terms for inline highlighting
  const clientId = job?.client_id;
  const { data: glossariesData } = useQuery({
    queryKey: ['client-glossaries', clientId],
    queryFn: () => apiClient.getGlossaries(clientId!),
    enabled: !!clientId,
    staleTime: 60_000,
  });
  const activeGlossaryId = glossariesData?.find(g => g.current_version_id)?.id;
  const { data: glossaryTermsData } = useQuery({
    queryKey: ['glossary-terms', clientId, activeGlossaryId],
    queryFn: () => apiClient.getGlossaryTerms(clientId!, activeGlossaryId!, { pageSize: 500 }),
    enabled: !!clientId && !!activeGlossaryId,
    staleTime: 120_000,
  });
  const glossaryTerms = glossaryTermsData?.terms ?? [];

  const [showLangRejectModal, setShowLangRejectModal] = useState(false);
  const [langRejectNotes, setLangRejectNotes] = useState('');
  const [langRejectCategory, setLangRejectCategory] = useState('');

  // Unified assign modal state — slot: 'linguist' | 'reviewer'
  const [showAssignModal, setShowAssignModal] = useState(false);
  const [assignLanguage, setAssignLanguage] = useState('');
  const [assignSlot, setAssignSlot] = useState<'linguist' | 'reviewer'>('linguist');
  const [assigningUserId, setAssigningUserId] = useState('');
  const [assignDeadline, setAssignDeadline] = useState('');

  // Bulk assign modal state
  const [showBulkAssignModal, setShowBulkAssignModal] = useState(false);
  const [bulkLinguistId, setBulkLinguistId] = useState('');
  const [bulkReviewerId, setBulkReviewerId] = useState('');
  const [bulkOnlyUnassigned, setBulkOnlyUnassigned] = useState(true);
  const [bulkDeadline, setBulkDeadline] = useState('');

  // Comments panel per language
  const [openCommentLang, setOpenCommentLang] = useState<string | null>(null);
  const [commentDraft, setCommentDraft] = useState('');

  // Per-language retranslate modal
  const [retranslateLang, setRetranslateLang] = useState<string | null>(null);
  const [retranslateReason, setRetranslateReason] = useState('');
  const [retranslateLoading, setRetranslateLoading] = useState(false);
  const [showBulkRetranslateConfirm, setShowBulkRetranslateConfirm] = useState(false);
  const [bulkRetranslateLoading, setBulkRetranslateLoading] = useState(false);

  const canAssign = authUser?.role === 'project_manager' || authUser?.role === 'production' || authUser?.role === 'admin';
  const canApproveAll = authUser?.role === 'production' || authUser?.role === 'admin';

  // Load users for assignment dropdown (linguists or reviewers depending on slot)
  const { data: assignableUsersData } = useQuery({
    queryKey: ['users-list', assignSlot],
    queryFn: () => apiClient.listUsers({ role: assignSlot === 'linguist' ? 'linguist,admin,production' : 'reviewer,admin,production', active_only: true, size: 100 }),
    enabled: showAssignModal,
  });
  const assignableUsers = assignableUsersData?.users ?? [];

  const { data: bulkLinguistsData } = useQuery({
    queryKey: ['users-list', 'linguist'],
    queryFn: () => apiClient.listUsers({ role: 'linguist,admin,production', active_only: true, size: 100 }),
    enabled: showBulkAssignModal,
  });
  const { data: bulkReviewersData } = useQuery({
    queryKey: ['users-list', 'reviewer'],
    queryFn: () => apiClient.listUsers({ role: 'reviewer,admin,production', active_only: true, size: 100 }),
    enabled: showBulkAssignModal,
  });
  const bulkLinguists = bulkLinguistsData?.users ?? [];
  const bulkReviewers = bulkReviewersData?.users ?? [];

  const approveLanguageMutation = useMutation({
    mutationFn: ({ lang, notes }: { lang: string; notes?: string }) =>
      apiClient.approveLanguageQC(id!, lang, notes),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['language-qc', id] });
      queryClient.invalidateQueries({ queryKey: ['job', id] });
      refetchLangQc();
      toast.toastOnly.success('Language approved');
    },
    onError: (e) => toast.toastOnly.error((e as ApiError)?.response?.data?.detail || 'Approval failed'),
  });

  const rejectLanguageMutation = useMutation({
    mutationFn: ({ lang, notes, category }: { lang: string; notes: string; category?: string }) =>
      apiClient.rejectLanguageQC(id!, lang, notes, category),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['language-qc', id] });
      queryClient.invalidateQueries({ queryKey: ['job', id] });
      setShowLangRejectModal(false);
      setLangRejectNotes('');
      setLangRejectCategory('');
      refetchLangQc();
      toast.toastOnly.success('Language sent back for changes');
    },
    onError: (e) => toast.toastOnly.error((e as ApiError)?.response?.data?.detail || 'Rejection failed'),
  });

  const assignMutation = useMutation({
    mutationFn: ({ lang, userId, slot, deadline }: { lang: string; userId: string; slot: 'linguist' | 'reviewer'; deadline?: string }) =>
      slot === 'linguist'
        ? apiClient.assignLanguageQC(id!, lang, userId, undefined, deadline || undefined)
        : apiClient.assignReviewerQC(id!, lang, userId, undefined, deadline || undefined),
    onSuccess: (_, vars) => {
      queryClient.invalidateQueries({ queryKey: ['language-qc', id] });
      refetchLangQc();
      setShowAssignModal(false);
      setAssigningUserId('');
      setAssignDeadline('');
      toast.toastOnly.success(`${vars.slot === 'linguist' ? 'Linguist' : 'Reviewer'} assigned`);
    },
    onError: (e) => toast.toastOnly.error((e as ApiError)?.response?.data?.detail || 'Assignment failed'),
  });

  const bulkAssignMutation = useMutation({
    mutationFn: () => apiClient.bulkAssignLanguages(id!, {
      linguist_user_id: bulkLinguistId,
      reviewer_user_id: bulkReviewerId || undefined,
      only_unassigned: bulkOnlyUnassigned,
      deadline: bulkDeadline || undefined,
    }),
    onSuccess: (result) => {
      queryClient.invalidateQueries({ queryKey: ['language-qc', id] });
      refetchLangQc();
      setShowBulkAssignModal(false);
      setBulkLinguistId('');
      setBulkReviewerId('');
      setBulkDeadline('');
      const errCount = Object.keys(result.errors).length;
      if (errCount > 0) {
        toast.toastOnly.error(`Assigned ${result.assigned.length} language(s); ${errCount} error(s)`);
      } else {
        toast.toastOnly.success(`Assigned ${result.assigned.length} language(s)${result.skipped.length ? `, skipped ${result.skipped.length}` : ''}`);
      }
    },
    onError: (e: unknown) => toast.toastOnly.error((e as { response?: { data?: { detail?: string } } })?.response?.data?.detail || 'Bulk assignment failed'),
  });

  // Share link state
  const [showShareModal, setShowShareModal] = useState(false);
  const [shareUrl, setShareUrl] = useState('');
  const [shareLabel, setShareLabel] = useState('');
  const [shareCopied, setShareCopied] = useState(false);

  const createShareTokenMutation = useMutation({
    mutationFn: () => apiClient.createShareToken(id!, { expires_in_days: 30, label: shareLabel || undefined }),
    onSuccess: (data) => {
      setShareUrl(data.share_url);
      setShareLabel('');
    },
    onError: (e: unknown) => toast.toastOnly.error((e as { response?: { data?: { detail?: string } } })?.response?.data?.detail || 'Failed to generate share link'),
  });

  const submitForReviewMutation = useMutation({
    mutationFn: (lang: string) => apiClient.submitForReview(id!, lang),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['language-qc', id] });
      refetchLangQc();
      toast.toastOnly.success('Submitted for review — reviewer notified');
    },
    onError: (e) => toast.toastOnly.error((e as ApiError)?.response?.data?.detail || 'Submit failed'),
  });

  const openReviewMutation = useMutation({
    mutationFn: (lang: string) => apiClient.openReview(id!, lang),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['language-qc', id] });
      refetchLangQc();
      toast.toastOnly.success('Review opened');
    },
    onError: (e) => toast.toastOnly.error((e as ApiError)?.response?.data?.detail || 'Open failed'),
  });

  const addCommentMutation = useMutation({
    mutationFn: ({ lang, body }: { lang: string; body: string }) => apiClient.addQCComment(id!, lang, body),
    onSuccess: (newComment, vars) => {
      queryClient.setQueryData(['qc-comments', id, vars.lang], (prev: LanguageQCComment[] | undefined) =>
        [...(prev ?? []), newComment],
      );
      setCommentDraft('');
      toast.toastOnly.success('Comment added');
    },
    onError: (e) => toast.toastOnly.error((e as ApiError)?.response?.data?.detail || 'Comment failed'),
  });

  // Total QC progress
  const totalLangs = availableLanguages.length;
  const approvedLangs = availableLanguages.filter(l => langQcMap[l]?.status === 'approved').length;
  const brokenLanguages = availableLanguages.filter(lang => {
    if (lang === sourceLanguage) return false;
    const out = job?.outputs?.[lang];
    return out?.origin === 'video_native' || !out?.captions_vtt_gcs;
  });
  const [costProjectIdSaved, setCostProjectIdSaved] = useState(false);
  const [showRejectForm, setShowRejectForm] = useState(false);
  const [captionsVtt, setCaptionsVtt] = useState('');
  const [retimedCaptionsVtt, setRetimedCaptionsVtt] = useState('');
  const [adVtt, setAdVtt] = useState('');

  // Accessible video captions: parse retimed (or fallback to original) cues
  const accessibleCaptionCues = useMemo(() => {
    const src = retimedCaptionsVtt || captionsVtt;
    if (!src) return [];
    try { return VTTParser.parse(src); } catch { return []; }
  }, [retimedCaptionsVtt, captionsVtt]);

  const currentAccessibleCaption = useMemo(() => {
    const timeSec = currentVideoTimeMs / 1000;
    return accessibleCaptionCues.find(c => timeSec >= c.startTime && timeSec <= c.endTime) || null;
  }, [accessibleCaptionCues, currentVideoTimeMs]);
  const [viewMode, setViewMode] = useState<'side-by-side' | 'video-only' | 'editor-only'>('side-by-side');
  const [showTimingAdjustment, setShowTimingAdjustment] = useState(false);
  const [timingOffset, setTimingOffset] = useState(0);
  const [adjustCaptions, setAdjustCaptions] = useState(true);
  const [adjustAudioDescription, setAdjustAudioDescription] = useState(true);
  const [showVoiceSettings, setShowVoiceSettings] = useState(false);
  const [showDownloads, setShowDownloads] = useState(false);
  const [showRetranslateDialog, setShowRetranslateDialog] = useState(false);
  const [pendingVttSave, setPendingVttSave] = useState<{ captions?: string; ad?: string } | null>(null);
  const [editingTitle, setEditingTitle] = useState(false);
  const [titleDraft, setTitleDraft] = useState('');
  const [adVttUploaded, setAdVttUploaded] = useState(false);
  const [renderJustCompleted, setRenderJustCompleted] = useState(false);
  const captionsFileInputRef = useRef<HTMLInputElement>(null);
  const adFileInputRef = useRef<HTMLInputElement>(null);
  const prevJobStatusRef = useRef<string | undefined>(undefined);
  const [ttsPreferences, setTtsPreferences] = useState<TTSPreferences>({
    provider: 'gemini',
    default_voice: 'Kore',
    voices_per_language: {},
    model: 'flash',
    speed: 1.0,
    style_preset: 'neutral',
    custom_style_prompt: undefined,
    stability: undefined,
    similarity_boost: undefined,
  });
  const [originalTtsPreferences, setOriginalTtsPreferences] = useState<TTSPreferences | null>(null);

  const isProcessing = approveEnglishMutation.isPending || rejectJobMutation.isPending || updateVttMutation.isPending || adjustTimingMutation.isPending || rerenderMutation.isPending || updateTTSPreferencesMutation.isPending;
  const isRendering = rerenderMutation.isPending || job?.status === 'rendering_qc';

  // Initialize selected language from source language when job loads
  useEffect(() => {
    if (sourceLanguage) {
      setSelectedLanguage(sourceLanguage);
    }
  }, [sourceLanguage]);

  // Reset states when language changes
  useEffect(() => {
    setPendingRegenerations([]);
    setPausePointsModified(false);
    setLastInsertedAdCue(null);
    setLastDeletedAdCue(null);
  }, [selectedLanguage]);

  // Refresh edit state when render completes (rendering_qc → pending_qc transition)
  const queryClient = useQueryClient();

  // Editing indicator (L-3 WS): "User X is editing [lang]" banner, auto-clears after 5s
  const [editingIndicator, setEditingIndicator] = useState<{ lang: string; userName: string } | null>(null);
  const editingIndicatorTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);

  const handleRawWsMessage = useCallback((msg: WebSocketMessage) => {
    if (msg.type === 'language_qc_comment' && msg.lang) {
      queryClient.invalidateQueries({ queryKey: ['qc-comments', id, msg.lang] });
    } else if (msg.type === 'language_editing_started' && msg.data) {
      const lang = (msg.data as { lang?: string }).lang ?? '';
      const userName = (msg.data as { user_name?: string }).user_name ?? 'Someone';
      if (editingIndicatorTimerRef.current) clearTimeout(editingIndicatorTimerRef.current);
      setEditingIndicator({ lang, userName });
      editingIndicatorTimerRef.current = setTimeout(() => setEditingIndicator(null), 5000);
    } else if (msg.type === 'language_qc_assigned') {
      refetchLangQc();
      queryClient.invalidateQueries({ queryKey: ['language-qc', id] });
      toast.toastOnly.success('Language QC assignment updated');
    }
  }, [id, queryClient, refetchLangQc, toast.toastOnly]);

  useJobStatusWebSocket(id, { onRawMessage: handleRawWsMessage });

  useEffect(() => {
    return () => {
      if (editingIndicatorTimerRef.current) clearTimeout(editingIndicatorTimerRef.current);
    };
  }, []);

  useEffect(() => {
    if (
      prevJobStatusRef.current === 'rendering_qc' &&
      job?.status === 'pending_qc'
    ) {
      queryClient.invalidateQueries({
        queryKey: ['jobs', id, 'accessible-video', selectedLanguage, 'edit-state'],
      });
      setRenderJustCompleted(true);
      toast.toastOnly.success('Re-render complete — review the updated video below');
      // Auto-dismiss banner after 10 seconds
      setTimeout(() => setRenderJustCompleted(false), 10000);
    }
    prevJobStatusRef.current = job?.status;
  }, [job?.status, id, selectedLanguage, queryClient]);

  // Sync pending regenerations from server edit state
  useEffect(() => {
    if (editState?.tts_regeneration_queue) {
      const queuedIndices = editState.tts_regeneration_queue
        .filter(item => item.status === 'pending')
        .map(item => item.cue_index);
      setPendingRegenerations(queuedIndices);
    }
  }, [editState?.tts_regeneration_queue]);

  // Get video URL from downloads - support both original and accessible video
  const videoUrl = typeof downloads?.downloads?.source_video === 'string'
    ? downloads.downloads.source_video
    : '';

  // Get accessible video URL for selected language
  const accessibleVideoUrl = editState?.accessible_video_url || (
    downloads?.downloads?.[selectedLanguage] &&
    typeof downloads.downloads[selectedLanguage] === 'object' &&
    (downloads.downloads[selectedLanguage] as { accessible_video_mp4?: string }).accessible_video_mp4
  ) || '';

  const [vttEtag, setVttEtag] = useState<string | undefined>(undefined);

  // Load VTT content when fetched
  useEffect(() => {
    if (vttContent) {
      setCaptionsVtt(vttContent.captions_vtt || '');
      setRetimedCaptionsVtt(vttContent.retimed_captions_vtt || '');
      setAdVtt(vttContent.audio_description_vtt || '');
      setVttEtag(vttContent.etag);
    }
  }, [vttContent]);

  // Initialize TTS preferences from job when loaded
  useEffect(() => {
    if (job?.requested_outputs?.tts_preferences) {
      setTtsPreferences(job.requested_outputs.tts_preferences);
      setOriginalTtsPreferences(job.requested_outputs.tts_preferences);
    }
    if (job?.cost_tracker_project_id !== undefined) {
      setCostProjectId(job.cost_tracker_project_id ?? '');
    }
  }, [job]);

  // Check if voice settings have changed from the original
  const voiceSettingsChanged = useMemo(() => {
    if (!originalTtsPreferences) return false;
    return JSON.stringify(ttsPreferences) !== JSON.stringify(originalTtsPreferences);
  }, [ttsPreferences, originalTtsPreferences]);

  // Keyboard shortcuts
  useEffect(() => {
    const handleKeyPress = (event: KeyboardEvent) => {
      // Only handle shortcuts when not typing in input fields
      if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement) {
        return;
      }

      switch (event.key.toLowerCase()) {
        case 'a':
          if (event.ctrlKey || event.metaKey) return;
          event.preventDefault();
          if (!isProcessing) handleApprove();
          break;
        case 'r':
          if (event.ctrlKey || event.metaKey) return;
          event.preventDefault();
          if (!isProcessing && !showRejectForm) setShowRejectForm(true);
          break;
        case 's':
          if (event.ctrlKey || event.metaKey) {
            // Cmd/Ctrl+S — save current VTT (full file)
            event.preventDefault();
            if (!isProcessing && !updateVttMutation.isPending) {
              handleSaveFullVtt();
            }
          }
          break;
        case 'escape':
          if (showRejectForm) setShowRejectForm(false);
          if (showLangRejectModal) { setShowLangRejectModal(false); setLangRejectNotes(''); setLangRejectCategory(''); }
          break;
        case '1':
          setViewMode('side-by-side');
          break;
        case '2':
          setViewMode('video-only');
          break;
        case '3':
          setViewMode('editor-only');
          break;
      }
    };

    document.addEventListener('keydown', handleKeyPress);
    return () => document.removeEventListener('keydown', handleKeyPress);
  }, [isProcessing, showRejectForm, showLangRejectModal, updateVttMutation.isPending]);

  const handleCaptionsChange = (content: string) => {
    setCaptionsVtt(content);
  };

  const handleAdChange = (content: string) => {
    setAdVtt(content);
  };

  const handleVttFileUpload = async (type: 'captions' | 'ad', file: File) => {
    if (!id) return;
    const content = await file.text();
    if (!content.trimStart().startsWith('WEBVTT')) {
      toast.toastOnly.error('Invalid VTT file — must start with WEBVTT header');
      return;
    }
    try {
      if (type === 'captions') {
        await updateVttMutation.mutateAsync({
          id,
          data: { captions_vtt: content, audio_description_vtt: adVtt || undefined, language: selectedLanguage, if_match: vttEtag },
        });
        setCaptionsVtt(content);
        toast.toastOnly.success('Captions VTT replaced from file');
      } else {
        await updateVttMutation.mutateAsync({
          id,
          data: { captions_vtt: captionsVtt || undefined, audio_description_vtt: content, language: selectedLanguage, if_match: vttEtag },
        });
        setAdVtt(content);
        setAdVttUploaded(true);
        toast.toastOnly.success('Audio Description VTT replaced from file');
      }
    } catch (error) {
      const httpStatus = (error as { response?: { status?: number } })?.response?.status;
      if (httpStatus === 409) {
        toast.toastOnly.error('Conflict: another user has modified this VTT. Reload to see their changes.');
      } else {
        toast.toastOnly.error('Failed to save VTT file. Please try again.');
      }
    }
  };

  // Cmd+S full-VTT save (hotkey handler)
  const handleTitleSave = async () => {
    if (!id || !titleDraft.trim() || titleDraft.trim() === job?.title) { setEditingTitle(false); return; }
    try {
      await updateJobMutation.mutateAsync({ id, data: { title: titleDraft.trim() } as never });
      toast.toastOnly.success('Renamed');
    } catch {
      toast.toastOnly.error('Failed to rename');
    } finally {
      setEditingTitle(false);
    }
  };

  const _doSaveVtt = async (retranslate: boolean, captions?: string, ad?: string) => {
    if (!id) return;
    try {
      await updateVttMutation.mutateAsync({
        id,
        data: {
          captions_vtt: captions || undefined,
          audio_description_vtt: ad || undefined,
          language: selectedLanguage,
          if_match: vttEtag,
          retranslate_languages: retranslate,
        }
      });
      toast.toastOnly.success(retranslate ? 'VTT saved — re-translating all languages…' : 'VTT saved');
    } catch (error) {
      const httpStatus = (error as { response?: { status?: number } })?.response?.status;
      if (httpStatus === 409) {
        toast.toastOnly.error('Conflict: another user has modified this VTT. Reload to see their changes.');
      } else {
        toast.toastOnly.error('Failed to save VTT');
      }
    }
  };

  const handleSaveFullVtt = async () => {
    if (!id || (!captionsVtt && !adVtt)) return;
    const targetLanguages = job?.requested_outputs?.languages?.filter(l => l !== sourceLanguage) ?? [];
    const savingSource = selectedLanguage === sourceLanguage;
    if (savingSource && targetLanguages.length > 0) {
      setPendingVttSave({ captions: captionsVtt || undefined, ad: adVtt || undefined });
      setShowRetranslateDialog(true);
      return;
    }
    await _doSaveVtt(false, captionsVtt || undefined, adVtt || undefined);
  };

  const handleBulkRetranslate = async () => {
    if (!id || brokenLanguages.length === 0) return;
    setBulkRetranslateLoading(true);
    setShowBulkRetranslateConfirm(false);
    let succeeded = 0;
    for (const lang of brokenLanguages) {
      try {
        await apiClient.retranslateLanguage(id, lang, 'Bulk retranslate broken languages');
        succeeded++;
      } catch {
        toast.toastOnly.error(`Failed to queue retranslation for ${lang.toUpperCase()}`);
      }
    }
    setBulkRetranslateLoading(false);
    queryClient.invalidateQueries({ queryKey: ['jobs', id] });
    queryClient.invalidateQueries({ queryKey: ['language-qc', id] });
    if (succeeded > 0) toast.toastOnly.success(`Queued retranslation for ${succeeded} language(s) — check back in a few minutes`);
  };

  // Immediate save handlers for individual cue edits
  const handleCaptionsCueSave = async (cueIndex: number, vttContent: string) => {
    if (!id) return;

    setCaptionsVtt(vttContent);
    await updateVttMutation.mutateAsync({
      id,
      data: {
        captions_vtt: vttContent,
        audio_description_vtt: adVtt || undefined,
        language: selectedLanguage,
        if_match: vttEtag,
      }
    });
    toast.toastOnly.success(`Caption cue ${cueIndex + 1} saved`);
  };

  const handleAdCueSave = async (cueIndex: number, vttContent: string) => {
    if (!id) return;

    setAdVtt(vttContent);

    // Save VTT to database
    await updateVttMutation.mutateAsync({
      id,
      data: {
        captions_vtt: captionsVtt || undefined,
        audio_description_vtt: vttContent,
        language: selectedLanguage,
        if_match: vttEtag,
      }
    });

    // Determine which cues need TTS regeneration
    let cueIndicesToRegenerate: number[];
    let actionDescription = 'saved';

    if (lastInsertedAdCue && cueIndex === lastInsertedAdCue.insertIndex) {
      // This is the newly inserted cue being saved
      // Queue TTS for this cue AND all shifted cues (from insertIndex to end)
      cueIndicesToRegenerate = Array.from(
        { length: lastInsertedAdCue.totalCues - lastInsertedAdCue.insertIndex },
        (_, i) => lastInsertedAdCue.insertIndex + i
      );
      setLastInsertedAdCue(null);  // Clear insertion context
      actionDescription = 'inserted';
    } else if (lastDeletedAdCue && cueIndex === lastDeletedAdCue.deletedIndex) {
      // A cue was deleted - queue TTS for all cues that shifted (from deleted index to end)
      // Note: cueIndex here points to the cue that now occupies the deleted position
      cueIndicesToRegenerate = Array.from(
        { length: lastDeletedAdCue.totalCuesAfterDelete - lastDeletedAdCue.deletedIndex },
        (_, i) => lastDeletedAdCue.deletedIndex + i
      );
      setLastDeletedAdCue(null);  // Clear deletion context
      actionDescription = 'deleted';
    } else {
      // Normal edit, just this cue
      cueIndicesToRegenerate = [cueIndex];
    }

    // Queue TTS regeneration for the cues (skip if no cues to regenerate, e.g., deleted last cue)
    if (cueIndicesToRegenerate.length === 0) {
      toast.toastOnly.success('AD cue deleted');
      return;
    }

    try {
      await queueTTSRegenerationMutation.mutateAsync({
        jobId: id,
        language: selectedLanguage,
        cueIndices: cueIndicesToRegenerate,
      });
      // Update local pending regenerations
      setPendingRegenerations(prev => [...new Set([...prev, ...cueIndicesToRegenerate])]);

      const cueCount = cueIndicesToRegenerate.length;
      if (cueCount > 1) {
        toast.toastOnly.success(
          `AD cue ${actionDescription}. Queued TTS for ${cueCount} cues (indices ${cueIndicesToRegenerate.join(', ')})`
        );
      } else {
        toast.toastOnly.success(`AD cue ${cueIndex + 1} saved and queued for TTS`);
      }
    } catch (queueError) {
      console.error('Failed to queue TTS regeneration:', queueError);
      toast.toastOnly.success(`AD cue ${cueIndex + 1} saved (TTS queue failed - queue manually)`);
    }
  };

  // Handler for AD cue insertions - tracks context for TTS queueing
  const handleAdCueInserted = (insertedIndex: number, totalCues: number) => {
    setLastInsertedAdCue({ insertIndex: insertedIndex, totalCues });
  };

  // Handler for AD cue deletions - tracks context for TTS queueing
  const handleAdCueDeleted = (deletedIndex: number, totalCuesAfterDelete: number) => {
    setLastDeletedAdCue({ deletedIndex, totalCuesAfterDelete });
  };

  const handleApprove = async () => {
    if (!id) return;

    try {
      // Only pass TTS preferences if MP3 generation is requested
      const ttsPrefsToSend = job?.requested_outputs?.audio_description_mp3 ? ttsPreferences : undefined;

      await approveEnglishMutation.mutateAsync({
        id,
        notes: reviewNotes,
        tts_preferences: ttsPrefsToSend,
      });
      toast.toastOnly.success('Job approved successfully');
      navigate('/admin/qc');
    } catch (error) {
      console.error('Approval failed:', error);
      toast.toastOnly.error('Failed to approve job. Please try again.');
    }
  };

  const handleSaveVoiceSettings = async () => {
    if (!id) return;

    try {
      await updateTTSPreferencesMutation.mutateAsync({
        id,
        tts_preferences: ttsPreferences
      });
      // Update original preferences to reflect the saved state
      setOriginalTtsPreferences(ttsPreferences);
      toast.toastOnly.success('Voice settings saved. TTS regeneration started for all languages.');
    } catch (error) {
      console.error('Failed to save voice settings:', error);
      toast.toastOnly.error('Failed to save voice settings. Please try again.');
    }
  };

  const handleReject = async () => {
    if (!id || !reviewNotes.trim()) return;
    
    try {
      await rejectJobMutation.mutateAsync({ 
        id, 
        notes: reviewNotes 
      });
      toast.toastOnly.success('Job rejected and returned for revision');
      navigate('/admin/qc');
    } catch (error) {
      console.error('Rejection failed:', error);
      toast.toastOnly.error('Failed to reject job. Please try again.');
    }
  };

  const handleTimingAdjustment = async () => {
    if (!id || timingOffset === 0) return;

    try {
      await adjustTimingMutation.mutateAsync({
        id,
        offsetSeconds: timingOffset,
        language: selectedLanguage,  // Use selected language for multi-language support
        adjustCaptions,
        adjustAudioDescription,
      });
      toast.toastOnly.success(`Timing adjusted by ${timingOffset > 0 ? '+' : ''}${timingOffset} seconds`);
      setShowTimingAdjustment(false);
      setTimingOffset(0);
    } catch (error) {
      console.error('Timing adjustment failed:', error);
      toast.toastOnly.error('Failed to adjust timing. Please try again.');
    }
  };

  // Accessible video editing handlers
  const handleSegmentClick = (segment: VideoSegmentMetadata) => {
    // Seek video to segment start
    if (videoRef.current) {
      videoRef.current.currentTime = segment.start_ms / 1000;
    }
    // If it's an AD segment with a cue, highlight the cue in the VTT editor
    if (segment.is_freeze_frame && segment.cue_index !== null) {
      // Could dispatch an event or set state to highlight the cue
      console.log(`AD cue ${segment.cue_index} clicked`);
    }
  };

  const handlePausePointClick = (pausePoint: PausePointData) => {
    // Seek video to pause point
    const effectiveMs = pausePoint.adjusted_ms ?? pausePoint.original_ms;
    if (videoRef.current) {
      videoRef.current.currentTime = effectiveMs / 1000;
    }
  };

  const handlePausePointAdd = (timeMs: number) => {
    // Insert a new AD cue at this position via the VttEditor insertAtTimeMs prop
    setAdInsertAtTimeMs(timeMs);
  };

  const handleAdCuePlay = useCallback((startTime: number) => {
    setVideoMode('accessible');
    setTimeout(() => {
      if (videoRef.current) {
        videoRef.current.currentTime = startTime;
        videoRef.current.play().catch(() => { /* autoplay may be blocked */ });
      }
    }, 80);
  }, []);

  const handlePausePointUpdate = async (cueIndex: number, adjustedMs: number) => {
    if (!id) return;

    try {
      await updatePausePointMutation.mutateAsync({
        jobId: id,
        language: selectedLanguage,
        cueIndex,
        adjustedMs,
      });
      setPausePointsModified(true);
      toast.toastOnly.success(`Pause point ${cueIndex} updated to ${adjustedMs}ms`);
    } catch (error) {
      console.error('Failed to update pause point:', error);
      toast.toastOnly.error('Failed to update pause point. Please try again.');
    }
  };

  const handleRegenerateTTS = async (cueIndex: number) => {
    if (!id) return;

    try {
      await queueTTSRegenerationMutation.mutateAsync({
        jobId: id,
        language: selectedLanguage,
        cueIndices: [cueIndex],
      });
      setPendingRegenerations(prev => [...prev, cueIndex]);
      toast.toastOnly.success(`TTS regeneration queued for cue ${cueIndex}`);
    } catch (error) {
      console.error('Failed to queue TTS regeneration:', error);
      toast.toastOnly.error('Failed to queue TTS regeneration. Please try again.');
    }
  };

  const handleClearRegenerationQueue = async () => {
    if (!id) return;

    // Remove pending regenerations one by one, only clearing local state for successful removals
    const failedIndices: number[] = [];
    for (const cueIndex of pendingRegenerations) {
      try {
        await removeTTSRegenerationMutation.mutateAsync({
          jobId: id,
          language: selectedLanguage,
          cueIndex,
        });
      } catch (error) {
        console.error(`Failed to remove cue ${cueIndex} from queue:`, error);
        failedIndices.push(cueIndex);
      }
    }
    // Only remove successfully cleared items from local state
    if (failedIndices.length > 0) {
      setPendingRegenerations(failedIndices);
      toast.toastOnly.error(`Cleared queue partially — ${failedIndices.length} item(s) could not be removed`);
    } else {
      setPendingRegenerations([]);
      toast.toastOnly.success('Regeneration queue cleared');
    }
  };

  const handleRender = async ({ whisperRefine }: { whisperRefine: boolean }) => {
    if (!id) return;

    try {
      // Force prevJobStatusRef to 'rendering_qc' so the completion effect fires
      // even if polling misses the rendering_qc state (fast renders < 10s)
      prevJobStatusRef.current = 'rendering_qc';
      await rerenderMutation.mutateAsync({
        jobId: id,
        language: selectedLanguage,
        whisperRefine,
      });
      toast.toastOnly.success('Accessible video rendering started');
      setPendingRegenerations([]);
      setPausePointsModified(false);
      setAdVttUploaded(false);
    } catch (error) {
      console.error('Failed to start re-render:', error);
      toast.toastOnly.error('Failed to start re-render. Please try again.');
    }
  };

  if (isLoading || vttLoading) {
    return (
      <div className="container mx-auto px-4 py-8">
        <div className="animate-pulse">
          <div className="h-8 bg-gray-200 rounded w-1/3 mb-8"></div>
          <div className="space-y-4">
            <div className="h-64 bg-gray-200 rounded"></div>
            <div className="h-64 bg-gray-200 rounded"></div>
          </div>
        </div>
      </div>
    );
  }

  if (error || !job) {
    return (
      <div className="container mx-auto px-4 py-8">
        <h1 className="text-3xl font-bold text-gray-900 mb-8">Quality Control Detail</h1>
        <div className="bg-red-50 border border-red-200 rounded-md p-4">
          <p className="text-red-600">Job not found or failed to load.</p>
        </div>
      </div>
    );
  }

  return (
    <div className="container mx-auto px-4 py-8 max-w-6xl">
      {/* Header */}
      <div className="flex justify-between items-start mb-8">
        <div>
          <div className="flex items-center gap-2 mb-2">
              {editingTitle ? (
                <input
                  type="text"
                  value={titleDraft}
                  onChange={e => setTitleDraft(e.target.value)}
                  onKeyDown={e => { if (e.key === 'Enter') handleTitleSave(); if (e.key === 'Escape') setEditingTitle(false); }}
                  onBlur={handleTitleSave}
                  className="text-3xl font-bold text-gray-900 border-b-2 border-indigo-500 focus:outline-none bg-transparent w-full"
                  autoFocus
                />
              ) : (
                <h1 className="text-3xl font-bold text-gray-900">{job.title}</h1>
              )}
              {canAssign && !editingTitle && (
                <button
                  onClick={() => { setTitleDraft(job.title); setEditingTitle(true); }}
                  className="text-gray-400 hover:text-gray-600 flex-shrink-0"
                  title="Rename"
                >
                  <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
                  </svg>
                </button>
              )}
            </div>
          <div className="flex items-center gap-4 text-sm text-gray-500">
            <StatusBadge status={job.status} />
            <span>Source: {job.source.filename}</span>
            <span>Language: {sourceLanguage.toUpperCase()}</span>
            {job.source.duration_s && (
              <span>Duration: {Math.round(job.source.duration_s)}s</span>
            )}
          </div>
        </div>
        
        <div className="flex items-center gap-3">
          {canAssign && (
            <button
              onClick={() => { setShareUrl(''); setShareLabel(''); setShareCopied(false); setShowShareModal(true); }}
              className="text-xs px-3 py-1.5 bg-white border border-gray-300 rounded-lg text-gray-600 hover:border-gray-400 hover:text-gray-800"
            >
              ↗ Share link
            </button>
          )}
          <button
            onClick={() => navigate('/admin/qc')}
            className="text-gray-500 hover:text-gray-700"
          >
            ← Back to Queue
          </button>
        </div>
      </div>

      {/* Re-render complete banner */}
      {renderJustCompleted && (
        <div className="mb-4 flex items-center justify-between p-3 bg-green-50 border border-green-300 rounded-md text-green-800">
          <span className="font-medium">Re-render complete — review the updated video below</span>
          <button
            onClick={() => setRenderJustCompleted(false)}
            className="ml-4 text-green-600 hover:text-green-800 font-bold"
            aria-label="Dismiss"
          >
            ✕
          </button>
        </div>
      )}

      {/* Editing indicator banner (L-3 WS) */}
      {editingIndicator && (
        <div className="mb-4 flex items-center justify-between p-3 bg-yellow-50 border border-yellow-300 rounded-md text-yellow-800">
          <span className="text-sm">
            <span className="font-medium">{editingIndicator.userName}</span> is editing{' '}
            <span className="font-mono">{editingIndicator.lang.toUpperCase()}</span>
          </span>
          <button
            onClick={() => setEditingIndicator(null)}
            className="ml-4 text-yellow-600 hover:text-yellow-800 font-bold"
            aria-label="Dismiss"
          >
            ✕
          </button>
        </div>
      )}

      {/* AI Confidence Score */}
      {job.ai?.confidence && (
        <div className="mb-6 p-4 bg-blue-50 border border-blue-200 rounded-md">
          <div className="flex items-center justify-between">
            <span className="font-medium text-blue-900">AI Confidence Score</span>
            <span className="text-blue-700 font-mono">
              {(job.ai.confidence * 100).toFixed(1)}%
            </span>
          </div>
        </div>
      )}

      {/* Per-language QC panel */}
      <div className="mb-6">
        <div className="flex items-center justify-between mb-2">
          <div className="flex items-center gap-3">
            <span className="text-sm font-medium text-gray-700">Languages</span>
            {totalLangs > 0 && (
              <span className="text-xs text-gray-500">
                {approvedLangs}/{totalLangs} approved
                {approvedLangs === totalLangs && totalLangs > 0 && (
                  <span className="ml-1 text-green-600 font-medium">— all done ✓</span>
                )}
              </span>
            )}
          </div>
          <div className="flex items-center gap-2">
            {canApproveAll && isSourceApproved && brokenLanguages.length > 0 && (
              <button
                onClick={() => setShowBulkRetranslateConfirm(true)}
                disabled={bulkRetranslateLoading}
                className="text-xs px-3 py-1.5 bg-orange-50 text-orange-700 border border-orange-200 rounded-lg hover:bg-orange-100 disabled:opacity-50"
              >
                {bulkRetranslateLoading ? 'Queuing…' : `↺ Retranslate broken (${brokenLanguages.length})`}
              </button>
            )}
            {canAssign && totalLangs > 1 && (
              <button
                onClick={() => { setBulkLinguistId(''); setBulkReviewerId(''); setBulkDeadline(''); setBulkOnlyUnassigned(true); setShowBulkAssignModal(true); }}
                className="text-xs px-3 py-1.5 bg-indigo-50 text-indigo-700 border border-indigo-200 rounded-lg hover:bg-indigo-100"
              >
                Assign all languages
              </button>
            )}
          </div>
        </div>

        {/* Progress bar */}
        {totalLangs > 1 && (
          <div className="h-1.5 bg-gray-200 rounded-full mb-3 overflow-hidden">
            <div className="h-full bg-green-500 rounded-full transition-all"
              style={{ width: `${totalLangs > 0 ? (approvedLangs / totalLangs) * 100 : 0}%` }} />
          </div>
        )}

        {/* Language cards */}
        <div className="space-y-3">
          {availableLanguages.map((lang) => {
            const qcState = langQcMap[lang];
            const qcStatus = (qcState?.status ?? 'pending') as LanguageQCStatus;
            const isActive = selectedLanguage === lang;
            const myId = authUser?.id;

            const isAssignedLinguist = qcState?.assigned_linguist_id === myId;
            const isAssignedReviewer = qcState?.assigned_reviewer_id === myId;

            // Linguist sees: start-work, submit
            const canStartWork = isAssignedLinguist && (qcStatus === 'pending' || qcStatus === 'rejected');
            const canSubmit = isAssignedLinguist && qcStatus === 'in_progress';
            // Reviewer sees: open-review, approve, reject/request-changes
            const canOpenReview = (isAssignedReviewer || canApproveAll) && qcStatus === 'pending_review';
            const canApproveThis = (isAssignedReviewer || canApproveAll) && (qcStatus === 'in_review' || (canApproveAll && qcStatus !== 'approved'));
            const canRejectThis = (isAssignedReviewer || canApproveAll) && (qcStatus === 'in_review' || (canApproveAll && qcStatus !== 'rejected'));

            const isCommentsOpen = openCommentLang === lang;

            // Deadline formatting
            const linguistDeadline = qcState?.linguist_deadline ? new Date(qcState.linguist_deadline).toLocaleDateString() : null;
            const reviewerDeadline = qcState?.reviewer_deadline ? new Date(qcState.reviewer_deadline).toLocaleDateString() : null;

            return (
              <div key={lang} className={`rounded-xl border transition-colors ${isActive ? 'border-indigo-300 bg-indigo-50/50' : 'border-gray-200 bg-white'}`}>
                {/* Card header — language selector + status */}
                <button
                  onClick={() => setSelectedLanguage(lang)}
                  className="w-full flex items-center justify-between px-4 py-3 text-left"
                >
                  <div className="flex items-center gap-2 flex-wrap">
                    <span className={`inline-flex items-center justify-center w-5 h-5 text-xs rounded-full ${LANG_QC_BADGE[qcStatus]}`}>
                      {LANG_QC_ICON[qcStatus]}
                    </span>
                    <span className="font-medium text-sm">{lang.toUpperCase()}</span>
                    {lang === sourceLanguage && <span className="text-xs text-gray-400">(source)</span>}
                    <span className={`text-xs px-2 py-0.5 rounded-full ${LANG_QC_BADGE[qcStatus]}`}>{LANG_QC_LABEL[qcStatus]}</span>
                    {lang !== sourceLanguage && (() => {
                      const origin = job?.outputs?.[lang]?.origin;
                      if (!origin) return null;
                      const cfg: Record<string, { cls: string; label: string }> = {
                        gemini_translate: { cls: 'bg-green-100 text-green-700', label: 'EN-aligned' },
                        transcreate: { cls: 'bg-green-100 text-green-700', label: 'transcreated' },
                        video_native: { cls: 'bg-red-100 text-red-700', label: '⚠ video-native' },
                      };
                      const c = cfg[origin] ?? { cls: 'bg-gray-100 text-gray-500', label: origin };
                      return <span className={`text-xs px-2 py-0.5 rounded-full ${c.cls}`}>{c.label}</span>;
                    })()}
                  </div>
                  <span className="text-gray-400 text-xs">{isActive ? '▲' : '▼'}</span>
                </button>

                {/* Card body — only shown when active */}
                {isActive && (
                  <div className="px-4 pb-4 space-y-3 border-t border-gray-100">
                    {/* Two-slot assignment row */}
                    <div className="grid grid-cols-2 gap-3 pt-3">
                      {/* Linguist slot */}
                      <div className="space-y-1">
                        <div className="text-xs font-medium text-gray-500 uppercase tracking-wide">Linguist</div>
                        {qcState?.assigned_linguist_name || qcState?.assigned_linguist_email ? (
                          <div className="text-sm text-gray-800">
                            {qcState.assigned_linguist_name || qcState.assigned_linguist_email}
                            {linguistDeadline && <span className="ml-1.5 text-xs text-orange-600">due {linguistDeadline}</span>}
                          </div>
                        ) : (
                          <div className="text-sm text-gray-400 italic">Unassigned</div>
                        )}
                        {canAssign && (
                          <button
                            onClick={() => { setAssignLanguage(lang); setAssignSlot('linguist'); setAssigningUserId(''); setAssignDeadline(''); setShowAssignModal(true); }}
                            className="text-xs text-blue-600 hover:underline"
                          >
                            {qcState?.assigned_linguist_id ? 'Reassign' : 'Assign'} linguist
                          </button>
                        )}
                      </div>

                      {/* Reviewer slot */}
                      <div className="space-y-1">
                        <div className="text-xs font-medium text-gray-500 uppercase tracking-wide">Reviewer</div>
                        {qcState?.assigned_reviewer_name || qcState?.assigned_reviewer_email ? (
                          <div className="text-sm text-gray-800">
                            {qcState.assigned_reviewer_name || qcState.assigned_reviewer_email}
                            {reviewerDeadline && <span className="ml-1.5 text-xs text-orange-600">due {reviewerDeadline}</span>}
                          </div>
                        ) : (
                          <div className="text-sm text-gray-400 italic">Unassigned</div>
                        )}
                        {canAssign && (
                          <button
                            onClick={() => { setAssignLanguage(lang); setAssignSlot('reviewer'); setAssigningUserId(''); setAssignDeadline(''); setShowAssignModal(true); }}
                            className="text-xs text-blue-600 hover:underline"
                          >
                            {qcState?.assigned_reviewer_id ? 'Reassign' : 'Assign'} reviewer
                          </button>
                        )}
                      </div>
                    </div>

                    {/* EN-first gate banner for target languages */}
                    {lang !== sourceLanguage && !isSourceApproved && (
                      <div className="rounded-lg bg-amber-50 border border-amber-200 px-3 py-2 text-xs text-amber-800">
                        ⏳ Waiting for <strong>{sourceLanguage.toUpperCase()}</strong> approval — translation will start automatically once the source language is approved.
                      </div>
                    )}

                    {/* Workflow action buttons */}
                    <div className="flex flex-wrap gap-2 pt-1">
                      {canStartWork && (
                        <button
                          onClick={() => apiClient.startLinguistWork(id!, lang).then(() => { queryClient.invalidateQueries({ queryKey: ['language-qc', id] }); refetchLangQc(); })}
                          className="text-xs px-3 py-1.5 bg-yellow-500 text-white rounded-lg hover:bg-yellow-600"
                        >
                          Start work
                        </button>
                      )}
                      {canSubmit && (
                        <button
                          onClick={() => submitForReviewMutation.mutate(lang)}
                          disabled={submitForReviewMutation.isPending}
                          className="text-xs px-3 py-1.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50"
                        >
                          {submitForReviewMutation.isPending ? 'Submitting…' : '↑ Submit for review'}
                        </button>
                      )}
                      {canOpenReview && (
                        <button
                          onClick={() => openReviewMutation.mutate(lang)}
                          disabled={openReviewMutation.isPending}
                          className="text-xs px-3 py-1.5 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 disabled:opacity-50"
                        >
                          Open review
                        </button>
                      )}
                      {canApproveThis && (() => {
                        const reviewedCues = qcState?.reviewed_cues ?? 0;
                        const totalCues = qcState?.total_cues ?? null;
                        const pct = totalCues ? Math.round((reviewedCues / totalCues) * 100) : null;
                        const gated = !canApproveAll && totalCues !== null && reviewedCues < Math.ceil(totalCues * 0.8);
                        return (
                          <div className="flex flex-col gap-1">
                            {totalCues !== null && (
                              <div className="flex items-center gap-2">
                                <div className="flex-1 h-1.5 bg-gray-200 rounded-full overflow-hidden">
                                  <div className="h-full bg-indigo-500 transition-all" style={{ width: `${pct}%` }} />
                                </div>
                                <span className="text-xs text-gray-500 whitespace-nowrap">{reviewedCues}/{totalCues} reviewed</span>
                              </div>
                            )}
                            <button
                              onClick={() => approveLanguageMutation.mutate({ lang })}
                              disabled={approveLanguageMutation.isPending || gated}
                              title={gated ? 'Review at least 80% of cues before approving' : undefined}
                              className="text-xs px-3 py-1.5 bg-green-600 text-white rounded-lg hover:bg-green-700 disabled:opacity-50 disabled:cursor-not-allowed"
                            >
                              ✓ Approve {gated && `(${pct}% reviewed)`}
                            </button>
                          </div>
                        );
                      })()}
                      {canRejectThis && (
                        <button
                          onClick={() => { setAssignLanguage(lang); setShowLangRejectModal(true); }}
                          className="text-xs px-3 py-1.5 bg-red-500 text-white rounded-lg hover:bg-red-600"
                        >
                          ✕ Request changes
                        </button>
                      )}
                      {canApproveAll && qcStatus === 'approved' && (
                        <button
                          onClick={() => apiClient.reopenLanguageQC(id!, lang).then(() => { queryClient.invalidateQueries({ queryKey: ['language-qc', id] }); refetchLangQc(); })}
                          className="text-xs px-3 py-1.5 border border-gray-300 text-gray-500 rounded-lg hover:bg-gray-50"
                        >
                          Reopen
                        </button>
                      )}
                      {canApproveAll && lang !== sourceLanguage && (
                        <button
                          onClick={() => { setRetranslateLang(lang); setRetranslateReason(''); }}
                          disabled={!isSourceApproved}
                          title={!isSourceApproved ? `${sourceLanguage.toUpperCase()} must be approved first` : 'Re-generate from approved EN master'}
                          className="text-xs px-3 py-1.5 border border-indigo-300 text-indigo-600 rounded-lg hover:bg-indigo-50 disabled:opacity-40 disabled:cursor-not-allowed"
                        >
                          ↺ Re-translate from EN
                        </button>
                      )}
                    </div>

                    {/* Comments toggle */}
                    <div>
                      <button
                        onClick={() => setOpenCommentLang(isCommentsOpen ? null : lang)}
                        className="text-xs text-gray-500 hover:text-gray-800 flex items-center gap-1"
                      >
                        💬 {isCommentsOpen ? 'Hide comments' : `Comments (${qcState?.comments?.length ?? 0})`}
                      </button>

                      {isCommentsOpen && (
                        <div className="mt-2 space-y-2">
                          {(qcState?.comments ?? []).length === 0 && (
                            <p className="text-xs text-gray-400 py-2">No comments yet.</p>
                          )}
                          {(qcState?.comments ?? []).map((c) => (
                            <div key={c.id} className="bg-gray-50 rounded-lg p-3">
                              <div className="flex items-baseline gap-2 mb-1">
                                <span className="text-xs font-medium text-gray-700">{c.author_name || c.author_email}</span>
                                <span className="text-xs text-gray-400">{new Date(c.created_at).toLocaleString()}</span>
                              </div>
                              <p className="text-sm text-gray-700 whitespace-pre-wrap">{c.body}</p>
                            </div>
                          ))}
                          <div className="flex gap-2 mt-2">
                            <textarea
                              value={commentDraft}
                              onChange={e => setCommentDraft(e.target.value)}
                              rows={2}
                              placeholder="Add a comment…"
                              className="flex-1 text-sm border border-gray-300 rounded-lg px-3 py-1.5 focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none"
                            />
                            <button
                              onClick={() => addCommentMutation.mutate({ lang, body: commentDraft })}
                              disabled={!commentDraft.trim() || addCommentMutation.isPending}
                              className="self-end px-3 py-1.5 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50"
                            >
                              Send
                            </button>
                          </div>
                        </div>
                      )}
                    </div>
                  </div>
                )}
              </div>
            );
          })}
        </div>
      </div>

      {/* Reject / Request-changes modal */}
      {showLangRejectModal && (
        <div className="fixed inset-0 bg-black/40 flex items-center justify-center z-50">
          <div className="bg-white rounded-lg shadow-xl p-6 max-w-md w-full mx-4 space-y-3">
            <h3 className="text-lg font-medium">Request changes — {assignLanguage.toUpperCase()}</h3>
            <p className="text-sm text-gray-500">Describe what the linguist needs to correct. This will be sent to them by email.</p>
            {/* Category selector (R-4) */}
            <div>
              <label className="block text-xs font-medium text-gray-700 mb-1">Issue category</label>
              <div className="flex flex-wrap gap-2">
                {['timing', 'mistranslation', 'terminology', 'profanity', 'length', 'other'].map(cat => (
                  <button
                    key={cat}
                    type="button"
                    onClick={() => setLangRejectCategory(prev => prev === cat ? '' : cat)}
                    className={`px-3 py-1 text-xs rounded-full border capitalize transition-colors ${
                      langRejectCategory === cat
                        ? 'bg-red-600 border-red-600 text-white'
                        : 'border-gray-300 text-gray-600 hover:border-red-400'
                    }`}
                  >
                    {cat}
                  </button>
                ))}
              </div>
            </div>
            <textarea
              value={langRejectNotes}
              onChange={e => setLangRejectNotes(e.target.value)}
              rows={3}
              placeholder="Required feedback…"
              className="w-full text-sm border border-gray-300 rounded p-2 focus:outline-none focus:ring-2 focus:ring-red-400"
            />
            <div className="flex gap-3 justify-end">
              <button onClick={() => { setShowLangRejectModal(false); setLangRejectNotes(''); setLangRejectCategory(''); }}
                className="px-4 py-2 text-sm border border-gray-300 rounded text-gray-700 hover:bg-gray-50">
                Cancel
              </button>
              <button
                onClick={() => rejectLanguageMutation.mutate({ lang: assignLanguage, notes: langRejectNotes, category: langRejectCategory || undefined })}
                disabled={!langRejectNotes.trim() || rejectLanguageMutation.isPending}
                className="px-4 py-2 text-sm bg-red-600 text-white rounded hover:bg-red-700 disabled:opacity-50"
              >
                {rejectLanguageMutation.isPending ? 'Sending…' : 'Send feedback'}
              </button>
            </div>
          </div>
        </div>
      )}

      {/* Unified assign modal — linguist or reviewer */}
      {showAssignModal && (
        <div className="fixed inset-0 bg-black/40 flex items-center justify-center z-50">
          <div className="bg-white rounded-lg shadow-xl p-6 max-w-sm w-full mx-4 space-y-4">
            <h3 className="text-lg font-medium">
              Assign {assignSlot} — {assignLanguage.toUpperCase()}
            </h3>
            <div>
              <label className="block text-xs font-medium text-gray-700 mb-1">
                {assignSlot === 'linguist' ? 'Linguist' : 'Reviewer'}
              </label>
              <select
                value={assigningUserId}
                onChange={e => setAssigningUserId(e.target.value)}
                className="w-full text-sm border border-gray-300 rounded px-2 py-1.5"
              >
                <option value="">Select {assignSlot}…</option>
                {assignableUsers.map((u) => (
                  <option key={u.id} value={u.id}>{u.full_name} ({u.email})</option>
                ))}
              </select>
              {/* R-8: language competence warning for linguists */}
              {assignSlot === 'linguist' && assigningUserId && (() => {
                const selected = assignableUsers.find(u => u.id === assigningUserId);
                if (!selected?.languages?.length) return null;
                const hasLang = selected.languages.some(
                  l => l.toLowerCase() === assignLanguage.toLowerCase() || l.toLowerCase().startsWith(assignLanguage.toLowerCase() + '-')
                );
                return !hasLang ? (
                  <p className="mt-1.5 text-xs text-amber-700 bg-amber-50 border border-amber-200 rounded px-2 py-1">
                    ⚠ {selected.full_name} has no {assignLanguage.toUpperCase()} competence listed. You can still assign, but double-check.
                  </p>
                ) : null;
              })()}
            </div>
            <div>
              <label className="block text-xs font-medium text-gray-700 mb-1">Deadline (optional)</label>
              <input
                type="date"
                value={assignDeadline}
                onChange={e => setAssignDeadline(e.target.value)}
                className="w-full text-sm border border-gray-300 rounded px-2 py-1.5"
              />
            </div>
            <div className="flex gap-3 justify-end">
              <button onClick={() => { setShowAssignModal(false); setAssigningUserId(''); setAssignDeadline(''); }}
                className="px-4 py-2 text-sm border border-gray-300 rounded text-gray-700 hover:bg-gray-50">
                Cancel
              </button>
              <button
                onClick={() => assignMutation.mutate({
                  lang: assignLanguage,
                  userId: assigningUserId,
                  slot: assignSlot,
                  deadline: assignDeadline || undefined,
                })}
                disabled={!assigningUserId || assignMutation.isPending}
                className="px-4 py-2 text-sm bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50"
              >
                {assignMutation.isPending ? 'Assigning…' : 'Assign'}
              </button>
            </div>
          </div>
        </div>
      )}

      {/* Bulk assign modal */}
      {showBulkAssignModal && (
        <div className="fixed inset-0 bg-black/40 flex items-center justify-center z-50">
          <div className="bg-white rounded-lg shadow-xl p-6 max-w-md w-full mx-4 space-y-4">
            <h3 className="text-lg font-medium">Assign all languages</h3>

            <div>
              <label className="block text-xs font-medium text-gray-700 mb-1">Linguist <span className="text-red-500">*</span></label>
              <select
                value={bulkLinguistId}
                onChange={e => setBulkLinguistId(e.target.value)}
                className="w-full text-sm border border-gray-300 rounded px-2 py-1.5"
              >
                <option value="">Select linguist…</option>
                {bulkLinguists.map((u) => (
                  <option key={u.id} value={u.id}>{u.full_name} ({u.email})</option>
                ))}
              </select>
            </div>

            <div>
              <label className="block text-xs font-medium text-gray-700 mb-1">Reviewer (optional)</label>
              <select
                value={bulkReviewerId}
                onChange={e => setBulkReviewerId(e.target.value)}
                className="w-full text-sm border border-gray-300 rounded px-2 py-1.5"
              >
                <option value="">None</option>
                {bulkReviewers.map((u) => (
                  <option key={u.id} value={u.id}>{u.full_name} ({u.email})</option>
                ))}
              </select>
            </div>

            <div>
              <label className="block text-xs font-medium text-gray-700 mb-1">Deadline (optional)</label>
              <input
                type="date"
                value={bulkDeadline}
                onChange={e => setBulkDeadline(e.target.value)}
                className="w-full text-sm border border-gray-300 rounded px-2 py-1.5"
              />
            </div>

            <label className="flex items-center gap-2 text-sm text-gray-700 cursor-pointer">
              <input
                type="checkbox"
                checked={bulkOnlyUnassigned}
                onChange={e => setBulkOnlyUnassigned(e.target.checked)}
                className="rounded"
              />
              Skip languages that already have a linguist assigned
            </label>

            <div className="flex gap-3 justify-end">
              <button
                onClick={() => setShowBulkAssignModal(false)}
                className="px-4 py-2 text-sm border border-gray-300 rounded text-gray-700 hover:bg-gray-50"
              >
                Cancel
              </button>
              <button
                onClick={() => bulkAssignMutation.mutate()}
                disabled={!bulkLinguistId || bulkAssignMutation.isPending}
                className="px-4 py-2 text-sm bg-indigo-600 text-white rounded hover:bg-indigo-700 disabled:opacity-50"
              >
                {bulkAssignMutation.isPending ? 'Assigning…' : `Assign ${availableLanguages.length} language(s)`}
              </button>
            </div>
          </div>
        </div>
      )}

      {/* Retranslate confirmation dialog */}
      {showRetranslateDialog && pendingVttSave && (
        <div className="fixed inset-0 bg-black/40 flex items-center justify-center z-50">
          <div className="bg-white rounded-lg shadow-xl p-6 max-w-md w-full mx-4 space-y-4">
            <h3 className="text-lg font-semibold text-gray-900">Apply changes to all languages?</h3>
            <p className="text-sm text-gray-600">
              You edited the source VTT. Do you want to re-translate all target languages from the updated source?
              Existing translations will be overwritten.
            </p>
            <div className="flex gap-3 justify-end pt-2">
              <button
                onClick={() => {
                  setShowRetranslateDialog(false);
                  _doSaveVtt(false, pendingVttSave.captions, pendingVttSave.ad);
                  setPendingVttSave(null);
                }}
                className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50"
              >
                No, source only
              </button>
              <button
                onClick={() => {
                  setShowRetranslateDialog(false);
                  _doSaveVtt(true, pendingVttSave.captions, pendingVttSave.ad);
                  setPendingVttSave(null);
                }}
                className="px-4 py-2 text-sm font-medium text-white bg-indigo-600 rounded-md hover:bg-indigo-700"
              >
                Yes, retranslate all
              </button>
            </div>
          </div>
        </div>
      )}

      {/* Bulk retranslate broken languages confirmation modal */}
      {showBulkRetranslateConfirm && (
        <div className="fixed inset-0 bg-black/40 flex items-center justify-center z-50">
          <div className="bg-white rounded-lg shadow-xl p-6 max-w-md w-full mx-4 space-y-4">
            <h3 className="text-lg font-semibold text-gray-900">Retranslate {brokenLanguages.length} broken language(s)?</h3>
            <p className="text-sm text-gray-600">
              The following languages will be regenerated from the approved EN master:
            </p>
            <div className="flex flex-wrap gap-1.5">
              {brokenLanguages.map(l => {
                const origin = job?.outputs?.[l]?.origin;
                return (
                  <span key={l} className={`text-xs px-2 py-0.5 rounded-full font-medium ${origin === 'video_native' ? 'bg-red-100 text-red-700' : 'bg-orange-100 text-orange-700'}`}>
                    {l.toUpperCase()} {origin === 'video_native' ? '(video-native)' : '(missing VTT)'}
                  </span>
                );
              })}
            </div>
            <p className="text-xs text-gray-400">Each language is queued separately. Translation may take a few minutes.</p>
            <div className="flex gap-3 justify-end pt-2">
              <button
                onClick={() => setShowBulkRetranslateConfirm(false)}
                className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50"
              >
                Cancel
              </button>
              <button
                onClick={handleBulkRetranslate}
                className="px-4 py-2 text-sm font-medium text-white bg-orange-600 rounded-md hover:bg-orange-700"
              >
                Yes, retranslate all
              </button>
            </div>
          </div>
        </div>
      )}

      {/* Per-language retranslate confirmation modal */}
      {retranslateLang && (
        <div className="fixed inset-0 bg-black/40 flex items-center justify-center z-50">
          <div className="bg-white rounded-lg shadow-xl p-6 max-w-md w-full mx-4 space-y-4">
            <h3 className="text-lg font-semibold text-gray-900">Re-translate {retranslateLang.toUpperCase()} from EN master?</h3>
            <p className="text-sm text-gray-600">
              This will discard the current <strong>{retranslateLang.toUpperCase()}</strong> translation,
              reset linguist progress for this language, and regenerate it from the approved English master.
            </p>
            <div>
              <label className="block text-xs font-medium text-gray-500 mb-1">Reason (optional)</label>
              <textarea
                rows={2}
                value={retranslateReason}
                onChange={e => setRetranslateReason(e.target.value)}
                placeholder="e.g. legacy video_native drift, cue count mismatch…"
                className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 resize-none"
              />
            </div>
            <div className="flex gap-3 justify-end pt-2">
              <button
                onClick={() => setRetranslateLang(null)}
                className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50"
              >
                Cancel
              </button>
              <button
                disabled={retranslateLoading}
                onClick={async () => {
                  if (!id) return;
                  setRetranslateLoading(true);
                  try {
                    await apiClient.retranslateLanguage(id, retranslateLang, retranslateReason || undefined);
                    toast.toastOnly.success(`Re-translating ${retranslateLang.toUpperCase()} from EN master…`);
                    queryClient.invalidateQueries({ queryKey: ['job', id] });
                    queryClient.invalidateQueries({ queryKey: ['language-qc', id] });
                    setRetranslateLang(null);
                  } catch {
                    toast.toastOnly.error('Failed to trigger retranslation');
                  } finally {
                    setRetranslateLoading(false);
                  }
                }}
                className="px-4 py-2 text-sm font-medium text-white bg-indigo-600 rounded-md hover:bg-indigo-700 disabled:opacity-50"
              >
                {retranslateLoading ? 'Starting…' : 'Yes, re-translate'}
              </button>
            </div>
          </div>
        </div>
      )}

      {/* Share link modal */}
      {showShareModal && (
        <div className="fixed inset-0 bg-black/40 flex items-center justify-center z-50">
          <div className="bg-white rounded-lg shadow-xl p-6 max-w-md w-full mx-4 space-y-4">
            <h3 className="text-lg font-medium">Share read-only link</h3>
            <p className="text-sm text-gray-500">Generate a public link that lets the client preview this job without logging in. Link expires in 30 days.</p>

            {!shareUrl ? (
              <>
                <div>
                  <label className="block text-xs font-medium text-gray-700 mb-1">Label (optional)</label>
                  <input
                    type="text"
                    placeholder="e.g. Sent to ACME 2026-05-01"
                    value={shareLabel}
                    onChange={e => setShareLabel(e.target.value)}
                    className="w-full text-sm border border-gray-300 rounded px-2 py-1.5"
                  />
                </div>
                <div className="flex gap-3 justify-end">
                  <button
                    onClick={() => setShowShareModal(false)}
                    className="px-4 py-2 text-sm border border-gray-300 rounded text-gray-700 hover:bg-gray-50"
                  >
                    Cancel
                  </button>
                  <button
                    onClick={() => createShareTokenMutation.mutate()}
                    disabled={createShareTokenMutation.isPending}
                    className="px-4 py-2 text-sm bg-indigo-600 text-white rounded hover:bg-indigo-700 disabled:opacity-50"
                  >
                    {createShareTokenMutation.isPending ? 'Generating…' : 'Generate link'}
                  </button>
                </div>
              </>
            ) : (
              <>
                <div className="flex items-center gap-2 p-3 bg-gray-50 rounded-lg border border-gray-200">
                  <input
                    readOnly
                    value={shareUrl}
                    className="flex-1 text-xs text-gray-700 bg-transparent outline-none select-all"
                    onFocus={e => e.target.select()}
                  />
                  <button
                    onClick={() => { navigator.clipboard.writeText(shareUrl); setShareCopied(true); setTimeout(() => setShareCopied(false), 2000); }}
                    className="text-xs px-2 py-1 bg-indigo-600 text-white rounded hover:bg-indigo-700 shrink-0"
                  >
                    {shareCopied ? 'Copied!' : 'Copy'}
                  </button>
                </div>
                <p className="text-xs text-gray-400">Anyone with this link can view the job. Link expires in 30 days.</p>
                <div className="flex justify-end">
                  <button
                    onClick={() => setShowShareModal(false)}
                    className="px-4 py-2 text-sm border border-gray-300 rounded text-gray-700 hover:bg-gray-50"
                  >
                    Close
                  </button>
                </div>
              </>
            )}
          </div>
        </div>
      )}

      {/* Rendering Status Banner */}
      {isRendering && (
        <div className="mb-6 p-4 bg-purple-50 border border-purple-200 rounded-md">
          <div className="flex items-center gap-3">
            <svg className="animate-spin h-5 w-5 text-purple-600" viewBox="0 0 24 24">
              <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none" />
              <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
            </svg>
            <span className="font-medium text-purple-900">
              Accessible video is being re-rendered with your changes...
            </span>
          </div>
        </div>
      )}

      {/* View Mode Toggle and Shortcuts */}
      <div className="mb-6 flex items-center justify-between">
        <div className="flex items-center space-x-4">
          <span className="text-sm font-medium text-gray-700">View Mode:</span>
          <div className="flex rounded-lg border border-gray-300">
            <button
              onClick={() => setViewMode('side-by-side')}
              className={`px-3 py-1 text-sm rounded-l-lg ${
                viewMode === 'side-by-side'
                  ? 'bg-blue-600 text-white'
                  : 'bg-white text-gray-700 hover:bg-gray-50'
              }`}
            >
              Side by Side <span className="text-xs opacity-75">(1)</span>
            </button>
            <button
              onClick={() => setViewMode('video-only')}
              className={`px-3 py-1 text-sm border-l border-gray-300 ${
                viewMode === 'video-only'
                  ? 'bg-blue-600 text-white'
                  : 'bg-white text-gray-700 hover:bg-gray-50'
              }`}
            >
              Video Only <span className="text-xs opacity-75">(2)</span>
            </button>
            <button
              onClick={() => setViewMode('editor-only')}
              className={`px-3 py-1 text-sm rounded-r-lg border-l border-gray-300 ${
                viewMode === 'editor-only'
                  ? 'bg-blue-600 text-white'
                  : 'bg-white text-gray-700 hover:bg-gray-50'
              }`}
            >
              Editor Only <span className="text-xs opacity-75">(3)</span>
            </button>
          </div>
        </div>
        
        <div className="text-xs text-gray-500">
          <span className="font-medium">Shortcuts:</span> A=Approve, R=Reject
        </div>
      </div>

      {/* Video Preview */}
      {(viewMode === 'side-by-side' || viewMode === 'video-only') && (
        <div className="mb-8">
          <div className="bg-white border border-gray-200 rounded-lg p-6">
            <div className="flex items-center justify-between mb-4">
              <h3 className="text-lg font-medium text-gray-900">Video Preview</h3>

              {/* Video Mode Tabs - only show if accessible video is available */}
              {(accessibleVideoUrl || editState?.video_segments?.length) && (
                <div className="flex rounded-lg border border-gray-300 overflow-hidden">
                  <button
                    onClick={() => setVideoMode('original')}
                    className={`px-4 py-1.5 text-sm font-medium transition-colors ${
                      videoMode === 'original'
                        ? 'bg-blue-600 text-white'
                        : 'bg-white text-gray-700 hover:bg-gray-50'
                    }`}
                  >
                    Original Video
                  </button>
                  <button
                    onClick={() => setVideoMode('accessible')}
                    className={`px-4 py-1.5 text-sm font-medium border-l border-gray-300 transition-colors ${
                      videoMode === 'accessible'
                        ? 'bg-purple-600 text-white'
                        : 'bg-white text-gray-700 hover:bg-gray-50'
                    }`}
                  >
                    Accessible Video
                  </button>
                </div>
              )}
            </div>

            {/* Original Video Mode */}
            {videoMode === 'original' && (
              <>
                {videoUrl ? (
                  <VideoWithCaptions
                    videoUrl={videoUrl}
                    captionsVtt={captionsVtt}
                    audioDescriptionVtt={adVtt}
                    sourceLanguage={selectedLanguage}
                  />
                ) : (
                  <div className="text-center py-8 text-gray-500">
                    <svg className="w-12 h-12 mx-auto mb-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                      <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" />
                    </svg>
                    <p>Video preview will be available once processing begins</p>
                  </div>
                )}
              </>
            )}

            {/* Accessible Video Mode */}
            {videoMode === 'accessible' && (
              <>
                {accessibleVideoUrl ? (
                  <div className="space-y-4">
                    <div className="relative bg-black rounded-lg overflow-hidden">
                      <video
                        ref={videoRef}
                        src={accessibleVideoUrl}
                        controls
                        className="w-full"
                        onTimeUpdate={(e) => setCurrentVideoTimeMs(e.currentTarget.currentTime * 1000)}
                      >
                        Your browser does not support the video tag.
                      </video>
                      {currentAccessibleCaption && (
                        <div className="absolute bottom-14 left-1/2 -translate-x-1/2 bg-black/80 text-white text-sm px-4 py-1.5 rounded max-w-[90%] text-center pointer-events-none whitespace-pre-wrap">
                          {currentAccessibleCaption.text}
                        </div>
                      )}
                    </div>

                    {/* Timeline Preview */}
                    {editState && editState.video_segments && editState.video_segments.length > 0 && (
                      <div className="mt-4 p-4 bg-gray-50 rounded-lg">
                        <h4 className="text-sm font-medium text-gray-700 mb-3">Timeline Preview</h4>
                        <TimelinePreview
                          segments={editState.video_segments}
                          pausePoints={editState.pause_points || []}
                          totalDurationMs={editState.total_duration_ms || 0}
                          currentTimeMs={currentVideoTimeMs}
                          onSegmentClick={handleSegmentClick}
                          onPausePointClick={handlePausePointClick}
                          onPausePointUpdate={handlePausePointUpdate}
                          onRegenerateTTS={handleRegenerateTTS}
                          regenerationQueue={pendingRegenerations}
                          isStale={pausePointsModified || pendingRegenerations.length > 0 || adVttUploaded}
                          onPausePointAdd={handlePausePointAdd}
                        />
                      </div>
                    )}

                    {/* AD VTT replaced banner */}
                    {adVttUploaded && (
                      <div className="mb-3 p-3 bg-amber-50 border border-amber-200 rounded-md text-sm text-amber-800">
                        Audio Description script was replaced — re-render the accessible video to apply the new script.
                      </div>
                    )}

                    {/* Re-render Controls */}
                    {editState && (
                      <RerenderControls
                        pendingRegenerations={pendingRegenerations}
                        pausePointsModified={pausePointsModified}
                        adVttUploaded={adVttUploaded}
                        isRendering={isRendering}
                        onRender={handleRender}
                        onClearQueue={handleClearRegenerationQueue}
                      />
                    )}
                  </div>
                ) : editStateLoading ? (
                  <div className="text-center py-8 text-gray-500">
                    <svg className="animate-spin h-8 w-8 mx-auto mb-4 text-purple-600" viewBox="0 0 24 24">
                      <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none" />
                      <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
                    </svg>
                    <p>Loading accessible video...</p>
                  </div>
                ) : (
                  <div className="text-center py-8 text-gray-500">
                    <svg className="w-12 h-12 mx-auto mb-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                      <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" />
                    </svg>
                    <p>Accessible video not available for this language yet.</p>
                    <p className="text-sm mt-1">Translation and TTS synthesis may still be in progress.</p>
                  </div>
                )}
              </>
            )}
          </div>
        </div>
      )}

      {/* VTT Editors */}
      {viewMode !== 'video-only' && (
        <div className={`mb-8 ${viewMode === 'side-by-side' ? 'grid grid-cols-1 lg:grid-cols-2 gap-6' : ''}`}>
          {job.requested_outputs.captions_vtt && (
            <div>
              <div className="flex items-center justify-between mb-2">
                <span className="text-sm font-medium text-gray-700">Closed Captions ({selectedLanguage.toUpperCase()})</span>
                <button
                  type="button"
                  onClick={() => captionsFileInputRef.current?.click()}
                  disabled={isProcessing}
                  className="px-2 py-1 text-xs bg-gray-100 text-gray-600 border border-gray-300 rounded hover:bg-gray-200 disabled:opacity-50"
                >
                  Upload .vtt
                </button>
              </div>
              <VttEditor
                vttContent={captionsVtt}
                onChange={handleCaptionsChange}
                onCueSave={handleCaptionsCueSave}
                title={`Closed Captions (${selectedLanguage.toUpperCase()})`}
                readOnly={isProcessing}
                glossaryTerms={glossaryTerms}
                language={selectedLanguage}
                isSourceLanguage={selectedLanguage === sourceLanguage}
                affectedLanguagesCount={
                  selectedLanguage === sourceLanguage
                    ? Object.entries(langQcMap).filter(
                        ([lang, state]) =>
                          lang !== sourceLanguage &&
                          ['approved', 'pending_review', 'in_review'].includes(
                            (state as { status?: string }).status ?? ''
                          )
                      ).length
                    : 0
                }
                onRetranslate={
                  selectedLanguage === sourceLanguage
                    ? async () => { await _doSaveVtt(true, captionsVtt || undefined, adVtt || undefined); }
                    : undefined
                }
              />
              <div className="mt-2">
                <VttDiffView jobId={id!} lang={selectedLanguage} kind="captions" />
              </div>
            </div>
          )}

          {job.requested_outputs.audio_description_vtt && (
            <div>
              <div className="flex items-center justify-between mb-2">
                <span className="text-sm font-medium text-gray-700">Audio Description ({selectedLanguage.toUpperCase()})</span>
                <button
                  type="button"
                  onClick={() => adFileInputRef.current?.click()}
                  disabled={isProcessing}
                  className="px-2 py-1 text-xs bg-gray-100 text-gray-600 border border-gray-300 rounded hover:bg-gray-200 disabled:opacity-50"
                >
                  Upload .vtt
                </button>
              </div>
              <VttEditor
                vttContent={adVtt}
                onChange={handleAdChange}
                onCueSave={handleAdCueSave}
                onCueInserted={handleAdCueInserted}
                onCueDeleted={handleAdCueDeleted}
                onCuePlay={accessibleVideoUrl ? handleAdCuePlay : undefined}
                title={`Audio Description (${selectedLanguage.toUpperCase()})`}
                readOnly={isProcessing}
                glossaryTerms={glossaryTerms}
                language={selectedLanguage}
                insertAtTimeMs={adInsertAtTimeMs}
                onInsertAtTimeDone={() => setAdInsertAtTimeMs(null)}
                isSourceLanguage={selectedLanguage === sourceLanguage}
                affectedLanguagesCount={
                  selectedLanguage === sourceLanguage
                    ? Object.entries(langQcMap).filter(
                        ([lang, state]) =>
                          lang !== sourceLanguage &&
                          ['approved', 'pending_review', 'in_review'].includes(
                            (state as { status?: string }).status ?? ''
                          )
                      ).length
                    : 0
                }
                onRetranslate={
                  selectedLanguage === sourceLanguage
                    ? async () => { await _doSaveVtt(true, captionsVtt || undefined, adVtt || undefined); }
                    : undefined
                }
              />
              <div className="mt-2">
                <VttDiffView jobId={id!} lang={selectedLanguage} kind="ad" />
              </div>
            </div>
          )}
        </div>
      )}

      {/* Timing Adjustment */}
      <div className="mb-6 p-4 bg-gray-50 border border-gray-200 rounded-lg">
        <div className="flex items-center justify-between mb-4">
          <h3 className="text-lg font-medium text-gray-900">VTT Timing Adjustment</h3>
          <button
            onClick={() => setShowTimingAdjustment(!showTimingAdjustment)}
            className="px-3 py-1 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700"
          >
            {showTimingAdjustment ? 'Hide' : 'Adjust Timing'}
          </button>
        </div>
        
        {showTimingAdjustment && (
          <div className="space-y-4">
            <div className="text-sm text-gray-600">
              Move captions forward or backward in time if they're not synchronized with the video.
            </div>
            
            <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
              <div>
                <label className="block text-sm font-medium text-gray-700 mb-2">
                  Time Offset (seconds)
                </label>
                <div className="flex items-center space-x-2">
                  <input
                    type="number"
                    step="0.1"
                    min="-30"
                    max="30"
                    value={timingOffset}
                    onChange={(e) => setTimingOffset(parseFloat(e.target.value) || 0)}
                    className="w-24 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
                    disabled={isProcessing}
                  />
                  <div className="flex space-x-1">
                    <button
                      onClick={() => setTimingOffset(prev => Math.max(-30, prev - 0.5))}
                      className="px-2 py-1 text-sm bg-gray-200 text-gray-700 rounded hover:bg-gray-300"
                      disabled={isProcessing}
                    >
                      -0.5s
                    </button>
                    <button
                      onClick={() => setTimingOffset(prev => Math.min(30, prev + 0.5))}
                      className="px-2 py-1 text-sm bg-gray-200 text-gray-700 rounded hover:bg-gray-300"
                      disabled={isProcessing}
                    >
                      +0.5s
                    </button>
                    <button
                      onClick={() => setTimingOffset(prev => Math.max(-30, prev - 1))}
                      className="px-2 py-1 text-sm bg-gray-200 text-gray-700 rounded hover:bg-gray-300"
                      disabled={isProcessing}
                    >
                      -1s
                    </button>
                    <button
                      onClick={() => setTimingOffset(prev => Math.min(30, prev + 1))}
                      className="px-2 py-1 text-sm bg-gray-200 text-gray-700 rounded hover:bg-gray-300"
                      disabled={isProcessing}
                    >
                      +1s
                    </button>
                  </div>
                </div>
                <div className="mt-1 text-xs text-gray-500">
                  Positive = move later, Negative = move earlier
                </div>
              </div>
              
              <div>
                <label className="block text-sm font-medium text-gray-700 mb-2">
                  Apply to:
                </label>
                <div className="space-y-2">
                  {job?.requested_outputs.captions_vtt && (
                    <label className="flex items-center">
                      <input
                        type="checkbox"
                        checked={adjustCaptions}
                        onChange={(e) => setAdjustCaptions(e.target.checked)}
                        className="mr-2"
                        disabled={isProcessing}
                      />
                      <span className="text-sm">Closed Captions</span>
                    </label>
                  )}
                  {job?.requested_outputs.audio_description_vtt && (
                    <label className="flex items-center">
                      <input
                        type="checkbox"
                        checked={adjustAudioDescription}
                        onChange={(e) => setAdjustAudioDescription(e.target.checked)}
                        className="mr-2"
                        disabled={isProcessing}
                      />
                      <span className="text-sm">Audio Description</span>
                    </label>
                  )}
                </div>
              </div>
            </div>
            
            <div className="flex items-center justify-between pt-4 border-t border-gray-200">
              <div className="text-xs text-gray-500">
                This will adjust all cue timings by the specified amount
              </div>
              <div className="flex space-x-2">
                <button
                  onClick={() => {
                    setShowTimingAdjustment(false);
                    setTimingOffset(0);
                  }}
                  disabled={isProcessing}
                  className="px-4 py-2 text-sm bg-gray-300 text-gray-700 rounded-md hover:bg-gray-400"
                >
                  Cancel
                </button>
                <button
                  onClick={handleTimingAdjustment}
                  disabled={isProcessing || timingOffset === 0 || (!adjustCaptions && !adjustAudioDescription)}
                  className="px-4 py-2 text-sm bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50"
                >
                  {adjustTimingMutation.isPending ? 'Adjusting...' : `Apply ${timingOffset > 0 ? '+' : ''}${timingOffset}s`}
                </button>
              </div>
            </div>
          </div>
        )}
      </div>

      {/* Voice Settings - Only show if MP3 generation is requested */}
      {job?.requested_outputs?.audio_description_mp3 && (
        <div className="mb-6 border border-gray-200 rounded-lg overflow-hidden">
          <button
            type="button"
            onClick={() => setShowVoiceSettings(!showVoiceSettings)}
            className="w-full flex items-center justify-between px-4 py-3 bg-gray-50 hover:bg-gray-100 transition-colors"
          >
            <div className="flex items-center gap-2">
              <svg
                className={`w-4 h-4 text-gray-500 transition-transform ${showVoiceSettings ? 'rotate-90' : ''}`}
                fill="none"
                stroke="currentColor"
                viewBox="0 0 24 24"
              >
                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
              </svg>
              <span className="text-sm font-medium text-gray-700">Voice Settings for Audio Description</span>
              {job.requested_outputs.tts_preferences && (
                <span className="text-xs text-blue-600">(Configured)</span>
              )}
            </div>
            <span className="text-xs text-gray-500">
              {showVoiceSettings ? 'Click to collapse' : 'Click to customize or override TTS voice'}
            </span>
          </button>
          {showVoiceSettings && (
            <div className="p-4 border-t border-gray-200">
              <VoiceSelector
                selectedLanguages={[sourceLanguage, ...(job.requested_outputs.languages || []).filter(l => l !== sourceLanguage)]}
                preferences={ttsPreferences}
                onChange={setTtsPreferences}
                disabled={isProcessing}
              />
              <div className="mt-4 pt-4 border-t border-gray-200">
                <div className="flex items-center justify-between">
                  <p className="text-xs text-gray-500">
                    Saving will regenerate all audio description audio with the new voice settings.
                  </p>
                  <button
                    type="button"
                    onClick={handleSaveVoiceSettings}
                    disabled={!voiceSettingsChanged || isProcessing}
                    className="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
                  >
                    {updateTTSPreferencesMutation.isPending ? 'Saving...' : 'Save Voice Settings'}
                  </button>
                </div>
                {voiceSettingsChanged && (
                  <p className="mt-2 text-xs text-amber-600">
                    Voice settings have been modified. Click "Save Voice Settings" to apply changes.
                  </p>
                )}
              </div>
            </div>
          )}
        </div>
      )}

      {/* Cost Tracker Project */}
      <div className="mb-6 p-4 border border-gray-200 rounded-lg">
        <label className="block text-sm font-medium text-gray-700 mb-2">
          Cost Tracker Project ID
        </label>
        <div className="flex gap-2">
          <input
            type="text"
            value={costProjectId}
            onChange={(e) => { setCostProjectId(e.target.value); setCostProjectIdSaved(false); }}
            placeholder="e.g. ford-q3-2026"
            className="flex-1 px-3 py-1.5 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
          />
          <button
            type="button"
            disabled={updateJobMutation.isPending}
            onClick={async () => {
              if (!id) return;
              await updateJobMutation.mutateAsync({ id, data: { cost_tracker_project_id: costProjectId || null } as Record<string, unknown> });
              setCostProjectIdSaved(true);
            }}
            className="px-3 py-1.5 text-sm bg-gray-100 border border-gray-300 rounded-md hover:bg-gray-200 disabled:opacity-50"
          >
            Save
          </button>
          {costProjectIdSaved && <span className="self-center text-xs text-green-600">Saved</span>}
        </div>
        <p className="mt-1 text-xs text-gray-400">Links this job to a project in the AI Cost Dashboard.</p>
      </div>

      {/* Download Assets */}
      <div className="mb-6 border border-gray-200 rounded-lg overflow-hidden">
        <button
          type="button"
          onClick={() => setShowDownloads(!showDownloads)}
          className="w-full flex items-center justify-between px-4 py-3 bg-gray-50 hover:bg-gray-100 transition-colors"
        >
          <div className="flex items-center gap-2">
            <svg
              className={`w-4 h-4 text-gray-500 transition-transform ${showDownloads ? 'rotate-90' : ''}`}
              fill="none"
              stroke="currentColor"
              viewBox="0 0 24 24"
            >
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
            </svg>
            <span className="text-sm font-medium text-gray-700">Download Assets</span>
          </div>
          <span className="text-xs text-gray-500">
            {showDownloads ? 'Click to collapse' : 'Click to download individual files'}
          </span>
        </button>
        {showDownloads && (
          <div className="p-4 border-t border-gray-200 space-y-3">
            {/* Source video */}
            {typeof downloads?.downloads?.source_video === 'string' && downloads.downloads.source_video && (
              <div className="flex items-center justify-between">
                <span className="text-sm text-gray-600">Source Video</span>
                <a
                  href={downloads.downloads.source_video}
                  download
                  target="_blank"
                  rel="noreferrer"
                  className="px-3 py-1 text-xs bg-blue-50 text-blue-700 border border-blue-200 rounded-full hover:bg-blue-100"
                >
                  Source (.mp4)
                </a>
              </div>
            )}

            {/* Per-language assets */}
            {(() => {
              const langAssets = downloads?.downloads?.[selectedLanguage];
              if (!langAssets || typeof langAssets !== 'object') return null;
              const assets = langAssets as Record<string, string>;
              const assetDefs = [
                { key: 'captions_vtt', label: 'Closed Captions (.vtt)' },
                { key: 'sdh_captions_vtt', label: 'SDH Captions (.vtt)' },
                { key: 'audio_description_vtt', label: 'Audio Description (.vtt)' },
                { key: 'audio_description_mp3', label: 'Audio Description (.mp3)' },
                { key: 'accessible_video_mp4', label: 'Accessible Video (.mp4)' },
                { key: 'accessible_captions_vtt', label: 'Retimed Captions (.vtt)' },
                { key: 'descriptive_transcript', label: 'Descriptive Transcript (.txt)' },
              ];
              return assetDefs
                .filter(({ key }) => assets[key])
                .map(({ key, label }) => (
                  <div key={key} className="flex items-center justify-between">
                    <span className="text-sm text-gray-600">{label}</span>
                    <a
                      href={assets[key]}
                      download
                      target="_blank"
                      rel="noreferrer"
                      className="px-3 py-1 text-xs bg-blue-50 text-blue-700 border border-blue-200 rounded-full hover:bg-blue-100"
                    >
                      {selectedLanguage.toUpperCase()} — {label.split('(')[1]?.replace(')', '') ?? 'file'}
                    </a>
                  </div>
                ));
            })()}

            {!downloads?.downloads && (
              <p className="text-sm text-gray-500">No download links available yet.</p>
            )}
          </div>
        )}
      </div>

      {/* Review Notes */}
      <div className="mb-6">
        <label className="block text-sm font-medium text-gray-700 mb-2">
          Review Notes
        </label>
        <textarea
          value={reviewNotes}
          onChange={(e) => setReviewNotes(e.target.value)}
          className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
          rows={3}
          placeholder="Add any notes about this review..."
          disabled={isProcessing}
        />
      </div>

      {/* Hidden file inputs for VTT upload */}
      <input
        ref={captionsFileInputRef}
        type="file"
        accept=".vtt"
        className="hidden"
        onChange={e => e.target.files?.[0] && handleVttFileUpload('captions', e.target.files[0])}
      />
      <input
        ref={adFileInputRef}
        type="file"
        accept=".vtt"
        className="hidden"
        onChange={e => e.target.files?.[0] && handleVttFileUpload('ad', e.target.files[0])}
      />

      {/* Action Buttons — job-level fast-path for PROD/ADMIN only */}
      {canApproveAll && (
        <div className="flex gap-4">
          {!showRejectForm ? (
            <>
              <button
                onClick={handleApprove}
                disabled={isProcessing}
                title="Approve all languages at once (admin fast-path)"
                className="px-6 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 disabled:opacity-50"
              >
                {approveEnglishMutation.isPending ? 'Approving...' : 'Approve All Languages'}
              </button>
              <button
                onClick={() => setShowRejectForm(true)}
                disabled={isProcessing}
                className="px-6 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 disabled:opacity-50"
              >
                Reject Job
              </button>
            </>
          ) : (
            <div className="flex items-center gap-4">
              <div className="text-sm text-red-600 font-medium">
                Rejection notes are required:
              </div>
              <button
                onClick={handleReject}
                disabled={isProcessing || !reviewNotes.trim()}
                className="px-6 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 disabled:opacity-50"
              >
                {rejectJobMutation.isPending ? 'Rejecting...' : 'Confirm Rejection'}
              </button>
              <button
                onClick={() => setShowRejectForm(false)}
                disabled={isProcessing}
                className="px-6 py-2 bg-gray-300 text-gray-700 rounded-md hover:bg-gray-400"
              >
                Cancel
              </button>
            </div>
          )}
        </div>
      )}
    </div>
  );
}