// metadata_ext.mbt
//
// 元数据块内容读取(M6 增量)。parse_metadata 只记录块的位置,
// 这里按需把 SEEKTABLE / VORBIS_COMMENT / PICTURE / APPLICATION 解析成结构。
// 所有长度字段先做边界与溢出校验——块数据同样来自外部不可信输入。

///|
/// 字节区间切片拷贝(Bytes 无子视图,小范围拷贝即可)。
fn slice_bytes(
  data : Bytes,
  offset : Int32,
  length : Int32,
) -> Bytes raise FlacError {
  require_range(data, offset, length)
  let buf = @buffer.Buffer()
  for i in 0.. Int32 raise FlacError {
  require_range(data, offset, 4)
  let b0 = data.unsafe_get(offset).to_int()
  if b0 >= 0x80 {
    raise BadMetadata(reason="32 位长度字段超出范围")
  }
  (b0 << 24) |
  (data.unsafe_get(offset + 1).to_int() << 16) |
  (data.unsafe_get(offset + 2).to_int() << 8) |
  data.unsafe_get(offset + 3).to_int()
}

///|
/// 小端 u32(VORBIS_COMMENT 用),同样限制 < 2^31。
fn le32_len(data : Bytes, offset : Int32) -> Int32 raise FlacError {
  require_range(data, offset, 4)
  let b3 = data.unsafe_get(offset + 3).to_int()
  if b3 >= 0x80 {
    raise BadMetadata(reason="32 位长度字段超出范围")
  }
  data.unsafe_get(offset).to_int() |
  (data.unsafe_get(offset + 1).to_int() << 8) |
  (data.unsafe_get(offset + 2).to_int() << 16) |
  (b3 << 24)
}

///|
/// SEEKTABLE 的一个定位点。
pub struct SeekPoint {
  /// 该帧首样本的样本序号。
  sample_position : Int64
  /// 该帧在文件中的字节偏移。
  byte_offset : Int64
  /// 该帧的样本数。
  frame_samples : Int32
}

///|
/// 读 SEEKTABLE:每点 18 字节(大端 样本号 8 + 字节偏移 8 + 帧长 2)。
/// 样本号全 1 的占位点按规范忽略;点数必须与块长整除。
pub fn read_seektable(
  data : Bytes,
  block : MetadataBlock,
) -> Array[SeekPoint] raise FlacError {
  require_range(data, block.offset, block.length)
  if block.length % 18 != 0 {
    raise BadMetadata(reason="SEEKTABLE 长度不是 18 的倍数")
  }
  let pts : Array[SeekPoint] = []
  let mut off = block.offset
  let end = block.offset + block.length
  while off < end {
    let mut all_ff = true
    for i in 0..<8 {
      if data.unsafe_get(off + i).to_int() != 0xFF {
        all_ff = false
      }
    }
    if !all_ff {
      // 样本号 < 2^63、字节偏移 < 2^63(否则视为非法)
      let b0 = data.unsafe_get(off).to_int()
      let b8 = data.unsafe_get(off + 8).to_int()
      if b0 >= 0x80 || b8 >= 0x80 {
        raise BadMetadata(reason="SEEKTABLE 64 位字段超出范围")
      }
      let mut sample = Int64::from_int(b0)
      let mut byte_off = Int64::from_int(b8)
      for i in 1..<8 {
        sample = (sample << 8) |
          Int64::from_int(data.unsafe_get(off + i).to_int())
        byte_off = (byte_off << 8) |
          Int64::from_int(data.unsafe_get(off + 8 + i).to_int())
      }
      let frame_samples = (data.unsafe_get(off + 16).to_int() << 8) |
        data.unsafe_get(off + 17).to_int()
      pts.push({
        sample_position: sample,
        byte_offset: byte_off,
        frame_samples,
      })
    }
    off += 18
  }
  pts
}

///|
/// VORBIS_COMMENT:小端长度前缀的 UTF-8 文本,注释项为 `KEY=VALUE`。
pub struct VorbisComment {
  /// 写入该注释的编码器标识。
  vendor : String
  /// 全部注释项(保留原始大小写;键在使用时应大小写不敏感比较)。
  comments : Array[(String, String)]
}

///|
/// 读 VORBIS_COMMENT 块。
pub fn read_vorbis_comment(
  data : Bytes,
  block : MetadataBlock,
) -> VorbisComment raise FlacError {
  require_range(data, block.offset, block.length)
  let end = block.offset + block.length
  let mut off = block.offset
  let vendor_len = le32_len(data, off)
  off += 4
  require_range(data, off, vendor_len)
  let vendor = @utf8.decode_lossy(slice_bytes(data, off, vendor_len))
  off += vendor_len
  let count = le32_len(data, off)
  off += 4
  let comments : Array[(String, String)] = []
  let mut i = 0
  while i < count {
    if off > end {
      raise Truncated(offset=off, need=4, actual=end)
    }
    let len = le32_len(data, off)
    off += 4
    if len > end - off {
      raise Truncated(offset=off, need=len, actual=end - off)
    }
    // 在字节层找第一个 '=',避免对解码后的字符串做搜索
    let mut eq = -1
    for k in 0..= 0 {
        break
      }
    }
    if eq < 0 {
      raise BadMetadata(reason="VORBIS_COMMENT 项缺少 = 分隔符")
    }
    let key = @utf8.decode_lossy(slice_bytes(data, off, eq))
    let value = @utf8.decode_lossy(
      slice_bytes(data, off + eq + 1, len - eq - 1),
    )
    comments.push((key, value))
    off += len
    i += 1
  }
  { vendor, comments, }
}

