// The one table. What used to be three — a `match provider` for the page, a
// closure table with the credentials for the server, and a set of capability
// rows for one of the four — held together by tests that walked one looking
// for the other.

///|
pub fn Registry::of(
  apis : Array[ApiSpec],
  providers : Array[ProviderSpec],
) -> Registry {
  let by_id : Map[String, ApiSpec] = Map([])
  for api in apis {
    by_id[api.id] = api
  }
  { apis: by_id, providers }
}

///|
pub fn Registry::with_provider(
  self : Registry,
  spec : ProviderSpec,
) -> Registry {
  let providers = self.providers.filter(p => p.id != spec.id)
  providers.push(spec)
  { apis: self.apis, providers }
}

///|
pub fn Registry::with_api(self : Registry, spec : ApiSpec) -> Registry {
  let apis = self.apis.copy()
  apis[spec.id] = spec
  { apis, providers: self.providers }
}

///|
pub fn Registry::api(self : Registry, id : String) -> ApiSpec? {
  self.apis.get(id)
}

///|
pub fn Registry::provider(self : Registry, id : String) -> ProviderSpec? {
  guard self.providers.search_by(p => p.id == id) is Some(i) else {
    return None
  }
  Some(self.providers[i])
}

///|
pub fn split_label(label : String) -> (String, String) {
  guard label.find(":") is Some(i) else { return (label, "") }
  (label[0:i].to_owned(), label[i + 1:].to_owned())
}

///|
/// The row `provider` has for `model`, if it lists one.
fn ProviderSpec::listed(self : ProviderSpec, model : String) -> ModelInfo? {
  guard self.models.search_by(m => m.id == model) is Some(i) else {
    return None
  }
  Some(self.models[i])
}

///|
/// Which API answers for this model of this provider: the model's own
/// override if it has one, else the provider's.
fn ProviderSpec::api_of(self : ProviderSpec, info : ModelInfo) -> String {
  info.api.unwrap_or(self.api)
}

///|
pub fn Registry::model_info(
  self : Registry,
  provider~ : String,
  model~ : String,
) -> ModelInfo? {
  guard self.provider(provider) is Some(spec) else { return None }
  let wanted = if model == "" { spec.default_model } else { model }
  if spec.listed(wanted) is Some(info) {
    return Some(info)
  }
  // Not listed is not unknown: the page may have been offered a model by a
  // proxy whose table is newer than this one, and refusing to lower for it
  // would strand a selector the user is already looking at. `endpoint` is
  // where an unlisted model IS refused, because that is where the name
  // reaches a URL.
  match self.api(spec.api) {
    Some(api) => Some((api.caps)(wanted))
    None => Some(ModelInfo::permissive(wanted))
  }
}

///|
pub fn Registry::labels(self : Registry) -> Array[String] {
  let out : Array[String] = []
  for spec in self.providers {
    for info in spec.models {
      out.push("\{spec.id}:\{info.id}")
    }
  }
  out
}

///|
pub fn Registry::dialect_of(self : Registry, label : String) -> &Dialect? {
  let (provider, model) = split_label(label)
  guard self.provider(provider) is Some(spec) else { return None }
  guard self.model_info(provider~, model~) is Some(info) else { return None }
  guard self.api(spec.api_of(info)) is Some(api) else { return None }
  Some((api.make)(info))
}

///|
pub fn Registry::provider_for_api(
  self : Registry,
  api : String,
) -> ProviderSpec? {
  guard self.providers.search_by(p => p.api == api) is Some(i) else {
    return None
  }
  Some(self.providers[i])
}

///|
pub fn Registry::endpoint(
  self : Registry,
  provider~ : String,
  model~ : String,
  key~ : String,
) -> Result[(String, Map[String, String]), String] {
  guard self.provider(provider) is Some(spec) else {
    return Err("no provider called \{provider}")
  }
  let wanted = if model == "" { spec.default_model } else { model }
  guard spec.listed(wanted) is Some(_) else {
    return Err(
      "\{spec.id} here does not serve \{wanted} — the model list is what it does",
    )
  }
  let url = spec.endpoint.replace(old="{model}", new=wanted)
  let headers = spec.headers.copy()
  let url = match spec.auth {
    BearerHeader => {
      headers["authorization"] = "Bearer \{key}"
      url
    }
    KeyHeader(name) => {
      headers[name] = key
      url
    }
    KeyQuery(name) => {
      let sep = if url.contains("?") { "&" } else { "?" }
      "\{url}\{sep}\{name}=\{key}"
    }
    NoAuth => url
  }
  Ok((url, headers))
}