'use client';

import { useState, useCallback, useRef } from 'react';
import { useEventChannel, type ChannelEvent } from './useEventChannel';

/**
 * Shot status values matching Go ShotStatus constants.
 */
export type ShotStatus = 'pending' | 'generating' | 'complete' | 'failed';

/**
 * A single shot in an album.
 */
export interface Shot {
  index: number;
  label: string;
  direction: string;
  imageUrl: string;
  jobId: string;
  status: ShotStatus;
  error?: string;
  generatedAt?: string;
}

/**
 * An album: one subject, one anchor, N shots.
 */
export interface Album {
  id: string;
  userId: string;
  name: string;
  subjectDesc: string;
  anchorUrl: string;
  anchorJobId: string;
  shots: Shot[];
  createdAt: string;
  updatedAt: string;
}

/**
 * Configuration for the useAlbumGeneration hook.
 */
export interface UseAlbumGenerationConfig {
  /** API base path for album endpoints (default: '/api/example-api') */
  apiBase: string;
  /** User ID for subscribing to user channel */
  userId: string;
  /** Album to track. Pass null to manage without an active album. */
  albumId: string | null;
  /** SSE endpoint (default: '/api/example-api/events') */
  sseEndpoint?: string;
  /** Auth token for API requests */
  authToken?: string;
}

/**
 * Result of the useAlbumGeneration hook.
 */
export interface UseAlbumGenerationResult {
  /** The currently loaded album (null if not yet loaded) */
  album: Album | null;
  /** Whether the album is being loaded */
  isLoading: boolean;
  /** Error message */
  error: string | null;
  /** Load (or refresh) the album from the API */
  loadAlbum: () => Promise<void>;
  /** Enqueue anchor generation for the album */
  generateAnchor: () => Promise<void>;
  /** Enqueue all pending shots (requires anchor to exist) */
  generateAllShots: () => Promise<void>;
  /** Enqueue a single shot by index (for regeneration) */
  regenerateShot: (index: number) => Promise<void>;
  /** Reset a shot to pending so it can be regenerated */
  resetShot: (index: number) => Promise<void>;
}

/**
 * Hook for managing album generation state with real-time progress via SSE.
 *
 * Subscribe to SSE events for the user channel and update album shot states
 * as generation_complete and anchor_complete events arrive.
 *
 * @example
 * ```tsx
 * const { album, generateAnchor, generateAllShots, regenerateShot } =
 *   useAlbumGeneration({
 *     apiBase: '/api/example-api',
 *     userId: currentUser.id,
 *     albumId: album.id,
 *     authToken: token,
 *   });
 *
 * return (
 *   <AlbumGrid album={album} onRegenerateShot={regenerateShot} />
 * );
 * ```
 */
