/**
 * Main TypedFetch Implementation
 */

import { WTinyLFUCache } from '../cache/w-tinylfu.js'
import { RequestDeduplicator } from '../cache/deduplicator.js'
import { RuntimeTypeInference } from '../types/runtime-inference.js'
import { OpenAPIParser } from '../discovery/openapi-parser.js'
import { TypedAPIProxy } from '../discovery/typed-api-proxy.js'
import { CircuitBreaker } from './circuit-breaker.js'
import { InterceptorChain } from './interceptors.js'
import { RequestMetrics } from './metrics.js'
import { OfflineHandler } from './offline-handler.js'
import { createHttpError, enhanceError, type ErrorContext } from './errors.js'
import { prepareBodyPayload } from './body-utils.js'
import { createResource, type ResourceBuilderOptions, type ResourceDefinition, type ResourceInstance } from './resource-builder.js'
import { applyPresetChain, type TypedFetchPreset } from './presets.js'
import { TypeDeclarationGenerator, type TypeSnapshotOptions } from '../discovery/type-generator.js'
import type { TypeRegistry, TypedError } from '../types/index.js'
import type { EndpointRequest, EndpointResponse, EndpointTypeMap } from '../types/endpoint-types.js'
import type { TypedFetchConfig } from '../types/config.js'
import { DEFAULT_CONFIG, mergeConfig } from '../types/config.js'
import { inferTypeDescriptor } from '../types/type-descriptor.js'

// Re-export configuration types for convenience
export type { TypedFetchConfig } from '../types/config.js'
export { DEFAULT_CONFIG, mergeConfig } from '../types/config.js'

const MAX_TYPE_SAMPLES = 5

export class RevolutionaryTypedFetch<TEndpoints extends EndpointTypeMap = EndpointTypeMap> {
  private config: Required<TypedFetchConfig>
  private cache: WTinyLFUCache
  private deduplicator = new RequestDeduplicator()
  private typeRegistry: TypeRegistry = {}
  private typeInference = new RuntimeTypeInference()
  private openApiParser = new OpenAPIParser()
  private circuitBreaker: CircuitBreaker
  private interceptors = new InterceptorChain()
  private metrics = new RequestMetrics()
  private offlineHandler = new OfflineHandler()
  private baseURL = ''
  
  constructor(config: TypedFetchConfig = {}) {
    this.config = mergeConfig(DEFAULT_CONFIG, config)
    this.cache = new WTinyLFUCache(this.config.cache.maxSize)
    this.circuitBreaker = new CircuitBreaker(
      this.config.circuit.threshold,
      this.config.circuit.timeout
    )
    this.baseURL = this.config.request.baseURL || ''
  }
  
  /**
   * Update configuration dynamically
   */
  configure(config: TypedFetchConfig): void {
    this.config = mergeConfig(this.config, config)
    
    // Reinitialize components that depend on config
    if (config.cache) {
      this.cache = new WTinyLFUCache(this.config.cache.maxSize)
    }
    
    if (config.circuit) {
      this.circuitBreaker = new CircuitBreaker(
        this.config.circuit.threshold,
        this.config.circuit.timeout
      )
    }
    
    // Always update baseURL from config
    this.baseURL = this.config.request.baseURL || ''
  }

  /**
   * Apply preset configuration stacks for simple ergonomics
   */
  use(...presetsInput: Array<TypedFetchPreset | TypedFetchPreset[]>): this {
    const flat = presetsInput.flat().filter(Boolean) as TypedFetchPreset[]
    if (!flat.length) return this
    const { overrides } = applyPresetChain(this.config, flat)
    if (Object.keys(overrides).length > 0) {
      this.configure(overrides)
    }
    return this
  }
  
  /**
   * Create a new instance with custom configuration
   */
  create(config: TypedFetchConfig): RevolutionaryTypedFetch<TEndpoints> {
    const mergedConfig = mergeConfig(this.config, config)
    return new RevolutionaryTypedFetch<TEndpoints>(mergedConfig)
  }
  
  // REAL runtime type tracking
  private recordRequest(endpoint: string, method: string, body: unknown): void {
    if (body === undefined) return
    const key = this.buildRegistryKey(method, endpoint)
    const entry = this.ensureRegistryEntry(key, method)
    entry.request = inferTypeDescriptor(body)
    entry.lastSeen = Date.now()
  }

