use crate::entry::{Entry, ParserConfig};
use crate::error::ParseError;

/// UTF-8 BOM bytes.
const BOM: [u8; 3] = [0xEF, 0xBB, 0xBF];

// ---------------------------------------------------------------------------
// Iterator-based streaming parser
// ---------------------------------------------------------------------------

/// A streaming parser that yields one [`Result<Entry, ParseError>`] per line.
///
/// Always yields all entries (Kv, Comment, Blank, Shebang) without
/// suppression. Config-level filtering is performed by the public API in
/// [`crate::lib`].
pub struct ParserIter<'a> {
    /// Remaining input bytes (after BOM/shebang have been consumed).
    remaining: &'a [u8],
    /// Current 1-based line number.
    line_num: u32,
    /// Pre-computed items to yield before line iteration begins (e.g. BOM
    /// error or shebang entry).
    pre_items: std::collections::VecDeque<Result<Entry, ParseError>>,
}

impl<'a> ParserIter<'a> {
    pub(crate) fn new(input: &'a [u8]) -> Self {
        let mut pre_items = std::collections::VecDeque::new();
        let mut pos = 0usize;

        // B.1 — BOM detection (before any other parsing).
        if input.starts_with(&BOM) {
            pre_items.push_back(Err(ParseError::BomError));
            pos += 3;
        }

        // Shebang detection (Sections 3.5, 6.4).
        if input[pos..].starts_with(b"#!") {
            // Find the EOL for the shebang line.
            let (shebang_raw, eol_len) = split_first_line(&input[pos..]);
            // The shebang line is line 0; validate UTF-8 for it.
            match std::str::from_utf8(shebang_raw) {
                Err(_) => {
                    pre_items.push_back(Err(ParseError::InvalidUtf8Error { line: 0 }));
                }
                Ok(text) => {
                    pre_items.push_back(Ok(Entry::Shebang {
                        text: text.to_owned(),
                    }));
                }
            }
            pos += shebang_raw.len() + eol_len;
        }

        ParserIter {
            remaining: &input[pos..],
            line_num: 0,
            pre_items,
        }
    }
}

impl<'a> Iterator for ParserIter<'a> {
    type Item = Result<Entry, ParseError>;

    fn next(&mut self) -> Option<Self::Item> {
        // First drain pre-items (BOM error, shebang entry).
        if let Some(item) = self.pre_items.pop_front() {
            return Some(item);
        }

        if self.remaining.is_empty() {
            return None;
        }

        // Find next EOL.
        let (raw_line, eol_len) = split_first_line(self.remaining);

        if eol_len == 0 {
            // No EOL found — consume remaining bytes and return error.
            self.remaining = b"";
            return Some(Err(ParseError::MissingFinalEolError));
        }

        self.remaining = &self.remaining[raw_line.len() + eol_len..];
        self.line_num += 1;
        let line_num = self.line_num;

        Some(parse_line(raw_line, line_num))
    }
}

// ---------------------------------------------------------------------------
// Line parsing helpers
// ---------------------------------------------------------------------------

/// Split `input` at the first `LF` or `CRLF`, returning `(line_bytes, eol_len)`.
///
/// `line_bytes` does **not** include the EOL bytes. `eol_len` is 0 when no
/// EOL is found (meaning the input has no terminator).
///
/// For a CRLF sequence the EOL length is 2 and the returned line slice does
/// **not** include the preceding `CR`. For a bare `CR` (not followed by `LF`)
/// the `CR` is kept inside the returned line bytes so that `parse_line` can
/// report it as `InvalidCharacterError`.
fn split_first_line(input: &[u8]) -> (&[u8], usize) {
    for (i, &b) in input.iter().enumerate() {
        if b == b'\n' {
            // Check whether the preceding byte was CR (CRLF sequence).
            if i > 0 && input[i - 1] == b'\r' {
                return (&input[..i - 1], 2);
            }
            // Bare LF.
            return (&input[..i], 1);
        }
    }
    // No EOL found.
    (input, 0)
}

/// Parse a single raw line (without EOL bytes) and return an entry or error.
fn parse_line(raw: &[u8], line_num: u32) -> Result<Entry, ParseError> {
    // B.2 — UTF-8 validation.
    let line_str = match std::str::from_utf8(raw) {
        Err(_) => return Err(ParseError::InvalidUtf8Error { line: line_num }),
        Ok(s) => s,
    };

    // B.3 — Standalone CR or NUL.
    //
    // At this point we know the bytes are valid UTF-8, so iterating over
    // chars is safe and correct.
    for ch in line_str.chars() {
        if ch == '\r' || ch == '\0' {
            return Err(ParseError::InvalidCharacterError { line: line_num });
        }
    }

    // Section 4.5 — strip leading whitespace (space / tab).
    let trimmed = line_str.trim_start_matches([' ', '\t']);

    // Blank line.
    if trimmed.is_empty() {
        return Ok(Entry::Blank { line: line_num });
    }

    // Comment line.
    if let Some(text) = trimmed.strip_prefix('#') {
        return Ok(Entry::Comment {
            line: line_num,
            text: text.to_owned(),
        });
    }

    // Data line — find first '='.
    match trimmed.find('=') {
        None => Err(ParseError::MissingOperatorError { line: line_num }),
        Some(eq_pos) => {
            let key_raw = &trimmed[..eq_pos];
            // Section 4.5 — strip trailing whitespace between key and '='.
            let key = key_raw.trim_end_matches([' ', '\t']);

            // B.4 — Empty key.
            if key.is_empty() {
                return Err(ParseError::EmptyKeyError { line: line_num });
            }

            // B.6 — Key validation: [A-Za-z_][A-Za-z0-9_]*
            if !is_valid_key(key) {
                return Err(ParseError::InvalidKeyError { line: line_num });
            }

            let value = &trimmed[eq_pos + 1..];
            Ok(Entry::Kv {
                line: line_num,
                key: key.to_owned(),
                value: value.to_owned(),
            })
        }
    }
}

/// Returns `true` if `key` matches `[A-Za-z_][A-Za-z0-9_]*`.
fn is_valid_key(key: &str) -> bool {
    let mut chars = key.chars();
    match chars.next() {
        None => false,
        Some(first) => {
            if !matches!(first, 'A'..='Z' | 'a'..='z' | '_') {
                return false;
            }
            chars.all(|c| matches!(c, 'A'..='Z' | 'a'..='z' | '0'..='9' | '_'))
        }
    }
}

// ---------------------------------------------------------------------------
// Public iterator constructors (used by lib.rs)
// ---------------------------------------------------------------------------

/// Create a streaming parser iterator.
pub fn make_iter<'a>(input: &'a [u8], _config: &ParserConfig) -> ParserIter<'a> {
    ParserIter::new(input)
}

