///|
/// 全库检查按需请求页面,进度事件允许宿主让出执行权和取消。
/// 全库供页协议:按需请求完整页、发送可让出执行权的进度事件,最后交付包含部分结果的详细报告。
pub(all) enum InspectionEvent {
  NeedInspectionPage(Int)
  InspectionProgress(ScanProgress)
  InspectionFinished(DatabaseInspectionDetails)
} derive(Debug)

///|
pub extend InspectionEvent with @debug.Debug::{to_repr}

///|
/// 无 I/O 全库检查游标;包含 schema、对象树、freelist 和 Ptrmap。宿主可供页、拒绝当前读请求或停止,并保留此前观察到的归属与诊断。
pub struct InspectionCursor {
  priv db : Database
  priv state : OwnershipState
  priv max_payload : UInt64
  priv entries : Array[SchemaEntry]
  priv mut entry_index : Int
  priv mut phase : Int
  priv mut root : Int
  priv mut name : String
  priv mut index_root : Bool
  priv mut scan : BTreeCursor?
  priv mut free : FreelistCursor?
  priv mut ptrmap : PtrmapCursor?
  priv mut ptrmap_checked : Bool
  priv mut all_claimed : Bool
  priv mut pending : Int?
  priv mut supplied : Bytes?
  priv mut read_error : SqliteError?
  priv mut request_kind : Int
  priv mut finished : DatabaseInspectionDetails?
}

///|
/// 创建包含 schema、对象树、freelist、Ptrmap 的全库游标;默认累计 payload 64 MiB、诊断 100 条。配置错误返回失败报告,宿主按事件供页或拒绝读请求。
pub fn Database::inspection_cursor(
  self : Database,
  max_total_payload_bytes? : UInt64 = 67108864UL,
  max_issues? : Int = 100,
) -> InspectionCursor {
  self.inspection_cursor_traced([], max_total_payload_bytes~, max_issues~)
}

///|
fn Database::inspection_cursor_traced(
  self : Database,
  located_issues : Array[LocatedDatabaseIssue],
  max_total_payload_bytes~ : UInt64,
  max_issues~ : Int,
) -> InspectionCursor {
  let state : OwnershipState = {
    owners: Map([]),
    issues: [],
    located_issues,
    trace: InspectionTrace::new(),
    max_pages: Int::min(self.limits.max_pages, self.report_max_pages),
    max_issues: if max_issues > 0 {
      max_issues
    } else {
      1
    },
    coverage: true,
    failed: false,
    truncated: false,
    roots: 0,
    records: 0,
    payload: 0UL,
  }
  let vacuum = self.header.largest_root_page != 0U
  let cursor : InspectionCursor = {
    db: self,
    state,
    max_payload: max_total_payload_bytes,
    entries: [],
    entry_index: 0,
    phase: 0,
    root: 1,
    name: "sqlite_schema",
    index_root: false,
    scan: None,
    free: None,
    ptrmap: None,
    ptrmap_checked: !vacuum,
    all_claimed: true,
    pending: None,
    supplied: None,
    read_error: None,
    request_kind: 0,
    finished: None,
  }
  if max_issues < 1 {
    state.error(None, Invalid("诊断条数上限必须为正数"))
    cursor.finished = Some({
      inspection: state.finish(0, !vacuum),
      issues: located_issues,
    })
    return cursor
  }
  if self.page_count > state.max_pages {
    state.error(
      None,
      LimitExceeded("数据库逻辑页数超过全局检查预算"),
    )
    cursor.ptrmap_checked = false
    cursor.phase = 4
    return cursor
  }
  if self.header.largest_root_page.to_uint64() > self.page_count.to_uint64() ||
    (!vacuum && self.header.incremental_vacuum) {
    state.trace.at(Configuration, Some(1), byte_offset=Some(52))
    state.error(None, Invalid("auto-vacuum 文件头字段不一致"))
    cursor.ptrmap_checked = false
    cursor.phase = 4
    return cursor
  }
  let lock_page = 1073741824 / self.header.page_size + 1
  if lock_page <= self.page_count {
    state.claim({
      page_number: lock_page,
      kind: LockByte,
      root_page: None,
      object_name: None,
      parent_page: None,
    }) catch {
      error => state.error(None, error)
    }
  }
  if vacuum {
    let step = self.header.usable_size / 5 + 1
    let mut number = 2
    while number <= self.page_count {
      let actual = if number == lock_page { number + 1 } else { number }
      if actual <= self.page_count {
        state.claim({
          page_number: actual,
          kind: PointerMap,
          root_page: None,
          object_name: None,
          parent_page: None,
        }) catch {
          error => state.error(None, error)
        }
      }
      if self.page_count - number < step {
        break
      }
      number = number + step
    }
  }
  state.roots = 1
  cursor
}

///|
/// 返回全库已成功解码记录、已认领页面和已请求 payload;schema 记录也计数。
pub fn InspectionCursor::progress(self : InspectionCursor) -> ScanProgress {
  {
    records_read: self.state.records,
    pages_read: self.state.owners.length(),
    payload_bytes: self.state.payload,
  }
}

