import axios, {
  AxiosError,
  InternalAxiosRequestConfig,
  AxiosResponse,
} from 'axios';
// CRITICAL FIX: Utiliser le wrapper lazy pour éviter les collisions de noms de variables
import toast from '@/utils/toast';
import { z } from 'zod';
import { TokenStorage } from '../tokenStorage';
import { refreshToken, isTokenExpiringSoon } from '../tokenRefresh';
import { env } from '@/config/env';
import { parseApiError, getErrorCategory } from '@/utils/apiErrorHandler';
import { formatUserFriendlyError } from '@/utils/errorMessages';
import { csrfService } from '../csrf';
import { logger, setLogContext } from '@/utils/logger';
import {
  isTimeoutError as _isTimeoutError,
  getTimeoutMessage as _getTimeoutMessage,
} from '@/utils/timeoutHandler';
import { offlineQueue } from '../offlineQueue';
import { requestDeduplication } from '../requestDeduplication';
import { responseCache } from '../responseCache';
import { invalidateStateAfterMutation } from '@/utils/stateInvalidation';
import { safeValidateApiResponse } from '@/schemas/apiSchemas';
import { safeValidateApiRequest } from '@/schemas/apiRequestSchemas';
import type { ApiResponse } from '@/types/api';
import { useRateLimitStore } from '@/stores/rateLimit';

/**
 * Action 1.2.2.3: Validation error metrics tracker
 * Tracks validation failure rate for monitoring
 */
interface ValidationMetrics {
  totalValidations: number;
  successfulValidations: number;
  failedValidations: number;
  failureRate: number; // percentage
  lastFailureTime?: string;
  lastSuccessTime?: string;
  failuresByEndpoint: Record<string, number>;
}

class ValidationMetricsTracker {
  private metrics: ValidationMetrics = {
    totalValidations: 0,
    successfulValidations: 0,
    failedValidations: 0,
    failureRate: 0,
    failuresByEndpoint: {},
  };

  recordSuccess(_url?: string): void {
    this.metrics.totalValidations++;
    this.metrics.successfulValidations++;
    this.metrics.lastSuccessTime = new Date().toISOString();
    this.updateFailureRate();
  }

  recordFailure(url?: string): void {
    this.metrics.totalValidations++;
    this.metrics.failedValidations++;
    this.metrics.lastFailureTime = new Date().toISOString();
    
    if (url) {
      const endpoint = this.normalizeEndpoint(url);
      this.metrics.failuresByEndpoint[endpoint] =
        (this.metrics.failuresByEndpoint[endpoint] || 0) + 1;
    }
    
    this.updateFailureRate();
  }

  private updateFailureRate(): void {
    if (this.metrics.totalValidations > 0) {
      this.metrics.failureRate =
        (this.metrics.failedValidations / this.metrics.totalValidations) * 100;
    }
  }

  private normalizeEndpoint(url?: string): string {
    if (!url) return 'unknown';
    // Normalize URL to endpoint pattern (e.g., /api/v1/tracks/123 -> /api/v1/tracks/:id)
    try {
      const urlObj = new URL(url, 'http://localhost');
      const path = urlObj.pathname;
      // Replace UUIDs and numeric IDs with :id
      return path.replace(/\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, '/:id')
                 .replace(/\/\d+/g, '/:id');
    } catch {
      // Fallback: remove query params and return path
      const path = url.split('?')[0];
      return path || 'unknown';
    }
  }

  getMetrics(): ValidationMetrics {
    return { ...this.metrics };
  }

  reset(): void {
    this.metrics = {
      totalValidations: 0,
      successfulValidations: 0,
      failedValidations: 0,
      failureRate: 0,
      failuresByEndpoint: {},
    };
  }
}

// Singleton instance for validation metrics
export const validationMetrics = new ValidationMetricsTracker();

/**
 * Action 1.2.2.4: Validation error alerting
 * Monitors validation metrics and alerts when thresholds are exceeded
 */
interface ValidationAlertConfig {
  failureRateThreshold: number; // Percentage (e.g., 5 = 5%)
  minValidationsForAlert: number; // Minimum validations before alerting
  checkInterval: number; // Milliseconds between checks
}

const DEFAULT_ALERT_CONFIG: ValidationAlertConfig = {
  failureRateThreshold: 5.0, // Alert if failure rate > 5%
  minValidationsForAlert: 10, // Need at least 10 validations before alerting
  checkInterval: 5 * 60 * 1000, // Check every 5 minutes
};

class ValidationAlerting {
  private config: ValidationAlertConfig = DEFAULT_ALERT_CONFIG;
  private checkIntervalId: NodeJS.Timeout | null = null;
  private lastAlertTime: number = 0;
  private alertCooldown: number = 15 * 60 * 1000; // 15 minutes between alerts

  start(config?: Partial<ValidationAlertConfig>): void {
    if (this.checkIntervalId) {
      this.stop();
    }

    this.config = { ...DEFAULT_ALERT_CONFIG, ...config };

    if (typeof window !== 'undefined') {
      // Initial check after 1 minute
      setTimeout(() => this.checkMetrics(), 60 * 1000);

      // Periodic checks
      this.checkIntervalId = setInterval(
        () => this.checkMetrics(),
        this.config.checkInterval,
      );
    }
  }

  stop(): void {
    if (this.checkIntervalId) {
      clearInterval(this.checkIntervalId);
      this.checkIntervalId = null;
    }
  }

  private checkMetrics(): void {
    const metrics = validationMetrics.getMetrics();

    // Skip if not enough validations
    if (metrics.totalValidations < this.config.minValidationsForAlert) {
      return;
    }

    // Check failure rate threshold
    if (metrics.failureRate > this.config.failureRateThreshold) {
      const now = Date.now();
      
      // Cooldown to avoid alert spam
      if (now - this.lastAlertTime < this.alertCooldown) {
        return;
      }

      this.lastAlertTime = now;

      // Log alert with structured context
      logger.error(
        '[API Validation Alert] High validation failure rate detected',
        {
          alert_type: 'high_validation_failure_rate',
          failure_rate: metrics.failureRate.toFixed(2),
          threshold: this.config.failureRateThreshold,
          total_validations: metrics.totalValidations,
          failed_validations: metrics.failedValidations,
          successful_validations: metrics.successfulValidations,
          last_failure_time: metrics.lastFailureTime,
          failures_by_endpoint: metrics.failuresByEndpoint,
          timestamp: new Date().toISOString(),
        },
      );

      // Also send to Sentry if configured (via logger.error)
    }
  }

  updateConfig(config: Partial<ValidationAlertConfig>): void {
    this.config = { ...this.config, ...config };
  }
}

// Singleton instance for validation alerting
export const validationAlerting = new ValidationAlerting();

// Start alerting in production (can be disabled via env var)
if (typeof window !== 'undefined' && import.meta.env.PROD) {
  const enableAlerting = import.meta.env.VITE_ENABLE_VALIDATION_ALERTING !== 'false';
  if (enableAlerting) {
    validationAlerting.start();
  }
}

/**
 * Client API avec interceptors pour refresh automatique des tokens,
 * unwrapping du format backend { success, data, error },
 * et retry automatique avec exponential backoff
 * Aligné avec FRONTEND_INTEGRATION.md
 */

