import type { RequestOptions } from "../types"

/**
 * Base context object shared by all interceptors
 */
interface BaseInterceptorContext {
  url: string
  options: RequestOptions
}

/**
 * Context object passed to request interceptors
 */
export interface RequestInterceptorContext extends BaseInterceptorContext {}

/**
 * Context object passed to response interceptors
 */
export interface ResponseInterceptorContext extends BaseInterceptorContext {
  response: Response
}

/**
 * Context object passed to error interceptors
 */
export interface ErrorInterceptorContext extends BaseInterceptorContext {
  response: Response | null
  error: Error
}

/**
 * Request interceptor function type
 */
export type RequestInterceptor = (
  ctx: RequestInterceptorContext,
) =>
  | Promise<{ url: string, options: RequestOptions }> |
  { url: string, options: RequestOptions }

/**
 * Response interceptor function type
 */
export type ResponseInterceptor = (
  ctx: ResponseInterceptorContext,
) => Promise<Response> | Response

/**
 * Error interceptor function type
 */
export type ErrorInterceptor = (
  ctx: ErrorInterceptorContext,
) => Promise<Error | void> | Error | void

/**
 * Interceptor manager for handling request/response middleware
 */
export class InterceptorManager {
  private requestInterceptors: RequestInterceptor[] = []
  private responseInterceptors: ResponseInterceptor[] = []
  private errorInterceptors: ErrorInterceptor[] = []

  /**
   * Generic method to add an interceptor to any array and return a cleanup function
   */
  private addInterceptor<T>(interceptor: T, interceptors: T[]): () => void {
    interceptors.push(interceptor)
    return () => {
      const index = interceptors.indexOf(interceptor)
      if (index !== -1) {
        interceptors.splice(index, 1)
      }
    }
  }

  /**
   * Add a request interceptor
   */
  addRequestInterceptor(interceptor: RequestInterceptor): () => void {
    return this.addInterceptor(interceptor.bind(null), this.requestInterceptors)
  }

  /**
   * Add a response interceptor
   */
  addResponseInterceptor(interceptor: ResponseInterceptor): () => void {
    return this.addInterceptor(interceptor.bind(null), this.responseInterceptors)
  }

  /**
   * Add an error interceptor
   */
  addErrorInterceptor(interceptor: ErrorInterceptor): () => void {
    return this.addInterceptor(interceptor.bind(null), this.errorInterceptors)
  }

  /**
   * Process request through all request interceptors
   */
  async processRequest(
    url: string,
    options: RequestOptions,
  ): Promise<{ url: string, options: RequestOptions }> {
    let currentUrl = url
    let currentOptions = options

    for (const interceptor of this.requestInterceptors) {
      const ctx: RequestInterceptorContext = {
        url: currentUrl,
        options: currentOptions,
      }
      const result = (await interceptor(ctx)) || ctx
      currentUrl = result.url
      currentOptions = result.options
    }

    return { url: currentUrl, options: currentOptions }
  }

  /**
   * Process response through all response interceptors
   */
  async processResponse(
    response: Response,
    url: string,
    options: RequestOptions,
  ): Promise<Response> {
    let currentResponse = response

    for (const interceptor of this.responseInterceptors) {
      const ctx: ResponseInterceptorContext = {
        url,
        options,
        response: currentResponse,
      }
      const returnedResponse = await interceptor(ctx)
      if (returnedResponse instanceof Response) {
        currentResponse = returnedResponse
      }
      // If interceptor returns undefined, it means the response should not be modified
    }

    return currentResponse
  }

  /**
   * Process error through all error interceptors
   */
  async processError(
    error: Error,
    response: Response | null,
    url: string,
    options: RequestOptions,
  ): Promise<Error | void> {
    let currentError: Error | void = error

    for (const interceptor of this.errorInterceptors) {
      const ctx: ErrorInterceptorContext = {
        url,
        options,
        response,
        error: currentError || error,
      }
      const result = await interceptor(ctx)
      if (result !== undefined) {
        currentError = result
      } else {
        // If interceptor returns undefined, it means the error should be handled/suppressed
        currentError = undefined
        break
      }
    }

    return currentError
  }

  /**
   * Clear all interceptors
   */
  clear(): void {
    this.requestInterceptors = []
    this.responseInterceptors = []
    this.errorInterceptors = []
  }
}

/**
 * Common interceptors for Follow API
 */
export const commonInterceptors = {
  /**
   * Add authentication token to requests
   */
  addAuthToken: (token: string): RequestInterceptor => {
    return (ctx) => {
      return {
        url: ctx.url,
        options: {
          ...ctx.options,
          headers: {
            ...ctx.options.headers,
            Authorization: `Bearer ${token}`,
          },
        },
      }
    }
  },

  /**
   * Log all requests
   */
  logRequests: (logger: {
    log: (message: string) => void
  }): RequestInterceptor => {
    return (ctx) => {
      logger.log(`Request: ${ctx.options.method || "GET"} ${ctx.url}`)
      return { url: ctx.url, options: ctx.options }
    }
  },

  /**
   * Log all responses
   */
  logResponses: (logger: {
    log: (message: string) => void
  }): ResponseInterceptor => {
    return (ctx) => {
      logger.log(
        `Response: ${ctx.response.status} ${ctx.options.method || "GET"} ${ctx.url}`,
      )
      return ctx.response
    }
  },

  /**
   * Retry failed requests
   */
  retryOnError: (maxRetries = 3, delay = 1000): ErrorInterceptor => {
    const retryCount = new WeakMap<Error, number>()

    return async (ctx) => {
      const currentRetries = retryCount.get(ctx.error) || 0

      if (currentRetries < maxRetries) {
        retryCount.set(ctx.error, currentRetries + 1)

        // Wait before retry
        await new Promise((resolve) =>
          setTimeout(resolve, delay * (currentRetries + 1)),
        )

        // Return undefined to indicate retry should happen
        return
      }

      // Max retries exceeded, return the error
      return ctx.error
    }
  },
}
