///|
pub(all) suberror SelectorError {
  InvalidSelector(String)
} derive(Eq, Debug)

///|
pub(all) enum SelectorStep {
  ShapeType(String)
  AnyShape
  Attribute(Array[String], String?, Array[String], Bool)
  ScopedAttribute(Array[String], Array[ScopedAssertion])
  Neighbor(String, Array[String])
  Function(String, Array[Array[SelectorStep]])
  VariableStore(String, Array[SelectorStep])
  VariableGet(String)
} derive(Eq, Debug)

///|
pub(all) enum ScopedValue {
  Literal(String)
  Path(Array[String])
} derive(Eq, Debug)

///|
pub(all) struct ScopedAssertion {
  left : ScopedValue
  operator : String
  right : Array[ScopedValue]
  insensitive : Bool
} derive(Eq, Debug)

///|
pub(all) struct Selector {
  source : String
  steps : Array[SelectorStep]
} derive(Eq, Debug)

///|
priv struct SelectorParser {
  chars : Array[Char]
  mut pos : Int32
  mut depth : Int32
}

///|
fn SelectorParser::peek(self : SelectorParser) -> Char {
  if self.pos < self.chars.length() {
    self.chars[self.pos]
  } else {
    '\u{0}'
  }
}

///|
fn SelectorParser::take(self : SelectorParser) -> Char {
  let value = self.peek()
  if value != '\u{0}' {
    self.pos += 1
  }
  value
}

///|
fn SelectorParser::space(self : SelectorParser) -> Unit {
  while self.peek() == ' ' ||
        self.peek() == '\n' ||
        self.peek() == '\r' ||
        self.peek() == '\t' {
    self.pos += 1
  }
}

///|
fn SelectorParser::expect(
  self : SelectorParser,
  character : Char,
) -> Unit raise SelectorError {
  if self.take() != character {
    raise InvalidSelector(
      "I expected '\{character}' at selector offset \{self.pos}.",
    )
  }
}

///|
fn selector_identifier_char(ch : Char) -> Bool {
  (ch >= 'a' && ch <= 'z') ||
  (ch >= 'A' && ch <= 'Z') ||
  (ch >= '0' && ch <= '9') ||
  ch == '_'
}

///|
fn selector_type_known(keyword : String) -> Bool {
  [
    "blob", "boolean", "string", "byte", "short", "integer", "long", "float", "double",
    "bigInteger", "bigDecimal", "timestamp", "document", "list", "set", "map", "structure",
    "union", "enum", "intEnum", "service", "resource", "operation", "member", "number",
    "simpleType", "collection", "aggregateType", "serviceType", "dataType",
  ].contains(keyword)
}

///|
fn SelectorParser::identifier(
  self : SelectorParser,
) -> String raise SelectorError {
  let start = self.pos
  while selector_identifier_char(self.peek()) {
    self.pos += 1
  }
  if self.pos == start {
    raise InvalidSelector(
      "I expected an identifier at selector offset \{start}.",
    )
  }
  String::from_array(self.chars[start:self.pos])
}

///|
fn SelectorParser::path_segment(
  self : SelectorParser,
) -> String raise SelectorError {
  let start = self.pos
  while selector_identifier_char(self.peek()) ||
        self.peek() == '.' ||
        self.peek() == '#' {
    self.pos += 1
  }
  if self.pos == start {
    raise InvalidSelector(
      "I expected an attribute path segment at selector offset \{start}.",
    )
  }
  String::from_array(self.chars[start:self.pos])
}

///|
fn SelectorParser::selector_path(
  self : SelectorParser,
) -> Array[String] raise SelectorError {
  let parts : Array[String] = []
  while true {
    if self.peek() == '(' {
      self.take() |> ignore
      let name = self.identifier()
      self.expect(')')
      parts.push("(" + name + ")")
    } else {
      parts.push(self.path_segment())
    }
    if self.peek() != '|' {
      break
    }
    self.take() |> ignore
  }
  parts
}

