use super::{MatrixError, Result};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::collections::{BTreeMap, BTreeSet};
use std::fs::{self, OpenOptions};
use std::io::Write as _;
use std::path::{Path, PathBuf};

const BASELINE_PATH: &str = "api/SEMVER-BASELINE.json";
const REPORT_PATH: &str = "api/SEMVER-AUDIT.md";
const MEMBER_PATH: &str = "api/RUST-MAPPING.tsv";
const TYPE_PATH: &str = "api/RUST-TYPES.tsv";
const UPSTREAM_COMMIT: &str = "2aa70bb68513b39795da5d13c88f31b86e85a3ba";

const PUBLIC_CRATES: [(&str, &str); 15] = [
    ("libremetaverse", "crates/libremetaverse"),
    ("libremetaverse-types", "crates/libremetaverse-types"),
    (
        "libremetaverse-structured-data",
        "crates/libremetaverse-structured-data",
    ),
    ("libremetaverse-imaging", "crates/libremetaverse-imaging"),
    (
        "libremetaverse-imaging-skia",
        "crates/libremetaverse-imaging-skia",
    ),
    (
        "libremetaverse-prim-mesher",
        "crates/libremetaverse-prim-mesher",
    ),
    (
        "libremetaverse-rendering-simple",
        "crates/libremetaverse-rendering-simple",
    ),
    (
        "libremetaverse-rendering-mesh-foundry",
        "crates/libremetaverse-rendering-mesh-foundry",
    ),
    ("metacrate-lsl-tools", "crates/metacrate-lsl-tools"),
    ("libremetaverse-rlv", "crates/libremetaverse-rlv"),
    (
        "libremetaverse-utilities",
        "crates/libremetaverse-utilities",
    ),
    (
        "libremetaverse-voice-vivox",
        "crates/libremetaverse-voice-vivox",
    ),
    (
        "libremetaverse-voice-webrtc",
        "crates/libremetaverse-voice-webrtc",
    ),
    ("libremetaverse-openjpeg", "crates/libremetaverse-openjpeg"),
    ("libremetaverse-opus", "crates/libremetaverse-opus"),
];

const REQUIRED_NON_EXHAUSTIVE_ERRORS: [(&str, &str); 7] = [
    ("crates/libremetaverse-types/src/shim.rs", "Error"),
    ("crates/libremetaverse-openjpeg/src/lib.rs", "Error"),
    (
        "crates/libremetaverse-voice-webrtc/src/native.rs",
        "WebRtcError",
    ),
    (
        "crates/libremetaverse/src/client_core.rs",
        "ClientCoreError",
    ),
    (
        "crates/libremetaverse/src/udp_transport.rs",
        "UdpTransportError",
    ),
    (
        "crates/libremetaverse-voice-vivox/src/protocol.rs",
        "VivoxError",
    ),
    (
        "crates/libremetaverse-rlv/src/protocol.rs",
        "RlvParseErrorKind",
    ),
];

const REQUIRED_THREAD_SAFE_TRAITS: [(&str, &str); 6] = [
    (
        "crates/libremetaverse/src/generated.rs",
        "pub trait IBakingTextureProvider: std::any::Any + Send + Sync",
    ),
    (
        "crates/libremetaverse/src/generated.rs",
        "pub trait IGridClient: Send + Sync",
    ),
    (
        "crates/libremetaverse/src/generated.rs",
        "pub trait ICurrentOutfitPolicy: Send + Sync",
    ),
    (
        "crates/libremetaverse-rlv/src/generated.rs",
        "pub trait IRlvActionCallbacks: Send + Sync",
    ),
    (
        "crates/libremetaverse-rlv/src/generated.rs",
        "pub trait IRlvQueryCallbacks: Send + Sync",
    ),
    (
        "crates/libremetaverse-voice-webrtc/src/compatibility.rs",
        "pub trait IVoiceLogger: Send + Sync",
    ),
];

const OWNERSHIP_TERMS: [&str; 14] = [
    "none",
    "owned_value",
    "shared_self",
    "shared_or_mutable_self",
    "mutable_self",
    "owned",
    "optional_owned",
    "mutable_borrow",
    "optional_mutable_borrow",
    "mutable_output",
    "shared_borrow",
    "optional_shared",
    "shared",
    "stored_callback",
];

type Row = BTreeMap<String, String>;

#[derive(Debug)]
struct QualityCounts {
    intentional_differences: usize,
    descriptive_overloads: usize,
    async_functions: usize,
    send_boxed_futures: usize,
    thread_safe_callbacks: usize,
    enum_types: usize,
    bitflag_types: usize,
}

#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
struct ApiBaseline {
    schema: u32,
    release: String,
    upstream_commit: String,
    mapped_public_types: usize,
    mapped_members: usize,
    external_signature_types: usize,
    support_traits: usize,
    intentional_differences: usize,
    descriptive_overloads: usize,
    async_functions: usize,
    send_boxed_futures: usize,
    thread_safe_callbacks: usize,
    thread_safe_dyn_traits: usize,
    non_exhaustive_error_enums: usize,
    enum_types: usize,
    bitflag_types: usize,
    unexplained_mappings: usize,
    accidental_public_dependency_types: usize,
    type_mapping_sha256: String,
    member_mapping_sha256: String,
    exported_mapping_sha256: String,
    rust_declaration_sha256: String,
    crates: Vec<CrateSurface>,
}

#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
struct CrateSurface {
    name: String,
    version: String,
    features: BTreeMap<String, Vec<String>>,
    mapped_types: usize,
    mapped_members: usize,
    public_declarations: usize,
    declaration_sha256: String,
}

#[derive(Debug, Serialize)]
struct ApiEvidence<'a> {
    schema: u32,
    release: &'a str,
    upstream_commit: &'a str,
    baseline_sha256: String,
    mapped_public_types: usize,
    mapped_members: usize,
    intentional_differences: usize,
    unexplained_mappings: usize,
    accidental_public_dependency_types: usize,
    thread_safe_dyn_traits: usize,
    non_exhaustive_error_enums: usize,
    downstream_compile_fixture: &'static str,
    representative_port: &'static str,
    status: &'static str,
}

