src/gleeps/stdlib/dynamic/decode.gleam

//// The `Dynamic` type is used to represent dynamically typed data. That is, data
//// that we don't know the precise type of yet, so we need to introspect the data to
//// see if it is of the desired type before we can use it. Typically data like this
//// would come from user input or from untyped languages such as Erlang or JavaScript.
////
//// This module provides the `Decoder` type and associated functions, which provides
//// a type-safe and composable way to convert dynamic data into some desired type,
//// or into errors if the data doesn't have the desired structure.
////
//// The `Decoder` type is generic and has 1 type parameter, which is the type that
//// it attempts to decode. A `Decoder(String)` can be used to decode strings, and a
//// `Decoder(Option(Int))` can be used to decode `Option(Int)`s
////
//// Decoders work using _runtime reflection_ and the data structures of the target
//// platform. Differences between Erlang and JavaScript data structures may impact
//// your decoders, so it is important to test your decoders on all supported
//// platforms.
////
//// The decoding technique used by this module was inspired by Juraj Petráš'
//// [Toy](https://github.com/Hackder/toy), Go's `encoding/json`, and Elm's
//// `Json.Decode`. Thank you to them!
////
//// # Generating decoders
////
//// The language server has the "generate dynamic decoder" code action, which
//// will generate a decoder function when run on a custom type definition.
//// This generated decoder function can be a convenient shortcut when creating
//// your own decoders, and you can edit the generated function to suit your needs.
////
//// # Examples
////
//// Dynamic data may come from various sources and so many different syntaxes could
//// be used to describe or construct them. In these examples a pseudocode
//// syntax is used to describe the data.
////
//// ## Simple types
////
//// This module defines decoders for simple data types such as [`string`](#string),
//// [`int`](#int), [`float`](#float), [`bit_array`](#bit_array), and [`bool`](#bool).
////
//// ```gleam
//// // Data:
//// // "Hello, Joe!"
////
//// let result = decode.run(data, decode.string)
//// assert result == Ok("Hello, Joe!")
//// ```
////
//// ## Lists
////
//// The [`list`](#list) decoder decodes `List`s. To use it you must construct it by
//// passing in another decoder into the `list` function, which is the decoder that
//// is to be used for the elements of the list, type checking both the list and its
//// elements.
////
//// ```gleam
//// // Data:
//// // [1, 2, 3, 4]
////
//// let result = decode.run(data, decode.list(decode.int))
//// assert result == Ok([1, 2, 3, 4])
//// ```
////
//// On Erlang this decoder can decode from lists, and on JavaScript it can
//// decode from lists as well as JavaScript arrays.
////
//// ## Options
////
//// The [`optional`](#optional) decoder is used to decode values that may or may not
//// be present. In other environments these might be called "nullable" values.
////
//// Like the `list` decoder, the `optional` decoder takes another decoder,
//// which is used to decode the value if it is present.
////
//// ```gleam
//// // Data:
//// // 12.45
////
//// let result = decode.run(data, decode.optional(decode.float))
//// assert result == Ok(option.Some(12.45))
//// ```
//// ```gleam
//// // Data:
//// // null
////
//// let result = decode.run(data, decode.optional(decode.int))
//// assert result == Ok(option.None)
//// ```
////
//// This decoder knows how to handle multiple different runtime representations of
//// absent values, including `Nil`, `None`, `null`, and `undefined`.
////
//// ## Dicts
////
//// The [`dict`](#dict) decoder decodes `Dicts` and contains two other decoders, one
//// for the keys, one for the values.
////
//// ```gleam
//// // Data:
//// // { "Lucy" -> 10, "Nubi" -> 20 }
////
//// let result = decode.run(data, decode.dict(decode.string, decode.int))
//// assert result == Ok(dict.from_list([
////   #("Lucy", 10),
////   #("Nubi", 20),
//// ]))
//// ```
////
//// ## Indexing objects
////
//// The [`at`](#at) decoder can be used to decode a value that is nested within
//// key-value containers such as Gleam dicts, Erlang maps, or JavaScript objects.
////
//// ```gleam
//// // Data:
//// // { "one" -> { "two" -> 123 } }
////
//// let result = decode.run(data, decode.at(["one", "two"], decode.int))
//// assert result == Ok(123)
//// ```
////
//// ## Indexing arrays
////
//// If you use ints as keys then the [`at`](#at) decoder can be used to index into
//// array-like containers such as Gleam or Erlang tuples, or JavaScript arrays.
////
//// ```gleam
//// // Data:
//// // ["one", "two", "three"]
////
//// let result = decode.run(data, decode.at([1], decode.string))
//// assert result == Ok("two")
//// ```
////
//// ## Records
////
//// Decoding records from dynamic data is more complex and requires combining a
//// decoder for each field and a special constructor that builds your records with
//// the decoded field values.
////
//// ```gleam
//// // Data:
//// // {
//// //   "score" -> 180,
//// //   "name" -> "Mel Smith",
//// //   "is-admin" -> false,
//// //   "enrolled" -> true,
//// //   "colour" -> "Red",
//// // }
////
//// let decoder = {
////   use name <- decode.field("name", decode.string)
////   use score <- decode.field("score", decode.int)
////   use colour <- decode.field("colour", decode.string)
////   use enrolled <- decode.field("enrolled", decode.bool)
////   decode.success(Player(name:, score:, colour:, enrolled:))
//// }
////
//// let result = decode.run(data, decoder)
//// assert result == Ok(Player("Mel Smith", 180, "Red", True))
//// ```
////
//// ## Enum variants
////
//// Imagine you have a custom type where all the variants do not contain any values.
////
//// ```gleam
//// pub type PocketMonsterType {
////   Fire
////   Water
////   Grass
////   Electric
//// }
//// ```
////
//// You might choose to encode these variants as strings, `"fire"` for `Fire`,
//// `"water"` for `Water`, and so on. To decode them you'll need to decode the dynamic
//// data as a string, but then you'll need to decode it further still as not all
//// strings are valid values for the enum. This can be done with the `then`
//// function, which enables running a second decoder after the first one
//// succeeds.
////
//// ```gleam
//// let decoder = {
////   use decoded_string <- decode.then(decode.string)
////   case decoded_string {
////     // Return succeeding decoders for valid strings
////     "fire" -> decode.success(Fire)
////     "water" -> decode.success(Water)
////     "grass" -> decode.success(Grass)
////     "electric" -> decode.success(Electric)
////     // Return a failing decoder for any other strings
////     _ -> decode.failure(Fire, expected: "PocketMonsterType")
////   }
//// }
////
//// let result = decode.run(dynamic.string("water"), decoder)
//// assert result == Ok(Water)
////
//// let result = decode.run(dynamic.string("wobble"), decoder)
//// assert result == Error([DecodeError("PocketMonsterType", "String", [])])
//// ```
////
//// ## Record variants
////
//// Decoding type variants that contain other values is done by combining the
//// techniques from the "enum variants" and "records" examples. Imagine you have
//// this custom type that you want to decode:
////
//// ```gleam
//// pub type PocketMonsterPerson {
////   Trainer(name: String, badge_count: Int)
////   GymLeader(name: String, speciality: PocketMonsterType)
//// }
//// ```
//// And you would like to be able to decode these from dynamic data like this:
//// ```erlang
//// {
////   "type" -> "trainer",
////   "name" -> "Ash",
////   "badge-count" -> 1,
//// }
//// ```
//// ```erlang
//// {
////   "type" -> "gym-leader",
////   "name" -> "Misty",
////   "speciality" -> "water",
//// }
//// ```
////
//// Notice how both documents have a `"type"` field, which is used to indicate which
//// variant the data is for.
////
//// First, define decoders for each of the variants:
////
//// ```gleam
//// let trainer_decoder = {
////   use name <- decode.field("name", decode.string)
////   use badge_count <- decode.field("badge-count", decode.int)
////   decode.success(Trainer(name, badge_count))
//// }
////
//// let gym_leader_decoder = {
////   use name <- decode.field("name", decode.string)
////   use speciality <- decode.field("speciality", pocket_monster_type_decoder)
////   decode.success(GymLeader(name, speciality))
//// }
//// ```
////
//// A third decoder can be used to extract and decode the `"type"` field, and the
//// expression can evaluate to whichever decoder is suitable for the document.
////
//// ```gleam
//// // Data:
//// // {
//// //   "type" -> "gym-leader",
//// //   "name" -> "Misty",
//// //   "speciality" -> "water",
//// // }
////
//// let decoder = {
////   use tag <- decode.field("type", decode.string)
////   case tag {
////     "gym-leader" -> gym_leader_decoder
////     _ -> trainer_decoder
////   }
//// }
////
//// let result = decode.run(data, decoder)
//// assert result == Ok(GymLeader("Misty", Water))
//// ```

