use std::str::FromStr;

use nom::{
  Compare, Parser,
  branch::alt,
  combinator::{map, map_res, opt, value},
  error::{FromExternalError, ParseError},
  multi::{fold_many0, many0, many1, separated_list0, separated_list1},
  sequence::{delimited, pair, preceded, separated_pair},
};

use crate::{
  common::map_res_failure,
  oc::{
    Error, ast,
    error::{ErrorKind, parse_int_error_to_error_kind},
    lexer::{SpannedToken, Token},
  },
};

impl<'a> ParseError<Input<'a>> for Error
{
  fn from_error_kind(input: Input<'a>, _: nom::error::ErrorKind) -> Self
  {
    Error::new(
      super::error::ErrorKind::UnexpectedToken,
      input.first().map_or(Default::default(), |t| t.span.clone()),
    )
  }

  fn append(_: Input<'a>, _: nom::error::ErrorKind, other: Self) -> Self
  {
    other
  }
  fn or(self, other: Self) -> Self
  {
    if self.span().is_empty() || other.span().start < self.span().start
    {
      other
    }
    else if other.span().is_empty() || self.span().start < other.span().start
    {
      self
    }
    else
    {
      if let ErrorKind::ExpectedTokens(mut a) = other.kind
        && let ErrorKind::ExpectedTokens(b) = self.kind
      {
        a.0.extend(b.0);
        return Error::new(ErrorKind::ExpectedTokens(a), self.span);
      }
      Error::new(ErrorKind::UnexpectedToken, self.span)
    }
  }
}

impl<'a> FromExternalError<Input<'a>, ErrorKind> for Error
{
  fn from_external_error(a: Input<'a>, _: nom::error::ErrorKind, e: ErrorKind) -> Self
  {
    Error::new(e, a.first().map_or(Default::default(), |t| t.span.clone()))
  }
}

pub(crate) type OCPResult<I, O> = nom::IResult<I, O, Error>;

#[cfg(test)]
mod tests;

#[derive(Clone, Debug)]
pub(super) struct Input<'a>(pub(super) &'a [SpannedToken], pub(super) &'a Context<'a>);

pub(super) struct TokenIterator<'a>
{
  tokens: &'a [SpannedToken],
  position: usize,
}

impl<'a> Iterator for TokenIterator<'a>
{
  type Item = Token;

  fn next(&mut self) -> Option<Self::Item>
  {
    if self.position >= self.tokens.len()
    {
      None
    }
    else
    {
      let token = &self.tokens[self.position];
      self.position += 1;
      Some(token.value)
    }
  }
}

pub(super) struct IndexTokenIterator<'a>
{
  tokens: &'a [SpannedToken],
  position: usize,
}

impl<'a> Iterator for IndexTokenIterator<'a>
{
  type Item = (usize, Token);

  fn next(&mut self) -> Option<Self::Item>
  {
    if self.position >= self.tokens.len()
    {
      None
    }
    else
    {
      let index = self.position;
      let token = &self.tokens[self.position];
      self.position += 1;
      Some((index, token.value))
    }
  }
}

impl<'a> nom::Input for Input<'a>
{
  type Item = Token;
  type Iter = TokenIterator<'a>;
  type IterIndices = IndexTokenIterator<'a>;

  fn input_len(&self) -> usize
  {
    self.len()
  }

  fn iter_elements(&self) -> Self::Iter
  {
    TokenIterator {
      tokens: self.0,
      position: 0,
    }
  }

  fn iter_indices(&self) -> Self::IterIndices
  {
    IndexTokenIterator {
      tokens: self.0,
      position: 0,
    }
  }

  fn position<P>(&self, predicate: P) -> Option<usize>
  where
    P: Fn(Self::Item) -> bool,
  {
    self.iter().position(|token| predicate(token.value))
  }

  fn slice_index(&self, count: usize) -> Result<usize, nom::Needed>
  {
    if count <= self.len()
    {
      Ok(count)
    }
    else
    {
      Err(nom::Needed::new(count - self.len()))
    }
  }
  fn take(&self, index: usize) -> Self
  {
    Input(&self.0[..index], self.1)
  }
  fn take_from(&self, index: usize) -> Self
  {
    Input(&self.0[index..], self.1)
  }
  fn take_split(&self, index: usize) -> (Self, Self)
  {
    (
      Input(&self.0[..index], self.1),
      Input(&self.0[index..], self.1),
    )
  }
}

impl<'a> Compare<Token> for Input<'a>
{
  fn compare(&self, t: Token) -> nom::CompareResult
  {
    if self.0.is_empty()
    {
      nom::CompareResult::Incomplete
    }
    else if t == self.0[0].value
    {
      nom::CompareResult::Ok
    }
    else
    {
      nom::CompareResult::Error
    }
  }
  fn compare_no_case(&self, t: Token) -> nom::CompareResult
  {
    self.compare(t)
  }
}

impl<'a> std::ops::Deref for Input<'a>
{
  type Target = [SpannedToken];
  fn deref(&self) -> &Self::Target
  {
    self.0
  }
}

#[derive(Debug)]
pub(super) struct Context<'a>
{
  identifiers: ast::VariableIdentifiers,
  source: &'a str,
}

impl<'a> Context<'a>
{
  pub(super) fn new(source: &'a str) -> Self
  {
    Self {
      identifiers: Default::default(),
      source,
    }
  }
  pub(super) fn extract_text(&self, span: &std::ops::Range<usize>) -> &str
  {
    &self.source[span.clone()]
  }
}

fn token(c: Token) -> impl FnMut(Input) -> OCPResult<Input, Token>
{
  move |input: Input<'_>| match input.0
  {
    [SpannedToken { value, span: _ }, rest @ ..] if *value == c => Ok((Input(rest, input.1), c)),
    _ => Err(nom::Err::Error(Error::new(
      ErrorKind::ExpectedTokens(vec![c].into()),
      input.first().map_or(Default::default(), |t| t.span.clone()),
    ))),
  }
}

fn spanned_token(c: Token) -> impl FnMut(Input) -> OCPResult<Input, SpannedToken>
{
  move |input: Input<'_>| match input.0
  {
    [SpannedToken { value, span }, rest @ ..] if *value == c => Ok((
      Input(rest, input.1),
      SpannedToken {
        value: *value,
        span: span.clone(),
      },
    )),
    _ => Err(nom::Err::Error(Error::new(
      ErrorKind::ExpectedTokens(vec![c].into()),
      input.first().map_or(Default::default(), |t| t.span.clone()),
    ))),
  }
}

pub fn to_string<'a, TOutput: Into<String>, E: ParseError<Input<'a>>, F>(
  parser: F,
) -> impl nom::Parser<Input<'a>, Output = String, Error = E>
where
  F: Parser<Input<'a>, Output = TOutput, Error = E>,
{
  parser.map(|x| x.into())
}

// Identifiers and names
fn ident(input: Input<'_>) -> OCPResult<Input<'_>, &str>
{
  match input.0
  {
    [SpannedToken { value, span }, rest @ ..]
      if *value == Token::Identifier || value.is_keyword() =>
    {
      Ok((Input(rest, input.1), input.1.extract_text(span)))
    }
    _ => Err(nom::Err::Error(Error::new(
      ErrorKind::ExpectedTokens(vec![Token::Identifier].into()),
      input.first().map_or(Default::default(), |t| t.span.clone()),
    ))),
  }
}

fn parameter_name(input: Input<'_>) -> OCPResult<Input<'_>, &str>
{
  match input.0
  {
    [
      SpannedToken {
        value: Token::ParameterName,
        span,
      },
      rest @ ..,
    ] => Ok((Input(rest, input.1), input.1.extract_text(span))),
    _ => Err(nom::Err::Error(Error::new(
      ErrorKind::ExpectedTokens(vec![Token::ParameterName].into()),
      input.first().map_or(Default::default(), |t| t.span.clone()),
    ))),
  }
}
// Literals

fn string_literal(input: Input<'_>) -> OCPResult<Input<'_>, &str>
{
  match input.0
  {
    [
      SpannedToken {
        value: Token::String,
        span,
      },
      rest @ ..,
    ] =>
    {
      let lit = input.1.extract_text(span);

      Ok((Input(rest, input.1), &lit[1..lit.len() - 1]))
    }
    _ => Err(nom::Err::Error(Error::new(
      ErrorKind::ExpectedTokens(vec![Token::String].into()),
      input.first().map_or(Default::default(), |t| t.span.clone()),
    ))),
  }
}
fn int(input: Input) -> OCPResult<Input, i64>
{
  match input.0
  {
    [
      SpannedToken {
        value: Token::HexaDecimal,
        span,
      },
      rest @ ..,
    ] => Ok((Input(rest, input.1), {
      let text = input.1.extract_text(span);
      i64::from_str_radix(&text[2..], 16)
        .map_err(|e| nom::Err::Failure(Error::from_parse_int_error(e, span.clone())))?
    })),
    [
      SpannedToken {
        value: Token::OctaDecimal,
        span,
      },
      rest @ ..,
    ] => Ok((Input(rest, input.1), {
      let text = input.1.extract_text(span);
      i64::from_str_radix(&text[2..], 8)
        .map_err(|e| nom::Err::Failure(Error::from_parse_int_error(e, span.clone())))?
    })),
    [
      SpannedToken {
        value: Token::Integer,
        span,
      },
      rest @ ..,
    ] => Ok((Input(rest, input.1), {
      let text = input.1.extract_text(span);
      i64::from_str(text)
        .map_err(|e| nom::Err::Failure(Error::from_parse_int_error(e, span.clone())))?
    })),
    _ => Err(nom::Err::Error(Error::new(
      ErrorKind::ExpectedTokens(vec![Token::Integer].into()),
      input.first().map_or(Default::default(), |t| t.span.clone()),
    ))),
  }
}
fn integer_dotdot(input: Input) -> OCPResult<Input, i64>
{
  match input.0
  {
    [
      SpannedToken {
        value: Token::IntegerDotDot,
        span,
      },
      rest @ ..,
    ] => Ok((Input(rest, input.1), {
      let text = input.1.extract_text(span);
      i64::from_str(&text[..text.len() - 2])
        .map_err(|e| nom::Err::Failure(Error::from_parse_int_error(e, span.clone())))?
    })),
    _ => Err(nom::Err::Error(Error::new(
      ErrorKind::ExpectedTokens(vec![Token::Integer].into()),
      input.first().map_or(Default::default(), |t| t.span.clone()),
    ))),
  }
}
fn num(input: Input) -> OCPResult<Input, f64>
{
  match input.0
  {
    [
      SpannedToken {
        value: Token::Number,
        span,
      },
      rest @ ..,
    ] => Ok((Input(rest, input.1), {
      let text = input.1.extract_text(span);
      let v = f64::from_str(text).map_err(|_| {
        nom::Err::Failure(Error::new(ErrorKind::MalformedFloatingPoint, span.clone()))
      })?;
      if v.is_finite()
      {
        v
      }
      else
      {
        return Err(nom::Err::Failure(Error::new(
          ErrorKind::FloatingPointOverflow,
          span.clone(),
        )));
      }
    })),
    _ => Err(nom::Err::Error(Error::new(
      ErrorKind::ExpectedTokens(vec![Token::Number].into()),
      input.first().map_or(Default::default(), |t| t.span.clone()),
    ))),
  }
}

fn function_call(input: Input) -> OCPResult<Input, ast::Expression>
{
  map(
    pair(
      to_string(ident),
      delimited(
        token(Token::StartParenthesis),
        separated_list0(token(Token::Comma), expression),
        token(Token::EndParenthesis),
      ),
    ),
    |(name, arguments)| ast::FunctionCall { name, arguments }.into(),
  )
  .parse(input)
}

fn primary(input: Input) -> OCPResult<Input, ast::Expression>
{
  alt((
    map(token(Token::Star), |_| {
      ast::Value { value: 0.into() }.into()
    }),
    // Parenthesis
    delimited(
      token(Token::StartParenthesis),
      expression,
      token(Token::EndParenthesis),
    ),
    // function call,
    function_call,
    // Map
    map_literal,
    // Array
    array_literal,
    // Parameter
    parameter,
    // Number
    map(num, |n| {
      ast::Value {
        value: graphcore::Value::Float(n),
      }
      .into()
    }),
    map(int, |i| {
      ast::Value {
        value: graphcore::Value::Integer(i),
      }
      .into()
    }),
    // String
    map(string_literal, |s: &str| {
      ast::Value {
        value: graphcore::Value::String(s.to_string()),
      }
      .into()
    }),
    map(token(Token::Null), |_| {
      ast::Value {
        value: graphcore::Value::Null,
      }
      .into()
    }),
    map(token(Token::True), |_| {
      ast::Value { value: true.into() }.into()
    }),
    map(token(Token::False), |_| {
      ast::Value {
        value: false.into(),
      }
      .into()
    }),
    // Variable
    map(ident, |name: &str| {
      ast::Variable {
        identifier: input.1.identifiers.create_variable_from_name(name),
      }
      .into()
    }),
  ))
  .parse(input)
}

fn member_access(input: Input) -> OCPResult<Input, ast::Expression>
{
  map(
    pair(
      primary,
      many0(preceded(
        token(Token::Dot),
        to_string(alt((ident, string_literal))),
      )),
    ),
    |(left, path)| {
      if path.is_empty()
      {
        left
      }
      else
      {
        ast::MemberAccess { left, path }.into()
      }
    },
  )
  .parse(input)
}
fn array_access(input: Input) -> OCPResult<Input, ast::Expression>
{
  let (input, base) = member_access(input)?;

  fold_many0(
    alt((
      map(
        delimited(
          token(Token::StartBracket),
          (opt(expression), token(Token::DotDot), opt(expression)),
          token(Token::EndBracket),
        ),
        |(start, _, end)| {
          Box::new(move |left| ast::RangeAccess { left, start, end }.into())
            as Box<dyn FnOnce(ast::Expression) -> ast::Expression>
        },
      ),
      map(
        delimited(
          token(Token::StartBracket),
          (integer_dotdot, opt(expression)),
          token(Token::EndBracket),
        ),
        |(start, end)| {
          Box::new(move |left| {
            ast::RangeAccess {
              left,
              start: Some(
                ast::Value {
                  value: start.into(),
                }
                .into(),
              ),
              end,
            }
            .into()
          }) as Box<dyn FnOnce(ast::Expression) -> ast::Expression>
        },
      ),
      map(
        delimited(
          token(Token::StartBracket),
          expression,
          token(Token::EndBracket),
        ),
        |index| {
          Box::new(move |left| ast::IndexAccess { left, index }.into())
            as Box<dyn FnOnce(ast::Expression) -> ast::Expression>
        },
      ),
    )),
    move || base.clone(),
    |left, f| f(left),
  )
  .parse(input)
}

fn label_check(input: Input) -> OCPResult<Input, ast::Expression>
{
  map(
    (array_access, many0((token(Token::Colon), ident))),
    |(expr, labels)| {
      if labels.is_empty()
      {
        expr
      }
      else
      {
        ast::FunctionCall {
          name: "has_labels".to_string(),
          arguments: vec![expr]
            .into_iter()
            .chain(labels.into_iter().map(|(_, label)| {
              ast::Value {
                value: label.into(),
              }
              .into()
            }))
            .collect(),
        }
        .into()
      }
    },
  )
  .parse(input)
}

fn unary(input: Input) -> OCPResult<Input, ast::Expression>
{
  alt((
    map_res_failure(
      preceded(token(Token::Minus), spanned_token(Token::Integer)),
      |i| {
        let span_with_minus = i.span.start - 1..i.span.end;
        let text = input.1.extract_text(&span_with_minus);
        Ok(
          ast::Value {
            value: i64::from_str(text)
              .map_err(parse_int_error_to_error_kind)?
              .into(),
          }
          .into(),
        )
      },
    ),
    map_res_failure(
      preceded(token(Token::Minus), spanned_token(Token::OctaDecimal)),
      |i| {
        let text = input.1.extract_text(&i.span);
        Ok(
          ast::Value {
            value: i64::from_str_radix(&format!("-{}", &text[2..]), 8)
              .map_err(parse_int_error_to_error_kind)?
              .into(),
          }
          .into(),
        )
      },
    ),
    map_res_failure(
      preceded(token(Token::Minus), spanned_token(Token::HexaDecimal)),
      |i| {
        let text = input.1.extract_text(&i.span);
        Ok(
          ast::Value {
            value: i64::from_str_radix(&format!("-{}", &text[2..]), 16)
              .map_err(parse_int_error_to_error_kind)?
              .into(),
          }
          .into(),
        )
      },
    ),
    map(preceded(token(Token::Not), unary), |e| {
      ast::LogicalNegation { value: e }.into()
    }),
    map(preceded(token(Token::Minus), unary), |e| match e
    {
      ast::Expression::Value(ast::Value {
        value: graphcore::Value::Integer(i),
      }) => ast::Value {
        value: graphcore::Value::Integer(-i),
      }
      .into(),
      ast::Expression::Value(ast::Value {
        value: graphcore::Value::Float(f),
      }) => ast::Value {
        value: graphcore::Value::Float(-f),
      }
      .into(),
      _ => ast::Negation { value: e }.into(),
    }),
    preceded(token(Token::Plus), unary),
    label_check,
  ))
  .parse(input)
}

fn exponent(input: Input) -> OCPResult<Input, ast::Expression>
{
  map(
    pair(unary, opt(preceded(token(Token::Caret), exponent))),
    |(l, r)| {
      if let Some(r) = r
      {
        ast::Exponent { left: l, right: r }.into()
      }
      else
      {
        l
      }
    },
  )
  .parse(input)
}

fn multiplicative(input: Input) -> OCPResult<Input, ast::Expression>
{
  let (input, first) = exponent(input)?;
  let (input, rest) = many0(pair(
    alt((
      token(Token::Star),
      token(Token::Slash),
      token(Token::Percent),
    )),
    exponent,
  ))
  .parse(input)?;

  let expr = rest.into_iter().fold(first, |left, (op, right)| match op
  {
    Token::Star => ast::Multiplication { left, right }.into(),
    Token::Slash => ast::Division { left, right }.into(),
    Token::Percent => ast::Modulo { left, right }.into(),
    _ => unreachable!(),
  });

  Ok((input, expr))
}

fn additive(input: Input) -> OCPResult<Input, ast::Expression>
{
  let (input, first) = multiplicative(input)?;
  let (input, rest) = many0(pair(
    alt((token(Token::Plus), token(Token::Minus))),
    multiplicative,
  ))
  .parse(input)?;

  let expr = rest.into_iter().fold(first, |left, (op, right)| match op
  {
    Token::Plus => ast::Addition { left, right }.into(),
    Token::Minus => ast::Subtraction { left, right }.into(),
    _ => unreachable!(),
  });

  Ok((input, expr))
}

fn is_not_null(input: Input) -> OCPResult<Input, ast::Expression>
{
  map(
    (
      additive,
      opt(alt((
        value(1, (token(Token::Is), token(Token::Not), token(Token::Null))),
        value(0, (token(Token::Is), token(Token::Null))),
      ))),
    ),
    |(expr, opt)| match opt
    {
      Some(1) => ast::IsNotNull { value: expr }.into(),
      Some(0) => ast::IsNull { value: expr }.into(),
      Some(_) => unreachable!(),
      None => expr,
    },
  )
  .parse(input)
}

fn relational(input: Input) -> OCPResult<Input, ast::Expression>
{
  let (input, left) = is_not_null(input)?;

  let ops = alt((
    token(Token::In),
    token(Token::Equal),
    token(Token::Different),
    token(Token::LessEqual),
    token(Token::GreaterEqual),
    token(Token::Less),
    token(Token::Greater),
  ));

  let (input, opt) = opt(pair(ops, is_not_null)).parse(input)?;

  if let Some((op, right)) = opt
  {
    let expr = match op
    {
      Token::In => ast::RelationalIn { left, right }.into(),
      Token::Equal => ast::RelationalEqual { left, right }.into(),
      Token::Different => ast::RelationalDifferent { left, right }.into(),
      Token::Less => ast::RelationalInferior { left, right }.into(),
      Token::Greater => ast::RelationalSuperior { left, right }.into(),
      Token::LessEqual => ast::RelationalInferiorEqual { left, right }.into(),
      Token::GreaterEqual => ast::RelationalSuperiorEqual { left, right }.into(),
      _ => unreachable!(),
    };
    Ok((input, expr))
  }
  else
  {
    Ok((input, left))
  }
}

fn logical_and(input: Input) -> OCPResult<Input, ast::Expression>
{
  map(separated_list1(token(Token::And), relational), |mut v| {
    let first = v.remove(0);
    v.into_iter()
      .fold(first, |l, r| ast::LogicalAnd { left: l, right: r }.into())
  })
  .parse(input)
}

fn logical_or(input: Input) -> OCPResult<Input, ast::Expression>
{
  map(separated_list1(token(Token::Or), logical_and), |mut v| {
    let first = v.remove(0);
    v.into_iter()
      .fold(first, |l, r| ast::LogicalOr { left: l, right: r }.into())
  })
  .parse(input)
}

fn logical_xor(input: Input) -> OCPResult<Input, ast::Expression>
{
  map(separated_list1(token(Token::Xor), logical_or), |mut v| {
    let first = v.remove(0);
    v.into_iter()
      .fold(first, |l, r| ast::LogicalXor { left: l, right: r }.into())
  })
  .parse(input)
}

// Expression parsing
fn expression(input: Input) -> OCPResult<Input, ast::Expression>
{
  logical_xor(input)
}

fn labels_and(input: Input) -> OCPResult<Input, ast::LabelExpression>
{
  map(
    separated_list1(token(Token::Colon), ident),
    |labels: Vec<&str>| {
      if labels.len() == 1
      {
        ast::LabelExpression::String(labels[0].to_string())
      }
      else
      {
        ast::LabelExpression::And(
          labels
            .into_iter()
            .map(|label| ast::LabelExpression::String(label.to_string()))
            .collect(),
        )
      }
    },
  )
  .parse(input)
}

// Pattern parsing
fn labels(input: Input) -> OCPResult<Input, ast::LabelExpression>
{
  map(
    preceded(
      token(Token::Colon),
      pair(
        labels_and,
        opt(preceded(
          token(Token::Pipe),
          separated_list1(
            token(Token::Pipe),
            preceded(opt(token(Token::Colon)), labels_and),
          ),
        )),
      ),
    ),
    |(first_label, other_labels)| match other_labels
    {
      None => first_label,
      Some(mut other_labels) =>
      {
        other_labels.insert(0, first_label);
        ast::LabelExpression::Or(other_labels)
      }
    },
  )
  .parse(input)
}

fn map_pair(input: Input) -> OCPResult<Input, (String, ast::Expression)>
{
  separated_pair(
    to_string(alt((ident, string_literal))),
    token(Token::Colon),
    expression,
  )
  .parse(input)
}

fn map_literal(input: Input) -> OCPResult<Input, ast::Expression>
{
  map(
    delimited(
      token(Token::StartBrace),
      opt(separated_list0(token(Token::Comma), map_pair)),
      token(Token::EndBrace),
    ),
    |pairs: Option<Vec<(String, ast::Expression)>>| {
      ast::Expression::Map(ast::Map {
        map: pairs.unwrap_or_default().into_iter().collect(),
      })
    },
  )
  .parse(input)
}

fn array_literal(input: Input) -> OCPResult<Input, ast::Expression>
{
  map(
    delimited(
      token(Token::StartBracket),
      opt(separated_list1(token(Token::Comma), expression)),
      token(Token::EndBracket),
    ),
    |exprs: Option<Vec<ast::Expression>>| {
      ast::Expression::Array(ast::Array {
        array: exprs.unwrap_or_default(),
      })
    },
  )
  .parse(input)
}

fn node_pattern(input: Input) -> OCPResult<Input, ast::NodePattern>
{
  delimited(
    token(Token::StartParenthesis),
    map(
      (
        // Optional variable
        opt(ident),
        // Optional labels (:Label1:Label2)
        opt(labels),
        // Optional properties map or parameter
        opt(properties),
      ),
      |(ident, labels, properties)| ast::NodePattern {
        variable: input
          .1
          .identifiers
          .create_variable_from_name_optional(ident),
        labels: labels.unwrap_or(ast::LabelExpression::None),
        properties,
      },
    ),
    token(Token::EndParenthesis),
  )
  .parse(input)
}

enum EdgeDirection
{
  Left,
  Right,
  Undirected,
}

trait EdgeMode
{
  const ALLOW_UNDIRECTED: bool;
  const ALLOW_LABEL_ALTERNATIVE: bool;
}

struct AllowUndirected;
impl EdgeMode for AllowUndirected
{
  const ALLOW_UNDIRECTED: bool = true;
  const ALLOW_LABEL_ALTERNATIVE: bool = true;
}

struct DisallowUndirected;
impl EdgeMode for DisallowUndirected
{
  const ALLOW_UNDIRECTED: bool = false;
  const ALLOW_LABEL_ALTERNATIVE: bool = false;
}

struct EdgePattern
{
  edge_direction: EdgeDirection,
  edge_variable: Option<ast::VariableIdentifier>,
  labels: ast::LabelExpression,
  properties: Option<ast::Expression>,
  recursive_range: Option<std::ops::Range<usize>>,
  destination_node: ast::NodePattern,
}

fn edge_range(input: Input) -> OCPResult<Input, std::ops::Range<usize>>
{
  map(
    (
      token(Token::Star),
      opt(alt((
        map(pair(integer_dotdot, opt(int)), |(min, max)| {
          (Some(min as usize), max.map(|x| x as usize))
        }),
        map(pair(token(Token::DotDot), opt(int)), |(_, max)| {
          (Some(0usize), max.map(|x| x as usize))
        }),
        map(int, |n| (Some(n as usize), Some(n as usize))),
      ))),
    ),
    |(_, range)| match range
    {
      Some((min, max)) => min.unwrap_or(0)..max.unwrap_or(usize::MAX),
      // Default for an unspecified range (i.e., `[*]`) is 1.., not 0..
      // so that `[*]` matches at least one relationship. This aligns
      // with openCypher TCK expectations for variable-length patterns.
      None => 1..usize::MAX,
    },
  )
  .parse(input)
}

fn edge_pattern<T: EdgeMode>(input: Input) -> OCPResult<Input, EdgePattern>
{
  map_res_failure(
    (
      alt((
        map(token(Token::RightArrow), |_| EdgeDirection::Right),
        map(token(Token::Minus), |_| EdgeDirection::Undirected),
        map(token(Token::LeftArrow), |_| EdgeDirection::Left),
      )),
      opt((
        token(Token::StartBracket),
        // Optional variable
        opt(ident),
        // Optional labels (:Label1:Label2)
        opt(labels),
        // Optional range, used for recursion
        opt(edge_range),
        // Optional properties map or parameter
        opt(properties),
        token(Token::EndBracket),
      )),
      alt((
        map(token(Token::RightArrow), |_| EdgeDirection::Right),
        map(token(Token::Minus), |_| EdgeDirection::Undirected),
        map(token(Token::LeftArrow), |_| EdgeDirection::Left),
      )),
      node_pattern, // Right node
    ),
    |(first_direction, edge_pattern, second_direction, destination_node)| {
      let (edge_variable, recursive_range, labels, properties) = match edge_pattern
      {
        Some((_, var, labels, recursive_range, properties, _)) =>
        {
          (var, recursive_range, labels, properties)
        }
        None => (None, None, None, None),
      };
      let labels = labels.unwrap_or(ast::LabelExpression::None);
      if !T::ALLOW_LABEL_ALTERNATIVE && !labels.is_all_inclusive()
      {
        return Err(ErrorKind::NoSingleRelationshipType);
      }
      let edge_direction = match (first_direction, second_direction)
      {
        (EdgeDirection::Right, EdgeDirection::Right)
        | (EdgeDirection::Right, EdgeDirection::Undirected)
        | (EdgeDirection::Undirected, EdgeDirection::Right) => EdgeDirection::Right,
        (EdgeDirection::Left, EdgeDirection::Left)
        | (EdgeDirection::Left, EdgeDirection::Undirected)
        | (EdgeDirection::Undirected, EdgeDirection::Left) => EdgeDirection::Left,
        (EdgeDirection::Undirected, EdgeDirection::Undirected)
        | (EdgeDirection::Left, EdgeDirection::Right) =>
        {
          if !T::ALLOW_UNDIRECTED
          {
            // Undirected edges are not allowed in this context, treat as Error
            return Err(ErrorKind::RequiresDirectedEdge);
          }
          EdgeDirection::Undirected
        }
        (EdgeDirection::Right, EdgeDirection::Left) =>
        {
          // Conflicting directions, treat as Error
          return Err(ErrorKind::ConflictingEdgeDirections);
        }
      };
      let edge_variable = edge_variable.map(|id| input.1.identifiers.create_variable_from_name(id));
      Ok(EdgePattern {
        edge_direction,
        edge_variable,
        labels,
        properties,
        destination_node,
        recursive_range,
      })
    },
  )
  .parse(input.clone())
}

fn node_or_edges_patterns<T: EdgeMode>(input: Input) -> OCPResult<Input, Vec<ast::Pattern>>
{
  let (input, node) = node_pattern(input)?;

  let (input, edges) = many0(edge_pattern::<T>).parse(input)?;

  if edges.is_empty()
  {
    Ok((input, vec![ast::Pattern::Node(node)]))
  }
  else
  {
    let mut current_node = node;
    let mut iterator = edges.into_iter().peekable();
    let mut patterns = Vec::new();
    while let Some(edge) = iterator.next()
    {
      let EdgePattern {
        edge_direction,
        edge_variable,
        labels,
        properties,
        mut destination_node,
        recursive_range,
      } = edge;
      if destination_node.variable.is_none() && iterator.peek().is_some()
      {
        destination_node.variable = Some(input.1.identifiers.create_anonymous_variable());
      }
      let source = current_node;
      current_node = destination_node.clone();
      let (source, directivity, destination) = match edge_direction
      {
        EdgeDirection::Right => (
          source,
          graphcore::EdgeDirectivity::Directed,
          destination_node,
        ),
        EdgeDirection::Left => (
          destination_node,
          graphcore::EdgeDirectivity::Directed,
          source,
        ),
        EdgeDirection::Undirected => (
          source,
          graphcore::EdgeDirectivity::Undirected,
          destination_node,
        ),
      };
      patterns.push(ast::Pattern::Edge(ast::EdgePattern {
        source,
        variable: edge_variable,
        directivity,
        labels,
        properties,
        destination,
        recursive_range,
      }));
    }

    Ok((input, patterns))
  }
}

fn patterns<T: EdgeMode>(input: Input) -> OCPResult<Input, Vec<ast::Pattern>>
{
  let context = &input.1;
  alt((
    map_res(
      (ident, token(Token::Equal), node_or_edges_patterns::<T>),
      |(name, _, patterns)| {
        // Accept path assignment for one or more consecutive edges by combining them into a single EdgePattern.
        if patterns.is_empty()
        {
          return Err(ErrorKind::PathAcceptSingleEdge);
        }
        // Ensure all inner patterns are edges
        if !patterns.iter().all(|p| matches!(p, ast::Pattern::Edge(_)))
        {
          return Err(ErrorKind::PathAcceptSingleEdge);
        }
        // Consume the patterns and extract their EdgePattern values
        let mut edges: Vec<ast::EdgePattern> = patterns
          .into_iter()
          .map(|p| match p
          {
            ast::Pattern::Edge(e) => e,
            _ => unreachable!(),
          })
          .collect();
        // Combine edges into a single EdgePattern by taking the first source and last destination.
        // Preserve an inner edge variable if exactly one inner edge is named.
        let combined_variable = {
          let mut found: Option<ast::VariableIdentifier> = None;
          for e in edges.iter()
          {
            if let Some(var) = &e.variable
            {
              if found.is_some()
              {
                // multiple inner variables; do not attempt to preserve a single one
                found = None;
                break;
              }
              else
              {
                found = Some(var.clone());
              }
            }
          }
          found
        };
        let original_count = edges.len();
        let first_edge = edges.remove(0);
        let last_edge = edges.pop().unwrap_or_else(|| first_edge.clone());
        // For a single-edge path, preserve that edge's labels, properties, and recursive_range.
        // For multi-hop paths, compute the combined hop range (each fixed edge = 1 hop,
        // var-length edge contributes its own range). Use original_count (before removes)
        // to correctly distinguish single-edge (N=1) from two-edge (N=2) paths.
        let (combined_labels, combined_properties, combined_recursive_range) = if original_count
          == 1
        {
          // Single edge: preserve its constraints.
          (
            first_edge.labels.clone(),
            first_edge.properties.clone(),
            first_edge.recursive_range.clone(),
          )
        }
        else
        {
          // Multi-hop: accumulate min and max hops across all constituent edges.
          // Fixed edges contribute 1 hop each; var-length edges contribute their range.
          let hop_range = |e: &ast::EdgePattern| -> (usize, usize) {
            match &e.recursive_range
            {
              Some(r) => (r.start, r.end),
              None => (1, 1),
            }
          };
          let (mut min_total, mut max_total) = hop_range(&first_edge);
          for e in &edges
          {
            let (mn, mx) = hop_range(e);
            min_total += mn;
            max_total = max_total.saturating_add(mx);
          }
          let (mn, mx) = hop_range(&last_edge);
          min_total += mn;
          max_total = max_total.saturating_add(mx);
          (ast::LabelExpression::None, None, Some(min_total..max_total))
        };
        let combined_edge = ast::EdgePattern {
          variable: combined_variable,
          source: first_edge.source,
          destination: last_edge.destination,
          directivity: first_edge.directivity,
          labels: combined_labels,
          properties: combined_properties,
          recursive_range: combined_recursive_range,
        };
        Ok(vec![
          ast::PathPattern {
            variable: context.identifiers.create_variable_from_name(name),
            edge: combined_edge,
          }
          .into(),
        ])
      },
    ),
    node_or_edges_patterns::<T>,
  ))
  .parse(input)
}

fn parameter(input: Input) -> OCPResult<Input, ast::Expression>
{
  map(to_string(parameter_name), |name| {
    ast::Expression::Parameter(ast::Parameter { name })
  })
  .parse(input)
}

fn properties(input: Input) -> OCPResult<Input, ast::Expression>
{
  alt((map_literal, parameter)).parse(input)
}

fn search_statement(input: Input) -> OCPResult<Input, ast::Search>
{
  let context = &input.1;
  map(
    (
      token(Token::Search),
      ident,
      token(Token::In),
      token(Token::StartParenthesis),
      token(Token::Vector),
      token(Token::Index),
      ident,
      token(Token::For),
      expression,
      token(Token::Limit),
      int,
      token(Token::EndParenthesis),
      opt(preceded(pair(token(Token::Score), token(Token::As)), ident)),
    ),
    |(_, var_name, _, _, _, _, index_name, _, query_expr, _, limit_val, _, score_alias)| {
      ast::Search {
        variable: context.identifiers.create_variable_from_name(var_name),
        index: index_name.to_string(),
        query: query_expr,
        limit: limit_val as usize,
        score_alias: score_alias.map(|alias| context.identifiers.create_variable_from_name(alias)),
      }
    },
  )
  .parse(input)
}

// Statement parsing
fn match_statement(input: Input) -> OCPResult<Input, ast::Statement>
{
  map_res(
    (
      opt(token(Token::Optional)),
      token(Token::Match),
      separated_list1(token(Token::Comma), patterns::<AllowUndirected>),
      opt(preceded(token(Token::Where), expression)),
      opt(search_statement),
    ),
    |(optional, _, patterns, where_expression, search)| {
      let patterns_vec: Vec<ast::Pattern> = patterns.into_iter().flatten().collect();

      // Validate SEARCH clause if present
      if let Some(ref search) = search
      {
        // Collect all variables from patterns
        let pattern_vars: std::collections::HashSet<String> = patterns_vec
          .iter()
          .filter_map(|p| match p
          {
            ast::Pattern::Node(n) => n.variable.as_ref().map(|v| v.name().clone()),
            ast::Pattern::Edge(e) => e.variable.as_ref().map(|v| v.name().clone()),
            ast::Pattern::Path(p) => Some(p.variable.name().clone()),
          })
          .collect();

        // Check if search variable exists in patterns
        if !pattern_vars.contains(search.variable.name())
        {
          return Err(ErrorKind::VariableNotFound);
        }
      }

      Ok(ast::Statement::Match(ast::Match {
        patterns: patterns_vec,
        where_expression,
        optional: optional.is_some(),
        search,
      }))
    },
  )
  .parse(input)
}

fn create_vector_index_statement(input: Input) -> OCPResult<Input, ast::Statement>
{
  map_res(
    (
      token(Token::Create),
      token(Token::Vector),
      token(Token::Index),
      ident,
      token(Token::On),
      preceded(token(Token::Colon), ident),
      delimited(
        token(Token::StartParenthesis),
        ident,
        token(Token::EndParenthesis),
      ),
      opt(preceded(
        token(Token::Options),
        delimited(
          token(Token::StartBrace),
          separated_list0(token(Token::Comma), map_pair),
          token(Token::EndBrace),
        ),
      )),
    ),
    |(_, _, _, index_name, _, label, property, options)| {
      let mut dimension: Option<usize> = None;
      let mut metric: Option<ast::Metric> = None;

      if let Some(opts) = options
      {
        for (key, value) in opts
        {
          match key.as_str()
          {
            "dimension" =>
            {
              if let ast::Expression::Value(v) = value
              {
                if let graphcore::Value::Integer(d) = v.value
                {
                  dimension = Some(d as usize);
                }
                else
                {
                  return Err(ErrorKind::UnexpectedToken);
                }
              }
              else
              {
                return Err(ErrorKind::UnexpectedToken);
              }
            }
            "metric" =>
            {
              if let ast::Expression::Value(v) = value
              {
                if let graphcore::Value::String(m) = v.value
                {
                  metric = match m.as_str()
                  {
                    "cosine" => Some(ast::Metric::Cosine),
                    "l2" => Some(ast::Metric::L2),
                    "dot_product" | "dotproduct" => Some(ast::Metric::DotProduct),
                    _ => return Err(ErrorKind::UnexpectedToken),
                  };
                }
                else
                {
                  return Err(ErrorKind::UnexpectedToken);
                }
              }
              else
              {
                return Err(ErrorKind::UnexpectedToken);
              }
            }
            _ =>
            {} // Ignore unknown options
          }
        }
      }

      Ok(ast::Statement::CreateVectorIndex(ast::CreateVectorIndex {
        name: index_name.to_string(),
        label: label.to_string(),
        property: property.to_string(),
        dimension,
        metric,
      }))
    },
  )
  .parse(input)
}

fn create_statement(input: Input) -> OCPResult<Input, ast::Statement>
{
  map(
    (
      token(Token::Create),
      separated_list1(token(Token::Comma), patterns::<DisallowUndirected>),
    ),
    |(_, patterns)| {
      ast::Statement::Create(ast::Create {
        patterns: patterns.into_iter().flatten().collect(),
      })
    },
  )
  .parse(input)
}

fn expression_with_span(input: Input<'_>) -> OCPResult<Input<'_>, (ast::Expression, &str)>
{
  let start = input.0.first().map_or(0, |x| x.span.start);
  let (input, expr) = expression(input)?;

  let end = input
    .0
    .first()
    .map_or(input.1.source.len(), |x| x.span.start);

  let text = &input.1.source[start..end];

  Ok((input, (expr, text)))
}

fn named_expression(input: Input) -> OCPResult<Input, ast::NamedExpression>
{
  let (input, (expr, text)) = expression_with_span(input)?;

  let (input, alias) = opt(preceded(token(Token::As), ident)).parse(input)?;

  let identifier = match alias
  {
    Some(name) => input.1.identifiers.create_variable_from_name(name),

    None => match &expr
    {
      ast::Expression::Variable(v) => v.identifier.clone(),
      _ => input.1.identifiers.create_variable_from_name(text.trim()),
    },
  };

  Ok((
    input,
    ast::NamedExpression {
      identifier,
      expression: expr,
    },
  ))
}

fn with_return_expression(input: Input) -> OCPResult<Input, (bool, Vec<ast::NamedExpression>)>
{
  alt((
    value((true, Default::default()), token(Token::Star)),
    map(
      separated_list1(token(Token::Comma), named_expression),
      |exprs| (false, exprs),
    ),
  ))
  .parse(input)
}

fn modifiers(input: Input) -> OCPResult<Input, ast::Modifiers>
{
  fold_many0(
    alt((
      map(
        (
          token(Token::Order),
          token(Token::By),
          separated_list1(
            token(Token::Comma),
            (
              expression,
              opt(alt((token(Token::Asc), token(Token::Desc)))),
            ),
          ),
        ),
        |(_, _, orderings)| {
          Box::new(move |mut modifiers: ast::Modifiers| {
            modifiers.order_by = Some(ast::OrderBy {
              expressions: orderings
                .into_iter()
                .map(|(expr, order)| ast::OrderByExpression {
                  expression: expr,
                  asc: !matches!(order, Some(Token::Desc)),
                })
                .collect(),
            });
            modifiers
          }) as Box<dyn FnOnce(ast::Modifiers) -> ast::Modifiers>
        },
      ),
      map((token(Token::Limit), expression), |(_, expr)| {
        Box::new(move |mut modifiers: ast::Modifiers| {
          modifiers.limit = Some(expr);
          modifiers
        }) as Box<dyn FnOnce(ast::Modifiers) -> ast::Modifiers>
      }),
      map((token(Token::Skip), expression), |(_, expr)| {
        Box::new(move |mut modifiers: ast::Modifiers| {
          modifiers.skip = Some(expr);
          modifiers
        }) as Box<dyn FnOnce(ast::Modifiers) -> ast::Modifiers>
      }),
    )),
    ast::Modifiers::default,
    |modifiers, f| f(modifiers),
  )
  .parse(input)
}

fn return_statement(input: Input) -> OCPResult<Input, ast::Statement>
{
  map(
    (
      token(Token::Return),
      with_return_expression,
      modifiers,
      opt(preceded(token(Token::Where), expression)),
    ),
    |(_, (all, expressions), modifiers, where_expression)| {
      ast::Return {
        all,
        expressions,
        modifiers,
        where_expression,
      }
      .into()
    },
  )
  .parse(input)
}

fn with_statement(input: Input) -> OCPResult<Input, ast::Statement>
{
  map(
    (
      token(Token::With),
      with_return_expression,
      modifiers,
      opt(preceded(token(Token::Where), expression)),
    ),
    |(_, (all, expressions), modifiers, where_expression)| {
      ast::With {
        all,
        expressions,
        modifiers,
        where_expression,
      }
      .into()
    },
  )
  .parse(input)
}

fn unwind_statement(input: Input) -> OCPResult<Input, ast::Statement>
{
  map(
    (token(Token::Unwind), named_expression),
    |(_, expression)| {
      ast::Unwind {
        identifier: expression.identifier,
        expression: expression.expression,
      }
      .into()
    },
  )
  .parse(input)
}

fn delete_statement(input: Input) -> OCPResult<Input, ast::Statement>
{
  map(
    (
      opt(token(Token::Detach)),
      token(Token::Delete),
      separated_list1(token(Token::Comma), expression),
    ),
    |(detach, _, expressions)| {
      ast::Delete {
        detach: detach.is_some(),
        expressions,
      }
      .into()
    },
  )
  .parse(input)
}

fn set_statement(input: Input) -> OCPResult<Input, ast::Statement>
{
  let context_0 = &input.1;
  preceded(
    token(Token::Set),
    map(
      separated_list1(
        token(Token::Comma),
        alt((
          map(
            (
              alt((
                ident,
                delimited(
                  token(Token::StartParenthesis),
                  ident,
                  token(Token::EndParenthesis),
                ),
              )),
              many0(preceded(
                token(Token::Dot),
                to_string(alt((ident, string_literal))),
              )),
              token(Token::Equal),
              expression,
            ),
            |(ident, path, _, expression)| {
              ast::OneUpdate::SetProperty(ast::UpdateProperty {
                target: context_0.identifiers.create_variable_from_name(ident),
                path,
                expression,
              })
            },
          ),
          map(
            (
              alt((
                ident,
                delimited(
                  token(Token::StartParenthesis),
                  ident,
                  token(Token::EndParenthesis),
                ),
              )),
              many0(preceded(
                token(Token::Dot),
                to_string(alt((ident, string_literal))),
              )),
              token(Token::PlusEqual),
              expression,
            ),
            |(ident, path, _, expression)| {
              ast::OneUpdate::AddProperty(ast::UpdateProperty {
                target: context_0.identifiers.create_variable_from_name(ident),
                path,
                expression,
              })
            },
          ),
          map(
            (
              ident,
              many1(preceded(token(Token::Colon), to_string(ident))),
            ),
            |(ident, labels)| {
              ast::OneUpdate::AddLabels(ast::AddRemoveLabels {
                target: context_0.identifiers.create_variable_from_name(ident),
                labels,
              })
            },
          ),
        )),
      ),
      |x| ast::Update { updates: x }.into(),
    ),
  )
  .parse(input)
}

fn remove_statement(input: Input) -> OCPResult<Input, ast::Statement>
{
  let context_0 = &input.1;
  preceded(
    token(Token::Remove),
    map(
      separated_list1(
        token(Token::Comma),
        alt((
          map(
            (
              ident,
              many1(preceded(
                token(Token::Dot),
                to_string(alt((ident, string_literal))),
              )),
            ),
            |(ident, path)| {
              ast::OneUpdate::RemoveProperty(ast::RemoveProperty {
                target: context_0.identifiers.create_variable_from_name(ident),
                path,
              })
            },
          ),
          map(
            (
              ident,
              many1(preceded(token(Token::Colon), to_string(ident))),
            ),
            |(ident, labels)| {
              ast::OneUpdate::RemoveLabels(ast::AddRemoveLabels {
                target: context_0.identifiers.create_variable_from_name(ident),
                labels,
              })
            },
          ),
        )),
      ),
      |x| ast::Update { updates: x }.into(),
    ),
  )
  .parse(input)
}

fn call_statement(input: Input) -> OCPResult<Input, ast::Statement>
{
  map(
    (
      token(Token::Call),
      separated_list1(token(Token::Dot), ident),
      delimited(
        token(Token::StartParenthesis),
        separated_list0(token(Token::Comma), expression),
        token(Token::EndParenthesis),
      ),
    ),
    |(_, function_name, arguments)| {
      ast::Call {
        name: function_name
          .into_iter()
          .map(|s| s.to_string())
          .collect::<Vec<_>>()
          .join("."),
        arguments,
      }
      .into()
    },
  )
  .parse(input)
}

fn use_statement(input: Input) -> OCPResult<Input, ast::Statement>
{
  map(
    (
      token(Token::Use),
      alt((
        map(string_literal, |s| s.to_string()),
        map(ident, |s| s.to_string()),
      )),
    ),
    |(_, name)| ast::UseGraph { name }.into(),
  )
  .parse(input)
}

fn create_graph_statement(input: Input) -> OCPResult<Input, ast::Statement>
{
  map(
    (
      token(Token::Create),
      token(Token::Graph),
      opt((token(Token::If), token(Token::Not), token(Token::Exists))),
      alt((
        map(string_literal, |s| s.to_string()),
        map(ident, |s| s.to_string()),
      )),
    ),
    |(_, _, if_not_exists, name)| {
      ast::CreateGraph {
        name,
        if_not_exists: if_not_exists.is_some(),
      }
      .into()
    },
  )
  .parse(input)
}

fn drop_graph_statement(input: Input) -> OCPResult<Input, ast::Statement>
{
  map(
    (
      token(Token::Drop),
      token(Token::Graph),
      opt((token(Token::If), token(Token::Exists))),
      alt((
        map(string_literal, |s| s.to_string()),
        map(ident, |s| s.to_string()),
      )),
    ),
    |(_, _, if_exists, name)| {
      ast::DropGraph {
        name,
        if_exists: if_exists.is_some(),
      }
      .into()
    },
  )
  .parse(input)
}

fn statement(input: Input) -> OCPResult<Input, ast::Statement>
{
  alt((
    match_statement,
    create_vector_index_statement,
    create_statement,
    with_statement,
    return_statement,
    unwind_statement,
    delete_statement,
    set_statement,
    remove_statement,
    call_statement,
    use_statement,
    create_graph_statement,
    drop_graph_statement,
  ))
  .parse(input)
}

fn query(input: Input) -> OCPResult<Input, ast::Statements>
{
  many0(statement).parse(input)
}

pub fn queries(input: Input) -> OCPResult<Input, ast::Queries>
{
  separated_list1(token(Token::Semi), query).parse(input)
}
