//! Canonical [`tracing`] span and event names emitted by the library.
//!
//! Centralised so a rename lands in one file and the `SemVer` impact of
//! changing a public-surface name is obvious from the diff. The crate-level
//! `# Tracing` section in [`lib.rs`](crate) documents the catalog plus the
//! stability contract; this module is the source of truth the emission
//! sites resolve against.
//!
//! Field names are intentionally **not** centralised: they appear at a
//! single emission site each, and the `tracing` macro syntax for
//! const-named fields (`{ FIELD_NAME } = ...`) clutters the call site
//! without a corresponding rename-cost benefit. The contract section in
//! `lib.rs` enumerates the stable field names directly.
//!
//! [`tracing`]: https://docs.rs/tracing

/// Top-level (`info!`-level) and per-section (`debug!`-level) span names.
///
/// Every value is a `'static` string suitable for use as the first argument
/// to [`tracing::info_span!`] / [`tracing::debug_span!`]. The macro accepts
/// a `const &str` in name position and emits the same metadata as a string
/// literal would.
pub(crate) mod span_names {
    // ----- Composition layer (`src/sqpack.rs`) -----

    /// Top-level span around
    /// [`SqPack::open`](crate::SqPack::open) /
    /// [`SqPack::open_with_vfs`](crate::SqPack::open_with_vfs).
    pub const SQPACK_OPEN: &str = "sqpack.open";
    /// Top-level span around [`SqPack::read_file`](crate::SqPack::read_file).
    pub const SQPACK_READ_FILE: &str = "sqpack.read_file";
    /// Top-level span around
    /// [`SqPack::read_file_typed`](crate::SqPack::read_file_typed).
    pub const SQPACK_READ_FILE_TYPED: &str = "sqpack.read_file_typed";
    /// Per-call sub-span (debug-level) around
    /// [`SqPack::read_file_streaming`](crate::SqPack::read_file_streaming).
    /// Streaming construction is a single-shot setup that does not justify
    /// an `info!`-level span; the completion event
    /// `"sqpack.read_file_streaming opened"` still fires at `info!` so a
    /// default-level subscriber sees the open.
    pub const SQPACK_READ_FILE_STREAMING: &str = "sqpack.read_file_streaming";
    /// Top-level span around [`SqPack::peek_kind`](crate::SqPack::peek_kind).
    pub const SQPACK_PEEK_KIND: &str = "sqpack.peek_kind";
    /// Top-level span around
    /// [`SqPack::excel_list`](crate::SqPack::excel_list). Wraps the
    /// `exd/root.exl` read + parse into a single info-level operation;
    /// the matching completion event reports sheet count and elapsed
    /// milliseconds.
    pub const SQPACK_EXCEL_LIST: &str = "sqpack.excel_list";
    /// Top-level span around
    /// [`SqPack::excel_header`](crate::SqPack::excel_header). Wraps the
    /// `exd/<sheet>.exh` read + parse into a single info-level operation;
    /// the matching completion event reports sheet name, byte size,
    /// column / page / language counts, and elapsed milliseconds.
    pub const SQPACK_EXCEL_HEADER: &str = "sqpack.excel_header";
    /// Top-level span around
    /// [`SqPack::excel_data`](crate::SqPack::excel_data). Wraps the
    /// `exd/<sheet>_<page>_<lang>.exd` read + parse into a single
    /// info-level operation; the matching completion event reports
    /// sheet name, page id, language, byte size, row and sub-row
    /// counts, and elapsed milliseconds.
    pub const SQPACK_EXCEL_DATA: &str = "sqpack.excel_data";
    /// Top-level span around
    /// [`SqPack::sheet`](crate::SqPack::sheet). Wraps the EXH read
    /// plus handle construction into a single info-level operation;
    /// the matching completion event `"sqpack.sheet"` reports sheet
    /// name, column / page / language counts, and elapsed milliseconds.
    /// The inner `sqpack.excel_header` span is preserved so a
    /// subscriber filtering on either name sees the full picture.
    pub const SQPACK_SHEET_OPEN: &str = "sqpack.sheet";
    /// Top-level span around
    /// [`SqPack::sheets`](crate::SqPack::sheets). Wraps the EXL read
    /// into a single info-level operation; the matching completion
    /// event `"sqpack.sheets"` reports the total number of sheet
    /// entries the iterator will visit and elapsed milliseconds. The
    /// inner `sqpack.excel_list` span is preserved.
    pub const SQPACK_SHEETS: &str = "sqpack.sheets";
    /// Top-level span around
    /// [`SqPack::sheet_names`](crate::SqPack::sheet_names). Same shape
    /// as [`SQPACK_SHEETS`] but no EXH files are touched — the
    /// completion event `"sqpack.sheet_names"` reports just the entry
    /// count and elapsed milliseconds.
    pub const SQPACK_SHEET_NAMES: &str = "sqpack.sheet_names";
    /// Top-level span around
    /// [`SqPack::read_tex_bytes`](crate::SqPack::read_tex_bytes).
    /// The matching info-level completion event
    /// `"sqpack.read_tex_bytes"` carries `path`, `bytes`, and
    /// `elapsed_ms`.
    pub const SQPACK_READ_TEX_BYTES: &str = "sqpack.read_tex_bytes";
    /// Top-level span around
    /// [`SqPack::read_mdl_bytes`](crate::SqPack::read_mdl_bytes).
    /// The matching info-level completion event
    /// `"sqpack.read_mdl_bytes"` carries `path`, `bytes`, and
    /// `elapsed_ms`.
    pub const SQPACK_READ_MDL_BYTES: &str = "sqpack.read_mdl_bytes";
    /// Per-call sub-span (debug-level) around
    /// [`Sheet::row`](crate::excel::Sheet::row). The matching
    /// completion event `"sqpack.sheet.row"` reports the outcome
    /// (`hit` / `miss` / `err`) and elapsed milliseconds; useful for
    /// scoping single-row lookups in a noisy install sweep.
    pub const SQPACK_SHEET_ROW: &str = "sqpack.sheet.row";
    /// Per-call sub-span (debug-level) around
    /// [`Sheet::sub_row`](crate::excel::Sheet::sub_row). Sister of
    /// [`SQPACK_SHEET_ROW`] for `SubRows` sheets; the completion
    /// event `"sqpack.sheet.sub_row"` carries the same outcome /
    /// elapsed-ms field set plus the `sub_id`.
    pub const SQPACK_SHEET_SUB_ROW: &str = "sqpack.sheet.sub_row";