// INT-API-004: Timeout configurations per endpoint type
export const API_TIMEOUTS = {
  DEFAULT: 10000, // 10 seconds - default timeout for normal requests
  UPLOAD: 300000, // 5 minutes - timeout for file uploads
  LONG_POLLING: 30000, // 30 seconds - timeout for long-polling requests
} as const;

// Client API réutilisable
export const apiClient = axios.create({
  baseURL: env.API_URL,
  timeout: API_TIMEOUTS.DEFAULT,
  headers: {
    'Content-Type': 'application/json',
  },
  // SECURITY: Activer withCredentials pour envoyer les cookies httpOnly automatiquement
  // Les cookies httpOnly sont set par le backend et envoyés automatiquement avec chaque requête
  withCredentials: true,
});

// Flag pour éviter les refresh en boucle
let isRefreshing = false;
let failedQueue: Array<{
  resolve: (value?: any) => void;
  reject: (error?: any) => void;
}> = [];

// Cache pour éviter les refresh proactifs multiples
let lastProactiveRefreshTime = 0;
const PROACTIVE_REFRESH_COOLDOWN_MS = 5000; // 5 secondes entre refresh proactifs

/**
 * Sleep utility function
 */
const sleep = (ms: number): Promise<void> => {
  return new Promise((resolve) => setTimeout(resolve, ms));
};

/**
 * Retry configuration
 */
interface RetryConfig {
  maxRetries: number;
  baseDelay: number;
  maxDelay: number;
  retryableStatusCodes: number[];
  retryableNetworkErrors: string[];
}

const DEFAULT_RETRY_CONFIG: RetryConfig = {
  maxRetries: 3,
  baseDelay: 1000, // 1 second
  maxDelay: 10000, // 10 seconds
  retryableStatusCodes: [500, 502, 503, 504], // Server errors, gateway errors (429 excluded - don't retry rate limits)
  retryableNetworkErrors: [
    'ECONNABORTED', // Timeout
    'ETIMEDOUT', // Timeout
    'ENOTFOUND', // DNS error
    'ECONNREFUSED', // Connection refused
    'ECONNRESET', // Connection reset
    'EAI_AGAIN', // DNS lookup failed
    'Network Error', // Generic network error
  ],
};

/**
 * Check if a request method is idempotent (safe to retry)
 */
const isIdempotentMethod = (method?: string): boolean => {
  const idempotentMethods = ['GET', 'HEAD', 'OPTIONS'];
  return method ? idempotentMethods.includes(method.toUpperCase()) : false;
};

/**
 * Check if an error is retryable
 */
const isRetryableError = (
  error: AxiosError,
  config: RetryConfig = DEFAULT_RETRY_CONFIG,
): boolean => {
  // Don't retry if request was cancelled
  if (axios.isCancel(error)) {
    return false;
  }

  // Check if retry is disabled for this request
  if ((error.config as any)?._disableRetry) {
    return false;
  }

  // Check status code
  if (error.response?.status) {
    return config.retryableStatusCodes.includes(error.response.status);
  }

  // Check network errors
  if (error.code) {
    return config.retryableNetworkErrors.includes(error.code);
  }

  // Check error message for network-related errors
  if (error.message) {
    const message = error.message.toLowerCase();
    const networkErrorPatterns = [
      'network',
      'timeout',
      'connection',
      'econn',
      'etimedout',
      'enotfound',
    ];
    return networkErrorPatterns.some((pattern) => message.includes(pattern));
  }

  // For errors without response (network errors), retry if it's an idempotent method
  if (!error.response && error.request) {
    return isIdempotentMethod(error.config?.method);
  }

  return false;
};

/**
 * Get retry delay from Retry-After header or use exponential backoff with jitter
 */
const getRetryDelay = (
  error: AxiosError,
  attempt: number,
  baseDelay: number = DEFAULT_RETRY_CONFIG.baseDelay,
  maxDelay: number = DEFAULT_RETRY_CONFIG.maxDelay,
): number => {
  // Check for Retry-After header (case-insensitive)
  const retryAfterHeader =
    error.response?.headers['retry-after'] ||
    error.response?.headers['Retry-After'];
  if (retryAfterHeader) {
    const delay = parseInt(String(retryAfterHeader), 10);
    if (!isNaN(delay) && delay > 0) {
      return Math.min(delay * 1000, maxDelay); // Convert to milliseconds, cap at maxDelay
    }
  }

  // Exponential backoff with jitter: baseDelay * 2^attempt + random(0, baseDelay)
  const exponentialDelay = baseDelay * Math.pow(2, attempt);
  const jitter = Math.random() * baseDelay; // Add jitter to avoid thundering herd
  return Math.min(exponentialDelay + jitter, maxDelay);
};

/**
 * Sanitize sensitive data from request/response for logging
 */
const sanitizeForLogging = (data: any): any => {
  if (!data || typeof data !== 'object') {
    return data;
  }

  const sensitiveKeys = [
    'password',
    'token',
    'access_token',
    'refresh_token',
    'secret',
    'authorization',
    'x-csrf-token',
  ];
  const sanitized = Array.isArray(data) ? [...data] : { ...data };

  for (const key in sanitized) {
    const lowerKey = key.toLowerCase();
    if (sensitiveKeys.some((sk) => lowerKey.includes(sk))) {
      sanitized[key] = '[REDACTED]';
    } else if (typeof sanitized[key] === 'object' && sanitized[key] !== null) {
      sanitized[key] = sanitizeForLogging(sanitized[key]);
    }
  }

  return sanitized;
};

/**
 * Get request ID from headers or generate one
 */
