export class InvalidOAuthClientNameError extends Error {
  override readonly name = 'InvalidOAuthClientNameError'
  readonly code = 'OAUTH_CLIENT_NAME_INVALID' as const

  constructor() {
    super('registerClient: name must be a non-empty string')
  }
}

export class InvalidOAuthRedirectUrisError extends Error {
  override readonly name = 'InvalidOAuthRedirectUrisError'
  readonly code = 'OAUTH_REDIRECT_URIS_INVALID' as const

  constructor() {
    super('registerClient: redirectUris must contain only absolute URLs')
  }
}

export class InvalidOAuthScopesError extends Error {
  override readonly name = 'InvalidOAuthScopesError'
  readonly code = 'OAUTH_SCOPES_INVALID' as const

  constructor() {
    super('registerClient: scopes must contain only non-empty strings')
  }
}

export class OAuthClientNotFoundError extends Error {
  override readonly name = 'OAuthClientNotFoundError'
  readonly code = 'OAUTH_CLIENT_NOT_FOUND' as const

  constructor(readonly clientId: string) {
    super(`oauth client not found: ${clientId}`)
  }
}

export class OAuthRedirectUriMismatchError extends Error {
  override readonly name = 'OAuthRedirectUriMismatchError'
  readonly code = 'OAUTH_REDIRECT_URI_MISMATCH' as const

  constructor(readonly redirectUri: string) {
    super(`oauth redirect URI mismatch: ${redirectUri}`)
  }
}

export class InvalidOAuthScopeError extends Error {
  override readonly name = 'InvalidOAuthScopeError'
  readonly code = 'OAUTH_SCOPE_INVALID' as const

  constructor(readonly scope: string) {
    super(`oauth scope is not allowed: ${scope}`)
  }
}

export class UnsupportedCodeChallengeMethodError extends Error {
  override readonly name = 'UnsupportedCodeChallengeMethodError'
  readonly code = 'OAUTH_UNSUPPORTED_CODE_CHALLENGE_METHOD' as const

  constructor(readonly codeChallengeMethod: string) {
    super(`oauth code challenge method must be S256: ${codeChallengeMethod}`)
  }
}

export class InvalidOAuthCodeChallengeError extends Error {
  override readonly name = 'InvalidOAuthCodeChallengeError'
  readonly code = 'OAUTH_CODE_CHALLENGE_INVALID' as const

  constructor() {
    super('oauth code challenge must be a non-empty string')
  }
}

export class OAuthCodeNotFoundError extends Error {
  override readonly name = 'OAuthCodeNotFoundError'
  readonly code = 'OAUTH_CODE_NOT_FOUND' as const

  constructor() {
    super('oauth authorization code not found')
  }
}

export class OAuthCodeAlreadyUsedError extends Error {
  override readonly name = 'OAuthCodeAlreadyUsedError'
  readonly code = 'OAUTH_CODE_ALREADY_USED' as const

  constructor() {
    super('oauth authorization code has already been used')
  }
}

export class OAuthCodeExpiredError extends Error {
  override readonly name = 'OAuthCodeExpiredError'
  readonly code = 'OAUTH_CODE_EXPIRED' as const

  constructor() {
    super('oauth authorization code has expired')
  }
}

export class OAuthCodeClientMismatchError extends Error {
  override readonly name = 'OAuthCodeClientMismatchError'
  readonly code = 'OAUTH_CODE_CLIENT_MISMATCH' as const

  constructor(readonly clientId: string) {
    super(`oauth authorization code was not issued for client: ${clientId}`)
  }
}

export class InvalidOAuthCodeVerifierError extends Error {
  override readonly name = 'InvalidOAuthCodeVerifierError'
  readonly code = 'OAUTH_CODE_VERIFIER_INVALID' as const

  constructor() {
    super('oauth code verifier does not match the stored S256 challenge')
  }
}

export class MissingOAuthClientSecretError extends Error {
  override readonly name = 'MissingOAuthClientSecretError'
  readonly code = 'OAUTH_CLIENT_SECRET_MISSING' as const

  constructor(readonly clientId: string) {
    super(`oauth client secret is required for confidential client: ${clientId}`)
  }
}

export class InvalidOAuthClientSecretError extends Error {
  override readonly name = 'InvalidOAuthClientSecretError'
  readonly code = 'OAUTH_CLIENT_SECRET_INVALID' as const

  constructor(readonly clientId: string) {
    super(`oauth client secret is invalid for confidential client: ${clientId}`)
  }
}

export type OAuthProviderError =
  | InvalidOAuthClientNameError
  | InvalidOAuthRedirectUrisError
  | InvalidOAuthScopesError
  | OAuthClientNotFoundError
  | OAuthRedirectUriMismatchError
  | InvalidOAuthScopeError
  | UnsupportedCodeChallengeMethodError
  | InvalidOAuthCodeChallengeError
  | OAuthCodeNotFoundError
  | OAuthCodeAlreadyUsedError
  | OAuthCodeExpiredError
  | OAuthCodeClientMismatchError
  | InvalidOAuthCodeVerifierError
  | MissingOAuthClientSecretError
  | InvalidOAuthClientSecretError