    // ----- Format layer (`src/index.rs`, `src/index2.rs`, `src/dat.rs`) -----

    /// Top-level span around [`Index::open`](crate::Index::open) /
    /// [`Index::open_in`](crate::Index::open_in) /
    /// [`Index::read`](crate::Index::read).
    pub const READ_INDEX: &str = "read_index";
    /// Top-level span around [`Index2::open`](crate::Index2::open) /
    /// [`Index2::open_in`](crate::Index2::open_in) /
    /// [`Index2::read`](crate::Index2::read).
    pub const READ_INDEX2: &str = "read_index2";
    /// Top-level span around [`DatFile::open`](crate::DatFile::open) /
    /// [`DatFile::open_in`](crate::DatFile::open_in) /
    /// [`DatFile::open_at`](crate::DatFile::open_at) /
    /// [`DatFile::open_at_in`](crate::DatFile::open_at_in).
    pub const OPEN_DAT: &str = "open_dat";
    /// Top-level span around
    /// [`DatFile::read_file`](crate::DatFile::read_file) /
    /// [`DatFile::read_file_into`](crate::DatFile::read_file_into).
    pub const DAT_READ_FILE: &str = "read_file";
    /// Top-level span around
    /// [`DatFile::read_file_typed`](crate::DatFile::read_file_typed).
    pub const DAT_READ_FILE_TYPED: &str = "read_file_typed";
    /// Per-call sub-span (debug-level) around
    /// [`DatFile::read_file_streaming`](crate::DatFile::read_file_streaming).
    /// Setup-only span; the completion event
    /// `"read_file_streaming opened"` fires at `info!` so a default-level
    /// subscriber sees the open.
    pub const DAT_READ_FILE_STREAMING: &str = "read_file_streaming";
    /// Per-file sub-span (debug-level) entered while decoding a
    /// [`FileKind::Model`](crate::dat::FileKind::Model) payload inside
    /// [`DatFile::read_file`](crate::DatFile::read_file) /
    /// [`DatFile::read_file_typed`](crate::DatFile::read_file_typed).
    pub const READ_MODEL: &str = "read_model";
    /// Per-file sub-span (debug-level) entered while decoding a
    /// [`FileKind::Texture`](crate::dat::FileKind::Texture) payload inside
    /// [`DatFile::read_file`](crate::DatFile::read_file) /
    /// [`DatFile::read_file_typed`](crate::DatFile::read_file_typed).
    pub const READ_TEXTURE: &str = "read_texture";