  private recordResponse(endpoint: string, method: string, data: any): void {
    const key = this.buildRegistryKey(method, endpoint)
    this.typeInference.addSample(key, data)
    const entry = this.ensureRegistryEntry(key, method)
    const inferred = this.typeInference.inferType(key)
    if (inferred) {
      entry.response = inferred
    }
    entry.lastSeen = Date.now()
    entry.samples = [...entry.samples, data].slice(-MAX_TYPE_SAMPLES)
  }

  private buildRegistryKey(method: string, endpoint: string): string {
    return `${method.toUpperCase()} ${endpoint}`
  }

  private ensureRegistryEntry(key: string, method: string) {
    if (!this.typeRegistry[key]) {
      this.typeRegistry[key] = {
        method: method.toUpperCase(),
        lastSeen: Date.now(),
        samples: []
      }
    }
    return this.typeRegistry[key]!
  }
  
  // REAL auto-discovery implementation
  async discover(baseURL?: string): Promise<TypedAPIProxy> {
    // Use provided baseURL or fall back to config
    const discoveryBaseURL = baseURL || this.baseURL || this.config.request.baseURL
    if (!discoveryBaseURL) {
      throw new Error('No baseURL provided for discovery')
    }
    this.baseURL = discoveryBaseURL
    
    try {
      // Try to fetch OpenAPI schema
      const schemaUrls = [
        '/openapi.json',
        '/swagger.json', 
        '/docs/openapi.json',
        '/api/openapi.json',
        '/.well-known/openapi'
      ]
      
      for (const url of schemaUrls) {
        try {
          const response = await fetch(new URL(url, discoveryBaseURL).toString())
          if (response.ok) {
            const schema = await response.json()
            const types = this.openApiParser.parse(schema)
            
            // Merge with existing registry
            Object.assign(this.typeRegistry, types)
            if (this.config.debug.verbose) {
              console.log(`🔍 Discovered ${Object.keys(types).length} endpoints from ${url}`)
            }
            break
          }
        } catch {
          // Continue to next URL
        }
      }
    } catch (error) {
      if (this.config.debug.verbose) {
        console.warn('Schema discovery failed, will use runtime inference')
      }
    }
    
    return new TypedAPIProxy(this, discoveryBaseURL)
  }
  
  // REAL HTTP methods with full type safety
  async get<Url extends string>(
    url: Url,
    options: RequestInit = {}
  ): Promise<{ data: EndpointResponse<TEndpoints, 'GET', Url>; response: Response }> {
    return this.request<'GET', Url>('GET', url, options)
  }

  async post<Url extends string, TBody = EndpointRequest<TEndpoints, 'POST', Url>>(
    url: Url,
    body?: TBody,
    options: RequestInit = {}
  ): Promise<{ data: EndpointResponse<TEndpoints, 'POST', Url>; response: Response }> {
    if (body === undefined) {
      return this.request<'POST', Url>('POST', url, options)
    }
    const prepared = prepareBodyPayload(body)
    return this.request<'POST', Url>('POST', url, { ...options, body: prepared.bodyInit }, prepared.sample)
  }

  async put<Url extends string, TBody = EndpointRequest<TEndpoints, 'PUT', Url>>(
    url: Url,
    body?: TBody,
    options: RequestInit = {}
  ): Promise<{ data: EndpointResponse<TEndpoints, 'PUT', Url>; response: Response }> {
    if (body === undefined) {
      return this.request<'PUT', Url>('PUT', url, options)
    }
    const prepared = prepareBodyPayload(body)
    return this.request<'PUT', Url>('PUT', url, { ...options, body: prepared.bodyInit }, prepared.sample)
  }

  async patch<Url extends string, TBody = EndpointRequest<TEndpoints, 'PATCH', Url>>(
    url: Url,
    body?: TBody,
    options: RequestInit = {}
  ): Promise<{ data: EndpointResponse<TEndpoints, 'PATCH', Url>; response: Response }> {
    if (body === undefined) {
      return this.request<'PATCH', Url>('PATCH', url, options)
    }
    const prepared = prepareBodyPayload(body)
    return this.request<'PATCH', Url>('PATCH', url, { ...options, body: prepared.bodyInit }, prepared.sample)
  }

  async delete<Url extends string>(
    url: Url,
    options: RequestInit = {}
  ): Promise<{ data: EndpointResponse<TEndpoints, 'DELETE', Url>; response: Response }> {
    return this.request<'DELETE', Url>('DELETE', url, options)
  }

  resource<TDefinition extends ResourceDefinition>(
    path: string,
    definition: TDefinition,
    options?: ResourceBuilderOptions
  ): ResourceInstance<TDefinition> {
    return createResource(this, path, definition, options)
  }

