(** Implementation of the protocol used by dune rpc. Independent of IO and any
    specific rpc requests. The protocol described here is stable and is relied
    on by 3rd party clients.

    The implementation is loosely modelled on jsonrpc. It defines the following
    concepts:

    Session - An active rpc session

    Request - A unique id with a call sent by a client. A server must respond to
    every request

    Notification - A call send by a client. A server must not respond to a
    notification

    It contains hooks that make it possible to use with any custom scheduler
    that uses fibers

    The API in this version is versioned. When using this library, we expect
    that the module corresponding to a particular version is used exclusively.

    While we guarantee stability of the API, we reserve the right to:

    - Add optional arguments to functions
    - Add new fields to records
    - New variant constructors that will not cause runtime errors in existing
      user programs.

    This means that you must refrain from re-exporting any values, constructing
    any records, using any module types as functor arguments, or make non
    exhaustive matches an error to guarantee compatibility. *)

[@@@alert unstable "The API of this library is not stable and may change without notice."]
[@@@alert "-unstable"]

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

    type t

    val payload : t -> Csexp.t option
    val message : t -> string
    val kind : t -> kind

    exception E of t
  end

  type t = (Csexp.t, Error.t) result
end

module Loc : sig
  type t

  val start : t -> Lexing.position
  val stop : t -> Lexing.position
end

module Path : sig
  type t

  val dune_root : t
  val absolute : string -> t
  val relative : t -> string -> t
  val to_string_absolute : t -> string
end

module Ansi_color : sig
  module RGB8 : sig
    (** 8-bit RGB color *)
    type t

    (** [to_int t] returns the 8-bit color as an integer in the range [0, 255]. *)
    val to_int : t -> int
  end

  module RGB24 : sig
    (** 24-bit RGB color (true color) *)
    type t

    (** [red t] returns the red component of the 24-bit color [t]. *)
    val red : t -> int

    (** [green t] returns the green component of the 24-bit color [t]. *)
    val green : t -> int

    (** [blue t] returns the blue component of the 24-bit color [t]. *)
    val blue : t -> int

    (** [to_int t] returns the 24-bit color as an integer in the range [0, 0xFFFFFF].
        Each color components consists of 8 bits. *)
    val to_int : t -> int
  end

  module Style : sig
    (** Ansi Terminal Styles *)
    type t =
      [ `Fg_default
      | `Fg_black
      | `Fg_red
      | `Fg_green
      | `Fg_yellow
      | `Fg_blue
      | `Fg_magenta
      | `Fg_cyan
      | `Fg_white
      | `Fg_bright_black
      | `Fg_bright_red
      | `Fg_bright_green
      | `Fg_bright_yellow
      | `Fg_bright_blue
      | `Fg_bright_magenta
      | `Fg_bright_cyan
      | `Fg_bright_white
      | `Fg_8_bit_color of RGB8.t
      | `Fg_24_bit_color of RGB24.t
      | `Bg_default
      | `Bg_black
      | `Bg_red
      | `Bg_green
      | `Bg_yellow
      | `Bg_blue
      | `Bg_magenta
      | `Bg_cyan
      | `Bg_white
      | `Bg_bright_black
      | `Bg_bright_red
      | `Bg_bright_green
      | `Bg_bright_yellow
      | `Bg_bright_blue
      | `Bg_bright_magenta
      | `Bg_bright_cyan
      | `Bg_bright_white
      | `Bg_8_bit_color of RGB8.t
      | `Bg_24_bit_color of RGB24.t
      | `Bold
      | `Dim
      | `Italic
      | `Underline
      ]
  end
end

module User_message : sig
  (** User Message Styles *)
  module Style : sig
    type t =
      | Loc
      | Error
      | Warning
      | Kwd
      | Id
      | Prompt
      | Hint
      | Details
      | Ok
      | Debug
      | Success
      | Ansi_styles of Ansi_color.Style.t list
  end
end

module Target : sig
  type t =
    | Path of string
    | Alias of string
    | Library of string
    | Executables of string list
    | Preprocess of string list
    | Loc of Loc.t
end

module Diagnostic : sig
  type severity =
    | Error
    | Warning

  module Promotion : sig
    type t

    val in_build : t -> string
    val in_source : t -> string
  end

  module Id : sig
    type t

    val compare : t -> t -> Ordering.t
    val hash : t -> int
    val create : int -> t
  end

  module Related : sig
    type t

    val loc : t -> Loc.t
    val message : t -> unit Pp.t
    val message_with_style : t -> User_message.Style.t Pp.t
  end

  type t

  val related : t -> Related.t list
  val loc : t -> Loc.t option
  val id : t -> Id.t
  val message : t -> unit Pp.t
  val message_with_style : t -> User_message.Style.t Pp.t
  val severity : t -> severity option
  val promotion : t -> Promotion.t list

  (* The list of targets is ordered such that the first element is the
     immediate ("innermost") target being built when the error was
     encountered, which was required by the next element, and so on. *)
  val targets : t -> Target.t list

  (* The directory from which the action producing the error was run. This is
     often, but not always, the directory of the first target in [targets].
     This path of this directory is absolute.

     If this is [None], then the error does not have an associated error (for
     example, if your opam installation is too old). *)
  val directory : t -> string option

  module Event : sig
    type nonrec t =
      | Add of t
      | Remove of t
  end
