import { observed, toRaw } from '../../decorators/observed.js'

export type RateLimitRuleKey = { type: 'ip' } | { type: 'userId' } | { type: 'role'; value: string }
export type RateLimitRuleInterval = 'second' | 'minute' | 'hour' | 'day'

/**
 * Represents a single rate limiting rule that applies to a specific
 * type of client, using a token bucket algorithm.
 */
export interface RateLimitRuleSchema {
  /**
   * A human-readable description of what this rule is for.
   * e.g., "Limit anonymous users to 60 requests per hour."
   */
  description?: string

  /**
   * Defines how to group requests for rate limiting. This determines
   * who the limit applies to.
   *
   * - 'ip': Keys on the client's IP address. Best for anonymous traffic.
   * - 'userId': Keys on the authenticated user's ID. Best for logged-in users.
   * - 'role': Applies a shared limit to all users of a specific role.
   */
  key?: RateLimitRuleKey

  /**
   * The number of requests allowed over the defined interval.
   * This is the rate at which tokens are added to the bucket.
   */
  rate?: number

  /**
   * The time interval for the rate.
   */
  interval?: RateLimitRuleInterval

  /**
   * The maximum number of requests that can be made in a burst.
   * This represents the "bucket size." A larger burst allows for
   * more requests to be made in a short period before throttling begins.
   */
  burst?: number
}

/**
 * Represents a single rate limiting rule that applies to a specific
 * type of client, using a token bucket algorithm.
 */
export class RateLimitRule extends EventTarget implements RateLimitRuleSchema {
  @observed() accessor description: string | undefined
  @observed({ deep: true }) accessor key: RateLimitRuleKey | undefined
  @observed() accessor rate: number | undefined
  @observed() accessor interval: RateLimitRuleInterval | undefined
  @observed() accessor burst: number | undefined

  constructor(state: Partial<RateLimitRuleSchema> = {}) {
    super()
    this.description = state.description
    this.key = RateLimitRule.isRateLimitRuleKey(state.key) ? structuredClone(state.key) : undefined
    this.rate = typeof state.rate === 'number' ? state.rate : undefined
    this.interval = RateLimitRule.isRateLimitRuleInterval(state.interval) ? state.interval : undefined
    this.burst = typeof state.burst === 'number' ? state.burst : undefined
  }

  toJSON(): RateLimitRuleSchema {
    const result: RateLimitRuleSchema = {}
    if (this.description) {
      result.description = this.description
    }
    if (RateLimitRule.isRateLimitRuleKey(this.key)) {
      result.key = structuredClone(toRaw(this, this.key))
    }
    if (typeof this.rate === 'number') {
      result.rate = this.rate
    }
    if (RateLimitRule.isRateLimitRuleInterval(this.interval)) {
      result.interval = this.interval
    }
    if (typeof this.burst === 'number') {
      result.burst = this.burst
    }
    return result
  }

  notifyChange() {
    this.dispatchEvent(new Event('change'))
  }

  static isRateLimitRuleInterval(value: unknown): value is RateLimitRuleInterval {
    if (typeof value !== 'string') {
      return false
    }
    return ['second', 'minute', 'hour', 'day'].includes(value)
  }

  static isRateLimitRuleKey(value: unknown): value is RateLimitRuleKey {
    if (typeof value !== 'object' || value === null) {
      return false
    }
    const key = value as Record<string, unknown>
    return 'type' in key && (key.type === 'ip' || key.type === 'userId' || key.type === 'role')
  }
}
