/**
 * API Client with Streaming Support and Date Conversion
 * 
 * This demonstrates how to use the generated types with the conversion utilities
 * for seamless frontend-backend communication, including streaming responses and
 * automatic date field conversion.
 */

// Import generated types (from running generate_types.py)
import * as Types from 'types/types';
import { 
  formatApiRequest, 
  parseApiResponse, 
  parsePaginatedResponse,
  handleApiResponse,
  // handlePaginatedApiResponse,
  createPaginatedRequest,
  toUrlParams,
  extractApiData,
  // ApiResponse,
  PaginatedResponse,
  PaginatedRequest,
  toSnakeCase
} from 'types/conversion';

// Import generated date conversion functions
import {
  convertFromApi,
  convertArrayFromApi
} from 'types/types';

// ============================
// Streaming Types and Interfaces
// ============================

interface StreamingOptions<T = Types.ChatMessage> {
  method?: string,
  headers?: Record<string, any>,
  onStatus?: (status: Types.ChatMessageStatus) => void;
  onMessage?: (message: T) => void;
  onStreaming?: (chunk: Types.ChatMessageStreaming) => void;
  onComplete?: () => void;
  onError?: (error: Types.ChatMessageError) => void;
  onWarn?: (warning: string) => void;
  signal?: AbortSignal;
}

interface DeleteResponse {
  success: boolean;
  message: string;
}

interface StreamingResponse<T = Types.ChatMessage> {
  messageId: string;
  cancel: () => void;
  promise: Promise<T>;
}

interface CreateCandidateAIResponse {
  message: string;
  candidate: Types.CandidateAI;
  resume: string;
};
  

export interface PasswordResetRequest {
  email: string;
}

export interface PasswordResetConfirm {
  token: string;
  newPassword: string;
}

// ============================
// Chat Types and Interfaces
// ============================

export interface CandidateInfo {
  id: string;
  name: string;
  email: string;
  username: string;
  skills: string[];
  experience: number;
  location: string;
}

export interface CreateChatSessionRequest {
  username?: string; // Optional candidate username to associate with
  context: Types.ChatContext;
  title?: string;
}

export interface UpdateChatSessionRequest {
  title?: string;
  context?: Partial<Types.ChatContext>;
  isArchived?: boolean;
  systemPrompt?: string;
}

export interface CandidateSessionsResponse {
  candidate: {
    id: string;
    username: string;
    fullName: string;
    email: string;
  };
  sessions: PaginatedResponse<Types.ChatSession>;
}

// ============================
// API Client Class
// ============================

class ApiClient {
  private baseUrl: string;
  private defaultHeaders: Record<string, string>;

  constructor(accessToken?: string) {
    const loc = window.location;
    if (!loc.host.match(/.*battle-linux.*/)) {
      this.baseUrl = loc.protocol + "//" + loc.host + "/api/1.0";
    } else {
      this.baseUrl = loc.protocol + "//battle-linux.ketrenos.com:8912/api/1.0";
    }    
    this.defaultHeaders = {
      'Content-Type': 'application/json',
      ...(accessToken && { 'Authorization': `Bearer ${accessToken}` })
    };
  }

  // ============================
  // Response Handlers with Date Conversion
  // ============================

  /**
   * Handle API response with automatic date conversion for specific model types
   */
  private async handleApiResponseWithConversion<T>(
    response: Response, 
    modelType?: string
  ): Promise<T> {
    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(`HTTP ${response.status}: ${errorText}`);
    }
    
    const data = await response.json();
    const apiResponse = parseApiResponse<T>(data);
    const extractedData = extractApiData(apiResponse);

    // Apply model-specific date conversion if modelType is provided
    if (modelType) {
      return convertFromApi<T>(extractedData, modelType);
    }