    // ----- Verifier layer (`src/verify.rs`) -----

    /// Top-level span around
    /// [`SqPackPathVerifier::verify`](crate::SqPackPathVerifier::verify).
    pub const VERIFY_RUN: &str = "verify.run";
    /// Per-entry sub-span (debug-level) entered inside
    /// [`SqPackPathManifestBuilder::add`](crate::SqPackPathManifestBuilder::add).
    /// Each manifest entry gets one span; the `"manifest.finish"` info
    /// event fires once per builder from
    /// [`SqPackPathManifestBuilder::finish`](crate::SqPackPathManifestBuilder::finish).
    pub const MANIFEST_ADD: &str = "manifest.add";

    /// Top-level span around
    /// [`SqPackHashManifestBuilder::build`](crate::SqPackHashManifestBuilder::build).
    /// The hash-keyed builder sweeps the entire install via
    /// [`SqPack::enumerate_archives`](crate::SqPack::enumerate_archives) and
    /// hashes every standard-kind entry; one `info!` completion event
    /// summarises counts + elapsed milliseconds for the whole sweep.
    pub const HASH_MANIFEST_BUILD: &str = "hash_manifest.build";
    /// Top-level span around
    /// [`SqPackHashVerifier::verify`](crate::SqPackHashVerifier::verify).
    /// Walks every archive in the live install and compares each entry's
    /// observed SHA-256 against the supplied
    /// [`SqPackHashManifest`](crate::SqPackHashManifest); one `info!`
    /// completion event summarises counts + elapsed milliseconds.
    pub const HASH_VERIFY_RUN: &str = "hash_verify.run";

    // ----- TEX layer (`src/tex/*.rs`) -----

    /// Top-level span around [`Tex::parse`](crate::tex::Tex::parse).
    /// The matching info-level completion event `"tex.parse"` carries
    /// `bytes`, `format`, `width`, `height`, `depth`, `mip_count`,
    /// `array_size`, `slices_per_mip`, and `elapsed_ms`.
    pub const TEX_PARSE: &str = "tex.parse";
    /// Top-level span around
    /// [`decompress_to_rgba8`](crate::tex::decompress_to_rgba8).
    /// The matching info-level completion event `"tex.decompress"`
    /// carries `format`, `width`, `height`, `bytes_in`, `bytes_out`,
    /// and `elapsed_ms`. Per-block walk progress is emitted at
    /// `debug!` only.
    pub const TEX_DECOMPRESS: &str = "tex.decompress";

    // ----- MDL layer (`src/mdl/*.rs`) -----

    /// Top-level span around [`Mdl::parse`](crate::mdl::Mdl::parse).
    /// The matching info-level completion event `"mdl.parse"` carries
    /// `bytes`, `version`, `lod_count`, `mesh_count`, `submesh_count`,
    /// `material_count`, `bone_count`, `shape_count`, and
    /// `elapsed_ms`.
    pub const MDL_PARSE: &str = "mdl.parse";

    // ----- SCD layer (`src/scd/*.rs`) -----