///|
/// PICTURE 块(内嵌图片)。
pub struct Picture {
  /// 图片类型(3 = 封面正/背,规范见 Vorbis PICTURE 规范)。
  picture_type : Int32
  /// MIME 类型。
  mime : String
  /// 描述文本。
  description : String
  /// 像素宽(0 = 未知)。
  width : Int32
  /// 像素高(0 = 未知)。
  height : Int32
  /// 每像素位深(0 = 未知)。
  depth : Int32
  /// 调色板颜色数(0 = 非调色板)。
  colors : Int32
  /// 图片数据。
  data : Bytes
}

///|
/// 读 PICTURE 块。
pub fn read_picture(
  data : Bytes,
  block : MetadataBlock,
) -> Picture raise FlacError {
  require_range(data, block.offset, block.length)
  let end = block.offset + block.length
  let mut off = block.offset
  let picture_type = be32_len(data, off)
  off += 4
  let mime_len = be32_len(data, off)
  off += 4
  if mime_len > end - off {
    raise Truncated(offset=off, need=mime_len, actual=end - off)
  }
  let mime = @utf8.decode_lossy(slice_bytes(data, off, mime_len))
  off += mime_len
  let desc_len = be32_len(data, off)
  off += 4
  if desc_len > end - off {
    raise Truncated(offset=off, need=desc_len, actual=end - off)
  }
  let description = @utf8.decode_lossy(slice_bytes(data, off, desc_len))
  off += desc_len
  let width = be32_len(data, off)
  off += 4
  let height = be32_len(data, off)
  off += 4
  let depth = be32_len(data, off)
  off += 4
  let colors = be32_len(data, off)
  off += 4
  let data_len = be32_len(data, off)
  off += 4
  if data_len > end - off {
    raise Truncated(offset=off, need=data_len, actual=end - off)
  }
  let picture = slice_bytes(data, off, data_len)
  {
    picture_type,
    mime,
    description,
    width,
    height,
    depth,
    colors,
    data: picture,
  }
}

///|
/// APPLICATION 块:4 字节应用标识 + 剩余数据。
pub struct ApplicationBlock {
  /// 4 字节 ASCII 应用标识。
  app_id : String
  /// 标识之后的应用数据。
  data : Bytes
}

///|
/// 读 APPLICATION 块。
pub fn read_application(
  data : Bytes,
  block : MetadataBlock,
) -> ApplicationBlock raise FlacError {
  require_range(data, block.offset, block.length)
  if block.length < 4 {
    raise BadMetadata(reason="APPLICATION 块短于 4 字节标识")
  }
  let app_id = @utf8.decode_lossy(slice_bytes(data, block.offset, 4))
  let rest = slice_bytes(data, block.offset + 4, block.length - 4)
  { app_id, data: rest, }
}

///|
/// CUESHEET 轨道索引点(RFC 9639 §8.7.1.1)。
pub struct CueSheetIndex {
  /// 相对轨道起点的样本偏移。
  offset : Int64
  /// 索引点编号(0 = 轨道前置空白,之后必须严格递增)。
  index_number : Int32
}

///|
/// CUESHEET 轨道(RFC 9639 §8.7.1)。
pub struct CueSheetTrack {
  /// 轨道首个索引点相对音频流起点的样本偏移。
  offset : Int64
  /// 轨道号(CD-DA:1-99,170 = lead-out;非 CD-DA:255 = lead-out)。
  track_number : Int32
  /// 12 字节 ISRC(全零表示无)。
  isrc : String
  /// 是否非音频轨道(CD-DA Q 通道控制位 3)。
  is_non_audio : Bool
  /// 是否有预加重(CD-DA Q 通道控制位 5)。
  pre_emphasis : Bool
  /// 索引点(lead-out 轨道必须为空)。
  indexes : Array[CueSheetIndex]
}

