// Copyright 2018-2026 the Deno authors. MIT license.

use std::borrow::Cow;

use deno_core::v8;

#[derive(Debug, thiserror::Error, deno_error::JsError)]
#[property("code" = self.code())]
pub enum Error {
  #[class(type)]
  #[error("{0}")]
  InvalidArgType(Cow<'static, str>),
  #[class(type)]
  #[error("{0}")]
  InvalidArgValue(Cow<'static, str>),
  #[class(range)]
  #[error("{0}")]
  OutOfRange(Cow<'static, str>),
  #[class(type)]
  #[error("Exception during property access")]
  V8Exception,
}

impl Error {
  pub fn code(&self) -> ErrorCode {
    match self {
      Self::InvalidArgType(_) => ErrorCode::ERR_INVALID_ARG_TYPE,
      Self::InvalidArgValue(_) => ErrorCode::ERR_INVALID_ARG_VALUE,
      Self::OutOfRange(_) => ErrorCode::ERR_OUT_OF_RANGE,
      Self::V8Exception => ErrorCode::ERR_INVALID_ARG_TYPE,
    }
  }
}

#[allow(non_camel_case_types, reason = "matches Node.js error code naming")]
pub enum ErrorCode {
  ERR_INVALID_ARG_TYPE,
  ERR_INVALID_ARG_VALUE,
  ERR_OUT_OF_RANGE,
}

impl std::fmt::Display for ErrorCode {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    write!(f, "{}", self.as_str())
  }
}

impl ErrorCode {
  pub fn as_str(&self) -> &str {
    match self {
      Self::ERR_INVALID_ARG_TYPE => "ERR_INVALID_ARG_TYPE",
      Self::ERR_INVALID_ARG_VALUE => "ERR_INVALID_ARG_VALUE",
      Self::ERR_OUT_OF_RANGE => "ERR_OUT_OF_RANGE",
    }
  }
}

impl From<ErrorCode> for deno_error::PropertyValue {
  fn from(code: ErrorCode) -> Self {
    deno_error::PropertyValue::from(code.as_str().to_string())
  }
}

pub(super) fn sql_str(
  _: &mut v8::PinScope<'_, '_>,
  value: v8::Local<v8::Value>,
) -> Result<(), Error> {
  if value.is_string() {
    return Ok(());
  }

  Err(Error::InvalidArgType(
    "The \"sql\" argument must be a string.".into(),
  ))
}

pub(super) fn changeset_buffer(
  _: &mut v8::PinScope<'_, '_>,
  value: v8::Local<v8::Value>,
) -> Result<(), Error> {
  if value.is_uint8_array() {
    return Ok(());
  }

  Err(Error::InvalidArgType(
    "The \"changeset\" argument must be a Uint8Array.".into(),
  ))
}

pub(super) fn name_str(
  _: &mut v8::PinScope<'_, '_>,
  value: v8::Local<v8::Value>,
) -> Result<(), Error> {
  if value.is_string() {
    return Ok(());
  }

  Err(Error::InvalidArgType(
    "The \"name\" argument must be a string.".into(),
  ))
}

pub(super) fn path_str(
  _: &mut v8::PinScope<'_, '_>,
  value: v8::Local<v8::Value>,
) -> Result<(), Error> {
  if value.is_string() {
    return Ok(());
  }

  Err(Error::InvalidArgType(
    "The \"path\" argument must be a string.".into(),
  ))
}

pub(super) fn allow_bare_named_params_bool(
  _: &mut v8::PinScope<'_, '_>,
  value: v8::Local<v8::Value>,
) -> Result<(), Error> {
  if value.is_boolean() {
    return Ok(());
  }

  Err(Error::InvalidArgType(
    "The \"allowBareNamedParameters\" argument must be a boolean.".into(),
  ))
}

pub(super) fn allow_unknown_named_params_bool(
  _: &mut v8::PinScope<'_, '_>,
  value: v8::Local<v8::Value>,
) -> Result<(), Error> {
  if value.is_boolean() {
    return Ok(());
  }

  Err(Error::InvalidArgType(
    "The \"enabled\" argument must be a boolean.".into(),
  ))
}

pub(super) fn read_big_ints_bool(
  _: &mut v8::PinScope<'_, '_>,
  value: v8::Local<v8::Value>,
) -> Result<(), Error> {
  if value.is_boolean() {
    return Ok(());
  }

  Err(Error::InvalidArgType(
    "The \"readBigInts\" argument must be a boolean.".into(),
  ))
}

pub(super) fn return_arrays_bool(
  _: &mut v8::PinScope<'_, '_>,
  value: v8::Local<v8::Value>,
) -> Result<(), Error> {
  if value.is_boolean() {
    return Ok(());
  }

  Err(Error::InvalidArgType(
    "The \"returnArrays\" argument must be a boolean.".into(),
  ))
}

pub(super) fn active_bool(
  _: &mut v8::PinScope<'_, '_>,
  value: v8::Local<v8::Value>,
) -> Result<(), Error> {
  if value.is_boolean() {
    return Ok(());
  }

  Err(Error::InvalidArgType(
    "The \"active\" argument must be a boolean.".into(),
  ))
}