    /// Top-level span around [`Scd::parse`](crate::scd::Scd::parse).
    /// The matching info-level completion event `"scd.parse"` carries
    /// `bytes`, `version`, `sound_count`, `track_count`, `audio_count`,
    /// and `elapsed_ms`.
    pub const SCD_PARSE: &str = "scd.parse";
    /// Top-level span around
    /// [`Track::decode_to_pcm16`](crate::scd::Track::decode_to_pcm16).
    /// The matching info-level completion event `"scd.decode"` carries
    /// `format`, `bytes_in`, `samples_out`, and `elapsed_ms`. Per-block
    /// MS-ADPCM walk progress is emitted at `debug!` only.
    pub const SCD_DECODE: &str = "scd.decode";

    // ----- SKLB layer (`src/sklb/*.rs`) -----

    /// Top-level span around [`Sklb::parse`](crate::sklb::Sklb::parse).
    /// The matching info-level completion event `"sklb.parse"` carries
    /// `bytes`, `version`, `header_size`, `havok_offset`, `havok_len`,
    /// `layer_offset`, and `elapsed_ms`.
    pub const SKLB_PARSE: &str = "sklb.parse";

    // ----- PAP layer (`src/pap/*.rs`) -----

    /// Top-level span around [`Pap::parse`](crate::pap::Pap::parse).
    /// The matching info-level completion event `"pap.parse"` carries
    /// `bytes`, `version`, `header_size`, `animation_count`,
    /// `info_offset`, `havok_offset`, `havok_len`, `tmb_offset`,
    /// `tmb_footer_len`, and `elapsed_ms`.
    pub const PAP_PARSE: &str = "pap.parse";

    // ----- PHYB layer (`src/phyb/*.rs`) -----

    /// Top-level span around [`Phyb::parse`](crate::phyb::Phyb::parse).
    /// The matching info-level completion event `"phyb.parse"` carries
    /// `bytes`, `version`, `data_type`, `collision_offset`,
    /// `simulator_offset`, `simulator_count`, `capsules`, `ellipsoids`,
    /// `normal_planes`, `three_point_planes`, `spheres`, and
    /// `elapsed_ms`.
    pub const PHYB_PARSE: &str = "phyb.parse";

    // ----- TMB layer (`src/tmb/*.rs`) -----

    /// Top-level span around [`Tmb::parse`](crate::tmb::Tmb::parse).
    /// The matching info-level completion event `"tmb.parse"` carries
    /// `bytes`, `file_size`, `chunk_count`, `known_chunks`,
    /// `opaque_chunks`, `entries`, `opaque_entries`, and
    /// `elapsed_ms`.
    pub const TMB_PARSE: &str = "tmb.parse";

    // ----- EID layer (`src/eid/*.rs`) -----

    /// Top-level span around [`Eid::parse`](crate::eid::Eid::parse).
    /// The matching info-level completion event `"eid.parse"` carries
    /// `bytes`, `version`, `header_size`, `entry_count`,
    /// `entries_offset`, and `elapsed_ms`.
    pub const EID_PARSE: &str = "eid.parse";

    // ----- Write layer (`src/write/*.rs`) -----

    /// Top-level span around per-file
    /// [`SqPackAppender::append`](crate::write::SqPackAppender::append).
    /// The matching info-level completion event `"sqpack.append"` carries
    /// `path`, `bytes_in`, `bytes_written`, `byte_offset`, `block_count`,
    /// and `elapsed_ms`.
    pub const SQPACK_APPEND: &str = "sqpack.append";
    /// Top-level span around
    /// [`SqPackAppender::commit`](crate::write::SqPackAppender::commit).
    /// The matching info-level completion event `"sqpack.append.commit"`
    /// carries `appended`, `dats_touched`, `indexes_touched`, and
    /// `elapsed_ms`.
    pub const SQPACK_APPEND_COMMIT: &str = "sqpack.append.commit";
    /// Top-level span around
    /// [`SqPackRepacker::build`](crate::write::SqPackRepacker::build).
    /// The matching info-level completion event `"sqpack.repack.build"`
    /// carries `file_count`, `dat_bytes`, `index_bytes`, `index2_bytes`,
    /// and `elapsed_ms`.
    pub const SQPACK_REPACK_BUILD: &str = "sqpack.repack.build";
    /// Top-level span around
    /// [`SqPackRepacker::write_to`](crate::write::SqPackRepacker::write_to).
    /// The matching info-level completion event `"sqpack.repack.write"`
    /// carries `file_count`, `bytes_written`, and `elapsed_ms`.
    pub const SQPACK_REPACK_WRITE: &str = "sqpack.repack.write";