/// Writes the reviewed first-release API/SemVer baseline and audit report.
///
/// # Errors
///
/// Returns an error when the mapped/exported surface or a quality invariant is
/// incomplete, or when the deterministic output cannot be written.
pub fn write_api_baseline(root: &Path) -> Result<()> {
    let baseline = inspect(root)?;
    fs::write(root.join(BASELINE_PATH), baseline_json(&baseline)?)?;
    fs::write(root.join(REPORT_PATH), render_report(&baseline))?;
    Ok(())
}

/// Audits the current API against the checked first-release `SemVer` baseline.
///
/// # Errors
///
/// Returns an error for missing/unexplained mappings, a changed public surface,
/// weak async/thread/error contracts, feature drift, leaked dependency types,
/// stale reports, or evidence I/O failures.
pub fn audit_api_surface(root: &Path, evidence: &Path) -> Result<()> {
    if evidence.exists() {
        return Err(MatrixError::new(format!(
            "{} already exists; preserve or remove it before rerunning the audit",
            evidence.display()
        )));
    }
    let current = inspect(root)?;
    let baseline_bytes = fs::read(root.join(BASELINE_PATH))?;
    let baseline: ApiBaseline = serde_json::from_slice(&baseline_bytes)?;
    if baseline != current {
        return Err(MatrixError::new(
            "public API differs from api/SEMVER-BASELINE.json; review the SemVer impact before recording a new baseline",
        ));
    }
    if fs::read_to_string(root.join(REPORT_PATH))? != render_report(&current) {
        return Err(MatrixError::new(
            "API/SemVer audit report is stale; run api-baseline-write after review",
        ));
    }

    let record = ApiEvidence {
        schema: 1,
        release: &current.release,
        upstream_commit: &current.upstream_commit,
        baseline_sha256: sha256(&baseline_bytes),
        mapped_public_types: current.mapped_public_types,
        mapped_members: current.mapped_members,
        intentional_differences: current.intentional_differences,
        unexplained_mappings: current.unexplained_mappings,
        accidental_public_dependency_types: current.accidental_public_dependency_types,
        thread_safe_dyn_traits: current.thread_safe_dyn_traits,
        non_exhaustive_error_enums: current.non_exhaustive_error_enums,
        downstream_compile_fixture: "tests/api-compile",
        representative_port: "tests/semver-port",
        status: "ok",
    };
    if let Some(parent) = evidence.parent() {
        fs::create_dir_all(parent)?;
    }
    let mut file = OpenOptions::new()
        .write(true)
        .create_new(true)
        .open(evidence)?;
    serde_json::to_writer_pretty(&mut file, &record)?;
    file.write_all(b"\n")?;
    file.sync_all()?;
    Ok(())
}

fn inspect(root: &Path) -> Result<ApiBaseline> {
    let (type_header, type_rows) = read_tsv(&root.join(TYPE_PATH))?;
    let (member_header, member_rows) = read_tsv(&root.join(MEMBER_PATH))?;
    validate_mapping_headers(&type_header, &member_header)?;
    validate_unique(&type_rows, "csharp_type_id", "type mapping")?;
    validate_unique(&member_rows, "csharp_id", "member mapping")?;
    validate_unique(&member_rows, "rust_item_path", "Rust member destination")?;
    validate_members(&member_rows)?;
    validate_types(&type_rows)?;

    let (public_type_ids, member_ids) = mapped_ids(&type_rows, &member_rows);
    let (exported_types, exported_members, exported_bytes) = exported_mappings(root)?;
    if public_type_ids != exported_types || member_ids != exported_members {
        return Err(MatrixError::new(format!(
            "mapped/exported coverage mismatch: types {}/{}, members {}/{}",
            exported_types.len(),
            public_type_ids.len(),
            exported_members.len(),
            member_ids.len()
        )));
    }

    validate_non_exhaustive_errors(root)?;
    validate_thread_safe_traits(root)?;
    validate_dependency_roots(&member_rows)?;

    let workspace = fs::read_to_string(root.join("Cargo.toml"))?;
    let release = package_value(&workspace, "version")
        .ok_or_else(|| MatrixError::new("workspace package version is missing"))?;
    let (crates, declarations) = inspect_crates(root, &release, &type_rows, &member_rows)?;
    let counts = quality_counts(&type_rows, &member_rows);

    let baseline = ApiBaseline {
        schema: 1,
        release,
        upstream_commit: UPSTREAM_COMMIT.to_owned(),
        mapped_public_types: public_type_ids.len(),
        mapped_members: member_ids.len(),
        external_signature_types: type_rows
            .iter()
            .filter(|row| {
                !field(row, "csharp_type_id").starts_with("T:LibreMetaverse")
                    && field(row, "source_kind") != "referenced_support_trait"
            })
            .count(),
        support_traits: type_rows
            .iter()
            .filter(|row| field(row, "source_kind") == "referenced_support_trait")
            .count(),
        intentional_differences: counts.intentional_differences,
        descriptive_overloads: counts.descriptive_overloads,
        async_functions: counts.async_functions,
        send_boxed_futures: counts.send_boxed_futures,
        thread_safe_callbacks: counts.thread_safe_callbacks,
        thread_safe_dyn_traits: REQUIRED_THREAD_SAFE_TRAITS.len(),
        non_exhaustive_error_enums: REQUIRED_NON_EXHAUSTIVE_ERRORS.len(),
        enum_types: counts.enum_types,
        bitflag_types: counts.bitflag_types,
        unexplained_mappings: 0,
        accidental_public_dependency_types: 0,
        type_mapping_sha256: sha256(&fs::read(root.join(TYPE_PATH))?),
        member_mapping_sha256: sha256(&fs::read(root.join(MEMBER_PATH))?),
        exported_mapping_sha256: sha256(&exported_bytes),
        rust_declaration_sha256: sha256(declarations.join("\n").as_bytes()),
        crates,
    };
    validate_totals(&baseline)?;
    Ok(baseline)
}