import gleeps/stdlib/bit_array
import gleeps/stdlib/dict.{type Dict}
import gleeps/stdlib/dynamic
import gleeps/stdlib/float
import gleeps/stdlib/int
import gleeps/stdlib/list
import gleeps/stdlib/option.{type Option, None, Some}

/// `Dynamic` data is data that we don't know the type of yet, originating from
/// external untyped systems.
///
/// You should never be converting your well typed data to dynamic data.
///
pub type Dynamic =
  dynamic.Dynamic

/// Error returned when unexpected data is encountered
///
pub type DecodeError {
  DecodeError(expected: String, found: String, path: List(String))
}

/// A decoder is a value that can be used to turn dynamically typed `Dynamic`
/// data into typed data using the `run` function.
///
/// Several smaller decoders can be combined to make larger decoders using
/// functions such as `list` and `field`.
///
pub opaque type Decoder(t) {
  Decoder(function: fn(Dynamic) -> #(t, List(DecodeError)))
}

/// The same as [`field`](#field), except taking a path to the value rather
/// than a field name.
///
/// This function will index into dictionaries with any key type, and if the key is
/// an int then it'll also index into Erlang tuples and JavaScript arrays, and
/// the first eight elements of Gleam lists.
///
/// ## Examples
///
/// ```gleam
/// let data = dynamic.properties([
///   #(dynamic.string("data"), dynamic.properties([
///     #(dynamic.string("email"), dynamic.string("lucy@example.com")),
///     #(dynamic.string("name"), dynamic.string("Lucy")),
///   ])
/// ])
///
/// let decoder = {
///   use name <- decode.subfield(["data", "name"], decode.string)
///   use email <- decode.subfield(["data", "email"], decode.string)
///   decode.success(SignUp(name: name, email: email))
/// }
/// let result = decode.run(data, decoder)
/// assert result == Ok(SignUp(name: "Lucy", email: "lucy@example.com"))
/// ```
///
pub fn subfield(
  field_path: List(name),
  field_decoder: Decoder(t),
  next: fn(t) -> Decoder(final),
) -> Decoder(final) {
  Decoder(function: fn(data) {
    let #(out, errors1) =
      index(field_path, [], field_decoder.function, data, fn(data, position) {
        let #(default, _) = field_decoder.function(data)
        #(default, [DecodeError("Field", "Nothing", [])])
        |> push_path(list.reverse(position))
      })
    let #(out, errors2) = next(out).function(data)
    #(out, list.append(errors1, errors2))
  })
}

/// Run a decoder on a `Dynamic` value, decoding the value if it is of the
/// desired type, or returning errors.
///
/// ## Examples
///
/// ```gleam
/// let decoder = {
///   use name <- decode.field("name", decode.string)
///   use email <- decode.field("email", decode.string)
///   decode.success(SignUp(name: name, email: email))
/// }
///
/// decode.run(data, decoder)
/// ```
///
pub fn run(data: Dynamic, decoder: Decoder(t)) -> Result(t, List(DecodeError)) {
  let #(maybe_invalid_data, errors) = decoder.function(data)
  case errors {
    [] -> Ok(maybe_invalid_data)
    [_, ..] -> Error(errors)
  }
}

/// A decoder that decodes a value that is nested within other values. For
/// example, decoding a value that is within some deeply nested JSON objects.
///
/// This function will index into dictionaries with any key type, and if the key is
/// an int then it'll also index into Erlang tuples and JavaScript arrays, and
/// the first eight elements of Gleam lists.
///
/// ## Examples
///
/// ```gleam
/// let decoder = decode.at(["one", "two"], decode.int)
///
/// let data = dynamic.properties([
///   #(dynamic.string("one"), dynamic.properties([
///     #(dynamic.string("two"), dynamic.int(1000)),
///   ]),
/// ])
///
/// assert decode.run(data, decoder) == Ok(1000)
/// ```
///
/// ```gleam
/// assert dynamic.nil()
///   |> decode.run(decode.optional(decode.int))
///   == Ok(option.None)
/// ```
///
pub fn at(path: List(segment), inner: Decoder(a)) -> Decoder(a) {
  Decoder(function: fn(data) {
    index(path, [], inner.function, data, fn(data, position) {
      let #(default, _) = inner.function(data)
      #(default, [DecodeError("Field", "Nothing", [])])
      |> push_path(list.reverse(position))
    })
  })
}

fn index(
  path: List(a),
  position: List(a),
  inner: fn(Dynamic) -> #(b, List(DecodeError)),
  data: Dynamic,
  handle_miss: fn(Dynamic, List(a)) -> #(b, List(DecodeError)),
) -> #(b, List(DecodeError)) {
  case path {
    [] -> {
      data
      |> inner
      |> push_path(list.reverse(position))
    }

    [key, ..path] -> {
      case bare_index(data, key) {
        Ok(Some(data)) -> {
          index(path, [key, ..position], inner, data, handle_miss)
        }
        Ok(None) -> {
          handle_miss(data, [key, ..position])
        }
        Error(kind) -> {
          let #(default, _) = inner(data)
          #(default, [DecodeError(kind, dynamic.classify(data), [])])
          |> push_path(list.reverse(position))
        }
      }
    }
  }
}

