//! Regex literal lexing.
//!
//! The lexer treats a `/` as the start of a regex when the previous
//! significant token's [`TokenKind::allows_regex_after`] returns true.
//! This module's [`scan`] then collects the pattern body up to the
//! closing `/`, distinguishing `/` inside character classes (`[a/b]`)
//! and respecting `\` escapes, then collects the flag characters.
//!
//! [`TokenKind::allows_regex_after`]: crate::token::TokenKind::allows_regex_after

use crate::error::Error;
use crate::token::TokenKind;
use ecma_syntax_cat::span::Position;

/// Scan a regex literal starting at byte `start` (the opening `/`).
///
/// # Errors
///
/// Returns [`Error::UnterminatedRegex`] on EOF or raw line terminator
/// before the closing `/`.
pub fn scan(src: &[u8], start: usize, at: Position) -> Result<(TokenKind, usize), Error> {
    let body_start = start + 1;
    let body_end = scan_body(src, body_start, false, at)?;
    let pattern_bytes = src.get(body_start..body_end).unwrap_or(&[]);
    let pattern = std::str::from_utf8(pattern_bytes).unwrap_or("").to_owned();
    let flags_start = body_end + 1;
    let flags_end = scan_flags(src, flags_start);
    let flags_bytes = src.get(flags_start..flags_end).unwrap_or(&[]);
    let flags = std::str::from_utf8(flags_bytes).unwrap_or("").to_owned();
    Ok((TokenKind::RegExp { pattern, flags }, flags_end))
}

fn scan_body(src: &[u8], pos: usize, in_class: bool, at: Position) -> Result<usize, Error> {
    match src.get(pos).copied() {
        None | Some(b'\n' | b'\r') => Err(Error::UnterminatedRegex { at }),
        Some(b'/') if !in_class => Ok(pos),
        Some(b'\\') => scan_after_escape(src, pos, in_class, at),
        Some(b'[') => scan_body(src, pos + 1, true, at),
        Some(b']') if in_class => scan_body(src, pos + 1, false, at),
        Some(_other) => scan_body(src, pos + 1, in_class, at),
    }
}

fn scan_after_escape(src: &[u8], pos: usize, in_class: bool, at: Position) -> Result<usize, Error> {
    match src.get(pos + 1).copied() {
        None | Some(b'\n' | b'\r') => Err(Error::UnterminatedRegex { at }),
        Some(_other) => scan_body(src, pos + 2, in_class, at),
    }
}

fn scan_flags(src: &[u8], from: usize) -> usize {
    src.get(from)
        .copied()
        .filter(u8::is_ascii_alphabetic)
        .map_or(from, |_| scan_flags(src, from + 1))
}