///|
fn SelectorParser::value(self : SelectorParser) -> String raise SelectorError {
  self.space()
  if self.peek() == '"' || self.peek() == '\'' {
    let quote = self.take()
    let start = self.pos
    while self.peek() != quote && self.peek() != '\u{0}' {
      if self.peek() == '\\' && self.pos + 1 < self.chars.length() {
        self.pos += 1
      }
      self.pos += 1
    }
    let value = String::from_array(self.chars[start:self.pos])
    self.expect(quote)
    return value
  }
  let start = self.pos
  while self.peek() != '\u{0}' &&
        self.peek() != ',' &&
        self.peek() != ']' &&
        self.peek() != ' ' &&
        self.peek() != '\n' &&
        self.peek() != '\r' {
    self.pos += 1
  }
  if self.pos == start {
    raise InvalidSelector(
      "I expected an attribute value at selector offset \{start}.",
    )
  }
  String::from_array(self.chars[start:self.pos])
}

///|
fn SelectorParser::attribute(
  self : SelectorParser,
) -> SelectorStep raise SelectorError {
  let path = self.attribute_path()
  self.space()
  if self.peek() == ']' {
    self.take() |> ignore
    return Attribute(path, None, [], false)
  }
  let first = self.take()
  let operator = match first {
    '=' => "="
    '!' | '^' | '$' | '*' | '?' => {
      self.expect('=')
      first.to_string() + "="
    }
    '>' | '<' =>
      if self.peek() == '=' {
        self.take() |> ignore
        first.to_string() + "="
      } else {
        first.to_string()
      }
    _ =>
      raise InvalidSelector(
        "I expected an attribute comparator at selector offset \{self.pos}.",
      )
  }
  self.space()
  if self.peek() == '=' {
    raise InvalidSelector("I found a repeated attribute comparator.")
  }
  let values = [self.value()]
  self.space()
  while self.peek() == ',' {
    self.take() |> ignore
    values.push(self.value())
    self.space()
  }
  let insensitive = self.peek() == 'i'
  if insensitive {
    self.take() |> ignore
    self.space()
  }
  self.expect(']')
  Attribute(path, Some(operator), values, insensitive)
}

///|
fn SelectorParser::attribute_path(
  self : SelectorParser,
) -> Array[String] raise SelectorError {
  let path : Array[String] = [self.identifier()]
  while self.peek() == '|' {
    self.take() |> ignore
    if self.peek() == '(' {
      self.take() |> ignore
      let function_name = self.identifier()
      self.expect(')')
      path.push("(" + function_name + ")")
    } else if self.peek() == '"' || self.peek() == '\'' {
      path.push(self.value())
    } else {
      path.push(self.path_segment())
    }
  }
  path
}

///|
fn SelectorParser::scoped_value(
  self : SelectorParser,
) -> ScopedValue raise SelectorError {
  self.space()
  if self.peek() == '@' {
    self.take() |> ignore
    self.expect('{')
    let path = self.selector_path()
    self.expect('}')
    Path(path)
  } else {
    Literal(self.value())
  }
}

///|
fn SelectorParser::scoped_assertion(
  self : SelectorParser,
) -> ScopedAssertion raise SelectorError {
  let left = self.scoped_value()
  self.space()
  let first = self.take()
  let operator = match first {
    '=' => "="
    '!' | '^' | '$' | '*' | '?' => {
      self.expect('=')
      first.to_string() + "="
    }
    '>' | '<' =>
      if self.peek() == '=' {
        self.take() |> ignore
        first.to_string() + "="
      } else {
        first.to_string()
      }
    '{' => {
      let start = self.pos
      while self.peek() != '}' && self.peek() != '\u{0}' {
        self.take() |> ignore
      }
      let token = String::from_array(self.chars[start:self.pos])
      self.expect('}')
      "{" + token + "}"
    }
    _ =>
      raise InvalidSelector(
        "I expected a scoped comparator at offset \{self.pos}.",
      )
  }
  let right = [self.scoped_value()]
  self.space()
  while self.peek() == ',' {
    self.take() |> ignore
    right.push(self.scoped_value())
    self.space()
  }
  let insensitive = self.peek() == 'i'
  if insensitive {
    self.take() |> ignore
    self.space()
  }
  { left, operator, right, insensitive, }
}

///|
fn SelectorParser::scoped_attribute(
  self : SelectorParser,
) -> SelectorStep raise SelectorError {
  let scope = if self.peek() == ':' { [] } else { self.attribute_path() }
  self.expect(':')
  let assertions = [self.scoped_assertion()]
  self.space()
  while self.peek() == '&' {
    self.expect('&')
    self.expect('&')
    assertions.push(self.scoped_assertion())
    self.space()
  }
  self.expect(']')
  ScopedAttribute(scope, assertions)
}