@external(erlang, "gleam_stdlib", "index")
@external(javascript, "../../gleam_stdlib.mjs", "index")
fn bare_index(data: Dynamic, key: anything) -> Result(Option(Dynamic), String)

fn push_path(
  layer: #(t, List(DecodeError)),
  path: List(key),
) -> #(t, List(DecodeError)) {
  let path = list.map(path, fn(key) { key |> cast |> path_segment_to_string })
  let errors =
    list.map(layer.1, fn(error) {
      DecodeError(..error, path: list.append(path, error.path))
    })
  #(layer.0, errors)
}

fn path_segment_to_string(key: Dynamic) -> String {
  let decoder =
    one_of(string, [
      int |> map(int.to_string),
      float |> map(float.to_string),
    ])
  case run(key, decoder) {
    Ok(key) -> key
    Error(_) -> "<" <> dynamic.classify(key) <> ">"
  }
}

/// Finalise a decoder having successfully extracted a value.
///
/// ## Examples
///
/// ```gleam
/// let data = dynamic.properties([
///   #(dynamic.string("email"), dynamic.string("lucy@example.com")),
///   #(dynamic.string("name"), dynamic.string("Lucy")),
/// ])
///
/// let decoder = {
///   use name <- decode.field("name", string)
///   use email <- decode.field("email", string)
///   decode.success(SignUp(name: name, email: email))
/// }
///
/// let result = decode.run(data, decoder)
/// assert result == Ok(SignUp(name: "Lucy", email: "lucy@example.com"))
/// ```
///
pub fn success(data: t) -> Decoder(t) {
  Decoder(function: fn(_) { #(data, []) })
}

/// Construct a decode error for some unexpected dynamic data.
///
pub fn decode_error(
  expected expected: String,
  found found: Dynamic,
) -> List(DecodeError) {
  [DecodeError(expected: expected, found: dynamic.classify(found), path: [])]
}

/// Run a decoder on a field of a `Dynamic` value, decoding the value if it is
/// of the desired type, or returning errors. An error is returned if there is
/// no field for the specified key.
///
/// This function will index into dictionaries with any key type, and if the key is
/// an int then it'll also index into Erlang tuples and JavaScript arrays, and
/// the first eight elements of Gleam lists.
///
/// ## Examples
///
/// ```gleam
/// let data = dynamic.properties([
///   #(dynamic.string("email"), dynamic.string("lucy@example.com")),
///   #(dynamic.string("name"), dynamic.string("Lucy")),
/// ])
///
/// let decoder = {
///   use name <- decode.field("name", string)
///   use email <- decode.field("email", string)
///   decode.success(SignUp(name: name, email: email))
/// }
///
/// let result = decode.run(data, decoder)
/// assert result == Ok(SignUp(name: "Lucy", email: "lucy@example.com"))
/// ```
///
/// If you wish to decode a value that is more deeply nested within the dynamic
/// data, see [`subfield`](#subfield) and [`at`](#at).
///
/// If you wish to return a default in the event that a field is not present,
/// see [`optional_field`](#optional_field) and / [`optionally_at`](#optionally_at).
///
pub fn field(
  field_name: name,
  field_decoder: Decoder(t),
  next: fn(t) -> Decoder(final),
) -> Decoder(final) {
  subfield([field_name], field_decoder, next)
}

/// Run a decoder on a field of a `Dynamic` value, decoding the value if it is
/// of the desired type, or returning errors. The given default value is
/// returned if there is no field for the specified key.
///
/// This function will index into dictionaries with any key type, and if the key is
/// an int then it'll also index into Erlang tuples and JavaScript arrays, and
/// the first eight elements of Gleam lists.
///
/// ## Examples
///
/// ```gleam
/// let data = dynamic.properties([
///   #(dynamic.string("name"), dynamic.string("Lucy")),
/// ])
///
/// let decoder = {
///   use name <- decode.field("name", string)
///   use email <- decode.optional_field("email", "n/a", string)
///   decode.success(SignUp(name: name, email: email))
/// }
///
/// let result = decode.run(data, decoder)
/// assert result == Ok(SignUp(name: "Lucy", email: "n/a"))
/// ```
///
pub fn optional_field(
  key: name,
  default: t,
  field_decoder: Decoder(t),
  next: fn(t) -> Decoder(final),
) -> Decoder(final) {
  Decoder(function: fn(data) {
    let #(out, errors1) =
      case bare_index(data, key) {
        Ok(Some(data)) -> field_decoder.function(data)
        Ok(None) -> #(default, [])
        Error(kind) -> #(default, [
          DecodeError(kind, dynamic.classify(data), []),
        ])
      }
      |> push_path([key])
    let #(out, errors2) = next(out).function(data)
    #(out, list.append(errors1, errors2))
  })
}