///|
/// 返回检查实际阶段及可用位置;None 字段表示未掌握位置。
pub fn InspectionCursor::location(
  self : InspectionCursor,
) -> DiagnosticLocation {
  self.state.trace.snapshot()
}

///|
/// 供给当前请求的完整页;错页、重复或长度不符抛 Invalid,不允许把宿主短读送入格式解析。
pub fn InspectionCursor::provide_page(
  self : InspectionCursor,
  number : Int,
  bytes : Bytes,
) -> Unit raise SqliteError {
  if self.finished is Some(_) ||
    self.pending != Some(number) ||
    self.supplied is Some(_) {
    raise Invalid("全库游标未请求此页,或页面已经提供")
  }
  if bytes.length() != self.db.header.page_size {
    raise Invalid("全库游标供页长度必须等于 page_size")
  }
  self.supplied = Some(bytes)
}

///|
/// 宿主失败或取消保留观察结果,并明确标记检查未完成。
/// 停止全库检查并释放游标,保留已观察页面、记录、诊断和位置;未完成时状态为 Incomplete,可提供停止原因。
pub fn InspectionCursor::stop(
  self : InspectionCursor,
  reason? : SqliteError = Unsupported("检查由宿主提前终止"),
) -> DatabaseInspectionDetails {
  if self.finished is Some(details) {
    return details
  }
  self.state.error(if self.phase < 2 { Some(self.root) } else { None }, reason)
  self.ptrmap_checked = false
  self.finish()
}

///|
fn InspectionCursor::finish(
  self : InspectionCursor,
) -> DatabaseInspectionDetails {
  if self.finished is Some(details) {
    return details
  }
  if self.scan is Some(scan) {
    ignore(scan.stop())
  }
  self.scan = None
  self.free = None
  self.ptrmap = None
  self.pending = None
  self.supplied = None
  self.entries.clear()
  let details : DatabaseInspectionDetails = {
    inspection: self.state.finish(self.db.page_count, self.ptrmap_checked),
    issues: self.state.located_issues,
  }
  self.finished = Some(details)
  details
}

///|
fn InspectionCursor::request(
  self : InspectionCursor,
  number : Int,
  kind : Int,
) -> InspectionEvent raise SqliteError {
  self.state.trace.at(ReadPage, Some(number))
  if number < 1 || number > self.db.page_count {
    raise Invalid("页号越界:\{number}")
  }
  self.pending = Some(number)
  self.request_kind = kind
  NeedInspectionPage(number)
}

///|
/// 推进一个供页/进度事件或返回详细报告;已完成或停止后重复调用保持最终结果,不执行宿主 I/O。
pub fn InspectionCursor::next(self : InspectionCursor) -> InspectionEvent {
  if self.finished is Some(details) {
    return InspectionFinished(details)
  }
  self.advance() catch {
    error => {
      if self.phase == 3 {
        if error is Invalid(_) {
          self.state.failed = true
        }
        self.state.issue(ScanError(None, error))
      } else {
        self.state.error(
          if self.phase < 2 {
            Some(self.root)
          } else {
            None
          },
          error,
        )
      }
      if self.scan is Some(scan) {
        ignore(scan.stop())
      }
      self.scan = None
      self.pending = None
      self.supplied = None
      if self.phase == 0 {
        self.phase = 4
      } else if self.phase == 1 {
        self.root = 0
      } else if self.phase == 2 {
        self.phase = 3
      } else {
        self.phase = 4
        self.ptrmap_checked = false
      }
      InspectionProgress(self.progress())
    }
  }
}

