/** Bad target / parent / author / body — carry field + detail (coding-standard §4). */
export class CommentValidationError extends Error {
  override readonly name = 'CommentValidationError'
  readonly code = 'COMMENT_VALIDATION' as const

  constructor(
    readonly field: string,
    readonly detail: string,
  ) {
    super(`comment validation failed: ${field} — ${detail}`)
  }
}

/** No row found for the given id. */
export class CommentNotFoundError extends Error {
  override readonly name = 'CommentNotFoundError'
  readonly code = 'COMMENT_NOT_FOUND' as const

  constructor(readonly selector: string) {
    super(`comment not found: ${selector}`)
  }
}

/** IDOR / moderation denial — carries action + actorId + commentId for tracing. */
export class CommentAuthzError extends Error {
  override readonly name = 'CommentAuthzError'
  readonly code = 'COMMENT_AUTHZ' as const

  constructor(
    readonly action: string,
    readonly actorId: string | null,
    readonly commentId: string,
    readonly detail: string,
  ) {
    super(`comment authz denied: ${action} by ${actorId} on ${commentId} — ${detail}`)
  }
}

/**
 * Missing or non-function sanitize argument on post/edit.
 * Hard-floor — a comment is never stored without a valid injected sanitizer (spec §3).
 */
export class CommentSanitizationError extends Error {
  override readonly name = 'CommentSanitizationError'
  readonly code = 'COMMENT_SANITIZATION' as const

  constructor(readonly detail: string) {
    super(`comment sanitization error: ${detail}`)
  }
}

/**
 * Injected moderator/classifier returned an out-of-contract verdict (malformed status, or a
 * non-finite spamScore). Trust-boundary failure — the moderator is an external authority and its
 * return is untrusted. Surfaced as a typed error (no bare throw) so consumers' isCommentError()
 * handling recognizes it; on the postModerated path it is caught internally → fail-safe pending.
 */
export class CommentModerationError extends Error {
  override readonly name = 'CommentModerationError'
  readonly code = 'COMMENT_MODERATION' as const

  constructor(readonly detail: string) {
    super(`comment moderation error: ${detail}`)
  }
}

/** Opaque keyset cursor is malformed / tampered. */
export class InvalidCursorError extends Error {
  override readonly name = 'InvalidCursorError'
  readonly code = 'COMMENT_INVALID_CURSOR' as const

  constructor() {
    super('comment cursor is invalid or tampered')
  }
}

/**
 * Structural type-guard — instanceof-free (cross-package dedup safety, CLAUDE.md §6).
 * Matches any of the module's typed errors by structural `code` property.
 */
export function isCommentError(e: unknown): e is { code: string } {
  return (
    typeof e === 'object' &&
    e !== null &&
    'code' in e &&
    typeof (e as { code: unknown }).code === 'string' &&
    (e as { code: string }).code.startsWith('COMMENT_')
  )
}
