/**
 * UnavailableGuard — Detects when a Feishu message becomes unavailable
 * (deleted or recalled by the user) and stops subsequent card updates.
 *
 * Feishu API error codes that indicate the target message is gone:
 *   230011 — message recalled (撤回)
 *   231003 — message deleted (删除)
 *
 * Adapted from openclaw-lark's card/unavailable-guard.ts.
 */

// Feishu API error codes that mean the message is permanently unavailable
const MESSAGE_UNAVAILABLE_CODES = new Set([
  230011, // Message recalled
  231003, // Message deleted
])

/**
 * Check if an error contains a Feishu API error code indicating
 * that a message is no longer available.
 */
function extractLarkApiCode(err: unknown): number | undefined {
  if (!err || typeof err !== 'object') return undefined
  const e = err as Record<string, unknown>

  // Direct code property
  if (typeof e.code === 'number') return e.code
  if (typeof e.code === 'string') {
    const num = parseInt(e.code, 10)
    if (!isNaN(num)) return num
  }

  // Nested in data or error
  const data = e.data as Record<string, unknown> | undefined
  if (data && typeof data.code === 'number') return data.code

  // Error message containing the code
  const msg = String(e.message ?? e.msg ?? '')
  const codeMatch = msg.match(/code[=:]\s*(\d{6})/)
  if (codeMatch) return parseInt(codeMatch[1], 10)

  return undefined
}

function isMessageUnavailableError(err: unknown): boolean {
  const code = extractLarkApiCode(err)
  return code != null && MESSAGE_UNAVAILABLE_CODES.has(code)
}

export class UnavailableGuard {
  private terminated = false
  private readonly onTerminate?: () => void

  constructor(options?: { onTerminate?: () => void }) {
    this.onTerminate = options?.onTerminate
  }

  get isTerminated(): boolean {
    return this.terminated
  }

  /**
   * Check whether updates should be skipped.
   * Returns true if the guard has already been terminated.
   */
  shouldSkip(source: string): boolean {
    return this.terminated
  }

  /**
   * Attempt to terminate the guard from an error.
   * Returns true if the error was a message-unavailable error
   * and the guard was terminated (or was already terminated).
   */
  terminate(source: string, err?: unknown): boolean {
    if (this.terminated) return true

    if (isMessageUnavailableError(err)) {
      this.terminated = true
      this.onTerminate?.()
      console.log(`[UnavailableGuard] Message unavailable (source=${source}), stopping updates`)
      return true
    }

    return false
  }

  /** Reset the guard state (for testing or reuse). */
  reset(): void {
    this.terminated = false
  }
}