///|
fn InspectionCursor::advance(
  self : InspectionCursor,
) -> InspectionEvent raise SqliteError {
  if self.pending is Some(number) {
    if self.read_error is Some(error) {
      self.read_error = None
      raise error
    }
    if self.supplied is None {
      return NeedInspectionPage(number)
    }
    let bytes = self.supplied.unwrap()
    self.pending = None
    self.supplied = None
    match self.request_kind {
      1 => {
        let page = self.db.page_from_bytes(
          number,
          bytes,
          trace=Some(self.state.trace),
        )
        self.state.trace.at(
          SchemaDecode,
          Some(number),
          byte_offset=if self.phase == 0 { Some(100) } else { None },
        )
        if self.phase == 0 &&
          page.kind != TableLeaf &&
          page.kind != TableInterior {
          raise Invalid("sqlite_schema 根页必须是 table B-tree")
        }
        if self.index_root &&
          page.kind != IndexLeaf &&
          page.kind != IndexInterior {
          raise Invalid("schema 索引根页不是 index B-tree")
        }
        let scan = self.db.scan_cursor_observed(
          self.root,
          self.state.observer(self.root, self.name),
          limit=self.db.limits.max_rows - self.state.records,
          max_total_payload_bytes=self.max_payload - self.state.payload,
        )
        ignore(scan.next())
        scan.provide_page(number, bytes)
        self.scan = Some(scan)
      }
      2 => self.scan.unwrap().provide_page(number, bytes)
      3 => self.free.unwrap().supplied = Some(bytes)
      _ => {
        let ptrmap = self.ptrmap.unwrap()
        ptrmap.cached = bytes
        ptrmap.cached_number = number
      }
    }
  }
  while true {
    if self.phase < 2 {
      if self.scan is Some(scan) {
        match scan.next() {
          NeedPage(number) => return self.request(number, 2)
          RecordReady(record) => {
            self.state.records = self.state.records + 1
            if self.phase == 0 {
              self.state.trace.at(
                SchemaDecode,
                Some(record.page_number),
                byte_offset=Some(record.cell_offset),
                cell_index=self.state.trace.cell_index,
              )
              self.entries.push(schema_entry(record.values, self.db.page_count))
            }
            return InspectionProgress(self.progress())
          }
          ScanFinished(summary) => {
            self.scan = None
            if summary.completion != Complete {
              self.state.trace.at(
                if self.phase == 0 {
                  SchemaDecode
                } else {
                  Configuration
                },
                if self.phase == 0 {
                  None
                } else {
                  Some(self.root)
                },
              )
              self.state.error(
                Some(self.root),
                LimitExceeded(
                  if self.phase == 0 {
                    "schema 未完整读取,无法发现全部根页"
                  } else {
                    "全局记录数预算已耗尽"
                  },
                ),
              )
              if self.phase == 0 {
                self.phase = 4
                continue
              }
            }
            self.phase = 1
            self.root = 0
          }
        }
      }
      if self.root != 0 {
        return self.request(self.root, 1)
      }
      self.state.trace.at(SchemaDecode, None)
      if self.state.issues.length() >= self.state.max_issues {
        self.state.coverage = false
        self.state.truncated = true
        self.phase = 3
        continue
      }
      if self.entry_index == self.entries.length() {
        self.phase = 2
        continue
      }
      let entry = self.entries[self.entry_index]
      self.entry_index = self.entry_index + 1
      if entry.object_type == "view" || entry.object_type == "trigger" {
        if entry.root_page != 0 {
          self.state.error(
            Some(entry.root_page),
            Invalid("view 或 trigger 不能拥有根页"),
          )
        }
        continue
      }
      if entry.root_page == 0 {
        if entry.object_type == "index" {
          self.state.error(Some(0), Invalid("索引没有根页"))
        }
        continue
      }
      self.root = entry.root_page
      self.name = entry.name
      self.index_root = entry.object_type == "index"
      self.state.roots = self.state.roots + 1
      if !self.index_root {
        self.scan = Some(
          self.db.scan_cursor_observed(
            self.root,
            self.state.observer(self.root, self.name),
            limit=self.db.limits.max_rows - self.state.records,
            max_total_payload_bytes=self.max_payload - self.state.payload,
          ),
        )
      }
      continue
    }
    if self.phase == 2 {
      if self.free is None {
        self.free = Some(
          self.db.freelist_cursor(
            (number, kind, parent) => {
              self.state.claim({
                page_number: number,
                kind,
                root_page: None,
                object_name: None,
                parent_page: parent,
              })
            },
            self.state.trace,
          ),
        )
      }
      match self.free.unwrap().next() {
        Some(number) => return self.request(number, 3)
        None => {
          self.free = None
          self.phase = 3
        }
      }
      return InspectionProgress(self.progress())
    }
    if self.phase == 3 {
      if self.db.header.largest_root_page == 0U {
        self.phase = 4
        continue
      }
      if !self.state.coverage {
        self.ptrmap_checked = false
        self.phase = 4
        continue
      }
      if self.ptrmap is None {
        self.state.check_root_positions(self.db)
        self.ptrmap = Some(self.db.ptrmap_cursor(self.state.trace))
      }
      match self.ptrmap.unwrap().next() {
        MapPage(number) => return self.request(number, 4)
        MapEntry(entry) => {
          if self.state.issues.length() >= self.state.max_issues {
            self.state.truncated = true
            self.ptrmap_checked = false
            self.phase = 4
            continue
          }
          if !self.state.check_ptrmap_entry(entry) {
            self.all_claimed = false
          }
          return InspectionProgress(self.progress())
        }
        MapFinished => {
          self.ptrmap_checked = self.all_claimed
          self.phase = 4
        }
      }
      continue
    }
    return InspectionFinished(self.finish())
  } nobreak {
    abort("全库检查循环意外结束")
  }
}

///|
/// 将本次页读取失败送回状态机,其他可独立检查的对象仍可继续。
/// 将当前页面读取错误交给核心,保留部分报告并继续可独立检查的对象;schema 读取失败不能继续发现全部根页。范围宿主错误应先使用 source/ 映射。
pub fn InspectionCursor::reject_page(
  self : InspectionCursor,
  reason : SqliteError,
) -> Unit raise SqliteError {
  if self.finished is Some(_) ||
    self.pending is None ||
    self.supplied is Some(_) ||
    self.read_error is Some(_) {
    raise Invalid("全库游标没有待失败的页请求")
  }
  self.read_error = Some(reason)
}