///|
fn path_intersection(left : PathScope, right : PathScope) -> PathScope? {
  if path_contains(left, right) {
    Some(right)
  } else if path_contains(right, left) {
    Some(left)
  } else {
    None
  }
}

///|
fn command_intersection(
  left : CommandScope,
  right : CommandScope,
) -> CommandScope? {
  if command_contains(left, right) {
    Some(right)
  } else if command_contains(right, left) {
    Some(left)
  } else {
    None
  }
}

///|
fn host_intersection(left : HostScope, right : HostScope) -> HostScope? {
  if host_contains(left, right) {
    Some(right)
  } else if host_contains(right, left) {
    Some(left)
  } else {
    None
  }
}

///|
fn data_class_intersection(left : DataClass, right : DataClass) -> DataClass {
  if data_class_rank(left) <= data_class_rank(right) {
    left
  } else {
    right
  }
}

///|
fn network_intersection(
  left : NetworkScope,
  right : NetworkScope,
) -> NetworkScope? {
  guard host_intersection(left.host, right.host) is Some(host) else {
    return None
  }
  let methods : Array[String] = []
  for http_method in left.methods {
    if right.methods.contains(http_method) {
      methods.push(http_method)
    }
  }
  if methods.is_empty() {
    return None
  }
  Some({
    host,
    methods,
    max_data_class: data_class_intersection(
      left.max_data_class,
      right.max_data_class,
    ),
  })
}

///|
pub fn effect_intersection(
  left : EffectScope,
  right : EffectScope,
) -> EffectScope? {
  match (left, right) {
    (FileRead(left), FileRead(right)) =>
      path_intersection(left, right).map(path => EffectScope::FileRead(path))
    (FileWrite(left), FileWrite(right)) =>
      path_intersection(left, right).map(path => EffectScope::FileWrite(path))
    (FileDelete(left), FileDelete(right)) =>
      path_intersection(left, right).map(path => EffectScope::FileDelete(path))
    (ProcessExec(left), ProcessExec(right)) =>
      command_intersection(left, right).map(command => {
        EffectScope::ProcessExec(command)
      })
    (NetworkSend(left), NetworkSend(right)) =>
      network_intersection(left, right).map(network => {
        EffectScope::NetworkSend(network)
      })
    (SecretRead(left), SecretRead(right)) =>
      if left != "" && left == right {
        Some(EffectScope::SecretRead(left))
      } else {
        None
      }
    _ => None
  }
}

///|
fn optional_int_min(left : Int?, right : Int?) -> Int? {
  match (left, right) {
    (None, value) | (value, None) => value
    (Some(left), Some(right)) => Some(if left <= right { left } else { right })
  }
}

///|
fn optional_int64_min(left : Int64?, right : Int64?) -> Int64? {
  match (left, right) {
    (None, value) | (value, None) => value
    (Some(left), Some(right)) => Some(if left <= right { left } else { right })
  }
}

///|
pub fn budget_intersection(left : Budget, right : Budget) -> Budget {
  {
    max_calls: optional_int_min(left.max_calls, right.max_calls),
    max_bytes: optional_int64_min(left.max_bytes, right.max_bytes),
    expires_at: optional_int64_min(left.expires_at, right.expires_at),
  }
}