export function useAlbumGeneration(
  config: UseAlbumGenerationConfig
): UseAlbumGenerationResult {
  const { apiBase, userId, albumId, sseEndpoint, authToken } = config;

  const [album, setAlbum] = useState<Album | null>(null);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  // Keep album in a ref for event handler closure (avoids stale state).
  const albumRef = useRef<Album | null>(null);
  albumRef.current = album;

  const effectiveSseEndpoint = sseEndpoint ?? `${apiBase}/events`;

  // Build auth headers.
  const headers = useCallback((): Record<string, string> => {
    const h: Record<string, string> = { 'Content-Type': 'application/json' };
    if (authToken) h['Authorization'] = `Bearer ${authToken}`;
    return h;
  }, [authToken]);

  const loadAlbum = useCallback(async () => {
    if (!albumId) return;
    setIsLoading(true);
    setError(null);
    try {
      const res = await fetch(`${apiBase}/albums/${albumId}`, {
        headers: headers(),
      });
      if (!res.ok) throw new Error(`Failed to load album: ${res.status}`);
      const json = (await res.json()) as { data: Album };
      const loaded = json.data ?? (json as unknown as Album);
      setAlbum(loaded);
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Failed to load album');
    } finally {
      setIsLoading(false);
    }
  }, [albumId, apiBase, headers]);

  // Handle incoming SSE events for this album.
  const handleEvent = useCallback((event: ChannelEvent) => {
    const current = albumRef.current;
    if (!current) return;

    // Extract albumId from the event result payload.
    const result = event.result as Record<string, unknown> | undefined;
    if (!result) return;

    const eventAlbumId = result['albumId'] as string | undefined;
    if (eventAlbumId && eventAlbumId !== current.id) return;

    switch (event.type) {
      case 'album_anchor_complete': {
        const anchorUrl = result['anchorUrl'] as string;
        setAlbum((prev) => prev ? { ...prev, anchorUrl } : prev);
        break;
      }

      case 'album_anchor_failed': {
        // Reload to get consistent state.
        void loadAlbum();
        break;
      }

      case 'album_shot_generating': {
        const shotIndex = result['shotIndex'] as number;
        setAlbum((prev) => {
          if (!prev) return prev;
          const shots = prev.shots.map((s) =>
            s.index === shotIndex ? { ...s, status: 'generating' as ShotStatus } : s
          );
          return { ...prev, shots };
        });
        break;
      }

      case 'album_shot_complete': {
        const shotIndex = result['shotIndex'] as number;
        const imageUrl = result['imageUrl'] as string;
        setAlbum((prev) => {
          if (!prev) return prev;
          const shots = prev.shots.map((s) =>
            s.index === shotIndex
              ? { ...s, imageUrl, status: 'complete' as ShotStatus, error: undefined }
              : s
          );
          return { ...prev, shots };
        });
        break;
      }

      case 'album_shot_failed': {
        const shotIndex = result['shotIndex'] as number;
        const errMsg = result['error'] as string;
        setAlbum((prev) => {
          if (!prev) return prev;
          const shots = prev.shots.map((s) =>
            s.index === shotIndex
              ? { ...s, status: 'failed' as ShotStatus, error: errMsg }
              : s
          );
          return { ...prev, shots };
        });
        break;
      }
    }
  }, [loadAlbum]);

  // Subscribe to the user SSE channel.
  useEventChannel({
    endpoint: effectiveSseEndpoint,
    channel: `user:${userId}`,
    onEvent: handleEvent,
  });

  const generateAnchor = useCallback(async () => {
    if (!albumId) return;
    setError(null);
    try {
      const res = await fetch(`${apiBase}/albums/${albumId}/anchor`, {
        method: 'POST',
        headers: headers(),
      });
      if (!res.ok) {
        const body = await res.json().catch(() => ({})) as { message?: string };
        throw new Error(body.message ?? `HTTP ${res.status}`);
      }
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Failed to start anchor generation');
    }
  }, [albumId, apiBase, headers]);

  const generateAllShots = useCallback(async () => {
    if (!albumId) return;
    setError(null);
    try {
      const res = await fetch(`${apiBase}/albums/${albumId}/shots`, {
        method: 'POST',
        headers: headers(),
      });
      if (!res.ok) {
        const body = await res.json().catch(() => ({})) as { message?: string };
        throw new Error(body.message ?? `HTTP ${res.status}`);
      }
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Failed to start shot generation');
    }
  }, [albumId, apiBase, headers]);

  const regenerateShot = useCallback(
    async (index: number) => {
      if (!albumId) return;
      setError(null);
      try {
        const res = await fetch(`${apiBase}/albums/${albumId}/shots/${index}`, {
          method: 'POST',
          headers: headers(),
        });
        if (!res.ok) {
          const body = await res.json().catch(() => ({})) as { message?: string };
          throw new Error(body.message ?? `HTTP ${res.status}`);
        }
        // Optimistically set shot to generating.
        setAlbum((prev) => {
          if (!prev) return prev;
          const shots = prev.shots.map((s) =>
            s.index === index ? { ...s, status: 'generating' as ShotStatus } : s
          );
          return { ...prev, shots };
        });
      } catch (err) {
        setError(err instanceof Error ? err.message : 'Failed to regenerate shot');
      }
    },
    [albumId, apiBase, headers]
  );

  const resetShot = useCallback(
    async (index: number) => {
      if (!albumId) return;
      setError(null);
      try {
        const res = await fetch(`${apiBase}/albums/${albumId}/shots/${index}`, {
          method: 'DELETE',
          headers: headers(),
        });
        if (!res.ok) {
          throw new Error(`HTTP ${res.status}`);
        }
        // Update shot to pending locally.
        setAlbum((prev) => {
          if (!prev) return prev;
          const shots = prev.shots.map((s) =>
            s.index === index
              ? { ...s, status: 'pending' as ShotStatus, imageUrl: '', error: undefined }
              : s
          );
          return { ...prev, shots };
        });
      } catch (err) {
        setError(err instanceof Error ? err.message : 'Failed to reset shot');
      }
    },
    [albumId, apiBase, headers]
  );

  return {
    album,
    isLoading,
    error,
    loadAlbum,
    generateAnchor,
    generateAllShots,
    regenerateShot,
    resetShot,
  };
}
