// New message generation logic for Visitor pattern (V2.0)
// This will replace the old message generation logic

/**
 * ⭐ 设计说明:为什么 Binary 用 Visitor 而 JSON 直接生成?
 *
 * 本项目采用**混合序列化策略**:
 *
 * 1. Binary(Protobuf wire format)- 使用 Visitor 模式
 *    优势:
 *    - 未来可轻松扩展(TextFormat, Hash, Debug 等)
 *    - 代码复用:Visitor 逻辑可在不同格式间共享
 *    - 架构一致性:遵循 SwiftProtobuf 成熟设计
 *
 * 2. JSON - 直接代码生成(本文件负责)
 *    优势:
 *    - 性能至上:JSON 最常用,避免 Visitor 间接调用开销(~30-50% 提升)
 *    - 特殊逻辑:枚举名称映射、bigint→string、Base64 编码等
 *    - 代码清晰:生成的代码更易阅读和调试
 *
 * 生成的 JSON 方法:
 * - static toJson(m: MessageType): MessageTypeJSON
 * - static fromJson(j: MessageTypeJSON): MessageType
 *
 * 注意:不使用 JsonEncodingVisitor,而是直接生成方法体。
 *
 * 详见:ARCHITECTURE.md > 混合序列化策略
 */

/**
 * Generate traverse() method for Visitor pattern
 *
 * @param {protobuf.Type} msg - Message type
 * @param {Array} fields - Message fields
 * @param {string} int64Mode - 'bigint' or 'number'
 * @param {Map} aliasMap - Type alias mappings
 * @returns {Array<string>} Generated code lines
 */
function generateTraverseMethod(msg, fields, oneofs, int64Mode, aliasMap) {
  const lines = []

  lines.push('  /**')
  lines.push('   * ⭐ Visitor pattern: traverse all fields')
  lines.push('   */')
  lines.push('  traverse(v: Visitor): void {')

  for (const f of fields) {
    const fnameSafe = safeIdent(f.name)
    const fieldNum = f.id

    if (f.map) {
      // Map field
      const keyType = mapKeyType(f, int64Mode)
      const valueType = f.resolvedType ? (aliasMap.get((f.resolvedType.fullName||'').replace(/^\./,''))?.local || f.resolvedType.name) : typeFor(f, int64Mode)

      // Determine map visitor method name
      const keyTypeSimple = keyType === 'string' ? 'String' : (keyType === 'number' ? 'Int32' : 'Int64')
      const valueTypeSimple = valueType === 'string' ? 'String' :
                              valueType === 'number' ? 'Int32' :
                              valueType === 'bigint' ? 'Int64' :
                              valueType === 'boolean' ? 'Bool' :
                              valueType === 'Uint8Array' ? 'Bytes' :
                              'Message'

      lines.push(`    if (this.${fnameSafe}.size > 0) {`)
      lines.push(`      v.visitMap${keyTypeSimple}${valueTypeSimple}(this.${fnameSafe}, ${fieldNum})`)
      lines.push('    }')
    } else if (f.repeated) {
      // Repeated field
      const elemType = f.resolvedType ? (aliasMap.get((f.resolvedType.fullName||'').replace(/^\./,''))?.local || f.resolvedType.name) : typeFor(f, int64Mode)
      const isEnum = f.resolvedType && (f.resolvedType instanceof protobuf.Enum)
      const isMessage = f.resolvedType && !(f.resolvedType instanceof protobuf.Enum)

      let visitMethod
      if (isEnum) {
        visitMethod = 'visitRepeatedEnum'
      } else if (isMessage) {
        visitMethod = 'visitRepeatedMessage'
      } else {
        visitMethod = `visitRepeated${capitalize(f.type)}`
      }

      lines.push(`    if (this.${fnameSafe}.length > 0) {`)
      lines.push(`      v.${visitMethod}(this.${fnameSafe}, ${fieldNum})`)
      lines.push('    }')
    } else {
      // Singular field
      const isEnum = f.resolvedType && (f.resolvedType instanceof protobuf.Enum)
      const isMessage = f.resolvedType && !(f.resolvedType instanceof protobuf.Enum)

      if (isMessage) {
        // Nested message
        lines.push(`    if (this.${fnameSafe} !== null) {`)
        lines.push(`      v.visitMessage(this.${fnameSafe}, ${fieldNum})`)
        lines.push('    }')
      } else if (isEnum) {
        // Enum
        lines.push(`    if (this.${fnameSafe} !== 0) {`)
        lines.push(`      v.visitEnum(this.${fnameSafe}, ${fieldNum})`)
        lines.push('    }')
      } else {
        // Scalar field
        const defaultValue = getDefaultValue(f, int64Mode)
        const condition = getDefaultCheckCondition(f, fnameSafe, defaultValue, int64Mode)

        let visitMethod = `visit${capitalize(f.type)}`

        lines.push(`    if (${condition}) {`)
        lines.push(`      v.${visitMethod}(this.${fnameSafe}, ${fieldNum})`)
        lines.push('    }')
      }
    }
  }

  // Handle oneofs
  for (const g of oneofs) {
    lines.push(`    // oneof: ${g.name}`)
    lines.push(`    if (this.${g.name} !== undefined) {`)
    lines.push(`      switch (this.${g.name}.kind) {`)
    for (const n of g.names) {
      const f = msg.fields[n]
      const fieldNum = f.id
      const isEnum = f.resolvedType && (f.resolvedType instanceof protobuf.Enum)
      const isMessage = f.resolvedType && !(f.resolvedType instanceof protobuf.Enum)

      lines.push(`        case '${n}': {`)
      if (isMessage) {
        lines.push(`          v.visitMessage(this.${g.name}.${n}, ${fieldNum})`)
      } else if (isEnum) {
        lines.push(`          v.visitEnum(this.${g.name}.${n}, ${fieldNum})`)
      } else {
        lines.push(`          v.visit${capitalize(f.type)}(this.${g.name}.${n}, ${fieldNum})`)
      }
      lines.push('          break')
      lines.push('        }')
    }
    lines.push('      }')
    lines.push('    }')
  }

  lines.push('  }')
  return lines
}