fn validate_mapping_headers(type_header: &[String], member_header: &[String]) -> Result<()> {
    require_columns(
        type_header,
        &[
            "csharp_type_id",
            "csharp_signature",
            "source_kind",
            "rust_crate",
            "rust_path",
            "mapping_decision",
            "inheritance_mapping",
            "enum_mapping",
            "status",
        ],
        TYPE_PATH,
    )?;
    require_columns(
        member_header,
        &[
            "csharp_id",
            "csharp_kind",
            "csharp_signature",
            "rust_crate",
            "rust_item_path",
            "rust_signature",
            "ownership",
            "asyncness",
            "error_model",
            "overload_decision",
            "mapping_kind",
            "status",
        ],
        MEMBER_PATH,
    )
}

fn mapped_ids(type_rows: &[Row], member_rows: &[Row]) -> (BTreeSet<String>, BTreeSet<String>) {
    let public_type_ids = type_rows
        .iter()
        .filter(|row| {
            field(row, "csharp_type_id").starts_with("T:LibreMetaverse")
                && field(row, "source_kind") != "referenced_support_trait"
        })
        .map(|row| field(row, "csharp_type_id").to_owned())
        .collect::<BTreeSet<_>>();
    let member_ids = member_rows
        .iter()
        .map(|row| field(row, "csharp_id").to_owned())
        .collect::<BTreeSet<_>>();
    (public_type_ids, member_ids)
}

fn validate_dependency_roots(member_rows: &[Row]) -> Result<()> {
    let accidental_roots = accidental_dependency_roots(member_rows);
    if !accidental_roots.is_empty() {
        return Err(MatrixError::new(format!(
            "mapped public signatures expose dependency roots: {}",
            accidental_roots.into_iter().collect::<Vec<_>>().join(", ")
        )));
    }
    Ok(())
}

fn inspect_crates(
    root: &Path,
    release: &str,
    type_rows: &[Row],
    member_rows: &[Row],
) -> Result<(Vec<CrateSurface>, Vec<String>)> {
    let mut declarations = Vec::new();
    let mut crates = Vec::new();
    for (name, relative) in PUBLIC_CRATES {
        let directory = root.join(relative);
        let manifest = fs::read_to_string(directory.join("Cargo.toml"))?;
        if package_value(&manifest, "name").as_deref() != Some(name) {
            return Err(MatrixError::new(format!(
                "public crate manifest mismatch for {name}"
            )));
        }
        let crate_version = if manifest
            .lines()
            .any(|line| line.trim() == "version.workspace = true")
        {
            release.to_owned()
        } else {
            package_value(&manifest, "version")
                .ok_or_else(|| MatrixError::new(format!("public crate {name} lacks a version")))?
        };
        if crate_version != release {
            return Err(MatrixError::new(format!(
                "public crate {name} has version {crate_version}, expected release {release}"
            )));
        }
        let features = parse_features(&manifest)?;
        validate_features(name, &features)?;
        let crate_declarations = crate_declarations(&directory)?;
        validate_declaration_dependencies(name, &manifest, &crate_declarations)?;
        let declaration_sha256 = sha256(crate_declarations.join("\n").as_bytes());
        declarations.extend(
            crate_declarations
                .iter()
                .map(|declaration| format!("{name}\t{declaration}")),
        );
        crates.push(CrateSurface {
            name: name.to_owned(),
            version: release.to_owned(),
            features,
            mapped_types: type_rows
                .iter()
                .filter(|row| {
                    field(row, "rust_crate") == name
                        && field(row, "csharp_type_id").starts_with("T:LibreMetaverse")
                        && field(row, "source_kind") != "referenced_support_trait"
                })
                .count(),
            mapped_members: member_rows
                .iter()
                .filter(|row| field(row, "rust_crate") == name)
                .count(),
            public_declarations: crate_declarations.len(),
            declaration_sha256,
        });
    }
    declarations.sort();
    Ok((crates, declarations))
}

fn quality_counts(type_rows: &[Row], member_rows: &[Row]) -> QualityCounts {
    let intentional_differences = member_rows
        .iter()
        .filter(|row| field(row, "status") == "intentional_difference")
        .count();
    let descriptive_overloads = member_rows
        .iter()
        .filter(|row| field(row, "overload_decision") != "direct_snake_case")
        .count();
    let async_functions = member_rows
        .iter()
        .filter(|row| field(row, "asyncness") == "async")
        .count();
    let send_boxed_futures = member_rows
        .iter()
        .filter(|row| field(row, "asyncness") == "boxed_future")
        .count();
    let thread_safe_callbacks = member_rows
        .iter()
        .filter(|row| field(row, "rust_signature").contains("dyn Fn"))
        .count();
    let enum_types = type_rows
        .iter()
        .filter(|row| {
            field(row, "csharp_type_id").starts_with("T:LibreMetaverse")
                && field(row, "source_kind") == "enum"
        })
        .count();
    let bitflag_types = type_rows
        .iter()
        .filter(|row| {
            field(row, "csharp_type_id").starts_with("T:LibreMetaverse")
                && field(row, "enum_mapping").starts_with("bitflags<")
        })
        .count();
    QualityCounts {
        intentional_differences,
        descriptive_overloads,
        async_functions,
        send_boxed_futures,
        thread_safe_callbacks,
        enum_types,
        bitflag_types,
    }
}