const getRequestId = (config: InternalAxiosRequestConfig): string => {
  // Try to get request_id from headers (if set by caller)
  const requestId =
    (config.headers as any)?.['X-Request-ID'] ||
    (config.headers as any)?.['x-request-id'] ||
    `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;

  // Store in config for later use
  (config as any)._requestId = requestId;

  return requestId;
};

// T0177: Fonction pour traiter la queue de requêtes en attente
const processQueue = (error: Error | null, token: string | null = null) => {
  failedQueue.forEach((prom) => {
    if (error) {
      prom.reject(error);
    } else {
      prom.resolve(token);
    }
  });

  failedQueue = [];
};

// T0177: Interceptor de requête pour ajouter le token d'accès
// CRITIQUE: Récupérer TOUJOURS le token frais depuis localStorage car Zustand peut ne pas être hydraté
apiClient.interceptors.request.use(
  async (config: InternalAxiosRequestConfig) => {
    // API Versioning: Add X-API-Version header to all requests
    if (config.headers) {
      config.headers['X-API-Version'] = env.API_VERSION;
    }

    // INT-AUTH-004: Vérifier l'expiration du token avant d'envoyer la requête
    // Buffer de 60 secondes pour éviter les 401 inutiles
    const PRE_REQUEST_REFRESH_BUFFER_MS = 60 * 1000; // 60 secondes

    const token = TokenStorage.getAccessToken();
    const isRefreshEndpoint = config.url?.includes('/auth/refresh');
    const isCSRFRoute = config.url?.includes('/csrf-token');

    // Ne pas vérifier l'expiration pour les endpoints de refresh et CSRF pour éviter les boucles
    if (token && !isRefreshEndpoint && !isCSRFRoute) {
      // Vérifier si le token expire bientôt (dans moins de 60s)
      if (isTokenExpiringSoon(token, PRE_REQUEST_REFRESH_BUFFER_MS)) {
        // Si un refresh est déjà en cours, attendre qu'il se termine
        if (isRefreshing) {
          logger.debug(
            '[API] Token expiring soon but refresh already in progress, waiting...',
            {
              url: config.url,
            },
          );
          // Attendre que le refresh se termine (max 5s)
          let waitCount = 0;
          while (isRefreshing && waitCount < 50) {
            await new Promise((resolve) => setTimeout(resolve, 100));
            waitCount++;
          }
          // Récupérer le nouveau token après le refresh
          const newToken = TokenStorage.getAccessToken();
          if (newToken && config.headers) {
            config.headers.Authorization = `Bearer ${newToken} `;
          }
        } else {
          // Vérifier le cooldown pour éviter les refresh proactifs multiples
          const now = Date.now();
          const timeSinceLastRefresh = now - lastProactiveRefreshTime;

          if (timeSinceLastRefresh < PROACTIVE_REFRESH_COOLDOWN_MS) {
            // Trop tôt depuis le dernier refresh, utiliser le token actuel
            logger.debug('[API] Skipping proactive refresh (cooldown)', {
              url: config.url,
              time_since_last_refresh_ms: timeSinceLastRefresh,
              cooldown_ms: PROACTIVE_REFRESH_COOLDOWN_MS,
            });
            if (token && config.headers) {
              config.headers.Authorization = `Bearer ${token} `;
            }
          } else {
            // Rafraîchir proactivement le token
            try {
              lastProactiveRefreshTime = now;
              logger.debug(
                '[API] Token expiring soon, refreshing proactively before request',
                {
                  url: config.url,
                  buffer_seconds: PRE_REQUEST_REFRESH_BUFFER_MS / 1000,
                },
              );
              await refreshToken();
              const newToken = TokenStorage.getAccessToken();
              if (newToken && config.headers) {
                config.headers.Authorization = `Bearer ${newToken} `;
              }
            } catch (refreshError) {
              // Si le refresh échoue, continuer avec le token actuel
              // L'interceptor de réponse gérera l'erreur 401 si nécessaire
              logger.warn(
                '[API] Proactive token refresh failed, continuing with current token',
                {
                  url: config.url,
                  error: refreshError,
                },
              );
              if (token && config.headers) {
                config.headers.Authorization = `Bearer ${token} `;
              }
            }
          }
        }
      } else {
        // Token valide, utiliser normalement
        if (config.headers) {
          config.headers.Authorization = `Bearer ${token} `;
        }
      }
    } else if (token && config.headers) {
      // Token présent mais endpoint de refresh/CSRF, utiliser sans vérification
      config.headers.Authorization = `Bearer ${token} `;
    }

    // Pour FormData, laisser Axios gérer automatiquement le Content-Type avec boundary
    // Ne pas forcer application/json si c'est un FormData
    if (config.data instanceof FormData && config.headers) {
      // Supprimer Content-Type pour que Axios calcule automatiquement multipart/form-data avec boundary
      delete config.headers['Content-Type'];
    }

    // CRITIQUE FIX #25: Ajouter le token CSRF pour toutes les requêtes mutantes (POST, PUT, DELETE, PATCH)
    // Le token CSRF est requis pour toutes les requêtes qui modifient l'état côté serveur
    const method = config.method?.toUpperCase();
    const isStateChanging = ['POST', 'PUT', 'DELETE', 'PATCH'].includes(
      method || '',
    );
    // isCSRFRoute déjà défini plus haut

    const isAuthRoute =
      config.url?.includes('/auth/login') ||
      config.url?.includes('/auth/register');

    if (
      isStateChanging &&
      !isCSRFRoute &&
      !isAuthRoute &&
      token &&
      config.headers
    ) {
      // CRITIQUE FIX #25: S'assurer que le token CSRF est toujours présent pour les requêtes mutantes
      // Si le token n'est pas disponible, en récupérer un nouveau avant d'envoyer la requête
      let csrfToken = csrfService.getToken();
      if (!csrfToken) {
        // Si pas de token, essayer d'en récupérer un nouveau de manière synchrone si possible
        // Sinon, l'interceptor de réponse gérera le retry avec nouveau token après une erreur 403
        try {
          csrfToken = await csrfService.ensureToken();
        } catch (error) {
          // Si la récupération échoue, continuer sans token - l'interceptor de réponse gérera le retry
          logger.warn(
            '[API] Failed to fetch CSRF token before request, will retry on 403',
            {
              url: config.url,
              method: config.method,
            },
          );
        }
      }

      if (csrfToken && config.headers) {
        config.headers['X-CSRF-Token'] = csrfToken;
      }
    }

    // Support AbortController: si un signal est fourni dans la config, l'utiliser
    // Sinon, créer un nouveau AbortController si nécessaire
    if (!config.signal && !config.cancelToken) {
      // Si aucune annulation n'est configurée, on peut créer un AbortController optionnel
      // Mais on ne le fait pas automatiquement pour éviter de créer des signaux inutiles
      // Les utilisateurs peuvent passer un signal via config.signal
    }

    // FE-TYPE-003: Validate request data if schema is provided
    // Action 1.2.1.5: Ensure all requests are validated when schema is provided
    const requestSchema = (config as any)?._requestSchema as
      | z.ZodSchema
      | undefined;
    if (requestSchema && config.data !== undefined && config.data !== null) {
      // Skip validation for FormData (file uploads) - they are validated separately
      if (!(config.data instanceof FormData)) {
        const validation = safeValidateApiRequest(requestSchema, config.data);
        if (!validation.success) {
          const requestId = getRequestId(config);
          // Log validation error with structured logging
          logger.warn(
            '[API Request Validation Error]',
            {
              request_id: requestId,
              url: config.url,
              method: config.method?.toUpperCase(),
              errors: validation.error?.errors.map((e) => ({
                path: e.path.join('.'),
                message: e.message,
                code: e.code,
              })),
            },
            validation.error,
          );

          // Throw error to prevent invalid request from being sent
          // This ensures data integrity before sending to backend
          const errorMessages =
            validation.error?.errors
              .map((e) => `${e.path.join('.')}: ${e.message}`)
              .join(', ') || 'Request validation failed';
          throw new Error(`Request validation failed: ${errorMessages}`);
        }
        // Use validated data (Zod may transform/coerce values)
        config.data = validation.data;
      }
    }

    // Store request start time for duration calculation
    (config as any)._requestStartTime = Date.now();

    // Log request (only in development or if explicitly enabled)
    if (import.meta.env.DEV || (config as any)?._enableLogging) {
      const requestId = getRequestId(config);
      const sanitizedHeaders = sanitizeForLogging({ ...config.headers });
      const sanitizedData = sanitizeForLogging(config.data);

      logger.debug(`[API Request] ${method || 'GET'} ${config.url}`, {
        request_id: requestId,
        method: method || 'GET',
        url: config.url,
        baseURL: config.baseURL,
        headers: sanitizedHeaders,
        params: config.params,
        data: sanitizedData,
        timeout: config.timeout,
        signal: config.signal ? 'AbortController' : undefined,
      });
    }

    return config;
  },
  (error) => {
    // Log request error
    if (import.meta.env.DEV) {
      logger.error('[API Request Error]', {
        error: error.message,
        config: error.config
          ? {
              url: error.config.url,
              method: error.config.method,
            }
          : undefined,
      });
    }
    return Promise.reject(error);
  },
);

// Interceptor de réponse pour unwrap le format backend et gérer les erreurs
apiClient.interceptors.response.use(
  (response: AxiosResponse<ApiResponse<any> | any>) => {
    // FIX #22: Extraire le request_id depuis les headers de réponse pour corrélation
    const requestIdFromHeader =
      response.headers['x-request-id'] || response.headers['X-Request-ID'];
    const requestId =
      requestIdFromHeader || (response.config as any)?._requestId;

    // Mettre à jour le contexte global du logger avec le request_id
    if (requestId) {
      setLogContext({ request_id: requestId });
    }

    // Action 5.4.1.1: Parse rate limit headers
    const rateLimitLimit =
      response.headers['x-ratelimit-limit'] ||
      response.headers['X-RateLimit-Limit'];
    const rateLimitRemaining =
      response.headers['x-ratelimit-remaining'] ||
      response.headers['X-RateLimit-Remaining'];
    const rateLimitReset =
      response.headers['x-ratelimit-reset'] ||
      response.headers['X-RateLimit-Reset'];

    if (rateLimitLimit || rateLimitRemaining || rateLimitReset) {
      useRateLimitStore.getState().updateRateLimit({
        limit: rateLimitLimit,
        remaining: rateLimitRemaining,
        reset: rateLimitReset,
        retryAfter: null, // Not set on successful responses
      });
    }

    // Log successful response (only in development or if explicitly enabled)
    const shouldLog =
      import.meta.env.DEV || (response.config as any)?._enableLogging;

    if (shouldLog) {
      const sanitizedData = sanitizeForLogging(response.data);
      const sanitizedHeaders = sanitizeForLogging(response.headers);

      logger.debug(
        `[API Response] ${response.config.method?.toUpperCase() || 'GET'} ${response.config.url} ${response.status}`,
        {
          request_id: requestId,
          status: response.status,
          statusText: response.statusText,
          headers: sanitizedHeaders,
          data: sanitizedData,
          duration: (response.config as any)?._requestStartTime
            ? Date.now() - (response.config as any)._requestStartTime
            : undefined,
        },
      );
    }

    // API Versioning: Check for deprecation warning
    const deprecatedHeader =
      response.headers['x-api-deprecated'] ||
      response.headers['X-API-Deprecated'];
    if (deprecatedHeader === 'true') {
      // Show deprecation warning (only once per session to avoid spam)
      const deprecationKey = 'api_deprecation_warning_shown';
      if (typeof window !== 'undefined' && !sessionStorage.getItem(deprecationKey)) {
        const sunsetDate = response.headers['sunset'] || response.headers['Sunset'];
        const message = sunsetDate
          ? `This API version is deprecated and will be removed on ${sunsetDate}. Please update to the latest version.`
          : 'This API version is deprecated. Please update to the latest version.';
        
        // Use toast with warning icon (react-hot-toast doesn't have toast.warning)
        toast(message, {
          icon: '⚠️',
          duration: 10000, // Show for 10 seconds
        });
        
        sessionStorage.setItem(deprecationKey, 'true');
        
        logger.warn('[API] Deprecated API version detected', {
          url: response.config.url,
          version: response.headers['x-api-version'] || response.headers['X-API-Version'],
          sunset_date: sunsetDate,
        });
      }
    }

    // Backend peut retourner plusieurs formats :
    // 1. Format standard avec wrapper: { success: true, data: {...} }
    // 2. Format direct JSON: { tracks: [...], pagination: {...} } (ex: SearchTracks, ListTracks)
    // 3. Format avec message: { success: true, data: {...}, message: "..." }

    if (!response.data || typeof response.data !== 'object') {
      // Si response.data n'est pas un objet, retourner tel quel
      return response;
    }

    // FE-COMP-005: Show success toast for mutation operations if enabled
    const method = response.config.method?.toUpperCase();
    const isMutation = ['POST', 'PUT', 'PATCH', 'DELETE'].includes(
      method || '',
    );
    const shouldShowSuccessToast =
      isMutation &&
      (response.config as any)?._showSuccessToast &&
      typeof window !== 'undefined';

    if (shouldShowSuccessToast) {
      const successMessage =
        (response.config as any)?._successMessage ||
        (response.data as any)?.message ||
        getDefaultSuccessMessage(method || '');

      if (successMessage) {
        toast.success(successMessage);
      }
    }

    // FE-API-017: Cache GET responses
    if (method === 'GET' && !(response.config as any)?._disableCache) {
      responseCache.set(response.config, response);
    }

    // FE-API-017: Invalidate cache on mutations
    // FE-STATE-004: Invalidate state after mutations
    if (isMutation) {
      const url = response.config.url || '';
      const method = response.config.method || 'POST';

      // Use centralized invalidation system
      invalidateStateAfterMutation(url, method);
    }

    // INT-API-002: Vérifier si c'est le format wrapper avec success
    if ('success' in response.data) {
      if (response.data.success === true) {
        // Format wrapper standard: { success: true, data: {...} }
        // On unwrap pour retourner directement data
        // Si data est null/undefined, on retourne null au lieu de undefined
        const unwrappedData =
          response.data.data !== undefined ? response.data.data : null;

        // FE-TYPE-002: Validate response data if schema is provided
        // Action 1.2.2.1: Enhanced response validation for all responses with schemas
        const responseSchema = (response.config as any)?._responseSchema as
          | z.ZodSchema
          | undefined;
        if (responseSchema && unwrappedData !== null) {
          const validation = safeValidateApiResponse(
            responseSchema,
            unwrappedData,
          );
          if (!validation.success) {
            const requestId = getRequestId(response.config);
            // Action 1.2.2.2: Production error logging for validation failures
            // Enhanced logging with structured error details for production monitoring
            const validationErrorContext = {
              request_id: requestId,
              url: response.config.url,
              method: response.config.method?.toUpperCase(),
              status: response.status,
              error_type: 'api_response_validation_failed',
              validation_errors: validation.error?.errors.map((e) => ({
                path: e.path.join('.'),
                message: e.message,
                code: e.code,
                received: e.code === 'invalid_type' ? e.received : undefined,
                expected: e.code === 'invalid_type' ? e.expected : undefined,
              })),
              response_data_preview: JSON.stringify(unwrappedData).substring(0, 200),
              schema_provided: !!responseSchema,
              timestamp: new Date().toISOString(),
            };
            
            // Log to structured logger (sends to backend endpoint and Sentry in production)
            logger.error(
              '[API Response Validation Failed]',
              validationErrorContext,
              validation.error,
            );
            
            // Action 1.2.2.3: Track validation failure metrics
            validationMetrics.recordFailure(response.config.url);
            
            // Action 1.2.2.5: Validation error recovery mechanism
            const recoveryConfig = (response.config as any)?._validationRecovery as
              | { useCache?: boolean; retry?: boolean; notifyUser?: boolean }
              | undefined;
            const useCache = recoveryConfig?.useCache !== false; // Default: true
            const retry = recoveryConfig?.retry === true; // Default: false (safety)
            const notifyUser = recoveryConfig?.notifyUser !== false; // Default: true
            
            // Recovery 1: Try cached response (for GET requests only)
            if (useCache && method === 'GET') {
              const cachedResponse = responseCache.get(response.config);
              if (cachedResponse) {
                // Cached response may be in wrapped or direct format
                // For wrapped format, unwrap it first
                let cachedData = cachedResponse.data;
                if (cachedData && typeof cachedData === 'object' && 'success' in cachedData && cachedData.success === true) {
                  cachedData = (cachedData as any).data !== undefined ? (cachedData as any).data : null;
                }
                
                // Validate cached response before using it
                if (cachedData !== null) {
                  const cachedValidation = safeValidateApiResponse(responseSchema, cachedData);
                  if (cachedValidation.success) {
                    logger.warn(
                      '[API Validation Recovery] Using cached response due to validation failure',
                      {
                        request_id: requestId,
                        url: response.config.url,
                        recovery_type: 'cache_fallback',
                      },
                    );
                    
                    if (notifyUser && typeof window !== 'undefined') {
                      toast('Data may be outdated. Please refresh if issues persist.', {
                        icon: '⚠️',
                        duration: 5000,
                      });
                    }
                    
                    // Return cached response with unwrapped data (matching current format)
                    return {
                      ...cachedResponse,
                      data: cachedData,
                    } as AxiosResponse<any>;
                  }
                }
              }
            }
            
            // Recovery 2: Optional retry (only if explicitly enabled)
            if (retry && !(response.config as any)?._validationRetryAttempted) {
              (response.config as any)._validationRetryAttempted = true;
              logger.warn(
                '[API Validation Recovery] Retrying request due to validation failure',
                {
                  request_id: requestId,
                  url: response.config.url,
                  recovery_type: 'retry',
                },
              );
              
              // Retry the request (will go through the same validation again)
              return apiClient.request(response.config);
            }
            
            // Continue with unvalidated data (graceful degradation)
            // In production, this allows the app to continue functioning even if
            // the backend response doesn't match the expected schema (e.g., during migrations)
            // Validation errors are logged for monitoring and alerting
            if (notifyUser && typeof window !== 'undefined') {
              toast('Some data may be incomplete. Please refresh if issues persist.', {
                icon: '⚠️',
                duration: 5000,
              });
            }
          } else {
            // Log successful validation in debug mode
            const requestId = getRequestId(response.config);
            logger.debug('[API Response Validation Success]', {
              request_id: requestId,
              url: response.config.url,
            });
            
            // Action 1.2.2.3: Track validation success metrics
            validationMetrics.recordSuccess(response.config.url);
          }
        }

        return {
          ...response,
          data: unwrappedData,
        } as AxiosResponse<any>;
      }

      // INT-API-002: Si success === false, traiter comme une erreur même si status est 200
      // Le backend peut retourner { success: false, error: {...} } avec un status 200 dans certains cas
      if (response.data.success === false) {
        const errorData = response.data.error || response.data;
        logger.error('[API] Response with success=false:', {
          url: response.config.url,
          error: errorData,
        });

        // Créer une erreur Axios pour que l'interceptor d'erreur la gère
        // Format attendu par parseApiError: { success: false, error: {...} }
        const axiosError = new AxiosError<ApiResponse<any>>(
          errorData?.message || 'Request failed',
          'API_ERROR',
          response.config,
          response.request,
          {
            ...response,
            status: response.status || 400, // Utiliser le status de la réponse ou 400 par défaut
            statusText: response.statusText || 'Bad Request',
            data: {
              success: false,
              error: errorData,
            },
          } as AxiosResponse<ApiResponse<any>>,
        );

        // Rejeter pour que l'interceptor d'erreur gère cette erreur
        // parseApiError détectera automatiquement le format { success: false, error: {...} }
        return Promise.reject(axiosError);
      }
    }

    // Action 1.3.2.2: Removed dual-format handling - backend now always returns wrapped format
    // If we receive a response without 'success' field, log a warning (should not happen)
    if (response.data && typeof response.data === 'object' && !('success' in response.data)) {
      const requestId = getRequestId(response.config);
      logger.warn(
        '[API] Received non-wrapped response format (unexpected)',
        {
          request_id: requestId,
          url: response.config.url,
          method: response.config.method?.toUpperCase(),
          status: response.status,
          response_preview: JSON.stringify(response.data).substring(0, 200),
          timestamp: new Date().toISOString(),
        },
      );
      // Continue with the response as-is (graceful degradation)
      // This should not happen if backend is properly deployed
    }

    return response;
  },
  async (error: AxiosError<ApiResponse<any>>) => {
    // Don't retry or process cancelled requests
    if (axios.isCancel(error)) {
      const requestId = (error.config as any)?._requestId;
      if (import.meta.env.DEV || (error.config as any)?._enableLogging) {
        logger.debug(
          `[API Request Cancelled] ${error.config?.method?.toUpperCase() || 'GET'} ${error.config?.url}`,
          {
            request_id: requestId,
          },
        );
      }
      return Promise.reject(error);
    }

    const originalRequest = error.config as InternalAxiosRequestConfig & {
      _retry?: boolean;
    };

    // FIX #22: Extraire le request_id depuis les headers de réponse d'erreur pour corrélation
    let requestId = (originalRequest as any)?._requestId;
    if (error.response?.headers) {
      const requestIdFromHeader =
        error.response.headers['x-request-id'] ||
        error.response.headers['X-Request-ID'];
      if (requestIdFromHeader) {
        requestId = requestIdFromHeader;
        // Mettre à jour le contexte global du logger avec le request_id
        setLogContext({ request_id: requestId });
      }

      // Action 5.4.1.1: Parse rate limit headers from error response
      const rateLimitLimit =
        error.response.headers['x-ratelimit-limit'] ||
        error.response.headers['X-RateLimit-Limit'];
      const rateLimitRemaining =
        error.response.headers['x-ratelimit-remaining'] ||
        error.response.headers['X-RateLimit-Remaining'];
      const rateLimitReset =
        error.response.headers['x-ratelimit-reset'] ||
        error.response.headers['X-RateLimit-Reset'];
      const retryAfter =
        error.response.headers['retry-after'] ||
        error.response.headers['Retry-After'];

      if (
        rateLimitLimit ||
        rateLimitRemaining ||
        rateLimitReset ||
        retryAfter
      ) {
        useRateLimitStore.getState().updateRateLimit({
          limit: rateLimitLimit,
          remaining: rateLimitRemaining,
          reset: rateLimitReset,
          retryAfter: retryAfter,
        });
      }
    }

    // Log error response (only in development or if explicitly enabled)
    const shouldLog =
      import.meta.env.DEV || (originalRequest as any)?._enableLogging;

    if (shouldLog && error.response) {
      const sanitizedErrorData = sanitizeForLogging(error.response.data);
      const sanitizedHeaders = sanitizeForLogging(error.response.headers);

      logger.error(
        `[API Error Response] ${originalRequest?.method?.toUpperCase() || 'GET'} ${originalRequest?.url} ${error.response.status}`,
        {
          request_id: requestId,
          status: error.response.status,
          statusText: error.response.statusText,
          headers: sanitizedHeaders,
          data: sanitizedErrorData,
          duration: (originalRequest as any)?._requestStartTime
            ? Date.now() - (originalRequest as any)._requestStartTime
            : undefined,
        },
      );
    } else if (shouldLog && error.request && !error.response) {
      // Network error (no response received)
      logger.error(
        `[API Network Error] ${originalRequest?.method?.toUpperCase() || 'GET'} ${originalRequest?.url}`,
        {
          request_id: requestId,
          message: error.message,
          code: error.code,
          duration: (originalRequest as any)?._requestStartTime
            ? Date.now() - (originalRequest as any)._requestStartTime
            : undefined,
        },
      );
    }

    // INT-AUTH-003: Détecter 401 et refresh automatiquement
    // EXCLURE l'endpoint /auth/refresh pour éviter les boucles infinies
    const isRefreshEndpoint = originalRequest?.url?.includes('/auth/refresh');

    // INT-AUTH-003: Handle 401 on /auth/refresh endpoint - token expired/revoked, logout and redirect
    if (error.response?.status === 401 && isRefreshEndpoint) {
      logger.error(
        '[API] 401 on /auth/refresh - refresh token expired or revoked, logging out',
        {
          request_id: requestId,
          url: originalRequest?.url,
        },
      );

      // Clear tokens
      TokenStorage.clearTokens();

      // Clear CSRF token
      csrfService.clearToken();

      // Clear auth store state
      if (typeof window !== 'undefined') {
        // Import and use auth store to clear state
        import('@/features/auth/store/authStore')
          .then(({ useAuthStore }) => {
            const store = useAuthStore.getState();
            store.logout().catch((err: unknown) => {
              logger.error(
                '[API] Failed to logout from store after refresh token 401',
                { error: err },
              );
            });
          })
          .catch((err: unknown) => {
            logger.error('[API] Failed to import auth store for logout', {
              error: err,
            });
          });

        // Store error message for display after redirect
        sessionStorage.setItem(
          'auth_error',
          'Votre session a expiré. Veuillez vous reconnecter.',
        );
        // Redirect to login
        window.location.href = '/login';
      }

      return Promise.reject(parseApiError(error));
    }

    if (
      error.response?.status === 401 &&
      originalRequest &&
      !originalRequest._retry &&
      !isRefreshEndpoint
    ) {
      // INT-AUTH-003: Éviter les refresh multiples simultanés
      if (isRefreshing) {
        // Si un refresh est en cours, mettre la requête en queue
        logger.debug('[API] Refresh already in progress, queuing request', {
          request_id: requestId,
          url: originalRequest?.url,
          queue_size: failedQueue.length,
        });
        return new Promise((resolve, reject) => {
          failedQueue.push({ resolve, reject });
        })
          .then((token) => {
            if (originalRequest.headers && token) {
              originalRequest.headers.Authorization = `Bearer ${token} `;
            }
            logger.debug(
              '[API] Replaying queued request after successful refresh',
              {
                request_id: requestId,
                url: originalRequest?.url,
              },
            );
            return apiClient(originalRequest);
          })
          .catch((err) => {
            logger.error('[API] Queued request failed after refresh', {
              request_id: requestId,
              url: originalRequest?.url,
              error: err,
            });
            return Promise.reject(err);
          });
      }

      originalRequest._retry = true;
      isRefreshing = true;

      logger.info('[API] Starting token refresh due to 401', {
        request_id: requestId,
        url: originalRequest?.url,
        method: originalRequest?.method,
      });

      try {
        // INT-AUTH-003: Refresh automatique du token
        await refreshToken();
        const newToken = TokenStorage.getAccessToken();

        if (!newToken) {
          throw new Error('Failed to get new access token after refresh');
        }

        logger.info(
          '[API] Token refresh successful, retrying original request',
          {
            request_id: requestId,
            url: originalRequest?.url,
            queue_size: failedQueue.length,
          },
        );

        if (originalRequest.headers) {
          originalRequest.headers.Authorization = `Bearer ${newToken} `;
        }

        // INT-AUTH-003: Traiter la queue et retry la requête originale
        // Toutes les requêtes en queue seront rejouées avec le nouveau token
        processQueue(null, newToken);
        return apiClient(originalRequest);
      } catch (refreshError) {
        // INT-AUTH-003: Gérer cas refresh échoué (expiration, révocation, erreur réseau)
        logger.error('[API] Token refresh failed, logging out', {
          request_id: requestId,
          error: refreshError,
          queue_size: failedQueue.length,
        });

        // Rejeter toutes les requêtes en queue
        processQueue(refreshError as Error, null);

        // Nettoyer les tokens
        TokenStorage.clearTokens();

        // Clear CSRF token
        csrfService.clearToken();

        // INT-AUTH-003: Clear auth store state and redirect to login
        if (typeof window !== 'undefined') {
          // Import and use auth store to clear state
          import('@/features/auth/store/authStore')
            .then(({ useAuthStore }) => {
              const store = useAuthStore.getState();
              store.logout().catch((err: unknown) => {
                logger.error(
                  '[API] Failed to logout from store after refresh failure',
                  { error: err },
                );
              });
            })
            .catch((err: unknown) => {
              logger.error('[API] Failed to import auth store for logout', {
                error: err,
              });
            });

          // Stocker un message d'erreur pour l'afficher après redirection
          sessionStorage.setItem(
            'auth_error',
            'Votre session a expiré. Veuillez vous reconnecter.',
          );
          // Rediriger vers login si refresh échoue (seulement dans le navigateur)
          window.location.href = '/login';
        }

        return Promise.reject(refreshError);
      } finally {
        isRefreshing = false;
        logger.debug('[API] Token refresh process completed', {
          request_id: requestId,
          is_refreshing: false,
        });
      }
    }

    // INT-AUTH-001: Détecter erreurs CSRF (403 avec message CSRF) et retry avec nouveau token
    const isCSRFError =
      error.response?.status === 403 &&
      originalRequest &&
      !(originalRequest as any)?._csrfRetry &&
      error.response?.data &&
      typeof error.response.data === 'object' &&
      ((error.response.data as any)?.error?.message
        ?.toLowerCase()
        .includes('csrf') ||
        (error.response.data as any)?.message?.toLowerCase().includes('csrf'));

    if (isCSRFError) {
      const method = originalRequest.method?.toUpperCase();
      const isStateChanging = ['POST', 'PUT', 'DELETE', 'PATCH'].includes(
        method || '',
      );

      if (isStateChanging) {
        (originalRequest as any)._csrfRetry = true;

        try {
          // Récupérer un nouveau token CSRF
          const newCsrfToken = await csrfService.refreshToken();

          if (originalRequest.headers && newCsrfToken) {
            originalRequest.headers['X-CSRF-Token'] = newCsrfToken;
          }

          // Retry la requête avec le nouveau token
          return apiClient(originalRequest);
        } catch (csrfError) {
          logger.error('[API] Failed to refresh CSRF token after CSRF error', {
            error: csrfError,
          });
          // Si on ne peut pas récupérer le token, rejeter l'erreur originale
          const apiError = parseApiError(error);
          return Promise.reject(apiError);
        }
      }
    }

    // INT-API-005: Unified retry logic for all retryable errors
    const status = error.response?.status;
    const retryCount = (originalRequest as any)?._retryCount || 0;
    const maxRetries = DEFAULT_RETRY_CONFIG.maxRetries;

    // INT-API-005: For 429 rate limit errors, don't retry - respect the rate limit
    const isRateLimitError = status === 429;
    // Don't retry 429 errors - respect the rate limit and show error immediately
    if (isRateLimitError) {
      const apiError = parseApiError(error);
      // Extract retry-after header if present
      const retryAfter =
        error.response?.headers['retry-after'] ||
        error.response?.headers['Retry-After'];
      const retryAfterSeconds = retryAfter ? parseInt(retryAfter, 10) : 60;

      logger.warn('[API] Rate limit exceeded, not retrying', {
        url: originalRequest?.url,
        retry_after: retryAfterSeconds,
        request_id: apiError.request_id,
      });

      // Show user-friendly error message
      if (apiError.message) {
        toast.error(apiError.message, {
          duration: retryAfterSeconds * 1000, // Show for the retry-after duration
        });
      }

      return Promise.reject(apiError);
    }

    const effectiveMaxRetries = maxRetries; // Use default max retries for other errors

    // Check if error is retryable
    if (
      isRetryableError(error, DEFAULT_RETRY_CONFIG) &&
      originalRequest &&
      retryCount < effectiveMaxRetries
    ) {
      // For non-idempotent methods (POST, PUT, DELETE, PATCH), only retry on specific errors
      const method = originalRequest.method?.toUpperCase();
      const isIdempotent = isIdempotentMethod(method);

      // For non-idempotent methods, only retry on network errors or 5xx errors
      // (429 rate limit errors are handled above and don't retry)
      if (
        !isIdempotent &&
        status &&
        status !== 500 &&
        status !== 502 &&
        status !== 503 &&
        status !== 504
      ) {
        // Don't retry non-idempotent methods on client errors (except 429 and 5xx)
        const apiError = parseApiError(error);
        return Promise.reject(apiError);
      }

      // Mark that we're retrying this request
      (originalRequest as any)._retryCount = retryCount + 1;

      // Calculate delay (exponential backoff with jitter)
      const delay = getRetryDelay(
        error,
        retryCount,
        DEFAULT_RETRY_CONFIG.baseDelay,
        DEFAULT_RETRY_CONFIG.maxDelay,
      );

      // Log retry attempt with request_id if available
      const apiError = parseApiError(error);
      const errorType = status
        ? `HTTP ${status}`
        : error.code || 'Network Error';

      // Log retry attempt
      if (apiError.request_id) {
        logger.warn(
          `[API Retry] ${errorType} error, retrying (${retryCount + 1}/${effectiveMaxRetries}) - Request ID: ${apiError.request_id}`,
          {
            status: status || 'N/A',
            error_code: error.code || 'N/A',
            retry_count: retryCount + 1,
            max_retries: effectiveMaxRetries,
            delay_ms: Math.round(delay),
            request_id: apiError.request_id,
            url: originalRequest?.url,
            method: originalRequest?.method,
            is_idempotent: isIdempotent,
          },
        );
      } else {
        logger.warn(
          `[API Retry] ${errorType} error, retrying (${retryCount + 1}/${effectiveMaxRetries})`,
          {
            status: status || 'N/A',
            error_code: error.code || 'N/A',
            retry_count: retryCount + 1,
            max_retries: effectiveMaxRetries,
            delay_ms: Math.round(delay),
            url: originalRequest?.url,
            method: originalRequest?.method,
            is_idempotent: isIdempotent,
          },
        );
      }

      // Wait before retrying
      return sleep(delay).then(() => {
        // Retry the request
        return apiClient(originalRequest);
      });
    }

    // INT-API-005: If already retried effectiveMaxRetries times or error is not retryable, reject immediately
    // Reuse the same effectiveMaxRetries calculation from above
    if (retryCount >= effectiveMaxRetries) {
      const apiError = parseApiError(error);
      const errorType = status
        ? `HTTP ${status}`
        : error.code || 'Network Error';

      // Log final error with request_id after all retries failed
      if (apiError.request_id) {
        logger.error(
          `[API Error] ${errorType} error after ${maxRetries} retries - Request ID: ${apiError.request_id}`,
          {
            code: apiError.code,
            message: apiError.message,
            request_id: apiError.request_id,
            timestamp: apiError.timestamp,
            url: originalRequest?.url,
            method: originalRequest?.method,
          },
        );
      } else {
        logger.error(
          `[API Error] ${errorType} error after ${maxRetries} retries`,
          {
            code: apiError.code,
            message: apiError.message,
            timestamp: apiError.timestamp,
            url: originalRequest?.url,
            method: originalRequest?.method,
          },
        );
      }

      return Promise.reject(apiError);
    }

    // Parser l'erreur en ApiError standardisé pour les autres codes
    const apiError = parseApiError(error);

    // Action 3.2.1.4: Auth errors redirect to login
    // Handle 401 errors that didn't trigger refresh (e.g., no originalRequest, already retried, etc.)
    if (status === 401 && !isRefreshEndpoint && typeof window !== 'undefined') {
      const errorCategory = getErrorCategory(apiError);
      if (errorCategory === 'authentication') {
        // Clear tokens
        TokenStorage.clearTokens();
        csrfService.clearToken();

        // Clear auth store state
        import('@/features/auth/store/authStore')
          .then(({ useAuthStore }) => {
            const store = useAuthStore.getState();
            store.logout().catch((err: unknown) => {
              logger.error('[API] Failed to logout from store after 401', {
                error: err,
              });
            });
          })
          .catch((err: unknown) => {
            logger.error('[API] Failed to import auth store for logout', {
              error: err,
            });
          });

        // Store error message for display after redirect
        sessionStorage.setItem(
          'auth_error',
          'Votre session a expiré. Veuillez vous reconnecter.',
        );
        // Redirect to login
        window.location.href = '/login';
      }
    }

    // FE-COMP-005: Show toast notification for API errors (unless disabled)
    const shouldShowToast =
      !(originalRequest as any)?._disableToast &&
      status !== 401 && // Don't show toast for 401 (handled by refresh/redirect)
      status !== 404 && // Don't show toast for 404 (handled by router)
      !axios.isCancel(error); // Don't show toast for cancelled requests

    // FIX: Implement toast throttling for network errors to prevent spam
    const isNetworkError = !error.response;

    // Track network errors for offline indicator
    if (isNetworkError) {
      const { recordNetworkError } = await import(
        '@/utils/networkErrorTracker'
      );
      recordNetworkError(apiError);
    }
    const toastId = isNetworkError ? 'network-error-toast' : undefined;

    if (shouldShowToast && typeof window !== 'undefined') {
      const url = originalRequest?.url || '';
      let context:
        | 'auth'
        | 'track'
        | 'playlist'
        | 'upload'
        | 'conversation'
        | 'search'
        | undefined;
      if (url.includes('/auth/')) {
        context = 'auth';
      } else if (url.includes('/tracks') || url.includes('/track/')) {
        context = 'track';
      } else if (url.includes('/playlists') || url.includes('/playlist/')) {
        context = 'playlist';
      } else if (url.includes('/upload')) {
        context = 'upload';
      } else if (url.includes('/conversations') || url.includes('/chat')) {
        context = 'conversation';
      } else if (url.includes('/search')) {
        context = 'search';
      }

      const includeDetails = status === 422;
      const errorMessage = formatUserFriendlyError(
        apiError,
        context,
        includeDetails,
      );

      // FE-API-015: Queue request for offline replay if it's a network error
      if (
        !error.response &&
        originalRequest &&
        offlineQueue.shouldQueueRequest(originalRequest)
      ) {
        const isOffline = typeof navigator !== 'undefined' && !navigator.onLine;
        if (isOffline || (!error.response && error.request)) {
          // Determine priority based on request type
          const method = originalRequest.method?.toUpperCase();
          const priority =
            method === 'DELETE' ? 'low' : method === 'POST' ? 'high' : 'normal';

          try {
            await offlineQueue.queueRequest(originalRequest, { priority });
            // Show info toast that request was queued
            toast.success(
              "Requête mise en file d'attente. Elle sera envoyée à la reconnexion.",
              {
                duration: 4000,
                id: 'offline-queue-toast', // Prevent duplicate queue toasts
              },
            );
          } catch (queueError) {
            logger.error('[API] Failed to queue request for offline replay', {
              error: queueError,
            });
          }
        }
      }

      // Use a fixed ID for network errors to prevent stacking
      // For network errors, show a more helpful message with suggestions
      let enhancedMessage = errorMessage;
      if (isNetworkError) {
        enhancedMessage = `${errorMessage} 💡 Vérifiez votre connexion internet. Si le problème persiste, le serveur pourrait être temporairement indisponible.`;
      }

      toast.error(enhancedMessage, {
        duration: 8000, // Longer duration for network errors to read suggestions
        id: toastId, // Use fixed ID if it's a network error
      });
    }

    // FIX #18, #22: Utiliser logger structuré avec request_id pour corrélation
    logger.error(`[API Error] ${apiError.message}`, {
      request_id: apiError.request_id || requestId,
      code: apiError.code,
      message: apiError.message,
      timestamp: apiError.timestamp,
      details: apiError.details,
      context: apiError.context,
      url: originalRequest?.url,
      method: originalRequest?.method,
    });

    return Promise.reject(apiError);
  },
);

/**
 * FE-COMP-005: Get default success message based on HTTP method
 */
function getDefaultSuccessMessage(method: string): string {
  switch (method) {
    case 'POST':
      return 'Opération réussie';
    case 'PUT':
    case 'PATCH':
      return 'Modification réussie';
    case 'DELETE':
      return 'Suppression réussie';
    default:
      return '';
  }
}

/**
 * Helper function to create a cancellable request
 * Returns an object with the request promise and an abort function
 *
 * @example
 * ```typescript
 * const { request, abort } = createCancellableRequest((signal) =>
 *   apiClient.get('/api/v1/tracks', { signal })
 * );
 *
 * // Later, to cancel:
 * abort();
 * ```
 */
export function createCancellableRequest<T>(
  requestFn: (signal: AbortSignal) => Promise<T>,
): { request: Promise<T>; abort: () => void } {
  const abortController = new AbortController();
  const signal = abortController.signal;

  const request = requestFn(signal);

  return {
    request,
    abort: () => {
      abortController.abort();
    },
  };
}

/**
 * Helper function to create a request with timeout
 * Automatically cancels the request if it exceeds the timeout duration
 *
 * @example
 * ```typescript
 * const { request, abort } = createRequestWithTimeout(
 *   (signal) => apiClient.get('/api/v1/tracks', { signal }),
 *   5000 // 5 seconds timeout
 * );
 * ```
 */
export function createRequestWithTimeout<T>(
  requestFn: (signal: AbortSignal) => Promise<T>,
  timeoutMs: number,
): { request: Promise<T>; abort: () => void } {
  const abortController = new AbortController();
  const signal = abortController.signal;

  // Set up timeout
  const timeoutId = setTimeout(() => {
    abortController.abort();
  }, timeoutMs);

  const request = requestFn(signal).finally(() => {
    // Clear timeout if request completes before timeout
    clearTimeout(timeoutId);
  });

  return {
    request,
    abort: () => {
      clearTimeout(timeoutId);
      abortController.abort();
    },
  };
}

/**
 * FE-API-016: Enhanced API client methods with automatic deduplication
 * FE-API-017: Enhanced with response caching for GET requests
 * These methods automatically deduplicate identical concurrent requests and cache GET responses
 *
 * @example
 * ```typescript
 * // Multiple identical requests will share the same promise
 * const promise1 = deduplicatedApiClient.get('/tracks');
 * const promise2 = deduplicatedApiClient.get('/tracks');
 * // promise1 === promise2 (same promise instance)
 *
 * // Cached responses are returned immediately
 * const response1 = await deduplicatedApiClient.get('/tracks');
 * const response2 = await deduplicatedApiClient.get('/tracks'); // Returns from cache
 * ```
 */
export const deduplicatedApiClient = {
  get: <T = any>(url: string, config?: InternalAxiosRequestConfig) => {
    // FE-API-017: Check cache first
    if (!(config as any)?._disableCache) {
      const cachedResponse = responseCache.get({
        ...config,
        method: 'GET',
        url,
      });
      if (cachedResponse) {
        logger.debug(`[API] Using cached response for: ${url}`);
        return Promise.resolve(cachedResponse as AxiosResponse<T>);
      }
    }

    return requestDeduplication.getOrCreateRequest(
      { ...config, method: 'GET', url },
      () => apiClient.get<T>(url, config),
    );
  },

  post: <T = any>(
    url: string,
    data?: any,
    config?: InternalAxiosRequestConfig,
  ) => {
    return requestDeduplication.getOrCreateRequest(
      { ...config, method: 'POST', url, data },
      () => apiClient.post<T>(url, data, config),
    );
  },

  put: <T = any>(
    url: string,
    data?: any,
    config?: InternalAxiosRequestConfig,
  ) => {
    return requestDeduplication.getOrCreateRequest(
      { ...config, method: 'PUT', url, data },
      () => apiClient.put<T>(url, data, config),
    );
  },

  patch: <T = any>(
    url: string,
    data?: any,
    config?: InternalAxiosRequestConfig,
  ) => {
    return requestDeduplication.getOrCreateRequest(
      { ...config, method: 'PATCH', url, data },
      () => apiClient.patch<T>(url, data, config),
    );
  },

  delete: <T = any>(url: string, config?: InternalAxiosRequestConfig) => {
    return requestDeduplication.getOrCreateRequest(
      { ...config, method: 'DELETE', url },
      () => apiClient.delete<T>(url, config),
    );
  },
};
