open Import

module Id : sig
  type t

  val make : Sexp.t -> t
  val sexp : (t, Conv.values) Conv.t
  val required_field : (t, Conv.fields) Conv.t
  val optional_field : (t option, Conv.fields) Conv.t
  val to_dyn : t -> Dyn.t
  val hash : t -> int
  val equal : t -> t -> bool
  val to_sexp : t -> Sexp.t

  include Comparable_intf.S with type key := t
end

module Version : sig
  type t = int * int

  val latest : t
  val sexp : t Conv.value
end

module Method : sig
  module Name : sig
    type t

    val to_dyn : t -> Dyn.t
    val of_string : string -> t
    val to_string : t -> string
    val sexp : t Conv.value
    val equal : t -> t -> bool
    val hash : t -> int

    module Map : Stdune.Map.S with type key = t
  end

  module Version : sig
    type t = int

    val sexp : t Conv.value

    module Set = Int.Set
    module Map = Int.Map
  end
end

module Call : sig
  (** Represents a single rpc call. Request or notification. *)

  type t =
    { method_ : Method.Name.t
    ; params : Sexp.t
    }

  val to_dyn : t -> Dyn.t
  val create : ?params:Sexp.t -> method_:Method.Name.t -> unit -> t
  val fields : (t, Conv.fields) Conv.t
end

module Response : sig
  module Error : sig
    type kind =
      | Invalid_request
      | Code_error
      | Connection_dead

    type t =
      { payload : Sexp.t option
      ; message : string
      ; kind : kind
      }

    val to_dyn : t -> Dyn.t
    val payload : t -> Sexp.t option
    val message : t -> string
    val kind : t -> kind

    exception E of t

    val create : ?payload:Sexp.t -> kind:kind -> message:string -> unit -> t
    val of_conv : Conv.error -> t
  end

  type t = (Sexp.t, Error.t) result

  val fields : (Id.t * t, Conv.fields) Conv.t
  val to_dyn : t -> Dyn.t
end

module Request : sig
  type t = Id.t * Call.t
end

module Protocol : sig
  type t = int

  val latest_version : t
  val sexp : t Conv.value
end

module Initialize : sig
  module Request : sig
    type t =
      { dune_version : int * int
      ; protocol_version : int
      ; id : Id.t
      }

    val create : id:Id.t -> t
    val of_call : Call.t -> version:int * int -> (t, Response.Error.t) result
    val dune_version : t -> int * int
    val protocol_version : t -> int
    val id : t -> Id.t
    val to_call : t -> Call.t
  end

  module Response : sig
    type t

    val create : unit -> t
    val to_response : t -> Sexp.t
    val sexp : t Conv.value
  end
end

module Version_negotiation : sig
  module Request : sig
    type t = private Menu of (Method.Name.t * Method.Version.t list) list

    val create : (Method.Name.t * Method.Version.t list) list -> t
    val sexp : t Conv.value
    val to_call : t -> Call.t
    val of_call : Call.t -> version:Version.t -> (t, Response.Error.t) result
  end

  module Response : sig
    type t = private Selected of (Method.Name.t * int) list

    val create : (Method.Name.t * int) list -> t
    val to_response : t -> Sexp.t
    val sexp : t Conv.value
  end
end

module Persistent : sig
  module In : sig
    type t =
      | New_connection
      | Packet of Csexp.t
      | Close_connection

    val sexp : t Conv.value
    val to_dyn : t -> Dyn.t
  end

  module Out : sig
    type t =
      | Packet of Sexp.t
      | Close_connection

    val sexp : t Conv.value
  end
end

module Packet : sig
  type t =
    | Request of Request.t
    | Response of (Id.t * Response.t)
    | Notification of Call.t

  val sexp : t Conv.value
end

module Decl : sig
  type 'gen t =
    { method_ : Method.Name.t
    ; key : 'gen Method.Version.Map.t Univ_map.Key.t
    }

  module Generation : sig
    type ('wire_req, 'wire_resp, 'real_req, 'real_resp) conv =
      { req : 'wire_req Conv.value
      ; resp : 'wire_resp Conv.value
      ; upgrade_req : 'wire_req -> 'real_req
      ; downgrade_req : 'real_req -> 'wire_req
      ; upgrade_resp : 'wire_resp -> 'real_resp
      ; downgrade_resp : 'real_resp -> 'wire_resp
      }

    type (_, _) t =
      | T :
          ('wire_req, 'wire_resp, 'real_req, 'real_resp) conv
          -> ('real_req, 'real_resp) t
  end

  module Request : sig
    type ('req, 'resp) gen = Method.Version.t * ('req, 'resp) Generation.t

    val make_gen
      :  req:'wire_req Conv.value
      -> resp:'wire_resp Conv.value
      -> upgrade_req:('wire_req -> 'req)
      -> downgrade_req:('req -> 'wire_req)
      -> upgrade_resp:('wire_resp -> 'resp)
      -> downgrade_resp:('resp -> 'wire_resp)
      -> version:Method.Version.t
      -> ('req, 'resp) gen

    val make_current_gen
      :  req:'req Conv.value
      -> resp:'resp Conv.value
      -> version:Method.Version.t
      -> ('req, 'resp) gen

    type ('req, 'resp) witness = ('req, 'resp) Generation.t t

    type nonrec ('req, 'resp) t =
      { decl : ('req, 'resp) witness
      ; generations : ('req, 'resp) gen list
      }

    val make
      :  method_:Method.Name.t
      -> generations:('req, 'resp) gen list
      -> ('req, 'resp) t

    val witness : ('a, 'b) t -> ('a, 'b) witness
  end

  module Notification : sig
    type 'payload gen = Method.Version.t * ('payload, unit) Generation.t

    val make_gen
      :  conv:'wire Conv.value
      -> upgrade:('wire -> 'model)
      -> downgrade:('model -> 'wire)
      -> version:Method.Version.t
      -> 'model gen

    val make_current_gen
      :  conv:'model Conv.value
      -> version:Method.Version.t
      -> 'model gen

    type 'payload witness = ('payload, unit) Generation.t t

    type nonrec 'payload t =
      { decl : 'payload witness
      ; generations : 'payload gen list
      }

    val make : method_:Method.Name.t -> generations:'payload gen list -> 'payload t
    val witness : 'a t -> 'a witness
  end

  type ('a, 'b) request = ('a, 'b) Request.t
  type 'a notification = 'a Notification.t
end
