// Copyright 2026 Noverberrain.
// SPDX-License-Identifier: Apache-2.0

///|
/// Validates a signature and then one complete event at a time. The caller
/// provides I/O and retains each returned event's underlying Bytes as needed.
/// Processing errors clear the FDE and permanently stop the reader. A feed
/// after successful finish is rejected without changing its final context.
/// No transaction or cross-file continuity is inferred.
pub struct BinlogEventStream {
  priv max_event_size : UInt32
  priv mut offset : UInt64
  priv mut context : FormatDescription?
  priv mut failed : Bool
  priv mut finished : Bool
}

///|
/// Signature must start at the beginning of an unencrypted v4 binlog file.
pub fn BinlogEventStream::new(
  signature : BytesView,
  max_event_size~ : UInt32 = 16_777_216,
) -> BinlogEventStream raise DecodeError {
  guard max_event_size >= 19 else { raise InvalidEventLimit(max_event_size~) }
  validate_file_header(signature)
  { max_event_size, offset: 4, context: None, failed: false, finished: false, }
}

///|
/// Physical offset of the next event, or the failing event after an error.
pub fn BinlogEventStream::position(self : BinlogEventStream) -> UInt64 {
  self.offset
}

///|
pub fn BinlogEventStream::format(
  self : BinlogEventStream,
) -> FormatDescription? {
  self.context
}

///|
/// Feed a complete event. Bytes after its declared size are not consumed.
/// The returned views retain their input, including after later calls.
pub fn BinlogEventStream::feed(
  self : BinlogEventStream,
  data : BytesView,
) -> BinlogFileEvent raise DecodeError {
  let file_offset = self.offset
  guard !self.failed else { raise FileReaderFailed(file_offset~) }
  guard !self.finished else { raise StreamAlreadyFinished(file_offset~) }
  errdefer {
    self.failed = true
    self.context = None
  }
  let (header, raw) = complete_event(
    data,
    file_offset~,
    max_event_size=self.max_event_size,
  )
  guard file_offset <= 0xffffffffffffffffUL - header.event_size.to_uint64() else {
    raise InvalidStreamOffset(file_offset~)
  }
  let event = decode_file_event(header, raw, self.context, self.max_event_size)
  self.offset += header.event_size.to_uint64()
  self.context = Some(event.format)
  event
}

///|
/// Mark clean EOF after at least one valid FDE. Successful finish is idempotent;
/// no further events can be fed. An unfinished transaction is outside scope.
pub fn BinlogEventStream::finish(
  self : BinlogEventStream,
) -> Unit raise DecodeError {
  let file_offset = self.offset
  guard !self.failed else { raise FileReaderFailed(file_offset~) }
  if self.finished {
    return
  }
  errdefer {
    self.failed = true
    self.context = None
  }
  guard self.context is Some(_) else {
    raise MissingFormatDescription(file_offset~)
  }
  self.finished = true
}