//! Bounded structural LSL source validation for native `MetaCrate` consumers.

use crate::MAX_SOURCE_UNITS;
use std::fmt;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum LslSyntaxError {
    Empty,
    Oversized,
    MissingDefaultState,
    UnterminatedString,
    UnterminatedComment,
    MismatchedDelimiter,
    InvalidCharacter,
}

impl fmt::Display for LslSyntaxError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{self:?}")
    }
}
impl std::error::Error for LslSyntaxError {}

/// Performs the bounded lexical and delimiter parse required before generated
/// LSL may cross an inventory boundary. This rejects malformed lexical states
/// and structural parse failures; it does not claim simulator runtime safety.
///
/// # Errors
///
/// Returns [`LslSyntaxError`] when the source exceeds its hard bound or its
/// lexical/structural parse does not form a complete LSL default state.
pub fn validate_lsl_source(source: &str) -> Result<(), LslSyntaxError> {
    if source.trim().is_empty() {
        return Err(LslSyntaxError::Empty);
    }
    if source.len() > MAX_SOURCE_UNITS {
        return Err(LslSyntaxError::Oversized);
    }
    if !source
        .split(|c: char| !c.is_alphanumeric() && c != '_')
        .any(|token| token == "default")
    {
        return Err(LslSyntaxError::MissingDefaultState);
    }
    let mut stack = Vec::new();
    let mut chars = source.chars().peekable();
    let mut string = false;
    let mut escaped = false;
    while let Some(ch) = chars.next() {
        if ch == '\0' {
            return Err(LslSyntaxError::InvalidCharacter);
        }
        if string {
            if escaped {
                escaped = false;
            } else if ch == '\\' {
                escaped = true;
            } else if ch == '"' {
                string = false;
            }
            continue;
        }
        if ch == '"' {
            string = true;
            continue;
        }
        if ch == '/' && chars.peek() == Some(&'/') {
            chars.next();
            for next in chars.by_ref() {
                if next == '\n' {
                    break;
                }
            }
            continue;
        }
        if ch == '/' && chars.peek() == Some(&'*') {
            chars.next();
            let mut closed = false;
            while let Some(next) = chars.next() {
                if next == '*' && chars.peek() == Some(&'/') {
                    chars.next();
                    closed = true;
                    break;
                }
            }
            if !closed {
                return Err(LslSyntaxError::UnterminatedComment);
            }
            continue;
        }
        match ch {
            '{' | '(' | '[' => stack.push(ch),
            '}' | ')' | ']' => {
                let expected = match ch {
                    '}' => '{',
                    ')' => '(',
                    _ => '[',
                };
                if stack.pop() != Some(expected) {
                    return Err(LslSyntaxError::MismatchedDelimiter);
                }
            }
            _ => {}
        }
    }
    if string {
        Err(LslSyntaxError::UnterminatedString)
    } else if stack.is_empty() {
        Ok(())
    } else {
        Err(LslSyntaxError::MismatchedDelimiter)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn lexical_and_structural_states_are_bounded() {
        assert!(validate_lsl_source("default { state_entry() { llSay(0, \"hi\"); } }").is_ok());
        assert_eq!(
            validate_lsl_source("default { \"x"),
            Err(LslSyntaxError::UnterminatedString)
        );
        assert_eq!(
            validate_lsl_source("default { /*"),
            Err(LslSyntaxError::UnterminatedComment)
        );
        assert_eq!(
            validate_lsl_source("default { ]"),
            Err(LslSyntaxError::MismatchedDelimiter)
        );
    }
}
