//! Deterministic TL-B schema parsing and blockchain schema checks.
//!
//! This is intentionally a small, checked generator foundation. It preserves
//! constructor names, explicit tags, field text, grouped references, and result
//! types for the checked block/config/proof schema surface. It is not yet a
//! general Rust emitter for every upstream `block.tlb` expression.

use crate::tlb::{Result, TlbError};

/// Local checked snapshot of upstream `crypto/block/block.tlb` constructors
/// currently covered by deterministic schema checks.
pub const BLOCK_TLB: &str = include_str!("schemas/block.tlb");

/// Backward-compatible alias for older examples and tests.
pub const BLOCK_PHASE1_TLB: &str = BLOCK_TLB;

/// Checked-in generated summary for `BLOCK_PHASE1_TLB`.
pub const BLOCK_PHASE1_GENERATED: &str = include_str!("generated/block_phase1.rs");

/// Parsed TL-B constructor.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Constructor {
    /// Constructor name before `$`, `#`, or `$_`.
    pub name: String,
    /// Explicit binary or hexadecimal constructor tag.
    pub tag: ConstructorTag,
    /// Raw field expressions between constructor and result type.
    pub fields: Vec<Field>,
    /// Result type expression after `=`.
    pub result: String,
}

/// Constructor tag spelling.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConstructorTag {
    /// Implicit CRC-derived tag, spelled `#_` or `$_` upstream.
    Implicit,
    /// Binary tag after `$`.
    Binary(String),
    /// Hexadecimal tag after `#`.
    Hex(String),
}

/// Parsed field or grouped reference expression.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Field {
    /// Optional field name before `:`.
    pub name: Option<String>,
    /// Type expression or grouped-reference contents.
    pub ty: String,
    /// Whether this field is encoded as a reference (`^T` or `^[ ... ]`).
    pub referenced: bool,
    /// Whether this field is a grouped reference (`^[ ... ]`).
    pub grouped: bool,
}

/// Parses a TL-B schema into constructors.
pub fn parse_schema(schema: &str) -> Result<Vec<Constructor>> {
    let schema = schema
        .lines()
        .map(strip_line_comment)
        .collect::<Vec<_>>()
        .join("\n");
    let mut constructors = Vec::new();
    for statement in split_statements(&schema) {
        let statement = statement.trim();
        if statement.is_empty() || !statement.contains('=') {
            continue;
        }
        constructors.push(parse_constructor(statement)?);
    }
    Ok(constructors)
}

/// Generates the deterministic checked-in summary for a parsed schema.
pub fn generate_summary(constructors: &[Constructor]) -> String {
    let mut out = String::from("// @generated by tonutils TL-B Phase 1 schema generator\n");
    out.push_str("// source: upstream ton-blockchain/ton crypto/block/block.tlb subset\n\n");
    out.push_str("pub const CONSTRUCTORS: &[(&str, &str, &str, usize)] = &[\n");
    for constructor in constructors {
        out.push_str("    (\"");
        out.push_str(&escape(&constructor.name));
        out.push_str("\", \"");
        out.push_str(&constructor.tag.to_string());
        out.push_str("\", \"");
        out.push_str(&escape(&constructor.result));
        out.push_str("\", ");
        out.push_str(&constructor.fields.len().to_string());
        out.push_str("),\n");
    }
    out.push_str("];\n");
    out
}

/// Regenerates the Phase 1 summary from the local schema slice.
pub fn generate_block_phase1() -> Result<String> {
    Ok(generate_summary(&parse_schema(BLOCK_TLB)?))
}

fn parse_constructor(statement: &str) -> Result<Constructor> {
    let (lhs, result) = statement
        .rsplit_once('=')
        .ok_or_else(|| schema_error("missing result separator"))?;
    let result = result.trim().trim_end_matches(';').trim().to_string();
    let lhs = lhs.trim();

    let (name, tag, rest) = if lhs.starts_with("_ ") {
        ("_".to_string(), ConstructorTag::Implicit, lhs[1..].trim())
    } else {
        let name_end = lhs
            .find(['$', '#'])
            .ok_or_else(|| schema_error("missing constructor tag"))?;
        let name = lhs[..name_end].trim().to_string();
        let (tag, rest) = parse_tag(&lhs[name_end..])?;
        (name, tag, rest)
    };
    let fields = parse_fields(rest)?;
    Ok(Constructor {
        name,
        tag,
        fields,
        result,
    })
}