/// A decoder that decodes a value that is nested within other values. For
/// example, decoding a value that is within some deeply nested JSON objects.
///
/// This function will index into dictionaries with any key type, and if the key is
/// an int then it'll also index into Erlang tuples and JavaScript arrays, and
/// the first eight elements of Gleam lists.
///
/// ## Examples
///
/// ```gleam
/// let decoder = decode.optionally_at(["one", "two"], 100, decode.int)
///
/// let data = dynamic.properties([
///   #(dynamic.string("one"), dynamic.properties([])),
/// ])
///
/// assert decode.run(data, decoder) == Ok(100)
/// ```
///
pub fn optionally_at(
  path: List(segment),
  default: a,
  inner: Decoder(a),
) -> Decoder(a) {
  Decoder(function: fn(data) {
    index(path, [], inner.function, data, fn(_, _) { #(default, []) })
  })
}

fn run_dynamic_function(
  data: Dynamic,
  name: String,
  f: fn(Dynamic) -> Result(t, t),
) -> #(t, List(DecodeError)) {
  case f(data) {
    Ok(data) -> #(data, [])
    Error(placeholder) -> #(placeholder, [
      DecodeError(name, dynamic.classify(data), []),
    ])
  }
}

/// A decoder that decodes `String` values.
///
/// ## Examples
///
/// ```gleam
/// let result = decode.run(dynamic.string("Hello!"), decode.string)
/// assert result == Ok("Hello!")
/// ```
///
pub const string: Decoder(String) = Decoder(decode_string)