    return extractedData;
  }

  /**
   * Handle paginated API response with automatic date conversion
   */
  private async handlePaginatedApiResponseWithConversion<T>(
    response: Response,
    modelType?: string
  ): Promise<PaginatedResponse<T>> {
    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(`HTTP ${response.status}: ${errorText}`);
    }
    
    const data = await response.json();
    const apiResponse = parsePaginatedResponse<T>(data);
    const extractedData = extractApiData(apiResponse);
    console.log("extracted", extractedData);
    // Apply model-specific date conversion to array items if modelType is provided
    if (modelType && extractedData.data) {
      return {
        ...extractedData,
        data: convertArrayFromApi<T>(extractedData.data, modelType)
      };
    }

    return extractedData;
  }

  /**
   * Create candidate with email verification
   */
  async createCandidate(
    candidate: CreateCandidateRequest
  ): Promise<RegistrationResponse> {
    const response = await fetch(`${this.baseUrl}/candidates`, {
      method: 'POST',
      headers: this.defaultHeaders,
      body: JSON.stringify(formatApiRequest(candidate))
    });

    return handleApiResponse<RegistrationResponse>(response);
  }

  async createCandidateAI(
    userMessage: Types.ChatMessageUser
  ): Promise<CreateCandidateAIResponse> {
    const response = await fetch(`${this.baseUrl}/candidates/ai`, {
      method: 'POST',
      headers: this.defaultHeaders,
      body: JSON.stringify(formatApiRequest(userMessage))
    });

    const result = await handleApiResponse<CreateCandidateAIResponse>(response);
    return {
      message: result.message,
      candidate: convertFromApi<Types.CandidateAI>(result.candidate, "CandidateAI"),
      resume: result.resume
    };
  }

  /**
   * Create employer with email verification
   */
  async createEmployerWithVerification(
    employer: CreateEmployerRequest
  ): Promise<RegistrationResponse> {
    const response = await fetch(`${this.baseUrl}/employers`, {
      method: 'POST',
      headers: this.defaultHeaders,
      body: JSON.stringify(formatApiRequest(employer))
    });

    return handleApiResponse<RegistrationResponse>(response);
  }

  /**
   * Verify email address
   */
  async verifyEmail(request: EmailVerificationRequest): Promise<EmailVerificationResponse> {
    const response = await fetch(`${this.baseUrl}/auth/verify-email`, {
      method: 'POST',
      headers: this.defaultHeaders,
      body: JSON.stringify(formatApiRequest(request))
    });

    return handleApiResponse<EmailVerificationResponse>(response);
  }

  /**
   * Resend verification email
   */
  async resendVerificationEmail(request: ResendVerificationRequest): Promise<{ message: string }> {
    const response = await fetch(`${this.baseUrl}/auth/resend-verification`, {
      method: 'POST',
      headers: this.defaultHeaders,
      body: JSON.stringify(formatApiRequest(request))
    });

    return handleApiResponse<{ message: string }>(response);
  }

  /**
   * Request MFA for new device
   */
  async requestMFA(request: MFARequest): Promise<Types.MFARequestResponse> {
    const response = await fetch(`${this.baseUrl}/auth/mfa/request`, {
      method: 'POST',
      headers: this.defaultHeaders,
      body: JSON.stringify(formatApiRequest(request))
    });

    return handleApiResponse<Types.MFARequestResponse>(response);
  }

  /**
   * Verify MFA code
   */
  async verifyMFA(request: Types.MFAVerifyRequest): Promise<Types.AuthResponse> {
    const formattedRequest = formatApiRequest(request)
    const response = await fetch(`${this.baseUrl}/auth/mfa/verify`, {
      method: 'POST',
      headers: this.defaultHeaders,
      body: JSON.stringify(formattedRequest)
    });

    return handleApiResponse<Types.AuthResponse>(response);
  }

  /**
   * login with device detection
   */
  async login(auth: Types.LoginRequest): Promise<Types.AuthResponse | Types.MFARequestResponse> {
    const response = await fetch(`${this.baseUrl}/auth/login`, {
      method: 'POST',
      headers: this.defaultHeaders,
      body: JSON.stringify(formatApiRequest(auth))
    });

    return handleApiResponse<Types.AuthResponse | Types.MFARequestResponse>(response);
  }

  /**
   * Logout with token revocation
   */
  async logout(accessToken: string, refreshToken: string): Promise<{ message: string; tokensRevoked: any }> {
    const response = await fetch(`${this.baseUrl}/auth/logout`, {
      method: 'POST',
      headers: this.defaultHeaders,
      body: JSON.stringify(formatApiRequest({ 
        accessToken, 
        refreshToken 
      }))
    });

    return handleApiResponse<{ message: string; tokensRevoked: any }>(response);
  }

  /**
   * Logout from all devices
   */
  async logoutAllDevices(): Promise<{ message: string }> {
    const response = await fetch(`${this.baseUrl}/auth/logout-all`, {
      method: 'POST',
      headers: this.defaultHeaders
    });

    return handleApiResponse<{ message: string }>(response);
  }

  // ============================
  // Device Management Methods
  // ============================

  /**
   * Get trusted devices for current user
   */
  async getTrustedDevices(): Promise<TrustedDevice[]> {
    const response = await fetch(`${this.baseUrl}/auth/trusted-devices`, {
      headers: this.defaultHeaders
    });

    return handleApiResponse<TrustedDevice[]>(response);
  }

  /**
   * Remove trusted device
   */
  async removeTrustedDevice(deviceId: string): Promise<{ message: string }> {
    const response = await fetch(`${this.baseUrl}/auth/trusted-devices/${deviceId}`, {
      method: 'DELETE',
      headers: this.defaultHeaders
    });

    return handleApiResponse<{ message: string }>(response);
  }

  /**
   * Get security log for current user
   */
  async getSecurityLog(days: number = 7): Promise<SecurityEvent[]> {
    const response = await fetch(`${this.baseUrl}/auth/security-log?days=${days}`, {
      headers: this.defaultHeaders
    });

    return handleApiResponse<SecurityEvent[]>(response);
  }

  // ============================
  // Admin Methods (if user has admin role)
  // ============================

  /**
   * Get pending user verifications (admin only)
   */
  async getPendingVerifications(): Promise<PendingVerification[]> {
    const response = await fetch(`${this.baseUrl}/admin/pending-verifications`, {
      headers: this.defaultHeaders
    });

    return handleApiResponse<PendingVerification[]>(response);
  }

  /**
   * Manually verify user (admin only)
   */
  async manuallyVerifyUser(userId: string, reason: string): Promise<{ message: string }> {
    const response = await fetch(`${this.baseUrl}/admin/verify-user/${userId}`, {
      method: 'POST',
      headers: this.defaultHeaders,
      body: JSON.stringify(formatApiRequest({ reason }))
    });

    return handleApiResponse<{ message: string }>(response);
  }

  /**
   * Get user security events (admin only)
   */
  async getUserSecurityEvents(userId: string, days: number = 30): Promise<SecurityEvent[]> {
    const response = await fetch(`${this.baseUrl}/admin/users/${userId}/security-events?days=${days}`, {
      headers: this.defaultHeaders
    });

    return handleApiResponse<SecurityEvent[]>(response);
  }

  // ============================
  // Utility Methods
  // ============================

  /**
   * Generate device fingerprint for MFA
   */
  generateDeviceFingerprint(): string {
    // Create a basic device fingerprint
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    if (ctx) {
      ctx.textBaseline = 'top';
      ctx.font = '14px Arial';
      ctx.fillText('Device fingerprint', 2, 2);
    }
    
    const fingerprint = 
      (canvas.toDataURL() || '') +
      navigator.userAgent +
      navigator.language +
      // screen.width + 'x' + screen.height +
      // (navigator.platform || '') +
      (navigator.cookieEnabled ? '1' : '0');
    
    // Create a hash-like string from the fingerprint
    let hash = 0;
    for (let i = 0; i < fingerprint.length; i++) {
      const char = fingerprint.charCodeAt(i);
      hash = ((hash << 5) - hash) + char;
      hash = hash & hash; // Convert to 32bit integer
    }
    
    return Math.abs(hash).toString(16).slice(0, 16);
  }

  /**
   * Get device name from user agent
   */
  getDeviceName(): string {
    const ua = navigator.userAgent;
    
    const browser = ua.includes('Chrome') ? 'Chrome' : 
                   ua.includes('Firefox') ? 'Firefox' : 
                   ua.includes('Safari') ? 'Safari' : 
                   ua.includes('Edge') ? 'Edge' : 'Browser';
    
    const os = ua.includes('Windows') ? 'Windows' :
              ua.includes('Mac') ? 'macOS' :
              ua.includes('Linux') ? 'Linux' :
              ua.includes('Android') ? 'Android' :
              ua.includes('iOS') ? 'iOS' : 'Unknown OS';
    
    return `${browser} on ${os}`;
  }

  /**
   * Check if email verification is pending
   */
  isEmailVerificationPending(): boolean {
    return localStorage.getItem('pendingEmailVerification') === 'true';
  }

  /**
   * Set email verification pending status
   */
  setPendingEmailVerification(email: string, pending: boolean = true): void {
    if (pending) {
      localStorage.setItem('pendingEmailVerification', 'true');
      localStorage.setItem('pendingVerificationEmail', email);
    } else {
      localStorage.removeItem('pendingEmailVerification');
      localStorage.removeItem('pendingVerificationEmail');
    }
  }

  /**
   * Get pending verification email
   */
  getPendingVerificationEmail(): string | null {
    return localStorage.getItem('pendingVerificationEmail');
  }

  // ============================
  // Authentication Methods
  // ============================
  async refreshToken(refreshToken: string): Promise<Types.AuthResponse> {
    const response = await fetch(`${this.baseUrl}/auth/refresh`, {
      method: 'POST',
      headers: this.defaultHeaders,
      body: JSON.stringify(formatApiRequest({ refreshToken }))
    });

    return handleApiResponse<Types.AuthResponse>(response);
  }

  // ============================
  // Candidate Methods with Date Conversion
  // ============================

  // reference can be candidateId, username, or email
  async getCandidate(reference: string): Promise<Types.Candidate> {
    const response = await fetch(`${this.baseUrl}/candidates/${reference}`, {
      headers: this.defaultHeaders
    });

    return this.handleApiResponseWithConversion<Types.Candidate>(response, 'Candidate');
  }

  async updateCandidate(id: string, updates: Partial<Types.Candidate>): Promise<Types.Candidate> {
    const request = formatApiRequest(updates);
    const response = await fetch(`${this.baseUrl}/candidates/${id}`, {
      method: 'PATCH',
      headers: this.defaultHeaders,
      body: JSON.stringify(request)
    });

    return this.handleApiResponseWithConversion<Types.Candidate>(response, 'Candidate');
  }

  async deleteCandidate(id: string): Promise<DeleteResponse> {
    const response = await fetch(`${this.baseUrl}/candidates/${id}`, {
      method: 'DELETE',
      headers: this.defaultHeaders,
      body: JSON.stringify({ id })
    });

    return handleApiResponse<DeleteResponse>(response);
  }

  async deleteJob(id: string): Promise<DeleteResponse> {
    const response = await fetch(`${this.baseUrl}/jobs/${id}`, {
      method: 'DELETE',
      headers: this.defaultHeaders,
      body: JSON.stringify({ id })
    });

    return handleApiResponse<DeleteResponse>(response);
  }

  async uploadCandidateProfile(file: File): Promise<boolean> {
    const formData = new FormData()
    formData.append('file', file);
    formData.append('filename', file.name);
    
    const response = await fetch(`${this.baseUrl}/candidates/profile/upload`, {
      method: 'POST',
      headers: {
        // Don't set Content-Type - browser will set it automatically with boundary
        'Authorization': this.defaultHeaders['Authorization']
      },
      body: formData
    });

    const result = await handleApiResponse<boolean>(response);
    
    return result;
  }

  async getCandidates(request: Partial<PaginatedRequest> = {}): Promise<PaginatedResponse<Types.Candidate>> {
    const paginatedRequest = createPaginatedRequest(request);
    const params = toUrlParams(formatApiRequest(paginatedRequest));
    
    const response = await fetch(`${this.baseUrl}/candidates?${params}`, {
      headers: this.defaultHeaders
    });

    return this.handlePaginatedApiResponseWithConversion<Types.Candidate>(response, 'Candidate');
  }

  async searchCandidates(query: string, filters?: Record<string, any>): Promise<PaginatedResponse<Types.Candidate>> {
    const searchRequest = {
      query,
      filters,
      page: 1,
      limit: 20
    };
    
    const params = toUrlParams(formatApiRequest(searchRequest));
    const response = await fetch(`${this.baseUrl}/candidates/search?${params}`, {
      headers: this.defaultHeaders
    });

    return this.handlePaginatedApiResponseWithConversion<Types.Candidate>(response, 'Candidate');
  }

  // ============================
  // Employer Methods with Date Conversion
  // ============================

  async createEmployer(request: CreateEmployerRequest): Promise<Types.Employer> {
    const response = await fetch(`${this.baseUrl}/employers`, {
      method: 'POST',
      headers: this.defaultHeaders,
      body: JSON.stringify(formatApiRequest(request))
    });

    return this.handleApiResponseWithConversion<Types.Employer>(response, 'Employer');
  }

  async getEmployer(id: string): Promise<Types.Employer> {
    const response = await fetch(`${this.baseUrl}/employers/${id}`, {
      headers: this.defaultHeaders
    });

    return this.handleApiResponseWithConversion<Types.Employer>(response, 'Employer');
  }

  async updateEmployer(id: string, updates: Partial<Types.Employer>): Promise<Types.Employer> {
    const response = await fetch(`${this.baseUrl}/employers/${id}`, {
      method: 'PATCH',
      headers: this.defaultHeaders,
      body: JSON.stringify(formatApiRequest(updates))
    });

    return this.handleApiResponseWithConversion<Types.Employer>(response, 'Employer');
  }

  // ============================
  // Job Methods with Date Conversion
  // ============================

  createJobFromDescription(job_description: string, streamingOptions?: StreamingOptions<Types.Job>): StreamingResponse<Types.Job> {
    const body = JSON.stringify(job_description);
    return this.streamify<Types.Job>('/jobs/from-content', body, streamingOptions);
  }

  async createJob(job: Omit<Types.Job, 'id' | 'datePosted' | 'views' | 'applicationCount'>): Promise<Types.Job> {
    const body = JSON.stringify(formatApiRequest(job));
    const response = await fetch(`${this.baseUrl}/jobs`, {
      method: 'POST',
      headers: this.defaultHeaders,
      body: body
    });

    return this.handleApiResponseWithConversion<Types.Job>(response, 'Job');
  }

  async getJob(id: string): Promise<Types.Job> {
    const response = await fetch(`${this.baseUrl}/jobs/${id}`, {
      headers: this.defaultHeaders
    });

    return this.handleApiResponseWithConversion<Types.Job>(response, 'Job');
  }

  async getJobs(request: Partial<PaginatedRequest> = {}): Promise<PaginatedResponse<Types.JobFull>> {
    const paginatedRequest = createPaginatedRequest(request);
    const params = toUrlParams(formatApiRequest(paginatedRequest));
    
    const response = await fetch(`${this.baseUrl}/jobs?${params}`, {
      headers: this.defaultHeaders
    });

    return this.handlePaginatedApiResponseWithConversion<Types.JobFull>(response, 'JobFull');
  }

  async getJobsByEmployer(employerId: string, request: Partial<PaginatedRequest> = {}): Promise<PaginatedResponse<Types.Job>> {
    const paginatedRequest = createPaginatedRequest(request);
    const params = toUrlParams(formatApiRequest(paginatedRequest));
    
    const response = await fetch(`${this.baseUrl}/employers/${employerId}/jobs?${params}`, {
      headers: this.defaultHeaders
    });

    return this.handlePaginatedApiResponseWithConversion<Types.Job>(response, 'Job');
  }

  async searchJobs(query: string, filters?: Record<string, any>): Promise<PaginatedResponse<Types.Job>> {
    const searchRequest = {
      query,
      filters,
      page: 1,
      limit: 20
    };
    
    const params = toUrlParams(formatApiRequest(searchRequest));
    const response = await fetch(`${this.baseUrl}/jobs/search?${params}`, {
      headers: this.defaultHeaders
    });

    return this.handlePaginatedApiResponseWithConversion<Types.Job>(response, 'Job');
  }

  // ============================
  // Job Application Methods with Date Conversion
  // ============================

  async applyToJob(application: Omit<Types.JobApplication, 'id' | 'appliedDate' | 'updatedDate' | 'status'>): Promise<Types.JobApplication> {
    const response = await fetch(`${this.baseUrl}/job-applications`, {
      method: 'POST',
      headers: this.defaultHeaders,
      body: JSON.stringify(formatApiRequest(application))
    });

    return this.handleApiResponseWithConversion<Types.JobApplication>(response, 'JobApplication');
  }

  async getJobApplication(id: string): Promise<Types.JobApplication> {
    const response = await fetch(`${this.baseUrl}/job-applications/${id}`, {
      headers: this.defaultHeaders
    });

    return this.handleApiResponseWithConversion<Types.JobApplication>(response, 'JobApplication');
  }

  async getJobApplications(request: Partial<PaginatedRequest> = {}): Promise<PaginatedResponse<Types.JobApplication>> {
    const paginatedRequest = createPaginatedRequest(request);
    const params = toUrlParams(formatApiRequest(paginatedRequest));
    
    const response = await fetch(`${this.baseUrl}/job-applications?${params}`, {
      headers: this.defaultHeaders
    });

    return this.handlePaginatedApiResponseWithConversion<Types.JobApplication>(response, 'JobApplication');
  }

  async updateApplicationStatus(id: string, status: Types.ApplicationStatus): Promise<Types.JobApplication> {
    const response = await fetch(`${this.baseUrl}/job-applications/${id}/status`, {
      method: 'PATCH',
      headers: this.defaultHeaders,
      body: JSON.stringify(formatApiRequest({ status }))
    });

    return this.handleApiResponseWithConversion<Types.JobApplication>(response, 'JobApplication');
  }

  // ============================
  // Chat Methods with Date Conversion
  // ============================

  /**
   * Create a chat session with optional candidate association
   */
  async createChatSessionWithCandidate(
    request: CreateChatSessionRequest
  ): Promise<Types.ChatSession> {
    const response = await fetch(`${this.baseUrl}/chat/sessions`, {
      method: 'POST',
      headers: this.defaultHeaders,
      body: JSON.stringify(formatApiRequest(request))
    });

    return this.handleApiResponseWithConversion<Types.ChatSession>(response, 'ChatSession');
  }

  /**
   * Get all chat sessions related to a specific candidate
   */
  async getCandidateChatSessions(
    username: string,
    request: Partial<PaginatedRequest> = {}
  ): Promise<CandidateSessionsResponse> {
    const paginatedRequest = createPaginatedRequest(request);
    const params = toUrlParams(formatApiRequest(paginatedRequest));
    
    const response = await fetch(`${this.baseUrl}/candidates/${username}/chat-sessions?${params}`, {
      headers: this.defaultHeaders
    });

    // Handle the nested sessions with date conversion
    const result = await this.handleApiResponseWithConversion<CandidateSessionsResponse>(response);
    
    // Convert the nested sessions array
    if (result.sessions && result.sessions.data) {
      result.sessions.data = convertArrayFromApi<Types.ChatSession>(result.sessions.data, 'ChatSession');
    }
    
    return result;
  }

  async getOrCreateChatSession(candidate: Types.Candidate, title: string, context_type: Types.ChatContextType) : Promise<Types.ChatSession> {
    const result = await this.getCandidateChatSessions(candidate.username);
    /* Find the 'candidate_chat' session if it exists, otherwise create it */
    let session = result.sessions.data.find(session => session.title === 'candidate_chat');
    if (!session) {
        session = await this.createCandidateChatSession(
            candidate.username,
            context_type,
            title
        );
    }
    return session;
  }

  async getCandidateSimilarContent(query: string
  ): Promise<Types.ChromaDBGetResponse> {
    const response = await fetch(`${this.baseUrl}/candidates/rag-search`, {
      method: 'POST',
      headers: this.defaultHeaders,
      body: JSON.stringify(query)
    });

    const result = await handleApiResponse<Types.ChromaDBGetResponse>(response);
    
    return result;
  }

  async getCandidateVectors(
    dimensions: number,
  ): Promise<Types.ChromaDBGetResponse> {
    const response = await fetch(`${this.baseUrl}/candidates/rag-vectors`, {
      method: 'POST',
      headers: this.defaultHeaders,
      body: JSON.stringify(dimensions)
    });

    const result = await handleApiResponse<Types.ChromaDBGetResponse>(response);
    
    return result;
  }

  async getCandidateRAGContent(
    documentId: string,
  ): Promise<Types.RagContentResponse> {
    const response = await fetch(`${this.baseUrl}/candidates/rag-content`, {
      method: 'POST',
      headers: this.defaultHeaders,
      body: JSON.stringify({ id: documentId })
    });

    const result = await handleApiResponse<Types.RagContentResponse>(response);
    
    return result;
  }

  /**

    uploadCandidateDocument

    usage:
    const controller : StreamingResponse<Types.Document> = uploadCandidateDocument(...);
    const document : Types.Document = await controller.promise;
    console.log(`Document id: ${document.id}`)
   */
  uploadCandidateDocument(file: File, options: Types.DocumentOptions, streamingOptions?: StreamingOptions<Types.DocumentMessage>): StreamingResponse<Types.DocumentMessage> {
    const convertedOptions = toSnakeCase(options);
    const formData = new FormData()
    formData.append('file', file);
    formData.append('filename', file.name);
    formData.append('options', JSON.stringify(convertedOptions));
    streamingOptions = {
      ...streamingOptions,
      headers: {
        // Don't set Content-Type - browser will set it automatically with boundary
        'Authorization': this.defaultHeaders['Authorization']
      }
    };
    return this.streamify<Types.DocumentMessage>('/candidates/documents/upload', formData, streamingOptions);
  }

  createJobFromFile(file: File, streamingOptions?: StreamingOptions<Types.Job>): StreamingResponse<Types.Job> {
    const formData = new FormData()
    formData.append('file', file);
    formData.append('filename', file.name);
    streamingOptions = {
      ...streamingOptions,
      headers: {
        // Don't set Content-Type - browser will set it automatically with boundary
        'Authorization': this.defaultHeaders['Authorization']
      }
    };
    return this.streamify<Types.Job>('/jobs/upload', formData, streamingOptions);
  }

  getJobRequirements(jobId: string, streamingOptions?: StreamingOptions<Types.DocumentMessage>): StreamingResponse<Types.DocumentMessage> {
    streamingOptions = {
      ...streamingOptions,
      headers: this.defaultHeaders,
    };
    return this.streamify<Types.DocumentMessage>(`/jobs/requirements/${jobId}`, null, streamingOptions);
  }

  async candidateMatchForRequirement(candidate_id: string, requirement: string) : Promise<Types.SkillMatch> {
    const response = await fetch(`${this.baseUrl}/candidates/${candidate_id}/skill-match`, {
      method: 'POST',
      headers: this.defaultHeaders,
      body: JSON.stringify(requirement)
    });

    const result = await handleApiResponse<Types.SkillMatch>(response);
    
    return result;
  }

  async updateCandidateDocument(document: Types.Document) : Promise<Types.Document> {
    const request : Types.DocumentUpdateRequest = {
      filename: document.filename,
      options: document.options
    }
    const response = await fetch(`${this.baseUrl}/candidates/documents/${document.id}`, {
      method: 'PATCH',
      headers: this.defaultHeaders,
      body: JSON.stringify(formatApiRequest(request))
    });

    const result = await handleApiResponse<Types.Document>(response);
    
    return result;
  };
        
  async deleteCandidateDocument(document: Types.Document): Promise<boolean> {
    const response = await fetch(`${this.baseUrl}/candidates/documents/${document.id}`, {
      method: 'DELETE',
      headers: this.defaultHeaders
    });

    const result = await handleApiResponse<boolean>(response);
    
    return result;
  }
        
  async getCandidateDocuments(): Promise<Types.DocumentListResponse> {
    const response = await fetch(`${this.baseUrl}/candidates/documents`, {
      headers: this.defaultHeaders,
    });

    const result = await handleApiResponse<Types.DocumentListResponse>(response);
    
    return result;
  }

  async getCandidateDocumentText(
    document: Types.Document,
  ): Promise<Types.DocumentContentResponse> {
    const response = await fetch(`${this.baseUrl}/candidates/documents/${document.id}/content`, {
      headers: this.defaultHeaders,
    });

    const result = await handleApiResponse<Types.DocumentContentResponse>(response);
    
    return result;
  }

  /**
   * Create a chat session about a specific candidate
   */
  async createCandidateChatSession(
    username: string,
    chatType: Types.ChatContextType = 'candidate_chat',
    title?: string
  ): Promise<Types.ChatSession> {
    const request: CreateChatSessionRequest = {
      username,
      title: title || `Discussion about ${username}`,
      context: {
        type: chatType,
        additionalContext: {}
      }
    };

    return this.createChatSessionWithCandidate(request);
  }

  async createChatSession(context: Types.ChatContext): Promise<Types.ChatSession> {
    const response = await fetch(`${this.baseUrl}/chat/sessions`, {
      method: 'POST',
      headers: this.defaultHeaders,
      body: JSON.stringify(formatApiRequest({ context }))
    });

    return this.handleApiResponseWithConversion<Types.ChatSession>(response, 'ChatSession');
  }

  async getChatSession(id: string): Promise<Types.ChatSession> {
    const response = await fetch(`${this.baseUrl}/chat/sessions/${id}`, {
      headers: this.defaultHeaders
    });

    return this.handleApiResponseWithConversion<Types.ChatSession>(response, 'ChatSession');
  }

  /**
   * Update a chat session's properties
   */
  async updateChatSession(
    id: string, 
    updates: UpdateChatSessionRequest
  ): Promise<Types.ChatSession> {
    const response = await fetch(`${this.baseUrl}/chat/sessions/${id}`, {
      method: 'PATCH',
      headers: this.defaultHeaders,
      body: JSON.stringify(formatApiRequest(updates))
    });

    return this.handleApiResponseWithConversion<Types.ChatSession>(response, 'ChatSession');
  }

  /**
   * Delete a chat session
   */
  async deleteChatSession(id: string): Promise<{ success: boolean; message: string }> {
    const response = await fetch(`${this.baseUrl}/chat/sessions/${id}`, {
      method: 'DELETE',
      headers: this.defaultHeaders
    });

    return handleApiResponse<{ success: boolean; message: string }>(response);
  }

  async resetChatSession(id: string): Promise<{ success: boolean; message: string }> {
    const response = await fetch(`${this.baseUrl}/chat/sessions/${id}/reset`, {
      method: 'PATCH',
      headers: this.defaultHeaders
    });

    return handleApiResponse<{ success: boolean; message: string }>(response);
  }

  /**
   * streamify<T = Types.ChatMessage[]>
   * @param api API entrypoint
   * @param data Data to be attached to request Body
   * @param options callbacks, headers, and method
   * @returns 
   */
  streamify<T = Types.ChatMessage[]>(api: string, data: BodyInit | null, options: StreamingOptions<T> = {}) : StreamingResponse<T> {
    const abortController = new AbortController();
    const signal = options.signal || abortController.signal;
    const headers = options.headers || null;
    const method = options.method || 'POST';

    let messageId = '';
    let finalMessage : T | null = null;
    const promise = new Promise<T>(async (resolve, reject) => {
      try {
        const response = await fetch(`${this.baseUrl}${api}`, {
          method,
          headers: headers || {
            ...this.defaultHeaders,
            'Accept': 'text/event-stream',
            'Cache-Control': 'no-cache',
          },
          body: data,
          signal
        });

        if (!response.ok) {
          throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }

        const reader = response.body?.getReader();
        if (!reader) {
          throw new Error('Response body is not readable');
        }

        const decoder = new TextDecoder();
        let buffer = '';
        let streamingMessage: Types.ChatMessageStreaming | null = null;
        try {
          while (true) {
            const { done, value } = await reader.read();
            
            if (done) {
              // Stream ended naturally
              break;
            }
            
            buffer += decoder.decode(value, { stream: true });
            
            // Process complete lines
            const lines = buffer.split('\n');
            buffer = lines.pop() || ''; // Keep incomplete line in buffer
            
            for (const line of lines) {
              if (line.trim() === '') continue; // Skip blank lines between SSEs
              
              try {
                if (line.startsWith('data: ')) {
                  const data = line.slice(5).trim();
                  const incoming: any = JSON.parse(data);
                  
                  // Handle different status types
                  switch (incoming.status) {
                    case 'streaming':
                      const streaming = Types.convertChatMessageStreamingFromApi(incoming);
                      if (streamingMessage === null) {
                        streamingMessage = {...streaming};
                      } else {
                        // Can't do a simple += as typescript thinks .content might not be there
                        streamingMessage.content = (streamingMessage?.content || '') + streaming.content;
                        // Update timestamp to latest
                        streamingMessage.timestamp = streaming.timestamp;
                      }
                      options.onStreaming?.(streamingMessage);
                      break;
                    
                    case 'status':
                      const status = Types.convertChatMessageStatusFromApi(incoming);
                      options.onStatus?.(status);
                      break;

                    case 'error':
                      const error = Types.convertChatMessageErrorFromApi(incoming);
                      options.onError?.(error);
                      break;

                    case 'done':
                      const message = Types.convertApiMessageFromApi(incoming) as T;
                      finalMessage = message as any;
                      try {
                        options.onMessage?.(message);
                      } catch (error) {
                        console.error('onMessage handler failed: ', error);
                      }
                      break;
                  }
                }
              } catch (error) {
                console.warn('Failed to process SSE:', error);
                if (error instanceof Error) {
                  options.onWarn?.(error.message);
                }
                // Continue processing other lines
              }
            }
          }
        } finally {
          reader.releaseLock();
        }
        
        options.onComplete?.();
        resolve(finalMessage as T);
      } catch (error) {
        if (signal.aborted) {
          options.onComplete?.();
          reject(new Error('Request was aborted'));
        } else {
          console.error(error);
          options.onError?.({ sessionId: '', status: 'error', type: 'text', content: (error as Error).message});
          options.onComplete?.();
          reject(error);
        }
      }
    });

    return {
      messageId,
      cancel: () => abortController.abort(),
      promise
    };
  }
  
  /**
   * Send message with streaming response support and date conversion
   */
  sendMessageStream(
    chatMessage: Types.ChatMessageUser,
    options: StreamingOptions = {}
  ): StreamingResponse {
    const body = JSON.stringify(formatApiRequest(chatMessage));
    return this.streamify(`/chat/sessions/${chatMessage.sessionId}/messages/stream`, body, options)
  }

  /**
   * Get persisted chat messages for a session with date conversion
   */
  async getChatMessages(
    sessionId: string, 
    request: Partial<PaginatedRequest> = {}
  ): Promise<PaginatedResponse<Types.ChatMessage>> {
    const paginatedRequest = createPaginatedRequest({
      limit: 50, // Higher default for chat messages
      ...request});
    const params = toUrlParams(formatApiRequest(paginatedRequest));
    
    const response = await fetch(`${this.baseUrl}/chat/sessions/${sessionId}/messages?${params}`, {
      headers: this.defaultHeaders
    });

    return this.handlePaginatedApiResponseWithConversion<Types.ChatMessage>(response, 'ChatMessage');
  }

  // ============================
  // Password Reset Methods
  // ============================

  async requestPasswordReset(request: PasswordResetRequest): Promise<{ message: string }> {
    const response = await fetch(`${this.baseUrl}/auth/password-reset/request`, {
      method: 'POST',
      headers: this.defaultHeaders,
      body: JSON.stringify(formatApiRequest(request))
    });

    return handleApiResponse<{ message: string }>(response);
  }

  async confirmPasswordReset(request: PasswordResetConfirm): Promise<{ message: string }> {
    const response = await fetch(`${this.baseUrl}/auth/password-reset/confirm`, {
      method: 'POST',
      headers: this.defaultHeaders,
      body: JSON.stringify(formatApiRequest(request))
    });

    return handleApiResponse<{ message: string }>(response);
  }

  // ============================
  // Password Validation Utilities
  // ============================

  validatePasswordStrength(password: string): { isValid: boolean; issues: string[] } {
    const issues: string[] = [];
    
    if (password.length < 8) {
      issues.push('Password must be at least 8 characters long');
    }
    
    if (!/[A-Z]/.test(password)) {
      issues.push('Password must contain at least one uppercase letter');
    }
    
    if (!/[a-z]/.test(password)) {
      issues.push('Password must contain at least one lowercase letter');
    }
    
    if (!/\d/.test(password)) {
      issues.push('Password must contain at least one digit');
    }
    
    const specialChars = '!@#$%^&*()_+-=[]{}|;:,.<>?/~`"\'\\';
    if (!password.split('').some(char => specialChars.includes(char))) {
      issues.push('Password must contain at least one special character');
    }
    
    return {
      isValid: issues.length === 0,
      issues
    };
  }

  validateEmail(email: string): boolean {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
  }

  validateUsername(username: string): { isValid: boolean; issues: string[] } {
    const issues: string[] = [];
    
    if (username.length < 3) {
      issues.push('Username must be at least 3 characters long');
    }
    
    if (username.length > 30) {
      issues.push('Username must be no more than 30 characters long');
    }
    
    if (!/^[a-zA-Z0-9_-]+$/.test(username)) {
      issues.push('Username can only contain letters, numbers, underscores, and hyphens');
    }
    
    return {
      isValid: issues.length === 0,
      issues
    };
  }

  // ============================
  // Error Handling Helper
  // ============================

  async handleRequest<T>(requestFn: () => Promise<Response>, modelType?: string): Promise<T> {
    try {
      const response = await requestFn();
      return await this.handleApiResponseWithConversion<T>(response, modelType);
    } catch (error) {
      console.error('API request failed:', error);
      throw error;
    }
  }

  // ============================
  // Utility Methods
  // ============================

  setAuthToken(token: string): void {
    this.defaultHeaders['Authorization'] = `Bearer ${token}`;
  }

  clearAuthToken(): void {
    delete this.defaultHeaders['Authorization'];
  }

  getBaseUrl(): string {
    return this.baseUrl;
  }
}


