import { Readable, Writable } from 'node:stream'

export default CacheHandler

declare namespace CacheHandler {
  export type CacheMethods = 'GET' | 'HEAD' | 'OPTIONS' | 'TRACE'

  export interface CacheHandlerOptions {
    store: CacheStore

    cacheByDefault?: number

    type?: CacheOptions['type']
  }

  export interface CacheOptions {
    store?: CacheStore

    /**
     * The methods to cache
     * Note we can only cache safe methods. Unsafe methods (i.e. PUT, POST)
     *  invalidate the cache for a origin.
     * @see https://www.rfc-editor.org/rfc/rfc9111.html#name-invalidating-stored-respons
     * @see https://www.rfc-editor.org/rfc/rfc9110#section-9.2.1
     */
    methods?: CacheMethods[]

    /**
     * RFC9111 allows for caching responses that we aren't explicitly told to
     *  cache or to not cache.
     * @see https://www.rfc-editor.org/rfc/rfc9111.html#section-3-5
     * @default undefined
     */
    cacheByDefault?: number

    /**
     * TODO docs
     * @default 'shared'
     */
    type?: 'shared' | 'private'
  }

  export interface CacheControlDirectives {
    'max-stale'?: number;
    'min-fresh'?: number;
    'max-age'?: number;
    's-maxage'?: number;
    'stale-while-revalidate'?: number;
    'stale-if-error'?: number;
    public?: true;
    private?: true | string[];
    'no-store'?: true;
    'no-cache'?: true | string[];
    'must-revalidate'?: true;
    'proxy-revalidate'?: true;
    immutable?: true;
    'no-transform'?: true;
    'must-understand'?: true;
    'only-if-cached'?: true;
  }

  export interface CacheKey {
    origin: string
    method: string
    path: string
    headers?: Record<string, string | string[]>
  }

  export interface CacheValue {
    statusCode: number
    statusMessage: string
    headers: Record<string, string | string[]>
    vary?: Record<string, string | string[] | null>
    etag?: string
    cacheControlDirectives?: CacheControlDirectives
    cachedAt: number
    staleAt: number
    deleteAt: number
  }

  export interface DeleteByUri {
    origin: string
    method: string
    path: string
  }

  type GetResult = {
    statusCode: number
    statusMessage: string
    headers: Record<string, string | string[]>
    vary?: Record<string, string | string[] | null>
    etag?: string
    body?: Readable | Iterable<Buffer> | AsyncIterable<Buffer> | Buffer | Iterable<string> | AsyncIterable<string> | string
    cacheControlDirectives: CacheControlDirectives,
    cachedAt: number
    staleAt: number
    deleteAt: number
  }

  /**
   * Underlying storage provider for cached responses
   */
  export interface CacheStore {
    get(key: CacheKey): GetResult | Promise<GetResult | undefined> | undefined

    createWriteStream(key: CacheKey, val: CacheValue): Writable | undefined

    delete(key: CacheKey): void | Promise<void>
  }

  export interface MemoryCacheStoreOpts {
    /**
       * @default Infinity
       */
    maxCount?: number

    /**
     * @default Infinity
     */
    maxSize?: number

    /**
     * @default Infinity
     */
    maxEntrySize?: number

    errorCallback?: (err: Error) => void
  }

  export class MemoryCacheStore implements CacheStore {
    constructor (opts?: MemoryCacheStoreOpts)

    get (key: CacheKey): GetResult | Promise<GetResult | undefined> | undefined

    createWriteStream (key: CacheKey, value: CacheValue): Writable | undefined

    delete (key: CacheKey): void | Promise<void>
  }

  export interface SqliteCacheStoreOpts {
    /**
     * Location of the database
     * @default ':memory:'
     */
    location?: string

    /**
     * @default Infinity
     */
    maxCount?: number

    /**
     * @default Infinity
     */
    maxEntrySize?: number
  }

  export class SqliteCacheStore implements CacheStore {
    constructor (opts?: SqliteCacheStoreOpts)

    /**
     * Closes the connection to the database
     */
    close (): void

    get (key: CacheKey): GetResult | Promise<GetResult | undefined> | undefined

    createWriteStream (key: CacheKey, value: CacheValue): Writable | undefined

    delete (key: CacheKey): void | Promise<void>
  }
}