fn decode_string(data: Dynamic) -> #(String, List(DecodeError)) {
  run_dynamic_function(data, "String", dynamic_string)
}

@external(javascript, "../../gleam_stdlib.mjs", "string")
fn dynamic_string(from data: Dynamic) -> Result(String, String) {
  case dynamic_bit_array(data) {
    Ok(data) ->
      case bit_array.to_string(data) {
        Ok(string) -> Ok(string)
        Error(_) -> Error("")
      }
    Error(_) -> Error("")
  }
}

/// A decoder that decodes `Bool` values.
///
/// ## Examples
///
/// ```gleam
/// let result = decode.run(dynamic.bool(True), decode.bool)
/// assert result == Ok(True)
/// ```
///
pub const bool: Decoder(Bool) = Decoder(decode_bool)

fn decode_bool(data: Dynamic) -> #(Bool, List(DecodeError)) {
  case cast(True) == data {
    True -> #(True, [])
    False ->
      case cast(False) == data {
        True -> #(False, [])
        False -> #(False, decode_error("Bool", data))
      }
  }
}

/// A decoder that decodes `Int` values.
///
/// This will not coerse float values into int values, so on platforms with
/// distinct runtime int and float types (Erlang, not JavaScript) it will fail,
/// even if the float is a whole number (e.g. 1.0).
///
/// If you want to decode both ints and floats you may want to use the `one_of`
/// function.
///
/// ## Examples
///
/// ```gleam
/// let result = decode.run(dynamic.int(147), decode.int)
/// assert result == Ok(147)
/// ```
///
pub const int: Decoder(Int) = Decoder(decode_int)

fn decode_int(data: Dynamic) -> #(Int, List(DecodeError)) {
  run_dynamic_function(data, "Int", dynamic_int)
}

@external(erlang, "gleam_stdlib", "int")
@external(javascript, "../../gleam_stdlib.mjs", "int")
fn dynamic_int(data: Dynamic) -> Result(Int, Int)