fn validate_members(rows: &[Row]) -> Result<()> {
    let ownership_terms = OWNERSHIP_TERMS.into_iter().collect::<BTreeSet<_>>();
    for row in rows {
        let id = field(row, "csharp_id");
        if [
            "csharp_id",
            "csharp_kind",
            "csharp_signature",
            "rust_crate",
            "rust_item_path",
            "rust_signature",
            "ownership",
            "asyncness",
            "error_model",
            "overload_decision",
            "mapping_kind",
            "status",
        ]
        .into_iter()
        .any(|name| field(row, name).is_empty())
        {
            return Err(MatrixError::new(format!("mapping {id} has an empty field")));
        }
        if field(row, "ownership")
            .split(',')
            .any(|term| !ownership_terms.contains(term))
        {
            return Err(MatrixError::new(format!(
                "mapping {id} has an unexplained ownership term"
            )));
        }
        let signature = field(row, "rust_signature");
        validate_rust_naming(id, signature)?;
        match field(row, "asyncness") {
            "sync" if signature.contains("pub async fn") => {
                return Err(MatrixError::new(format!(
                    "mapping {id} marks an async function as synchronous"
                )));
            }
            "async" if !signature.contains("pub async fn") => {
                return Err(MatrixError::new(format!(
                    "mapping {id} lacks an async Rust signature"
                )));
            }
            "boxed_future"
                if !signature.contains("dyn std::future::Future")
                    || !signature.contains("+ Send") =>
            {
                return Err(MatrixError::new(format!(
                    "mapping {id} lacks a Send boxed future"
                )));
            }
            "sync" | "async" | "boxed_future" => {}
            value => {
                return Err(MatrixError::new(format!(
                    "mapping {id} has unknown asyncness {value}"
                )));
            }
        }
        if signature.contains("dyn Fn") && !signature.contains("+ Send + Sync") {
            return Err(MatrixError::new(format!(
                "mapping {id} exposes a callback without Send + Sync"
            )));
        }
        match field(row, "error_model") {
            "Result<_, crate::Error>" if !signature.contains("Result<") => {
                return Err(MatrixError::new(format!(
                    "mapping {id} declares Result errors without a Result signature"
                )));
            }
            "none" | "infallible_placeholder" if signature.contains("Result<") => {
                return Err(MatrixError::new(format!(
                    "mapping {id} returns Result without a matching error model"
                )));
            }
            "Result<_, crate::Error>" | "none" | "infallible_placeholder" => {}
            value => {
                return Err(MatrixError::new(format!(
                    "mapping {id} has unknown error model {value}"
                )));
            }
        }
        let intentional = field(row, "status") == "intentional_difference";
        if intentional != (field(row, "mapping_kind") == "native_callback_replaces_delegate_apm") {
            return Err(MatrixError::new(format!(
                "mapping {id} has an unexplained intentional difference"
            )));
        }
        if !matches!(field(row, "status"), "mapped" | "intentional_difference") {
            return Err(MatrixError::new(format!(
                "mapping {id} has an unresolved status"
            )));
        }
    }
    Ok(())
}

fn validate_rust_naming(id: &str, signature: &str) -> Result<()> {
    let function = ["pub async fn ", "pub fn ", "async fn ", "fn "]
        .into_iter()
        .find_map(|prefix| signature.strip_prefix(prefix));
    if let Some(function) = function {
        let name = function
            .split(|character: char| !(character.is_ascii_alphanumeric() || character == '_'))
            .next()
            .unwrap_or_default();
        if name.is_empty()
            || name.starts_with(|character: char| character.is_ascii_digit())
            || !name.chars().all(|character| {
                character.is_ascii_lowercase() || character.is_ascii_digit() || character == '_'
            })
        {
            return Err(MatrixError::new(format!(
                "mapping {id} has a non-idiomatic Rust function name: {name}"
            )));
        }
    }
    for prefix in ["pub const ", "pub static "] {
        if let Some(item) = signature.strip_prefix(prefix) {
            let name = item
                .split(|character: char| !(character.is_ascii_alphanumeric() || character == '_'))
                .next()
                .unwrap_or_default();
            if name.is_empty()
                || !name.chars().all(|character| {
                    character.is_ascii_uppercase() || character.is_ascii_digit() || character == '_'
                })
            {
                return Err(MatrixError::new(format!(
                    "mapping {id} has a non-idiomatic Rust constant name: {name}"
                )));
            }
        }
    }
    Ok(())
}

fn validate_types(rows: &[Row]) -> Result<()> {
    for row in rows {
        let id = field(row, "csharp_type_id");
        if [
            "csharp_type_id",
            "csharp_signature",
            "source_kind",
            "rust_crate",
            "rust_path",
            "mapping_decision",
            "inheritance_mapping",
            "enum_mapping",
            "status",
        ]
        .into_iter()
        .any(|name| field(row, name).is_empty())
            || field(row, "status") != "mapped"
        {
            return Err(MatrixError::new(format!(
                "type mapping {id} is incomplete or unresolved"
            )));
        }
        if !matches!(
            field(row, "mapping_decision"),
            "native_metacrate_type"
                | "native_cross_platform_replacement"
                | "stdlib_or_adopted_crate"
                | "language"
                | "native_metacrate_support_trait"
        ) {
            return Err(MatrixError::new(format!(
                "type mapping {id} has an unexplained decision"
            )));
        }
    }
    Ok(())
}

fn validate_totals(baseline: &ApiBaseline) -> Result<()> {
    if baseline.mapped_public_types != 3_066
        || baseline.mapped_members != 30_789
        || baseline.external_signature_types != 142
        || baseline.support_traits != 2
        || baseline.intentional_differences != 80
        || baseline.descriptive_overloads != 7_151
        || baseline.async_functions != 334
        || baseline.send_boxed_futures != 26
        || baseline.thread_safe_callbacks != 137
        || baseline.enum_types != 243
        || baseline.bitflag_types != 46
        || baseline.unexplained_mappings != 0
        || baseline.accidental_public_dependency_types != 0
    {
        return Err(MatrixError::new(
            "API quality totals differ from the reviewed first-release surface",
        ));
    }
    Ok(())
}

