const API_BASE = '/api'

/**
 * Safely extract error message from unknown error type
 * Use this instead of (error: any) => error.message
 */
export function getErrorMessage(error: unknown): string {
  if (error instanceof Error) {
    return error.message
  }
  if (typeof error === 'string') {
    return error
  }
  if (error && typeof error === 'object' && 'message' in error) {
    return String((error as { message: unknown }).message)
  }
  return 'An unknown error occurred'
}

/**
 * Retry configuration for API requests
 */
interface RetryConfig {
  maxRetries: number
  baseDelay: number
  maxDelay: number
  retryableStatuses: number[]
}

const DEFAULT_RETRY_CONFIG: RetryConfig = {
  maxRetries: 3,
  baseDelay: 1000,    // Start with 1 second
  maxDelay: 10000,    // Max 10 seconds between retries
  retryableStatuses: [408, 429, 500, 502, 503, 504], // Timeout, Rate limit, Server errors
}

/**
 * Sleep for a given number of milliseconds
 */
function sleep(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms))
}

/**
 * Calculate exponential backoff delay with jitter
 */
function calculateBackoffDelay(attempt: number, config: RetryConfig): number {
  // Exponential backoff: baseDelay * 2^attempt
  const exponentialDelay = config.baseDelay * Math.pow(2, attempt)
  // Add jitter (±25%) to prevent thundering herd
  const jitter = exponentialDelay * 0.25 * (Math.random() * 2 - 1)
  const delay = Math.min(exponentialDelay + jitter, config.maxDelay)
  return Math.round(delay)
}

/**
 * Check if an error is retryable
 */
function isRetryableError(status: number, config: RetryConfig): boolean {
  return config.retryableStatuses.includes(status)
}

export interface Download {
  id: number
  platform: string
  source: string
  content_type: string | null
  filename: string | null
  file_path: string | null
  file_token?: string
  file_size: number | null
  download_date: string
  status: string
  width?: number
  height?: number
}

export interface Stats {
  total_downloads: number
  by_platform: Record<string, number>
  total_size: number
  recent_24h: number
  duplicates_prevented: number
  review_queue_count: number
}

export interface SystemStatus {
  total_downloads: number
  recent_24h: number
  platforms: Record<string, number>
  scheduler_running: boolean
  active_websockets: number
  timestamp: string
}

export interface Platform {
  name: string
  type?: 'scheduled' | 'manual'
  enabled: boolean
  display_name: string
  check_interval_hours?: number
  account_count?: number
  download_count?: number
  config?: PlatformConfig
}

// YouTube Channel Monitor types - Global Settings + Channels
export interface YouTubeMonitorSettings {
  phrases: string[]
  check_interval_hours: number
  quality: string
  enabled: number
  last_checked: string | null
  updated_at: string | null
  auto_start_queue: number
  notifications_enabled: number
}

export interface YouTubeChannel {
  id: number
  channel_url: string
  channel_name: string | null
  enabled: number
  last_checked: string | null
  videos_found: number
  created_at: string
  status?: string
  always_active?: number
  last_video_date?: string | null
  last_check_date?: string | null
  paused_date?: string | null
  paused_reason?: string | null
  total_videos_found?: number
}

export interface YouTubeMonitorSettingsUpdate {
  phrases?: string[]
  check_interval_hours?: number
  quality?: string
  enabled?: boolean
  auto_start_queue?: boolean
  notifications_enabled?: boolean
  [key: string]: string[] | number | string | boolean | undefined
}

export interface YouTubeChannelCreate {
  channel_url: string
  channel_name?: string
  enabled?: boolean
  [key: string]: string | boolean | undefined
}

export interface YouTubeChannelUpdate {
  channel_url?: string
  channel_name?: string
  enabled?: boolean
  [key: string]: string | boolean | undefined
}

export interface YouTubeMonitorHistory {
  id: number
  monitor_id: number
  video_id: string
  video_title: string
  matched_phrase: string | null
  action: string
  created_at: string
  channel_name?: string
  channel_url?: string
}

// User and authentication types
export interface User {
  id?: number
  username: string
  email?: string
  role: string
  is_active?: boolean
  created_at?: string
  last_login?: string
  preferences?: UserPreferences
  two_factor_enabled?: boolean
  totp_enabled?: boolean
  passkey_enabled?: boolean
  duo_enabled?: boolean
}

export interface UserPreferences {
  theme?: 'light' | 'dark' | 'system'
  notifications_enabled?: boolean
  default_view?: string
  items_per_page?: number
  [key: string]: string | number | boolean | undefined
}

// Configuration types
export interface PlatformConfig {
  enabled?: boolean
  check_interval_hours?: number
  max_downloads?: number
  days_back?: number
  accounts?: string[]
  [key: string]: unknown
}

export interface AppConfig {
  platforms?: Record<string, PlatformConfig>
  general?: {
    output_dir?: string
    temp_dir?: string
    log_level?: string
    [key: string]: unknown
  }
  immich?: {
    enabled?: boolean
    api_url?: string
    api_key?: string
    library_id?: string
    [key: string]: unknown
  }
  [key: string]: unknown
}

export interface SettingValue {
  value: string | number | boolean | object
  category?: string
  description?: string
  updated_at?: string
}

// WebAuthn/Passkey types
export interface PublicKeyCredentialCreationOptionsJSON {
  challenge: string
  rp: { name: string; id?: string }
  user: { id: string; name: string; displayName: string }
  pubKeyCredParams: Array<{ type: 'public-key'; alg: number }>
  timeout?: number
  attestation?: AttestationConveyancePreference
  authenticatorSelection?: AuthenticatorSelectionCriteria
  excludeCredentials?: Array<{ type: 'public-key'; id: string; transports?: AuthenticatorTransport[] }>
}

export interface PublicKeyCredentialRequestOptionsJSON {
  challenge: string
  timeout?: number
  rpId?: string
  allowCredentials?: Array<{ type: 'public-key'; id: string; transports?: AuthenticatorTransport[] }>
  userVerification?: UserVerificationRequirement
}

export interface RegistrationCredentialJSON {
  id: string
  rawId: string
  type: 'public-key'
  response: {
    clientDataJSON: string
    attestationObject: string
    transports?: AuthenticatorTransport[]
  }
  authenticatorAttachment?: AuthenticatorAttachment
}

export interface AuthenticationCredentialJSON {
  id: string
  rawId: string
  type: 'public-key'
  response: {
    clientDataJSON: string
    authenticatorData: string
    signature: string
    userHandle?: string
  }
  authenticatorAttachment?: AuthenticatorAttachment
}

// Platform Credentials (unified cookie management)
export interface PlatformCredential {
  id: string
  name: string
  type: 'cookies' | 'token' | 'session'
  source: 'scraper' | 'paid_content' | 'private_gallery'
  base_url?: string | null
  cookies_count?: number
  has_credentials: boolean
  gallery_locked?: boolean
  updated_at: string | null
  used_by: string[]
  monitoring_enabled: boolean
}

// WebSocket message types - using unknown instead of any for type safety
export type WebSocketMessage = Record<string, unknown> & { type: string }

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type WebSocketCallback = (data: any) => void

// API error type for catch blocks
export interface APIError extends Error {
  name: string
  message: string
  status?: number
}

// Two-Factor Authentication status types
export interface TwoFAStatus {
  totp?: {
    enabled: boolean
  }
  passkey?: {
    enabled: boolean
    available: boolean
    credentialCount: number
  }
  duo?: {
    enabled: boolean
  }
}

// Passkey list response
export interface PasskeyListResponse {
  credentials: Array<{
    id: string
    name: string
    created_at: string
    last_used?: string
  }>
}

// Appearance config response
export interface AppearanceConfig {
  tmdb_enabled?: boolean
  tmdb_api_key?: string
  tmdb_check_interval_hours?: number
  notify_new_appearances?: boolean
  notify_include_poster?: boolean
  notify_plex_matches?: boolean
  podcast_enabled?: boolean
  radio_enabled?: boolean
  podchaser_enabled?: boolean
  plex_url?: string
  plex_token?: string
}

// Media gallery item type
export interface MediaGalleryItem {
  id: number
  filename: string
  file_path: string
  file_size: number
  platform: string
  source: string
  content_type?: string
  download_date: string
  post_date?: string
  media_type?: string
  width?: number
  height?: number
  duration?: number | null
  video_id?: string | null
  file_token?: string
}

// ============================================================================
// PAID CONTENT INTERFACES
// ============================================================================

export interface PaidContentService {
  id: string
  name: string
  base_url: string
  enabled: number
  session_cookie: string | null
  session_updated_at: string | null
  last_health_check: string | null
  health_status: string
  supported_services: string
  rate_limit_requests: number
  rate_limit_window_seconds: number
}

export interface PaidContentIdentity {
  id: number
  name: string
  slug: string
  profile_image_url: string | null
  notes: string | null
  created_at: string
  creator_count?: number
  creators?: PaidContentCreator[]
}

export interface PaidContentCreator {
  id: number
  service_id: string
  platform: string
  creator_id: string
  username: string
  display_name: string | null
  profile_image_url: string | null
  banner_image_url: string | null
  bio: string | null
  joined_date: string | null
  location: string | null
  external_links: string | null  // JSON string of [{title, url}]
  identity_id: number | null
  identity_name?: string | null
  enabled: number
  last_checked: string | null
  last_post_date: string | null
  post_count: number
  downloaded_count: number
  total_size_bytes: number
  auto_download: number
  download_embeds: number
  sync_posts: number
  sync_stories: number
  sync_highlights: number
  use_authenticated_api: number
  filter_tagged_users: string | null  // JSON string of username array
  created_at: string
  stats?: PaidContentCreatorStats
}

export interface PaidContentCreatorStats {
  total_posts: number
  downloaded_posts: number
  total_attachments: number
  downloaded_attachments: number
  total_size_bytes: number
  failed_downloads: number
}

export interface PaidContentCreatorGroup {
  id: number
  name: string
  description: string | null
  member_count: number
  members?: PaidContentCreatorGroupMember[]
  created_at: string
  updated_at: string
}

export interface PaidContentCreatorGroupMember {
  id: number
  username: string
  display_name: string | null
  platform: string
  service_id: string
  profile_image_url: string | null
  post_count: number
  enabled: number
  added_at: string
  filter_tagged_users: string | null
  filter_tag_ids: string | null
}

export interface PaidContentTag {
  id: number
  name: string
  slug: string
  color: string
  description: string | null
  created_at?: string
  post_count?: number
}

export interface PaidContentAutoTagRule {
  id: number
  name: string
  enabled: number
  conditions: {
    creator_id?: number
    platform?: string
    content_type?: string
    min_resolution?: string
    title_contains?: string
    description_contains?: string
  }
  tag_ids: number[]
  priority: number
  match_count: number
  created_at: string
  updated_at: string
}

export interface PaidContentWatchLaterItem {
  id: number
  attachment_id: number
  post_id: number
  creator_id: number
  position: number
  added_at: string
  filename: string
  file_type: string | null
  extension: string | null
  file_size: number | null
  width: number | null
  height: number | null
  duration: number | null
  status: string
  local_path: string | null
  local_filename: string | null
  post_title: string | null
  external_post_id: string
  username: string
  display_name: string | null
  platform: string
  profile_image_url: string | null
}

export interface PaidContentAnalyticsSummary {
  storage_growth: { date: string; daily_bytes: number; daily_count: number; cumulative_bytes: number }[]
  downloads_per_period: { period: string; count: number; bytes: number }[]
  storage_by_creator: { id: number; username: string; display_name: string | null; platform: string; profile_image_url: string | null; total_bytes: number; file_count: number }[]
  platform_distribution: { platform: string; post_count: number; file_count: number; total_bytes: number }[]
  content_type_distribution: { content_type: string; count: number; total_bytes: number }[]
  creator_scorecards: { id: number; username: string; display_name: string | null; platform: string; profile_image_url: string | null; post_count: number; file_count: number; total_bytes: number; latest_download: string | null; image_count: number; video_count: number }[]
}

export interface GalleryMediaItem {
  id: number
  post_id: number
  name: string
  file_type: string
  extension: string | null
  width: number | null
  height: number | null
  duration: number | null
  file_hash: string | null
  local_path: string | null
  local_filename: string | null
  file_size: number | null
  downloaded_at: string | null
  media_date: string | null
  post_title: string | null
  is_favorited: number
  creator_id: number
  username: string
  display_name: string | null
  platform: string
  service_id: string
  profile_image_url: string | null
  identity_name: string | null
}

export interface GalleryGroupCard {
  id: number
  name: string
  description: string | null
  member_count: number
  total_media: number
  latest_media_date: string | null
  representative_attachment_id: number | null
}

export interface PaidContentPost {
  id: number
  creator_id: number
  post_id: string
  title: string | null
  content: string | null
  published_at: string | null
  added_at: string | null
  has_attachments: number
  attachment_count: number
  downloaded: number
  download_date: string | null
  is_favorited: number
  is_viewed: number
  is_pinned: number
  pinned_at: string | null
  username: string
  platform: string
  service_id: string
  display_name: string | null
  profile_image_url: string | null
  identity_id: number | null
  identity_name?: string | null
  attachments: PaidContentAttachment[]
  embeds?: PaidContentEmbed[]
  tags?: PaidContentTag[]
  tagged_users?: string[]
  image_count?: number
  video_count?: number
}

export interface PaidContentAttachment {
  id: number
  post_id: number
  attachment_index: number
  name: string
  file_type: string | null
  extension: string | null
  server_path: string | null
  download_url: string | null
  file_size: number | null
  width: number | null
  height: number | null
  duration: number | null
  status: string
  local_path: string | null
  local_filename: string | null
  file_hash: string | null
  error_message: string | null
  download_attempts: number
  downloaded_at: string | null
  created_at: string | null
  file_token?: string
}

export interface PaidContentEmbed {
  id: number
  post_id: number
  url: string
  platform: string | null
  video_id: string | null
  title: string | null
  status: string
  local_path: string | null
  error_message: string | null
}

export interface PaidContentConversation {
  creator_id: number
  username: string
  display_name: string | null
  platform: string
  service_id: string
  profile_image_url: string | null
  message_count: number
  unread_count: number
  last_message_at: string | null
  last_message_text: string | null
  last_message_from_creator: number | null
}

export interface PaidContentMessage {
  id: number
  creator_id: number
  message_id: string
  text: string | null
  sent_at: string | null
  is_from_creator: number
  is_tip: number
  tip_amount: number | null
  price: number | null
  is_free: number
  is_purchased: number
  has_attachments: number
  attachment_count: number
  is_read: number
  reply_to_message_id: string | null
  metadata: string | null
  created_at: string
  attachments: PaidContentAttachment[]
}

export interface PaidContentNotification {
  id: number
  notification_type: string
  creator_id: number | null
  post_id: number | null
  title: string
  message: string
  download_count: number
  file_count: number
  is_read: number
  read_at: string | null
  created_at: string
  username?: string
  platform?: string
  metadata?: {
    media_files?: Array<{
      id: number
      local_path: string
      file_path: string
      filename: string
      file_type: string
      source?: string
      content_type?: string
      attachment_id?: number
      width?: number
      height?: number
      duration?: number
      file_size?: number
      post_content?: string
      post_date?: string
      platform?: string
    }>
  }
}

export interface PaidContentSettings {
  base_download_path: string
  organize_by_date: number
  organize_by_post: number
  check_interval_hours: number
  max_concurrent_downloads: number
  download_embeds: number
  embed_quality: string
  notifications_enabled: number
  push_notifications_enabled: number
  perceptual_duplicate_detection: number
  perceptual_threshold: number
  auto_retry_failed: number
  retry_max_attempts: number
}

// Attachment status constants
export const ATTACHMENT_STATUS = {
  PENDING: 'pending',
  DOWNLOADING: 'downloading',
  COMPLETED: 'completed',
  FAILED: 'failed',
  SKIPPED: 'skipped',
  MISSING: 'missing',
  DUPLICATE: 'duplicate',
} as const

export type AttachmentStatus = typeof ATTACHMENT_STATUS[keyof typeof ATTACHMENT_STATUS]

// Embed status constants
export const EMBED_STATUS = {
  PENDING: 'pending',
  DOWNLOADING: 'downloading',
  COMPLETED: 'completed',
  FAILED: 'failed',
  SKIPPED: 'skipped',
} as const

export type EmbedStatus = typeof EMBED_STATUS[keyof typeof EMBED_STATUS]

// Active task types for sync operations
export interface PaidContentActiveDownload {
  name: string
  size: number | null
  progress: number
}

export interface PaidContentActiveTask {
  creator_id: number
  username: string
  platform: string
  service: string
  status: string
  phase: 'fetching' | 'processing' | 'downloading' | 'backfilling'
  posts_fetched?: number
  total_files?: number
  downloaded?: number
  progress?: number
  failed?: number
  active_downloads?: PaidContentActiveDownload[]
  active_count?: number
  started_at: string
  updated_at?: string
}

export interface PaidContentDashboardStats {
  total_creators: number
  all_creators: number
  total_posts: number
  downloaded_posts: number
  total_files: number
  total_size_bytes: number
  failed_downloads: number
  pending_downloads: number
  unread_notifications: number
  storage_by_creator: Array<{
    id: number
    username: string
    platform: string
    service_id: string
    total_size: number
    file_count: number
    profile_image_url?: string
    display_name?: string
  }>
  recent_activity: Array<{
    date: string
    count: number
  }>
  services: Array<{
    id: string
    name: string
    health_status: string
    last_health_check: string | null
    creator_count: number
  }>
}

export interface PaidContentRecycleBinItem {
  id: number
  item_type: string
  original_id: number
  original_data: Record<string, unknown>
  deleted_at: string
  deleted_by: string | null
  restore_path: string | null
}

// Review queue file type
export interface ReviewFile {
  id?: number
  filename: string
  file_path: string
  file_size: number
  platform: string
  source: string
  added_date: string
  post_date?: string
  width?: number
  height?: number
  video_id?: string | null
  original_path?: string
  file_token?: string
  face_recognition?: {
    scanned: boolean
    matched?: boolean
    person_name?: string
    confidence?: number
    scan_date?: string
  }
}

// API client
class APIClient {
  private retryConfig: RetryConfig = DEFAULT_RETRY_CONFIG

  private getCsrfToken(): string | null {
    // Get CSRF token from cookie
    const cookies = document.cookie.split('; ')
    const csrfCookie = cookies.find(row => row.startsWith('csrftoken='))
    return csrfCookie ? csrfCookie.split('=')[1] : null
  }

  private getAuthHeaders(method: string = 'GET'): HeadersInit {
    const token = localStorage.getItem('auth_token')
    const headers: HeadersInit = {
      'Content-Type': 'application/json',
    }
    if (token) {
      headers['Authorization'] = `Bearer ${token}`
    }

    // Add CSRF token for non-GET requests
    if (method !== 'GET') {
      const csrfToken = this.getCsrfToken()
      if (csrfToken) {
        headers['X-CSRFToken'] = csrfToken
      }
    }

    return headers
  }

  /**
   * Extract error message from API response.
   * Centralized error extraction to avoid duplication.
   */
  private async extractErrorMessage(response: Response): Promise<string> {
    let errorMessage = response.statusText
    try {
      const errorData = await response.json()
      if (errorData.detail) {
        if (typeof errorData.detail === 'object') {
          errorMessage = errorData.detail.message || JSON.stringify(errorData.detail)
        } else {
          errorMessage = errorData.detail
        }
      }
    } catch {
      // If parsing fails, use statusText
    }
    return errorMessage
  }

  /**
   * Handle response errors and unauthorized status.
   * Centralized response handling to avoid duplication.
   */
  private async handleResponseError(response: Response): Promise<never> {
    if (response.status === 401) {
      this.handleUnauthorized()
    }
    const errorMessage = await this.extractErrorMessage(response)
    throw new Error(errorMessage)
  }

