///|
/// A term's identity includes its lexical spelling and datatype, never its coerced value.
pub(all) enum Term {
  Iri(String)
  Blank(String)
  Literal(String, String, String?)
} derive(@debug.Debug)

///|
pub impl Eq for Term with fn equal(self, other) {
  match (self, other) {
    (Iri(a), Iri(b)) | (Blank(a), Blank(b)) => a == b
    (Literal(a, ad, al), Literal(b, bd, bl)) =>
      a == b &&
      ad == bd &&
      al.map(s => s.to_lower()) == bl.map(s => s.to_lower())
    _ => false
  }
}

///|
pub impl Hash for Term with fn hash_combine(self, hasher) {
  match self {
    Iri(s) => {
      hasher.combine_int(0)
      hasher.combine_string(s)
    }
    Blank(s) => {
      hasher.combine_int(1)
      hasher.combine_string(s)
    }
    Literal(value, datatype, language) => {
      hasher.combine_int(2)
      hasher.combine_string(value)
      hasher.combine_string(datatype)
      match language {
        Some(tag) => {
          hasher.combine_int(1)
          hasher.combine_string(tag.to_lower())
        }
        None => hasher.combine_int(0)
      }
    }
  }
}

///|
pub extend Term with Eq::{equal, not_equal}

///|
pub extend Term with Hash::{hash, hash_combine}

///|
pub extend Term with @debug.Debug::{to_repr}

///|
pub(all) struct Triple {
  subject : Term
  predicate : String
  object : Term
} derive(Eq, Hash, @debug.Debug)

///|
pub extend Triple with Eq::{equal, not_equal}

///|
pub extend Triple with Hash::{hash, hash_combine}

///|
pub extend Triple with @debug.Debug::{to_repr}

///|
/// Graphs use RDF set semantics: duplicate triples never count twice.
pub struct Graph {
  triples : Array[Triple]
  spo : Map[(Term, String), Array[Term]]
  pos : Map[(String, Term), Array[Term]]
}

///|
pub fn Graph::new(triples : Array[Triple]) -> Graph {
  let result : Array[Triple] = []
  let seen : Map[Triple, Bool] = Map([])
  let spo : Map[(Term, String), Array[Term]] = Map([])
  let pos : Map[(String, Term), Array[Term]] = Map([])
  for t in triples {
    if !seen.contains(t) {
      seen[t] = true
      result.push(t)
      spo.get_or_init((t.subject, t.predicate), () => []).push(t.object)
      pos.get_or_init((t.predicate, t.object), () => []).push(t.subject)
    }
  }
  { triples: result, spo, pos, }
}

///|
pub fn Graph::triples(self : Graph) -> Array[Triple] {
  self.triples.copy()
}

///|
pub fn Graph::size(self : Graph) -> Int {
  self.triples.length()
}

///|
pub let rdf : String = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"

///|
pub let rdfs : String = "http://www.w3.org/2000/01/rdf-schema#"

///|
pub let sh : String = "http://www.w3.org/ns/shacl#"

///|
pub let xsd : String = "http://www.w3.org/2001/XMLSchema#"

///|
pub fn literal(value : String) -> Term {
  Literal(value, xsd + "string", None)
}

///|
pub fn lang_literal(value : String, language : String) -> Term {
  Literal(value, rdf + "langString", Some(language.to_lower()))
}

///|
pub fn Graph::objects(
  self : Graph,
  subject : Term,
  predicate : String,
) -> Array[Term] {
  self.spo.get_or_default((subject, predicate), []).copy()
}

///|
pub fn Graph::subjects(
  self : Graph,
  predicate : String,
  object : Term,
) -> Array[Term] {
  self.pos.get_or_default((predicate, object), []).copy()
}

///|
pub fn Term::to_ntriples(self : Term) -> String {
  match self {
    Iri(s) => "<" + s + ">"
    Blank(s) => "_:" + s
    Literal(value, datatype, language) => {
      let quoted = Json::string(value).stringify()
      match language {
        Some(tag) => quoted + "@" + tag
        None => quoted + "^^<" + datatype + ">"
      }
    }
  }
}

///|
pub(all) enum Path {
  Predicate(String)
  Inverse(Path)
  Sequence(Array[Path])
  Alternative(Array[Path])
  ZeroOrMore(Path)
  OneOrMore(Path)
  ZeroOrOne(Path)
} derive(Eq, @debug.Debug)

///|
pub extend Path with Eq::{equal, not_equal}

///|
pub extend Path with @debug.Debug::{to_repr}

///|
/// Shape compilation errors are not validation results.
pub(all) struct Problem {
  node : Term
  predicate : String
  message : String
} derive(Eq, @debug.Debug)

///|
pub extend Problem with Eq::{equal, not_equal}

///|
pub extend Problem with @debug.Debug::{to_repr}

///|
pub(all) struct ValidationResult {
  focus_node : Term
  source_shape : Term
  component : String
  severity : Term
  path : Path?
  value : Term?
  messages : Array[Term]
  details : Array[ValidationResult]
} derive(Eq, @debug.Debug)

///|
pub extend ValidationResult with Eq::{equal, not_equal}

///|
pub extend ValidationResult with @debug.Debug::{to_repr}

///|
pub(all) struct Report {
  conforms : Bool
  results : Array[ValidationResult]
  checked : Int
  reused : Int
}

///|
/// Read dependencies include absence queries, preventing stale minCount/closed results.
priv enum Read {
  Objects(Term, String)
  Subjects(String, Term)
  Outgoing(Term)
}

///|
priv struct Reader {
  graph : Graph
  reads : Array[Read]
}

///|
fn Reader::objects(self : Reader, s : Term, p : String) -> Array[Term] {
  self.reads.push(Objects(s, p))
  self.graph.objects(s, p)
}

///|
fn Reader::subjects(self : Reader, p : String, o : Term) -> Array[Term] {
  self.reads.push(Subjects(p, o))
  self.graph.subjects(p, o)
}

///|
fn Reader::outgoing(self : Reader, s : Term) -> Array[Triple] {
  self.reads.push(Outgoing(s))
  self.graph.triples.filter(t => t.subject == s)
}

///|
fn unique(nodes : Array[Term]) -> Array[Term] {
  let seen : Map[Term, Bool] = Map([])
  let out = []
  for node in nodes {
    if !seen.contains(node) {
      seen[node] = true
      out.push(node)
    }
  }
  out
}