///|
/// A configured source. Its provider interprets location and artifact keys.
pub(all) struct Repository {
  id : RepositoryId
  provider : ProviderId
  location : RepositoryLocation
} derive(Debug, Eq)

///|
pub(all) struct ArtifactRef {
  repository : RepositoryId
  key : ArtifactKey
} derive(Debug, Eq)

///|
/// Provider-specific resolved identity and the common entry tree.
/// Archive trees expose a SHA-256 digest; mutable directories have no digest.
pub(all) struct ResolvedArtifact {
  reference : ArtifactRef
  identity : ArtifactIdentity
  tree : EntryTree
}

///|
pub struct Registry {
  priv repositories : Map[RepositoryId, Repository]
  priv providers : Map[
    ProviderId,
    (Repository, ArtifactRef) -> ResolvedArtifact raise ResourceError,
  ]
}

///|
pub fn Registry::new() -> Registry {
  { repositories: Map([]), providers: Map([]), }
}

///|
pub fn Registry::register_provider(
  self : Registry,
  name : ProviderId,
  provider : (Repository, ArtifactRef) -> ResolvedArtifact raise ResourceError,
) -> Registry raise ResourceError {
  if self.providers.contains(name) {
    raise ResourceError(
      DuplicateProvider,
      Provenance::provider_registration(name),
      "Provider already registered",
    )
  }
  self.providers[name] = provider
  self
}

///|
pub fn Registry::add_repository(
  self : Registry,
  repository : Repository,
) -> Registry raise ResourceError {
  if self.repositories.contains(repository.id) {
    raise ResourceError(
      DuplicateRepository,
      Provenance::new(Location::Source(repository.location)),
      "Repository `" + repository.id.to_string() + "` already registered",
    )
  }
  self.repositories[repository.id] = repository
  self
}

///|
pub fn Registry::resolve(
  self : Registry,
  reference : ArtifactRef,
) -> ResolvedArtifact raise ResourceError {
  guard self.repositories.get(reference.repository) is Some(repository) else {
    raise ResourceError(
      UnknownRepository,
      Provenance::repository_reference(reference.repository).resolved(
        reference,
        None,
      ),
      "Unknown repository for `" + reference.key.to_string() + "`",
    )
  }
  guard self.providers.get(repository.provider) is Some(provider) else {
    raise ResourceError(
      UnknownProvider,
      Provenance::new(Location::Source(repository.location)).resolved(
        reference,
        None,
      ),
      "Unknown provider `" + repository.provider.to_string() + "`",
    )
  }
  let artifact = provider(repository, reference) catch {
    ResourceError(code, origin, message) =>
      raise ResourceError(code, origin.resolved(reference, None), message)
  }
  if artifact.reference != reference {
    raise ResourceError(
      InvalidResolution,
      Provenance::new(Location::Source(repository.location)).resolved(
        reference,
        None,
      ),
      "Provider returned a different artifact reference",
    )
  }
  {
    ..artifact,
    tree: {
      ..artifact.tree,
      provenance: artifact.tree.provenance.resolved(
        reference,
        Some(artifact.identity),
      ),
    },
  }
}

///|
pub fn ArtifactRef::to_string(self : ArtifactRef) -> String {
  self.repository.to_string() + "[" + self.key.to_string() + "]"
}