'use strict'

const assert = require('node:assert')

/**
 * This takes care of revalidation requests we send to the origin. If we get
 *  a response indicating that what we have is cached (via a HTTP 304), we can
 *  continue using the cached value. Otherwise, we'll receive the new response
 *  here, which we then just pass on to the next handler (most likely a
 *  CacheHandler). Note that this assumes the proper headers were already
 *  included in the request to tell the origin that we want to revalidate the
 *  response (i.e. if-modified-since).
 *
 * @see https://www.rfc-editor.org/rfc/rfc9111.html#name-validation
 *
 * @implements {import('../../types/dispatcher.d.ts').default.DispatchHandler}
 */
class CacheRevalidationHandler {
  #successful = false

  /**
   * @type {((boolean, any) => void) | null}
   */
  #callback

  /**
   * @type {(import('../../types/dispatcher.d.ts').default.DispatchHandler)}
   */
  #handler

  #context

  /**
   * @type {boolean}
   */
  #allowErrorStatusCodes

  /**
   * @param {(boolean) => void} callback Function to call if the cached value is valid
   * @param {import('../../types/dispatcher.d.ts').default.DispatchHandlers} handler
   * @param {boolean} allowErrorStatusCodes
   */
  constructor (callback, handler, allowErrorStatusCodes) {
    if (typeof callback !== 'function') {
      throw new TypeError('callback must be a function')
    }

    this.#callback = callback
    this.#handler = handler
    this.#allowErrorStatusCodes = allowErrorStatusCodes
  }

  onRequestStart (_, context) {
    this.#successful = false
    this.#context = context
  }

  onRequestUpgrade (controller, statusCode, headers, socket) {
    this.#handler.onRequestUpgrade?.(controller, statusCode, headers, socket)
  }

  onResponseStart (
    controller,
    statusCode,
    headers,
    statusMessage
  ) {
    assert(this.#callback != null)

    // https://www.rfc-editor.org/rfc/rfc9111.html#name-handling-a-validation-respo
    // https://datatracker.ietf.org/doc/html/rfc5861#section-4
    this.#successful = statusCode === 304 ||
      (this.#allowErrorStatusCodes && statusCode >= 500 && statusCode <= 504)
    this.#callback(this.#successful, this.#context)
    this.#callback = null

    if (this.#successful) {
      return true
    }

    this.#handler.onRequestStart?.(controller, this.#context)
    this.#handler.onResponseStart?.(
      controller,
      statusCode,
      headers,
      statusMessage
    )
  }

  onResponseData (controller, chunk) {
    if (this.#successful) {
      return
    }

    return this.#handler.onResponseData?.(controller, chunk)
  }

  onResponseEnd (controller, trailers) {
    if (this.#successful) {
      return
    }

    this.#handler.onResponseEnd?.(controller, trailers)
  }

  onResponseError (controller, err) {
    if (this.#successful) {
      return
    }

    if (this.#callback) {
      this.#callback(false)
      this.#callback = null
    }

    if (typeof this.#handler.onResponseError === 'function') {
      this.#handler.onResponseError(controller, err)
    } else {
      throw err
    }
  }
}

module.exports = CacheRevalidationHandler
