//! State machine parser for parameter strings.

use super::super::errors::ParamsError;
use super::{Param, Params};

/// Parser states for the parameter string parser.
#[derive(Debug, Clone, Copy, PartialEq)]
enum ParserState {
    /// Initial state, looking for content.
    Start,
    /// Reading a key or positional argument name.
    Name,
    /// Found ':', waiting for value.
    ValueWait,
    /// Reading an unquoted value.
    Value,
    /// Reading a quoted value (single quotes).
    Quote,
    /// Value completed, ready for next token.
    ValueEnd,
    /// Reading a comment.
    Comment,
}

/// Valid characters for parameter keys.
fn is_valid_key_char(c: char) -> bool {
    c.is_ascii_alphanumeric() || c == '_' || c == '.' || c == '/' || c == ','
}

/// Dedent text by removing common leading whitespace.
fn dedent(text: &str) -> String {
    let lines: Vec<&str> = text.lines().collect();
    if lines.is_empty() {
        return String::new();
    }

    // Find minimum indentation (ignoring empty lines)
    let min_indent = lines
        .iter()
        .filter(|line| !line.trim().is_empty())
        .map(|line| line.len() - line.trim_start().len())
        .min()
        .unwrap_or(0);

    // Remove the common indentation
    lines
        .iter()
        .map(|line| {
            if line.len() >= min_indent {
                &line[min_indent..]
            } else {
                line.trim()
            }
        })
        .collect::<Vec<_>>()
        .join("\n")
}

/// Parse a parameter string into Params.
///
/// Supported formats:
/// - `key:value`
/// - `key:'value with spaces'`
/// - `key:"value with spaces"` (quotes are normalized to single quotes)
/// - `positional_arg`
/// - `// comment`
/// - `key:value // inline comment`
/// - `key:item1,item2,item3` (lists)
///
/// # Example
///
/// ```
/// use herosal_heroscript::params::parse;
///
/// let params = parse("color:red size:'large item' debug:true").unwrap();
/// assert_eq!(params.get("color").unwrap(), "red");
/// assert_eq!(params.get("size").unwrap(), "large item");
/// ```
pub fn parse(text: &str) -> Result<Params, ParamsError> {
    let mut params = Params::new();

    if text.trim().is_empty() {
        return Ok(params);
    }

    // Dedent and normalize quotes
    let text = dedent(text);
    let text = text.replace('"', "'");

    // Convert multiline to single line handling
    let text = normalize_multiline(&text);

    let chars: Vec<char> = text.chars().collect();
    let len = chars.len();

    let mut state = ParserState::Start;
    let mut current_key = String::new();
    let mut current_value = String::new();
    let mut current_comment = String::new();
    let mut i = 0;

    while i < len {
        let c = chars[i];

        match state {
            ParserState::Start => {
                if c.is_whitespace() {
                    // Skip whitespace
                    i += 1;
                    continue;
                }

                // Check for comment start
                if c == '/' && i + 1 < len && chars[i + 1] == '/' {
                    state = ParserState::Comment;
                    i += 2;
                    continue;
                }

                // Start reading a name (key or arg)
                if is_valid_key_char(c) || c == '-' {
                    current_key.clear();
                    current_key.push(c);
                    state = ParserState::Name;
                    i += 1;
                    continue;
                }

                // Skip unexpected characters
                i += 1;
            }

            ParserState::Name => {
                if c == ':' {
                    // This is a key, move to value
                    state = ParserState::ValueWait;
                    i += 1;
                    continue;
                }

                if c.is_whitespace() {
                    // This is a positional argument
                    if !current_key.is_empty() {
                        params.args.push(current_key.clone());
                    }
                    current_key.clear();
                    state = ParserState::Start;
                    i += 1;
                    continue;
                }

                if c == '/' && i + 1 < len && chars[i + 1] == '/' {
                    // Comment after arg
                    if !current_key.is_empty() {
                        params.args.push(current_key.clone());
                    }
                    current_key.clear();
                    state = ParserState::Comment;
                    i += 2;
                    continue;
                }

                if is_valid_key_char(c) || c == '-' {
                    current_key.push(c);
                    i += 1;
                    continue;
                }

                // Invalid character in key
                return Err(ParamsError::InvalidKeyChar(c));
            }

            ParserState::ValueWait => {
                current_value.clear();

                // Skip whitespace after colon (e.g., "key: value" or "key: 'value'")
                if c.is_whitespace() {
                    i += 1;
                    continue;
                }

                if c == '\'' {
                    // Quoted value
                    state = ParserState::Quote;
                    i += 1;
                    continue;
                }

                // Check for comment after empty value
                if c == '/' && i + 1 < len && chars[i + 1] == '/' {
                    // Empty value followed by comment
                    params.params.push(Param::new(&current_key, ""));
                    current_key.clear();
                    state = ParserState::Comment;
                    i += 2;
                    continue;
                }

                // Start unquoted value
                current_value.push(c);
                state = ParserState::Value;
                i += 1;
            }

            ParserState::Value => {
                if c.is_whitespace() {
                    // End of value
                    params.params.push(Param::new(&current_key, &current_value));
                    current_key.clear();
                    current_value.clear();
                    state = ParserState::Start;
                    i += 1;
                    continue;
                }

                if c == '/' && i + 1 < len && chars[i + 1] == '/' {
                    // Comment after value
                    params.params.push(Param::new(&current_key, &current_value));
                    current_key.clear();
                    current_value.clear();
                    state = ParserState::Comment;
                    i += 2;
                    continue;
                }

                current_value.push(c);
                i += 1;
            }

            ParserState::Quote => {
                if c == '\'' {
                    // Check for escaped quote
                    if i + 1 < len && chars[i + 1] == '\'' {
                        current_value.push('\'');
                        i += 2;
                        continue;
                    }

                    // End of quoted value
                    params.params.push(Param::new(&current_key, &current_value));
                    current_key.clear();
                    current_value.clear();
                    state = ParserState::ValueEnd;
                    i += 1;
                    continue;
                }

                current_value.push(c);
                i += 1;
            }

            ParserState::ValueEnd => {
                if c.is_whitespace() {
                    state = ParserState::Start;
                    i += 1;
                    continue;
                }

                if c == '/' && i + 1 < len && chars[i + 1] == '/' {
                    state = ParserState::Comment;
                    i += 2;
                    continue;
                }

                // Start new token
                state = ParserState::Start;
            }

            ParserState::Comment => {
                // Check for comment end marker -/
                if c == '-' && i + 1 < len && chars[i + 1] == '/' {
                    // Inline comment ended
                    if !current_comment.trim().is_empty() {
                        params.comments.push(current_comment.trim().to_string());
                    }
                    current_comment.clear();
                    state = ParserState::Start;
                    i += 2;
                    continue;
                }

                if c == '\n' {
                    // End of line comment
                    if !current_comment.trim().is_empty() {
                        params.comments.push(current_comment.trim().to_string());
                    }
                    current_comment.clear();
                    state = ParserState::Start;
                    i += 1;
                    continue;
                }

                current_comment.push(c);
                i += 1;
            }
        }
    }

    // Handle remaining content
    match state {
        ParserState::Name => {
            if !current_key.is_empty() {
                params.args.push(current_key);
            }
        }
        ParserState::Value => {
            if !current_key.is_empty() {
                params.params.push(Param::new(&current_key, &current_value));
            }
        }
        ParserState::Quote => {
            // Unterminated quote - still save the value
            if !current_key.is_empty() {
                params.params.push(Param::new(&current_key, &current_value));
            }
        }
        ParserState::Comment => {
            if !current_comment.trim().is_empty() {
                params.comments.push(current_comment.trim().to_string());
            }
        }
        _ => {}
    }

    Ok(params)
}