fn parse_tag(input: &str) -> Result<(ConstructorTag, &str)> {
    let marker = input
        .chars()
        .next()
        .ok_or_else(|| schema_error("empty constructor tag"))?;
    let rest = &input[1..];
    let tag_end = rest.find(char::is_whitespace).unwrap_or(rest.len());
    let tag_text = &rest[..tag_end];
    let remaining = rest[tag_end..].trim();
    let tag = match (marker, tag_text) {
        ('$', "_") | ('#', "_") => ConstructorTag::Implicit,
        ('$', bits) if bits.bytes().all(|byte| byte == b'0' || byte == b'1') => {
            ConstructorTag::Binary(bits.to_string())
        }
        ('#', hex) if hex.bytes().all(|byte| byte.is_ascii_hexdigit()) => {
            ConstructorTag::Hex(hex.to_ascii_lowercase())
        }
        _ => return Err(schema_error("invalid constructor tag")),
    };
    Ok((tag, remaining))
}

fn parse_fields(input: &str) -> Result<Vec<Field>> {
    let tokens = split_top_level_ws(input);
    let mut fields = Vec::new();
    for token in tokens {
        let token = token.trim();
        if token.is_empty() || token.starts_with('{') {
            continue;
        }
        if token.starts_with("^[") {
            fields.push(Field {
                name: None,
                ty: token.trim_start_matches('^').to_string(),
                referenced: true,
                grouped: true,
            });
            continue;
        }
        let (name, ty) = match token.split_once(':') {
            Some((name, ty)) => (Some(name.to_string()), ty.to_string()),
            None => (None, token.to_string()),
        };
        let referenced = ty.starts_with('^');
        let grouped = ty.starts_with("^[");
        fields.push(Field {
            name,
            ty,
            referenced,
            grouped,
        });
    }
    Ok(fields)
}

fn split_statements(schema: &str) -> Vec<&str> {
    schema
        .split(';')
        .map(str::trim)
        .filter(|statement| !statement.is_empty())
        .collect()
}

fn strip_line_comment(input: &str) -> &str {
    input.split_once("//").map_or(input, |(before, _)| before)
}

fn split_top_level_ws(input: &str) -> Vec<String> {
    let mut tokens = Vec::new();
    let mut current = String::new();
    let mut paren = 0usize;
    let mut brace = 0usize;
    let mut bracket = 0usize;

    for ch in input.chars() {
        match ch {
            '(' => paren += 1,
            ')' => paren = paren.saturating_sub(1),
            '{' => brace += 1,
            '}' => brace = brace.saturating_sub(1),
            '[' => bracket += 1,
            ']' => bracket = bracket.saturating_sub(1),
            _ => {}
        }

        if ch.is_whitespace() && paren == 0 && brace == 0 && bracket == 0 {
            if !current.trim().is_empty() {
                tokens.push(current.trim().to_string());
                current.clear();
            }
        } else {
            current.push(ch);
        }
    }

    if !current.trim().is_empty() {
        tokens.push(current.trim().to_string());
    }
    tokens
}

fn escape(input: &str) -> String {
    input.replace('\\', "\\\\").replace('"', "\\\"")
}

fn schema_error(message: &str) -> TlbError {
    TlbError::CustomSchema {
        schema: "TL-B schema",
        message: message.to_string(),
    }
}

impl std::fmt::Display for ConstructorTag {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Implicit => formatter.write_str("_"),
            Self::Binary(bits) => write!(formatter, "${bits}"),
            Self::Hex(hex) => write!(formatter, "#{hex}"),
        }
    }
}

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

    #[test]
    fn parses_phase1_schema_constructs_needed_by_models() {
        let constructors = parse_schema(BLOCK_PHASE1_TLB).unwrap();
        assert!(constructors.iter().any(|item| item.name == "block"));
        assert!(constructors.iter().any(|item| item.name == "!merkle_proof"));
        assert!(
            constructors
                .iter()
                .any(|item| item.name == "!merkle_update")
        );
        assert!(
            constructors
                .iter()
                .flat_map(|item| item.fields.iter())
                .any(|field| field.grouped)
        );
    }

    #[test]
    fn generated_phase1_summary_is_deterministic() {
        assert_eq!(generate_block_phase1().unwrap(), BLOCK_PHASE1_GENERATED);
    }
}
