/**
 * SDK API Client
 *
 * Centralized API client for SDK state management with error handling,
 * retry logic, and optimistic locking support.
 *
 * Domain methods are implemented in sibling files and delegated to here:
 *   api-client-state.ts      — getState, saveState, deleteState, exportState
 *   api-client-projects.ts   — listProjects … permanentlyDeleteProject
 *   api-client-wiki.ts       — listWikiCategories … searchWiki
 *   api-client-operations.ts — checkpoints, flow, modules, UCCA, import, screening
 */

import { SDKState, CheckpointStatus, ProjectInfo, WikiCategory, WikiArticle, WikiSearchResult } from './types'
import {
  APIResponse,
  StateResponse,
  SaveStateRequest,
  CheckpointValidationResult,
  APIError,
  FetchContext,
  DEFAULT_BASE_URL,
  DEFAULT_TIMEOUT,
  MAX_RETRIES,
  RETRY_DELAYS,
} from './api-client-types'

// Re-export public types so existing consumers keep working
export type { APIResponse, StateResponse, SaveStateRequest, CheckpointValidationResult, APIError }

// Domain helpers
import * as stateHelpers from './api-client-state'
import * as projectHelpers from './api-client-projects'
import * as wikiHelpers from './api-client-wiki'
import * as opsHelpers from './api-client-operations'

// =============================================================================
// API CLIENT
// =============================================================================

export class SDKApiClient {
  private baseUrl: string
  private tenantId: string
  private projectId: string | undefined
  private timeout: number
  private abortControllers: Map<string, AbortController> = new Map()

  constructor(options: {
    baseUrl?: string
    tenantId: string
    projectId?: string
    timeout?: number
  }) {
    this.baseUrl = options.baseUrl || DEFAULT_BASE_URL
    this.tenantId = options.tenantId
    this.projectId = options.projectId
    this.timeout = options.timeout || DEFAULT_TIMEOUT
  }

  // ---------------------------------------------------------------------------
  // Private infrastructure — also exposed via FetchContext to helpers
  // ---------------------------------------------------------------------------

  createError(message: string, status?: number, retryable = false): APIError {
    const error = new Error(message) as APIError
    error.status = status
    error.retryable = retryable
    return error
  }

  async fetchWithTimeout(
    url: string,
    options: RequestInit,
    requestId: string
  ): Promise<Response> {
    const controller = new AbortController()
    this.abortControllers.set(requestId, controller)

    const timeoutId = setTimeout(() => controller.abort(), this.timeout)

    try {
      const response = await fetch(url, {
        ...options,
        signal: controller.signal,
      })
      return response
    } finally {
      clearTimeout(timeoutId)
      this.abortControllers.delete(requestId)
    }
  }

  async fetchWithRetry<T>(
    url: string,
    options: RequestInit,
    retries = MAX_RETRIES
  ): Promise<T> {
    const requestId = `${Date.now()}-${Math.random()}`
    let lastError: Error | null = null

    for (let attempt = 0; attempt <= retries; attempt++) {
      try {
        const response = await this.fetchWithTimeout(url, options, requestId)

        if (!response.ok) {
          const errorBody = await response.text()
          let errorMessage = `HTTP ${response.status}`

          try {
            const errorJson = JSON.parse(errorBody)
            errorMessage = errorJson.error || errorJson.message || errorMessage
          } catch {
            // Keep the HTTP status message
          }

          // Don't retry client errors (4xx) except for 429 (rate limit)
          const retryable = response.status >= 500 || response.status === 429

          if (!retryable || attempt === retries) {
            throw this.createError(errorMessage, response.status, retryable)
          }
        } else {
          const data = await response.json()
          return data as T
        }
      } catch (error) {
        lastError = error as Error

        if (error instanceof Error && error.name === 'AbortError') {
          throw this.createError('Request timeout', 408, true)
        }

        // Check if it's a retryable error
        const apiError = error as APIError
        if (!apiError.retryable || attempt === retries) {
          throw error
        }
      }

      // Wait before retrying (exponential backoff)
      if (attempt < retries) {
        await this.sleep(RETRY_DELAYS[attempt] || RETRY_DELAYS[RETRY_DELAYS.length - 1])
      }
    }

    throw lastError || this.createError('Unknown error', 500, false)
  }