    // ----- Shared format sub-spans (debug-level, used by multiple parsers) -----

    /// Per-section sub-span entered while parsing the generic
    /// [`SqPackHeader`](crate::SqPackHeader). Lives inside the parent's
    /// info-level span (`read_index`, `read_index2`, or `open_dat`).
    pub const SQPACK_HEADER: &str = "sqpack_header";
    /// Per-section sub-span entered while parsing the
    /// [`SqPackIndexHeader`](crate::SqPackIndexHeader). Lives inside the
    /// parent's info-level span (`read_index` or `read_index2`).
    pub const INDEX_HEADER: &str = "index_header";
    /// Per-section sub-span entered while parsing the v1 / v2 hash table.
    /// Lives inside `read_index` / `read_index2`.
    pub const HASH_TABLE: &str = "hash_table";
}

/// Canonical `debug!` / `info!` event message names emitted by the
/// library outside any matching-named span.
///
/// Most info-level completion events reuse their parent span's name as
/// the message (so a subscriber filtering on `sqpack.open` sees both
/// the span and its completion event). The constants below cover the
/// remaining events that don't share a name with an existing span.
pub(crate) mod event_names {
    /// Debug-level event fired on the first-touch mmap of a file via
    /// [`MmapFs`](crate::mmap_fs::MmapFs). Carries `path` (canonical
    /// `Display`) and `len` (`Option<u64>`, `None` if metadata
    /// reported an error). Best-effort: debug-level events are
    /// documented in the `lib.rs` catalog as not contracted at `SemVer`.
    ///
    /// `#[allow(dead_code)]` because the emission site in
    /// `mmap_fs.rs` uses the literal directly (the `tracing` macros
    /// only accept literals in message position); the test
    /// `mmap_map_event_name_matches_schema` pins literal == constant.
    /// Gated on the `mmap` Cargo feature so the constant is only
    /// reachable from builds that actually emit it (mirrors the
    /// `block_cache` event constants below).
    #[cfg(feature = "mmap")]
    #[allow(dead_code)]
    pub const MMAP_MAP: &str = "mmap.map";

    /// Trace-level event fired on every [`BlockCache`](crate::BlockCache)
    /// lookup that produced a hit (feature `block_cache`). Carries
    /// `install_id`, `triple` (debug format), `dat_id`, `byte_offset`,
    /// and `len` (current cache size).
    ///
    /// Pinned as part of the SemVer-stable schema for the
    /// `block_cache` feature: the test `event_names_match_schema`
    /// asserts the emission-site literal matches this constant, so a
    /// rename in either place triggers test failure rather than
    /// silently breaking downstream subscriber filters.
    ///
    /// `#[allow(dead_code)]` because the emission site in
    /// `block_cache.rs` uses the literal directly (the `tracing`
    /// macros only accept literals in message position).
    #[cfg(feature = "block-cache")]
    #[allow(dead_code)]
    pub const BLOCK_CACHE_HIT: &str = "block_cache.hit";

    /// Trace-level event fired on every [`BlockCache`](crate::BlockCache)
    /// lookup that produced a miss (feature `block_cache`). Same
    /// field set as [`BLOCK_CACHE_HIT`].
    ///
    /// Pinned as part of the SemVer-stable schema for the
    /// `block_cache` feature: the test `event_names_match_schema`
    /// asserts the emission-site literal matches this constant.
    #[cfg(feature = "block-cache")]
    #[allow(dead_code)]
    pub const BLOCK_CACHE_MISS: &str = "block_cache.miss";
}