///|
/// CUESHEET 块(RFC 9639 §8.7):CD-DA 轨道结构,或通用的"流内关注点"
/// 定位机制。
pub struct CueSheet {
  /// 128 字节媒体目录号(ASCII,不足右补 0x00;CD-DA 为 13 位数字)。
  media_catalog_number : String
  /// 前导区样本数(仅 CD-DA 有意义,其余应为 0)。
  lead_in_samples : Int64
  /// 是否对应 CD-DA。
  is_cd : Bool
  /// 轨道(最后一轨必须是 lead-out)。
  tracks : Array[CueSheetTrack]
}

///|
/// 校验保留位区必须全零(RFC 9639 对所有保留位的强制要求),
/// 非零时抛 BadMetadata。
fn require_zero_reserved(
  data : Bytes,
  offset : Int32,
  length : Int32,
  what : String,
) -> Unit raise FlacError {
  require_range(data, offset, length)
  for i in 0.. Int64 raise FlacError {
  require_range(data, offset, 8)
  if data.unsafe_get(offset).to_int() >= 0x80 {
    raise BadMetadata(reason="CUESHEET 64 位字段超出范围")
  }
  let mut v = Int64::from_int(data.unsafe_get(offset).to_int())
  for i in 1..<8 {
    v = (v << 8) | Int64::from_int(data.unsafe_get(offset + i).to_int())
  }
  v
}

///|
/// ASCII 字段去掉尾部 0x00(媒体目录号/ISRC 的定长存储约定)。
fn trim_ascii_nulls(bytes : Bytes) -> String {
  let mut end = bytes.length()
  while end > 0 && bytes.unsafe_get(end - 1).to_int() == 0 {
    end -= 1
  }
  @utf8.decode_lossy(slice_bytes_range(bytes, 0, end))
}

///|
/// slice_bytes 的非 raise 包装(输入已在 require_range 中验证过边界)。
fn slice_bytes_range(bytes : Bytes, offset : Int32, length : Int32) -> Bytes {
  let buf = @buffer.Buffer()
  for i in 0.. CueSheet raise FlacError {
  require_range(data, block.offset, block.length)
  let end = block.offset + block.length
  let mut off = block.offset
  // 媒体目录号:128 字节定长
  if end - off < 128 {
    raise Truncated(offset=off, need=128, actual=end - off)
  }
  let media_catalog_number = trim_ascii_nulls(slice_bytes(data, off, 128))
  off += 128
  let lead_in_samples = read_be64(data, off)
  off += 8
  // is_cd: 1 位 + 7 位保留(凑满 1 字节)+ 258 字节保留
  let is_cd_byte = data.unsafe_get(off).to_int()
  let is_cd = (is_cd_byte & 0x80) != 0
  if (is_cd_byte & 0x7F) != 0 {
    raise BadMetadata(reason="CUESHEET is_cd 后 7 位保留位非零")
  }
  off += 1
  require_zero_reserved(data, off, 258, "CUESHEET 258 字节保留区非零")
  off += 258
  if end - off < 1 {
    raise Truncated(offset=off, need=1, actual=end - off)
  }
  let track_count = data.unsafe_get(off).to_int()
  off += 1
  if track_count < 1 {
    raise BadMetadata(
      reason="CUESHEET 轨道数少于 1(必须有 lead-out)",
    )
  }
  let tracks : Array[CueSheetTrack] = []
  let mut t = 0
  while t < track_count {
    let track_offset = read_be64(data, off)
    off += 8
    if end - off < 1 + 12 + 1 + 13 + 1 {
      raise Truncated(offset=off, need=28, actual=end - off)
    }
    let track_number = data.unsafe_get(off).to_int()
    off += 1
    let isrc = trim_ascii_nulls(slice_bytes(data, off, 12))
    off += 12
    let flags = data.unsafe_get(off).to_int()
    let is_non_audio = (flags & 0x80) != 0
    let pre_emphasis = (flags & 0x40) != 0
    if (flags & 0x3F) != 0 {
      raise BadMetadata(reason="CUESHEET 轨道标志后 6 位保留位非零")
    }
    off += 1
    require_zero_reserved(
      data, off, 13, "CUESHEET 轨道 13 字节保留区非零",
    )
    off += 13
    let index_count = data.unsafe_get(off).to_int()
    off += 1
    let indexes : Array[CueSheetIndex] = []
    let mut k = 0
    while k < index_count {
      if end - off < 12 {
        raise Truncated(offset=off, need=12, actual=end - off)
      }
      let index_offset = read_be64(data, off)
      off += 8
      let index_number = data.unsafe_get(off).to_int()
      off += 1
      require_zero_reserved(
        data, off, 3, "CUESHEET 索引点 3 字节保留区非零",
      )
      off += 3
      indexes.push({ offset: index_offset, index_number, })
      k += 1
    }
    tracks.push({
      offset: track_offset,
      track_number,
      isrc,
      is_non_audio,
      pre_emphasis,
      indexes,
    })
    t += 1
  }
  { media_catalog_number, lead_in_samples, is_cd, tracks, }
}