fn exported_mappings(root: &Path) -> Result<(BTreeSet<String>, BTreeSet<String>, Vec<u8>)> {
    let mut types = BTreeSet::new();
    let mut members = BTreeSet::new();
    let mut canonical = Vec::new();
    for (_, relative) in PUBLIC_CRATES {
        let path = root.join(relative).join("src/generated.rs");
        if !path.exists() {
            continue;
        }
        for line in fs::read_to_string(&path)?.lines().map(str::trim) {
            if let Some(id) = marker(line, "/// C# type: `") {
                if !types.insert(id.to_owned()) {
                    return Err(MatrixError::new(format!(
                        "duplicate exported type marker {id}"
                    )));
                }
                canonical.push(format!("type\t{id}"));
            }
            if let Some(id) = marker(line, "/// C# member: `") {
                if !members.insert(id.to_owned()) {
                    return Err(MatrixError::new(format!(
                        "duplicate exported member marker {id}"
                    )));
                }
                canonical.push(format!("member\t{id}"));
            }
        }
    }
    canonical.sort_unstable();
    Ok((types, members, canonical.join("\n").into_bytes()))
}

fn marker<'a>(line: &'a str, prefix: &str) -> Option<&'a str> {
    line.strip_prefix(prefix)?.strip_suffix("`.")
}

fn accidental_dependency_roots(rows: &[Row]) -> BTreeSet<String> {
    let mut roots = BTreeSet::new();
    for row in rows {
        for root in path_roots(field(row, "rust_signature")) {
            if !matches!(root.as_str(), "crate" | "std" | "core" | "self" | "super")
                && !root.starts_with("libremetaverse")
            {
                roots.insert(root);
            }
        }
    }
    roots
}

fn validate_declaration_dependencies(
    crate_name: &str,
    manifest: &str,
    declarations: &[String],
) -> Result<()> {
    let dependencies = dependency_roots(manifest);
    for declaration in declarations {
        let surface = dependency_surface(declaration);
        for root in path_roots(&surface).intersection(&dependencies) {
            if root.starts_with("libremetaverse") {
                continue;
            }
            return Err(MatrixError::new(format!(
                "public Rust declaration exposes dependency root {root}: {crate_name}/{declaration}"
            )));
        }
    }
    Ok(())
}

fn dependency_surface(declaration: &str) -> String {
    let source = declaration
        .split_once('\t')
        .map_or(declaration, |(_, source)| source);
    if !source.starts_with("pub struct ") && !source.starts_with("pub union ") {
        return source.to_owned();
    }

    let (opening, closing) = if let Some(opening) = source.find('{') {
        (opening, '}')
    } else if let Some(opening) = source.find('(') {
        (opening, ')')
    } else {
        return source.to_owned();
    };
    let Some(closing) = source.rfind(closing).filter(|closing| *closing > opening) else {
        return source.to_owned();
    };
    let mut public = source[..opening].to_owned();
    for field in split_top_level(&source[opening + 1..closing], ',') {
        let field = field.trim();
        if field.starts_with("pub ") {
            public.push(' ');
            public.push_str(field);
        }
    }
    public
}

fn split_top_level(value: &str, separator: char) -> Vec<&str> {
    let mut fields = Vec::new();
    let mut start = 0;
    let mut depth = 0usize;
    for (index, character) in value.char_indices() {
        match character {
            '<' | '(' | '[' | '{' => depth += 1,
            '>' | ')' | ']' | '}' => depth = depth.saturating_sub(1),
            candidate if candidate == separator && depth == 0 => {
                fields.push(&value[start..index]);
                start = index + character.len_utf8();
            }
            _ => {}
        }
    }
    fields.push(&value[start..]);
    fields
}

fn dependency_roots(manifest: &str) -> BTreeSet<String> {
    let mut roots = BTreeSet::new();
    let mut normal_dependencies = false;
    for line in manifest.lines().map(str::trim) {
        if line.starts_with('[') {
            normal_dependencies = line == "[dependencies]"
                || (line.starts_with("[target.") && line.ends_with(".dependencies]"));
            continue;
        }
        if !normal_dependencies || line.is_empty() || line.starts_with('#') {
            continue;
        }
        if let Some((name, _)) = line.split_once('=') {
            roots.insert(name.trim().replace('-', "_"));
        }
    }
    roots
}

fn path_roots(value: &str) -> BTreeSet<String> {
    let bytes = value.as_bytes();
    let mut roots = BTreeSet::new();
    let mut index = 0;
    while index < bytes.len() {
        if bytes[index].is_ascii_lowercase()
            && (index == 0
                || (!bytes[index - 1].is_ascii_alphanumeric()
                    && bytes[index - 1] != b'_'
                    && bytes[index - 1] != b':'))
        {
            let start = index;
            index += 1;
            while index < bytes.len()
                && (bytes[index].is_ascii_alphanumeric() || bytes[index] == b'_')
            {
                index += 1;
            }
            let mut separator = index;
            while separator < bytes.len() && bytes[separator].is_ascii_whitespace() {
                separator += 1;
            }
            if bytes.get(separator..separator + 2) == Some(b"::") {
                roots.insert(value[start..index].to_owned());
            }
        } else {
            index += 1;
        }
    }
    roots
}

fn validate_non_exhaustive_errors(root: &Path) -> Result<()> {
    for (relative, name) in REQUIRED_NON_EXHAUSTIVE_ERRORS {
        let text = fs::read_to_string(root.join(relative))?;
        let declaration = format!("pub enum {name}");
        let Some(position) = text.find(&declaration) else {
            return Err(MatrixError::new(format!(
                "missing public error enum {name}"
            )));
        };
        let prefix = &text[..position];
        let attribute = prefix
            .rsplit_once("#[non_exhaustive]")
            .map(|(_, tail)| tail);
        if attribute.is_none_or(|tail| tail.contains("pub enum")) {
            return Err(MatrixError::new(format!(
                "public error enum {name} must be non_exhaustive before release"
            )));
        }
    }
    Ok(())
}

