// src/hooks/useFlyerUploader.ts
// src/hooks/useFlyerUploader.ts
import { useState, useCallback, useMemo } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import {
  uploadAndProcessFlyer,
  getJobStatus,
  type JobStatus,
  JobFailedError,
} from '../services/aiApiClient';
import { logger } from '../services/logger.client';
import { generateFileChecksum } from '../utils/checksum';
import type { ProcessingStage } from '../types';

export type ProcessingState = 'idle' | 'uploading' | 'polling' | 'completed' | 'error';

// Define a type for the structured error thrown by the API client
interface ApiError {
  status: number;
  body: {
    message: string;
    flyerId?: number;
  };
}

// Type guard to check if an error is a structured API error
function isApiError(error: unknown): error is ApiError {
  return (
    typeof error === 'object' &&
    error !== null &&
    'status' in error &&
    typeof (error as { status: unknown }).status === 'number' &&
    'body' in error &&
    typeof (error as { body: unknown }).body === 'object' &&
    (error as { body: unknown }).body !== null &&
    'message' in ((error as { body: unknown }).body as object)
  );
}
export const useFlyerUploader = () => {
  const queryClient = useQueryClient();
  const [jobId, setJobId] = useState<string | null>(null);
  const [currentFile, setCurrentFile] = useState<string | null>(null);

  // Mutation for the initial file upload
  const uploadMutation = useMutation({
    mutationFn: async (file: File) => {
      setCurrentFile(file.name);
      const checksum = await generateFileChecksum(file);
      return uploadAndProcessFlyer(file, checksum);
    },
    onSuccess: (data) => {
      // When upload is successful, we get a jobId and can start polling.
      setJobId(data.jobId);
    },
    // onError is handled automatically by react-query and exposed in `uploadMutation.error`
  });

  // Query for polling the job status
  const { data: jobStatus, error: pollError } = useQuery({
    queryKey: ['jobStatus', jobId],
    queryFn: () => {
      if (!jobId) throw new Error('No job ID to poll');
      return getJobStatus(jobId);
    },
    // Only run this query if there is a jobId
    enabled: !!jobId,
    // Polling logic: react-query handles the interval
    refetchInterval: (query) => {
      const data = query.state.data as JobStatus | undefined;
      // Stop polling if the job is completed or has failed
      if (data?.state === 'completed' || data?.state === 'failed') {
        return false;
      }
      // Also stop polling if the query itself has errored (e.g. network error, or JobFailedError thrown from getJobStatus)
      if (query.state.status === 'error') {
        logger.warn('[useFlyerUploader] Polling stopped due to query error state.');
        return false;
      }
      // Otherwise, poll every 3 seconds
      return 3000;
    },
    refetchOnWindowFocus: false, // No need to refetch on focus, interval is enough
    // If a poll fails (e.g., network error), don't retry automatically.
    // The user can see the error and choose to retry manually if we build that feature.
    retry: false,
  });

  const upload = useCallback(
    (file: File) => {
      // Reset previous state before a new upload
      setJobId(null);
      setCurrentFile(null);
      queryClient.removeQueries({ queryKey: ['jobStatus'] });
      uploadMutation.mutate(file);
    },
    [uploadMutation, queryClient],
  );

  const resetUploaderState = useCallback(() => {
    setJobId(null);
    setCurrentFile(null);
    uploadMutation.reset();
    queryClient.removeQueries({ queryKey: ['jobStatus'] });
  }, [uploadMutation, queryClient]);

  // Consolidate state derivation for the UI from the react-query hooks using useMemo.
  // This improves performance by memoizing the derived state and makes the logic easier to follow.
  const { processingState, errorMessage, duplicateFlyerId, flyerId, statusMessage } = useMemo(() => {
    // The order of these checks is critical. Errors must be checked first to override
    // any stale `jobStatus` from a previous successful poll.
    const state: ProcessingState = (() => {
      if (uploadMutation.isError || pollError) return 'error';
      if (uploadMutation.isPending) return 'uploading';
      if (jobStatus && (jobStatus.state === 'active' || jobStatus.state === 'waiting'))
        return 'polling';
      if (jobStatus?.state === 'completed') {
        if (!jobStatus.returnValue?.flyerId) return 'error';
        return 'completed';
      }
      return 'idle';
    })();

    let msg: string | null = null;
    let dupId: number | null = null;

    if (state === 'error') {
      if (uploadMutation.isError) {
        const uploadError = uploadMutation.error;
        if (isApiError(uploadError)) {
          msg = uploadError.body.message;
          // Specifically handle 409 Conflict for duplicate flyers
          if (uploadError.status === 409) {
            dupId = uploadError.body.flyerId ?? null;
          }
        } else if (uploadError instanceof Error) {
          msg = uploadError.message;
        } else {
          msg = 'An unknown upload error occurred.';
        }
      } else if (pollError) {
        msg = `Polling failed: ${pollError.message}`;
      } else if (jobStatus?.state === 'failed') {
        msg = `Processing failed: ${jobStatus.progress?.message || jobStatus.failedReason || 'Unknown reason'}`;
      } else if (jobStatus?.state === 'completed' && !jobStatus.returnValue?.flyerId) {
        msg = 'Job completed but did not return a flyer ID.';
      }
    }

    return {
      processingState: state,
      errorMessage: msg,
      duplicateFlyerId: dupId,
      flyerId: jobStatus?.state === 'completed' ? jobStatus.returnValue?.flyerId ?? null : null,
      statusMessage: uploadMutation.isPending ? 'Uploading file...' : jobStatus?.progress?.message,
    };
  }, [uploadMutation, jobStatus, pollError]);

  return {
    processingState,
    statusMessage: uploadMutation.isPending ? 'Uploading file...' : jobStatus?.progress?.message,
    errorMessage,
    duplicateFlyerId,
    processingStages: jobStatus?.progress?.stages || [],
    estimatedTime: jobStatus?.progress?.estimatedTimeRemaining || 0,
    currentFile,
    flyerId,
    upload,
    resetUploaderState,
    jobId,
  };
};