end

module Progress : sig
  type t =
    | Waiting
    | In_progress of
        { complete : int
        ; remaining : int
        ; failed : int
        }
    | Failed
    | Interrupted
    | Success
end

module Job : sig
  module Id : sig
    type t

    val compare : t -> t -> Ordering.t
    val hash : t -> int
  end

  type t

  val id : t -> Id.t
  val description : t -> unit Pp.t
  val started_at : t -> float

  module Event : sig
    type nonrec t =
      | Start of t
      | Stop of Id.t
  end
end

module Sub : sig
  type 'a t

  val progress : Progress.t t
  val diagnostic : Diagnostic.Event.t list t
end

module Message : sig
  type t

  val payload : t -> Csexp.t option
  val message : t -> string
end

(** A [Version_error] is returned on the client-side when a request or
    notification is determined to be invalid due to version negotiation (no
    known method or no common version). *)
module Version_error : sig
  type t

  val payload : t -> Csexp.t option
  val message : t -> string

  exception E of t
end

module Notification : sig
  type 'a t

  (** Request dune to shutdown. The current build job will be cancelled. *)
  val shutdown : unit t
end

module Request : sig
  type ('a, 'b) t

  val ping : (unit, unit) t
  val diagnostics : (unit, Diagnostic.t list) t

  (** format a [dune], [dune-project], or a [dune-workspace] file. The full
      path to the file is necessary so that dune knows the formatting options
      for the project this file is in *)
  val format_dune_file : (Path.t * [ `Contents of string ], string) t

  (** Promote a file. *)
  val promote : (Path.t, unit) t

  (** Returns the location of the build directory for the current build. *)
  val build_dir : (unit, Path.t) t

  module Id : sig
    (** Id's for requests.

        Id's are permitted to be arbitrary s-expressions to allow users pick
        descriptive tokens to ease debugging. *)

    type t

    val make : Csexp.t -> t
  end

  module Initialize : sig
    type t

    val create : id:Id.t -> t
  end
end

module Client : sig
  module type S = sig
    (** Rpc client *)

    type t
    type 'a fiber
    type chan

    module Handler : sig
      type t

      val create
        :  ?log:(Message.t -> unit fiber)
        -> ?abort:(Message.t -> unit fiber)
             (** If [abort] is called, the server has terminated the
                 connection due to a protocol error. This should never be
                 called unless there's a server side bug. *)
        -> unit
        -> t
    end

    (** Individual RPC procedures are versioned beyond the larger API version.
        At session startup, the server and client exchange version information
        for each method ("negotiation"), setting on a common version for each
        (if possible) to produce a "version menu".

        To initiate a method, then, that method must be looked up in the
        version menu to determine the correct protocol for this session. This
        module stages this pattern to share the lookup for all calls to the
        same procedure.

        For lower-level design details, see [doc/dev/rpc-versioning.md] in the
        main dune repository. *)
    module Versioned : sig
      type 'a notification
      type ('a, 'b) request

      (** [prepare_request client r] checks the request [r] against the
          negotiated version menu, giving a versioned request as a result.

          This function does not initiate any communication with the server.
          However, as this function must check the version menu, it cannot
          complete until after version negotiation, and so returns a [fiber]. *)
      val prepare_request
        :  t
        -> ('a, 'b) Request.t
        -> (('a, 'b) request, Version_error.t) result fiber

      (** See [prepare_request]. *)
      val prepare_notification
        :  t
        -> 'a Notification.t
        -> ('a notification, Version_error.t) result fiber
    end

    (** [request ?id client decl req] send a request [req] specified by [decl]
        to [client]. If [id] is [None], it will be automatically generated. *)
    val request
      :  ?id:Request.Id.t
      -> t
      -> ('a, 'b) Versioned.request
      -> 'a
      -> ('b, Response.Error.t) result fiber

    val notification : t -> 'a Versioned.notification -> 'a -> unit fiber

    (** [disconnected client] produces a fiber that only becomes determined
        when the session is ended from the server side (such as if the build
        server is killed entirely). *)
    val disconnected : t -> unit fiber

    module Stream : sig
      (** Control for a polling loop *)

      type 'a t

      (** [cancel t] notify the server that we are stopping our polling loop.
          It is an error to call [next] after [cancel] *)
      val cancel : _ t -> unit fiber

      (** [next t] poll for the next value. It is an error to call [next]
          again until the previous [next] terminated. If [next] returns
          [None], subsequent calls to [next] is forbidden. *)
      val next : 'a t -> 'a option fiber
    end

    (** [poll client sub] Initialize a polling loop for [sub] *)
    val poll
      :  ?id:Request.Id.t
      -> t
      -> 'a Sub.t
      -> ('a Stream.t, Version_error.t) result fiber

    module Batch : sig
      type client := t
      type t

      val create : client -> t

      val request
        :  ?id:Request.Id.t
        -> t
        -> ('a, 'b) Versioned.request
        -> 'a
        -> ('b, Response.Error.t) result fiber

      val notification : t -> 'a Versioned.notification -> 'a -> unit
      val submit : t -> unit fiber
    end

    (** [connect ?on_handler session init ~f] connect to [session], initialize
        with [init] and call [f] once the client is initialized. [handler] is
        called for some notifications sent to [session] *)
    val connect
      :  ?handler:Handler.t
      -> chan
      -> Request.Initialize.t
      -> f:(t -> 'a fiber)
      -> 'a fiber
  end

  (** Functor to create a client implementation *)
  module Make
      (Fiber : sig
         type 'a t

         val return : 'a -> 'a t
         val fork_and_join_unit : (unit -> unit t) -> (unit -> 'a t) -> 'a t
         val parallel_iter : (unit -> 'a option t) -> f:('a -> unit t) -> unit t
         val finalize : (unit -> 'a t) -> finally:(unit -> unit t) -> 'a t
         val collect_errors : (unit -> 'a t) -> ('a, exn list) result t

         module O : sig
           val ( let* ) : 'a t -> ('a -> 'b t) -> 'b t
           val ( let+ ) : 'a t -> ('a -> 'b) -> 'b t
         end

         module Ivar : sig
           type 'a fiber := 'a t
           type 'a t

           val create : unit -> 'a t
           val read : 'a t -> 'a fiber
           val fill : 'a t -> 'a -> unit fiber
         end
       end)
      (Chan : sig
         type t

         (* [write t x] writes the s-expression*)
         val write : t -> Csexp.t list -> unit Fiber.t

         (*  closes the session  *)
         val close : t -> unit Fiber.t

         (* [read t] attempts to read from [t]. If an s-expression is read, it is
            returned as [Some sexp], otherwise [None] is returned and the session
            is closed. *)
         val read : t -> Csexp.t option Fiber.t
       end) : S with type 'a fiber := 'a Fiber.t and type chan := Chan.t
end

module Where : sig
  (** represents the address where a dune rpc instance might be listening *)

  type t =
    [ `Unix of string
    | `Ip of [ `Host of string ] * [ `Port of int ]
    ]

  type error = Invalid_where of string

  exception E of error

  module type S = sig
    type 'a fiber

    val get
      :  env:(string -> string option)
      -> build_dir:string
      -> (t option, exn) result fiber

    val default : ?win32:bool -> build_dir:string -> unit -> t
  end

  (** obtain the address from the build directory and environment *)
  module Make
      (Fiber : sig
         type 'a t

         val return : 'a -> 'a t

         module O : sig
           val ( let* ) : 'a t -> ('a -> 'b t) -> 'b t
           val ( let+ ) : 'a t -> ('a -> 'b) -> 'b t
         end
       end)
      (_ : sig
         val read_file : string -> (string, exn) result Fiber.t

         val analyze_path
           :  string
           -> ([ `Unix_socket | `Normal_file | `Other ], exn) result Fiber.t
       end) : S with type 'a fiber := 'a Fiber.t