/// A decoder that decodes `Float` values.
///
/// This will not coerse int values into float values, so on platforms with
/// distinct runtime int and float types (Erlang, not JavaScript) it will fail
/// for ints. One time this may happen is when decoding JSON data.
///
/// If you want to decode both ints and floats you may want to use the `one_of`
/// function.
///
/// ## Examples
///
/// ```gleam
/// let result = decode.run(dynamic.float(3.14), decode.float)
/// assert result == Ok(3.14)
/// ```
///
pub const float: Decoder(Float) = Decoder(decode_float)

fn decode_float(data: Dynamic) -> #(Float, List(DecodeError)) {
  run_dynamic_function(data, "Float", dynamic_float)
}

@external(erlang, "gleam_stdlib", "float")
@external(javascript, "../../gleam_stdlib.mjs", "float")
fn dynamic_float(data: Dynamic) -> Result(Float, Float)

/// A decoder that decodes `Dynamic` values. This decoder never returns an error.
///
/// ## Examples
///
/// ```gleam
/// let result = decode.run(dynamic.float(3.14), decode.dynamic)
/// assert result == Ok(dynamic.float(3.14))
/// ```
///
pub const dynamic: Decoder(Dynamic) = Decoder(decode_dynamic)

fn decode_dynamic(data: Dynamic) -> #(Dynamic, List(DecodeError)) {
  #(data, [])
}

/// A decoder that decodes `BitArray` values. This decoder never returns an error.
///
/// ## Examples
///
/// ```gleam
/// let result = decode.run(dynamic.bit_array(<<5, 7>>), decode.bit_array)
/// assert result == Ok(<<5, 7>>)
/// ```
///
pub const bit_array: Decoder(BitArray) = Decoder(decode_bit_array)

fn decode_bit_array(data: Dynamic) -> #(BitArray, List(DecodeError)) {
  run_dynamic_function(data, "BitArray", dynamic_bit_array)
}

@external(erlang, "gleam_stdlib", "bit_array")
@external(javascript, "../../gleam_stdlib.mjs", "bit_array")
fn dynamic_bit_array(data: Dynamic) -> Result(BitArray, BitArray)

/// A decoder that decodes lists where all elements are decoded with a given
/// decoder.
///
/// ## Examples
///
/// ```gleam
/// let result =
///   [1, 2, 3]
///   |> list.map(dynamic.int)
///   |> dynamic.list
///   |> decode.run(decode.list(of: decode.int))
/// assert result == Ok([1, 2, 3])
/// ```
///
pub fn list(of inner: Decoder(a)) -> Decoder(List(a)) {
  Decoder(fn(data) {
    decode_list(data, inner.function, fn(p, k) { push_path(p, [k]) }, 0, [])
  })
}

@external(erlang, "gleam_stdlib", "list")
@external(javascript, "../../gleam_stdlib.mjs", "list")
fn decode_list(
  data: Dynamic,
  item: fn(Dynamic) -> #(t, List(DecodeError)),
  push_path: fn(#(t, List(DecodeError)), key) -> #(t, List(DecodeError)),
  index: Int,
  acc: List(t),
) -> #(List(t), List(DecodeError))

/// A decoder that decodes dicts where all keys and values are decoded with
/// given decoders.
///
/// ## Examples
///
/// ```gleam
/// let values = dynamic.properties([
///   #(dynamic.string("one"), dynamic.int(1)),
///   #(dynamic.string("two"), dynamic.int(2)),
/// ])
///
/// let result =
///   decode.run(values, decode.dict(decode.string, decode.int))
/// assert result == Ok(values)
/// ```
///
pub fn dict(
  key: Decoder(key),
  value: Decoder(value),
) -> Decoder(Dict(key, value)) {
  Decoder(fn(data) {
    case decode_dict(data) {
      Error(_) -> #(dict.new(), decode_error("Dict", data))
      Ok(dict) ->
        dict.fold(dict, #(dict.new(), []), fn(a, k, v) {
          // If there are any errors from previous key-value pairs then we
          // don't need to run the decoders, instead return the existing acc.
          case a.1 {
            [] -> fold_dict(a, k, v, key.function, value.function)
            [_, ..] -> a
          }
        })
    }
  })
}