  /**
   * Execute a fetch request with retry logic and exponential backoff.
   * Only retries on network errors and specific HTTP status codes.
   */
  private async fetchWithRetry<T>(
    endpoint: string,
    options: RequestInit,
    requestOptions?: { timeout?: number; retry?: boolean | Partial<RetryConfig> }
  ): Promise<T> {
    const shouldRetry = requestOptions?.retry !== false
    const config: RetryConfig = {
      ...this.retryConfig,
      ...(typeof requestOptions?.retry === 'object' ? requestOptions.retry : {})
    }

    let lastError: Error | null = null

    for (let attempt = 0; attempt <= (shouldRetry ? config.maxRetries : 0); attempt++) {
      const controller = new AbortController()
      const timeoutId = requestOptions?.timeout
        ? setTimeout(() => controller.abort(), requestOptions.timeout)
        : null

      try {
        const response = await fetch(`${API_BASE}${endpoint}`, {
          ...options,
          credentials: 'include',
          signal: controller.signal,
        })

        if (timeoutId) clearTimeout(timeoutId)

        if (!response.ok) {
          // Check if we should retry this status code
          if (shouldRetry && attempt < config.maxRetries && isRetryableError(response.status, config)) {
            const delay = calculateBackoffDelay(attempt, config)
            console.warn(`API request to ${endpoint} failed with status ${response.status}, retrying in ${delay}ms (attempt ${attempt + 1}/${config.maxRetries})`)
            await sleep(delay)
            continue
          }
          await this.handleResponseError(response)
        }

        return response.json()
      } catch (error: unknown) {
        if (timeoutId) clearTimeout(timeoutId)

        // Handle abort errors (timeout)
        if (error instanceof Error && error.name === 'AbortError') {
          lastError = new Error(requestOptions?.timeout && requestOptions.timeout > 60000
            ? 'Request timed out. The operation may still be processing.'
            : 'Request timed out')

          // Retry on timeout if allowed
          if (shouldRetry && attempt < config.maxRetries) {
            const delay = calculateBackoffDelay(attempt, config)
            console.warn(`API request to ${endpoint} timed out, retrying in ${delay}ms (attempt ${attempt + 1}/${config.maxRetries})`)
            await sleep(delay)
            continue
          }
          throw lastError
        }

        // Retry on network errors
        if (error instanceof TypeError && error.message.includes('fetch')) {
          lastError = error
          if (shouldRetry && attempt < config.maxRetries) {
            const delay = calculateBackoffDelay(attempt, config)
            console.warn(`Network error on ${endpoint}, retrying in ${delay}ms (attempt ${attempt + 1}/${config.maxRetries})`)
            await sleep(delay)
            continue
          }
        }

        throw error
      }
    }

    // This should not be reached, but TypeScript requires a return
    throw lastError || new Error('Request failed after retries')
  }

  async get<T>(endpoint: string, options?: { timeout?: number; retry?: boolean | Partial<RetryConfig> }): Promise<T> {
    return this.fetchWithRetry<T>(endpoint, {
      method: 'GET',
      headers: this.getAuthHeaders(),
    }, options)
  }

  async post<T>(endpoint: string, data?: Record<string, unknown> | unknown[], options?: { timeout?: number; retry?: boolean | Partial<RetryConfig> }): Promise<T> {
    return this.fetchWithRetry<T>(endpoint, {
      method: 'POST',
      headers: this.getAuthHeaders('POST'),
      body: data ? JSON.stringify(data) : undefined,
    }, options)
  }

  async put<T>(endpoint: string, data: Record<string, unknown>, options?: { timeout?: number; retry?: boolean | Partial<RetryConfig> }): Promise<T> {
    return this.fetchWithRetry<T>(endpoint, {
      method: 'PUT',
      headers: this.getAuthHeaders('PUT'),
      body: JSON.stringify(data),
    }, options)
  }

  async patch<T>(endpoint: string, data: Record<string, unknown>, options?: { timeout?: number; retry?: boolean | Partial<RetryConfig> }): Promise<T> {
    return this.fetchWithRetry<T>(endpoint, {
      method: 'PATCH',
      headers: this.getAuthHeaders('PATCH'),
      body: JSON.stringify(data),
    }, options)
  }

  async delete<T>(endpoint: string, data?: Record<string, unknown>, options?: { timeout?: number; retry?: boolean | Partial<RetryConfig> }): Promise<T> {
    return this.fetchWithRetry<T>(endpoint, {
      method: 'DELETE',
      headers: this.getAuthHeaders('DELETE'),
      body: data ? JSON.stringify(data) : undefined,
    }, options)
  }

  private handleUnauthorized() {
    localStorage.removeItem('auth_token')
    localStorage.removeItem('auth_user')
    // Reload the page to trigger MemoryRouter re-initialization
    window.location.reload()
  }