fn validate_thread_safe_traits(root: &Path) -> Result<()> {
    for (relative, declaration) in REQUIRED_THREAD_SAFE_TRAITS {
        if !fs::read_to_string(root.join(relative))?.contains(declaration) {
            return Err(MatrixError::new(format!(
                "public callback trait lacks its Send + Sync contract: {declaration}"
            )));
        }
    }
    Ok(())
}

fn validate_features(name: &str, actual: &BTreeMap<String, Vec<String>>) -> Result<()> {
    let expected = match name {
        "libremetaverse" => BTreeMap::from([
            ("dds-bc67".to_owned(), vec!["dep:bcdec_rs".to_owned()]),
            (
                "default".to_owned(),
                vec!["dds-bc67".to_owned(), "rust-j2k".to_owned()],
            ),
            (
                "jpeg2000".to_owned(),
                vec!["libremetaverse-imaging/jpeg2000".to_owned()],
            ),
            (
                "rust-j2k".to_owned(),
                vec!["libremetaverse-imaging/rust-j2k".to_owned()],
            ),
            ("vorbis".to_owned(), vec!["dep:vorbis_rs".to_owned()]),
        ]),
        "libremetaverse-imaging" => BTreeMap::from([
            ("default".to_owned(), vec!["rust-j2k".to_owned()]),
            (
                "jpeg2000".to_owned(),
                vec!["dep:libremetaverse-openjpeg".to_owned()],
            ),
            ("rust-j2k".to_owned(), vec!["dep:j2k".to_owned()]),
        ]),
        "libremetaverse-imaging-skia" => BTreeMap::from([
            ("default".to_owned(), vec!["rust-skia".to_owned()]),
            (
                "rust-skia".to_owned(),
                vec![
                    "dep:image-webp".to_owned(),
                    "dep:skia-rs-codec".to_owned(),
                    "skia-rs-codec/gif".to_owned(),
                    "skia-rs-codec/jpeg".to_owned(),
                    "skia-rs-codec/png".to_owned(),
                ],
            ),
            ("skia".to_owned(), vec!["dep:skia-safe".to_owned()]),
        ]),
        "libremetaverse-voice-webrtc" => BTreeMap::from([
            ("default".to_owned(), Vec::new()),
            ("real-audio".to_owned(), vec!["dep:cpal".to_owned()]),
        ]),
        _ => BTreeMap::new(),
    };
    if actual != &expected {
        return Err(MatrixError::new(format!(
            "public feature surface changed for {name}: expected {expected:?}, found {actual:?}"
        )));
    }
    Ok(())
}

fn parse_features(manifest: &str) -> Result<BTreeMap<String, Vec<String>>> {
    let mut features = BTreeMap::new();
    let mut in_features = false;
    for line in manifest.lines() {
        let line = line.trim();
        if line.starts_with('[') {
            in_features = line == "[features]";
            continue;
        }
        if !in_features || line.is_empty() || line.starts_with('#') {
            continue;
        }
        let (name, values) = line
            .split_once('=')
            .ok_or_else(|| MatrixError::new("feature declaration lacks ="))?;
        let values = serde_json::from_str::<Vec<String>>(values.trim())?;
        if features.insert(name.trim().to_owned(), values).is_some() {
            return Err(MatrixError::new("duplicate feature declaration"));
        }
    }
    Ok(features)
}

fn crate_declarations(directory: &Path) -> Result<Vec<String>> {
    let mut files = Vec::new();
    collect_rs(&directory.join("src"), &mut files)?;
    files.sort();
    let mut declarations = Vec::new();
    for file in files {
        let relative = file.strip_prefix(directory).unwrap_or(&file);
        for declaration in extract_public_declarations(&fs::read_to_string(&file)?) {
            declarations.push(format!("{}\t{declaration}", relative.display()));
        }
    }
    declarations.sort();
    Ok(declarations)
}

fn collect_rs(directory: &Path, files: &mut Vec<PathBuf>) -> Result<()> {
    for entry in fs::read_dir(directory)? {
        let path = entry?.path();
        if path.is_dir() {
            collect_rs(&path, files)?;
        } else if path.extension().and_then(|value| value.to_str()) == Some("rs") {
            files.push(path);
        }
    }
    Ok(())
}

fn extract_public_declarations(source: &str) -> Vec<String> {
    let lines = source.lines().collect::<Vec<_>>();
    let mut declarations = Vec::new();
    let mut index = 0;
    while index < lines.len() {
        let trimmed = lines[index].trim_start();
        if !trimmed.starts_with("pub ") {
            index += 1;
            continue;
        }
        let whole_block = ["pub struct ", "pub enum ", "pub union ", "pub trait "]
            .iter()
            .any(|prefix| trimmed.starts_with(prefix));
        let inline_module = trimmed.starts_with("pub mod ") && trimmed.contains('{');
        let mut chunk = Vec::new();
        let mut braces = 0isize;
        let mut opened = false;
        loop {
            let line = lines[index].trim();
            if !line.starts_with("///") && !line.starts_with("//") {
                let selected = if !whole_block && !inline_module {
                    line.split_once('{')
                        .map_or(line, |(header, _)| header.trim())
                } else if inline_module {
                    line.split_once('{')
                        .map_or(line, |(header, _)| header.trim())
                } else {
                    line
                };
                if !selected.is_empty() {
                    chunk.push(selected);
                }
            }
            if whole_block {
                braces += brace_delta(line);
                opened |= line.contains('{');
            }
            let finished = if whole_block {
                (opened && braces == 0) || (!opened && line.ends_with(';'))
            } else {
                line.contains('{') || line.ends_with(';')
            };
            index += 1;
            if finished || index == lines.len() {
                break;
            }
        }
        let normalized = chunk
            .join(" ")
            .split_whitespace()
            .collect::<Vec<_>>()
            .join(" ");
        if !normalized.is_empty() {
            declarations.push(normalized);
        }
    }
    declarations
}