  private resolveUrl(url: string): string {
    // Use baseURL from config or instance
    const baseURL = this.config.request.baseURL || this.baseURL
    
    // Construct full URL
    if (url.startsWith('http')) {
      return url
    } else if (baseURL) {
      return new URL(url, baseURL).toString()
    } else {
      throw new Error(`Relative URL "${url}" requires a baseURL to be set`)
    }
  }

  async request<
    Method extends string,
    Url extends string,
    TResponse = EndpointResponse<TEndpoints, Method, Url>
  >(
    method: Method,
    url: Url,
    options: RequestInit = {},
    bodySample?: unknown
  ): Promise<{
    data: TResponse
    response: Response
  }> {
    const fullUrl = this.resolveUrl(url)
    const cacheKey = `${method}:${fullUrl}`
    const startTime = performance.now()
    let cached = false
    let error: any = null
    
    try {
      // Check cache for GET requests
      if (method === 'GET' && this.config.cache.enabled) {
        const cachedData = this.cache.get(cacheKey)
        if (cachedData) {
          cached = true
          const duration = performance.now() - startTime
          if (this.config.metrics.enabled) {
            this.metrics.recordRequest(fullUrl, duration, cached)
          }
          return { data: cachedData as TResponse, response: new Response('cached') }
        }
      }
      
      // Build request options
      const requestOptions: RequestInit = {
        method,
        ...options,
        headers: {
          ...this.config.request.headers,
          ...(options.headers || {})
        }
      }
      
      // Only set Content-Type for JSON bodies
      if (options.body && typeof options.body === 'string') {
        (requestOptions.headers as any)['Content-Type'] = 'application/json'
      }
      
      // Add timeout if configured
      if (this.config.request.timeout && !requestOptions.signal) {
        const controller = new AbortController()
        setTimeout(() => controller.abort(), this.config.request.timeout)
        requestOptions.signal = controller.signal
      }
      
      // Process through interceptors
      const processedOptions = await this.interceptors.processRequest(requestOptions)

      if (bodySample !== undefined) {
        this.recordRequest(url, method, bodySample)
      }
      
      // Handle offline requests
      const result = await this.offlineHandler.handleRequest(fullUrl, processedOptions, async () => {
        // Deduplicate identical requests
        return this.deduplicator.dedupe(cacheKey, async () => {
          // Execute with circuit breaker and retry logic
          return this.executeWithRetry<Method, Url, TResponse>(fullUrl, processedOptions, url, method)
        })
      })
      
      const duration = performance.now() - startTime
      if (this.config.metrics.enabled) {
        this.metrics.recordRequest(fullUrl, duration, cached, error)
      }
      
      // Log successful requests if configured
      if (this.config.debug.logSuccess) {
        console.log(`✅ Request successful: ${method} ${fullUrl} (${duration.toFixed(0)}ms${cached ? ', cached' : ''})`)
      }
      
      return result
      
    } catch (err) {
      error = err
      const duration = performance.now() - startTime
      if (this.config.metrics.enabled) {
        this.metrics.recordRequest(fullUrl, duration, cached, error)
      }
      
      // Log errors if configured
      if (this.config.debug.logErrors) {
        console.error(`❌ Request failed: ${method} ${fullUrl}`, err)
      }
      
      // Create error context
      const errorContext: ErrorContext = {
        method,
        duration
      }
      
      throw enhanceError(err, fullUrl, errorContext)
    }
  }
  