/**
 * Generate decodeFrom() method
 */
function generateDecodeFromMethod(msg, fields, oneofs, int64Mode, aliasMap) {
  const lines = []

  lines.push('  /**')
  lines.push('   * Decode from Reader (internal)')
  lines.push('   */')
  lines.push('  protected decodeFrom(r: Reader, len?: number): void {')
  lines.push('    const endPos = len === undefined ? r.len : r.pos + len')
  lines.push('')
  lines.push('    while (r.pos < endPos) {')
  lines.push('      const tag = r.uint32()')
  lines.push('      const fieldNum = tag >>> 3')
  lines.push('')
  lines.push('      switch (fieldNum) {')

  for (const f of fields) {
    const fnameSafe = safeIdent(f.name)
    const fieldNum = f.id
    const alias = f.resolvedType ? (aliasMap.get((f.resolvedType.fullName||'').replace(/^\./,''))?.local || f.resolvedType.name) : undefined

    lines.push(`        case ${fieldNum}: {`)

    if (f.map) {
      // Map field decoding
      lines.push('          const l = r.uint32()')
      lines.push('          const end2 = r.pos + l')
      lines.push('          let k: ' + mapKeyType(f, int64Mode) + ' = ' + (mapKeyType(f, int64Mode) === 'string' ? `''` : '0'))
      const vType = f.resolvedType ? (aliasMap.get((f.resolvedType.fullName||'').replace(/^\./,''))?.local || f.resolvedType.name) : typeFor(f, int64Mode)
      lines.push('          let v: ' + vType + ' = ' + getDefaultValue({type: f.type.replace('map<', '').replace('>', '').split(',')[1].trim()}, int64Mode))
      lines.push('          while (r.pos < end2) {')
      lines.push('            const mapTag = r.uint32()')
      lines.push('            const mapFieldNum = mapTag >>> 3')
      lines.push('            switch (mapFieldNum) {')
      lines.push('              case 1:')
      lines.push('                k = ' + readerCall(mapKeyType(f, int64Mode) === 'string' ? 'string' : 'int32', int64Mode, 'r'))
      lines.push('                break')
      lines.push('              case 2:')
      if (f.resolvedType && !(f.resolvedType instanceof protobuf.Enum)) {
        lines.push('                {')
        lines.push('                  const msgLen = r.uint32()')
        lines.push(`                  v = new ${alias}()`)
        lines.push('                  v.decodeFrom(r, msgLen)')
        lines.push('                }')
      } else {
        lines.push('                v = ' + readerCall(f.type.replace('map<', '').replace('>', '').split(',')[1].trim(), int64Mode, 'r'))
      }
      lines.push('                break')
      lines.push('              default:')
      lines.push('                r.skipType(mapTag & 7)')
      lines.push('            }')
      lines.push('          }')
      lines.push(`          this.${fnameSafe}.set(k, v)`)
      lines.push('          break')
      lines.push('        }')
    } else if (f.repeated) {
      // Repeated field decoding
      const isMessage = f.resolvedType && !(f.resolvedType instanceof protobuf.Enum)

      if (isMessage) {
        lines.push('          {')
        lines.push('            const msgLen = r.uint32()')
        lines.push(`            const msg = new ${alias}()`)
        lines.push('            msg.decodeFrom(r, msgLen)')
        lines.push(`            this.${fnameSafe}.push(msg)`)
        lines.push('          }')
      } else {
        const rm = readerMethodName(f, int64Mode)
        lines.push(`          this.${fnameSafe}.push(r.${rm}())  // ⚠️ push, not replace!`)
      }
      lines.push('          break')
      lines.push('        }')
    } else {
      // Singular field decoding
      const isMessage = f.resolvedType && !(f.resolvedType instanceof protobuf.Enum)

      if (isMessage) {
        lines.push('          {')
        lines.push('            const msgLen = r.uint32()')
        if (f.partOf) {
          // oneof field
          lines.push(`            const msg = new ${alias}()`)
          lines.push('            msg.decodeFrom(r, msgLen)')
          lines.push(`            this.${f.partOf.name} = { kind: '${f.name}', ${f.name}: msg }`)
        } else {
          lines.push(`            this.${fnameSafe} = new ${alias}()`)
          lines.push(`            this.${fnameSafe}.decodeFrom(r, msgLen)`)
        }
        lines.push('          }')
      } else {
        const rm = readerMethodName(f, int64Mode)
        if (f.partOf) {
          lines.push(`          this.${f.partOf.name} = { kind: '${f.name}', ${f.name}: r.${rm}() }`)
        } else {
          lines.push(`          this.${fnameSafe} = r.${rm}()`)
        }
      }
      lines.push('          break')
      lines.push('        }')
    }
  }

  lines.push('        default: {')
  lines.push('          // Save unknown fields')
  lines.push('          const wireType = tag & 7')
  lines.push('          const start = r.pos')
  lines.push('          r.skipType(wireType)')
  lines.push('          this.unknownFields.append(r.view(start, r.pos))')
  lines.push('        }')
  lines.push('      }')
  lines.push('    }')
  lines.push('  }')

  return lines
}