fn brace_delta(line: &str) -> isize {
    let mut delta = 0;
    let mut quoted = false;
    let mut escaped = false;
    let mut characters = line.chars().peekable();
    while let Some(character) = characters.next() {
        if !quoted && character == '/' && characters.peek() == Some(&'/') {
            break;
        }
        if quoted {
            if escaped {
                escaped = false;
            } else if character == '\\' {
                escaped = true;
            } else if character == '"' {
                quoted = false;
            }
            continue;
        }
        match character {
            '"' => quoted = true,
            '{' => delta += 1,
            '}' => delta -= 1,
            _ => {}
        }
    }
    delta
}

fn read_tsv(path: &Path) -> Result<(Vec<String>, Vec<Row>)> {
    let text = fs::read_to_string(path)?;
    let mut lines = text.lines();
    let header = lines
        .next()
        .ok_or_else(|| MatrixError::new(format!("{} is empty", path.display())))?
        .split('\t')
        .map(str::to_owned)
        .collect::<Vec<_>>();
    let mut rows = Vec::new();
    for line in lines.filter(|line| !line.is_empty()) {
        let values = line.split('\t').collect::<Vec<_>>();
        if values.len() != header.len() {
            return Err(MatrixError::new(format!(
                "{} contains a malformed row",
                path.display()
            )));
        }
        rows.push(
            header
                .iter()
                .cloned()
                .zip(values.into_iter().map(str::to_owned))
                .collect(),
        );
    }
    Ok((header, rows))
}

fn require_columns(header: &[String], required: &[&str], path: &str) -> Result<()> {
    for field in required {
        if !header.iter().any(|candidate| candidate == field) {
            return Err(MatrixError::new(format!(
                "{path} lacks required column {field}"
            )));
        }
    }
    Ok(())
}

fn validate_unique(rows: &[Row], key: &str, label: &str) -> Result<()> {
    let mut values = BTreeSet::new();
    for row in rows {
        let value = field(row, key);
        if value.is_empty() || !values.insert(value) {
            return Err(MatrixError::new(format!(
                "{label} values must be nonempty and unique: {value}"
            )));
        }
    }
    Ok(())
}

fn field<'a>(row: &'a Row, name: &str) -> &'a str {
    row.get(name).map_or("", String::as_str)
}

fn package_value(manifest: &str, key: &str) -> Option<String> {
    let mut in_package = false;
    for line in manifest.lines() {
        let line = line.trim();
        if line.starts_with('[') {
            in_package = matches!(line, "[package]" | "[workspace.package]");
            continue;
        }
        if in_package
            && let Some(value) = line.strip_prefix(&format!("{key} = \""))
            && let Some(value) = value.strip_suffix('"')
        {
            return Some(value.to_owned());
        }
    }
    None
}

fn baseline_json(baseline: &ApiBaseline) -> Result<Vec<u8>> {
    let mut bytes = serde_json::to_vec_pretty(baseline)?;
    bytes.push(b'\n');
    Ok(bytes)
}

fn sha256(bytes: &[u8]) -> String {
    let digest = Sha256::digest(bytes);
    let mut output = String::with_capacity(digest.len() * 2);
    for byte in digest {
        use std::fmt::Write as _;
        write!(output, "{byte:02x}").expect("writing to a String cannot fail");
    }
    output
}

