///|
/// A validated absolute semantic shape ID. Relative IDs belong to the IDL
/// representation and are resolved before they enter the model.
pub struct ShapeId {
  namespace : String
  name : String
  member : String?
} derive(Eq, Debug, Hash)

///|
fn identifier(text : String) -> Bool {
  let mut first = true
  let mut has_alnum = false
  for c in text.iter() {
    let alpha = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
    let digit = c >= '0' && c <= '9'
    if !alpha && !digit && c != '_' {
      return false
    }
    if first && digit {
      return false
    }
    has_alnum = has_alnum || alpha || digit
    first = false
  }
  has_alnum
}

///|
pub fn ShapeId::parse(text : String) -> ShapeId? {
  let parts = text.split("#").to_array()
  guard parts.length() == 2 else { return None }
  let namespace = parts[0].to_owned()
  guard namespace.split(".").all(p => identifier(p.to_owned())) else {
    return None
  }
  let names = parts[1].split("$").to_array()
  guard names.length() <= 2 && names.iter().all(p => identifier(p.to_owned())) else {
    return None
  }
  Some({
    namespace,
    name: names[0].to_owned(),
    member: if names.length() == 2 {
      Some(names[1].to_owned())
    } else {
      None
    },
  })
}

///|
pub fn ShapeId::with_member(self : ShapeId, name : String) -> ShapeId? {
  if identifier(name) {
    Some({ ..self, member: Some(name), })
  } else {
    None
  }
}

///|
pub fn ShapeId::root(self : ShapeId) -> ShapeId {
  { ..self, member: None, }
}

///|
pub fn ShapeId::to_string(self : ShapeId) -> String {
  Show::to_string(self)
}

///|
pub impl Show for ShapeId with fn output(self, logger) {
  logger.write_string(self.namespace)
  logger.write_string("#")
  logger.write_string(self.name)
  if self.member is Some(name) {
    logger.write_string("$")
    logger.write_string(name)
  }
}