end

module Registry : sig
  (** The registry is where all running instances of dune rpc are stored.

      It's used by clients to determine which dune rpc instance corresponds to
      the workspace they're trying to edit. *)

  module Dune : sig
    (** a registered instance of dune. supposedly running and listening to rpc
        connections *)

    type t

    val to_dyn : t -> Dyn.t
    val compare : t -> t -> Ordering.t
    val pid : t -> int
    val where : t -> Where.t
    val root : t -> string
  end

  module Config : sig
    (** The registry directory is located using xdg *)

    type t

    val create : Xdg.t -> t
    val watch_dir : t -> string
  end

  type t

  val create : Config.t -> t

  (** currently detected running instances *)
  val current : t -> Dune.t list

  module Refresh : sig
    (** the result of polling the registry *)

    type t

    val added : t -> Dune.t list
    val removed : t -> Dune.t list
    val errored : t -> (string * exn) list
  end

  (** we can poll the registry efficiently using the following functor *)
  module Poll
      (Fiber : sig
         type 'a t

         val return : 'a -> 'a t
         val parallel_map : 'a list -> f:('a -> 'b t) -> 'b list t

         module O : sig
           val ( let* ) : 'a t -> ('a -> 'b t) -> 'b t
           val ( let+ ) : 'a t -> ('a -> 'b) -> 'b t
         end
       end)
      (_ : sig
         val scandir : string -> (string list, exn) result Fiber.t
         val stat : string -> ([ `Mtime of float ], exn) result Fiber.t
         val read_file : string -> (string, exn) result Fiber.t
       end) : sig
    val poll : t -> (Refresh.t, exn) result Fiber.t
  end
end
