///|
pub const PROFILE : String = "morphir-conceptual/0.1.0-draft.1"

///|
pub const TRAITS_PROFILE : String = "morphir-traits/0.1.0-draft.1"

///|
pub const RECORD_VERSION : String = "0.1.0-draft.1"

///|
pub using @intent {
  type Id,
  type Revision,
  type Snapshot,
  type ResolvedRef,
  type SourceRef,
}

///|
#derive(Eq, Debug)
pub(all) struct Diagnostic {
  code : String
  path : String
  related_path : String?
}

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

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

///|
#derive(Debug)
pub(all) suberror ModelError {
  Invalid(Diagnostic)
}

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

///|
/// A lossless record view. Values returned to callers never alias its storage.
pub struct Record {
  priv value : @ion.IonValue
  priv fields : Map[String, @ion.IonValue]
  priv path : String
}

///|
pub fn Record::field(self : Record, name : String) -> @ion.IonValue? {
  self.fields.get(name).map(copy_value)
}

///|
pub fn Record::to_ion(self : Record) -> @ion.IonValue {
  copy_value(self.value)
}

///|
#derive(Eq, Debug)
pub(all) enum EntityKind {
  Intent
  Requirement
  Concept
  DataElement
  SourceFragment
  Declaration
  Contract
  Scenario
  Implementation
  Claim
  Evidence
  Unknown(String)
}

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

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

///|
#derive(Eq, Debug)
pub(all) enum Reference {
  Resolved(ResolvedRef)
  Unresolved(String, String, String)
}

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

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

///|
pub struct Entity {
  priv record : Record
  priv identity : Snapshot
  priv kind : EntityKind
  priv content : Record?
}

///|
pub fn Entity::identity(self : Entity) -> Snapshot {
  self.identity
}

///|
pub fn Entity::kind(self : Entity) -> EntityKind {
  self.kind
}

///|
pub fn Entity::record(self : Entity) -> Record {
  self.record
}

///|
pub fn Entity::content(self : Entity) -> @ion.IonValue {
  copy_value(self.record.fields["content"])
}

///|
pub fn Entity::content_field(self : Entity, name : String) -> @ion.IonValue? {
  match self.content {
    Some(r) => r.field(name)
    None => None
  }
}

///|
/// Each concern is independent; absence means that no fact was recorded.
pub fn Entity::concern(
  self : Entity,
  name : String,
) -> Record? raise ModelError {
  if !["elaboration", "definition", "recovery", "review", "implementation"].contains(
      name,
    ) {
    return None
  }
  self.record.fields
  .get(name)
  .map(v => record(v, self.record.path + "/" + name))
}

///|
pub(all) struct Fragment {
  anchor : Id
  text : String
}

///|
pub struct Source {
  priv record : Record
  priv id : Id
  priv fragments : Array[Fragment]
}

///|
pub fn Source::id(self : Source) -> Id {
  self.id
}

///|
pub fn Source::record(self : Source) -> Record {
  self.record
}

///|
pub fn Source::fragments(self : Source) -> Array[Fragment] {
  self.fragments.copy()
}

///|
pub struct Relationship {
  priv record : Record
  priv id : Id
  priv predicate : String
  priv subject : Reference
  priv target : Reference
}

///|
pub fn Relationship::id(self : Relationship) -> Id {
  self.id
}

///|
pub fn Relationship::predicate(self : Relationship) -> String {
  self.predicate
}

///|
pub fn Relationship::subject(self : Relationship) -> Reference {
  self.subject
}

///|
pub fn Relationship::target(self : Relationship) -> Reference {
  self.target
}

///|
pub fn Relationship::record(self : Relationship) -> Record {
  self.record
}

///|
pub struct TraitDefinition {
  priv record : Record
  priv identity : Snapshot
}

///|
pub fn TraitDefinition::identity(self : TraitDefinition) -> Snapshot {
  self.identity
}

///|
pub fn TraitDefinition::record(self : TraitDefinition) -> Record {
  self.record
}

///|
pub struct TraitApplication {
  priv record : Record
  priv identity : Snapshot
  priv definition : Reference
  priv provenance : Record
}

///|
pub fn TraitApplication::identity(self : TraitApplication) -> Snapshot {
  self.identity
}

///|
pub fn TraitApplication::record(self : TraitApplication) -> Record {
  self.record
}

///|
pub fn TraitApplication::definition(self : TraitApplication) -> Reference {
  self.definition
}

///|
pub fn TraitApplication::payload(self : TraitApplication) -> @ion.IonValue {
  copy_value(self.record.fields["payload"])
}