  private async executeWithRetry<
    Method extends string,
    Url extends string,
    TResponse = EndpointResponse<TEndpoints, Method, Url>
  >(
    fullUrl: string,
    options: any,
    originalUrl: Url,
    method: Method
  ): Promise<{ data: TResponse; response: Response }> {
    let lastError: any
    const maxAttempts = method === 'GET' ? (this.config.retry.maxAttempts || 1) : 1
    const startTime = performance.now()
    
    for (let attempt = 0; attempt < maxAttempts; attempt++) {
      try {
        const attemptStartTime = performance.now()
        
        // Use circuit breaker if enabled
        const executeRequest = async () => {
          const response = await fetch(fullUrl, options)
          
          if (!response.ok) {
            const errorContext: ErrorContext = {
              method,
              attempt: attempt + 1,
              duration: performance.now() - attemptStartTime
            }
            throw createHttpError(response, fullUrl, errorContext)
          }
          
          const data = await response.json()
          
          // Record response for type inference
          this.recordResponse(originalUrl, method, data)
          
          // Cache successful GET requests
          if (method === 'GET' && this.config.cache.enabled) {
            this.cache.set(`${method}:${fullUrl}`, data, this.config.cache.ttl)
          }
          
          // Process through response interceptors
          const processedResponse = await this.interceptors.processResponse({ data, response })

          return processedResponse as { data: TResponse; response: Response }
        }
        
        // Execute with or without circuit breaker
        if (this.config.circuit.enabled) {
          return await this.circuitBreaker.execute(executeRequest, fullUrl)
        } else {
          return await executeRequest()
        }
      } catch (err) {
        lastError = err
        
        // Check if error is retryable based on config
        const error = err as any
        const isRetryableStatus = error.status && (this.config.retry.retryableStatuses?.includes(error.status) || false)
        const isNetworkError = !error.status && error.type === 'network'
        
        if (!isRetryableStatus && !isNetworkError) {
          // Add error context for non-retryable errors
          if (!error.attempt) {
            const errorContext: ErrorContext = {
              method,
              attempt: attempt + 1,
              duration: performance.now() - startTime
            }
            throw enhanceError(error, fullUrl, errorContext)
          }
          throw error
        }
        
        // Wait before retry (except on last attempt)
        if (attempt < maxAttempts - 1) {
          const delays = this.config.retry.delays || []
          const delay = delays[attempt] || delays[delays.length - 1] || 1000
          
          // Respect Retry-After header if present
          if (error.retryAfter) {
            await this.delay(error.retryAfter)
          } else {
            await this.delay(delay)
          }
        }
      }
    }
    
    // Add final error context
    if (lastError && !lastError.attempt) {
      const errorContext: ErrorContext = {
        method,
        duration: performance.now() - startTime
      }
      if (maxAttempts !== undefined) {
        errorContext.attempt = maxAttempts
      }
      throw enhanceError(lastError, fullUrl, errorContext)
    }
    
    throw lastError || new Error('Request failed after retries')
  }
  
