import { useState, useEffect, useRef, useCallback } from "react";
import { Script, Knobs, Job, RenderJobResult, TaskStatus, VideoGenerationSettings } from "../types";
import { podcastApi } from "../../../services/podcastApi";

interface UseRenderQueueProps {
  script: Script;
  jobs: Job[];
  knobs: Knobs;
  projectId: string;
  budgetCap?: number;
  avatarImageUrl?: string | null;
  onUpdateJob: (sceneId: string, updates: Partial<Job>) => void;
  onUpdateScript?: (script: Script) => void;
  onError: (message: string) => void;
}

const getSceneVoiceEmotion = (knobs: Knobs) => knobs.voice_emotion || "neutral";

export const useRenderQueue = ({
  script,
  jobs,
  knobs,
  projectId,
  budgetCap,
  avatarImageUrl,
  onUpdateJob,
  onUpdateScript,
  onError,
}: UseRenderQueueProps) => {
  const [rendering, setRendering] = useState<string | null>(null);
  const [generatingImage, setGeneratingImage] = useState<string | null>(null);
  const [combiningAudio, setCombiningAudio] = useState(false);
  const [combinedAudioResult, setCombinedAudioResult] = useState<{
    url: string;
    filename: string;
    duration: number;
    sceneCount: number;
  } | null>(null);
  const [combiningVideos, setCombiningVideos] = useState(false);
  const [finalVideoUrl, setFinalVideoUrl] = useState<string | null>(null);
  const [combiningProgress, setCombiningProgress] = useState<{ progress: number; message: string } | null>(null);
  const pollingIntervals = useRef<Map<string, NodeJS.Timeout>>(new Map());
  const pollingErrorCounts = useRef<Map<string, number>>(new Map());

  // Cleanup polling intervals on unmount
  useEffect(() => {
    const intervals = pollingIntervals.current;
    return () => {
      intervals.forEach((interval) => clearInterval(interval));
      intervals.clear();
      pollingErrorCounts.current.clear();
    };
  }, []);

  // Initialize jobs if empty (audio/image only)
  useEffect(() => {
    if (jobs.length === 0 && script.scenes.length > 0) {
      const initialJobs: Job[] = script.scenes.map((s) => {
        const hasExistingAudio = Boolean(s.audioUrl);
        return {
          sceneId: s.id,
          title: s.title,
          status: hasExistingAudio ? ("completed" as const) : ("idle" as const),
          progress: hasExistingAudio ? 100 : 0,
          previewUrl: null,
          finalUrl: hasExistingAudio ? s.audioUrl || null : null,
          imageUrl: s.imageUrl || null,
          jobId: null,
        };
      });
      initialJobs.forEach((job) => {
        onUpdateJob(job.sceneId, job);
      });
    }
  }, [jobs.length, script.scenes.length, onUpdateJob, script.scenes]);

  // Load final video URL from project on mount (for persistence across reloads)
  useEffect(() => {
    if (!projectId) return;

    podcastApi
      .loadProject(projectId)
      .then((project) => {
        if (project.final_video_url) {
          console.log("[useRenderQueue] Loaded final video URL from project:", project.final_video_url);
          setFinalVideoUrl(project.final_video_url);
        }
      })
      .catch((error) => {
        console.error("[useRenderQueue] Failed to load project for final video URL:", error);
        // Don't show error to user - this is just for restoring state
      });
  }, [projectId]);

  // Always try to attach existing videos to scenes (even after reloads)
  useEffect(() => {
    if (script.scenes.length === 0) return;

    podcastApi
      .listVideos(projectId)
      .then((result) => {
        const videoMap = new Map<number, string>();
        
        result.videos.forEach((video) => {
          // Use the most recent video for each scene number
          if (!videoMap.has(video.scene_number)) {
            // Store the raw video URL - SceneCard will handle authentication via blob loading
            videoMap.set(video.scene_number, video.video_url);
          }
        });

        script.scenes.forEach((scene) => {
          const sceneNumberMatch = scene.id.match(/\d+/);
          const sceneNumber = sceneNumberMatch ? parseInt(sceneNumberMatch[0], 10) : null;
          
          if (sceneNumber === null) return;

          const videoUrl = videoMap.get(sceneNumber);
          if (!videoUrl) return;

          const job = jobs.find((j) => j.sceneId === scene.id);

          // Avoid redundant updates
          if (job?.videoUrl === videoUrl) return;

          onUpdateJob(scene.id, {
            sceneId: scene.id,
            title: scene.title,
            videoUrl,
            status: "completed" as const,
            progress: 100,
          });
        });
      })
      .catch((error) => {
        console.error("[useRenderQueue] Failed to list existing videos:", error);
      });
  }, [projectId, script.scenes, jobs, onUpdateJob]);

  // Periodic check to rescue videos that were generated but not detected by polling
  useEffect(() => {
    if (rendering && script.scenes.length > 0) {
      const rescueInterval = setInterval(async () => {
        // Check for videos every 2 minutes while rendering is active
        try {
          const videoList = await podcastApi.listVideos(projectId);
          
          const videoMap = new Map<number, string>();
          videoList.videos.forEach((video) => {
            if (!videoMap.has(video.scene_number)) {
              // Store the raw video URL - SceneCard will handle authentication via blob loading
              videoMap.set(video.scene_number, video.video_url);
            }
          });

          // Update jobs for scenes that have videos but no videoUrl set
          script.scenes.forEach((scene) => {
            const sceneNumberMatch = scene.id.match(/\d+/);
            const sceneNumber = sceneNumberMatch ? parseInt(sceneNumberMatch[0], 10) : null;
            if (sceneNumber !== null) {
              const videoUrl = videoMap.get(sceneNumber);
              const job = jobs.find((j) => j.sceneId === scene.id);
              
              if (videoUrl) {
                if (!job) {
                  onUpdateJob(scene.id, { 
                    sceneId: scene.id,
                    title: scene.title,
                    status: "completed" as const,
                    progress: 100,
                    videoUrl,
                  });
                } else if (!job.videoUrl) {
                  onUpdateJob(scene.id, { videoUrl, status: "completed" as const, progress: 100 });
                  // If this was the rendering scene, stop rendering
                  if (rendering === scene.id) {
                    setRendering(null);
                  }
                }
              }
            }
          });
        } catch (error) {
          console.error("[useRenderQueue] Failed to rescue videos:", error);
        }
      }, 120000); // Check every 2 minutes

      return () => clearInterval(rescueInterval);
    }
  }, [rendering, script.scenes, jobs, projectId, onUpdateJob]);

  const getScene = useCallback((sceneId: string) => script.scenes.find((s) => s.id === sceneId), [script.scenes]);

  const pollTaskStatus = useCallback(async (taskId: string, sceneId: string) => {
    try {
      const status: TaskStatus | null = await podcastApi.pollTaskStatus(taskId);

      // Handle null response (task not found)
      if (!status) {
        const errorCount = (pollingErrorCounts.current.get(sceneId) || 0) + 1;
        pollingErrorCounts.current.set(sceneId, errorCount);
        
        // Stop polling after 3 consecutive "task not found" errors
        if (errorCount >= 3) {
          onUpdateJob(sceneId, { status: "failed", progress: 0 });
          const interval = pollingIntervals.current.get(sceneId);
          if (interval) {
            clearInterval(interval);
            pollingIntervals.current.delete(sceneId);
          }
          pollingErrorCounts.current.delete(sceneId);
          setRendering(null);
          onError("Video generation task not found. The task may have expired or been cancelled.");
          return true; // Stop polling
        }
        return false; // Continue polling (might be transient)
      }

      // Reset error count on successful poll
      pollingErrorCounts.current.delete(sceneId);

      onUpdateJob(sceneId, {
        progress: status.progress ?? 0,
        status: status.status === "completed" ? "completed" : status.status === "failed" ? "failed" : "running",
      });

      // Check for completion - handle both "completed" and "processing" with 100% progress
      const isCompleted = status.status === "completed" || (status.status === "processing" && status.progress === 100);
      
      if (isCompleted && status.result) {
        const result = status.result;
        console.log("[useRenderQueue] Task completed, extracting video URL", {
          result,
          video_url: result.video_url,
          status: status.status,
          progress: status.progress,
        });
        
        let videoUrl = result.video_url;
        if (!videoUrl) {
          console.error("[useRenderQueue] No video_url in result! Attempting to rescue from file system...", { result });
          // Try to rescue: check if video exists for this scene
          const sceneNumberMatch = getScene(sceneId)?.id.match(/\d+/);
          const sceneNumber = sceneNumberMatch ? parseInt(sceneNumberMatch[0], 10) : null;
          if (sceneNumber !== null) {
            podcastApi
              .listVideos(projectId)
              .then((videoList) => {
                const sceneVideo = videoList.videos.find((v) => v.scene_number === sceneNumber);
                if (sceneVideo) {
                  // Store the raw video URL - SceneCard will handle authentication via blob loading
                  onUpdateJob(sceneId, {
                    status: "completed",
                    progress: 100,
                    videoUrl: sceneVideo.video_url,
                    cost: result.cost || 0,
                  });
                }
              })
              .catch((err) => console.error("[useRenderQueue] Failed to rescue video:", err));
          }
          return true; // Stop polling
        }
        
        // Store the raw video URL - SceneCard will handle authentication via blob loading
        onUpdateJob(sceneId, {
          status: "completed",
          progress: 100,
          videoUrl,
          cost: result.cost,
        });

        const interval = pollingIntervals.current.get(sceneId);
        if (interval) {
          clearInterval(interval);
          pollingIntervals.current.delete(sceneId);
        }
        setRendering(null);
        return true; // Stop polling
      } else if (status.status === "failed") {
        // Extract user-friendly error message
        let errorMessage = "Video generation failed";
        if (status.error) {
          // Try to extract meaningful error from various formats
          const errorStr = status.error;
          if (errorStr.includes("Insufficient credits")) {
            errorMessage = "Video generation failed: Insufficient WaveSpeed credits. Please top up your account.";
          } else if (errorStr.includes("HTTPException") || errorStr.includes("502")) {
            // Extract the actual error message from HTTPException details
            const match = errorStr.match(/message[":\s]+"([^"]+)"/i) || errorStr.match(/detail[":\s]+"([^"]+)"/i);
            if (match && match[1]) {
              errorMessage = `Video generation failed: ${match[1]}`;
            } else {
              errorMessage = `Video generation failed: ${errorStr}`;
            }
          } else {
            errorMessage = `Video generation failed: ${errorStr}`;
          }
        }

        onUpdateJob(sceneId, { status: "failed", progress: 0 });
        const interval = pollingIntervals.current.get(sceneId);
        if (interval) {
          clearInterval(interval);
          pollingIntervals.current.delete(sceneId);
        }
        pollingErrorCounts.current.delete(sceneId);
        setRendering(null);
        onError(errorMessage);
        return true; // Stop polling
      }

      return false; // Continue polling
    } catch (error) {
      console.error("Error polling task status:", error);
      const errorCount = (pollingErrorCounts.current.get(sceneId) || 0) + 1;
      pollingErrorCounts.current.set(sceneId, errorCount);
      
      // Stop polling after 5 consecutive network errors
      if (errorCount >= 5) {
        onUpdateJob(sceneId, { status: "failed", progress: 0 });
        const interval = pollingIntervals.current.get(sceneId);
        if (interval) {
          clearInterval(interval);
          pollingIntervals.current.delete(sceneId);
        }
        pollingErrorCounts.current.delete(sceneId);
        setRendering(null);
        const errorMsg = error instanceof Error ? error.message : String(error);
        onError(`Video generation failed: Unable to check status. ${errorMsg}`);
        return true; // Stop polling
      }
      return false; // Continue polling (might be transient network error)
    }
  }, [onUpdateJob, onError]);

  const startPolling = useCallback((taskId: string, sceneId: string) => {
    const existingInterval = pollingIntervals.current.get(sceneId);
    if (existingInterval) {
      clearInterval(existingInterval);
    }

    const interval = setInterval(async () => {
      const isComplete = await pollTaskStatus(taskId, sceneId);
      if (isComplete) {
        clearInterval(interval);
        pollingIntervals.current.delete(sceneId);
      }
    }, 3000);

    pollingIntervals.current.set(sceneId, interval);
  }, [pollTaskStatus]);

  const runRender = useCallback(async (sceneId: string, mode: "preview" | "full") => {
    if (rendering && rendering !== sceneId) return;
    const job = jobs.find((j) => j.sceneId === sceneId);
    if (job && job.status !== "idle") return;
    const scene = getScene(sceneId);
    if (!scene) return;

    const textLength = scene.lines.map((l) => l.text).join(" ").length;
    const estimatedCost = (textLength / 1000) * 0.05;

    if (budgetCap && budgetCap > 0) {
      const currentSpent = jobs
        .filter((j) => j.status === "completed" && j.cost)
        .reduce((sum, j) => sum + (j.cost || 0), 0);

      if (currentSpent + estimatedCost > budgetCap) {
        onError(`Budget cap exceeded. Estimated cost: $${estimatedCost.toFixed(4)}, Budget remaining: $${(budgetCap - currentSpent).toFixed(2)}`);
        return;
      }
    }

    setRendering(sceneId);
    onUpdateJob(sceneId, {
      status: mode === "preview" ? "previewing" : "running",
      progress: mode === "preview" ? 25 : 40,
    });

    try {
      const result: RenderJobResult = await podcastApi.renderSceneAudio({
        scene,
        voiceId: "Wise_Woman",
        emotion: scene.emotion || getSceneVoiceEmotion(knobs),
        speed: knobs.voice_speed,
      });

      const updates: Partial<Job> = {
        status: "completed",
        progress: 100,
        cost: result.cost,
        provider: result.provider,
        voiceId: result.voiceId,
        fileSize: result.fileSize,
      };

      if (mode === "preview") {
        updates.previewUrl = result.audioUrl;
        window.open(result.audioUrl, "_blank");
      } else {
        updates.finalUrl = result.audioUrl;
        try {
          await podcastApi.saveAudioToAssetLibrary({
            audioUrl: result.audioUrl,
            filename: result.audioFilename,
            title: `${scene.title} - ${projectId}`,
            description: `Podcast episode scene audio: ${scene.title}`,
            projectId,
            sceneId,
            cost: result.cost,
            provider: result.provider,
            model: result.model,
            fileSize: result.fileSize,
          });
        } catch (assetError) {
          console.error("Failed to save to asset library:", assetError);
        }
      }

      onUpdateJob(sceneId, updates);
    } catch (error) {
      onUpdateJob(sceneId, { status: "failed", progress: 0 });
      const message = error instanceof Error ? error.message : "Render failed";
      onError(message);
    } finally {
      setRendering(null);
    }
  }, [rendering, jobs, getScene, knobs, budgetCap, projectId, onUpdateJob, onError]);

  const runImageGeneration = useCallback(async (sceneId: string) => {
    if (generatingImage && generatingImage !== sceneId) return;
    const scene = getScene(sceneId);
    if (!scene) return;

    setGeneratingImage(sceneId);
    try {
      const sceneContent = scene.lines.map((line) => line.text).join(" ");
      const result = await podcastApi.generateSceneImage({
        sceneId: scene.id,
        sceneTitle: scene.title,
        sceneContent: sceneContent,
        baseAvatarUrl: avatarImageUrl || undefined, // Use base avatar if available
        width: 1024,
        height: 1024,
      });

      // Update job with image URL
      onUpdateJob(sceneId, {
        imageUrl: result.image_url,
      });

      // Also update the scene's imageUrl so it persists
      if (onUpdateScript) {
        const updatedScenes = script.scenes.map((s) =>
          s.id === sceneId ? { ...s, imageUrl: result.image_url } : s
        );
        onUpdateScript({ ...script, scenes: updatedScenes });
      }
    } catch (error) {
      const message = error instanceof Error ? error.message : "Image generation failed";
      onError(message);
    } finally {
      setGeneratingImage(null);
    }
  }, [generatingImage, getScene, avatarImageUrl, onUpdateJob, onError, script]);

  const runVideoRender = useCallback(
    async (sceneId: string, settings?: VideoGenerationSettings) => {
      if (rendering && rendering !== sceneId) {
        return;
      }

      const scene = getScene(sceneId);
      if (!scene) {
        return;
      }

      // Guard: require image and audio before calling expensive video gen
      const sceneImageUrl = scene.imageUrl || avatarImageUrl;
      if (!sceneImageUrl) {
        onError("Scene image is required for video generation. Please generate images for scenes first.");
        return;
      }

      const job = jobs.find((j) => j.sceneId === sceneId);
      // Use job.finalUrl if available, otherwise fall back to scene.audioUrl (from Script Editor)
      const audioUrl = job?.finalUrl || scene.audioUrl;
      if (!audioUrl || audioUrl.startsWith("blob:")) {
        onError("Please generate audio first before creating video.");
        return;
      }

      // Guard: ensure every scene has audio and image before enabling render queue video
      const allScenesHaveAudio = script.scenes.every((s) => s.audioUrl && !s.audioUrl.startsWith("blob:"));
      const allScenesHaveImage = script.scenes.every((s) => s.imageUrl);
      if (!allScenesHaveAudio || !allScenesHaveImage) {
        onError("Please ensure all scenes have both audio and image before generating video.");
        return;
      }

      // Resolution & simple cost heuristic (default 480p for lower cost)
      const targetResolution: "480p" | "720p" =
        settings?.resolution || (knobs.resolution as "480p" | "720p") || "480p";
      const baseCost = 0.3; // 5s at 720p
      const estimatedCost = targetResolution === "480p" ? baseCost / 2 : baseCost;

      if (budgetCap && budgetCap > 0) {
        const currentSpent = jobs
          .filter((j) => j.status === "completed" && j.cost)
          .reduce((sum, j) => sum + (j.cost || 0), 0);

        if (currentSpent + estimatedCost > budgetCap) {
          onError(
            `Budget cap exceeded. Estimated cost: $${estimatedCost.toFixed(
              2
            )}, Budget remaining: $${(budgetCap - currentSpent).toFixed(2)}`
          );
          return;
        }
      }

      setRendering(sceneId);
      onUpdateJob(sceneId, {
        status: "running",
        progress: 5,
      });

      try {
        console.log("[useRenderQueue] Starting video generation", {
          sceneId,
          sceneTitle: scene.title,
          audioUrl,
          avatarImageUrl: sceneImageUrl,
          resolution: targetResolution,
          prompt: settings?.prompt,
          seed: settings?.seed,
          maskImageUrl: settings?.maskImageUrl,
        });

        const result = await podcastApi.generateVideo({
          projectId,
          sceneId,
          sceneTitle: scene.title,
          audioUrl,
          avatarImageUrl: sceneImageUrl,
          resolution: targetResolution,
          prompt: settings?.prompt || undefined,
          seed: settings?.seed ?? -1,
          maskImageUrl: settings?.maskImageUrl || undefined,
        });

        if (!result.taskId) {
          throw new Error("Backend did not return a task ID. Response: " + JSON.stringify(result));
        }

        onUpdateJob(sceneId, {
          taskId: result.taskId,
          status: "running",
          progress: 5,
        });

        startPolling(result.taskId, sceneId);
      } catch (error) {
        onUpdateJob(sceneId, { status: "failed", progress: 0 });
        const message = error instanceof Error ? error.message : "Video generation failed";
        onError(message);
      }
    },
    [rendering, getScene, avatarImageUrl, jobs, budgetCap, projectId, knobs, onUpdateJob, onError, script.scenes, startPolling]
  );

  const combineAudio = useCallback(async () => {
    try {
      setCombiningAudio(true);

      const sceneIds: string[] = [];
      const sceneAudioUrls: string[] = [];

      script.scenes.forEach((scene) => {
        if (scene.audioUrl) {
          // Ensure we're using the correct URL format (not blob URLs)
          const audioUrl = scene.audioUrl.startsWith('blob:') ? '' : scene.audioUrl;
          if (audioUrl) {
            sceneIds.push(scene.id);
            sceneAudioUrls.push(audioUrl);
          }
        }
      });

      jobs.forEach((job) => {
        // Prefer finalUrl over previewUrl, and ensure it's not a blob URL
        const audioUrl = job.finalUrl || job.previewUrl;
        if (audioUrl && !audioUrl.startsWith('blob:') && !sceneAudioUrls.includes(audioUrl)) {
          sceneIds.push(job.sceneId);
          sceneAudioUrls.push(audioUrl);
        }
      });

      if (sceneIds.length === 0) {
        onError("No audio files found to combine.");
        return;
      }

      const result = await podcastApi.combineAudio({
        projectId,
        sceneIds,
        sceneAudioUrls,
      });

      // Store combined audio result for preview
      setCombinedAudioResult({
        url: result.combined_audio_url,
        filename: result.combined_audio_filename,
        duration: result.total_duration,
        sceneCount: result.scene_count,
      });

      // Auto-download the combined audio
      const link = document.createElement("a");
      link.href = result.combined_audio_url;
      link.download = `podcast-episode-${projectId.slice(-8)}.mp3`;
      link.click();
    } catch (error) {
      const message = error instanceof Error ? error.message : "Failed to combine audio";
      onError(`Failed to combine audio: ${message}`);
    } finally {
      setCombiningAudio(false);
    }
  }, [script.scenes, jobs, projectId, onError]);

  const combineFinalVideo = useCallback(async () => {
    try {
      setCombiningVideos(true);
      onError("");

      // Collect all scene video URLs
      const sceneVideoUrls: string[] = [];
      for (const scene of script.scenes) {
        const job = jobs.find((j) => j.sceneId === scene.id);
        if (!job?.videoUrl) {
          throw new Error(`Scene "${scene.title}" is missing a video. Please generate videos for all scenes first.`);
        }
        // Remove blob URLs and query params - use the API path only
        let videoUrl = job.videoUrl;
        if (videoUrl.startsWith("blob:")) {
          throw new Error(`Scene "${scene.title}" has a blob URL. Cannot combine blob URLs. Please use API URLs.`);
        }
        videoUrl = videoUrl.split("?")[0]; // Remove query params
        sceneVideoUrls.push(videoUrl);
      }

      console.log("[combineFinalVideo] Starting combination with", sceneVideoUrls.length, "videos");

      // Start combination task
      const result = await podcastApi.combineVideos({
        projectId,
        sceneVideoUrls,
        podcastTitle: script.scenes[0]?.title || "Podcast",
      });

      console.log("[combineFinalVideo] Task created:", result.taskId);

      // Poll for completion
      const taskId = result.taskId;
      let done = false;
      let pollCount = 0;
      const maxPolls = 300; // 10 minutes max (300 * 2 seconds) - encoding can take time
      let lastProgress = 0;
      let lastMessage = "Starting video combination...";
      
      while (!done && pollCount < maxPolls) {
        await new Promise((r) => setTimeout(r, 2000)); // Poll every 2 seconds
        pollCount++;
        
        const status = await podcastApi.pollTaskStatus(taskId);
        
        // Update progress and message for user feedback
        if (status) {
          const currentProgress = status.progress ?? 0;
          const currentMessage = status.message || "Processing...";
          
          // Update UI with progress
          setCombiningProgress({
            progress: currentProgress,
            message: currentMessage,
          });
          
          // Only log if progress or message changed to reduce noise
          if (currentProgress !== lastProgress || currentMessage !== lastMessage) {
            console.log(
              `[combineFinalVideo] Poll ${pollCount}: ${status.status} | ` +
              `Progress: ${currentProgress.toFixed(1)}% | Message: ${currentMessage}`
            );
            lastProgress = currentProgress;
            lastMessage = currentMessage;
          }
        } else {
          console.log(`[combineFinalVideo] Poll ${pollCount}: No status yet...`);
        }
        
        if (!status) {
          // Don't fail immediately - task might still be initializing
          if (pollCount < 10) {
            continue; // Wait up to 20 seconds for task to appear
          }
          console.error("[combineFinalVideo] Task not found after 10 polls");
          throw new Error("Task not found. Video combination may have failed on the server. Please try again.");
        }

        if (status.status === "completed") {
          done = true;
          const videoUrl = status.result?.video_url;
          if (!videoUrl) {
            console.error("[combineFinalVideo] No video URL in result:", status.result);
            throw new Error("Final video URL not found in result. Please contact support.");
          }
          console.log("[combineFinalVideo] Success! Video URL:", videoUrl);
          setFinalVideoUrl(videoUrl);
          
          // Save final video URL to project for persistence across reloads
          try {
            await podcastApi.saveProject(projectId, { final_video_url: videoUrl });
            console.log("[combineFinalVideo] Saved final video URL to project");
          } catch (error) {
            console.warn("[combineFinalVideo] Failed to save final video URL to project:", error);
            // Don't fail the operation if project save fails - video is still available
          }
        } else if (status.status === "failed") {
          const errorMsg = status.error || status.message || "Video combination failed";
          console.error("[combineFinalVideo] Task failed:", errorMsg);
          throw new Error(`Video combination failed: ${errorMsg}`);
        }
      }
      
      if (pollCount >= maxPolls) {
        throw new Error("Video combination timed out after 10 minutes. The video may still be processing. Please check back in a few minutes or try again.");
      }
    } catch (error: any) {
      console.error("[combineFinalVideo] Error:", error);
      
      // Extract detailed error message
      let message = "Failed to combine videos";
      
      if (error?.response?.data?.detail) {
        // Backend error with detail
        message = error.response.data.detail;
      } else if (error?.message) {
        // Standard error message
        message = error.message;
      } else if (typeof error === "string") {
        message = error;
      }
      
      console.error("[combineFinalVideo] Displaying error to user:", message);
      onError(message);
    } finally {
      setCombiningVideos(false);
      setCombiningProgress(null);
    }
  }, [script.scenes, jobs, projectId, onError]);

  return {
    rendering,
    generatingImage,
    combiningAudio,
    combinedAudioResult,
    combiningVideos,
    combiningProgress,
    finalVideoUrl,
    isBusy: Boolean(rendering),
    runRender,
    runImageGeneration,
    runVideoRender,
    combineAudio,
    combineFinalVideo,
  };
};