/// Normalize multiline quoted values to use \n.
fn normalize_multiline(text: &str) -> String {
    let mut result = String::new();
    let mut in_quote = false;
    let chars: Vec<char> = text.chars().collect();
    let len = chars.len();
    let mut i = 0;

    while i < len {
        let c = chars[i];

        if c == '\'' && !in_quote {
            in_quote = true;
            result.push(c);
            i += 1;
            continue;
        }

        if c == '\'' && in_quote {
            // Check for escaped quote
            if i + 1 < len && chars[i + 1] == '\'' {
                result.push('\'');
                result.push('\'');
                i += 2;
                continue;
            }
            in_quote = false;
            result.push(c);
            i += 1;
            continue;
        }

        if in_quote && c == '\n' {
            result.push_str("\\n");
            i += 1;
            continue;
        }

        result.push(c);
        i += 1;
    }

    result
}

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

    #[test]
    fn test_basic_parsing() {
        let params = parse("color:red size:large").unwrap();
        assert_eq!(params.get("color").unwrap(), "red");
        assert_eq!(params.get("size").unwrap(), "large");
    }

    #[test]
    fn test_quoted_values() {
        let params = parse("name:'John Doe' desc:'has spaces'").unwrap();
        assert_eq!(params.get("name").unwrap(), "John Doe");
        assert_eq!(params.get("desc").unwrap(), "has spaces");
    }

    #[test]
    fn test_double_quotes_normalized() {
        let params = parse(r#"name:"John Doe""#).unwrap();
        assert_eq!(params.get("name").unwrap(), "John Doe");
    }

    #[test]
    fn test_positional_args() {
        let params = parse("arg1 arg2 key:value").unwrap();
        assert!(params.exists_arg("arg1"));
        assert!(params.exists_arg("arg2"));
        assert!(!params.exists_arg("key"));
        assert_eq!(params.get("key").unwrap(), "value");
    }

    #[test]
    fn test_empty_value() {
        // With the new parser, "key: next:value" parses "next:value" as the value for key
        // To get an empty value, use "key:" at end of line or "key:'' next:value"
        let params = parse("key:''").unwrap();
        assert_eq!(params.get("key").unwrap(), "");
    }

    #[test]
    fn test_comments() {
        let params = parse("key:value // this is a comment").unwrap();
        assert_eq!(params.get("key").unwrap(), "value");
        assert!(params
            .comments
            .iter()
            .any(|c| c.contains("this is a comment")));
    }

    #[test]
    fn test_empty_input() {
        let params = parse("").unwrap();
        assert!(params.is_empty());
    }

    #[test]
    fn test_whitespace_only() {
        let params = parse("   \n\t  ").unwrap();
        assert!(params.is_empty());
    }
}
