/**
 * JsonEncodingVisitor.ets
 *
 * JSON 编码 Visitor - Protobuf JSON mapping
 *
 * Protobuf JSON 映射规范:
 * - int32/uint32/sint32 → number
 * - int64/uint64/sint64 → string (避免精度丢失)
 * - float/double → number (特殊值: NaN, Infinity, -Infinity)
 * - string → string
 * - bytes → base64 string
 * - bool → boolean
 * - enum → number
 * - message → nested object
 * - repeated → array
 * - map<string, V> → object
 * - map<K, V> (K非string) → array of {key, value}
 *
 * Version: 1.0.0
 * ArkTS 2025 兼容
 */

import { Visitor } from './Visitor'
import { Message } from './Message'

/**
 * JSON 编码 Visitor
 *
 * 将消息编码为 Protobuf JSON 格式
 *
 * 使用方式:
 * ```typescript
 * const visitor = new JsonEncodingVisitor()
 * message.traverse(visitor)
 * const json = visitor.finish()
 * ```
 *
 * 递归深度保护:
 * - 最大嵌套深度:100 层
 * - 超过限制会抛出错误
 */
export class JsonEncodingVisitor implements Visitor {
  private json: Record<string, Object> = {}
  private recursionDepth: number = 0
  private readonly maxRecursionDepth: number = 100

  // 字段编号到字段名的映射
  // 注意:生成的消息类会设置此映射
  // 临时实现:使用字段编号作为 key
  private fieldNames: Map<number, string> = new Map()

  constructor() {
    this.json = {}
  }

  /**
   * 设置字段名映射(由生成的消息类调用)
   */
  setFieldNames(names: Map<number, string>): void {
    this.fieldNames = names
  }

  /**
   * 获取字段名(如果没有映射,返回 "field_N")
   */
  private getFieldName(fieldNumber: number): string {
    const name = this.fieldNames.get(fieldNumber)
    if (name !== undefined) {
      return name
    }
    // 临时实现:使用 field_N 作为字段名
    return `field_${fieldNumber}`
  }

  // ========== 标量字段 ==========

  /**
   * 访问 int32 字段
   * JSON: number
   */
  visitInt32(value: number, fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    this.json[fieldName] = value as Object
  }

  /**
   * 访问 int64 字段
   * JSON: string (避免精度丢失)
   */
  visitInt64(value: bigint, fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    this.json[fieldName] = value.toString() as Object
  }

  /**
   * 访问 uint32 字段
   * JSON: number
   */
  visitUint32(value: number, fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    this.json[fieldName] = value as Object
  }

  /**
   * 访问 uint64 字段
   * JSON: string (避免精度丢失)
   */
  visitUint64(value: bigint, fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    this.json[fieldName] = value.toString() as Object
  }

  /**
   * 访问 sint32 字段
   * JSON: number
   */
  visitSint32(value: number, fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    this.json[fieldName] = value as Object
  }

  /**
   * 访问 sint64 字段
   * JSON: string (避免精度丢失)
   */
  visitSint64(value: bigint, fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    this.json[fieldName] = value.toString() as Object
  }

  /**
   * 访问 fixed32 字段
   * JSON: number
   */
  visitFixed32(value: number, fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    this.json[fieldName] = value as Object
  }

  /**
   * 访问 fixed64 字段
   * JSON: string (避免精度丢失)
   */
  visitFixed64(value: bigint, fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    this.json[fieldName] = value.toString() as Object
  }

  /**
   * 访问 sfixed32 字段
   * JSON: number
   */
  visitSfixed32(value: number, fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    this.json[fieldName] = value as Object
  }

  /**
   * 访问 sfixed64 字段
   * JSON: string (避免精度丢失)
   */
  visitSfixed64(value: bigint, fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    this.json[fieldName] = value.toString() as Object
  }

  /**
   * 访问 string 字段
   * JSON: string
   */
  visitString(value: string, fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    this.json[fieldName] = value as Object
  }

  /**
   * 访问 bytes 字段
   * JSON: base64 string
   */
  visitBytes(value: Uint8Array, fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    // 转换为 base64
    const base64 = this.bytesToBase64(value)
    this.json[fieldName] = base64 as Object
  }

  /**
   * 访问 bool 字段
   * JSON: boolean
   */
  visitBool(value: boolean, fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    this.json[fieldName] = value as Object
  }