///|
pub fn TraitApplication::provenance(self : TraitApplication) -> Record {
  self.provenance
}

///|
/// Structural preservation is distinct from semantic validation and execution.
pub struct Graph {
  priv record : Record
  priv identity : Snapshot
  priv sources : Array[Source]
  priv entities : Array[Entity]
  priv relationships : Array[Relationship]
  priv capabilities : Array[Record]
  priv definitions : Array[TraitDefinition]
  priv applications : Array[TraitApplication]
  priv references : Array[(Reference, String)]
  priv source_refs : Array[(SourceRef, String)]
  priv records : Array[Record]
}

///|
pub fn Graph::identity(self : Graph) -> Snapshot {
  self.identity
}

///|
pub fn Graph::record(self : Graph) -> Record {
  self.record
}

///|
pub fn Graph::to_ion(self : Graph) -> @ion.IonValue {
  self.record.to_ion()
}

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

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

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

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

///|
pub fn Graph::trait_definitions(self : Graph) -> Array[TraitDefinition] {
  self.definitions.copy()
}

///|
pub fn Graph::trait_applications(self : Graph) -> Array[TraitApplication] {
  self.applications.copy()
}

///|
pub fn Graph::entity(self : Graph, id : String) -> Entity? {
  for e in self.entities {
    if e.identity.id.value() == id {
      return Some(e)
    }
  }
  None
}

///|
fn fail(
  code : String,
  path : String,
  related_path~ : String? = None,
) -> Unit raise ModelError {
  raise Invalid({ code, path, related_path, })
}

///|
fn pointer(name : String) -> String {
  name.replace(old="~", new="~0").replace(old="/", new="~1")
}

///|
fn copy_value(value : @ion.IonValue) -> @ion.IonValue {
  let kind = match value.kind() {
    Blob(bytes) =>
      @ion.IonValueKind::Blob(Bytes::makei(bytes.length(), i => bytes[i]))
    Clob(bytes) =>
      @ion.IonValueKind::Clob(Bytes::makei(bytes.length(), i => bytes[i]))
    List(xs) => @ion.IonValueKind::List(xs.map(copy_value))
    Sexp(xs) => @ion.IonValueKind::Sexp(xs.map(copy_value))
    Struct(xs) =>
      @ion.IonValueKind::Struct(
        xs.map(f => @ion.Field::from_token(f.name(), copy_value(f.value()))),
      )
    other => other
  }
  @ion.Ion(kind).with_annotations(value.annotations().copy())
}

///|
pub(all) enum TraitInterpretation {
  Descriptive
  Semantic(Array[Id])
}

///|
pub(all) enum Multiplicity {
  One
  Many
}

///|
#derive(Eq)
pub(all) enum TraitTargetKind {
  ConceptTarget
  TypeTarget
  MemberTarget
  FunctionTarget
  OperationTarget
  ServiceBindingTarget
}

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

///|
pub(all) enum TraitTarget {
  ModelTarget(Snapshot)
  ModuleTarget(Snapshot, String)
  EntityTarget(TraitTargetKind, Reference)
  ProfileTarget(TraitTargetKind, Id, Reference)
  UnknownTarget(Record)
}

///|
pub(all) enum Origin {
  Authored
  Imported
  Derived
}

///|
pub fn TraitDefinition::interpretation(
  self : TraitDefinition,
) -> TraitInterpretation raise ModelError {
  trait_interpretation(child(self.record, "interpretation"))
}

///|
pub fn TraitApplication::interpretation(
  self : TraitApplication,
) -> TraitInterpretation raise ModelError {
  trait_interpretation(child(self.record, "interpretation"))
}

///|
pub fn TraitDefinition::multiplicity(
  self : TraitDefinition,
) -> Multiplicity raise ModelError {
  match text(self.record, "multiplicity") {
    "one" => One
    "many" => Many
    _ => {
      fail("conceptual.unsupported_variant", self.record.path + "/multiplicity")
      abort("unreachable")
    }
  }
}

///|
pub fn TraitApplication::target(
  self : TraitApplication,
) -> TraitTarget raise ModelError {
  trait_target(child(self.record, "target"))
}

///|
pub fn TraitApplication::origin(
  self : TraitApplication,
) -> Origin raise ModelError {
  match text(self.provenance, "origin") {
    "authored" => Authored
    "imported" => Imported
    "derived" => Derived
    _ => {
      fail("conceptual.unsupported_variant", self.provenance.path + "/origin")
      abort("unreachable")
    }
  }
}