///|
/// Errors raised while constructing the continuity domain model.
pub(all) suberror ModelError {
  EmptyField(String)
  SelfDependency(String)
  DuplicateAsset(String)
  MissingAsset(String)
  DuplicateDependency(String)
  DependencyCycle(String)
  InvalidImpactScore(String)
  InvalidDuration(String)
  InvalidBia(String)
  RecoveryResourceConflict(String)
  RecoveryTargetUnmet(String)
  PlaybookValidation(String)
  StepNotFound(String)
  StepBlocked(String)
  InvalidStepTransition(String)
  DrillValidation(String)
  InvalidDrillTransition(String)
  StorageValidation(String)
  ProjectNotFound(String)
  DuplicateAuditRecord(String)
  DuplicateUser(String)
  UserNotFound(String)
  AccessDenied(String)
}

///|
/// Stable identifier for a business service.
pub(all) struct ServiceId {
  value : String
}

///|
/// Creates a service identifier after checking that it is present.
pub fn ServiceId::new(value~ : String) -> ServiceId raise ModelError {
  if value == "" {
    raise ModelError::EmptyField("service_id")
  }
  { value, }
}

///|
/// Stable identifier for a technical component.
pub(all) struct ComponentId {
  value : String
}

///|
/// Creates a component identifier after checking that it is present.
pub fn ComponentId::new(value~ : String) -> ComponentId raise ModelError {
  if value == "" {
    raise ModelError::EmptyField("component_id")
  }
  { value, }
}

///|
/// A business capability or service tracked by the continuity platform.
pub(all) struct BusinessService {
  id : ServiceId
  name : String
  owner : String
}

///|
/// Creates a business service with the fields required for ownership and reporting.
pub fn BusinessService::new(
  id~ : ServiceId,
  name~ : String,
  owner~ : String,
) -> BusinessService raise ModelError {
  if name == "" {
    raise ModelError::EmptyField("service.name")
  }
  if owner == "" {
    raise ModelError::EmptyField("service.owner")
  }
  { id, name, owner, }
}

///|
/// A technical component that supports one or more business services.
pub(all) struct TechnicalComponent {
  id : ComponentId
  name : String
  technology : String
}

///|
/// Creates a technical component with a descriptive technology field.
pub fn TechnicalComponent::new(
  id~ : ComponentId,
  name~ : String,
  technology~ : String,
) -> TechnicalComponent raise ModelError {
  if name == "" {
    raise ModelError::EmptyField("component.name")
  }
  if technology == "" {
    raise ModelError::EmptyField("component.technology")
  }
  { id, name, technology, }
}

///|
/// A dependency endpoint. The dependency graph is validated in a later stage.
pub(all) enum AssetRef {
  Service(ServiceId)
  Component(ComponentId)
}

///|
fn asset_ref_key(asset : AssetRef) -> String {
  match asset {
    Service(id) => "service:\{id.value}"
    Component(id) => "component:\{id.value}"
  }
}

///|
/// A directed dependency where `dependent` requires `dependency` first.
pub(all) struct Dependency {
  dependent : AssetRef
  dependency : AssetRef
}

///|
/// Creates a dependency edge and rejects an endpoint depending on itself.
pub fn Dependency::new(
  dependent~ : AssetRef,
  dependency~ : AssetRef,
) -> Dependency raise ModelError {
  if asset_ref_key(dependent) == asset_ref_key(dependency) {
    raise ModelError::SelfDependency(asset_ref_key(dependent))
  }
  { dependent, dependency, }
}