type t =
  | A
  | B
[@@deriving enumerate]

let%test _ = all = [ A; B ]

type s =
  | C
  | D
[@@deriving enumerate]

let%test _ = all_of_s = [ C; D ]

type u =
  | E
  | F of s
[@@deriving enumerate]

let%test _ = all_of_u = [ E; F C; F D ]

module V = struct
  type v =
    | G of t
    | H of u
  [@@deriving enumerate]

  let%test _ = all_of_v = [ G A; G B; H E; H (F C); H (F D) ]
end

type w = I of V.v [@@deriving enumerate]

let%test _ = all_of_w = [ I (V.G A); I (V.G B); I (V.H E); I (V.H (F C)); I (V.H (F D)) ]

type x =
  [ `A
  | `B of t
  ]
[@@deriving enumerate]

let%test _ = all_of_x = [ `A; `B A; `B B ]

(* variant with multiple arguments are not special *)
type xx = [ `A of s * s ] [@@deriving enumerate]

let%test _ = all_of_xx = [ `A (C, C); `A (D, C); `A (C, D); `A (D, D) ]

type variant_inclusion =
  [ x
  | `C
  | x
  ]
[@@deriving enumerate]

let%test _ =
  all_of_variant_inclusion
  = (all_of_x :> variant_inclusion list) @ [ `C ] @ (all_of_x :> variant_inclusion list)
;;

type y = J of t * s [@@deriving enumerate]

let%test _ = all_of_y = [ J (A, C); J (B, C); J (A, D); J (B, D) ]

type z = t * s [@@deriving enumerate]

let%test _ = all_of_z = [ A, C; B, C; A, D; B, D ]

type a =
  { foo : t
  ; bar : s
  }
[@@deriving enumerate]

let%test _ =
  all_of_a
  = [ { foo = A; bar = C }
    ; { foo = B; bar = C }
    ; { foo = A; bar = D }
    ; { foo = B; bar = D }
    ]
;;

type b = K of t * t * s [@@deriving enumerate]

let%test _ =
  all_of_b
  = [ K (A, A, C)
    ; K (B, A, C)
    ; K (A, B, C)
    ; K (B, B, C)
    ; K (A, A, D)
    ; K (B, A, D)
    ; K (A, B, D)
    ; K (B, B, D)
    ]
;;

type c = t * t * s [@@deriving enumerate]

let%test _ =
  all_of_c = [ A, A, C; B, A, C; A, B, C; B, B, C; A, A, D; B, A, D; A, B, D; B, B, D ]
;;

type d =
  { a : t
  ; b : t
  ; c : s
  }
[@@deriving enumerate]

let%test _ =
  all_of_d
  = [ { a = A; b = A; c = C }
    ; { a = B; b = A; c = C }
    ; { a = A; b = B; c = C }
    ; { a = B; b = B; c = C }
    ; { a = A; b = A; c = D }
    ; { a = B; b = A; c = D }
    ; { a = A; b = B; c = D }
    ; { a = B; b = B; c = D }
    ]
;;

type e = { foo : t } [@@deriving enumerate]

module M = struct
  type nonrec e = { bar : e } [@@deriving enumerate]
end

let%test _ = M.all_of_e = [ { M.bar = { foo = A } }; { M.bar = { foo = B } } ]

type f = L of [ `A | `B ] [@@deriving enumerate]

let%test _ = all_of_f = [ L `A; L `B ]

type g = f [@@deriving enumerate]

let%test _ = all_of_g = all_of_f

type h =
  | M
  | N

type i = h =
  | M
  | N
[@@deriving enumerate]

let%test _ = all_of_i = [ M; N ]

type 'a j = 'a [@@deriving enumerate]
type k = i j [@@deriving enumerate]

let%test _ = all_of_k = [ M; N ]

let%test _ =
  [%all: [ `A of [ `B | `C ] | `D of [ `E of [ `F ] ] ]] = [ `A `B; `A `C; `D (`E `F) ]
;;

type l =
  { baz : bool
  ; quux : unit
  }
[@@deriving enumerate]

let%test _ = all_of_l = [ { baz = false; quux = () }; { baz = true; quux = () } ]

type o = [ `A ] option [@@deriving enumerate]

let%test _ = all_of_o = [ None; Some `A ]

(* Check that enumerations are only computed once *)
type 'a count = 'a

let number_of_computations = ref 0

let all_of_count all_of_a =
  incr number_of_computations;
  all_of_a
;;

type p =
  { baz : bool count
  ; quux : unit count
  }
[@@deriving enumerate]

let%test _ = !number_of_computations = 2
let () = number_of_computations := 0

type p_nested =
  [ `A of [ `B of unit count * unit count ] * [ `C of unit count * unit count ] ]