  /**
   * 访问 float 字段
   * JSON: number or "NaN"/"Infinity"/"-Infinity"
   */
  visitFloat(value: number, fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    if (isNaN(value)) {
      this.json[fieldName] = "NaN" as Object
    } else if (value === Infinity) {
      this.json[fieldName] = "Infinity" as Object
    } else if (value === -Infinity) {
      this.json[fieldName] = "-Infinity" as Object
    } else {
      this.json[fieldName] = value as Object
    }
  }

  /**
   * 访问 double 字段
   * JSON: number or "NaN"/"Infinity"/"-Infinity"
   */
  visitDouble(value: number, fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    if (isNaN(value)) {
      this.json[fieldName] = "NaN" as Object
    } else if (value === Infinity) {
      this.json[fieldName] = "Infinity" as Object
    } else if (value === -Infinity) {
      this.json[fieldName] = "-Infinity" as Object
    } else {
      this.json[fieldName] = value as Object
    }
  }

  // ========== 枚举字段 ==========

  /**
   * 访问 enum 字段
   * JSON: number (可选:也可以是 string 枚举名)
   */
  visitEnum(value: number, fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    this.json[fieldName] = value as Object
  }

  // ========== 嵌套消息(⭐ 自动递归) ==========

  /**
   * 访问嵌套消息字段
   *
   * ⭐ 核心方法:自动处理递归!
   *
   * 实现逻辑:
   * 1. 检查递归深度(防止栈溢出)
   * 2. 创建新的 JsonEncodingVisitor
   * 3. 调用嵌套消息的 traverse() 方法(自动递归)
   * 4. 获取嵌套消息的 JSON 对象
   * 5. 设置到当前 JSON 对象中
   *
   * @param value 嵌套消息对象
   * @param fieldNumber proto 字段编号
   * @throws 如果递归深度超过限制
   */
  visitMessage(value: Message, fieldNumber: number): void {
    // 递归深度保护
    this.recursionDepth++
    if (this.recursionDepth > this.maxRecursionDepth) {
      throw new Error(`Message nesting depth exceeds limit: ${this.maxRecursionDepth}`)
    }

    try {
      const fieldName = this.getFieldName(fieldNumber)

      // 创建嵌套 Visitor,递归遍历
      const nestedVisitor = new JsonEncodingVisitor()
      nestedVisitor.recursionDepth = this.recursionDepth  // 继承递归深度
      value.traverse(nestedVisitor)  // ⭐ 自动递归
      const nestedJson = nestedVisitor.finish()

      this.json[fieldName] = nestedJson as Object
    } finally {
      this.recursionDepth--
    }
  }

  // ========== Repeated 字段 ==========

  /**
   * 访问 repeated int32 字段
   * JSON: array of number
   */
  visitRepeatedInt32(value: number[], fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    this.json[fieldName] = [...value] as Object
  }

  /**
   * 访问 repeated int64 字段
   * JSON: array of string
   */
  visitRepeatedInt64(value: bigint[], fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    this.json[fieldName] = value.map(v => v.toString()) as Object
  }

  /**
   * 访问 repeated uint32 字段
   * JSON: array of number
   */
  visitRepeatedUint32(value: number[], fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    this.json[fieldName] = [...value] as Object
  }

  /**
   * 访问 repeated uint64 字段
   * JSON: array of string
   */
  visitRepeatedUint64(value: bigint[], fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    this.json[fieldName] = value.map(v => v.toString()) as Object
  }

  /**
   * 访问 repeated sint32 字段
   * JSON: array of number
   */
  visitRepeatedSint32(value: number[], fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    this.json[fieldName] = [...value] as Object
  }

  /**
   * 访问 repeated sint64 字段
   * JSON: array of string
   */
  visitRepeatedSint64(value: bigint[], fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    this.json[fieldName] = value.map(v => v.toString()) as Object
  }

  /**
   * 访问 repeated fixed32 字段
   * JSON: array of number
   */
  visitRepeatedFixed32(value: number[], fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    this.json[fieldName] = [...value] as Object
  }

  /**
   * 访问 repeated fixed64 字段
   * JSON: array of string
   */
  visitRepeatedFixed64(value: bigint[], fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    this.json[fieldName] = value.map(v => v.toString()) as Object
  }

  /**
   * 访问 repeated sfixed32 字段
   * JSON: array of number
   */
  visitRepeatedSfixed32(value: number[], fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    this.json[fieldName] = [...value] as Object
  }

  /**
   * 访问 repeated sfixed64 字段
   * JSON: array of string
   */
  visitRepeatedSfixed64(value: bigint[], fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    this.json[fieldName] = value.map(v => v.toString()) as Object
  }

