/**
 * ExportQueueContext — Global Context for managing export jobs via SSE.
 *
 * Provides:
 * - startExport(): Create a new render job
 * - jobs: Live list of all render jobs
 * - cancelJob(): Delete a job
 * - downloadJob(): Trigger browser download
 * - isConnected: SSE connection status
 */
import React, { createContext, useContext, useState, useEffect, useCallback, useRef } from 'react';
import type { TimelineElement, TimelineLayer, DesignMD } from '../types';
import { resolveBlobUrls } from '../utils/uploadBlobContent';
import { useToast } from '../components/ui/ToastProvider';

// ═══ Types (mirror server-side) ═══

export type RenderFormat = 'mp4' | 'webm' | 'gif' | 'png' | 'jpeg';

export interface RenderJobClient {
  id: string;
  status: 'queued' | 'rendering' | 'done' | 'error';
  progress: number;
  format: RenderFormat;
  width: number;
  height: number;
  fps: number;
  durationInFrames: number;
  compositionId: string;
  downloadUrl?: string;
  error?: string;
  createdAt: number;
  startedAt?: number;
  completedAt?: number;
  renderedFrames?: number;
  totalFrames?: number;
}

export interface ExportConfig {
  format: RenderFormat;
  width: number;
  height: number;
  fps: number;
  durationInFrames: number;
  designMD: DesignMD;
  textOverlay: string;
  timelineElements: TimelineElement[];
  layers: TimelineLayer[];
  brandVisibility?: { logo: boolean; frame: boolean; background: boolean };
  outputFormat?: 'video' | 'image';
}

interface ExportCallbacks {
  onSuccess?: (url: string) => void;
  onError?: (err: string) => void;
}

interface ExportQueueContextValue {
  jobs: RenderJobClient[];
  activeJobs: RenderJobClient[];
  hasActiveJobs: boolean;
  isConnected: boolean;
  startExport: (config: ExportConfig, callbacks?: ExportCallbacks) => Promise<RenderJobClient | null>;
  cancelJob: (jobId: string) => Promise<void>;
  downloadJob: (job: RenderJobClient) => Promise<void>;
}

const ExportQueueContext = createContext<ExportQueueContextValue | null>(null);

