use core::range::Range;

use crate::{
    error::{Error, ErrorKind, Result},
    tokens::{Token, TokenStream, TokenWithContext},
    traverse::{Visitor, parse_tokens, validate_start_of_value},
};

#[derive(Debug, PartialEq, Eq, Clone)]
pub enum ArrayState {
    Open,
    ValueOrEnd {
        open_ctx: TokenWithContext,
    },
    Value {
        open_ctx: TokenWithContext,
        expect_ctx: TokenWithContext,
    },
    CommaOrEnd {
        open_ctx: TokenWithContext,
        last_value_range: Range<usize>,
    },
    End(Range<usize>),
}

impl ArrayState {
    pub fn process<'a>(
        self,
        tokens: &mut TokenStream<'a>,
        text: &'a str,
        visitor: &mut impl Visitor<'a>,
    ) -> Result<Self> {
        let next_state = match self {
            ArrayState::Open => match tokens.next_token()? {
                Some(
                    open_ctx @ TokenWithContext {
                        token: Token::OpenSquareBracket,
                        ..
                    },
                ) => {
                    visitor.on_array_open();
                    ArrayState::ValueOrEnd { open_ctx }
                }
                maybe_token => {
                    return Err(Error::from_maybe_token_with_context(
                        |tok| ErrorKind::ExpectedOpenBrace {
                            expected: '['.into(),
                            context: None,
                            found: tok,
                        },
                        maybe_token,
                        text,
                    ));
                }
            },

            ArrayState::ValueOrEnd { open_ctx } => match tokens.peek_token()?.copied() {
                Some(TokenWithContext {
                    token: Token::ClosedSquareBracket,
                    range: closed_range,
                }) => {
                    tokens.next_token()?;
                    visitor.on_array_close();
                    ArrayState::End(open_ctx.range.start..closed_range.end)
                }
                Some(token_ctx) if token_ctx.token.is_start_of_value() => ArrayState::Value {
                    open_ctx,
                    expect_ctx: open_ctx,
                },
                Some(_) => {
                    return Err(Error::from_maybe_token_with_context(
                        |tok| {
                            ErrorKind::expected_entry_or_closed_delimiter(open_ctx, tok)
                                .expect("array should open with a square bracket")
                        },
                        tokens.next_token()?,
                        text,
                    ));
                }
                None => {
                    return Err(Error::from_maybe_token_with_context(
                        |tok| {
                            ErrorKind::expected_entry_or_closed_delimiter(open_ctx, tok)
                                .expect("array should open with a square bracket")
                        },
                        None,
                        text,
                    ));
                }
            },

            ArrayState::Value {
                open_ctx,
                expect_ctx,
            } => {
                validate_start_of_value(text, expect_ctx, tokens.peek_token()?.copied())?;

                let value_range = parse_tokens(tokens, text, false, visitor)?;
                ArrayState::CommaOrEnd {
                    open_ctx,
                    last_value_range: value_range,
                }
            }

            ArrayState::CommaOrEnd { open_ctx, .. } => match tokens.peek_token()?.copied() {
                Some(TokenWithContext {
                    token: Token::ClosedSquareBracket,
                    range: closed_range,
                }) => {
                    tokens.next_token()?;

                    visitor.on_array_close();
                    ArrayState::End(open_ctx.range.start..closed_range.end)
                }
                Some(
                    comma_ctx @ TokenWithContext {
                        token: Token::Comma,
                        ..
                    },
                ) => {
                    tokens.next_token()?;
                    visitor.on_item_delim();
                    ArrayState::Value {
                        open_ctx,
                        expect_ctx: comma_ctx,
                    }
                }
                _ => {
                    return Err(Error::from_maybe_token_with_context(
                        |tok| {
                            ErrorKind::expected_entry_or_closed_delimiter(open_ctx, tok)
                                .expect("array should open with a square bracket")
                        },
                        tokens.next_token()?,
                        text,
                    ));
                }
            },

            ArrayState::End(_) => {
                return Ok(self);
            }
        };

        Ok(next_state)
    }
}

pub fn parse_array<'a>(
    tokens: &mut TokenStream<'a>,
    text: &'a str,
    visitor: &mut impl Visitor<'a>,
) -> Result<Range<usize>> {
    let mut state = ArrayState::Open;

    loop {
        state = state.process(tokens, text, visitor)?;
        if let ArrayState::End(result) = state {
            break Ok(result);
        }
    }
}
