module Simple : sig
  type t =
    { x : int
    ; w : int
    }
  [@@deriving fields]
end = struct
  type t =
    { x : int
    ; w : int
    }
  [@@deriving fields ~fold_right]

  let%test _ = Fields.create ~x:2 ~w:4 = { x = 2; w = 4 }

  let all_even = { x = 2; w = 4 }
  let some_even = { x = 1; w = 4 }
  let none_even = { x = 1; w = 3 }
  let is_even t field = Fieldslib.Field.get field t mod 2 = 0

  let%test _ = Fields.for_all ~x:(is_even all_even) ~w:(is_even all_even) = true
  let%test _ = Fields.for_all ~x:(is_even some_even) ~w:(is_even some_even) = false
  let%test _ = Fields.exists ~x:(is_even some_even) ~w:(is_even some_even) = true
  let%test _ = Fields.exists ~x:(is_even none_even) ~w:(is_even none_even) = false

  let is_even field t n =
    assert (Fieldslib.Field.get field t = n);
    n mod 2 = 0
  ;;

  let%test _ = Fields.Direct.for_all all_even ~x:is_even ~w:is_even = true
  let%test _ = Fields.Direct.for_all some_even ~x:is_even ~w:is_even = false
  let%test _ = Fields.Direct.exists some_even ~x:is_even ~w:is_even = true
  let%test _ = Fields.Direct.exists none_even ~x:is_even ~w:is_even = false

  let t = { x = 1; w = 3 }
  let add_one t field = Fieldslib.Field.get field t + 1

  let%test _ = Fields.map ~x:(add_one t) ~w:(add_one t) = { x = 2; w = 4 }
  let%test _ = Fields.to_list ~x:(add_one t) ~w:(add_one t) = [ 2; 4 ]

  let add_one field t n =
    assert (Fieldslib.Field.get field t = n);
    n + 1
  ;;

  let%test _ = Fields.Direct.map t ~x:add_one ~w:add_one = { x = 2; w = 4 }
  let%test _ = Fields.Direct.to_list t ~x:add_one ~w:add_one = [ 2; 4 ]

  let fold_one t acc field = (Fieldslib.Field.get field t + 1) :: acc

  let%test _ = Fields.fold ~init:[] ~x:(fold_one t) ~w:(fold_one t) = [ 4; 2 ]

  let fold_one t field acc = (Fieldslib.Field.get field t + 1) :: acc

  let%test _ = Fields.fold_right ~x:(fold_one t) ~w:(fold_one t) ~init:[] = [ 2; 4 ]

  let fold_one acc field t n =
    assert (Fieldslib.Field.get field t = n);
    (n + 1) :: acc
  ;;

  let%test _ = Fields.Direct.fold t ~init:[] ~x:fold_one ~w:fold_one = [ 4; 2 ]

  let fold_one field t n acc =
    assert (Fieldslib.Field.get field t = n);
    (n + 1) :: acc
  ;;

  let%test _ = Fields.Direct.fold_right t ~x:fold_one ~w:fold_one ~init:[] = [ 2; 4 ]

  let iter_one t buf field = buf := (Fieldslib.Field.get field t + 1) :: !buf

  let%test _ =
    let buf = ref [] in
    Fields.iter ~x:(iter_one t buf) ~w:(iter_one t buf);
    !buf = [ 4; 2 ]
  ;;

  let iter_one buf field t n =
    assert (Fieldslib.Field.get field t = n);
    buf := (n + 1) :: !buf
  ;;

  let%test _ =
    let buf = ref [] in
    Fields.Direct.iter t ~x:(iter_one buf) ~w:(iter_one buf);
    !buf = [ 4; 2 ]
  ;;
end

module Rec = struct
  type a = { something1 : b }

  and b = A of a [@@deriving fields]

  let _ = something1
end

module Multiple_names = struct
  type a = { a : int }

  and b = { b : int } [@@deriving fields]

  let%test _ = b { b = 1 } = 1
  let%test _ = a { a = 1 } = 1

  let _ = Fields_of_a.a
  let _ = Fields_of_b.b
  let _ = (Fields_of_a.a : (_, _) Fieldslib.Field.t :> (_, _) Fieldslib.Field.readonly_t)
end

module Private : sig
  type t = private
    { a : int
    ; mutable b : int
    }
  [@@deriving fields]
end = struct
  type u =
    { a : int
    ; mutable b : int
    }

  type t = u = private
    { a : int
    ; mutable b : int
    }
  [@@deriving fields]

  (* let _ = Fieldslib.Field.setter Fields.a *)
end

(* let _ = Fieldslib.Field.setter Private.Fields.a *)
let _ = Private.Fields.fold
let _ = Private.Fields.a
let _ = Fieldslib.Field.name Private.Fields.a
let (_ : Private.t -> int) = Fieldslib.Field.get Private.Fields.a

let _ =
  Private.Fields.map_poly
    { Fieldslib.Field.f =
        (fun f ->
           let (_ : Private.t -> _) = Fieldslib.Field.get f in
           ())
    }
;;

module Warnings : sig
  (* could generate an unused warning but for crazy reasons, only
     when the type is private *)
  type t = private { foo : int } [@@deriving fields]

  val foo : string
end = struct
  type t = { foo : int } [@@deriving fields]

  let foo = "a"
end

module Wildcard : sig
  type _ t =
    { x : int
    ; y : string
    }
  [@@deriving fields]
end = struct
  type _ t =
    { x : int
    ; y : string
    }
  [@@deriving fields]

  let _ = x
  let _ = y
end

let%test_module "set_all_mutable_fields" =
  (module struct
    module M : sig
      type 'a t =
        { mutable a : int
        ; b : string
        ; mutable c : 'a
        }
      [@@deriving fields]
    end = struct
      type 'a t =
        { mutable a : int
        ; b : string
        ; mutable c : 'a
        }
      [@@deriving fields]
    end

    open M

    let%test_unit _ =
      let t : _ t = { a = 0; b = ""; c = nan } in
      let final_t : _ t = { a = 12; b = t.b; c = 12. } in
      Fields.Direct.set_all_mutable_fields t ~a:final_t.a ~c:final_t.c;
      assert (t = final_t)
    ;;
  end)
;;

(* Sometimes it's convenient for the type of the accumulator to change as you handle
   the individual fields. *)
module M (F1 : sig
    type t =
      { a : int
      ; b : string
      ; c : bool
      }
    [@@deriving fields]
  end) (F2 : sig
          type t =
            { a : int
            ; b : string
            }
          [@@deriving fields]
        end) =
struct
  let convert : F1.t -> F2.t =
    F1.Fields.Direct.fold
      ~init:F2.Fields.create
      ~a:(fun acc field x _ -> acc ~a:(Fieldslib.Field.get field x))
      ~b:(fun acc field x _ -> acc ~b:(Fieldslib.Field.get field x))
      ~c:(fun acc _field _x _ -> acc)
  ;;

  let construct () : F1.t =
    F1.Fields.fold
      ~init:F1.Fields.create
      ~a:(fun f _ -> f ~a:8)
      ~b:(fun f _ -> f ~b:"foo")
      ~c:(fun f _ -> f ~c:true)
  ;;
end

(* We expect no unused value, unused type, unused module warnings, as only a part of the
   generated code is used in normal circumstances. *)
module Unused_warnings : sig end = struct
  [@@@ocaml.warning "+60"]

  type t =
    { a : int
    ; b : int
    }
  [@@deriving fields]
end