  // Authentication methods
  async login(username: string, password: string, rememberMe: boolean = false) {
    const response = await fetch(`${API_BASE}/auth/login`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ username, password, rememberMe }),
      credentials: 'include',  // Include cookies to receive auth_token and csrftoken
    })

    if (!response.ok) {
      const error = await response.json().catch(() => ({ detail: response.statusText }))
      throw new Error(error.detail || 'Login failed')
    }

    return response.json()
  }


  async logout() {
    try {
      await this.post('/auth/logout')
    } catch (error) {
      console.error('Logout error:', error)
    } finally {
      localStorage.removeItem('auth_token')
      localStorage.removeItem('auth_user')
      // Reload the page to trigger MemoryRouter re-initialization
      window.location.reload()
    }
  }

  async getMe() {
    return this.get<User>('/auth/me')
  }


  async changePassword(currentPassword: string, newPassword: string) {
    return this.post<{ success: boolean }>('/auth/change-password', {
      current_password: currentPassword,
      new_password: newPassword,
    })
  }

  async updatePreferences(preferences: UserPreferences) {
    const result = await this.post<{ success: boolean; preferences: UserPreferences }>('/auth/preferences', preferences)

    // Update cached user object with new preferences
    const user = this.getAuthUser()
    if (user) {
      user.preferences = result.preferences
      localStorage.setItem('auth_user', JSON.stringify(user))
    }

    return result
  }

  setAuthToken(token: string, user: User, sessionId?: string) {
    localStorage.setItem('auth_token', token)
    localStorage.setItem('auth_user', JSON.stringify(user))
    if (sessionId) {
      localStorage.setItem('sessionId', sessionId)
    }
  }

  getAuthToken(): string | null {
    return localStorage.getItem('auth_token')
  }

  getAuthUser(): User | null {
    const user = localStorage.getItem('auth_user')
    if (!user) return null
    try {
      return JSON.parse(user) as User
    } catch {
      // Invalid JSON in localStorage, clear it
      localStorage.removeItem('auth_user')
      return null
    }
  }

  isAuthenticated(): boolean {
    return !!this.getAuthToken()
  }

  // Specific API methods
  async getStatus() {
    return this.get<SystemStatus>('/status')
  }

  // Day-grouped downloads for new Downloads page UI
  async getDownloadsByDay(params?: {
    location?: 'all' | 'media' | 'review' | 'recycle'
    platform?: string
    source?: string
    limit_days?: number
    offset_date?: string
    items_per_day?: number
    date_from?: string
    date_to?: string
    size_min?: number
    size_max?: number
    search?: string
  }) {
    const query = new URLSearchParams()
    if (params?.location) query.set('location', params.location)
    if (params?.platform) query.set('platform', params.platform)
    if (params?.source) query.set('source', params.source)
    if (params?.limit_days !== undefined) query.set('limit_days', params.limit_days.toString())
    if (params?.offset_date) query.set('offset_date', params.offset_date)
    if (params?.items_per_day !== undefined) query.set('items_per_day', params.items_per_day.toString())
    if (params?.date_from) query.set('date_from', params.date_from)
    if (params?.date_to) query.set('date_to', params.date_to)
    if (params?.size_min !== undefined) query.set('size_min', params.size_min.toString())
    if (params?.size_max !== undefined) query.set('size_max', params.size_max.toString())
    if (params?.search) query.set('search', params.search)

    return this.get<{
      days: Array<{
        date: string
        count: number
        items: Array<{
          id: number
          file_path: string
          filename: string
          platform: string
          source: string
          content_type: string
          media_type: 'image' | 'video'
          file_size: number
          width: number | null
          height: number | null
          download_date: string
          deleted_from: string | null
          location_type?: 'media' | 'review' | 'recycle'
        }>
        summary: {
          by_location: { media: number; review: number; recycle: number }
          by_platform: Record<string, number>
        }
      }>
      total_days: number
      total_items: number
      location: string
      has_more: boolean
      summary: {
        by_location: { media: number; review: number; recycle: number }
        by_platform: Record<string, number>
      }
    }>(`/downloads/by-day?${query}`)
  }

  async getDownloadsByDayFilters(location: 'all' | 'media' | 'review' | 'recycle', platform?: string) {
    const params = new URLSearchParams({ location })
    if (platform) params.append('platform', platform)
    return this.get<{
      platforms: string[]
      sources: string[]
      location: string
    }>(`/downloads/by-day/filters?${params}`)
  }

  async searchDownloads(params?: {
    limit?: number
    offset?: number
    query?: string
    platforms?: string[]
    sources?: string[]
    contentTypes?: string[]
    mediaType?: 'image' | 'video'
    dateFrom?: string
    dateTo?: string
    sizeMin?: number
    sizeMax?: number
    sortBy?: string
    sortOrder?: 'asc' | 'desc'
  }) {
    const query = new URLSearchParams()
    if (params?.limit !== undefined) query.set('limit', params.limit.toString())
    if (params?.offset !== undefined) query.set('offset', params.offset.toString())
    if (params?.query) query.set('query', params.query)
    if (params?.platforms && params.platforms.length > 0) {
      query.set('platforms', params.platforms.join(','))
    }
    if (params?.sources && params.sources.length > 0) {
      query.set('sources', params.sources.join(','))
    }
    if (params?.contentTypes && params.contentTypes.length > 0) {
      query.set('content_types', params.contentTypes.join(','))
    }
    if (params?.mediaType) query.set('media_type', params.mediaType)
    if (params?.dateFrom) query.set('date_from', params.dateFrom)
    if (params?.dateTo) query.set('date_to', params.dateTo)
    if (params?.sizeMin !== undefined) query.set('size_min', params.sizeMin.toString())
    if (params?.sizeMax !== undefined) query.set('size_max', params.sizeMax.toString())
    if (params?.sortBy) query.set('sort_by', params.sortBy)
    if (params?.sortOrder) query.set('sort_order', params.sortOrder)

    return this.get<{
      results: Download[]
      total: number
      limit: number
      offset: number
      has_more: boolean
    }>(`/downloads/search?${query}`)
  }

  async getStats() {
    return this.get<Stats>('/downloads/stats')
  }

  async getAnalytics() {
    return this.get<{
      downloads_per_day: Array<{ date: string; count: number }>
      file_types: Array<{ type: string; count: number; size: number }>
      storage_by_platform: Array<{
        platform: string
        count: number
        total_size: number
        avg_size: number
      }>
      top_sources: Array<{ source: string; count: number; platform: string }>
      hourly_distribution: Array<{ hour: number; count: number }>
      weekly_comparison: {
        this_week: number
        last_week: number
        growth_rate: number
      }
    }>('/downloads/analytics')
  }

  async getDashboardRecentItems(since?: string) {
    const params = since ? `?since=${encodeURIComponent(since)}` : ''
    return this.get<{
      media: {
        count: number
        items: Array<{
          id: number
          file_path: string
          filename: string
          source: string
          platform: string
          media_type: string
          file_size: number
          added_at: string
          width: number | null
          height: number | null
        }>
      }
      review: {
        count: number
        items: Array<{
          id: number
          file_path: string
          filename: string
          source: string
          platform: string
          media_type: string
          file_size: number
          added_at: string
          width: number | null
          height: number | null
          face_recognition: {
            scanned: boolean
            matched: boolean
            confidence: number | null
            matched_person: string | null
          } | null
        }>
      }
      internet_discovery: {
        count: number
        items: Array<{
          id: number
          video_id: string
          title: string
          thumbnail: string
          channel_name: string
          platform: string
          duration: number
          max_resolution: number | null
          status: string
          discovered_at: string
          url: string
          view_count: number
          upload_date: string | null
          celebrity_name: string
        }>
      }
    }>(`/dashboard/recent-items${params}`)
  }

  async getDismissedCards() {
    return this.get<{
      media: number | null
      review: number | null
      internet_discovery: number | null
    }>('/dashboard/dismissed-cards')
  }

  async setDismissedCards(data: {
    media: number | null
    review: number | null
    internet_discovery: number | null
  }) {
    return this.post<{ status: string }>('/dashboard/dismissed-cards', data)
  }

  async getDownloadFilters(platform?: string) {
    const params = platform ? `?platform=${encodeURIComponent(platform)}` : ''
    return this.get<{ platforms: string[]; sources: string[] }>(`/downloads/filters${params}`)
  }

  async getRecycleFilters(platform?: string) {
    const params = platform ? `?platform=${encodeURIComponent(platform)}` : ''
    return this.get<{ platforms: string[]; sources: string[] }>(`/recycle/filters${params}`)
  }

  async getPlatforms() {
    return this.get<Platform[]>('/platforms')
  }

  async triggerDownload(platform: string, username?: string) {
    return this.post(`/platforms/${platform}/trigger`, {
      username,
    })
  }

  async getRunningPlatforms() {
    return this.get<{
      platforms: Array<{ platform: string; started_at: string; session_id: string }>
      count: number
    }>('/platforms/running')
  }

  async stopPlatformDownload(platform: string) {
    return this.post<{
      success: boolean
      platform: string
      message: string
    }>(`/platforms/${platform}/stop`)
  }

  // YouTube Channel Monitors - Global Settings + Channels
  async getYouTubeMonitors() {
    return this.get<{
      success: boolean
      settings: YouTubeMonitorSettings
      channels: YouTubeChannel[]
      count: number
    }>('/platforms/youtube-monitors')
  }

  async updateYouTubeMonitorSettings(data: YouTubeMonitorSettingsUpdate) {
    return this.put<{
      success: boolean
      settings: YouTubeMonitorSettings
      message: string
    }>('/platforms/youtube-monitors/settings', data)
  }

  async addYouTubeChannel(data: YouTubeChannelCreate) {
    return this.post<{
      success: boolean
      channel: YouTubeChannel
      message: string
    }>('/platforms/youtube-monitors/channels', data)
  }

  async updateYouTubeChannel(id: number, data: YouTubeChannelUpdate) {
    return this.put<{
      success: boolean
      channel: YouTubeChannel
      message: string
    }>(`/platforms/youtube-monitors/channels/${id}`, data)
  }

  async deleteYouTubeChannel(id: number) {
    return this.delete<{
      success: boolean
      message: string
    }>(`/platforms/youtube-monitors/channels/${id}`)
  }

  async checkAllYouTubeChannels() {
    return this.post<{
      success: boolean
      message: string
      channels_count: number
    }>('/platforms/youtube-monitors/check-all', {})
  }

  async checkYouTubeChannel(id: number) {
    return this.post<{
      success: boolean
      message: string
      channel_name: string
    }>(`/platforms/youtube-monitors/channels/${id}/check-now`, {})
  }

  async checkSelectedYouTubeChannels(channelIds: number[]) {
    return this.post<{
      success: boolean
      message: string
      channel_count: number
    }>('/platforms/youtube-monitors/check-selected', { channel_ids: channelIds })
  }

  async getYouTubeChannelHistory(id: number, limit: number = 50) {
    return this.get<{
      success: boolean
      history: YouTubeMonitorHistory[]
      count: number
    }>(`/platforms/youtube-monitors/channels/${id}/history?limit=${limit}`)
  }

  async getYouTubeChannelsFiltered(params: {
    status?: string
    always_active?: string
    search?: string
    sort_field?: string
    sort_ascending?: boolean
    limit?: number
    offset?: number
  }) {
    const queryParams = new URLSearchParams()
    if (params.status) queryParams.append('status', params.status)
    if (params.always_active) queryParams.append('always_active', params.always_active)
    if (params.search) queryParams.append('search', params.search)
    if (params.sort_field) queryParams.append('sort_field', params.sort_field)
    if (params.sort_ascending !== undefined) queryParams.append('sort_ascending', String(params.sort_ascending))
    if (params.limit) queryParams.append('limit', String(params.limit))
    if (params.offset) queryParams.append('offset', String(params.offset))

    const url = `/platforms/youtube-monitors/channels${queryParams.toString() ? '?' + queryParams.toString() : ''}`
    return this.get<{
      success: boolean
      channels: YouTubeChannel[]
      total: number
    }>(url)
  }

  async getYouTubeMonitorStatistics() {
    return this.get<{
      success: boolean
      statistics: {
        total: number
        active: number
        paused_manual: number
        paused_auto: number
        always_active_count: number
        total_videos: number
      }
    }>('/platforms/youtube-monitors/statistics')
  }

  async pauseYouTubeChannel(id: number, reason?: string) {
    return this.post<{
      success: boolean
      message: string
    }>(`/platforms/youtube-monitors/channels/${id}/pause`, { reason })
  }

  async resumeYouTubeChannel(id: number) {
    return this.post<{
      success: boolean
      message: string
    }>(`/platforms/youtube-monitors/channels/${id}/resume`, {})
  }

  async toggleYouTubeChannelAlwaysActive(id: number, value: boolean) {
    return this.post<{
      success: boolean
      message: string
    }>(`/platforms/youtube-monitors/channels/${id}/toggle-always-active`, { value })
  }

  async getConfig() {
    return this.get<AppConfig>('/config')
  }

  async updateConfig(config: AppConfig) {
    return this.put('/config', { config })
  }

  // Instagram Unified
  async getInstagramUnifiedConfig() {
    return this.get<{
      config: Record<string, any>
      migrated: boolean
      hidden_modules: string[]
      scraper_capabilities: Record<string, Record<string, boolean>>
      scraper_labels: Record<string, string>
    }>('/instagram-unified/config')
  }

  async updateInstagramUnifiedConfig(config: Record<string, any>) {
    return this.put<{
      success: boolean
      message: string
      legacy_configs_updated: string[]
    }>('/instagram-unified/config', { config })
  }

  async getInstagramCapabilities() {
    return this.get<{
      scraper_capabilities: Record<string, Record<string, boolean>>
      scraper_labels: Record<string, string>
      hidden_modules: string[]
      content_types: string[]
    }>('/instagram-unified/capabilities')
  }

  async getSetting(key: string) {
    return this.get<SettingValue>(`/settings/${key}`)
  }

  async updateSetting(key: string, value: string | number | boolean | object, category?: string, description?: string) {
    return this.put(`/settings/${key}`, { value, category, description })
  }

  async getMergedLogs(lines: number = 500, components: string[], aroundTime?: string) {
    return this.post<{
      logs: string[]
      available_components: string[]
      selected_components: string[]
      total_logs: number
    }>('/logs/merged', { lines, components, around_time: aroundTime })
  }

  async getSchedulerStatus() {
    return this.get<{
      running: boolean
      tasks: Array<{
        task_id: string
        last_run: string | null
        next_run: string | null
        run_count: number
        status: string
        last_download_count: number
      }>
      total_tasks: number
      active_tasks: number
      next_run: string | null
    }>('/scheduler/status')
  }

  async getCurrentActivity() {
    return this.get<{
      active: boolean
      scheduler_running?: boolean
      task_id: string | null
      platform: string | null
      account: string | null
      start_time: string | null
      status: string | null
      detailed_status?: string
      progress?: {
        current: number
        total: number
      }
      account_progress?: {
        current: number
        total: number
      }
    }>('/scheduler/current-activity')
  }

  async getBackgroundTasks() {
    return this.get<{
      tasks: Array<{
        task_id: string
        active: boolean
        task_type: string
        display_name: string
        start_time: string
        status: string
        detailed_status?: string
        progress?: {
          current: number
          total: number
        }
        extra_data?: {
          videos_found?: number
          current_channel?: string
          last_found?: number
          total_channels?: number
          total_presets?: number
          current_preset?: string
          phase?: string
        }
        updated_at: string
      }>
    }>('/scheduler/background-tasks')
  }

  async getBackgroundTask(taskId: string) {
    return this.get<{
      task_id: string
      active: boolean
      task_type?: string
      display_name?: string
      start_time?: string
      status?: string
      detailed_status?: string
      progress?: {
        current: number
        total: number
      }
      extra_data?: {
        videos_found?: number
        current_channel?: string
        last_found?: number
        total_channels?: number
        current_community?: string
        total_communities?: number
        media_found?: number
        phase?: string
        files_downloaded?: number
        posts_imported?: number
        posts_total?: number
        current_file?: number
        total_files?: number
      }
      updated_at?: string
    }>(`/scheduler/background-tasks/${taskId}`)
  }

  async skipNextRun(taskId: string) {
    return this.post(`/scheduler/tasks/${taskId}/skip`, {})
  }

  async getSystemHealth() {
    return this.get<{
      overall_status: 'healthy' | 'warning' | 'error'
      timestamp: string
      services: {
        api: 'healthy' | 'warning' | 'error'
        database: 'healthy' | 'warning' | 'error'
        scheduler: 'healthy' | 'warning' | 'error'
        websocket: 'healthy' | 'warning' | 'error'
        cache_builder: 'healthy' | 'warning' | 'error' | 'unknown'
      }
      system: {
        cpu_percent: number
        cpu_count: number
        memory_percent: number
        memory_used: number
        memory_total: number
        disk_percent: number
        disk_used: number
        disk_total: number
        disk_free: number
        uptime_hours: number
        disks: Array<{
          mountpoint: string
          device: string
          fstype: string
          total: number
          used: number
          free: number
          percent: number
        }>
      }
      scheduler_info: {
        active_tasks: number
        total_tasks: number
      }
      websocket_info: {
        active_connections: number
      }
      cache_info: {
        files_cached: number
        last_run: string | null
      }
      db_performance: {
        query_time_ms: number
        connection_pool_size: number
      }
      process_info: {
        threads: number
        open_files: number
      }
      download_activity: {
        last_24h: number
        last_7d: number
        last_30d: number
      }
      recent_errors: Array<{
        message: string
        details?: string
        timestamp: string
      }>
    }>('/health/system')
  }

  async pauseSchedulerTask(taskId: string) {
    return this.post(`/scheduler/tasks/${taskId}/pause`)
  }

  async resumeSchedulerTask(taskId: string) {
    return this.post(`/scheduler/tasks/${taskId}/resume`)
  }

  async rescheduleTask(taskId: string, nextRun: string) {
    return this.post<{ success: boolean; task_id: string; new_next_run: string }>(
      `/scheduler/tasks/${taskId}/reschedule`,
      { next_run: nextRun }
    )
  }

  async getSchedulerServiceStatus() {
    return this.get<{
      running: boolean
      status: string
    }>('/scheduler/service/status')
  }

  async startSchedulerService() {
    return this.post<{ success: boolean; message: string }>('/scheduler/service/start')
  }

  async stopSchedulerService() {
    return this.post<{ success: boolean; message: string }>('/scheduler/service/stop')
  }

  async restartSchedulerService() {
    return this.post<{ success: boolean; message: string }>('/scheduler/service/restart')
  }

  async getDependenciesStatus() {
    return this.get<{
      last_check: string | null
      components: Record<string, {
        last_update?: string
        last_check?: string
        status?: string
        updated_packages?: string[]
      }>
    }>('/scheduler/dependencies/status')
  }

  async checkDependencies() {
    return this.post<{
      success: boolean
      results: Record<string, boolean>
      message: string
    }>('/scheduler/dependencies/check')
  }

  async triggerCacheBuilder() {
    return this.post<{
      success: boolean
      message: string
    }>('/scheduler/cache-builder/trigger')
  }


  async getMediaGallery(params?: {
    platform?: string
    source?: string
    media_type?: string
    face_recognition?: string
    limit?: number
    offset?: number
    sort_by?: string
    sort_order?: 'asc' | 'desc'
    date_from?: string
    date_to?: string
    size_min?: number
    size_max?: number
    search?: string
    shuffle?: boolean
    shuffle_seed?: number
  }) {
    const query = new URLSearchParams()
    if (params?.platform) query.set('platform', params.platform)
    if (params?.source) query.set('source', params.source)
    if (params?.media_type) query.set('media_type', params.media_type)
    if (params?.face_recognition) query.set('face_recognition', params.face_recognition)
    if (params?.limit !== undefined) query.set('limit', params.limit.toString())
    if (params?.offset !== undefined) query.set('offset', params.offset.toString())
    if (params?.sort_by) query.set('sort_by', params.sort_by)
    if (params?.sort_order) query.set('sort_order', params.sort_order)
    if (params?.date_from) query.set('date_from', params.date_from)
    if (params?.date_to) query.set('date_to', params.date_to)
    if (params?.size_min !== undefined) query.set('size_min', params.size_min.toString())
    if (params?.size_max !== undefined) query.set('size_max', params.size_max.toString())
    if (params?.search) query.set('search', params.search)
    if (params?.shuffle) query.set('shuffle', 'true')
    if (params?.shuffle_seed !== undefined) query.set('shuffle_seed', params.shuffle_seed.toString())

    return this.get<{
      media: Array<{
        id: number
        platform: string
        source: string
        filename: string
        file_path: string
        file_size: number
        media_type: 'image' | 'video'
        download_date: string
        post_date?: string
        width?: number
        height?: number
        duration?: number | null
      }>
      total: number
      limit: number
      offset: number
    }>(`/media/gallery?${query}`)
  }

  async getMediaGalleryDateRange(params?: {
    media_type?: string
  }) {
    const query = new URLSearchParams()
    if (params?.media_type) query.set('media_type', params.media_type)
    const qs = query.toString()
    return this.get<{
      ranges: Array<{ year: number; month: number; count: number }>
    }>(`/media/gallery/date-range${qs ? '?' + qs : ''}`).then(r => r.ranges)
  }

  getMediaPreviewUrl(filePath: string, fileToken?: string) {
    if (fileToken) {
      return `${API_BASE}/media/preview?t=${encodeURIComponent(fileToken)}`
    }
    return `${API_BASE}/media/preview?file_path=${encodeURIComponent(filePath)}`
  }

  getMediaThumbnailUrl(filePath: string, mediaType: 'image' | 'video', fileToken?: string) {
    if (fileToken) {
      return `${API_BASE}/media/thumbnail?t=${encodeURIComponent(fileToken)}&media_type=${mediaType}`
    }
    return `${API_BASE}/media/thumbnail?file_path=${encodeURIComponent(filePath)}&media_type=${mediaType}`
  }

  async batchDeleteMedia(filePaths: string[]) {
    return this.post<{
      success: boolean
      deleted: string[]
      errors: Array<{ file: string; error: string }>
      deleted_count: number
      error_count: number
    }>('/media/batch-delete', filePaths)
  }

  async batchMoveMedia(filePaths: string[], destination: string) {
    return this.post<{
      success: boolean
      moved: string[]
      errors: Array<{ file: string; error: string }>
      moved_count: number
      error_count: number
    }>('/media/batch-move', { file_paths: filePaths, destination })
  }

  async moveToReview(filePaths: string[]) {
    return this.post<{
      success: boolean
      moved: string[]
      errors: Array<{ file: string; error: string }>
      moved_count: number
      error_count: number
    }>('/media/move-to-review', { file_paths: filePaths })
  }

  async updateMediaDate(
    ids: number[],
    newDate: string,
    updateFile: boolean = true,
    dateType: 'post_date' | 'download_date' = 'post_date'
  ) {
    return this.post<{
      success: boolean
      results: Array<{
        id: number
        success: boolean
        error?: string
        db_rows_updated?: number
        file_updated?: boolean | null
      }>
      success_count: number
      failed_count: number
    }>('/media/update-date', {
      ids,
      new_date: newDate,
      update_file: updateFile,
      date_type: dateType
    })
  }

  async checkFileStatuses(filePaths: string[]) {
    return this.post<{
      file_statuses: Record<string, {
        status: 'media' | 'review' | 'recycle' | 'deleted' | 'missing'
        current_path?: string
        recycle_id?: string
      }>
    }>('/media/check-status', { file_paths: filePaths })
  }

  async batchDownloadMedia(filePaths: string[]) {
    const response = await fetch(`${API_BASE}/media/batch-download`, {
      method: 'POST',
      headers: this.getAuthHeaders('POST'),
      body: JSON.stringify(filePaths),
      credentials: 'include',  // Include cookies for CSRF token
    })
    if (!response.ok) {
      throw new Error(`API error: ${response.statusText}`)
    }
    // Return blob for download
    const blob = await response.blob()
    const url = window.URL.createObjectURL(blob)
    const a = document.createElement('a')
    a.href = url
    a.download = `media-batch-${Date.now()}.zip`
    document.body.appendChild(a)
    a.click()
    document.body.removeChild(a)
    window.URL.revokeObjectURL(url)
    return { success: true }
  }

  // Review Queue API methods
  async getReviewQueue(params?: { limit?: number; offset?: number; platform?: string; source?: string; media_type?: string; sort_by?: string; sort_order?: 'asc' | 'desc'; date_from?: string; date_to?: string; size_min?: number; size_max?: number; search?: string }) {
    const query = new URLSearchParams()
    if (params?.limit !== undefined) query.set('limit', params.limit.toString())
    if (params?.offset !== undefined) query.set('offset', params.offset.toString())
    if (params?.platform) query.set('platform', params.platform)
    if (params?.source) query.set('source', params.source)
    if (params?.media_type) query.set('media_type', params.media_type)
    if (params?.sort_by) query.set('sort_by', params.sort_by)
    if (params?.sort_order) query.set('sort_order', params.sort_order)
    if (params?.date_from) query.set('date_from', params.date_from)
    if (params?.date_to) query.set('date_to', params.date_to)
    if (params?.size_min !== undefined) query.set('size_min', params.size_min.toString())
    if (params?.size_max !== undefined) query.set('size_max', params.size_max.toString())
    if (params?.search) query.set('search', params.search)

    return this.get<{
      files: Array<{
        filename: string
        file_path: string
        file_size: number
        added_date: string
        source: string
        platform: string
        original_path: string
        face_recognition?: {
          scanned: boolean
          matched?: boolean
          person_name?: string
          confidence?: number
          scan_date?: string
        }
      }>
      total: number
    }>(`/review/list?${query}`)
  }

  async getReviewFilters(platform?: string) {
    const params = platform ? `?platform=${encodeURIComponent(platform)}` : ''
    return this.get<{
      platforms: string[]
      sources: string[]
    }>(`/review/filters${params}`)
  }

  async getAllReviewFilePaths() {
    return this.get<{ file_paths: string[] }>('/review/all-paths')
  }

  async reviewKeep(filePath: string, destination: string) {
    return this.post<{
      success: boolean
      destination: string
    }>('/review/keep', { file_path: filePath, destination })
  }

  async reviewDelete(filePath: string) {
    return this.delete<{ success: boolean }>('/review/delete', { file_path: filePath })
  }


  async reviewRescan() {
    return this.post<{ success: boolean; message: string; total: number }>('/review/rescan', {})
  }

  async reviewRescanStatus() {
    return this.get<{ running: boolean; progress: { current: number; total: number; current_file: string; complete?: boolean; stats?: Record<string, unknown>; error?: string } }>('/review/rescan/status')
  }

  async reviewAddReference(filePath: string, personName: string, destination: string) {
    return this.post<{
      success: boolean
      destination: string
      person_name: string
      face_added: boolean
      face_error?: string
    }>('/review/add-reference', { file_path: filePath, person_name: personName, destination })
  }

  async addFaceReference(filePath: string, personName?: string, background: boolean = false) {
    return this.post<{
      success: boolean
      message: string
      person_name: string
      file_path: string
    }>('/face/add-reference', { file_path: filePath, person_name: personName, background })
  }

  async getFaceReferenceStats() {
    return this.get<{
      total: number
      by_person: {
        [personName: string]: {
          count: number
          first_added: string
          last_added: string
        }
      }
    }>('/face/reference-stats')
  }

  async getFaceReferences() {
    return this.get<{
      references: Array<{
        id: number
        person_name: string
        reference_image_path: string
        created_at: string
        thumbnail: string | null
      }>
    }>('/face/references')
  }

  async addFaceReferenceFromPath(filePath: string, personName: string) {
    return this.post<{
      success: boolean
      message: string
    }>('/face/references', { file_path: filePath, person_name: personName })
  }

  async deleteFaceReference(referenceId: number) {
    return this.delete<{
      success: boolean
      message: string
    }>(`/face/references/${referenceId}`)
  }

  async retrainFaceReferences(model: string) {
    // Use 3 minute timeout for retrain operation (can take 1-2 minutes for many references)
    return this.post<{
      success: boolean
      message: string
      total: number
      updated: number
      failed: number
      errors: string[]
    }>('/face/retrain-references', { model }, { timeout: 180000 })
  }

  async getRetrainProgress() {
    return this.get<{
      in_progress: boolean
      current: number
      total: number
      person_name: string
    }>('/face/retrain-progress')
  }

  async getFaceDashboardStats() {
    return this.get<{
      total_scans: number
      total_matches: number
      total_no_matches: number
      match_rate: number
      by_person: Array<{
        person: string
        count: number
        avg_confidence: number
        min_confidence: number
        max_confidence: number
      }>
      confidence_distribution: Record<string, number>
      recent_matches: Array<{
        file_path: string
        person: string
        confidence: number
        scan_date: string
        source: string | null
      }>
      scans_over_time: Array<{
        date: string
        total_scans: number
        matches: number
        match_rate: number
      }>
    }>('/face/dashboard-stats')
  }

  async getFaceImmichStatus() {
    return this.get<{
      enabled: boolean
      connected: boolean
      message?: string
    }>('/face/immich/status')
  }

  async getFaceImmichStatistics() {
    return this.get<{
      total_assets: number
      total_faces: number
      total_people: number
    }>('/face/immich/statistics')
  }

  async updateFaceImmichSettings(settings: { enabled: boolean }) {
    return this.put<{ success: boolean; message: string }>('/face/immich/settings', settings)
  }

  async getDashboardStats() {
    return this.get<{
      storage_by_platform: Array<{
        platform: string
        count: number
        size_bytes: number
        size_mb: number
      }>
      downloads_per_day: Array<{
        date: string
        count: number
      }>
      content_types: Record<string, number>
      top_sources: Array<{
        source: string
        platform: string
        count: number
      }>
      totals: {
        total_downloads: number
        total_size_bytes: number
        total_size_gb: number
        unique_sources: number
        platforms_used: number
      }
      growth_rate: number
    }>('/stats/dashboard')
  }

  async getFlareSolverrHealth() {
    return this.get<{
      status: 'healthy' | 'unhealthy' | 'offline' | 'timeout' | 'error'
      url: string
      response_time_ms?: number
      last_check: number
      sessions?: string[]
      error?: string
    }>('/health/flaresolverr')
  }

  async triggerImmichScan() {
    return this.post<{
      success: boolean
      message: string
    }>('/immich/scan', {})
  }

  // Monitoring APIs
  async getMonitoringStatus(hours: number = 24) {
    return this.get<{
      success: boolean
      downloaders: Array<{
        downloader: string
        total_attempts: number
        successful: number
        failed: number
        total_files: number
        success_rate: number
        last_success: string | null
        last_attempt: string
      }>
      window_hours: number
    }>(`/monitoring/status?hours=${hours}`)
  }

  async getMonitoringHistory(downloader: string | null = null, limit: number = 100) {
    const params = new URLSearchParams({ limit: limit.toString() })
    if (downloader) {
      params.append('downloader', downloader)
    }
    return this.get<{
      success: boolean
      history: Array<{
        id: number
        downloader: string
        username: string
        timestamp: string
        success: boolean
        file_count: number
        error_message: string | null
        alert_sent: boolean
      }>
    }>(`/monitoring/history?${params.toString()}`)
  }

  getReviewThumbnailUrl(filePath: string, fileToken?: string): string {
    // Determine media type from file extension
    const isVideo = filePath.match(/\.(mp4|mov|webm|avi|mkv|flv|m4v)$/i)
    const mediaType = isVideo ? 'video' : 'image'
    if (fileToken) {
      return `${API_BASE}/media/thumbnail?t=${encodeURIComponent(fileToken)}&media_type=${mediaType}`
    }
    return `${API_BASE}/media/thumbnail?file_path=${encodeURIComponent(filePath)}&media_type=${mediaType}`
  }

  getReviewPreviewUrl(filePath: string, fileToken?: string): string {
    if (fileToken) {
      return `${API_BASE}/review/file?t=${encodeURIComponent(fileToken)}`
    }
    return `${API_BASE}/review/file?file_path=${encodeURIComponent(filePath)}`
  }

  getMediaEmbeddedMetadataUrl(filePath: string, fileToken?: string): string {
    if (fileToken) {
      return `${API_BASE}/media/embedded-metadata?t=${encodeURIComponent(fileToken)}`
    }
    return `${API_BASE}/media/embedded-metadata?file_path=${encodeURIComponent(filePath)}`
  }

  getPaidContentServeUrl(localPath: string, fileToken?: string): string {
    if (fileToken) {
      return `${API_BASE}/paid-content/files/serve?t=${encodeURIComponent(fileToken)}`
    }
    return `${API_BASE}/paid-content/files/serve?path=${encodeURIComponent(localPath)}`
  }

  getPaidContentThumbnailUrl(filePath: string, fileToken?: string): string {
    if (fileToken) {
      return `${API_BASE}/paid-content/thumbnail?t=${encodeURIComponent(fileToken)}`
    }
    return `${API_BASE}/paid-content/thumbnail?file_path=${encodeURIComponent(filePath)}`
  }

  getPaidContentPreviewUrl(filePath: string, fileToken?: string): string {
    if (fileToken) {
      return `${API_BASE}/paid-content/preview?t=${encodeURIComponent(fileToken)}`
    }
    return `${API_BASE}/paid-content/preview?file_path=${encodeURIComponent(filePath)}`
  }

  // ============================================================================
  // Smart Folders Methods
  // ============================================================================

  async getSmartFolderStats() {
    return this.get<{
      stats: Record<number, {
        count: number
        previews: Array<{
          file_path: string
          file_token?: string
          content_type: string
        }>
      }>
    }>('/smart-folders/stats')
  }

  // ============================================================================
  // Discovery Methods
  // ============================================================================

  async getRecentActivity(limit: number = 10) {
    return this.get<{
      recent_downloads: Array<{
        id: number
        file_path: string
        file_token?: string
        filename: string
        platform: string
        source: string
        content_type: string
        file_size: number
        timestamp: string
        action: string
      }>
      recent_deleted: Array<{
        id: number
        file_path: string
        file_token?: string
        filename: string
        platform: string
        source: string
        content_type: string
        file_size: number
        timestamp: string
        deleted_from: string
        action: string
      }>
      recent_moved_to_review: Array<{
        id: number
        file_path: string
        file_token?: string
        filename: string
        platform: string
        source: string
        content_type: string
        file_size: number
        timestamp: string
        action: string
      }>
      summary: {
        downloads_24h: number
        downloads_7d: number
        deleted_24h: number
        deleted_7d: number
      }
    }>(`/discovery/recent-activity?limit=${limit}`)
  }

  // ============================================================================
  // 2FA Methods
  // ============================================================================

  // TOTP Methods
  async setupTOTP() {
    return this.post('/auth/2fa/totp/setup')
  }

  async verifyTOTP(code: string) {
    return this.post('/auth/2fa/totp/verify', { code })
  }

  async disableTOTP(password: string, code: string) {
    return this.post('/auth/2fa/totp/disable', { password, code })
  }

  async verifyTOTPLogin(username: string, code: string, useBackupCode: boolean = false, rememberMe: boolean = false) {
    return this.post('/auth/2fa/totp/login/verify', { username, code, useBackupCode, rememberMe })
  }

  // Video download management
  async deleteVideoDownload(platform: string, videoId: string) {
    return this.delete<{
      success: boolean
      deleted_files: number
      message: string
    }>(`/video/download/${encodeURIComponent(platform)}/${encodeURIComponent(videoId)}`)
  }

  async getGalleryFiles(platform: string, videoId: string) {
    return this.get<{
      files: Array<{
        file_path: string
        filename: string
        file_size: number
        media_type: 'image' | 'video'
      }>
      is_gallery: boolean
      video_id: string
      platform: string
    }>(`/video/gallery-files/${encodeURIComponent(platform)}/${encodeURIComponent(videoId)}`)
  }

  // ============================================================================
  // Video Streaming Methods
  // ============================================================================

  // Passkey Methods
  async getPasskeyRegistrationOptions() {
    return this.post('/auth/2fa/passkey/registration-options')
  }

  async getPasskeyAuthenticationOptions(username?: string) {
    return this.post('/auth/2fa/passkey/authentication-options', { username })
  }

  // Duo Methods
  async createDuoAuth(username: string, rememberMe: boolean = false) {
    return this.post('/auth/2fa/duo/auth', { username, rememberMe })
  }

  async enrollDuo(duo_username?: string) {
    return this.post('/auth/2fa/duo/enroll', { duo_username })
  }

  async unenrollDuo() {
    return this.post('/auth/2fa/duo/unenroll')
  }

  // Combined 2FA Status
  async get2FAStatus() {
    return this.get('/auth/2fa/status')
  }

  // Manual Import Methods
  async getManualImportServices() {
    return this.get('/manual-import/services')
  }

  async uploadFilesForImport(files: File[], serviceName: string): Promise<{
    session_id: string
    files: Array<{
      filename: string
      temp_path: string
      size: number
      parsed: {
        valid: boolean
        username: string | null
        datetime: string | null
        media_id: string | null
        error: string | null
      }
    }>
  }> {
    const formData = new FormData()
    files.forEach(file => formData.append('files', file))
    formData.append('service_name', serviceName)

    const token = localStorage.getItem('auth_token')
    const csrfToken = this.getCsrfToken()
    const response = await fetch(`${API_BASE}/manual-import/upload`, {
      method: 'POST',
      credentials: 'include',
      headers: {
        ...(token ? { 'Authorization': `Bearer ${token}` } : {}),
        ...(csrfToken ? { 'X-CSRFToken': csrfToken } : {})
      },
      body: formData
    })

    if (!response.ok) {
      const error = await response.json().catch(() => ({ detail: 'Upload failed' }))
      throw new Error(error.detail || 'Upload failed')
    }

    return response.json()
  }

  async processImportedFiles(files: { temp_path: string; filename: string; manual_datetime?: string; manual_username?: string }[], serviceName: string): Promise<{
    job_id: string
    status: string
    total_files: number
    message: string
  }> {
    return this.post('/manual-import/process', { files, service_name: serviceName })
  }

  async getImportJobStatus(jobId: string): Promise<{
    id: string
    status: 'processing' | 'completed'
    service_name: string
    total_files: number
    processed_files: number
    success_count: number
    failed_count: number
    results: Array<{ filename: string; status: string; error?: string; destination?: string }>
    current_file: string | null
    started_at: string
    completed_at: string | null
  }> {
    return this.get(`/manual-import/status/${jobId}`)
  }

  async clearImportTemp() {
    return this.delete('/manual-import/temp')
  }

  // ============================================================================
  // SEMANTIC SEARCH (CLIP)
  // ============================================================================

  async getSemanticStats() {
    return this.get<{
      total_embeddings: number
      total_files: number
      missing_embeddings: number
      coverage_percent: number
    }>('/semantic/stats')
  }

  async getSemanticIndexingStatus() {
    return this.get<{
      indexing_running: boolean
      elapsed_seconds: number | null
    }>('/semantic/status')
  }

  async semanticSearch(params: {
    query: string
    limit?: number
    platform?: string
    source?: string
    threshold?: number
  }) {
    return this.post<{
      results: Array<{
        id: number
        file_path: string
        file_token?: string
        filename: string
        platform: string
        source: string
        content_type: string
        file_size: number
        similarity: number
      }>
      count: number
      query: string
    }>('/semantic/search', params)
  }

  async generateEmbeddings(params?: {
    limit?: number
    platform?: string
  }) {
    return this.post<{
      message: string
      status: string
    }>('/semantic/generate', params || {})
  }

  // ============================================================================
  // SCRAPER CONFIGURATION
  // ============================================================================

  async getActiveScraperSessions() {
    return this.get<{
      sessions: Array<any>
      count: number
    }>('/scraper-sessions/active')
  }

  async getScrapers(typeFilter?: string) {
    const query = typeFilter ? `?type=${encodeURIComponent(typeFilter)}` : ''
    return this.get<{
      scrapers: Array<{
        id: string
        name: string
        type: string
        module: string | null
        base_url: string | null
        target_platform: string | null
        enabled: boolean
        proxy_enabled: boolean
        proxy_url: string | null
        flaresolverr_required: boolean
        cookies_json: string | null
        cookies_updated_at: string | null
        last_test_at: string | null
        last_test_status: string | null
        last_test_message: string | null
        settings_json: string | null
        created_at: string
        updated_at: string
      }>
    }>(`/scrapers${query}`)
  }

  async getScraper(scraperId: string) {
    return this.get<{
      id: string
      name: string
      type: string
      module: string | null
      base_url: string | null
      target_platform: string | null
      enabled: boolean
      proxy_enabled: boolean
      proxy_url: string | null
      flaresolverr_required: boolean
      cookies_json: string | null
      cookies_updated_at: string | null
      last_test_at: string | null
      last_test_status: string | null
      last_test_message: string | null
      settings_json: string | null
      created_at: string
      updated_at: string
    }>(`/scrapers/${encodeURIComponent(scraperId)}`)
  }

  async updateScraper(scraperId: string, updates: {
    enabled?: boolean
    proxy_enabled?: boolean
    proxy_url?: string | null
    base_url?: string | null
    settings?: Record<string, unknown>
  }) {
    return this.put<{ success: boolean; scraper: Record<string, unknown> }>(`/scrapers/${encodeURIComponent(scraperId)}`, updates)
  }

  async testScraper(scraperId: string) {
    return this.post<{
      success: boolean
      status: string
      message: string
      response_time_ms?: number
    }>(`/scrapers/${encodeURIComponent(scraperId)}/test`, {}, { timeout: 120000 })
  }

  async uploadScraperCookies(scraperId: string, cookies: Record<string, unknown>[], userAgent?: string) {
    return this.post<{
      success: boolean
      message: string
      cookies_count: number
    }>(`/scrapers/${encodeURIComponent(scraperId)}/cookies`, { cookies, user_agent: userAgent })
  }

  async clearScraperCookies(scraperId: string) {
    return this.delete<{ success: boolean; message: string }>(`/scrapers/${encodeURIComponent(scraperId)}/cookies`)
  }

  // ============================================================================
  // Error Monitoring Methods
  // ============================================================================

  async getRecentErrors(limit: number = 50, includeDismissed: boolean = false, sinceVisit: boolean = false) {
    return this.get<{
      errors: Array<{
        id: number
        error_hash: string
        module: string
        message: string
        first_seen: string
        last_seen: string
        occurrence_count: number
        log_file: string | null
        line_context: string | null
        dismissed_at: string | null
        viewed_at: string | null
      }>
      since_last_visit: number
      total_unviewed: number
    }>(`/errors/recent?limit=${limit}&include_dismissed=${includeDismissed}&since_visit=${sinceVisit}`)
  }

  async getErrorCount() {
    return this.get<{
      unviewed_count: number
      total_recent: number
      since_last_visit: number
    }>('/errors/count')
  }

  async dismissErrors(errorIds: number[]) {
    return this.post<{
      dismissed_count: number
      message: string
    }>('/errors/dismiss', { error_ids: errorIds })
  }

  async markErrorsViewed() {
    return this.post<{
      message: string
    }>('/errors/mark-viewed', { mark_all: true })
  }

  async updateDashboardVisit() {
    return this.post<{
      message: string
    }>('/errors/update-visit', {})
  }

  // ============================================================================
  // Cookie Health Monitoring
  // ============================================================================

  async getCookieHealth() {
    return this.get<{
      services: Array<{
        id: string
        name: string
        type: 'scraper' | 'paid_content'
        status: 'healthy' | 'expired' | 'missing' | 'down' | 'degraded' | 'unknown' | 'failed'
        last_updated?: string
        last_checked?: string
        message: string
      }>
      has_issues: boolean
      issue_count: number
      checked_at: string
    }>('/health/cookies')
  }

  // ============================================================================
  // PLATFORM CREDENTIALS (UNIFIED COOKIE MANAGEMENT)
  // ============================================================================

  async getPlatformCredentials() {
    return this.get<{
      platforms: PlatformCredential[]
      global_monitoring_enabled: boolean
    }>('/scrapers/platform-credentials')
  }

  async togglePlatformMonitoring(platformId: string, enabled: boolean) {
    return this.put<{ success: boolean; message: string }>(
      `/scrapers/platform-credentials/${encodeURIComponent(platformId)}/monitoring`,
      { enabled }
    )
  }

  async toggleGlobalMonitoring(enabled: boolean) {
    return this.put<{ success: boolean; message: string }>(
      '/scrapers/platform-credentials/monitoring',
      { enabled }
    )
  }

  // ============================================================================
  // VIDEO QUEUE SETTINGS
  // ============================================================================

  async getVideoQueueSettings() {
    return this.get<{
      success: boolean
      settings: {
        download_delay_seconds: number
        base_directory: string
      }
    }>('/video-queue/settings')
  }

  async updateVideoQueueSettings(settings: { download_delay_seconds?: number; base_directory?: string }) {
    return this.post<{
      success: boolean
      settings: {
        download_delay_seconds: number
        base_directory: string
      }
    }>('/video-queue/settings', settings)
  }

  // Anti-bot Settings
  // ============================================================================

  async getAntibotSettings() {
    return this.get<{
      success: boolean
      settings: {
        browser: string
        custom_user_agent: string
        limit_rate: string
        throttled_rate: string
        sleep_requests_min: number
        sleep_requests_max: number
        retries: number
        fragment_retries: number
        concurrent_fragments: number
        socket_timeout: number
        enabled: boolean
      }
    }>('/video-queue/antibot-settings')
  }

  async updateAntibotSettings(settings: {
    browser?: string
    custom_user_agent?: string
    limit_rate?: string
    throttled_rate?: string
    sleep_requests_min?: number
    sleep_requests_max?: number
    retries?: number
    fragment_retries?: number
    concurrent_fragments?: number
    socket_timeout?: number
    enabled?: boolean
  }) {
    return this.post<{
      success: boolean
      settings: {
        browser: string
        custom_user_agent: string
        limit_rate: string
        throttled_rate: string
        sleep_requests_min: number
        sleep_requests_max: number
        retries: number
        fragment_retries: number
        concurrent_fragments: number
        socket_timeout: number
        enabled: boolean
      }
    }>('/video-queue/antibot-settings', settings)
  }

  // ============================================================================
  // Celebrity Appearances Methods
  // ============================================================================

  async getUpcomingAppearances(params?: { limit?: number; days_ahead?: number }) {
    const query = new URLSearchParams()
    if (params?.limit) query.set('limit', params.limit.toString())
    if (params?.days_ahead) query.set('days_ahead', params.days_ahead.toString())

    return this.get<Array<{
      id: number
      celebrity_id: number
      celebrity_name: string
      appearance_type: 'TV' | 'Podcast' | 'Radio' | 'Movie'
      show_name: string
      episode_title: string | null
      network: string | null
      appearance_date: string
      url: string | null
      audio_url: string | null
      watch_url: string | null
      description: string | null
      tmdb_show_id: number | null
      season_number: number | null
      episode_number: number | null
      status: string
      poster_url: string | null
      episode_count: number
      credit_type: 'acting' | 'host' | 'directing' | 'producing' | 'writing' | 'creator' | 'guest' | null
      character_name: string | null
      job_title: string | null
      plex_rating_key: string | null
      plex_watch_url: string | null
      all_credit_types?: string[]
    }>>(`/appearances/upcoming?${query}`)
  }

  async getAiredAppearances(params?: { limit?: number; days_back?: number }) {
    const query = new URLSearchParams()
    if (params?.limit) query.set('limit', params.limit.toString())
    if (params?.days_back) query.set('days_back', params.days_back.toString())

    return this.get<Array<{
      id: number
      celebrity_id: number
      celebrity_name: string
      appearance_type: 'TV' | 'Podcast' | 'Radio' | 'Movie'
      show_name: string
      episode_title: string | null
      network: string | null
      appearance_date: string
      url: string | null
      audio_url: string | null
      watch_url: string | null
      description: string | null
      tmdb_show_id: number | null
      season_number: number | null
      episode_number: number | null
      status: string
      poster_url: string | null
      episode_count: number
      credit_type: 'acting' | 'host' | 'directing' | 'producing' | 'writing' | 'creator' | 'guest' | null
      character_name: string | null
      job_title: string | null
      plex_rating_key: string | null
      plex_watch_url: string | null
      all_credit_types?: string[]
    }>>(`/appearances/aired?${query}`)
  }

  async getAllAppearances(params?: {
    status?: string
    appearance_type?: string
    celebrity_name?: string
    credit_type?: string
    search?: string
    date_filter?: string
    start_date?: string
    end_date?: string
    plex_only?: boolean
  }) {
    const query = new URLSearchParams()
    if (params?.status && params.status !== 'all') query.set('status', params.status)
    if (params?.appearance_type && params.appearance_type !== 'all') query.set('appearance_type', params.appearance_type)
    if (params?.celebrity_name && params.celebrity_name !== 'all') query.set('celebrity_name', params.celebrity_name)
    if (params?.credit_type && params.credit_type !== 'all') query.set('credit_type', params.credit_type)
    if (params?.search) query.set('search', params.search)
    if (params?.date_filter && params.date_filter !== 'all') query.set('date_filter', params.date_filter)
    if (params?.start_date) query.set('start_date', params.start_date)
    if (params?.end_date) query.set('end_date', params.end_date)
    if (params?.plex_only) query.set('plex_only', 'true')

    return this.get<Array<{
      id: number
      celebrity_id: number
      celebrity_name: string
      appearance_type: 'TV' | 'Podcast' | 'Radio' | 'Movie'
      show_name: string
      episode_title: string | null
      network: string | null
      appearance_date: string
      url: string | null
      audio_url: string | null
      watch_url: string | null
      description: string | null
      tmdb_show_id: number | null
      season_number: number | null
      episode_number: number | null
      status: string
      created_at: string
      updated_at: string
      poster_url: string | null
      episode_count: number
      credit_type: 'acting' | 'host' | 'directing' | 'producing' | 'writing' | 'creator' | 'guest' | null
      character_name: string | null
      job_title: string | null
      plex_rating_key: string | null
      plex_watch_url: string | null
      all_credit_types?: string[]
    }>>(`/appearances/all?${query}`)
  }

  async syncAppearances(celebrityIds?: number[]) {
    return this.post<{
      success: boolean
      message: string
    }>('/appearances/sync', { celebrity_ids: celebrityIds || null })
  }

  async getAppearanceSyncStatus() {
    return this.get<{
      is_running: boolean
      current_celebrity: string | null
      celebrities_processed: number
      total_celebrities: number
      new_appearances: number
      started_at: string | null
      last_update: string | null
      phase: 'fetching_movies' | 'fetching_tv' | 'fetching_podcasts' | 'fetching' | 'saving' | 'complete' | null
      current_show: string | null
      shows_processed: number
      total_shows: number
    }>('/appearances/sync/status')
  }

  async getAppearanceConfig() {
    return this.get<{
      tmdb_enabled: boolean
      tmdb_check_interval_hours: number
      notify_new_appearances: boolean
      notify_days_before: number
      sync_past_movies: boolean
    }>('/appearances/config')
  }

  async updateAppearanceConfig(config: {
    tmdb_enabled?: boolean
    tmdb_check_interval_hours?: number
    notify_new_appearances?: boolean
    notify_days_before?: number
    sync_past_movies?: boolean
    notify_include_poster?: boolean
    notify_plex_matches?: boolean
    taddy_user_id?: string
    taddy_api_key?: string
    taddy_user_id_2?: string
    taddy_api_key_2?: string
    taddy_enabled?: boolean
    taddy_lookback_days?: number
    taddy_max_results?: number
  }) {
    return this.put<{
      success: boolean
      message: string
    }>('/appearances/config', config)
  }

  async updateAppearanceStatus(appearanceId: number, status: string) {
    return this.put<{
      success: boolean
      status: string
    }>(`/appearances/${appearanceId}/status`, { status })
  }

  async getShowEpisodes(celebrityId: number, showName: string, appearanceType: string = 'Podcast') {
    const query = new URLSearchParams()
    query.set('celebrity_id', celebrityId.toString())
    query.set('show_name', showName)
    query.set('appearance_type', appearanceType)

    return this.get<Array<{
      id: number
      episode_title: string | null
      appearance_date: string
      season_number: number | null
      episode_number: number | null
      url: string | null
      audio_url: string | null
      watch_url: string | null
      description: string | null
      status: string
      role_type: string | null
      credit_type: 'acting' | 'host' | 'directing' | 'producing' | 'writing' | 'creator' | 'guest' | null
      character_name: string | null
      job_title: string | null
      plex_rating_key: string | null
      plex_watch_url: string | null
      all_credit_types?: string[]
      all_roles?: { credit_type: string; character_name: string | null; job_title: string | null }[]
    }>>(`/appearances/show/episodes?${query}`)
  }

  // Plex integration methods
  async configurePlex(config: { plex_url: string; plex_token: string }) {
    return this.post<{ success: boolean; message: string }>('/appearances/plex/configure', config)
  }

  async testPlexConnection() {
    return this.get<{ success: boolean; server_name?: string; message?: string }>('/appearances/plex/test')
  }

  async getPlexLibraries() {
    return this.get<Array<{
      id: string
      title: string
      type: string
      uuid: string
    }>>('/appearances/plex/libraries')
  }

  async matchAppearancesToPlex() {
    return this.post<{
      success: boolean
      message: string
    }>('/appearances/plex/match', {})
  }

  async getPlexStats() {
    return this.get<{
      total: number
      matched: number
      unmatched: number
      percentage: number
      breakdown: {
        Movie?: { total: number; matched: number }
        TV?: { total: number; matched: number }
      }
    }>('/appearances/plex/stats')
  }

  // Plex OAuth methods
  async startPlexAuth() {
    return this.post<{
      success: boolean
      pin_id: number
      auth_url: string
      expires_at: string
      message: string
    }>('/appearances/plex/auth/start', {})
  }

  async checkPlexAuth(pinId: number) {
    return this.get<{
      success: boolean
      authenticated: boolean
      message: string
      token?: string
      user?: {
        username: string
        email: string
        thumb: string
        title: string
      }
      servers?: Array<{
        name: string
        machineIdentifier: string
        owned: boolean
        url: string
        local: boolean
        accessToken: string
      }>
    }>(`/appearances/plex/auth/check/${pinId}`)
  }

  async savePlexAuth(config: {
    server_url: string
    server_token: string
    server_name: string
    machine_identifier: string
  }) {
    return this.post<{
      success: boolean
      message: string
      server_name?: string
    }>('/appearances/plex/auth/save', config)
  }

  async getAppearancesTVGrouped(params?: {
    celebrity_id?: number
    credit_type?: string
    limit?: number
  }) {
    const query = new URLSearchParams()
    if (params?.celebrity_id) query.set('celebrity_id', params.celebrity_id.toString())
    if (params?.credit_type && params.credit_type !== 'all') query.set('credit_type', params.credit_type)
    if (params?.limit) query.set('limit', params.limit.toString())

    return this.get<Array<{
      celebrity_id: number
      celebrity_name: string
      show_name: string
      tmdb_show_id: number | null
      poster_url: string | null
      network: string | null
      episode_count: number
      first_appearance: string
      last_appearance: string
      credit_types: string[]
      plex_watch_url: string | null
    }>>(`/appearances/tv/grouped?${query}`)
  }

  // Appearance notification methods
  async getAppearanceNotifications(params?: {
    limit?: number
    offset?: number
    notification_type?: string
  }) {
    const query = new URLSearchParams()
    if (params?.limit) query.set('limit', params.limit.toString())
    if (params?.offset) query.set('offset', params.offset.toString())
    if (params?.notification_type) query.set('notification_type', params.notification_type)

    return this.get<{
      success: boolean
      notifications: Array<{
        id: number
        appearance_id: number | null
        celebrity_name: string
        show_name: string
        appearance_type: string
        appearance_date: string
        notification_type: string
        message: string
        poster_url: string | null
        plex_watch_url: string | null
        sent_at: string
      }>
      total: number
      limit: number
      offset: number
    }>(`/appearances/notifications?${query}`)
  }

  async getAppearanceNotificationStats() {
    return this.get<{
      success: boolean
      stats: {
        total: number
        today: number
        by_type: Record<string, number>
      }
    }>('/appearances/notifications/stats')
  }

  async triggerAppearanceReminders() {
    return this.post<{
      success: boolean
      message: string
      sent: number
      total_upcoming?: number
    }>('/appearances/notifications/send-reminders', {})
  }

  // ============================================================================
  // Easynews Methods
  // ============================================================================

  async getEasynewsConfig() {
    return this.get<{
      success: boolean
      config: {
        username: string | null
        password: string | null
        enabled: boolean
        check_interval_hours: number
        last_check: string | null
        auto_download: boolean
        min_quality: string
        proxy_enabled: boolean
        proxy_type: string
        proxy_host: string | null
        proxy_port: number | null
        proxy_username: string | null
        proxy_password: string | null
        has_credentials: boolean
      }
    }>('/easynews/config')
  }

  async updateEasynewsConfig(config: {
    username?: string
    password?: string
    enabled?: boolean
    check_interval_hours?: number
    auto_download?: boolean
    min_quality?: string
    proxy_enabled?: boolean
    proxy_type?: string
    proxy_host?: string
    proxy_port?: number
    proxy_username?: string
    proxy_password?: string
  }) {
    return this.put<{ success: boolean; message: string }>('/easynews/config', config)
  }

  async testEasynewsConnection() {
    return this.post<{ success: boolean; message: string }>('/easynews/test', {})
  }

  async getEasynewsSearches() {
    return this.get<{
      success: boolean
      searches: Array<{
        id: number
        search_term: string
        media_type: string
        tmdb_id: number | null
        tmdb_title: string | null
        poster_url: string | null
        enabled: boolean
        created_at: string
        updated_at: string
      }>
      count: number
    }>('/easynews/searches')
  }

  async addEasynewsSearch(search: {
    search_term: string
    media_type?: string
    tmdb_id?: number
    tmdb_title?: string
    poster_url?: string
  }) {
    return this.post<{ success: boolean; id?: number; message: string }>('/easynews/searches', search)
  }

  async updateEasynewsSearch(searchId: number, updates: {
    search_term?: string
    media_type?: string
    enabled?: boolean
    tmdb_id?: number
    tmdb_title?: string
    poster_url?: string
  }) {
    return this.put<{ success: boolean; message: string }>(`/easynews/searches/${searchId}`, updates)
  }

  async deleteEasynewsSearch(searchId: number) {
    return this.delete<{ success: boolean; message: string }>(`/easynews/searches/${searchId}`)
  }

  async getEasynewsResults(params?: {
    status?: string
    search_id?: number
    limit?: number
    offset?: number
  }) {
    const query = new URLSearchParams()
    if (params?.status) query.set('status', params.status)
    if (params?.search_id) query.set('search_id', params.search_id.toString())
    if (params?.limit) query.set('limit', params.limit.toString())
    if (params?.offset) query.set('offset', params.offset.toString())

    return this.get<{
      success: boolean
      results: Array<{
        id: number
        search_id: number
        search_term: string
        filename: string
        download_url: string
        size_bytes: number
        post_date: string | null
        discovered_at: string
        parsed_title: string | null
        parsed_season: number | null
        parsed_episode: number | null
        parsed_year: number | null
        tmdb_id: number | null
        tmdb_title: string | null
        poster_url: string | null
        quality: string | null
        status: string
        download_path: string | null
      }>
      count: number
      total: number
    }>(`/easynews/results?${query}`)
  }

  async updateEasynewsResultStatus(resultId: number, status: string) {
    return this.post<{ success: boolean; message: string }>(`/easynews/results/${resultId}/status?status=${status}`, {})
  }

  async downloadEasynewsResult(resultId: number) {
    return this.post<{ success: boolean; path?: string; size?: number; message?: string }>(`/easynews/results/${resultId}/download`, {})
  }

  async getEasynewsStatus() {
    return this.get<{
      success: boolean
      status: {
        is_running: boolean
        current_search: string | null
        searches_processed: number
        total_searches: number
        results_found: number
        last_check: string | null
        error: string | null
      }
      last_check: string | null
      enabled: boolean
      has_credentials: boolean
      celebrity_count: number
    }>('/easynews/status')
  }

  async triggerEasynewsCheck() {
    return this.post<{ success: boolean; message: string }>('/easynews/check', {})
  }

  async triggerEasynewsSingleCheck(searchId: number) {
    return this.post<{ success: boolean; message: string }>(`/easynews/check/${searchId}`, {})
  }

  // ============================================================================
  // PRESS API METHODS
  // ============================================================================

  async getPressConfig() {
    return this.get<{ success: boolean; config: { enabled: boolean; check_interval_hours: number; max_records_per_query: number; notify_new_articles: boolean } }>('/press/config')
  }

  async updatePressConfig(config: { enabled?: boolean; check_interval_hours?: number; max_records_per_query?: number; notify_new_articles?: boolean }) {
    return this.put<{ success: boolean; message: string }>('/press/config', config)
  }

  async getPressArticles(params?: { celebrity_id?: number; domain?: string; read?: boolean; search?: string; page?: number; per_page?: number }) {
    const searchParams = new URLSearchParams()
    if (params?.celebrity_id) searchParams.set('celebrity_id', String(params.celebrity_id))
    if (params?.domain) searchParams.set('domain', params.domain)
    if (params?.read !== undefined) searchParams.set('read', String(params.read))
    if (params?.search) searchParams.set('search', params.search)
    if (params?.page) searchParams.set('page', String(params.page))
    if (params?.per_page) searchParams.set('per_page', String(params.per_page))
    const qs = searchParams.toString()
    return this.get<{ success: boolean; articles: unknown[]; total: number; page: number; pages: number }>(`/press/articles${qs ? '?' + qs : ''}`)
  }

  async getPressArticle(id: number) {
    return this.get<{ success: boolean; article: unknown }>(`/press/articles/${id}`)
  }

  async markPressArticleRead(id: number, read: boolean) {
    return this.patch<{ success: boolean; message: string }>(`/press/articles/${id}/read`, { read })
  }

  async deletePressArticle(id: number) {
    return this.delete<{ success: boolean; message: string }>(`/press/articles/${id}`)
  }

  async getPressStats() {
    return this.get<{ success: boolean; stats: { total: number; unread: number; by_celebrity: unknown[]; by_domain: unknown[] } }>('/press/stats')
  }

  async triggerPressFetch(celebrityId?: number) {
    const params = celebrityId ? `?celebrity_id=${celebrityId}` : ''
    return this.post<{ success: boolean; message: string }>(`/press/fetch${params}`, {})
  }

  async getPressFetchStatus() {
    return this.get<{ success: boolean; is_running: boolean }>('/press/fetch/status')
  }

  // ============================================================================
  // PAID CONTENT API METHODS
  // ============================================================================

  // Dashboard
  async getPaidContentDashboardStats() {
    return this.get<{ stats: PaidContentDashboardStats; timestamp: string }>('/paid-content/dashboard/stats')
  }

  async getPaidContentFailedDownloads() {
    return this.get<{ downloads: PaidContentAttachment[]; count: number }>('/paid-content/dashboard/failed-downloads')
  }

  async retryPaidContentDownloads(attachmentIds: number[]) {
    return this.post<{ status: string; count: number }>('/paid-content/dashboard/retry-failed', { attachment_ids: attachmentIds })
  }

  async getPaidContentActiveSyncs() {
    return this.get<{ syncs: Array<{
      creator_id: number
      username: string
      platform: string
      service: string
      status: string
      phase: 'fetching' | 'processing' | 'downloading' | 'backfilling'
      posts_fetched?: number
      total_files?: number
      downloaded?: number
      failed?: number
      started_at: string
      updated_at?: string
    }>; count: number }>('/paid-content/dashboard/active-syncs')
  }

  // Services
  async getPaidContentServices() {
    return this.get<{ services: PaidContentService[] }>('/paid-content/services')
  }

  async getPaidContentService(serviceId: string) {
    return this.get<PaidContentService>(`/paid-content/services/${serviceId}`)
  }

  async checkPaidContentServiceHealth(serviceId: string) {
    return this.get<{ status: string; response_time?: number; error?: string }>(`/paid-content/services/${serviceId}/health`)
  }

  async checkAllPaidContentServicesHealth() {
    return this.post<{ services: Record<string, { status: string; message?: string }>; checked_at: string }>('/paid-content/services/check-all-health', {})
  }

  async updatePaidContentServiceSession(serviceId: string, sessionCookie: string) {
    return this.post<{ success: boolean; message: string }>(`/paid-content/services/${serviceId}/session`, { session_cookie: sessionCookie })
  }

  async updatePaidContentServiceUrl(serviceId: string, baseUrl: string) {
    return this.put<{ success: boolean; message: string }>(`/paid-content/services/${serviceId}/url`, { base_url: baseUrl })
  }

  // Creators
  async getPaidContentCreators(params?: {
    service?: string
    platform?: string
    identity_id?: number
    search?: string
    enabled_only?: boolean
    limit?: number
    offset?: number
  }) {
    const query = new URLSearchParams()
    if (params?.service) query.append('service', params.service)
    if (params?.platform) query.append('platform', params.platform)
    if (params?.identity_id) query.append('identity_id', params.identity_id.toString())
    if (params?.search) query.append('search', params.search)
    if (params?.enabled_only) query.append('enabled_only', 'true')
    if (params?.limit) query.append('limit', params.limit.toString())
    if (params?.offset) query.append('offset', params.offset.toString())
    return this.get<{ creators: PaidContentCreator[]; count: number }>(`/paid-content/creators?${query}`)
  }

  async getPaidContentCreator(creatorId: number) {
    return this.get<PaidContentCreator>(`/paid-content/creators/${creatorId}`)
  }

  async addPaidContentCreator(data: {
    service_id: string
    platform: string
    creator_id: string
    auto_download?: boolean
    download_embeds?: boolean
  }) {
    return this.post<{ creator: PaidContentCreator; message: string }>('/paid-content/creators', data)
  }

  async updatePaidContentCreator(creatorId: number, data: {
    enabled?: boolean
    auto_download?: boolean
    download_embeds?: boolean
    identity_id?: number | null
    sync_posts?: boolean
    sync_stories?: boolean
    sync_highlights?: boolean
    use_authenticated_api?: boolean
    filter_tagged_users?: string[]
  }) {
    return this.put<{ success: boolean; message: string }>(`/paid-content/creators/${creatorId}`, data)
  }

  async getPaidContentCreatorTaggedUsers(creatorId: number) {
    return this.get<{username: string, post_count: number}[]>(`/paid-content/creators/${creatorId}/tagged-users`)
  }

  async deletePaidContentCreator(creatorId: number, deleteFiles: boolean = false) {
    return this.delete<{ success: boolean; message: string }>(`/paid-content/creators/${creatorId}?delete_files=${deleteFiles}`)
  }

  async syncPaidContentCreator(creatorId: number, download: boolean = true, forceBackfill: boolean = false) {
    return this.post<{ status: string; creator: string }>(`/paid-content/creators/${creatorId}/sync?download=${download}&force_backfill=${forceBackfill}`, {})
  }

  async syncAllPaidContentCreators() {
    return this.post<{ status: string }>('/paid-content/creators/sync-all', {})
  }

  async syncPaidContentService(serviceId: string) {
    return this.post<{ status: string; count: number; creators: string[] }>(`/paid-content/creators/sync-service/${serviceId}`, {})
  }

  async searchPaidContentCreators(serviceId: string, query: string, platform?: string) {
    const params = new URLSearchParams({ service_id: serviceId, query })
    if (platform) params.append('platform', platform)
    return this.get<{ results: Array<{ id: string; name: string; service: string; favorited: number }>; count: number }>(`/paid-content/creators/search?${params}`)
  }

  // Identities
  async getPaidContentIdentities() {
    return this.get<{ identities: PaidContentIdentity[] }>('/paid-content/identities')
  }

  async getPaidContentIdentity(identityId: number) {
    return this.get<PaidContentIdentity>(`/paid-content/identities/${identityId}`)
  }

  async createPaidContentIdentity(name: string, notes?: string) {
    return this.post<{ id: number; name: string; message: string }>('/paid-content/identities', { name, notes })
  }

  async updatePaidContentIdentity(identityId: number, data: { name?: string; notes?: string; profile_image_url?: string }) {
    return this.put<{ success: boolean; message: string }>(`/paid-content/identities/${identityId}`, data)
  }

  async deletePaidContentIdentity(identityId: number) {
    return this.delete<{ success: boolean; message: string }>(`/paid-content/identities/${identityId}`)
  }

  async linkPaidContentCreatorToIdentity(identityId: number, creatorId: number) {
    return this.post<{ success: boolean; message: string }>(`/paid-content/identities/${identityId}/link`, { creator_id: creatorId })
  }

  async unlinkPaidContentCreatorFromIdentity(identityId: number, creatorId: number) {
    return this.post<{ success: boolean; message: string }>(`/paid-content/identities/${identityId}/unlink/${creatorId}`, {})
  }

  // Creator Groups
  async getPaidContentCreatorGroups() {
    return this.get<{ groups: PaidContentCreatorGroup[] }>('/paid-content/creator-groups')
  }

  async getPaidContentCreatorGroup(groupId: number) {
    return this.get<PaidContentCreatorGroup>(`/paid-content/creator-groups/${groupId}`)
  }

  async createPaidContentCreatorGroup(data: { name: string; description?: string }) {
    return this.post<{ group: PaidContentCreatorGroup; message: string }>('/paid-content/creator-groups', data)
  }

  async updatePaidContentCreatorGroup(groupId: number, data: { name?: string; description?: string }) {
    return this.put<{ success: boolean; message: string }>(`/paid-content/creator-groups/${groupId}`, data)
  }

  async deletePaidContentCreatorGroup(groupId: number) {
    return this.delete<{ success: boolean; message: string }>(`/paid-content/creator-groups/${groupId}`)
  }

  async addCreatorToPaidContentGroup(groupId: number, creatorId: number) {
    return this.post<{ success: boolean; message: string }>(`/paid-content/creator-groups/${groupId}/members`, { creator_id: creatorId })
  }

  async removeCreatorFromPaidContentGroup(groupId: number, creatorId: number) {
    return this.delete<{ success: boolean; message: string }>(`/paid-content/creator-groups/${groupId}/members/${creatorId}`)
  }

  async updatePaidContentGroupMemberFilters(groupId: number, creatorId: number, data: { filter_tagged_users?: string[] | null; filter_tag_ids?: number[] | null }) {
    return this.put<{ success: boolean; message: string }>(`/paid-content/creator-groups/${groupId}/members/${creatorId}/filters`, data)
  }

  // Feed
  async getPaidContentFeed(params?: {
    creator_id?: number
    creator_ids?: number[]
    creator_group_id?: number
    identity_id?: number
    service?: string
    platform?: string
    content_type?: string
    tag_id?: number
    tagged_user?: string
    date_from?: string
    date_to?: string
    search?: string
    min_resolution?: string
    favorites_only?: boolean
    unviewed_only?: boolean
    downloaded_only?: boolean
    has_missing?: boolean
    missing_description?: boolean
    hide_empty?: boolean
    sort_by?: string
    sort_order?: 'asc' | 'desc'
    limit?: number
    offset?: number
    pinned_first?: boolean
    skip_pinned?: boolean
    shuffle?: boolean
    shuffle_seed?: number
  }) {
    const query = new URLSearchParams()
    if (params?.creator_id) query.append('creator_id', params.creator_id.toString())
    if (params?.creator_ids?.length) query.append('creator_ids', params.creator_ids.join(','))
    if (params?.creator_group_id) query.append('creator_group_id', params.creator_group_id.toString())
    if (params?.identity_id) query.append('identity_id', params.identity_id.toString())
    if (params?.service) query.append('service', params.service)
    if (params?.platform) query.append('platform', params.platform)
    if (params?.content_type) query.append('content_type', params.content_type)
    if (params?.tag_id) query.append('tag_ids', params.tag_id.toString())
    if (params?.tagged_user) query.append('tagged_user', params.tagged_user)
    if (params?.date_from) query.append('date_from', params.date_from)
    if (params?.date_to) query.append('date_to', params.date_to)
    if (params?.search) query.append('search', params.search)
    if (params?.min_resolution) query.append('min_resolution', params.min_resolution)
    if (params?.favorites_only) query.append('favorites_only', 'true')
    if (params?.unviewed_only) query.append('unviewed_only', 'true')
    if (params?.downloaded_only) query.append('downloaded_only', 'true')
    if (params?.has_missing) query.append('has_missing', 'true')
    if (params?.missing_description) query.append('missing_description', 'true')
    if (params?.hide_empty === false) query.append('hide_empty', 'false')
    if (params?.sort_by) query.append('sort_by', params.sort_by)
    if (params?.sort_order) query.append('sort_order', params.sort_order)
    if (params?.limit) query.append('limit', params.limit.toString())
    if (params?.offset) query.append('offset', params.offset.toString())
    if (params?.pinned_first === false) query.append('pinned_first', 'false')
    if (params?.skip_pinned) query.append('skip_pinned', 'true')
    if (params?.shuffle) query.append('shuffle', 'true')
    if (params?.shuffle_seed !== undefined) query.append('shuffle_seed', params.shuffle_seed.toString())
    return this.get<{ posts: PaidContentPost[]; count: number; total: number; total_media?: number; limit: number; offset: number }>(`/paid-content/feed?${query}`)
  }

  async getPaidContentPost(postId: number) {
    return this.get<PaidContentPost>(`/paid-content/feed/${postId}`)
  }

  async togglePaidContentPostFavorite(postId: number) {
    return this.post<{ is_favorited: boolean }>(`/paid-content/feed/${postId}/favorite`, {})
  }

  async togglePaidContentPostViewed(postId: number) {
    return this.post<{ is_viewed: boolean }>(`/paid-content/feed/${postId}/viewed`, {})
  }

  async downloadPaidContentPost(postId: number) {
    return this.post<{ status: string; post_id: number }>(`/paid-content/feed/${postId}/download`, {})
  }

  async getPaidContentUnviewedCount() {
    return this.get<{ count: number }>('/paid-content/unviewed-count')
  }

  async markAllPaidContentViewed() {
    return this.post<{ updated: number }>('/paid-content/mark-all-viewed', {})
  }

  async getPaidContentUnreadMessagesCount() {
    return this.get<{ count: number }>('/paid-content/messages/unread-count')
  }

  async markAllPaidContentMessagesRead() {
    return this.post<{ updated: number }>('/paid-content/messages/mark-all-read', {})
  }

  // Notifications
  async getPaidContentNotifications(params?: { unread_only?: boolean; limit?: number; offset?: number }) {
    const query = new URLSearchParams()
    if (params?.unread_only) query.append('unread_only', 'true')
    if (params?.limit) query.append('limit', params.limit.toString())
    if (params?.offset) query.append('offset', params.offset.toString())
    return this.get<{ notifications: PaidContentNotification[]; count: number; unread_count: number }>(`/paid-content/notifications?${query}`)
  }

  async markPaidContentNotificationsRead(notificationIds?: number[], markAll?: boolean) {
    return this.post<{ marked_count: number }>('/paid-content/notifications/mark-read', { notification_ids: notificationIds, mark_all: markAll })
  }

  // Settings
  async getPaidContentSettings() {
    return this.get<PaidContentSettings>('/paid-content/settings')
  }

  async updatePaidContentSettings(settings: Partial<PaidContentSettings>) {
    return this.put<{ success: boolean; message: string }>('/paid-content/settings', settings)
  }

  // Import
  async importPaidContentFromUrl(url: string, creatorId?: number) {
    return this.post<{ status: string; url: string }>('/paid-content/import/url', { url, creator_id: creatorId })
  }

  // Recycle Bin
  async getPaidContentRecycleBin(params?: {
    item_type?: string
    search?: string
    platform?: string
    creator?: string
    content_type?: string
    limit?: number
    offset?: number
  }) {
    const query = new URLSearchParams()
    if (params?.item_type) query.append('item_type', params.item_type)
    if (params?.search) query.append('search', params.search)
    if (params?.platform) query.append('platform', params.platform)
    if (params?.creator) query.append('creator', params.creator)
    if (params?.content_type) query.append('content_type', params.content_type)
    if (params?.limit) query.append('limit', params.limit.toString())
    if (params?.offset) query.append('offset', params.offset.toString())
    return this.get<{ items: PaidContentRecycleBinItem[]; count: number }>(`/paid-content/recycle-bin?${query}`)
  }

  async restorePaidContentFromRecycleBin(itemId: number) {
    return this.post<{ success: boolean; message: string }>(`/paid-content/recycle-bin/${itemId}/restore`, {})
  }

  async emptyPaidContentRecycleBin(itemType?: string) {
    const query = itemType ? `?item_type=${itemType}` : ''
    return this.delete<{ deleted_count: number }>(`/paid-content/recycle-bin${query}`)
  }

  async permanentlyDeletePaidContent(itemIds: number[]) {
    return this.post<{ deleted_count: number }>('/paid-content/recycle-bin/delete-permanently', { item_ids: itemIds })
  }

  async restorePaidContentBatch(itemIds: number[]) {
    return this.post<{ restored_count: number }>('/paid-content/recycle-bin/restore-batch', { item_ids: itemIds })
  }

  async addPaidContentCreatorByUrl(url: string, autoDownload: boolean = true, downloadEmbeds: boolean = true) {
    return this.post<{ creator: PaidContentCreator; message: string }>('/paid-content/creators/by-url', {
      url,
      auto_download: autoDownload,
      download_embeds: downloadEmbeds
    })
  }

  // Queue
  async getPaidContentQueue(status?: string, creatorId?: number) {
    const query = new URLSearchParams()
    if (status) query.append('status', status)
    if (creatorId) query.append('creator_id', creatorId.toString())
    const queryStr = query.toString()
    return this.get<{
      pending: PaidContentAttachment[]
      downloading: PaidContentAttachment[]
      failed: PaidContentAttachment[]
      counts: { pending: number; downloading: number; failed: number }
    }>(`/paid-content/queue${queryStr ? '?' + queryStr : ''}`)
  }

  async removeFromPaidContentQueue(id: number) {
    return this.delete<{ status: string; id: number }>(`/paid-content/queue/${id}`)
  }

  async removeMultipleFromPaidContentQueue(ids: number[]) {
    return this.post<{ status: string; count: number }>('/paid-content/queue/remove', { attachment_ids: ids })
  }

  async updatePaidContentQueueItem(id: number, autoRequeue: boolean) {
    return this.put<{ status: string; id: number }>(`/paid-content/queue/${id}`, { auto_requeue: autoRequeue })
  }

  async stopAllPaidContentDownloads() {
    return this.post<{ status: string; syncs_cancelled: number; downloads_paused: number }>('/paid-content/queue/stop-all', {})
  }

  async clearFailedPaidContentDownloads() {
    return this.post<{ status: string; count: number }>('/paid-content/queue/clear-failed', {})
  }

  // Namespaced API for Paid Content feature
  paidContent = {
    // Dashboard
    getDashboardStats: () => this.getPaidContentDashboardStats().then(r => r.stats),
    getFailedDownloads: () => this.getPaidContentFailedDownloads().then(r => r.downloads),
    retryFailedDownloads: (ids: number[]) => this.retryPaidContentDownloads(ids),
    getActiveSyncs: () => this.getPaidContentActiveSyncs().then(r => r.syncs),

    // Queue
    getQueue: (status?: string, creatorId?: number) => this.getPaidContentQueue(status, creatorId),
    removeFromQueue: (id: number) => this.removeFromPaidContentQueue(id),
    removeMultipleFromQueue: (ids: number[]) => this.removeMultipleFromPaidContentQueue(ids),
    updateQueueItem: (id: number, autoRequeue: boolean) => this.updatePaidContentQueueItem(id, autoRequeue),
    stopAllDownloads: () => this.stopAllPaidContentDownloads(),
    clearFailedDownloads: () => this.clearFailedPaidContentDownloads(),

    // Services
    getServices: () => this.getPaidContentServices().then(r => r.services),
    getService: (id: string) => this.getPaidContentService(id),
    checkServiceHealth: (id: string) => this.checkPaidContentServiceHealth(id),
    checkAllServicesHealth: () => this.checkAllPaidContentServicesHealth(),
    updateSession: (id: string, cookie: string) => this.updatePaidContentServiceSession(id, cookie),
    updateServiceUrl: (id: string, url: string) => this.updatePaidContentServiceUrl(id, url),

    // Creators
    getCreators: (params?: Parameters<typeof this.getPaidContentCreators>[0]) =>
      this.getPaidContentCreators(params).then(r => r.creators),
    getCreator: (id: number) => this.getPaidContentCreator(id),
    addCreator: (data: Parameters<typeof this.addPaidContentCreator>[0]) =>
      this.addPaidContentCreator(data).then(r => r.creator),
    addCreatorByUrl: (url: string, autoDownload?: boolean, downloadEmbeds?: boolean) =>
      this.addPaidContentCreatorByUrl(url, autoDownload, downloadEmbeds).then(r => r.creator),
    updateCreator: (id: number, data: Parameters<typeof this.updatePaidContentCreator>[1]) =>
      this.updatePaidContentCreator(id, data),
    getCreatorTaggedUsers: (id: number) => this.getPaidContentCreatorTaggedUsers(id),
    deleteCreator: (id: number, deleteFiles?: boolean) => this.deletePaidContentCreator(id, deleteFiles),
    syncCreator: (id: number, download?: boolean, forceBackfill?: boolean) => this.syncPaidContentCreator(id, download, forceBackfill),
    syncAllCreators: () => this.syncAllPaidContentCreators(),
    syncService: (serviceId: string) => this.syncPaidContentService(serviceId),
    startDownloadQueue: () => this.post<{ status: string; message: string }>('/paid-content/download-queue/start', {}),
    searchCreators: (serviceId: string, query: string, platform?: string) =>
      this.searchPaidContentCreators(serviceId, query, platform),

    // Identities
    getIdentities: () => this.getPaidContentIdentities().then(r => r.identities),
    getIdentity: (id: number) => this.getPaidContentIdentity(id),
    createIdentity: (name: string, notes?: string) => this.createPaidContentIdentity(name, notes),
    updateIdentity: (id: number, data: Parameters<typeof this.updatePaidContentIdentity>[1]) =>
      this.updatePaidContentIdentity(id, data),
    deleteIdentity: (id: number) => this.deletePaidContentIdentity(id),
    linkCreatorToIdentity: (identityId: number, creatorId: number) =>
      this.linkPaidContentCreatorToIdentity(identityId, creatorId),
    unlinkCreatorFromIdentity: (identityId: number, creatorId: number) =>
      this.unlinkPaidContentCreatorFromIdentity(identityId, creatorId),

    // Creator Groups
    getCreatorGroups: () => this.getPaidContentCreatorGroups().then(r => r.groups),
    getCreatorGroup: (id: number) => this.getPaidContentCreatorGroup(id),
    createCreatorGroup: (name: string, description?: string) =>
      this.createPaidContentCreatorGroup({ name, description }).then(r => r.group),
    updateCreatorGroup: (id: number, data: { name?: string; description?: string }) =>
      this.updatePaidContentCreatorGroup(id, data),
    deleteCreatorGroup: (id: number) => this.deletePaidContentCreatorGroup(id),
    addCreatorToGroup: (groupId: number, creatorId: number) =>
      this.addCreatorToPaidContentGroup(groupId, creatorId),
    removeCreatorFromGroup: (groupId: number, creatorId: number) =>
      this.removeCreatorFromPaidContentGroup(groupId, creatorId),
    updateGroupMemberFilters: (groupId: number, creatorId: number, data: { filter_tagged_users?: string[] | null; filter_tag_ids?: number[] | null }) =>
      this.updatePaidContentGroupMemberFilters(groupId, creatorId, data),

    // Gallery
    getGalleryGroups: () =>
      this.get<{ groups: GalleryGroupCard[] }>('/paid-content/gallery/groups').then(r => r.groups),
    getGalleryMedia: (params?: {
      creator_group_id?: number
      creator_id?: number
      content_type?: string
      min_resolution?: string
      date_from?: string
      date_to?: string
      search?: string
      shuffle?: boolean
      shuffle_seed?: number
      limit?: number
      offset?: number
    }) => {
      const qp = new URLSearchParams()
      if (params?.creator_group_id != null) qp.set('creator_group_id', String(params.creator_group_id))
      if (params?.creator_id) qp.set('creator_id', String(params.creator_id))
      if (params?.content_type) qp.set('content_type', params.content_type)
      if (params?.min_resolution) qp.set('min_resolution', params.min_resolution)
      if (params?.date_from) qp.set('date_from', params.date_from)
      if (params?.date_to) qp.set('date_to', params.date_to)
      if (params?.search) qp.set('search', params.search)
      if (params?.shuffle) qp.set('shuffle', 'true')
      if (params?.shuffle_seed != null) qp.set('shuffle_seed', String(params.shuffle_seed))
      if (params?.limit) qp.set('limit', String(params.limit))
      if (params?.offset != null) qp.set('offset', String(params.offset))
      const qs = qp.toString()
      return this.get<{ items: GalleryMediaItem[]; total: number; limit: number; offset: number }>(
        `/paid-content/gallery/media${qs ? '?' + qs : ''}`
      )
    },
    getGalleryDateRange: (params?: {
      creator_group_id?: number
      creator_id?: number
      content_type?: string
    }) => {
      const qp = new URLSearchParams()
      if (params?.creator_group_id != null) qp.set('creator_group_id', String(params.creator_group_id))
      if (params?.creator_id) qp.set('creator_id', String(params.creator_id))
      if (params?.content_type) qp.set('content_type', params.content_type)
      const qs = qp.toString()
      return this.get<{ ranges: { year: number; month: number; count: number }[] }>(
        `/paid-content/gallery/date-range${qs ? '?' + qs : ''}`
      ).then(r => r.ranges)
    },

    // Feed
    getFeed: (params?: Parameters<typeof this.getPaidContentFeed>[0]) =>
      this.getPaidContentFeed(params),
    getPost: (id: number) => this.getPaidContentPost(id),
    toggleFavorite: (id: number) => this.togglePaidContentPostFavorite(id),
    toggleViewed: (id: number) => this.togglePaidContentPostViewed(id),
    downloadPost: (id: number) => this.downloadPaidContentPost(id),
    getUnviewedCount: () => this.getPaidContentUnviewedCount(),
    markAllViewed: () => this.markAllPaidContentViewed(),

    // Notifications
    getNotifications: (params?: Parameters<typeof this.getPaidContentNotifications>[0]) =>
      this.getPaidContentNotifications(params).then(r => r.notifications),
    markNotificationsRead: (ids?: number[], markAll?: boolean) =>
      this.markPaidContentNotificationsRead(ids, markAll),
    deleteNotification: (id: number) =>
      this.delete(`/paid-content/notifications/${id}`),

    // Settings
    getSettings: () => this.getPaidContentSettings(),
    updateSettings: (settings: Partial<PaidContentSettings>) => this.updatePaidContentSettings(settings),

    // Attachments
    getAttachment: (attachmentId: number) =>
      this.get<PaidContentAttachment>(`/paid-content/attachments/${attachmentId}`),

    // Import
    importUrl: (url: string, creatorId?: number) => this.importPaidContentFromUrl(url, creatorId),
    importUrlToAttachment: (attachmentId: number, url: string) =>
      this.post(`/paid-content/attachments/${attachmentId}/import-url`, { url }),

    // Filename parsing
    parseFilenames: (filenames: string[]) =>
      this.post<{
        files: Array<{
          original_filename: string
          detected_date: string | null
          fansly_id: string | null
          date_source: string | null
          confidence: string
        }>
        earliest_date: string | null
        latest_date: string | null
        suggested_date: string | null
        has_dates: boolean
      }>('/paid-content/parse-filenames', { filenames }),

    // Tags
    getTags: (creatorIds?: number[]) => {
      const params = creatorIds?.length ? `?creator_ids=${creatorIds.join(',')}` : ''
      return this.get<{ tags: PaidContentTag[] }>(`/paid-content/tags${params}`).then(r => r.tags)
    },
    createTag: (name: string, color?: string, description?: string) =>
      this.post<{ tag: PaidContentTag }>('/paid-content/tags', { name, color, description }).then(r => r.tag),
    updateTag: (tagId: number, updates: { name?: string; color?: string; description?: string }) =>
      this.put<{ tag: PaidContentTag }>(`/paid-content/tags/${tagId}`, updates).then(r => r.tag),
    deleteTag: (tagId: number) => this.delete(`/paid-content/tags/${tagId}`),
    addTagsToPosts: (postIds: number[], tagIds: number[]) =>
      this.post<{ added: number }>('/paid-content/tags/add-to-posts', { post_ids: postIds, tag_ids: tagIds }),
    removeTagsFromPosts: (postIds: number[], tagIds: number[]) =>
      this.post<{ removed: number }>('/paid-content/tags/remove-from-posts', { post_ids: postIds, tag_ids: tagIds }),
    getPostTags: (postId: number) =>
      this.get<{ tags: PaidContentTag[] }>(`/paid-content/posts/${postId}/tags`).then(r => r.tags),
    setPostTags: (postId: number, tagIds: number[]) =>
      this.put<{ tags: PaidContentTag[] }>(`/paid-content/posts/${postId}/tags`, { tag_ids: tagIds }).then(r => r.tags),

    // Tagged Users
    getTaggedUsers: (creatorIds?: number[]) => {
      const params = creatorIds?.length ? `?creator_ids=${creatorIds.join(',')}` : ''
      return this.get<{username: string, post_count: number}[]>(`/paid-content/tagged-users${params}`)
    },
    getContentTypes: (creatorIds?: number[]) => {
      const params = creatorIds?.length ? `?creator_ids=${creatorIds.join(',')}` : ''
      return this.get<string[]>(`/paid-content/content-types${params}`)
    },
    setPostTaggedUsers: (postId: number, usernames: string[]) =>
      this.put<{ tagged_users: string[] }>(`/paid-content/posts/${postId}/tagged-users`, { usernames }).then(r => r.tagged_users),

    // Auto-Tag Rules
    getAutoTagRules: () =>
      this.get<{ rules: PaidContentAutoTagRule[] }>('/paid-content/auto-tag-rules').then(r => r.rules),
    getAutoTagRule: (ruleId: number) =>
      this.get<{ rule: PaidContentAutoTagRule }>(`/paid-content/auto-tag-rules/${ruleId}`).then(r => r.rule),
    createAutoTagRule: (data: { name: string; conditions: Record<string, any>; tag_ids: number[]; priority?: number }) =>
      this.post<{ rule: PaidContentAutoTagRule }>('/paid-content/auto-tag-rules', data).then(r => r.rule),
    updateAutoTagRule: (ruleId: number, updates: Partial<{ name: string; enabled: number; conditions: Record<string, any>; tag_ids: number[]; priority: number }>) =>
      this.put<{ rule: PaidContentAutoTagRule }>(`/paid-content/auto-tag-rules/${ruleId}`, updates).then(r => r.rule),
    deleteAutoTagRule: (ruleId: number) =>
      this.delete(`/paid-content/auto-tag-rules/${ruleId}`),
    runAllAutoTagRules: () =>
      this.post<{ status: string; job_id: string }>('/paid-content/auto-tag-rules/run', {}),
    runAutoTagRule: (ruleId: number) =>
      this.post<{ status: string; job_id: string }>(`/paid-content/auto-tag-rules/${ruleId}/run`, {}),

    // Analytics
    getAnalyticsSummary: (days: number = 30) =>
      this.get<PaidContentAnalyticsSummary>(`/paid-content/analytics/summary?days=${days}`),

    // Watch Later
    getWatchLater: () =>
      this.get<{ items: PaidContentWatchLaterItem[] }>('/paid-content/watch-later').then(r => r.items),
    getWatchLaterCount: () =>
      this.get<{ count: number }>('/paid-content/watch-later/count').then(r => r.count),
    getWatchLaterAttachmentIds: () =>
      this.get<{ attachment_ids: number[] }>('/paid-content/watch-later/attachment-ids').then(r => r.attachment_ids),
    addToWatchLater: (attachmentId: number) =>
      this.post<{ status: string; id?: number }>('/paid-content/watch-later', { attachment_id: attachmentId }),
    addBulkToWatchLater: (attachmentIds: number[]) =>
      this.post<{ status: string; added: number }>('/paid-content/watch-later/bulk', { attachment_ids: attachmentIds }),
    removeFromWatchLater: (attachmentId: number) =>
      this.delete(`/paid-content/watch-later/${attachmentId}`),
    removeMultipleFromWatchLater: (attachmentIds: number[]) =>
      this.post<{ status: string; removed: number }>('/paid-content/watch-later/remove', { attachment_ids: attachmentIds }),
    reorderWatchLater: (orderedIds: number[]) =>
      this.post('/paid-content/watch-later/reorder', { ordered_ids: orderedIds }),
    clearWatchLater: () =>
      this.delete<{ status: string; removed: number }>('/paid-content/watch-later'),

    // Messages
    getConversations: (params?: { creator_id?: number; service_id?: string; search?: string; limit?: number; offset?: number }) => {
      const qp = new URLSearchParams()
      if (params?.creator_id) qp.set('creator_id', String(params.creator_id))
      if (params?.service_id) qp.set('service_id', params.service_id)
      if (params?.search) qp.set('search', params.search)
      if (params?.limit) qp.set('limit', String(params.limit))
      if (params?.offset) qp.set('offset', String(params.offset))
      const qs = qp.toString()
      return this.get<{ conversations: PaidContentConversation[] }>(`/paid-content/messages/conversations${qs ? '?' + qs : ''}`).then(r => r.conversations)
    },
    getMessages: (creatorId: number, params?: { before?: string; limit?: number }) => {
      const qp = new URLSearchParams()
      if (params?.before) qp.set('before', params.before)
      if (params?.limit) qp.set('limit', String(params.limit))
      const qs = qp.toString()
      return this.get<{ messages: PaidContentMessage[]; total: number }>(`/paid-content/messages/${creatorId}${qs ? '?' + qs : ''}`)
    },
    syncMessages: (creatorId: number) =>
      this.post<{ status: string; creator_id: number; username: string }>(`/paid-content/messages/${creatorId}/sync`, {}),
    markMessagesRead: (creatorId: number, messageIds?: number[]) =>
      this.post<{ status: string; marked_read: number }>(`/paid-content/messages/${creatorId}/mark-read`, messageIds ? { message_ids: messageIds } : {}),
    getUnreadMessagesCount: () => this.getPaidContentUnreadMessagesCount(),
    markAllMessagesRead: () => this.markAllPaidContentMessagesRead(),

    // Recycle Bin
    getRecycleBin: (params?: Parameters<typeof this.getPaidContentRecycleBin>[0]) =>
      this.getPaidContentRecycleBin(params).then(r => r.items),
    restoreFromRecycleBin: (ids: number[]) => this.restorePaidContentBatch(ids),
    permanentlyDelete: (ids: number[]) => this.permanentlyDeletePaidContent(ids),
    emptyRecycleBin: (itemType?: string) => this.emptyPaidContentRecycleBin(itemType),

    // Fansly Direct
    verifyFanslyAuth: (token: string) =>
      this.post<{ valid: boolean; username?: string; account_id?: string; display_name?: string; error?: string }>(
        '/paid-content/fansly-direct/verify-auth',
        { auth_token: token }
      ),
    syncFanslyDirect: (req: {
      username: string
      date_from?: string
      date_to?: string
      days_back?: number
      download?: boolean
    }) =>
      this.post<{
        status: string
        creator_id: number
        username: string
        date_filter?: { from?: string; to?: string; days_back?: number }
      }>('/paid-content/fansly-direct/sync', req),

    // OnlyFans Direct
    verifyOnlyFansAuth: (creds: {
      sess: string
      auth_id: string
      auth_uid?: string
      x_bc: string
      user_agent: string
      signing_url?: string
    }) =>
      this.post<{ valid: boolean; user_id?: string; username?: string; name?: string; error?: string }>(
        '/paid-content/onlyfans-direct/verify-auth',
        creds
      ),
    syncOnlyFansDirect: (req: {
      username: string
      date_from?: string
      date_to?: string
      days_back?: number
      download?: boolean
    }) =>
      this.post<{
        status: string
        creator_id: number
        username: string
        date_filter?: { from?: string; to?: string; days_back?: number }
      }>('/paid-content/onlyfans-direct/sync', req),

    // Bulk Upload
    uploadContent: async (formData: FormData) => {
      const token = localStorage.getItem('auth_token')
      const csrfToken = this.getCsrfToken()
      const headers: HeadersInit = {}
      if (token) headers['Authorization'] = `Bearer ${token}`
      if (csrfToken) headers['X-CSRFToken'] = csrfToken
      const response = await fetch(`${API_BASE}/paid-content/upload`, {
        method: 'POST',
        headers,
        body: formData,
        credentials: 'include',
      })
      if (!response.ok) {
        const error = await response.json().catch(() => ({ detail: 'Upload failed' }))
        throw new Error(error.detail || 'Upload failed')
      }
      return response.json()
    },

    // Attachment Management
    uploadAttachment: async (postId: number, file: File) => {
      const formData = new FormData()
      formData.append('file', file)
      const token = localStorage.getItem('auth_token')
      const csrfToken = this.getCsrfToken()
      const headers: HeadersInit = {}
      if (token) {
        headers['Authorization'] = `Bearer ${token}`
      }
      if (csrfToken) {
        headers['X-CSRFToken'] = csrfToken
      }
      const response = await fetch(`${API_BASE}/paid-content/posts/${postId}/attachments/upload`, {
        method: 'POST',
        headers,
        body: formData,
        credentials: 'include',
      })
      if (!response.ok) {
        const error = await response.json().catch(() => ({ detail: response.statusText }))
        throw new Error(error.detail || 'Upload failed')
      }
      return response.json() as Promise<{
        attachment_id: number
        filename: string
        file_type: string
        file_size: number
        local_path: string
      }>
    },
    deleteAttachment: (postId: number, attachmentId: number, permanent?: boolean) =>
      this.delete<{ success: boolean; action: string }>(
        `/paid-content/posts/${postId}/attachments/${attachmentId}${permanent ? '?permanent=true' : ''}`
      ),
    deletePost: (postId: number) =>
      this.delete<{ success: boolean; message: string }>(`/paid-content/feed/${postId}`),
    bulkDeletePosts: (postIds: number[]) =>
      this.post<{ success: boolean; deleted_count: number }>('/paid-content/feed/bulk-delete', { post_ids: postIds }),
    recheckQuality: (attachmentIds: number[]) =>
      this.post<{ status: string; count: number }>('/paid-content/attachments/recheck-quality', { attachment_ids: attachmentIds }),

    // Face Recognition
    faceAddReference: (personName: string, filePath: string) =>
      this.post<{ id: number; person_name: string; thumbnail: string | null }>('/paid-content/face/add-reference', { person_name: personName, file_path: filePath }),
    faceGetReferences: () =>
      this.get<{ references: Array<{ id: number; person_name: string; thumbnail: string | null; created_at: string }> }>('/paid-content/face/references').then(r => r.references),
    faceDeleteReference: (id: number) =>
      this.delete(`/paid-content/face/references/${id}`),
    faceScanCreator: (creatorId: number, personName: string, tolerance?: number) =>
      this.post<{ status: string; creator_id: number; person_name: string }>('/paid-content/face/scan-creator', { creator_id: creatorId, person_name: personName, tolerance: tolerance ?? 0.35 }),
    faceScanStatus: () =>
      this.get<{ status: string; processed: number; total: number; matched: number; unmatched: number; errors: number; match_tag_id?: number; no_match_tag_id?: number; error_message?: string; avg_confidence?: number; min_confidence?: number; max_confidence?: number }>('/paid-content/face/scan-status'),
    uploadFileToAttachment: (
      attachmentId: number,
      file: File,
      onProgress?: (loaded: number, total: number) => void
    ): Promise<{
      status: string
      attachment_id: number
      filename: string
      file_type: string
      file_size: number
      local_path: string
    }> => {
      return new Promise((resolve, reject) => {
        const formData = new FormData()
        formData.append('file', file)
        const token = localStorage.getItem('auth_token')

        const xhr = new XMLHttpRequest()
        xhr.open('POST', `${API_BASE}/paid-content/attachments/${attachmentId}/upload-file`)

        if (token) {
          xhr.setRequestHeader('Authorization', `Bearer ${token}`)
        }
        const csrfToken = this.getCsrfToken()
        if (csrfToken) {
          xhr.setRequestHeader('X-CSRFToken', csrfToken)
        }
        xhr.withCredentials = true

        xhr.upload.onprogress = (event) => {
          if (event.lengthComputable && onProgress) {
            onProgress(event.loaded, event.total)
          }
        }

        xhr.onload = () => {
          if (xhr.status >= 200 && xhr.status < 300) {
            try {
              resolve(JSON.parse(xhr.responseText))
            } catch {
              reject(new Error('Invalid response'))
            }
          } else {
            try {
              const error = JSON.parse(xhr.responseText)
              reject(new Error(error.detail || 'Upload failed'))
            } catch {
              reject(new Error(`Upload failed: ${xhr.statusText}`))
            }
          }
        }

        xhr.onerror = () => reject(new Error('Network error'))
        xhr.send(formData)
      })
    },
  }

  // ============================================================================
  // PRIVATE GALLERY API
  // ============================================================================

  // Private gallery session token (stored in sessionStorage - clears on browser close)
  private _privateGalleryToken: string | null = null

  constructor() {
    // Restore token from sessionStorage on initialization
    if (typeof window !== 'undefined') {
      this._privateGalleryToken = sessionStorage.getItem('private_gallery_token')
    }
  }

  setPrivateGalleryToken(token: string | null) {
    this._privateGalleryToken = token
    if (typeof window !== 'undefined') {
      if (token) {
        sessionStorage.setItem('private_gallery_token', token)
      } else {
        sessionStorage.removeItem('private_gallery_token')
      }
    }
  }

  getPrivateGalleryToken(): string | null {
    return this._privateGalleryToken
  }

  private async privateGalleryRequest<T>(
    method: 'GET' | 'POST' | 'PUT' | 'DELETE',
    endpoint: string,
    data?: unknown
  ): Promise<T> {
    const token = localStorage.getItem('auth_token')
    const headers: HeadersInit = {
      'Content-Type': 'application/json',
    }
    if (token) {
      headers['Authorization'] = `Bearer ${token}`
    }
    if (this._privateGalleryToken) {
      headers['X-Private-Gallery-Token'] = this._privateGalleryToken
    }
    // Include CSRF token for state-changing requests
    if (method !== 'GET') {
      const csrfToken = this.getCsrfToken()
      if (csrfToken) {
        headers['X-CSRFToken'] = csrfToken
      }
    }

    const response = await fetch(`${API_BASE}${endpoint}`, {
      method,
      headers,
      credentials: 'include',
      body: data ? JSON.stringify(data) : undefined,
    })

    if (!response.ok) {
      // If gallery session expired (401), clear the token
      if (response.status === 401) {
        this.setPrivateGalleryToken(null)
      }
      const error = await response.json().catch(() => ({ detail: response.statusText }))
      throw new Error(error.detail || error.message || 'Request failed')
    }

    return response.json()
  }

  privateGallery = {
    // Auth - uses privateGalleryRequest to include token if available
    getStatus: () => this.privateGalleryRequest<{
      is_setup_complete: boolean
      is_unlocked: boolean
      active_sessions: number
    }>('GET', '/private-gallery/status'),

    setup: (password: string) => this.post<{
      message: string
      token: string
    }>('/private-gallery/setup', { password }).then(r => {
      this.setPrivateGalleryToken(r.token)
      return r
    }),

    unlock: (password: string) => this.post<{
      message: string
      token: string
    }>('/private-gallery/unlock', { password }).then(r => {
      this.setPrivateGalleryToken(r.token)
      return r
    }),

    lock: () => this.privateGalleryRequest<{ message: string }>('POST', '/private-gallery/lock', {}).then(r => {
      this.setPrivateGalleryToken(null)
      return r
    }),

    changePassword: (currentPassword: string, newPassword: string) =>
      this.privateGalleryRequest<{ message: string }>('POST', '/private-gallery/change-password', {
        current_password: currentPassword,
        new_password: newPassword,
      }),

    // Config
    getConfig: () => this.privateGalleryRequest<{ config: Record<string, unknown> }>('GET', '/private-gallery/config')
      .then(r => r.config),

    updateConfig: (config: {
      storage_path?: string
      thumbnail_path?: string
      organize_by_person?: boolean
      organize_by_date?: boolean
      auto_lock_minutes?: number
      duplicate_auto_select_distance?: number
    }) => this.privateGalleryRequest<{ message: string }>('PUT', '/private-gallery/config', config),

    // Features (for enabling/disabling menu items)
    getFeatures: () => this.privateGalleryRequest<{
      all_features: string[]
      enabled_features: string[]
      feature_order: Record<string, string[]>
      feature_labels: Record<string, string>
      group_order: string[]
    }>('GET', '/private-gallery/features'),

    updateFeatures: (data: {
      enabled_features: string[]
      feature_order?: Record<string, string[]>
      feature_labels?: Record<string, string>
      group_order?: string[]
    }) =>
      this.privateGalleryRequest<{
        message: string
        enabled_features: string[]
        feature_order: Record<string, string[]>
        feature_labels: Record<string, string>
        group_order: string[]
      }>('PUT', '/private-gallery/features', data),

    resetFeatures: () =>
      this.privateGalleryRequest<{
        message: string
        enabled_features: string[]
        feature_order: Record<string, string[]>
        feature_labels: Record<string, string>
        group_order: string[]
      }>('POST', '/private-gallery/features/reset'),

    // Public features endpoint (doesn't require private gallery auth)
    getEnabledFeatures: () => this.get<{
      enabled_features: string[]
      feature_order: Record<string, string[]>
      feature_labels: Record<string, string>
      group_order: string[]
    }>('/private-gallery/features/public'),

    // Relationships
    getRelationships: (params?: { assigned_only?: boolean }) =>
      this.privateGalleryRequest<{ relationships: Array<{ id: number; name: string; color: string }> }>(
        'GET', `/private-gallery/relationships${params?.assigned_only ? '?assigned_only=true' : ''}`
      ).then(r => r.relationships),

    createRelationship: (name: string, color?: string) =>
      this.privateGalleryRequest<{ id: number; name: string; color: string }>(
        'POST', '/private-gallery/relationships', { name, color }
      ),

    updateRelationship: (id: number, name: string, color?: string) =>
      this.privateGalleryRequest<{ message: string }>(
        'PUT', `/private-gallery/relationships/${id}`, { name, color }
      ),

    deleteRelationship: (id: number) =>
      this.privateGalleryRequest<{ message: string }>('DELETE', `/private-gallery/relationships/${id}`),

    // Persons
    getPersons: () =>
      this.privateGalleryRequest<{
        persons: Array<{
          id: number
          name: string
          sort_name?: string | null
          relationship_id: number
          relationship: { id: number; name: string; color: string }
          default_tag_ids: number[]
        }>
      }>('GET', '/private-gallery/persons').then(r => r.persons),

    createPerson: (data: { name: string; sort_name?: string; relationship_id: number; default_tag_ids?: number[] }) =>
      this.privateGalleryRequest<{ id: number; name: string; sort_name?: string; relationship_id: number; default_tag_ids: number[] }>(
        'POST', '/private-gallery/persons', data
      ),

    updatePerson: (id: number, data: { name?: string; sort_name?: string; relationship_id?: number; default_tag_ids?: number[] }) =>
      this.privateGalleryRequest<{ message: string }>('PUT', `/private-gallery/persons/${id}`, data),

    deletePerson: (id: number) =>
      this.privateGalleryRequest<{ message: string }>('DELETE', `/private-gallery/persons/${id}`),

    // Person Groups
    getPersonGroups: () =>
      this.privateGalleryRequest<{
        groups: Array<{
          id: number
          name: string
          description: string | null
          member_count: number
          tag_member_count: number
          relationship_member_count: number
          min_resolution?: number
          created_at: string
          updated_at: string
        }>
      }>('GET', '/private-gallery/person-groups').then(r => r.groups),

    getPersonGroup: (id: number) =>
      this.privateGalleryRequest<{
        id: number
        name: string
        description: string | null
        member_count: number
        tag_member_count: number
        relationship_member_count: number
        members: Array<{
          id: number
          name: string
          sort_name?: string | null
          relationship_id: number
          relationship: { id: number; name: string; color: string }
          added_at: string
        }>
        tag_members: Array<{
          id: number
          name: string
          color: string
          added_at: string
        }>
        relationship_members: Array<{
          id: number
          name: string
          color: string
          added_at: string
        }>
        excluded_tags: Array<{
          id: number
          name: string
          color: string
          added_at: string
        }>
        min_resolution?: number
        created_at: string
        updated_at: string
      }>('GET', `/private-gallery/person-groups/${id}`),

    createPersonGroup: (name: string, description?: string, min_resolution?: number) =>
      this.privateGalleryRequest<{ group: { id: number; name: string; description: string | null; min_resolution: number; member_count: number }; message: string }>(
        'POST', '/private-gallery/person-groups', { name, description, min_resolution }
      ).then(r => r.group),

    updatePersonGroup: (id: number, data: { name?: string; description?: string; min_resolution?: number }) =>
      this.privateGalleryRequest<{ success: boolean; message: string }>(
        'PUT', `/private-gallery/person-groups/${id}`, data
      ),

    deletePersonGroup: (id: number) =>
      this.privateGalleryRequest<{ success: boolean; message: string }>(
        'DELETE', `/private-gallery/person-groups/${id}`
      ),

    addPersonToGroup: (groupId: number, personId: number) =>
      this.privateGalleryRequest<{ success: boolean; message: string }>(
        'POST', `/private-gallery/person-groups/${groupId}/members`, { person_id: personId }
      ),

    removePersonFromGroup: (groupId: number, personId: number) =>
      this.privateGalleryRequest<{ success: boolean; message: string }>(
        'DELETE', `/private-gallery/person-groups/${groupId}/members/${personId}`
      ),

    addTagToGroup: (groupId: number, tagId: number) =>
      this.privateGalleryRequest<{ success: boolean; message: string }>(
        'POST', `/private-gallery/person-groups/${groupId}/tags`, { tag_id: tagId }
      ),

    removeTagFromGroup: (groupId: number, tagId: number) =>
      this.privateGalleryRequest<{ success: boolean; message: string }>(
        'DELETE', `/private-gallery/person-groups/${groupId}/tags/${tagId}`
      ),

    addRelationshipToGroup: (groupId: number, relationshipId: number) =>
      this.privateGalleryRequest<{ success: boolean; message: string }>(
        'POST', `/private-gallery/person-groups/${groupId}/relationships`, { relationship_id: relationshipId }
      ),

    removeRelationshipFromGroup: (groupId: number, relationshipId: number) =>
      this.privateGalleryRequest<{ success: boolean; message: string }>(
        'DELETE', `/private-gallery/person-groups/${groupId}/relationships/${relationshipId}`
      ),

    addExcludedTagToGroup: (groupId: number, tagId: number) =>
      this.privateGalleryRequest<{ success: boolean; message: string }>(
        'POST', `/private-gallery/person-groups/${groupId}/excluded-tags`, { tag_id: tagId }
      ),

    removeExcludedTagFromGroup: (groupId: number, tagId: number) =>
      this.privateGalleryRequest<{ success: boolean; message: string }>(
        'DELETE', `/private-gallery/person-groups/${groupId}/excluded-tags/${tagId}`
      ),

    // Simpcity threads
    getSimpcityThreads: () =>
      this.privateGalleryRequest<{
        threads: Array<{ id: number; url: string; title?: string; monitoring_enabled: boolean }>
      }>('GET', '/private-gallery/simpcity/threads').catch(() => ({ threads: [] as Array<{ id: number; url: string; title?: string; monitoring_enabled: boolean }> })),

    // Tags (encrypted private_gallery_tags)
    getTags: (params?: { assigned_only?: boolean }) =>
      this.privateGalleryRequest<{
        tags: Array<{ id: number; name: string; color: string; description?: string; usage_count?: number }>
      }>('GET', `/private-gallery/tags${params?.assigned_only ? '?assigned_only=true' : ''}`).then(r => r.tags),

    createTag: (name: string, color?: string, description?: string) =>
      this.privateGalleryRequest<{ id: number; name: string; color: string; description?: string }>(
        'POST', '/private-gallery/tags', { name, color, description }
      ),

    updateTag: (tagId: number, updates: { name?: string; color?: string; description?: string }) =>
      this.privateGalleryRequest<{ message: string }>(
        'PUT', `/private-gallery/tags/${tagId}`, updates
      ),

    deleteTag: (tagId: number) =>
      this.privateGalleryRequest<{ message: string }>(
        'DELETE', `/private-gallery/tags/${tagId}`
      ),

    // Import Auth
    getImportAuth: () =>
      this.privateGalleryRequest<{ entries: Array<import('../types/privateGallery').ImportAuthEntry> }>(
        'GET', '/private-gallery/import-auth'
      ).then(r => r.entries),

    createImportAuth: (data: {
      domain: string; auth_type: string; username?: string; password?: string;
      cookies?: Array<Record<string, string>>; user_agent?: string; notes?: string
    }) =>
      this.privateGalleryRequest<{ id: number; domain: string; auth_type: string }>(
        'POST', '/private-gallery/import-auth', data
      ),

    updateImportAuth: (id: number, data: {
      domain?: string; auth_type?: string; username?: string; password?: string;
      cookies?: Array<Record<string, string>>; user_agent?: string; notes?: string
    }) =>
      this.privateGalleryRequest<{ message: string }>(
        'PUT', `/private-gallery/import-auth/${id}`, data
      ),

    deleteImportAuth: (id: number) =>
      this.privateGalleryRequest<{ message: string }>(
        'DELETE', `/private-gallery/import-auth/${id}`
      ),

    // Media
    getMedia: (params: {
      offset?: number
      limit?: number
      person_id?: number
      person_group_id?: number
      relationship_id?: number
      tag_ids?: number[]
      file_type?: 'image' | 'video' | 'all'
      search?: string
      sort_by?: 'media_date' | 'created_at' | 'filename'
      sort_order?: 'asc' | 'desc'
      date_from?: string
      date_to?: string
      has_tags?: boolean
      has_description?: boolean
      include_attached?: boolean
      shuffle?: boolean
      shuffle_seed?: number
      unread_only?: boolean
      min_resolution?: number
    }) => {
      const queryParams = new URLSearchParams()
      if (params.offset !== undefined) queryParams.set('offset', params.offset.toString())
      if (params.limit !== undefined) queryParams.set('limit', params.limit.toString())
      if (params.person_id !== undefined) queryParams.set('person_id', params.person_id.toString())
      if (params.person_group_id !== undefined) queryParams.set('person_group_id', params.person_group_id.toString())
      if (params.relationship_id !== undefined) queryParams.set('relationship_id', params.relationship_id.toString())
      if (params.tag_ids?.length) queryParams.set('tag_ids', params.tag_ids.join(','))
      if (params.file_type) queryParams.set('file_type', params.file_type)
      if (params.search) queryParams.set('search', params.search)
      if (params.sort_by) queryParams.set('sort_by', params.sort_by)
      if (params.sort_order) queryParams.set('sort_order', params.sort_order)
      if (params.date_from) queryParams.set('date_from', params.date_from)
      if (params.date_to) queryParams.set('date_to', params.date_to)
      if (params.has_tags !== undefined) queryParams.set('has_tags', params.has_tags.toString())
      if (params.has_description !== undefined) queryParams.set('has_description', params.has_description.toString())
      if (params.include_attached !== undefined) queryParams.set('include_attached', params.include_attached.toString())
      if (params.shuffle) queryParams.set('shuffle', 'true')
      if (params.shuffle_seed !== undefined) queryParams.set('shuffle_seed', params.shuffle_seed.toString())
      if (params.unread_only) queryParams.set('unread_only', 'true')
      if (params.min_resolution) queryParams.set('min_resolution', params.min_resolution.toString())

      return this.privateGalleryRequest<{
        items: Array<unknown>
        total: number
        offset: number
        limit: number
        has_more: boolean
      }>('GET', `/private-gallery/media?${queryParams.toString()}`)
    },

    // Posts (grouped media)
    getPosts: (params: {
      offset?: number
      limit?: number
      person_id?: number
      person_group_id?: number
      relationship_id?: number
      tag_ids?: number[]
      file_type?: 'image' | 'video' | 'all'
      search?: string
      sort_by?: 'media_date' | 'created_at'
      sort_order?: 'asc' | 'desc'
      date_from?: string
      date_to?: string
      has_tags?: boolean
      has_description?: boolean
      unread_only?: boolean
    }) => {
      const queryParams = new URLSearchParams()
      if (params.offset !== undefined) queryParams.set('offset', params.offset.toString())
      if (params.limit !== undefined) queryParams.set('limit', params.limit.toString())
      if (params.person_id !== undefined) queryParams.set('person_id', params.person_id.toString())
      if (params.person_group_id !== undefined) queryParams.set('person_group_id', params.person_group_id.toString())
      if (params.relationship_id !== undefined) queryParams.set('relationship_id', params.relationship_id.toString())
      if (params.tag_ids?.length) queryParams.set('tag_ids', params.tag_ids.join(','))
      if (params.file_type) queryParams.set('file_type', params.file_type)
      if (params.search) queryParams.set('search', params.search)
      if (params.sort_by) queryParams.set('sort_by', params.sort_by)
      if (params.sort_order) queryParams.set('sort_order', params.sort_order)
      if (params.date_from) queryParams.set('date_from', params.date_from)
      if (params.date_to) queryParams.set('date_to', params.date_to)
      if (params.has_tags !== undefined) queryParams.set('has_tags', params.has_tags.toString())
      if (params.has_description !== undefined) queryParams.set('has_description', params.has_description.toString())
      if (params.unread_only) queryParams.set('unread_only', 'true')

      return this.privateGalleryRequest<{
        posts: Array<{
          id: number
          person_id: number | null
          description: string | null
          media_date: string
          created_at: string
          is_read: number
          person: {
            id: number
            name: string
            relationship: {
              id: number
              name: string
              color: string
            }
          } | null
          attachments: Array<{
            id: number
            filename: string
            file_type: string
            file_size: number
            width: number | null
            height: number | null
            duration: number | null
            media_date: string
            thumbnail_url: string
            stream_url: string
            file_url: string
          }>
          tags: Array<{ id: number; name: string; slug: string; color: string }>
          attachment_count: number
        }>
        total: number
        total_media: number
        offset: number
        limit: number
        has_more: boolean
      }>('GET', `/private-gallery/posts?${queryParams.toString()}`)
    },

    getNewPostsCount: () =>
      this.privateGalleryRequest<{ count: number }>(
        'GET', '/private-gallery/new-posts-count'
      ),

    markPostsSeen: () =>
      this.privateGalleryRequest<{ updated: number }>(
        'POST', '/private-gallery/mark-posts-seen'
      ),

    batchUpdateReadStatus: (data: { post_ids: number[]; is_read: boolean }) =>
      this.privateGalleryRequest<{ updated: number }>(
        'PUT', '/private-gallery/posts/batch/read-status', data
      ),

    updatePostReadStatus: (postId: number, is_read: boolean) =>
      this.privateGalleryRequest<{ post_id: number; is_read: number }>(
        'PUT', `/private-gallery/posts/${postId}/read-status`, { is_read }
      ),

    getPostAttachments: (postId: number, offset: number = 0, limit: number = 100) =>
      this.privateGalleryRequest<{
        items: Array<{
          id: number
          storage_id: string
          filename: string
          description: string | null
          file_hash: string
          file_size: number
          file_type: string
          mime_type: string
          width: number | null
          height: number | null
          duration: number | null
          media_date: string
          thumbnail_url: string
          stream_url: string
          file_url: string
        }>
        total: number
        offset: number
        limit: number
        has_more: boolean
      }>('GET', `/private-gallery/posts/${postId}/attachments?offset=${offset}&limit=${limit}`),

    getMediaItem: (id: number) =>
      this.privateGalleryRequest<unknown>('GET', `/private-gallery/media/${id}`),

    updateMedia: (id: number, data: {
      description?: string
      person_id?: number
      media_date?: string
      tag_ids?: number[]
    }) => this.privateGalleryRequest<{ message: string }>('PUT', `/private-gallery/media/${id}`, data),

    deleteMedia: (id: number) =>
      this.privateGalleryRequest<{ message: string }>('DELETE', `/private-gallery/media/${id}`),

    batchDeleteMedia: (mediaIds: number[]) =>
      this.privateGalleryRequest<{ deleted: number }>('POST', '/private-gallery/media/batch-delete', { media_ids: mediaIds }),

    // Post management
    updatePost: (id: number, data: {
      description?: string
      person_id?: number
      media_date?: string
      tag_ids?: number[]
    }) => this.privateGalleryRequest<{ message: string }>('PUT', `/private-gallery/posts/${id}`, data),

    deletePost: (id: number) =>
      this.privateGalleryRequest<{ message: string }>('DELETE', `/private-gallery/posts/${id}`),

    createPost: (data: { person_id: number; tag_ids?: number[]; media_date?: string; description?: string; media_ids?: number[] }) =>
      this.privateGalleryRequest<{ post_id: number; attached: number }>('POST', '/private-gallery/posts', data),

    attachMediaToPost: (postId: number, mediaIds: number[]) =>
      this.privateGalleryRequest<{ attached: number }>('POST', `/private-gallery/posts/${postId}/attach-media`, { media_ids: mediaIds }),

    reparseDates: () =>
      this.privateGalleryRequest<{ message: string; updated: number }>('POST', '/private-gallery/reparse-dates'),

    regenerateThumbnails: (missingOnly: boolean = false) =>
      this.privateGalleryRequest<{ total: number; regenerated: number; skipped: number; errors: Array<{ id: number; error: string }> }>(
        'POST', `/private-gallery/regenerate-thumbnails?missing_only=${missingOnly}`
      ),

    // Job status polling
    getJobStatus: (jobId: string) =>
      this.privateGalleryRequest<{
        id: string
        status: 'processing' | 'completed'
        operation: 'copy' | 'upload' | 'import' | 'directory'
        total_files: number
        processed_files: number
        success_count: number
        failed_count: number
        duplicate_count: number
        results: Array<{
          id?: number
          path?: string
          filename?: string
          status: 'created' | 'duplicate' | 'failed'
          existing_id?: number
          media_date?: string
          error?: string
        }>
        current_file: string | null
        current_phase: 'resolving' | 'scraping' | 'filtering' | 'downloading' | 'processing' | 'thumbnail' | 'encrypting' | null
        resolved_filenames?: string[]
        bytes_downloaded: number
        bytes_total: number
        started_at: string
        completed_at: string | null
        post_id?: number | null
      }>('GET', `/private-gallery/job-status/${jobId}`),

    // Upload with progress tracking
    upload: async (data: {
      files: File[]
      person_id: number
      tag_ids: number[]
      media_date?: string
      description?: string
      onProgress?: (progress: { loaded: number; total: number; percent: number }) => void
    }): Promise<{
      job_id: string
      post_id: number | null
      status: string
      total_files: number
    }> => {
      const formData = new FormData()
      data.files.forEach(file => formData.append('files', file))
      formData.append('person_id', data.person_id.toString())
      formData.append('tag_ids', data.tag_ids.join(','))
      if (data.media_date) formData.append('media_date', data.media_date)
      if (data.description) formData.append('description', data.description)

      const token = localStorage.getItem('auth_token')

      return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest()

        xhr.upload.addEventListener('progress', (event) => {
          if (event.lengthComputable && data.onProgress) {
            data.onProgress({
              loaded: event.loaded,
              total: event.total,
              percent: Math.round((event.loaded / event.total) * 100),
            })
          }
        })

        xhr.addEventListener('load', () => {
          if (xhr.status >= 200 && xhr.status < 300) {
            try {
              resolve(JSON.parse(xhr.responseText))
            } catch {
              reject(new Error('Invalid JSON response'))
            }
          } else {
            try {
              const error = JSON.parse(xhr.responseText)
              reject(new Error(error.detail || 'Upload failed'))
            } catch {
              reject(new Error(xhr.statusText || 'Upload failed'))
            }
          }
        })

        xhr.addEventListener('error', () => reject(new Error('Network error')))
        xhr.addEventListener('abort', () => reject(new Error('Upload cancelled')))

        xhr.open('POST', `${API_BASE}/private-gallery/upload`)
        xhr.withCredentials = true
        if (token) xhr.setRequestHeader('Authorization', `Bearer ${token}`)
        if (this._privateGalleryToken) xhr.setRequestHeader('X-Private-Gallery-Token', this._privateGalleryToken)
        const csrfToken = this.getCsrfToken()
        if (csrfToken) xhr.setRequestHeader('X-CSRFToken', csrfToken)
        xhr.send(formData)
      })
    },

    // Copy
    copy: (data: {
      source_paths: string[]
      person_id: number
      tag_ids: number[]
      media_date?: string
      description?: string
      original_filenames?: Record<string, string>
    }) => this.privateGalleryRequest<{
      job_id: string
      post_id: number | null
      status: string
      total_files: number
    }>('POST', '/private-gallery/copy', data),

    // Import from URLs
    importUrls: (data: {
      urls: string[]
      person_id: number
      tag_ids: number[]
      media_date?: string
      description?: string
    }) => this.privateGalleryRequest<{
      job_id: string
      post_id: number | null
      status: string
      total_files: number
    }>('POST', '/private-gallery/import-urls', data),

    // Import from server directory
    importDirectory: (data: {
      directory_path: string
      person_id: number
      tag_ids: number[]
      media_date?: string
      description?: string
      recursive?: boolean
    }) => this.privateGalleryRequest<{
      job_id: string
      post_id: number | null
      status: string
      total_files: number
    }>('POST', '/private-gallery/import-directory', data),

    // List server directory contents
    listDirectory: (path: string, recursive: boolean = false) =>
      this.privateGalleryRequest<{
        path: string
        files: Array<{ name: string; size: number; type: 'image' | 'video' }>
        subdirectories: string[]
        file_count: number
        total_size: number
      }>('GET', `/private-gallery/list-directory?path=${encodeURIComponent(path)}&recursive=${recursive}`),

    // Batch operations
    batchUpdateTags: (data: {
      media_ids: number[]
      add_tag_ids?: number[]
      remove_tag_ids?: number[]
    }) => this.privateGalleryRequest<{ added: number; removed: number }>('PUT', '/private-gallery/media/batch/tags', data),

    batchUpdateDate: (mediaIds: number[], newDate: string) =>
      this.privateGalleryRequest<{ updated: number }>('PUT', '/private-gallery/media/batch/date', {
        media_ids: mediaIds,
        new_date: newDate,
      }),

    batchUpdatePerson: (mediaIds: number[], personId: number) =>
      this.privateGalleryRequest<{ updated: number }>('PUT', '/private-gallery/media/batch/person', {
        media_ids: mediaIds,
        person_id: personId,
      }),

    // Duplicate scanning
    scanDuplicates: (postId: number) =>
      this.privateGalleryRequest<{
        job_id: string | null
        message?: string
      }>('POST', `/private-gallery/posts/${postId}/scan-duplicates`),

    scanAllDuplicates: () =>
      this.privateGalleryRequest<{
        job_id: string | null
        message?: string
      }>('POST', '/private-gallery/scan-all-duplicates'),

    cleanupEmptyPosts: (postIds: number[]) =>
      this.privateGalleryRequest<{
        deleted_post_ids: number[]
      }>('POST', '/private-gallery/posts/cleanup-empty', { post_ids: postIds }),

    // Export
    exportSingle: (mediaId: number) => {
      // Return URL for download - auth handled via cookies
      const token = this._privateGalleryToken
      return `${API_BASE}/private-gallery/export/${mediaId}${token ? `?_token=${encodeURIComponent(token)}` : ''}`
    },

    exportBatch: async (data: {
      media_ids: number[]
      organize_by_date?: boolean
      organize_by_person?: boolean
    }) => {
      const token = localStorage.getItem('auth_token')
      const csrfToken = this.getCsrfToken()
      const headers: HeadersInit = { 'Content-Type': 'application/json' }
      if (token) headers['Authorization'] = `Bearer ${token}`
      if (this._privateGalleryToken) headers['X-Private-Gallery-Token'] = this._privateGalleryToken
      if (csrfToken) headers['X-CSRFToken'] = csrfToken
      const response = await fetch(`${API_BASE}/private-gallery/export/batch`, {
        method: 'POST',
        headers,
        credentials: 'include',
        body: JSON.stringify(data),
      })
      if (!response.ok) {
        const error = await response.json().catch(() => ({ detail: response.statusText }))
        throw new Error(error.detail || 'Export failed')
      }
      return response.blob()
    },

    // Albums (auto-generated from persons)
    getAlbums: () =>
      this.privateGalleryRequest<{
        albums: Array<{
          person_id: number
          person_name: string
          relationship: { id: number; name: string; color: string }
          item_count: number
          cover_thumbnail_url?: string | null
        }>
      }>('GET', '/private-gallery/albums').then(r => r.albums),

    // Stats
    getStats: () =>
      this.privateGalleryRequest<{
        total_items: number
        total_size: number
        total_persons: number
        by_type: Record<string, number>
        by_person_count: Record<number, number>
        recent_7d: number
        storage_used: number
      }>('GET', '/private-gallery/stats'),

    // Reddit monitor
    getRedditSettings: () =>
      this.privateGalleryRequest<{ settings: {
        enabled: boolean
        check_interval_hours: number
        lookback_days: number
        has_cookies: boolean
        last_checked: string | null
      } }>('GET', '/private-gallery/reddit/settings').then(r => r.settings),

    updateRedditSettings: (data: {
      enabled?: boolean
      check_interval_hours?: number
      lookback_days?: number
    }) => this.privateGalleryRequest<{ message: string }>('PUT', '/private-gallery/reddit/settings', data),

    uploadRedditCookies: (cookiesJson: string) =>
      this.privateGalleryRequest<{ message: string }>('PUT', '/private-gallery/reddit/cookies', { cookies_json: cookiesJson }),

    deleteRedditCookies: () =>
      this.privateGalleryRequest<{ message: string }>('DELETE', '/private-gallery/reddit/cookies'),

    getRedditCommunities: () =>
      this.privateGalleryRequest<{ communities: Array<{
        id: number
        subreddit_name: string
        person_id: number
        person_name: string
        relationship_name: string
        relationship_color: string
        enabled: boolean
        last_checked: string | null
        total_media_found: number
        created_at: string
        updated_at: string
      }> }>('GET', '/private-gallery/reddit/communities').then(r => r.communities),

    addRedditCommunity: (data: { subreddit_name: string; person_id: number }) =>
      this.privateGalleryRequest<{ id: number; message: string }>('POST', '/private-gallery/reddit/communities', data),

    updateRedditCommunity: (id: number, data: { subreddit_name?: string; person_id?: number; enabled?: boolean }) =>
      this.privateGalleryRequest<{ message: string }>('PUT', `/private-gallery/reddit/communities/${id}`, data),

    deleteRedditCommunity: (id: number) =>
      this.privateGalleryRequest<{ message: string }>('DELETE', `/private-gallery/reddit/communities/${id}`),

    triggerRedditCheck: () =>
      this.privateGalleryRequest<{ message: string }>('POST', '/private-gallery/reddit/check-now'),

    downloadFullCommunity: (id: number) =>
      this.privateGalleryRequest<{ message: string }>('POST', `/private-gallery/reddit/communities/${id}/download-all`),

    checkSingleCommunity: (id: number) =>
      this.privateGalleryRequest<{ message: string }>('POST', `/private-gallery/reddit/communities/${id}/check`),

    checkCommunitiesByPerson: (personId: number) =>
      this.privateGalleryRequest<{ message: string }>('POST', `/private-gallery/reddit/communities/check-by-person/${personId}`),

    getRedditHistory: (communityId: number) =>
      this.privateGalleryRequest<{ history: Array<{
        id: number
        community_id: number
        reddit_post_id: string
        media_count: number
        processed_at: string
      }> }>('GET', `/private-gallery/reddit/history/${communityId}`).then(r => r.history),

    // Scraper account mappings (Instagram, TikTok, Snapchat)
    getScraperAccounts: (platform: string) =>
      this.privateGalleryRequest<{ accounts: Array<{
        id: number
        platform: string
        username: string
        person_id: number
        person_name: string
        relationship_name: string | null
        relationship_color: string | null
        enabled: boolean
        last_imported_at: string | null
        total_media_imported: number
        created_at: string
        updated_at: string
      }> }>('GET', `/private-gallery/scraper-accounts/${platform}`).then(r => r.accounts),

    addScraperAccount: (platform: string, data: { username: string; person_id: number }) =>
      this.privateGalleryRequest<{ id: number; message: string }>('POST', `/private-gallery/scraper-accounts/${platform}`, data),

    updateScraperAccount: (platform: string, id: number, data: { person_id?: number; enabled?: boolean }) =>
      this.privateGalleryRequest<{ message: string }>('PUT', `/private-gallery/scraper-accounts/${platform}/${id}`, data),

    deleteScraperAccount: (platform: string, id: number) =>
      this.privateGalleryRequest<{ message: string }>('DELETE', `/private-gallery/scraper-accounts/${platform}/${id}`),

    getAvailableAccounts: (platform: string) =>
      this.privateGalleryRequest<{ accounts: Array<{
        username: string
        sources: string[]
        is_mapped: boolean
      }> }>('GET', `/private-gallery/scraper-accounts/${platform}/available`).then(r => r.accounts),

    // Helper to get thumbnail URL with auth
    getThumbnailUrl: (mediaId: number) => {
      const token = this._privateGalleryToken
      const tokenParam = token ? `?_token=${encodeURIComponent(token)}` : ''
      return `${API_BASE}/private-gallery/thumbnail/${mediaId}${tokenParam}`
    },

    // Helper to get file URL with auth
    getFileUrl: (mediaId: number) => {
      const token = this._privateGalleryToken
      const tokenParam = token ? `?_token=${encodeURIComponent(token)}` : ''
      return `${API_BASE}/private-gallery/file/${mediaId}${tokenParam}`
    },

    // Helper to get stream URL with auth
    getStreamUrl: (mediaId: number) => {
      const token = this._privateGalleryToken
      const tokenParam = token ? `?_token=${encodeURIComponent(token)}` : ''
      return `${API_BASE}/private-gallery/stream/${mediaId}${tokenParam}`
    },
  }
}