#[cfg(test)]
mod tests {
    use super::span_names;

    /// Smoke test that every schema constant is a non-empty `&str`. Not a
    /// deep audit — the compiler enforces the type — but a single point
    /// that breaks if a constant is ever emptied or renamed to a typo.
    #[test]
    fn span_names_are_non_empty() {
        for name in [
            span_names::SQPACK_OPEN,
            span_names::SQPACK_READ_FILE,
            span_names::SQPACK_READ_FILE_TYPED,
            span_names::SQPACK_READ_FILE_STREAMING,
            span_names::SQPACK_PEEK_KIND,
            span_names::SQPACK_EXCEL_LIST,
            span_names::SQPACK_EXCEL_HEADER,
            span_names::SQPACK_EXCEL_DATA,
            span_names::SQPACK_SHEET_OPEN,
            span_names::SQPACK_SHEETS,
            span_names::SQPACK_SHEET_NAMES,
            span_names::SQPACK_READ_TEX_BYTES,
            span_names::SQPACK_READ_MDL_BYTES,
            span_names::SQPACK_SHEET_ROW,
            span_names::SQPACK_SHEET_SUB_ROW,
            span_names::READ_INDEX,
            span_names::READ_INDEX2,
            span_names::OPEN_DAT,
            span_names::DAT_READ_FILE,
            span_names::DAT_READ_FILE_TYPED,
            span_names::DAT_READ_FILE_STREAMING,
            span_names::READ_MODEL,
            span_names::READ_TEXTURE,
            span_names::VERIFY_RUN,
            span_names::MANIFEST_ADD,
            span_names::HASH_MANIFEST_BUILD,
            span_names::HASH_VERIFY_RUN,
            span_names::TEX_PARSE,
            span_names::TEX_DECOMPRESS,
            span_names::MDL_PARSE,
            span_names::SCD_PARSE,
            span_names::SCD_DECODE,
            span_names::SKLB_PARSE,
            span_names::PAP_PARSE,
            span_names::PHYB_PARSE,
            span_names::TMB_PARSE,
            span_names::EID_PARSE,
            span_names::SQPACK_APPEND,
            span_names::SQPACK_APPEND_COMMIT,
            span_names::SQPACK_REPACK_BUILD,
            span_names::SQPACK_REPACK_WRITE,
            span_names::SQPACK_HEADER,
            span_names::INDEX_HEADER,
            span_names::HASH_TABLE,
        ] {
            assert!(!name.is_empty(), "schema span name must be non-empty");
        }
    }

    #[test]
    fn sqpack_layer_span_names_are_namespaced() {
        // Sanity: every span emitted from `src/sqpack.rs` carries the
        // `sqpack.` prefix so a default-level subscriber can scope its
        // output via a single filter.
        for name in [
            span_names::SQPACK_OPEN,
            span_names::SQPACK_READ_FILE,
            span_names::SQPACK_READ_FILE_TYPED,
            span_names::SQPACK_READ_FILE_STREAMING,
            span_names::SQPACK_PEEK_KIND,
            span_names::SQPACK_EXCEL_LIST,
            span_names::SQPACK_EXCEL_HEADER,
            span_names::SQPACK_EXCEL_DATA,
            span_names::SQPACK_SHEET_OPEN,
            span_names::SQPACK_SHEETS,
            span_names::SQPACK_SHEET_NAMES,
            span_names::SQPACK_READ_TEX_BYTES,
            span_names::SQPACK_READ_MDL_BYTES,
            span_names::SQPACK_SHEET_ROW,
            span_names::SQPACK_SHEET_SUB_ROW,
            span_names::SQPACK_APPEND,
            span_names::SQPACK_APPEND_COMMIT,
            span_names::SQPACK_REPACK_BUILD,
            span_names::SQPACK_REPACK_WRITE,
        ] {
            assert!(
                name.starts_with("sqpack."),
                "sqpack-layer span `{name}` must carry the `sqpack.` prefix",
            );
        }
    }
}