[@@deriving enumerate]

let%test _ = !number_of_computations = 4

(* checking the lack of unused value warning *)
type 'a phantom_variable = unit [@@deriving enumerate]
type empty

let%test _ = all_of_phantom_variable ([] : empty list) = [ () ]

(* check that the coercions happen correctly when nested *)
type q = [ x | `C ] option option [@@deriving enumerate]

let%test _ =
  all_of_q
  = [ None
    ; Some None
    ; Some (Some `A)
    ; Some (Some (`B A))
    ; Some (Some (`B B))
    ; Some (Some `C)
    ]
;;

type 'tt tt =
  [ `G of 'tt
  | x
  ]
[@@deriving enumerate]

let%test _ = all_of_tt [ () ] = [ `G (); `A; `B A; `B B ]

type ir =
  | A of { foo : i }
  | B
[@@deriving enumerate]

let%test _ = all_of_ir = [ A { foo = M }; A { foo = N }; B ]

(* Tricky case where the scoping of type variable prevents generalization. If the
   constraints looked like (... :> [ a tt | `F ]) (instead of the same thing with s/a/'a/)
   where there is a fun (type a) somewhere in scope it would work, but it is simpler to
   remove variables than replace them by local types consistently. *)
type 'a nested_include_with_variable = [ 'a tt | `F ] option [@@deriving enumerate]
type +'a variance = 'a [@@deriving enumerate]

module Check_sigs = struct
  module type S1 = sig
    type t =
      | A
      | B
    [@@deriving_inline enumerate]

    include sig
      [@@@ocaml.warning "-32"]

      include Ppx_enumerate_lib.Enumerable.S with type t := t
    end
    [@@ocaml.doc "@inline"]

    [@@@end]
  end

  module type S2 = sig
    type t =
      | A
      | B

    val all : t list
  end

  let _ =
    fun (module M : S1) ->
    let module M : S2 = M in
    let module _ : S1 = M in
    ()
  ;;
end

module Check_sigs_with_params_and_variance = struct
  module type S1 = sig
    type (+'a, 'b) t =
      | A of 'a
      | B of 'b
    [@@deriving enumerate]
  end

  module type S2 = sig
    type ('a, +'b) t =
      | A of 'a
      | B of 'b

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

  let _ =
    fun (module M : S1) ->
    let module M : S2 = M in
    let module _ : S1 = M in
    ()
  ;;
end

(* if you remove the "~no_exhaustiveness_check" flag to enumerate, the compilation time
   will noticeably spike. *)
type big_record =
  { field1 : t
  ; field2 : t
  ; field3 : t
  ; field4 : t
  ; field5 : s
  ; field6 : s
  ; field7 : t
  ; field8 : t
  ; field9 : t
  ; fielda : t
  ; fieldb : t
  ; fieldc : t
  ; fieldd : t
  ; fielde : t
  ; fieldf : t
  ; fieldg : u
  ; fieldh : t
  ; fieldi : t
  ; fieldj : t
  ; fieldk : t
  ; fieldl : t
      (* (* just keep adding fields to make things worse. *)
     fieldm: t;
     fieldn: t;
     fieldo: s;
     fieldp: t;
     fieldq: s;
     fieldr: s;
     fields: u;
  *)
  }
[@@deriving enumerate ~no_exhaustiveness_check]

module Wildcard : sig
  type 'a transparent =
    | A
    | B of bool
  [@@deriving_inline enumerate]

  include sig
    [@@@ocaml.warning "-32"]

    val all_of_transparent : 'a list -> 'a transparent list
  end
  [@@ocaml.doc "@inline"]

  [@@@end]

  type 'a opaque [@@deriving_inline enumerate]

  include sig
    [@@@ocaml.warning "-32"]

    val all_of_opaque : 'a list -> 'a opaque list
  end
  [@@ocaml.doc "@inline"]

  [@@@end]
end = struct
  type _ transparent =
    | A
    | B of bool
  [@@deriving enumerate]

  let%test _ = all_of_transparent all_of_x = [ A; B false; B true ]

  type 'a opaque = 'a option [@@deriving enumerate]

  let%test _ = all_of_opaque all_of_x = [ None; Some `A; Some (`B A); Some (`B B) ]
end