///|
fn SelectorParser::labels(
  self : SelectorParser,
) -> Array[String] raise SelectorError {
  let labels = [self.identifier()]
  self.space()
  while self.peek() == ',' {
    self.take() |> ignore
    self.space()
    labels.push(self.identifier())
    self.space()
  }
  self.expect(']')
  labels
}

///|
fn SelectorParser::function_(
  self : SelectorParser,
) -> SelectorStep raise SelectorError {
  let name = self.identifier()
  self.space()
  self.expect('(')
  let args : Array[Array[SelectorStep]] = []
  self.space()
  if self.peek() != ')' {
    args.push(self.sequence())
    while self.peek() == ',' {
      self.take() |> ignore
      args.push(self.sequence())
    }
  }
  self.expect(')')
  if args.is_empty() ||
    (["not", "in", "root", "recursive"].contains(name) && args.length() != 1) ||
    (name == "topdown" && (args.is_empty() || args.length() > 2)) ||
    (["is", "each", "test"].contains(name) && args.is_empty()) {
    raise InvalidSelector("I found the wrong number of arguments for :\{name}.")
  }
  Function(name, args)
}

///|
fn SelectorParser::variable(
  self : SelectorParser,
) -> SelectorStep raise SelectorError {
  self.space()
  if self.peek() == '{' {
    self.take() |> ignore
    self.space()
    let name = self.identifier()
    self.space()
    self.expect('}')
    return VariableGet(name)
  }
  let name = self.identifier()
  self.space()
  self.expect('(')
  let selector = self.sequence()
  self.expect(')')
  VariableStore(name, selector)
}

///|
fn SelectorParser::step(
  self : SelectorParser,
) -> SelectorStep raise SelectorError {
  self.space()
  match self.take() {
    '*' => AnyShape
    '[' =>
      if self.peek() == '@' {
        self.take() |> ignore
        self.scoped_attribute()
      } else {
        self.attribute()
      }
    ':' => self.function_()
    '$' => self.variable()
    '>' => Neighbor(">", [])
    '<' =>
      if self.peek() == '-' {
        self.take() |> ignore
        self.expect('[')
        let labels = self.labels()
        self.expect('-')
        Neighbor("<-", labels)
      } else {
        Neighbor("<", [])
      }
    '~' => {
      self.expect('>')
      Neighbor("~>", [])
    }
    '-' => {
      self.expect('[')
      let labels = self.labels()
      self.expect('-')
      self.expect('>')
      Neighbor("->", labels)
    }
    '\u{0}' =>
      raise InvalidSelector(
        "I found the end of the selector where a step was required.",
      )
    other => {
      self.pos -= 1
      if selector_identifier_char(other) {
        let keyword = self.identifier()
        if !selector_type_known(keyword) {
          raise InvalidSelector("I found unknown shape type \{keyword}.")
        }
        ShapeType(keyword)
      } else {
        raise InvalidSelector(
          "I found unexpected selector character '\{other}'.",
        )
      }
    }
  }
}

///|
fn SelectorParser::sequence(
  self : SelectorParser,
) -> Array[SelectorStep] raise SelectorError {
  self.depth += 1
  if self.depth > 100 {
    raise InvalidSelector("I found more than 100 nested selectors.")
  }
  self.space()
  let steps : Array[SelectorStep] = []
  while self.peek() != '\u{0}' && self.peek() != ',' && self.peek() != ')' {
    steps.push(self.step())
    self.space()
  }
  self.depth -= 1
  if steps.is_empty() {
    raise InvalidSelector("I expected a selector step at offset \{self.pos}.")
  }
  steps
}

///|
pub fn Selector::parse(source : String) -> Selector raise SelectorError {
  let parser : SelectorParser = { chars: source.to_array(), pos: 0, depth: 0, }
  let steps = parser.sequence()
  parser.space()
  if parser.peek() != '\u{0}' {
    raise InvalidSelector(
      "I found unexpected selector text at offset \{parser.pos}.",
    )
  }
  { source, steps, }
}