// ============================
// Request/Response Types
// ============================

export interface CreateCandidateRequest {
  email: string;
  username: string;
  password: string;
  firstName: string;
  lastName: string;
  phone?: string;
}

export interface CreateEmployerRequest {
  email: string;
  username: string;
  password: string;
  companyName: string;
  industry: string;
  companySize: string;
  companyDescription: string;
  websiteUrl?: string;
  phone?: string;
}



export interface EmailVerificationRequest {
  token: string;
}

export interface ResendVerificationRequest {
  email: string;
}

export interface MFARequest {
  email: string;
  password: string;
  deviceId: string;
  deviceName: string;
}

export interface RegistrationResponse {
  message: string;
  email: string;
  verificationRequired: boolean;
}

export interface EmailVerificationResponse {
  message: string;
  accountActivated: boolean;
  userType: string;
}

export interface TrustedDevice {
  deviceId: string;
  deviceName: string;
  browser: string;
  browserVersion: string;
  os: string;
  osVersion: string;
  addedAt: string;
  lastUsed: string;
  ipAddress: string;
}

// ============================
// Additional Types
// ============================

export interface SecurityEvent {
  timestamp: string;
  userId: string;
  eventType: 'login' | 'logout' | 'mfa_request' | 'mfa_verify' | 'password_change' | 'email_verify' | 'device_add' | 'device_remove';
  details: {
    ipAddress?: string;
    deviceName?: string;
    success?: boolean;
    failureReason?: string;
    [key: string]: any;
  };
}

export interface PendingVerification {
  id: string;
  email: string;
  userType: 'candidate' | 'employer';
  createdAt: string;
  expiresAt: string;
  attempts: number;
}

export { ApiClient }
export type { StreamingOptions, StreamingResponse }