  /**
   * 访问 repeated string 字段
   * JSON: array of string
   */
  visitRepeatedString(value: string[], fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    this.json[fieldName] = [...value] as Object
  }

  /**
   * 访问 repeated bytes 字段
   * JSON: array of base64 string
   */
  visitRepeatedBytes(value: Uint8Array[], fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    this.json[fieldName] = value.map(v => this.bytesToBase64(v)) as Object
  }

  /**
   * 访问 repeated bool 字段
   * JSON: array of boolean
   */
  visitRepeatedBool(value: boolean[], fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    this.json[fieldName] = [...value] as Object
  }

  /**
   * 访问 repeated float 字段
   * JSON: array of number
   */
  visitRepeatedFloat(value: number[], fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    // 处理特殊值
    const jsonValues = value.map(v => {
      if (isNaN(v)) return "NaN"
      if (v === Infinity) return "Infinity"
      if (v === -Infinity) return "-Infinity"
      return v
    })
    this.json[fieldName] = jsonValues as Object
  }

  /**
   * 访问 repeated double 字段
   * JSON: array of number
   */
  visitRepeatedDouble(value: number[], fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    // 处理特殊值
    const jsonValues = value.map(v => {
      if (isNaN(v)) return "NaN"
      if (v === Infinity) return "Infinity"
      if (v === -Infinity) return "-Infinity"
      return v
    })
    this.json[fieldName] = jsonValues as Object
  }

  /**
   * 访问 repeated enum 字段
   * JSON: array of number
   */
  visitRepeatedEnum(value: number[], fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    this.json[fieldName] = [...value] as Object
  }

  /**
   * 访问 repeated message 字段
   * JSON: array of object
   * ⭐ 每个消息都会递归调用 visitMessage
   */
  visitRepeatedMessage(value: Message[], fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    const jsonArray: Record<string, Object>[] = []

    for (const item of value) {
      const nestedVisitor = new JsonEncodingVisitor()
      nestedVisitor.recursionDepth = this.recursionDepth + 1
      if (nestedVisitor.recursionDepth > this.maxRecursionDepth) {
        throw new Error(`Message nesting depth exceeds limit: ${this.maxRecursionDepth}`)
      }
      item.traverse(nestedVisitor)
      jsonArray.push(nestedVisitor.finish())
    }

    this.json[fieldName] = jsonArray as Object
  }

  // ========== Map 字段 ==========

  /**
   * 访问 map<string, int32> 字段
   * JSON: object with string keys
   */
  visitMapStringInt32(value: Map<string, number>, fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    const obj: Record<string, Object> = {}
    value.forEach((v, k) => {
      obj[k] = v as Object
    })
    this.json[fieldName] = obj as Object
  }

  /**
   * 访问 map<string, int64> 字段
   * JSON: object with string values (避免精度丢失)
   */
  visitMapStringInt64(value: Map<string, bigint>, fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    const obj: Record<string, Object> = {}
    value.forEach((v, k) => {
      obj[k] = v.toString() as Object
    })
    this.json[fieldName] = obj as Object
  }

  /**
   * 访问 map<string, string> 字段
   * JSON: object with string keys and values
   */
  visitMapStringString(value: Map<string, string>, fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    const obj: Record<string, Object> = {}
    value.forEach((v, k) => {
      obj[k] = v as Object
    })
    this.json[fieldName] = obj as Object
  }

  /**
   * 访问 map<string, bytes> 字段
   * JSON: object with base64 string values
   */
  visitMapStringBytes(value: Map<string, Uint8Array>, fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    const obj: Record<string, Object> = {}
    value.forEach((v, k) => {
      obj[k] = this.bytesToBase64(v) as Object
    })
    this.json[fieldName] = obj as Object
  }

  /**
   * 访问 map<string, bool> 字段
   * JSON: object with boolean values
   */
  visitMapStringBool(value: Map<string, boolean>, fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    const obj: Record<string, Object> = {}
    value.forEach((v, k) => {
      obj[k] = v as Object
    })
    this.json[fieldName] = obj as Object
  }

  /**
   * 访问 map<string, message> 字段
   * JSON: object with nested object values
   */
  visitMapStringMessage(value: Map<string, Message>, fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    const obj: Record<string, Object> = {}

    value.forEach((v, k) => {
      const nestedVisitor = new JsonEncodingVisitor()
      nestedVisitor.recursionDepth = this.recursionDepth + 1
      if (nestedVisitor.recursionDepth > this.maxRecursionDepth) {
        throw new Error(`Message nesting depth exceeds limit: ${this.maxRecursionDepth}`)
      }
      v.traverse(nestedVisitor)
      obj[k] = nestedVisitor.finish() as Object
    })

    this.json[fieldName] = obj as Object
  }