export const api = new APIClient()

// WebSocket connection
export class WebSocketClient {
  private ws: WebSocket | null = null
  private listeners: Map<string, Set<WebSocketCallback>> = new Map()
  private connecting: boolean = false
  private reconnectTimeout: ReturnType<typeof setTimeout> | null = null
  private intentionalClose: boolean = false

  connect() {
    // Clear any pending reconnect
    if (this.reconnectTimeout) {
      clearTimeout(this.reconnectTimeout)
      this.reconnectTimeout = null
    }

    // Don't create duplicate connections - check both ws state and connecting flag
    if (this.connecting) {
      console.log('WebSocket connection already in progress, skipping')
      return
    }

    if (this.ws && (this.ws.readyState === WebSocket.CONNECTING || this.ws.readyState === WebSocket.OPEN)) {
      console.log('WebSocket already connected or connecting, skipping')
      return
    }

    this.connecting = true
    this.intentionalClose = false
    const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
    const wsUrl = `${protocol}//${window.location.host}/ws`

    // Use cookie-based authentication (auth_token cookie is set by login)
    // Don't use subprotocol - it can cause issues with some proxies
    try {
      this.ws = new WebSocket(wsUrl)
    } catch (error) {
      console.error('WebSocket creation failed:', error)
      this.connecting = false
      this.scheduleReconnect()
      return
    }

    this.ws.onopen = () => {
      this.connecting = false
      console.log('WebSocket connected')
    }

    this.ws.onmessage = (event) => {
      try {
        const data = JSON.parse(event.data) as WebSocketMessage
        const listeners = this.listeners.get(data.type)
        if (listeners) {
          listeners.forEach((listener) => listener(data))
        }
        // Also notify wildcard listeners
        const wildcard = this.listeners.get('*')
        if (wildcard) {
          wildcard.forEach((listener) => listener(data))
        }
      } catch (error) {
        console.error('WebSocket message error:', error)
      }
    }

    this.ws.onerror = (error) => {
      this.connecting = false
      console.error('WebSocket error:', error)
    }

    this.ws.onclose = (event) => {
      this.connecting = false
      this.ws = null

      // Don't reconnect if intentionally closed or authentication failed (code 4001)
      if (this.intentionalClose) {
        console.log('WebSocket closed intentionally, not reconnecting')
        return
      }

      if (event.code === 4001) {
        console.log('WebSocket authentication failed, not reconnecting')
        return
      }

      console.log('WebSocket disconnected, reconnecting in 5s...')
      this.scheduleReconnect()
    }
  }

  private scheduleReconnect() {
    if (this.reconnectTimeout) {
      clearTimeout(this.reconnectTimeout)
    }
    this.reconnectTimeout = setTimeout(() => {
      this.reconnectTimeout = null
      this.connect()
    }, 5000)
  }

  on(type: string, callback: WebSocketCallback) {
    if (!this.listeners.has(type)) {
      this.listeners.set(type, new Set())
    }
    this.listeners.get(type)!.add(callback)

    // Return unsubscribe function
    return () => {
      this.listeners.get(type)?.delete(callback)
    }
  }

  send(data: WebSocketMessage) {
    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify(data))
    }
  }

  disconnect() {
    // Clear any pending reconnect
    if (this.reconnectTimeout) {
      clearTimeout(this.reconnectTimeout)
      this.reconnectTimeout = null
    }

    // Mark as intentional close to prevent reconnection attempts
    this.intentionalClose = true

    if (this.ws) {
      // Only close if not already closing/closed
      if (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) {
        this.ws.close(1000, 'Client disconnect')
      }
      this.ws = null
    }

    this.connecting = false
  }

  isConnected(): boolean {
    return this.ws !== null && this.ws.readyState === WebSocket.OPEN
  }
}

export const wsClient = new WebSocketClient()
