defmodule Doggo.Components.FieldTest do
  use ExUnit.Case
  use Phoenix.Component

  import Doggo.TestHelpers

  defmodule TestComponents do
    @moduledoc """
    Generates components for tests.
    """

    use Doggo.Components
    use Phoenix.Component

    build_field(gettext_module: Doggo.Gettext)

    build_field(
      name: :field_with_optional_text,
      gettext_module: Doggo.Gettext,
      optional_text: "(optional)"
    )

    build_field(name: :field_without_gettext, optional_text: "(optional)")
  end

  describe "field/1" do
    test "with text input" do
      assigns = %{form: to_form(%{})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field field={@form[:age]} label="Age" />
        </.form>
        """)

      input = find_one(html, "input")
      assert attribute(input, "id") == "age"
      assert attribute(input, "name") == "age"
      assert attribute(input, "type") == "text"
      assert attribute(input, "aria-describedby") == nil
      assert attribute(input, "aria-invalid") == nil
      assert attribute(input, "aria-errormessage") == nil

      assert attribute(html, "label", "for") == "age"
      assert text(html, "label") == "Age"
    end

    test "with text input and description" do
      assigns = %{form: to_form(%{})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field field={@form[:age]} label="Age">
            <:description>How old?</:description>
          </TestComponents.field>
        </.form>
        """)

      assert text(html, ".field-description") == "How old?"
      assert attribute(html, ".field-description", "id") == "age_description"
      assert attribute(html, "input", "aria-describedby") == "age_description"
    end

    test "with hidden label" do
      assigns = %{form: to_form(%{})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field field={@form[:age]} label="Age" hide_label />
        </.form>
        """)

      label = find_one(html, "label")
      assert attribute(label, "class") == "is-visually-hidden"
    end

    test "with required text" do
      assigns = %{form: to_form(%{})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field
            field={@form[:age]}
            label="Age"
            validations={[required: true]}
          />
        </.form>
        """)

      span = find_one(html, "label > span.field-required-mark")
      assert text(span) == "(required)"
    end

    test "with required text, without gettext" do
      assigns = %{form: to_form(%{})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field_without_gettext
            field={@form[:age]}
            label="Age"
            validations={[required: true]}
          />
        </.form>
        """)

      span = find_one(html, "label > span.field-required-mark")
      assert text(span) == "(required)"
    end

    test "with optional text" do
      assigns = %{form: to_form(%{})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field_with_optional_text field={@form[:age]} label="Age" />
        </.form>
        """)

      span = find_one(html, "label > span.field-optional-mark")
      assert text(span) == "(optional)"
    end

    test "with optional text, without_gettext" do
      assigns = %{form: to_form(%{})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field_without_gettext field={@form[:age]} label="Age" />
        </.form>
        """)

      span = find_one(html, "label > span.field-optional-mark")
      assert text(span) == "(optional)"
    end

    test "checkbox-group with optional text" do
      assigns = %{form: to_form(%{})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field_with_optional_text
            field={@form[:color]}
            type="checkbox-group"
            options={["blue", "green"]}
            label="Color"
          />
        </.form>
        """)

      span = find_one(html, "legend > span.field-optional-mark")
      assert text(span) == "(optional)"
    end

    test "radio-group with optional text" do
      assigns = %{form: to_form(%{})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field_with_optional_text
            field={@form[:color]}
            type="radio-group"
            options={["blue", "green"]}
            label="Color"
          />
        </.form>
        """)

      span = find_one(html, "legend > span.field-optional-mark")
      assert text(span) == "(optional)"
    end

    test "with checkbox" do
      assigns = %{form: to_form(%{})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field
            field={@form[:subscribe]}
            label="Subscribe"
            type="checkbox"
          >
            <:description>Please do.</:description>
          </TestComponents.field>
        </.form>
        """)

      assert attribute(html, "label", "class") == "field-checkbox"
      assert attribute(html, "input[type='hidden']", "value") == "false"

      assert text(html, ".field-description") == "Please do."

      assert attribute(html, ".field-description", "id") ==
               "subscribe_description"

      assert attribute(html, "input[type='checkbox']", "aria-describedby") ==
               "subscribe_description"

      assert attribute(html, "input[type='checkbox']", "value") == "true"
    end

    test "with checkbox and checked value" do
      assigns = %{form: to_form(%{})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field
            field={@form[:subscribe]}
            label="Subscribe"
            type="checkbox"
            checked_value="yes"
          >
            <:description>Please do.</:description>
          </TestComponents.field>
        </.form>
        """)

      assert attribute(html, "input[type='checkbox']", "value") == "yes"
    end

    test "with checkbox group" do
      assigns = %{form: to_form(%{})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field
            field={@form[:animals]}
            label="Animals"
            type="checkbox-group"
            options={[{"Dog", "dog"}, "cat", "rabbit_id", :elk]}
            value={["dog", "elk"]}
          >
            <:description>Which animals?</:description>
          </TestComponents.field>
        </.form>
        """)

      assert text(html, "fieldset > legend") == "Animals"
      assert attribute(html, "input[type='hidden']", "name") == "animals[]"
      assert attribute(html, "input[type='hidden']", "value") == ""

      input = find_one(html, "input[id='animals_dog']")
      assert attribute(input, "value") == "dog"

      input = find_one(html, "input[id='animals_cat']")
      assert attribute(input, "value") == "cat"
    end

    test "with radio group" do
      assigns = %{form: to_form(%{})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field
            field={@form[:animals]}
            label="Animals"
            type="radio-group"
            options={[{"Dog", "dog"}, "cat", "rabbit_id", :elk]}
          >
            <:description>Which animals?</:description>
          </TestComponents.field>
        </.form>
        """)

      assert text(html, "fieldset > legend") == "Animals"

      input = find_one(html, "input[id='animals_dog']")
      assert attribute(input, "value") == "dog"

      input = find_one(html, "input[id='animals_cat']")
      assert attribute(input, "value") == "cat"
    end

    test "with switch off" do
      assigns = %{form: to_form(%{})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field
            field={@form[:subscribe]}
            label="Subscribe"
            type="switch"
          >
            <:description>Subscribe?</:description>
          </TestComponents.field>
        </.form>
        """)

      assert text(html, ".field-switch-state-off") == "Off"
    end

    test "with switch on" do
      assigns = %{form: to_form(%{})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field
            field={@form[:subscribe]}
            label="Subscribe"
            type="switch"
            checked
          >
            <:description>Subscribe?</:description>
          </TestComponents.field>
        </.form>
        """)

      assert text(html, ".field-switch-state-on") == "On"
    end

    test "with select" do
      assigns = %{form: to_form(%{})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field
            field={@form[:animals]}
            label="Animals"
            type="select"
            options={[{"Dog", "dog"}, :hr, {"Cat", "cat"}]}
            value="dog"
          >
            <:description>Which animals?</:description>
          </TestComponents.field>
        </.form>
        """)

      assert attribute(html, "option:first-child", "value") == "dog"
      assert attribute(html, "option:first-child", "selected") == "selected"
      assert attribute(html, "option:last-child", "value") == "cat"
      assert attribute(html, "option:last-child", "selected") == nil

      # Check if the <hr /> is the middle option
      options = html |> Floki.find("select") |> hd() |> elem(2)
      assert [_, {"hr", [], []}, _] = options
    end

    test "escapes select options" do
      assigns = %{form: to_form(%{"animals" => "dog"})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field
            field={@form[:animals]}
            label="Animals"
            type="select"
            options={[{"Dog", :dog}, :hr, {"Cat", :cat}]}
          >
            <:description>Which animals?</:description>
          </TestComponents.field>
        </.form>
        """)

      assert attribute(html, "option:first-child", "value") == "dog"
      assert attribute(html, "option:first-child", "selected") == "selected"
      assert attribute(html, "option:last-child", "value") == "cat"
      assert attribute(html, "option:last-child", "selected") == nil

      # Check if the <hr /> is the middle option
      options = html |> Floki.find("select") |> hd() |> elem(2)
      assert [_, {"hr", [], []}, _] = options
    end

    test "with multiple select" do
      assigns = %{form: to_form(%{})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field
            field={@form[:animals]}
            label="Animals"
            type="select"
            options={[[key: "Dog", value: "dog"], %{"Cat" => "cat"}]}
            multiple
            value={["dog", "cat"]}
          >
            <:description>Which animals?</:description>
          </TestComponents.field>
        </.form>
        """)

      assert attribute(html, "select", "multiple") == "multiple"
      assert attribute(html, "option:first-child", "selected") == "selected"
      assert attribute(html, "option:last-child", "selected") == "selected"
    end

    test "with select, with groups" do
      assigns = %{form: to_form(%{})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field
            field={@form[:animals]}
            label="Animals"
            type="select"
            options={[{"Canines", %{"Dog" => "dog"}}, {"Felines", [{"Cat", "cat"}]}]}
            multiple
            value={["dog", "cat"]}
          >
            <:description>Which animals?</:description>
          </TestComponents.field>
        </.form>
        """)

      assert attribute(html, "select", "multiple") == "multiple"

      assert attribute(html, "optgroup:first-child", "label") == "Canines"
      assert attribute(html, "optgroup:last-child", "label") == "Felines"

      assert attribute(
               html,
               "optgroup:first-child option:first-child",
               "selected"
             ) == "selected"

      assert attribute(
               html,
               "optgroup:last-child option:last-child",
               "selected"
             ) == "selected"
    end

    test "with textarea" do
      assigns = %{form: to_form(%{})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field field={@form[:bio]} label="Bio" type="textarea">
            <:description>Tell us more about you.</:description>
          </TestComponents.field>
        </.form>
        """)

      textarea = find_one(html, "textarea")
      assert attribute(textarea, "id") == "bio"
      assert attribute(textarea, "name") == "bio"
      assert attribute(textarea, "aria-describedby") == "bio_description"

      assert attribute(html, "label", "for") == "bio"
      assert text(html, "label") == "Bio"
    end

    test "with hidden input" do
      assigns = %{form: to_form(%{})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field field={@form[:sentiment]} type="hidden" value="jaja" />
        </.form>
        """)

      assert attribute(html, "input", "type") == "hidden"
      assert attribute(html, "input", "name") == "sentiment"
      assert attribute(html, "input", "value") == "jaja"
    end

    test "with hidden input and list value" do
      assigns = %{form: to_form(%{})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field
            field={@form[:sentiment]}
            type="hidden"
            value={["ja", "ne"]}
          />
        </.form>
        """)

      assert attribute(html, "input:first-child", "type") == "hidden"
      assert attribute(html, "input:last-child", "type") == "hidden"
      assert attribute(html, "input:first-child", "name") == "sentiment[]"
      assert attribute(html, "input:last-child", "name") == "sentiment[]"
      assert attribute(html, "input:first-child", "value") == "ja"
      assert attribute(html, "input:last-child", "value") == "ne"
    end

    test "with add-ons" do
      assigns = %{form: to_form(%{})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field field={@form[:addons]} type="text">
            <:addon_left>left</:addon_left>
            <:addon_right>right</:addon_right>
          </TestComponents.field>
        </.form>
        """)

      assert text(html, ".field-input-wrapper > .field-input-addon-left") ==
               "left"

      assert text(html, ".field-input-wrapper > .field-input-addon-right") ==
               "right"
    end

    test "with datalist" do
      assigns = %{form: to_form(%{})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field
            field={@form[:species]}
            type="text"
            options={["option_a", {"Option B", "option_b"}]}
          />
        </.form>
        """)

      assert attribute(html, "input", "list") == "species_datalist"
      assert attribute(html, "datalist", "id") == "species_datalist"

      assert attribute(html, "datalist > option:first-child", "value") ==
               "option_a"

      assert attribute(html, "datalist > option:last-child", "value") ==
               "option_b"

      assert text(html, "datalist > option:first-child") == "option_a"
      assert text(html, "datalist > option:last-child") == "Option B"
    end

    test "with errors" do
      assigns = %{form: to_form(%{})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field field={@form[:species]} type="text" errors={["wrong"]} />
        </.form>
        """)

      assert attribute(html, "input", "aria-invalid") == "true"
      assert attribute(html, "input", "aria-errormessage") == "species_errors"
      assert attribute(html, "ul", "id") == "species_errors"
      assert attribute(html, "ul", "class") == "field-errors"
      assert text(html, ".field-errors > li") == "wrong"
    end

    test "with errors and description" do
      assigns = %{form: to_form(%{})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field field={@form[:species]} type="text" errors={["wrong"]}>
            <:description>What are you?</:description>
          </TestComponents.field>
        </.form>
        """)

      assert attribute(html, "input", "aria-invalid") == "true"
      assert attribute(html, "input", "aria-errormessage") == "species_errors"

      assert attribute(html, "input", "aria-describedby") ==
               "species_description"

      assert attribute(html, "ul", "id") == "species_errors"
      assert attribute(html, "ul", "class") == "field-errors"
      assert text(html, ".field-errors > li") == "wrong"

      assert text(html, ".field-description") == "What are you?"

      assert attribute(html, ".field-description", "id") ==
               "species_description"
    end

    test "converts datetime to date string for date input" do
      assigns = %{form: to_form(%{})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field
            field={@form[:when]}
            type="date"
            value={~U[1900-01-01T12:00:00Z]}
          />
        </.form>
        """)

      assert attribute(html, "input", "value") == "1900-01-01"
    end

    test "converts datetime string to date string for date input" do
      assigns = %{form: to_form(%{})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field
            field={@form[:when]}
            type="date"
            value="1900-01-01T12:00:00Z"
          />
        </.form>
        """)

      assert attribute(html, "input", "value") == "1900-01-01"
    end

    test "removes other invalid date values" do
      assigns = %{form: to_form(%{})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field field={@form[:when]} type="date" value="1900-01" />
        </.form>
        """)

      assert attribute(html, "input", "value") == ""
    end

    test "hides errors if field is unused" do
      assigns = %{form: to_form(%{})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field field={%{@form[:what] | errors: [{"weird", []}]}} />
        </.form>
        """)

      assert Floki.find(html, ".field-errors") == []

      assigns = %{form: to_form(%{"what" => "what", "_unused_what" => ""})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field field={%{@form[:what] | errors: [{"weird", []}]}} />
        </.form>
        """)

      assert Floki.find(html, ".field-errors") == []
    end

    test "inserts gettext variables in errors without gettext module" do
      assigns = %{form: to_form(%{"what" => "what"})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field field={
            %{@form[:what] | errors: [{"weird %{animal}", [animal: "dog"]}]}
          } />
        </.form>
        """)

      assert text(html, ".field-errors > li") == "weird dog"
    end

    test "translates errors with gettext" do
      assigns = %{form: to_form(%{"what" => "what"})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field field={%{@form[:what] | errors: [{"weird dog", []}]}} />
        </.form>
        """)

      assert text(html, ".field-errors > li") == "chien bizarre"
    end

    test "translates errors with numbers with gettext" do
      assigns = %{form: to_form(%{"what" => "what"})}

      html =
        parse_heex(~H"""
        <.form for={@form}>
          <TestComponents.field
            field={
              %{
                @form[:what]
                | errors: [
                    {"only %{count} dog(s) allowed", [count: 5]}
                  ]
              }
            }
            gettext={Doggo.Gettext}
          />
        </.form>
        """)

      assert text(html, ".field-errors > li") == "seulement 5 chiens autorisés"
    end
  end
end