function hasErrorShape(error: unknown, name: string, code: string): boolean {
  return (
    typeof error === 'object' &&
    error !== null &&
    (error as { name?: unknown }).name === name &&
    (error as { code?: unknown }).code === code
  )
}

export function isOAuthProviderError(error: unknown): error is OAuthProviderError {
  return (
    isInvalidOAuthClientNameError(error) ||
    isInvalidOAuthRedirectUrisError(error) ||
    isInvalidOAuthScopesError(error) ||
    isOAuthClientNotFoundError(error) ||
    isOAuthRedirectUriMismatchError(error) ||
    isInvalidOAuthScopeError(error) ||
    isUnsupportedCodeChallengeMethodError(error) ||
    isInvalidOAuthCodeChallengeError(error) ||
    isOAuthCodeNotFoundError(error) ||
    isOAuthCodeAlreadyUsedError(error) ||
    isOAuthCodeExpiredError(error) ||
    isOAuthCodeClientMismatchError(error) ||
    isInvalidOAuthCodeVerifierError(error) ||
    isMissingOAuthClientSecretError(error) ||
    isInvalidOAuthClientSecretError(error)
  )
}

export function isInvalidOAuthClientNameError(
  error: unknown,
): error is InvalidOAuthClientNameError {
  return hasErrorShape(error, 'InvalidOAuthClientNameError', 'OAUTH_CLIENT_NAME_INVALID')
}

export function isInvalidOAuthRedirectUrisError(
  error: unknown,
): error is InvalidOAuthRedirectUrisError {
  return hasErrorShape(error, 'InvalidOAuthRedirectUrisError', 'OAUTH_REDIRECT_URIS_INVALID')
}

export function isInvalidOAuthScopesError(error: unknown): error is InvalidOAuthScopesError {
  return hasErrorShape(error, 'InvalidOAuthScopesError', 'OAUTH_SCOPES_INVALID')
}

export function isOAuthClientNotFoundError(error: unknown): error is OAuthClientNotFoundError {
  return hasErrorShape(error, 'OAuthClientNotFoundError', 'OAUTH_CLIENT_NOT_FOUND')
}

export function isOAuthRedirectUriMismatchError(
  error: unknown,
): error is OAuthRedirectUriMismatchError {
  return hasErrorShape(error, 'OAuthRedirectUriMismatchError', 'OAUTH_REDIRECT_URI_MISMATCH')
}

export function isInvalidOAuthScopeError(error: unknown): error is InvalidOAuthScopeError {
  return hasErrorShape(error, 'InvalidOAuthScopeError', 'OAUTH_SCOPE_INVALID')
}

export function isUnsupportedCodeChallengeMethodError(
  error: unknown,
): error is UnsupportedCodeChallengeMethodError {
  return hasErrorShape(
    error,
    'UnsupportedCodeChallengeMethodError',
    'OAUTH_UNSUPPORTED_CODE_CHALLENGE_METHOD',
  )
}

export function isInvalidOAuthCodeChallengeError(
  error: unknown,
): error is InvalidOAuthCodeChallengeError {
  return hasErrorShape(error, 'InvalidOAuthCodeChallengeError', 'OAUTH_CODE_CHALLENGE_INVALID')
}

export function isOAuthCodeNotFoundError(error: unknown): error is OAuthCodeNotFoundError {
  return hasErrorShape(error, 'OAuthCodeNotFoundError', 'OAUTH_CODE_NOT_FOUND')
}

export function isOAuthCodeAlreadyUsedError(error: unknown): error is OAuthCodeAlreadyUsedError {
  return hasErrorShape(error, 'OAuthCodeAlreadyUsedError', 'OAUTH_CODE_ALREADY_USED')
}

export function isOAuthCodeExpiredError(error: unknown): error is OAuthCodeExpiredError {
  return hasErrorShape(error, 'OAuthCodeExpiredError', 'OAUTH_CODE_EXPIRED')
}

export function isOAuthCodeClientMismatchError(
  error: unknown,
): error is OAuthCodeClientMismatchError {
  return hasErrorShape(error, 'OAuthCodeClientMismatchError', 'OAUTH_CODE_CLIENT_MISMATCH')
}

export function isInvalidOAuthCodeVerifierError(
  error: unknown,
): error is InvalidOAuthCodeVerifierError {
  return hasErrorShape(
    error,
    'InvalidOAuthCodeVerifierError',
    'OAUTH_CODE_VERIFIER_INVALID',
  )
}

export function isMissingOAuthClientSecretError(
  error: unknown,
): error is MissingOAuthClientSecretError {
  return hasErrorShape(error, 'MissingOAuthClientSecretError', 'OAUTH_CLIENT_SECRET_MISSING')
}

export function isInvalidOAuthClientSecretError(
  error: unknown,
): error is InvalidOAuthClientSecretError {
  return hasErrorShape(error, 'InvalidOAuthClientSecretError', 'OAUTH_CLIENT_SECRET_INVALID')
}