fn render_report(baseline: &ApiBaseline) -> String {
    let crates = baseline
        .crates
        .iter()
        .map(|surface| {
            let features = if surface.features.is_empty() {
                "none".to_owned()
            } else {
                surface
                    .features
                    .keys()
                    .cloned()
                    .collect::<Vec<_>>()
                    .join(", ")
            };
            format!(
                "| `{}` | {} | {} | {} | {} | `{}` |",
                surface.name,
                surface.mapped_types,
                surface.mapped_members,
                surface.public_declarations,
                features,
                surface.declaration_sha256
            )
        })
        .collect::<Vec<_>>()
        .join("\n");
    format!(
        "# First-release API and SemVer audit\n\n\
Generated by `metacrate-ci-matrix api-baseline-write`; do not edit by hand.\n\n\
The compatibility source is pinned at [`{commit}`](https://github.com/cinderblocks/libremetaverse/tree/{commit}). \
The baseline version is `{release}`.\n\n\
## Coverage and quality decisions\n\n\
- Mapped/exported public types: **{types} / {types}**; unexplained mappings: **0**.\n\
- Mapped/exported public members: **{members} / {members}**; duplicate destinations: **0**.\n\
- Rust naming and ownership: mapped function/constant names follow Rust conventions and every member uses a reviewed ownership term.\n\
- External signature types: **{external}**; referenced support traits: **{support}**; accidental dependency types exposed by mapped signatures or public Rust declarations: **0**.\n\
- Intentional delegate APM replacements: **{differences}**; each uses a native callback and is compiled by the downstream fixture.\n\
- Descriptively named overloads: **{overloads}**; names remain fixed by the mapping ledger.\n\
- Native `async fn` mappings: **{async_functions}**; boxed object-safe trait futures with explicit `Send`: **{boxed}**.\n\
- Callback signatures requiring `Send + Sync`: **{callbacks}**; reviewed dyn-compatible thread-safe boundary traits: **{traits}** (`IGridClient`, `IBakingTextureProvider`, `ICurrentOutfitPolicy`, `IRlvActionCallbacks`, `IRlvQueryCallbacks`, and `IVoiceLogger`).\n\
- C# enum mappings: **{enums}** ({bitflags} unknown-bit-retaining flag types); stable public error enums marked `non_exhaustive`: **{errors}**.\n\n\
## Crate baselines\n\n\
| Crate | Mapped types | Mapped members | Rust declarations | Features | Declaration SHA-256 |\n\
| --- | ---: | ---: | ---: | --- | --- |\n\
{crates}\n\n\
The exact mapping, exported-marker, feature, and normalized Rust declaration hashes are stored in \
[`SEMVER-BASELINE.json`](SEMVER-BASELINE.json). A change must be reviewed and classified before the baseline is re-recorded.\n\n\
## Intentional Rust migration recipes\n\n\
- C# `PascalCase` methods/properties become Rust `snake_case`; overloads use the ledger's stable descriptive suffix, never argument-count dispatch.\n\
- C# reference ownership becomes owned values, shared or mutable borrows, `Arc`-backed managers, and explicit optional values as recorded per member.\n\
- `Task<T>` becomes `async fn -> Result<T, Error>`; object-safe async traits return `Pin<Box<dyn Future<Output = Result<...>> + Send + '_>>`.\n\
- .NET events become `subscribe_*` calls returning an owned `Subscription`; callbacks are `Send + Sync` and dispatch outside internal locks.\n\
- `IDisposable` becomes explicit idempotent shutdown plus `Drop`; cancellation remains a typed `Error::Cancelled`.\n\
- Closed C# enums retain fixed discriminants and are SemVer-breaking to extend; flags retain unknown bits, while public Rust error enums are `non_exhaustive`.\n\
- Pure-Rust image backends are enabled by default (`rust-j2k`, `rust-skia`); native alternatives remain feature-gated (`jpeg2000`, `skia`, `vorbis`, `real-audio`), and empty-default sets remain part of the baseline.\n\
- Capability HTTP injection uses project-owned `HttpMessageHandler`, `CapsHttpLimits`, and `with_native_transport`; the internal `reqwest` client is not part of the public surface.\n\
- External C# signature types map to core/std, adopted cross-platform crates, or project-owned boundary types; no third-party crate path leaks through mapped signatures or public Rust declarations.\n\n\
## Compile consumers\n\n\
- `tests/api-compile` compiles every mapped type/member from a standalone downstream workspace.\n\
- `tests/semver-port` is a runnable representative port covering lifecycle, value types, structured data, imaging, RLV, cancellation, dyn trait, and `Send`/`Sync` contracts without network access.\n",
        commit = baseline.upstream_commit,
        release = baseline.release,
        types = baseline.mapped_public_types,
        members = baseline.mapped_members,
        external = baseline.external_signature_types,
        support = baseline.support_traits,
        differences = baseline.intentional_differences,
        overloads = baseline.descriptive_overloads,
        async_functions = baseline.async_functions,
        boxed = baseline.send_boxed_futures,
        callbacks = baseline.thread_safe_callbacks,
        traits = baseline.thread_safe_dyn_traits,
        enums = baseline.enum_types,
        bitflags = baseline.bitflag_types,
        errors = baseline.non_exhaustive_error_enums,
    )
}

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

    #[test]
    fn declaration_extractor_ignores_function_bodies_and_private_items() {
        let source = r"
            struct Private;
            pub struct Public { pub value: i32, hidden: bool }
            impl Public {
                pub fn value(
                    &self,
                ) -> i32 { self.value }
                fn hidden(&self) -> bool { self.hidden }
            }
        ";
        let declarations = extract_public_declarations(source);
        assert_eq!(declarations.len(), 2);
        assert!(declarations[0].starts_with("pub struct Public"));
        assert_eq!(declarations[1], "pub fn value( &self, ) -> i32");
        assert!(!declarations.join(" ").contains("self.value"));
    }

    #[test]
    fn path_root_parser_ignores_nested_modules() {
        let roots =
            path_roots("pub fn f(value: libremetaverse::packets::Packet) -> std::io::Result<()>;");
        assert_eq!(
            roots,
            BTreeSet::from(["libremetaverse".to_owned(), "std".to_owned()])
        );
    }

    #[test]
    fn dependency_root_parser_covers_normal_target_and_renamed_crates() {
        let manifest = r#"
            [dependencies]
            normal-crate = "1"
            [dev-dependencies]
            ignored_dev = "1"
            [target.'cfg(unix)'.dependencies]
            target-crate = "1"
            [build-dependencies]
            ignored_build = "1"
        "#;
        assert_eq!(
            dependency_roots(manifest),
            BTreeSet::from(["normal_crate".to_owned(), "target_crate".to_owned()])
        );
    }

    #[test]
    fn dependency_surface_excludes_private_but_retains_public_struct_fields() {
        let declaration = "src/example.rs\tpub struct Example<T> { private: dep::Hidden<T>, pub visible: dep::Visible<(T, T)>, pub(crate) scoped: dep::Scoped, }";
        assert_eq!(
            dependency_surface(declaration),
            "pub struct Example<T>  pub visible: dep::Visible<(T, T)>"
        );
        assert_eq!(
            dependency_surface("src/example.rs\tpub struct Tuple(pub dep::Visible, dep::Hidden);"),
            "pub struct Tuple pub dep::Visible"
        );
    }

    #[test]
    fn naming_audit_accepts_rust_conventions_and_rejects_pascal_case_functions() {
        validate_rust_naming(
            "good",
            "pub async fn request_asset_2() -> Result<(), Error>",
        )
        .unwrap();
        validate_rust_naming("constant", "pub const MAX_BYTES: usize").unwrap();
        assert!(validate_rust_naming("bad", "pub fn RequestAsset() -> bool").is_err());
    }

    #[test]
    fn repository_surface_has_reviewed_totals() {
        let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../..");
        let baseline = inspect(&root).unwrap();
        assert_eq!(baseline.mapped_public_types, 3_066);
        assert_eq!(baseline.mapped_members, 30_789);
        assert_eq!(baseline.unexplained_mappings, 0);
        assert_eq!(baseline.accidental_public_dependency_types, 0);
    }
}