  private async delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms))
  }
  
  // REAL type registry access
  getTypeInfo(endpoint: string): any {
    return this.typeRegistry[endpoint]
  }
  
  getAllTypes(): TypeRegistry {
    return { ...this.typeRegistry }
  }

  async exportTypes(options: TypeSnapshotOptions = {}): Promise<string> {
    const generator = new TypeDeclarationGenerator(this.typeRegistry)
    if (options.outFile) {
      return generator.writeToFile(options)
    }
    return generator.generate(options)
  }
  
  getInferenceConfidence(endpoint: string): number {
    return this.typeInference.getConfidence(endpoint)
  }
  
  // Advanced features
  addRequestInterceptor(fn: (config: any) => any): void {
    this.interceptors.addRequestInterceptor(fn)
  }
  
  addResponseInterceptor(fn: (response: any) => any): void {
    this.interceptors.addResponseInterceptor(fn)
  }
  
  getMetrics() {
    return this.metrics.getStats()
  }
  
  // Streaming support
  async stream(url: string, options: RequestInit = {}): Promise<ReadableStream> {
    const fullUrl = this.resolveUrl(url)
    
    // Merge with default headers
    const requestOptions: RequestInit = {
      ...options,
      headers: {
        ...this.config.request.headers,
        ...options.headers
      }
    }
    
    // Add timeout if configured
    if (this.config.request.timeout && !requestOptions.signal) {
      const controller = new AbortController()
      setTimeout(() => controller.abort(), this.config.request.timeout)
      requestOptions.signal = controller.signal
    }
    
    const response = await fetch(fullUrl, requestOptions)
    if (!response.ok) {
      const error = createHttpError(response, fullUrl, { method: options.method || 'GET' })
      throw error
    }
    if (!response.body) throw new Error('No response body')
    return response.body
  }
  
  async *streamJSON(url: string, options: RequestInit = {}): AsyncGenerator<any> {
    const stream = await this.stream(url, options)
    const reader = stream.getReader()
    const decoder = new TextDecoder()
    
    let buffer = ''
    while (true) {
      const { done, value } = await reader.read()
      if (done) break
      
      buffer += decoder.decode(value, { stream: true })
      const lines = buffer.split('\n')
      buffer = lines.pop() || ''
      
      for (const line of lines) {
        if (line.trim()) {
          try {
            yield JSON.parse(line)
          } catch {
            // Skip invalid JSON lines
          }
        }
      }
    }
  }
  
  // Server-Sent Events (SSE) support for AI/ML streaming
  async *streamSSE(url: string, options: RequestInit = {}): AsyncGenerator<{
    event?: string
    data: any
    id?: string
    retry?: number
  }> {
    const stream = await this.stream(url, {
      ...options,
      headers: {
        ...options.headers,
        'Accept': 'text/event-stream',
        'Cache-Control': 'no-cache'
      }
    })
    
    const reader = stream.getReader()
    const decoder = new TextDecoder()
    let buffer = ''
    
    while (true) {
      const { done, value } = await reader.read()
      if (done) break
      
      buffer += decoder.decode(value, { stream: true })
      const lines = buffer.split('\n')
      buffer = lines.pop() || ''
      
      let event: any = {}
      for (const line of lines) {
        if (line.trim() === '') {
          // Empty line signals end of event
          if (event.data !== undefined) {
            // Parse data if it's JSON
            try {
              event.data = JSON.parse(event.data)
            } catch {
              // Keep as string if not JSON
            }
            yield event
            event = {}
          }
        } else if (line.startsWith('event:')) {
          event.event = line.slice(6).trim()
        } else if (line.startsWith('data:')) {
          const data = line.slice(5).trim()
          event.data = event.data ? event.data + '\n' + data : data
        } else if (line.startsWith('id:')) {
          event.id = line.slice(3).trim()
        } else if (line.startsWith('retry:')) {
          event.retry = parseInt(line.slice(6).trim(), 10)
        }
      }
    }
  }
  
  // AI/ML optimized batch request method
  async batch<T = unknown>(
    requests: Array<{
      method?: string
      url: string
      body?: any
      headers?: Record<string, string>
    }>,
    options: {
      maxConcurrency?: number
      throwOnError?: boolean
    } = {}
  ): Promise<Array<{ data?: T; error?: any; response?: Response }>> {
    const { maxConcurrency = 5, throwOnError = false } = options
    const results: Array<{ data?: T; error?: any; response?: Response }> = []
    
    // Process in chunks for controlled concurrency
    for (let i = 0; i < requests.length; i += maxConcurrency) {
      const chunk = requests.slice(i, i + maxConcurrency)
      const chunkPromises = chunk.map(async (req) => {
        try {
          const method = req.method || 'GET'
          const init: RequestInit = { headers: req.headers || {} }
          let sample: unknown
          if (req.body !== undefined) {
            const prepared = prepareBodyPayload(req.body)
            init.body = prepared.bodyInit
            sample = prepared.sample
          }
          const result = await this.request<string, string, T>(method, req.url, init, sample)
          return { data: result.data, response: result.response }
        } catch (error) {
          if (throwOnError) throw error
          return { error }
        }
      })
      
      const chunkResults = await Promise.all(chunkPromises)
      results.push(...chunkResults)
    }
    
    return results
  }
  
  // Streaming file upload for large models/datasets
  async uploadStream(
    url: string,
    stream: ReadableStream | AsyncIterable<Uint8Array>,
    options: {
      filename?: string
      contentType?: string
      onProgress?: (loaded: number) => void
    } & RequestInit = {}
  ): Promise<{ data: any; response: Response }> {
    const { filename = 'upload', contentType = 'application/octet-stream', onProgress, ...requestOptions } = options
    
    // Convert AsyncIterable to ReadableStream if needed
    let bodyStream: ReadableStream
    if (stream instanceof ReadableStream) {
      bodyStream = stream
    } else {
      bodyStream = new ReadableStream({
        async start(controller) {
          for await (const chunk of stream) {
            controller.enqueue(chunk)
            if (onProgress) {
              onProgress(chunk.length)
            }
          }
          controller.close()
        }
      })
    }
    
    return this.request('POST', url, {
      ...requestOptions,
      body: bodyStream,
      headers: {
        'Content-Type': contentType,
        'X-Filename': filename,
        ...requestOptions.headers
      }
    })
  }
  
  // File upload support
  async upload(url: string, file: File | Blob, options: RequestInit = {}): Promise<{ data: any; response: Response }> {
    const formData = new FormData()
    formData.append('file', file)
    
    return this.request('POST', url, {
      ...options,
      body: formData,
      headers: {
        // Don't set Content-Type for FormData - browser will set it with boundary
        ...options.headers
      }
    })
  }
  
  // GraphQL support
  async graphql(url: string, query: string, variables?: any): Promise<{ data: any; response: Response }> {
    return this.post(url, {
      query,
      variables
    })
  }
  
  // Circuit breaker control
  resetCircuitBreaker(): void {
    this.circuitBreaker.reset()
  }
  
  // Advanced features
  
  // Streaming with automatic reconnection for resilient SSE
  async *streamWithReconnect(
    url: string,
    options: RequestInit & {
      maxReconnects?: number
      reconnectDelay?: number
      lastEventId?: string
    } = {}
  ): AsyncGenerator<any> {
    const { maxReconnects = 3, reconnectDelay = 1000, lastEventId, ...requestOptions } = options
    let reconnects = 0
    let currentEventId = lastEventId
    
    while (reconnects <= maxReconnects) {
      try {
        const headers = {
          ...requestOptions.headers,
          ...(currentEventId ? { 'Last-Event-ID': currentEventId } : {})
        }
        
        const stream = this.streamSSE(url, { ...requestOptions, headers })
        
        for await (const event of stream) {
          if (event.id) {
            currentEventId = event.id
          }
          yield event
        }
        
        // Stream ended normally
        break
      } catch (error) {
        reconnects++
        if (reconnects > maxReconnects) {
          throw error
        }
        
        await this.delay(reconnectDelay * reconnects)
      }
    }
  }
  
  // Race multiple endpoints - first successful response wins
  async race<T>(
    requests: Array<{
      method?: string
      url: string
      body?: any
      headers?: Record<string, string>
    }>
  ): Promise<{ data: T; response: Response; winner: number }> {
    if (requests.length === 0) {
      throw new Error('No requests provided to race')
    }
    
    const promises = requests.map(async (req, index) => {
      const method = req.method || 'GET'
      const init: RequestInit = { headers: req.headers || {} }
      let sample: unknown
      if (req.body !== undefined) {
        const prepared = prepareBodyPayload(req.body)
        init.body = prepared.bodyInit
        sample = prepared.sample
      }
      const result = await this.request<string, string, T>(method, req.url, init, sample)
      return { ...result, winner: index }
    })
    
    return Promise.race(promises)
  }
  
  // Parallel requests with Web Workers for true parallelism
  async parallel<T>(
    requests: Array<{
      method?: string
      url: string
      body?: any
      headers?: Record<string, string>
    }>,
    options: {
      workers?: boolean // Use Web Workers (default: true if available)
      maxWorkers?: number // Max concurrent workers (default: 4)
      fallbackToMain?: boolean // Fallback to main thread if workers fail
    } = {}
  ): Promise<Array<{ data?: T; error?: any; response?: Response }>> {
    const { 
      workers = typeof Worker !== 'undefined',
      maxWorkers = 4,
      fallbackToMain = true
    } = options
    
    // Use Web Workers if available and requested
    if (workers && typeof Worker !== 'undefined') {
      try {
        return await this.parallelWithWorkers(requests, maxWorkers)
      } catch (error) {
        if (!fallbackToMain) throw error
        // Fall through to main thread execution
      }
    }
    
    // Fallback to Promise.all on main thread
    return Promise.all(
      requests.map(async (req) => {
        try {
          const method = req.method || 'GET'
          const init: RequestInit = { headers: req.headers || {} }
          let sample: unknown
          if (req.body !== undefined) {
            const prepared = prepareBodyPayload(req.body)
            init.body = prepared.bodyInit
            sample = prepared.sample
          }
          const result = await this.request<string, string, T>(method, req.url, init, sample)
          return { data: result.data, response: result.response }
        } catch (error) {
          return { error }
        }
      })
    )
  }
  
  private async parallelWithWorkers<T>(
    requests: Array<any>,
    maxWorkers: number
  ): Promise<Array<{ data?: T; error?: any; response?: Response }>> {
    // Create worker pool
    const workerCode = `
      self.onmessage = async (e) => {
        const { id, method, url, body, headers } = e.data
        try {
          const response = await fetch(url, {
            method,
            headers,
            body: body ? JSON.stringify(body) : undefined
          })
          const data = await response.json()
          self.postMessage({ 
            id, 
            success: true, 
            data, 
            status: response.status,
            headers: Object.fromEntries(response.headers.entries())
          })
        } catch (error) {
          self.postMessage({ 
            id, 
            success: false, 
            error: error.message 
          })
        }
      }
    `
    
    const blob = new Blob([workerCode], { type: 'application/javascript' })
    const workerUrl = URL.createObjectURL(blob)
    const workers: Worker[] = []
    
    try {
      // Create worker pool
      for (let i = 0; i < Math.min(maxWorkers, requests.length); i++) {
        workers.push(new Worker(workerUrl))
      }
      
      // Process requests
      const results = await new Promise<Array<any>>((resolve, reject) => {
        const responses: Array<any> = new Array(requests.length)
        let completed = 0
        let nextRequest = 0
        
        const assignWork = (worker: Worker, workerIndex: number) => {
          if (nextRequest >= requests.length) return
          
          const requestIndex = nextRequest++
          const request = requests[requestIndex]
          
          worker.onmessage = (e) => {
            responses[requestIndex] = e.data.success 
              ? { data: e.data.data, response: { status: e.data.status, headers: e.data.headers } }
              : { error: new Error(e.data.error) }
            
            completed++
            if (completed === requests.length) {
              resolve(responses)
            } else {
              assignWork(worker, workerIndex)
            }
          }
          
          worker.onerror = (error) => {
            responses[requestIndex] = { error }
            completed++
            if (completed === requests.length) {
              resolve(responses)
            } else {
              assignWork(worker, workerIndex)
            }
          }
          
          worker.postMessage({
            id: requestIndex,
            method: request.method || 'GET',
            url: this.resolveUrl(request.url),
            body: request.body,
            headers: { ...this.config.request.headers, ...request.headers }
          })
        }
        
        // Start workers
        workers.forEach((worker, index) => assignWork(worker, index))
      })
      
      return results
    } finally {
      // Cleanup
      workers.forEach(w => w.terminate())
      URL.revokeObjectURL(workerUrl)
    }
  }
  
  // Resumable upload with adaptive chunking
  async uploadResumable(
    url: string,
    file: File | Blob,
    options: {
      chunkSize?: number | 'adaptive'
      maxParallelChunks?: number
      onProgress?: (progress: {
        loaded: number
        total: number
        percent: number
        speed: string
        eta: number
      }) => void
      resumeKey?: string
      metadata?: Record<string, any>
    } = {}
  ): Promise<{ data: any; response: Response }> {
    const {
      chunkSize = 'adaptive',
      maxParallelChunks = 3,
      onProgress,
      resumeKey = `upload-${(file as File).name || 'blob'}-${file.size}`,
      metadata = {}
    } = options
    
    // Check for existing upload session
    const session = await this.getUploadSession(resumeKey)
    const startByte = session?.uploadedBytes || 0
    
    // Calculate chunk size
    const adaptiveChunkSize = this.calculateAdaptiveChunkSize(file.size)
    const actualChunkSize = chunkSize === 'adaptive' ? adaptiveChunkSize : chunkSize
    
    // Upload state
    const totalChunks = Math.ceil((file.size - startByte) / actualChunkSize)
    const uploadState = {
      startTime: Date.now(),
      uploadedBytes: startByte,
      uploadedChunks: new Set<number>(),
      errors: new Map<number, number>() // chunk index -> retry count
    }
    
    // Progress tracking
    const updateProgress = () => {
      if (!onProgress) return
      
      const loaded = uploadState.uploadedBytes
      const total = file.size
      const percent = Math.round((loaded / total) * 100)
      const elapsed = Date.now() - uploadState.startTime
      const speed = loaded / (elapsed / 1000) // bytes per second
      const remaining = total - loaded
      const eta = remaining / speed // seconds
      
      onProgress({
        loaded,
        total,
        percent,
        speed: this.formatBytes(speed) + '/s',
        eta: Math.round(eta)
      })
    }
    
    // Upload chunks with parallel processing
    const uploadChunk = async (chunkIndex: number): Promise<void> => {
      const start = startByte + (chunkIndex * actualChunkSize)
      const end = Math.min(start + actualChunkSize, file.size)
      const chunk = file.slice(start, end)
      
      const formData = new FormData()
      formData.append('chunk', chunk)
      formData.append('chunkIndex', chunkIndex.toString())
      formData.append('totalChunks', totalChunks.toString())
      formData.append('resumeKey', resumeKey)
      formData.append('metadata', JSON.stringify(metadata))
      
      try {
        await this.post(url, formData, {
          headers: {
            'Content-Range': `bytes ${start}-${end - 1}/${file.size}`,
            'X-Chunk-Index': chunkIndex.toString(),
            'X-Total-Chunks': totalChunks.toString(),
            'X-Resume-Key': resumeKey
          }
        })
        
        uploadState.uploadedChunks.add(chunkIndex)
        uploadState.uploadedBytes = startByte + (uploadState.uploadedChunks.size * actualChunkSize)
        updateProgress()
        
        // Save progress for resume
        await this.saveUploadSession(resumeKey, {
          uploadedBytes: uploadState.uploadedBytes,
          uploadedChunks: Array.from(uploadState.uploadedChunks),
          totalChunks,
          chunkSize: actualChunkSize,
          fileSize: file.size,
          fileName: (file as File).name || 'blob'
        })
      } catch (error) {
        const retries = uploadState.errors.get(chunkIndex) || 0
        if (retries < 3) {
          uploadState.errors.set(chunkIndex, retries + 1)
          await this.delay(Math.pow(2, retries) * 1000) // Exponential backoff
          return uploadChunk(chunkIndex) // Retry
        }
        throw error
      }
    }
    
    // Process chunks in parallel batches
    const chunks: number[] = []
    for (let i = 0; i < totalChunks; i++) {
      if (!session?.uploadedChunks?.includes(i)) {
        chunks.push(i)
      }
    }
    
    for (let i = 0; i < chunks.length; i += maxParallelChunks) {
      const batch = chunks.slice(i, i + maxParallelChunks)
      await Promise.all(batch.map(uploadChunk))
    }
    
    // Finalize upload
    const finalResponse = await this.post(url, {
      action: 'finalize',
      resumeKey,
      totalChunks,
      metadata
    })
    
    // Clear session
    await this.clearUploadSession(resumeKey)
    
    return finalResponse
  }
  
  // Helper methods for resumable uploads
  private async getUploadSession(key: string): Promise<any> {
    if (typeof localStorage === 'undefined') return null
    const data = localStorage.getItem(`typedfetch-upload-${key}`)
    return data ? JSON.parse(data) : null
  }
  
  private async saveUploadSession(key: string, data: any): Promise<void> {
    if (typeof localStorage === 'undefined') return
    localStorage.setItem(`typedfetch-upload-${key}`, JSON.stringify(data))
  }
  
  private async clearUploadSession(key: string): Promise<void> {
    if (typeof localStorage === 'undefined') return
    localStorage.removeItem(`typedfetch-upload-${key}`)
  }
  
  private calculateAdaptiveChunkSize(fileSize: number): number {
    // Adaptive chunk sizing based on file size and connection quality
    const minChunk = 256 * 1024 // 256KB
    const maxChunk = 10 * 1024 * 1024 // 10MB
    
    if (fileSize < 10 * 1024 * 1024) return minChunk // Small files
    if (fileSize < 100 * 1024 * 1024) return 1024 * 1024 // Medium files: 1MB chunks
    if (fileSize < 1024 * 1024 * 1024) return 5 * 1024 * 1024 // Large files: 5MB chunks
    return maxChunk // Very large files
  }
  
  private formatBytes(bytes: number): string {
    if (bytes < 1024) return bytes + ' B'
    if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + ' KB'
    if (bytes < 1024 * 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(2) + ' MB'
    return (bytes / (1024 * 1024 * 1024)).toFixed(2) + ' GB'
  }
  
  // Bandwidth-throttled requests
  async throttled<T>(
    fn: () => Promise<T>,
    options: {
      bandwidth?: number | string // e.g., 1048576 (1MB/s) or '1MB/s'
      burst?: number // Allow burst up to this many bytes
    } = {}
  ): Promise<T> {
    const { bandwidth = '1MB/s', burst = 0 } = options
    
    // Parse bandwidth string
    const bytesPerSecond = typeof bandwidth === 'string' 
      ? this.parseBandwidth(bandwidth)
      : bandwidth
    
    // Token bucket algorithm
    const bucket = {
      tokens: burst || bytesPerSecond,
      lastRefill: Date.now(),
      capacity: burst || bytesPerSecond
    }
    
    // Refill tokens
    const refill = () => {
      const now = Date.now()
      const elapsed = (now - bucket.lastRefill) / 1000
      const tokensToAdd = elapsed * bytesPerSecond
      bucket.tokens = Math.min(bucket.capacity, bucket.tokens + tokensToAdd)
      bucket.lastRefill = now
    }
    
    // Wait for tokens
    const waitForTokens = async (needed: number) => {
      refill()
      while (bucket.tokens < needed) {
        const deficit = needed - bucket.tokens
        const waitTime = (deficit / bytesPerSecond) * 1000
        await this.delay(Math.min(waitTime, 100)) // Check every 100ms
        refill()
      }
      bucket.tokens -= needed
    }
    
    // TODO: Implement actual throttling for the response stream
    // For now, just execute the function
    return fn()
  }
  
  private parseBandwidth(bandwidth: string): number {
    const match = bandwidth.match(/^([\d.]+)\s*(B|KB|MB|GB)\/s$/i)
    if (!match) throw new Error(`Invalid bandwidth format: ${bandwidth}`)
    
    const value = parseFloat(match[1] || '0')
    const unit = match[2]?.toUpperCase() || 'B'
    
    const multipliers: Record<string, number> = {
      'B': 1,
      'KB': 1024,
      'MB': 1024 * 1024,
      'GB': 1024 * 1024 * 1024
    }
    
    return value * (multipliers[unit] || 1)
  }
}