/**
 * Helper: capitalize first letter
 */
function capitalize(str) {
  if (!str) return ''
  if (str === 'int32') return 'Int32'
  if (str === 'int64') return 'Int64'
  if (str === 'uint32') return 'Uint32'
  if (str === 'uint64') return 'Uint64'
  if (str === 'sint32') return 'Sint32'
  if (str === 'sint64') return 'Sint64'
  if (str === 'fixed32') return 'Fixed32'
  if (str === 'fixed64') return 'Fixed64'
  if (str === 'sfixed32') return 'Sfixed32'
  if (str === 'sfixed64') return 'Sfixed64'
  if (str === 'string') return 'String'
  if (str === 'bytes') return 'Bytes'
  if (str === 'bool') return 'Bool'
  if (str === 'float') return 'Float'
  if (str === 'double') return 'Double'
  return str.charAt(0).toUpperCase() + str.slice(1)
}

/**
 * Helper: get default value for a field
 */
function getDefaultValue(field, int64Mode) {
  switch (field.type) {
    case 'string': return `''`
    case 'bool': return 'false'
    case 'bytes': return 'new Uint8Array(0)'
    case 'int32':
    case 'uint32':
    case 'sint32':
    case 'fixed32':
    case 'sfixed32':
    case 'float':
    case 'double':
      return '0'
    case 'int64':
    case 'uint64':
    case 'sint64':
    case 'fixed64':
    case 'sfixed64':
      return int64Mode === 'number' ? '0' : '0n'
    default:
      // enum or message
      return '0'
  }
}

/**
 * Helper: get condition to check if field is not default
 */
function getDefaultCheckCondition(field, fieldName, defaultValue, int64Mode) {
  switch (field.type) {
    case 'string':
      return `${fieldName} !== ''`
    case 'bool':
      return `${fieldName} !== false`
    case 'bytes':
      return `${fieldName}.length > 0`
    default:
      return `${fieldName} !== ${defaultValue}`
  }
}

/**
 * Helper: generate reader method call
 */
function readerCall(type, int64Mode, readerVar) {
  const rm = readerMethodName({type}, int64Mode)
  return `${readerVar}.${rm}()`
}

module.exports = {
  generateTraverseMethod,
  generateDecodeFromMethod
}