  /**
   * 访问 map<int32, int32> 字段
   * JSON: object with string keys (int32 转为 string)
   */
  visitMapInt32Int32(value: Map<number, number>, fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    const obj: Record<string, Object> = {}
    value.forEach((v, k) => {
      obj[k.toString()] = v as Object
    })
    this.json[fieldName] = obj as Object
  }

  /**
   * 访问 map<int32, string> 字段
   * JSON: object with string keys
   */
  visitMapInt32String(value: Map<number, string>, fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    const obj: Record<string, Object> = {}
    value.forEach((v, k) => {
      obj[k.toString()] = v as Object
    })
    this.json[fieldName] = obj as Object
  }

  /**
   * 访问 map<int32, message> 字段
   * JSON: object with string keys
   */
  visitMapInt32Message(value: Map<number, Message>, fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    const obj: Record<string, Object> = {}

    value.forEach((v, k) => {
      const nestedVisitor = new JsonEncodingVisitor()
      nestedVisitor.recursionDepth = this.recursionDepth + 1
      if (nestedVisitor.recursionDepth > this.maxRecursionDepth) {
        throw new Error(`Message nesting depth exceeds limit: ${this.maxRecursionDepth}`)
      }
      v.traverse(nestedVisitor)
      obj[k.toString()] = nestedVisitor.finish() as Object
    })

    this.json[fieldName] = obj as Object
  }

  /**
   * 访问 map<int64, int64> 字段
   * JSON: object with string keys and values
   */
  visitMapInt64Int64(value: Map<bigint, bigint>, fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    const obj: Record<string, Object> = {}
    value.forEach((v, k) => {
      obj[k.toString()] = v.toString() as Object
    })
    this.json[fieldName] = obj as Object
  }

  /**
   * 访问 map<int64, string> 字段
   * JSON: object with string keys
   */
  visitMapInt64String(value: Map<bigint, string>, fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    const obj: Record<string, Object> = {}
    value.forEach((v, k) => {
      obj[k.toString()] = v as Object
    })
    this.json[fieldName] = obj as Object
  }

  /**
   * 访问 map<int64, message> 字段
   * JSON: object with string keys
   */
  visitMapInt64Message(value: Map<bigint, Message>, fieldNumber: number): void {
    const fieldName = this.getFieldName(fieldNumber)
    const obj: Record<string, Object> = {}

    value.forEach((v, k) => {
      const nestedVisitor = new JsonEncodingVisitor()
      nestedVisitor.recursionDepth = this.recursionDepth + 1
      if (nestedVisitor.recursionDepth > this.maxRecursionDepth) {
        throw new Error(`Message nesting depth exceeds limit: ${this.maxRecursionDepth}`)
      }
      v.traverse(nestedVisitor)
      obj[k.toString()] = nestedVisitor.finish() as Object
    })

    this.json[fieldName] = obj as Object
  }

  // ========== 完成编码 ==========

  /**
   * 完成编码,获取最终的 JSON 对象
   *
   * @returns Protobuf JSON 格式对象
   */
  finish(): Record<string, Object> {
    return this.json
  }

  // ========== 辅助方法 ==========

  /**
   * 将 Uint8Array 转换为 base64 字符串
   *
   * @param bytes 字节数组
   * @returns base64 字符串
   */
  private bytesToBase64(bytes: Uint8Array): string {
    // Base64 字符表
    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
    let result = ''
    let i = 0

    // 每 3 个字节编码为 4 个 base64 字符
    for (; i < bytes.length - 2; i += 3) {
      const b1 = bytes[i]
      const b2 = bytes[i + 1]
      const b3 = bytes[i + 2]

      result += chars.charAt(b1 >> 2)
      result += chars.charAt(((b1 & 0x03) << 4) | (b2 >> 4))
      result += chars.charAt(((b2 & 0x0F) << 2) | (b3 >> 6))
      result += chars.charAt(b3 & 0x3F)
    }

    // 处理剩余字节
    if (i < bytes.length) {
      const b1 = bytes[i]
      result += chars.charAt(b1 >> 2)

      if (i + 1 < bytes.length) {
        const b2 = bytes[i + 1]
        result += chars.charAt(((b1 & 0x03) << 4) | (b2 >> 4))
        result += chars.charAt((b2 & 0x0F) << 2)
        result += '='
      } else {
        result += chars.charAt((b1 & 0x03) << 4)
        result += '=='
      }
    }

    return result
  }
}