/**
 * DownloadContext
 *
 * React context for managing download state and progress across the application.
 * Integrates with DownloadEventBus for real-time progress updates.
 */

import React, {
  createContext,
  useContext,
  useEffect,
  useState,
  useCallback,
  useMemo,
  type ReactNode,
} from 'react';
import { downloadEventBus } from '../lib/offline/DownloadEventBus';
import { OFFLINE_CONFIG } from '../config/offline.config';
import { PRELOAD_CONFIG } from '../config/preload.config';
import type {
  PreloadJob,
  PreloadJobStatus,
  PreloadProgressEvent,
  PreloadCompleteEvent,
  PreloadErrorEvent,
  PreloadStartEvent,
} from '../types/offline';

// Context state
interface DownloadState {
  jobs: PreloadJob[];
  activeCount: number;
  completedCount: number;
  errorCount: number;
  totalProgress: number;
  isDownloading: boolean;
}

// Context value with actions
interface DownloadContextValue extends DownloadState {
  clearJob: (id: string) => void;
  clearAllCompleted: () => void;
  clearAllErrors: () => void;
  clearAll: () => void;
}

const DownloadContext = createContext<DownloadContextValue | null>(null);

// Helper to update a job in the array
const updateJobInArray = (
  jobs: PreloadJob[],
  jobId: string,
  updates: Partial<PreloadJob>,
): PreloadJob[] => {
  return jobs.map((job) => (job.id === jobId ? { ...job, ...updates } : job));
};

// Helper to calculate overall progress
const calculateTotalProgress = (jobs: PreloadJob[]): number => {
  if (jobs.length === 0) return 0;

  const activeJobs = jobs.filter(
    (j) => j.status !== 'completed' && j.status !== 'error',
  );
  if (activeJobs.length === 0) return 100;

  const totalBytes = activeJobs.reduce((sum, j) => sum + j.totalBytes, 0);
  const loadedBytes = activeJobs.reduce((sum, j) => sum + j.bytesLoaded, 0);

  if (totalBytes === 0) {
    // Fallback to count-based progress
    const completedCount = jobs.filter((j) => j.status === 'completed').length;
    return Math.round((completedCount / jobs.length) * 100);
  }

  return Math.round((loadedBytes / totalBytes) * 100);
};

interface DownloadProviderProps {
  children: ReactNode;
}

export function DownloadProvider({ children }: DownloadProviderProps) {
  const [jobs, setJobs] = useState<PreloadJob[]>([]);

  // Handle preload start
  useEffect(() => {
    const handleStart = (data: PreloadStartEvent) => {
      const newJob: PreloadJob = {
        id: data.jobId,
        assetId: data.assetId,
        url: data.url,
        filename: data.url.split('/').pop() || 'unknown',
        progress: 0,
        status: 'downloading',
        bytesLoaded: 0,
        totalBytes: 0,
        addedAt: Date.now(),
        startedAt: Date.now(),
      };

      setJobs((prev) => {
        // Avoid duplicates
        if (prev.some((j) => j.id === data.jobId)) {
          return prev;
        }
        return [...prev, newJob];
      });
    };

    return downloadEventBus.on(
      OFFLINE_CONFIG.events.preloadStart as Parameters<
        typeof downloadEventBus.on
      >[0],
      handleStart as Parameters<typeof downloadEventBus.on>[1],
    );
  }, []);

  // Handle preload progress
  useEffect(() => {
    const handleProgress = (data: PreloadProgressEvent) => {
      setJobs((prev) =>
        updateJobInArray(prev, data.jobId, {
          progress: data.progress,
          bytesLoaded: data.bytesLoaded,
          totalBytes: data.totalBytes,
          status: 'downloading',
        }),
      );
    };

    return downloadEventBus.on(
      OFFLINE_CONFIG.events.preloadProgress as Parameters<
        typeof downloadEventBus.on
      >[0],
      handleProgress as Parameters<typeof downloadEventBus.on>[1],
    );
  }, []);

  // Handle preload complete
  useEffect(() => {
    const handleComplete = (data: PreloadCompleteEvent) => {
      setJobs((prev) =>
        updateJobInArray(prev, data.jobId, {
          status: 'completed',
          progress: 100,
          completedAt: Date.now(),
        }),
      );

      // Auto-remove completed jobs after delay
      setTimeout(() => {
        setJobs((prev) => prev.filter((j) => j.id !== data.jobId));
      }, PRELOAD_CONFIG.autoRemove.completedMs);
    };

    return downloadEventBus.on(
      OFFLINE_CONFIG.events.preloadComplete as Parameters<
        typeof downloadEventBus.on
      >[0],
      handleComplete as Parameters<typeof downloadEventBus.on>[1],
    );
  }, []);

  // Handle preload error
  useEffect(() => {
    const handleError = (data: PreloadErrorEvent) => {
      setJobs((prev) =>
        updateJobInArray(prev, data.jobId, {
          status: 'error',
          error: data.error,
        }),
      );

      // Auto-remove error jobs after delay
      setTimeout(() => {
        setJobs((prev) => prev.filter((j) => j.id !== data.jobId));
      }, PRELOAD_CONFIG.autoRemove.errorMs);
    };

    return downloadEventBus.on(
      OFFLINE_CONFIG.events.preloadError as Parameters<
        typeof downloadEventBus.on
      >[0],
      handleError as Parameters<typeof downloadEventBus.on>[1],
    );
  }, []);

  // Actions
  const clearJob = useCallback((id: string) => {
    setJobs((prev) => prev.filter((j) => j.id !== id));
  }, []);

  const clearAllCompleted = useCallback(() => {
    setJobs((prev) => prev.filter((j) => j.status !== 'completed'));
  }, []);

  const clearAllErrors = useCallback(() => {
    setJobs((prev) => prev.filter((j) => j.status !== 'error'));
  }, []);

  const clearAll = useCallback(() => {
    setJobs([]);
  }, []);

  // Computed values
  const value = useMemo<DownloadContextValue>(() => {
    const activeCount = jobs.filter(
      (j) => j.status === 'downloading' || j.status === 'queued',
    ).length;
    const completedCount = jobs.filter((j) => j.status === 'completed').length;
    const errorCount = jobs.filter((j) => j.status === 'error').length;
    const totalProgress = calculateTotalProgress(jobs);
    const isDownloading = activeCount > 0;

    return {
      jobs,
      activeCount,
      completedCount,
      errorCount,
      totalProgress,
      isDownloading,
      clearJob,
      clearAllCompleted,
      clearAllErrors,
      clearAll,
    };
  }, [jobs, clearJob, clearAllCompleted, clearAllErrors, clearAll]);

  return (
    <DownloadContext.Provider value={value}>
      {children}
    </DownloadContext.Provider>
  );
}

/**
 * Hook to access download context
 */
export function useDownloadContext(): DownloadContextValue {
  const context = useContext(DownloadContext);
  if (!context) {
    throw new Error(
      'useDownloadContext must be used within a DownloadProvider',
    );
  }
  return context;
}

/**
 * Optional hook that returns null if not within provider
 */
export function useDownloadContextOptional(): DownloadContextValue | null {
  return useContext(DownloadContext);
}