fn fold_dict(
  acc: #(Dict(k, v), List(DecodeError)),
  key: Dynamic,
  value: Dynamic,
  key_decoder: fn(Dynamic) -> #(k, List(DecodeError)),
  value_decoder: fn(Dynamic) -> #(v, List(DecodeError)),
) -> #(Dict(k, v), List(DecodeError)) {
  // First we decode the key.
  case key_decoder(key) {
    #(key_decoded, []) ->
      // Then we decode the value.
      case value_decoder(value) {
        #(value, []) -> {
          // It worked! Insert the new key-value pair so we can move onto the next.
          let dict = dict.insert(acc.0, key_decoded, value)
          #(dict, acc.1)
        }
        #(_, errors) -> {
          let key_identifier = path_segment_to_string(key)
          push_path(#(dict.new(), errors), [key_identifier])
        }
      }
    #(_, errors) -> push_path(#(dict.new(), errors), ["keys"])
  }
}

@external(erlang, "gleam_stdlib", "dict")
@external(javascript, "../../gleam_stdlib.mjs", "dict")
fn decode_dict(data: Dynamic) -> Result(Dict(Dynamic, Dynamic), Nil)

/// A decoder that decodes nullable values of a type decoded by with a given
/// decoder.
///
/// This function can handle common representations of null on all runtimes, such as
/// `nil`, `null`, and `undefined` on Erlang, and `undefined` and `null` on
/// JavaScript.
///
/// ## Examples
///
/// ```gleam
/// let result = decode.run(dynamic.int(100), decode.optional(decode.int))
/// assert result == Ok(option.Some(100))
/// ```
///
/// ```gleam
/// let result = decode.run(dynamic.nil(), decode.optional(decode.int))
/// assert result == Ok(option.None)
/// ```
///
pub fn optional(inner: Decoder(a)) -> Decoder(Option(a)) {
  Decoder(function: fn(data) {
    case is_null(data) {
      True -> #(option.None, [])
      False -> {
        let #(data, errors) = inner.function(data)
        #(option.Some(data), errors)
      }
    }
  })
}

/// Apply a transformation function to any value decoded by the decoder.
///
/// ## Examples
///
/// ```gleam
/// let decoder = decode.int |> decode.map(int.to_string)
/// let result = decode.run(dynamic.int(1000), decoder)
/// assert result == Ok("1000")
/// ```
///
pub fn map(decoder: Decoder(a), transformer: fn(a) -> b) -> Decoder(b) {
  Decoder(function: fn(d) {
    let #(data, errors) = decoder.function(d)
    #(transformer(data), errors)
  })
}

/// Apply a transformation function to any errors returned by the decoder.
///
pub fn map_errors(
  decoder: Decoder(a),
  transformer: fn(List(DecodeError)) -> List(DecodeError),
) -> Decoder(a) {
  Decoder(function: fn(d) {
    let #(data, errors) = decoder.function(d)
    #(data, transformer(errors))
  })
}

/// Replace all errors produced by a decoder with one single error for a named
/// expected type.
///
/// This function may be useful if you wish to simplify errors before
/// presenting them to a user, particularly when using the `one_of` function.
///
/// ## Examples
///
/// ```gleam
/// let decoder = decode.string |> decode.collapse_errors("MyThing")
/// let result = decode.run(dynamic.int(1000), decoder)
/// assert result == Error([DecodeError("MyThing", "Int", [])])
/// ```
///
pub fn collapse_errors(decoder: Decoder(a), name: String) -> Decoder(a) {
  Decoder(function: fn(dynamic_data) {
    let #(data, errors) as layer = decoder.function(dynamic_data)
    case errors {
      [] -> layer
      [_, ..] -> #(data, decode_error(name, dynamic_data))
    }
  })
}

/// Create a new decoder based upon the value of a previous decoder.
///
/// This may be useful to run one previous decoder to use in further decoding.
///
pub fn then(decoder: Decoder(a), next: fn(a) -> Decoder(b)) -> Decoder(b) {
  Decoder(function: fn(dynamic_data) {
    let #(data, errors) = decoder.function(dynamic_data)
    let decoder = next(data)
    let #(data, _) as layer = decoder.function(dynamic_data)
    case errors {
      [] -> layer
      [_, ..] -> #(data, errors)
    }
  })
}

/// Create a new decoder from several other decoders. Each of the inner
/// decoders is run in turn, and the value from the first to succeed is used.
///
/// If no decoder succeeds then the errors from the first decoder are used.
/// If you wish for different errors then you may wish to use the
/// `collapse_errors` or `map_errors` functions.
///
/// ## Examples
///
/// ```gleam
/// let decoder = decode.one_of(decode.string, or: [
///   decode.int |> decode.map(int.to_string),
///   decode.float |> decode.map(float.to_string),
/// ])
/// assert decode.run(dynamic.int(1000), decoder) == Ok("1000")
/// ```
///
pub fn one_of(
  first: Decoder(a),
  or alternatives: List(Decoder(a)),
) -> Decoder(a) {
  Decoder(function: fn(dynamic_data) {
    let #(_, errors) as layer = first.function(dynamic_data)
    case errors {
      [] -> layer
      [_, ..] -> run_decoders(dynamic_data, layer, alternatives)
    }
  })
}

