///|
pub(all) struct RouteLocation {
  path : String
  query : Array[RouteQueryParam]
} derive(Eq, Debug, ToJson)

///|
pub(all) struct RouteQueryParam {
  key : String
  value : String
} derive(Eq, Debug, ToJson)

///|
pub(all) struct RouterState {
  navigation : NavigationState
}

///|
pub(all) struct FormFieldState {
  name : String
  value : State[String]
  validator : (String) -> String?
}

///|
pub(all) struct FormValidationResult {
  valid : Bool
  errors : Array[FormFieldError]
} derive(Eq, Debug, ToJson)

///|
pub(all) struct FormFieldError {
  name : String
  message : String
} derive(Eq, Debug, ToJson)

///|
pub fn RouteLocation::new(
  path~ : String,
  query? : Array[RouteQueryParam] = [],
) -> RouteLocation {
  { path, query }
}

///|
pub fn RouteLocation::parse(value : String) -> RouteLocation {
  let parts = split_once(value, '?')
  match parts {
    Some((path, query_string)) => {
      let query : Array[RouteQueryParam] = []
      for pair_view in query_string.split("&") {
        let pair = pair_view.to_owned()
        if pair != "" {
          match split_once(pair, '=') {
            Some((key, value)) => query.push({ key, value })
            None => query.push({ key: pair, value: "" })
          }
        }
      }
      RouteLocation::new(path~, query~)
    }
    None => RouteLocation::new(path=value)
  }
}

///|
pub fn RouteLocation::query_value(
  self : RouteLocation,
  key : String,
) -> String? {
  for param in self.query {
    if param.key == key {
      return Some(param.value)
    }
  }
  None
}

///|
pub fn RouteLocation::to_route(self : RouteLocation) -> String {
  if self.query.length() == 0 {
    self.path
  } else {
    let parts : Array[String] = []
    for param in self.query {
      parts.push("\{param.key}=\{param.value}")
    }
    let query_string = parts.join("&")
    "\{self.path}?\{query_string}"
  }
}

///|
pub fn RouterState::new(
  initial? : RouteLocation = RouteLocation::new(path="/"),
) -> RouterState {
  { navigation: NavigationState::new(initial=initial.to_route()) }
}

///|
pub fn RouterState::current(self : RouterState) -> RouteLocation {
  RouteLocation::parse(self.navigation.current())
}

///|
pub fn RouterState::push(self : RouterState, location : RouteLocation) -> Unit {
  self.navigation.push(location.to_route())
}

///|
pub fn RouterState::replace(
  self : RouterState,
  location : RouteLocation,
) -> Unit {
  self.navigation.stack_slot.update(stack => {
    if stack.length() == 0 {
      [location.to_route()]
    } else {
      let next : Array[String] = []
      for index, route in stack {
        if index + 1 < stack.length() {
          next.push(route)
        }
      }
      next.push(location.to_route())
      next
    }
  })
}

///|
pub fn RouterState::pop(self : RouterState) -> Bool {
  self.navigation.pop()
}

///|
pub fn FormFieldState::new(
  name~ : String,
  initial? : String = "",
  validator? : (String) -> String? = _value => None,
) -> FormFieldState {
  { name, value: State::new(initial), validator }
}

///|
pub fn FormFieldState::binding(self : FormFieldState) -> Binding[String] {
  self.value.binding()
}

///|
pub fn FormFieldState::validate(self : FormFieldState) -> FormFieldError? {
  match (self.validator)(self.value.get()) {
    Some(message) => Some({ name: self.name, message })
    None => None
  }
}

///|
pub fn validate_form(fields : Array[FormFieldState]) -> FormValidationResult {
  let errors : Array[FormFieldError] = []
  for field in fields {
    match field.validate() {
      Some(error) => errors.push(error)
      None => ()
    }
  }
  { valid: errors.length() == 0, errors }
}

///|
pub fn required_field(message? : String = "Required") -> (String) -> String? {
  value => if value == "" { Some(message) } else { None }
}

///|
fn split_once(value : String, separator : Char) -> (String, String)? {
  let before : Array[Char] = []
  let after : Array[Char] = []
  let mut found = false
  for ch in value {
    if !found && ch == separator {
      found = true
    } else if found {
      after.push(ch)
    } else {
      before.push(ch)
    }
  }
  if found {
    Some((String::from_array(before), String::from_array(after)))
  } else {
    None
  }
}