///|
pub(all) suberror Invalid {
  Rejected(String)
}

///|
/// Constructed only from Proton's native request context, never renderer data.
pub struct Caller {
  priv page : String
}

///|
pub fn Caller::admit(
  window : String,
  origin : String,
  page : String?,
) -> Caller raise Invalid {
  guard window == "main" && origin == "app" else {
    raise Rejected("workbench.native_caller")
  }
  guard page is Some(page) && !page.is_empty() && page.length() <= 128 else {
    raise Rejected("workbench.native_page")
  }
  { page, }
}

///|
enum State {
  Idle
  Active(String, String, @request.Contract)
  Closed
}

///|
pub struct Session {
  priv mut state : State
  priv mut next : Int32
  priv executions : Bool
  priv workspace : Bool
  priv contract : @request.Contract
}

///|
pub fn Session::new(
  executions~ : Bool = false,
  workspace~ : Bool = false,
  contract~ : @request.Contract = Executions,
) -> Session {
  { state: Idle, next: 0, executions, workspace, contract, }
}

///|
fn Session::descriptor(
  self : Session,
  id : String,
  contract : @request.Contract,
) -> Json {
  let operations : Array[Json] = []
  if self.executions {
    operations.push(Json::string("executions"))
  }
  if self.workspace {
    operations.push(Json::string("workspace"))
  }
  {
    "ok": true,
    "bridgeVersion": contract.version(),
    "sessionId": id,
    "operations": Json::array(operations),
  }
}

///|
/// Bootstrap identities are not credentials. Native caller context is required
/// on every command. Operations come from qualified host configuration. The
/// private bootstrap does not claim the proposed public native profile.
pub fn Session::open(
  self : Session,
  caller : Caller,
  version : String,
) -> Json raise Invalid {
  let contract = @request.Contract::from_string(version) catch {
    _ => raise Rejected("workbench.native_version")
  }
  guard contract == @request.Executions || self.contract == contract else {
    raise Rejected("workbench.native_version")
  }
  match self.state {
    Closed => raise Rejected("workbench.native_closed")
    Active(page, id, accepted) if page == caller.page => {
      guard accepted == contract else {
        raise Rejected("workbench.native_version")
      }
      return self.descriptor(id, accepted)
    }
    _ => ()
  }
  guard self.next < 2147483647 else {
    raise Rejected("workbench.native_capacity")
  }
  self.next += 1
  let id = "proton-session-" + self.next.to_string()
  self.state = Active(caller.page, id, contract)
  self.descriptor(id, contract)
}

///|
pub fn Session::check(
  self : Session,
  caller : Caller,
  id : String,
  contract~ : @request.Contract? = None,
) -> Json raise Invalid {
  guard self.state is Active(page, current, accepted) &&
    page == caller.page &&
    current == id &&
    (contract is None || contract == Some(accepted)) else {
    raise Rejected("workbench.native_stale_session")
  }
  self.descriptor(current, accepted)
}

///|
pub fn Session::close(
  self : Session,
  caller : Caller,
  id : String,
) -> Json raise Invalid {
  ignore(self.check(caller, id))
  self.state = Idle
  { "ok": true }
}

///|
pub fn Session::revoke(self : Session) -> Unit {
  match self.state {
    Closed => ()
    _ => self.state = Idle
  }
}

///|
pub fn Session::shutdown(self : Session) -> Unit {
  self.state = Closed
}

///|
pub fn failure(error : Error) -> Json {
  let code = match error {
    Rejected(code) => code
    _ => "workbench.native_session"
  }
  { "ok": false, "code": code }
}