fn run_decoders(
  data: Dynamic,
  failure: #(a, List(DecodeError)),
  decoders: List(Decoder(a)),
) -> #(a, List(DecodeError)) {
  case decoders {
    [] -> failure

    [decoder, ..decoders] -> {
      let #(_, errors) as layer = decoder.function(data)
      case errors {
        [] -> layer
        [_, ..] -> run_decoders(data, failure, decoders)
      }
    }
  }
}

/// Define a decoder that always fails.
///
/// The first parameter is a "placeholder" value, which is some default value that the
/// decoder uses internally in place of the value that would have been produced
/// if the decoder was successful. It doesn't matter what this value is, it is
/// never returned by the decoder or shown to the user, so pick some arbitrary
/// value. If it is an int you might pick `0`, if it is a list you might pick
/// `[]`.
///
/// The second parameter is the name of the type that has failed to decode.
///
/// ```gleam
/// decode.failure(User(name: "", score: 0, tags: []), expected: "User")
/// ```
///
pub fn failure(placeholder: a, expected name: String) -> Decoder(a) {
  Decoder(function: fn(d) { #(placeholder, decode_error(name, d)) })
}

/// Create a decoder for a new data type from a decoding function.
///
/// This function is used for new primitive types. For example, you might
/// define a decoder for Erlang's pid type.
///
/// A default "placeholder" value is also required to make a decoder. When this
/// decoder is used as part of a larger decoder this placeholder value is used
/// so that the rest of the decoder can continue to run and
/// collect all decoding errors. It doesn't matter what this value is, it is
/// never returned by the decoder or shown to the user, so pick some arbitrary
/// value. If it is an int you might pick `0`, if it is a list you might pick
/// `[]`.
///
/// If you were to make a decoder for the `Int` type (rather than using the
/// built-in `Int` decoder) you would define it like so:
///
/// ```gleam
/// pub fn int_decoder() -> decode.Decoder(Int) {
///   let default = ""
///   decode.new_primitive_decoder("Int", int_from_dynamic)
/// }
///
/// @external(erlang, "my_module", "int_from_dynamic")
/// fn int_from_dynamic(data: Int) -> Result(Int, Int)
/// ```
///
/// ```erlang
/// -module(my_module).
/// -export([int_from_dynamic/1]).
///
/// int_from_dynamic(Data) ->
///     case is_integer(Data) of
///         true -> {ok, Data};
///         false -> {error, 0}
///     end.
/// ```
///
pub fn new_primitive_decoder(
  name: String,
  decoding_function: fn(Dynamic) -> Result(t, t),
) -> Decoder(t) {
  Decoder(function: fn(d) {
    case decoding_function(d) {
      Ok(t) -> #(t, [])
      Error(placeholder) -> #(placeholder, [
        DecodeError(name, dynamic.classify(d), []),
      ])
    }
  })
}

/// Create a decoder that can refer to itself, useful for decoding deeply
/// nested data.
///
/// Attempting to create a recursive decoder without this function could result
/// in an infinite loop. If you are using `field` or other `use`able functions
/// then you may not need to use this function.
///
/// ## Examples
///
/// ```gleam
/// type Nested {
///   Nested(List(Nested))
///   Value(String)
/// }
///
/// fn nested_decoder() -> decode.Decoder(Nested) {
///   use <- decode.recursive
///   decode.one_of(decode.string |> decode.map(Value), [
///     decode.list(nested_decoder()) |> decode.map(Nested),
///   ])
/// }
/// ```
///
pub fn recursive(inner: fn() -> Decoder(a)) -> Decoder(a) {
  Decoder(function: fn(data) {
    let decoder = inner()
    decoder.function(data)
  })
}

@external(erlang, "gleam_stdlib", "identity")
@external(javascript, "../../gleam_stdlib.mjs", "identity")
fn cast(a: anything) -> Dynamic

@external(erlang, "gleam_stdlib", "is_null")
@external(javascript, "../../gleam_stdlib.mjs", "is_null")
fn is_null(a: Dynamic) -> Bool