export const ExportQueueProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const [jobs, setJobs] = useState<RenderJobClient[]>([]);
  const [isConnected, setIsConnected] = useState(false);
  const eventSourceRef = useRef<EventSource | null>(null);
  const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const callbacksRef = useRef<Record<string, ExportCallbacks>>({});
  const { showToast } = useToast();

  // ─── SSE Connection ───
  const connect = useCallback(() => {
    if (eventSourceRef.current) {
      eventSourceRef.current.close();
    }

    const es = new EventSource('/api/render/events');
    eventSourceRef.current = es;

    es.onopen = () => {
      setIsConnected(true);
      console.log('📡 SSE connected (Global)');
    };

    es.onmessage = (event) => {
      try {
        const data = JSON.parse(event.data);

        if (data.type === 'job-update' && data.job) {
          const updatedJob = data.job as RenderJobClient;
          
          setJobs(prev => {
            const idx = prev.findIndex(j => j.id === updatedJob.id);
            const oldJob = idx >= 0 ? prev[idx] : null;

            // Check if it just finished
            if (updatedJob.status === 'done' && oldJob?.status !== 'done' && updatedJob.downloadUrl) {
              const cbs = callbacksRef.current[updatedJob.id];
              if (cbs?.onSuccess) {
                cbs.onSuccess(updatedJob.downloadUrl);
              }
              delete callbacksRef.current[updatedJob.id];
              
              if (!cbs?.onSuccess) {
                // If there's no custom callback, show a generic toast
                showToast('Renderización completada', 'success');
              }
            } else if (updatedJob.status === 'error' && oldJob?.status !== 'error') {
              const cbs = callbacksRef.current[updatedJob.id];
              if (cbs?.onError) {
                cbs.onError(updatedJob.error || 'Error');
              }
              delete callbacksRef.current[updatedJob.id];
              showToast(`Error de renderización: ${updatedJob.error}`, 'error');
            }

            if (idx >= 0) {
              const updated = [...prev];
              updated[idx] = updatedJob;
              return updated;
            }
            return [updatedJob, ...prev];
          });
        }
      } catch (err) {
        console.warn('SSE parse error:', err);
      }
    };

    es.onerror = () => {
      setIsConnected(false);
      es.close();
      eventSourceRef.current = null;

      // Auto-reconnect after 3s
      reconnectTimeoutRef.current = setTimeout(connect, 3000);
    };
  }, [showToast]);

  // ─── Connect on mount ───
  useEffect(() => {
    connect();

    // Load initial jobs
    fetch('/api/render/jobs')
      .then(res => res.json())
      .then(data => {
        if (Array.isArray(data)) setJobs(data);
      })
      .catch(() => {});

    return () => {
      eventSourceRef.current?.close();
      if (reconnectTimeoutRef.current) clearTimeout(reconnectTimeoutRef.current);
    };
  }, [connect]);

  // ─── Start Export ───
  const startExport = useCallback(async (config: ExportConfig, callbacks?: ExportCallbacks): Promise<RenderJobClient | null> => {
    const isStill = config.format === 'png' || config.format === 'jpeg';

    // Resolve blob: URLs to persistent server URLs before sending to server-side render
    const resolvedElements = await resolveBlobUrls(config.timelineElements);

    // Also resolve blob URLs in designMD (introVideoUrl, outroVideoUrl, logoUrl)
    const resolvedDesignMD = { ...config.designMD };
    const designMDUrlFields: (keyof typeof resolvedDesignMD)[] = [
      'introVideoUrl', 'outroVideoUrl', 'logoUrl', 'brandAudioUrl',
    ];
    for (const field of designMDUrlFields) {
      const val = resolvedDesignMD[field];
      if (typeof val === 'string' && val.startsWith('blob:')) {
        try {
          const res = await fetch(val);
          const blob = await res.blob();
          const ext = blob.type.includes('video') ? '.mp4'
            : blob.type.includes('audio') ? '.mp3'
            : blob.type.includes('png') ? '.png'
            : '.jpg';
          const file = new File([blob], `designmd-${String(field)}${ext}`, { type: blob.type });
          const formData = new FormData();
          formData.append('file', file);
          const uploadRes = await fetch('/api/upload', { method: 'POST', body: formData });
          if (uploadRes.ok) {
            const data = await uploadRes.json();
            // Use Express origin for Remotion compatibility
            const electronAPI = (window as any).electronAPI;
            const origin = electronAPI?.isElectron
              ? 'http://127.0.0.1:3000'
              : window.location.origin;
            (resolvedDesignMD as any)[field] = `${origin}${data.url}`;
          }
        } catch (err) {
          console.warn(`Failed to resolve blob for designMD.${String(field)}:`, err);
        }
      }
    }

    // Strip non-serializable props for render (callbacks, refs, etc.)
    const inputProps = {
      designMD: resolvedDesignMD,
      textOverlay: config.textOverlay,
      timelineElements: resolvedElements,
      layers: config.layers,
      brandVisibility: config.brandVisibility,
      outputFormat: config.outputFormat,
    };

    const body = {
      format: config.format,
      width: config.width,
      height: config.height,
      fps: config.fps,
      durationInFrames: isStill ? 1 : config.durationInFrames,
      compositionId: isStill ? 'BrandStill' : 'BrandVideo',
      inputProps,
    };

    try {
      const res = await fetch('/api/render/start', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body),
      });

      if (!res.ok) {
        const err = await res.json();
        throw new Error(err.error || 'Export failed');
      }

      const job: RenderJobClient = await res.json();
      
      if (callbacks) {
        callbacksRef.current[job.id] = callbacks;
      }

      setJobs(prev => [job, ...prev.filter(j => j.id !== job.id)]);
      return job;
    } catch (err) {
      console.error('Export start failed:', err);
      if (callbacks?.onError) callbacks.onError(err instanceof Error ? err.message : 'Error');
      return null;
    }
  }, []);

  // ─── Cancel Job ───
  const cancelJob = useCallback(async (jobId: string) => {
    try {
      await fetch(`/api/render/jobs/${jobId}`, { method: 'DELETE' });
      setJobs(prev => prev.filter(j => j.id !== jobId));
      delete callbacksRef.current[jobId];
    } catch (err) {
      console.error('Cancel failed:', err);
    }
  }, []);

  // ─── Download ───
  const downloadJob = useCallback(async (job: RenderJobClient) => {
    if (!job.downloadUrl) return;

    const defaultName = `export-${job.id.slice(0, 8)}.${job.format}`;

    // In Electron, use native save dialog via IPC
    const electronAPI = (window as any).electronAPI;
    if (electronAPI?.saveRenderedFile) {
      try {
        const savedPath = await electronAPI.saveRenderedFile(job.downloadUrl, defaultName);
        if (savedPath) {
          console.log('✅ Saved to:', savedPath);
        }
      } catch (err) {
        console.error('Save failed:', err);
      }
      return;
    }

    // Web fallback: <a> tag download
    const a = document.createElement('a');
    a.href = job.downloadUrl;
    a.download = defaultName;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
  }, []);

  // ─── Derived state ───
  const activeJobs = jobs.filter(j => j.status === 'queued' || j.status === 'rendering');
  const hasActiveJobs = activeJobs.length > 0;

  return (
    <ExportQueueContext.Provider value={{
      jobs,
      activeJobs,
      hasActiveJobs,
      isConnected,
      startExport,
      cancelJob,
      downloadJob,
    }}>
      {children}
    </ExportQueueContext.Provider>
  );
};

export const useExportQueue = () => {
  const context = useContext(ExportQueueContext);
  if (!context) {
    throw new Error('useExportQueue must be used within ExportQueueProvider');
  }
  return context;
};