  private sleep(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms))
  }

  /** Build a FetchContext for passing to domain helpers */
  private get ctx(): FetchContext {
    return {
      baseUrl: this.baseUrl,
      tenantId: this.tenantId,
      projectId: this.projectId,
      fetchWithRetry: this.fetchWithRetry.bind(this),
      fetchWithTimeout: this.fetchWithTimeout.bind(this),
      createError: this.createError.bind(this),
    }
  }

  // ---------------------------------------------------------------------------
  // State Management  (api-client-state.ts)
  // ---------------------------------------------------------------------------

  async getState(): Promise<StateResponse | null> { return stateHelpers.getState(this.ctx) }
  async saveState(state: SDKState, version?: number): Promise<StateResponse> { return stateHelpers.saveState(this.ctx, state, version) }
  async deleteState(): Promise<void> { return stateHelpers.deleteState(this.ctx) }
  async exportState(format: 'json' | 'pdf' | 'zip'): Promise<Blob> { return stateHelpers.exportState(this.ctx, format) }

  // ---------------------------------------------------------------------------
  // Checkpoints & Flow  (api-client-operations.ts)
  // ---------------------------------------------------------------------------

  async validateCheckpoint(checkpointId: string, data?: unknown): Promise<CheckpointValidationResult> { return opsHelpers.validateCheckpoint(this.ctx, checkpointId, data) }
  async getCheckpoints(): Promise<Record<string, CheckpointStatus>> { return opsHelpers.getCheckpoints(this.ctx) }
  async getFlowState() { return opsHelpers.getFlowState(this.ctx) }
  async navigateFlow(direction: 'next' | 'previous') { return opsHelpers.navigateFlow(this.ctx, direction) }

  // ---------------------------------------------------------------------------
  // Modules, UCCA, Import, Screening, Health  (api-client-operations.ts)
  // ---------------------------------------------------------------------------

  async getModules(filters?: Parameters<typeof opsHelpers.getModules>[1]) { return opsHelpers.getModules(this.ctx, filters) }
  async assessUseCase(intake: unknown) { return opsHelpers.assessUseCase(this.ctx, intake) }
  async getAssessments() { return opsHelpers.getAssessments(this.ctx) }
  async getAssessment(id: string) { return opsHelpers.getAssessment(this.ctx, id) }
  async deleteAssessment(id: string) { return opsHelpers.deleteAssessment(this.ctx, id) }
  async analyzeDocument(formData: FormData) { return opsHelpers.analyzeDocument(this.ctx, formData) }
  async scanDependencies(formData: FormData) { return opsHelpers.scanDependencies(this.ctx, formData) }
  async healthCheck() { return opsHelpers.healthCheck(this.ctx) }

  // ---------------------------------------------------------------------------
  // Projects  (api-client-projects.ts)
  // ---------------------------------------------------------------------------

  async listProjects(includeArchived = true) { return projectHelpers.listProjects(this.ctx, includeArchived) }
  async createProject(data: Parameters<typeof projectHelpers.createProject>[1]) { return projectHelpers.createProject(this.ctx, data) }
  async updateProject(projectId: string, data: Parameters<typeof projectHelpers.updateProject>[2]) { return projectHelpers.updateProject(this.ctx, projectId, data) }
  async getProject(projectId: string) { return projectHelpers.getProject(this.ctx, projectId) }
  async archiveProject(projectId: string) { return projectHelpers.archiveProject(this.ctx, projectId) }
  async restoreProject(projectId: string) { return projectHelpers.restoreProject(this.ctx, projectId) }
  async permanentlyDeleteProject(projectId: string) { return projectHelpers.permanentlyDeleteProject(this.ctx, projectId) }

  // ---------------------------------------------------------------------------
  // Wiki  (api-client-wiki.ts)
  // ---------------------------------------------------------------------------

  async listWikiCategories() { return wikiHelpers.listWikiCategories(this.ctx) }
  async listWikiArticles(categoryId?: string) { return wikiHelpers.listWikiArticles(this.ctx, categoryId) }
  async getWikiArticle(id: string) { return wikiHelpers.getWikiArticle(this.ctx, id) }
  async searchWiki(query: string) { return wikiHelpers.searchWiki(this.ctx, query) }

  // ---------------------------------------------------------------------------
  // Utility
  // ---------------------------------------------------------------------------

  cancelAllRequests(): void {
    this.abortControllers.forEach(controller => controller.abort())
    this.abortControllers.clear()
  }

  setTenantId(tenantId: string): void { this.tenantId = tenantId }
  getTenantId(): string { return this.tenantId }
  setProjectId(projectId: string | undefined): void { this.projectId = projectId }
  getProjectId(): string | undefined { return this.projectId }
}

// =============================================================================
// SINGLETON FACTORY
// =============================================================================

let clientInstance: SDKApiClient | null = null

export function getSDKApiClient(tenantId?: string, projectId?: string): SDKApiClient {
  if (!clientInstance && !tenantId) {
    throw new Error('SDKApiClient not initialized. Provide tenantId on first call.')
  }

  if (!clientInstance && tenantId) {
    clientInstance = new SDKApiClient({ tenantId, projectId })
  }

  if (tenantId && clientInstance && clientInstance.getTenantId() !== tenantId) {
    clientInstance.setTenantId(tenantId)
  }

  if (clientInstance) {
    clientInstance.setProjectId(projectId)
  }

  return clientInstance!
}

export function resetSDKApiClient(): void {
  if (clientInstance) {
    clientInstance.cancelAllRequests()
  }
  clientInstance = null
}
