defmodule Elixir.Moon.Light.Form do
  @moduledoc false
  use Moon.Light.Component

  alias Phoenix.LiveView.JS

  import Moon.Helpers.Form
  import Moon.Helpers.MakeList, only: [make_list: 1]

  attr(:field, Phoenix.HTML.FormField, required: true)
  attr(:size, :string, values: ~w(sm md lg), default: "md", doc: "Input size")
  attr(:label, :string, doc: "Label for input field", default: nil)
  attr(:hint, :string, doc: "Hint for input field", default: nil)
  attr(:hide_errors, :boolean, doc: "Whether error message is shown", default: nil)
  attr(:class, :any, doc: "Additional Tailwind classes", default: nil)
  attr(:error_class, :any, doc: "Additional Tailwind classes", default: nil)
  attr(:hint_class, :any, doc: "Additional Tailwind classes", default: nil)
  attr(:id, :string, doc: "id attribute for DOM element", default: nil)
  attr(:testid, :string, doc: "data-testid attribute for DOM element", default: nil)
  attr(:is_horizontal, :boolean, doc: "Whether label is on side of input field", default: nil)
  attr(:has_error_icon, :boolean, doc: "Whether error icon is shown", default: nil)
  slot(:inner_block, doc: "Inner content of the component")
  attr(:attrs, :map, default: nil)
  attr(:feedback_for, :string, default: nil)

  def field(assigns) do
    import Moon.Light.Form.Field

    ~H"""
    <div
      class={merge([["grid grid-cols-2": @is_horizontal], @class])}
      id={@id}
      data-testid={@testid}
      phx-feedback-for={@feedback_for || @field.name}
    >
      <.label :if={@label} size={@size} title={@label} field={@field} />
      <%= render_slot(@inner_block, @field) %>
      <.hint :if={@hint} title={@hint} class={@hint_class} is_horizontal={@is_horizontal} />
      <.error
        :if={!@hide_errors && @field.errors != []}
        field={@field}
        class={@error_class}
        has_error_icon={@has_error_icon}
        is_horizontal={@is_horizontal}
      />
    </div>
    """
  end

  attr(:field, Phoenix.HTML.FormField, default: nil)

  attr(:size, :string,
    values: ["sm", "md", "lg"],
    default: "md",
    doc: "Common moon size property"
  )

  attr(:error, :boolean,
    doc:
      "Some additional styling will be set to indicate the field is invalid. Generally should be set by Form.Field or Form.InsetField component",
    default: nil
  )

  attr(:type, :string,
    values: [
      "date",
      "month",
      "datetime-local",
      "email",
      "number",
      "password",
      "search",
      "tel",
      "text",
      "time",
      "url"
    ],
    default: "text",
    doc: "HTML5 type of the input, please don't use checkbox or radio here"
  )

  attr(:placeholder, :string,
    default: " ",
    doc: "Text to be set as a HTML placeholder attribute - when input is empty"
  )

  attr(:class, :any, doc: "Additional classes for the <input> tag", default: nil)

  attr(:field_class, :any,
    doc: "Additional classes for the <input> tag in case of floating label or Group",
    default: nil
  )

  attr(:value, :string, doc: "Value of the input, don't use it inside the forms", default: nil)
  attr(:id, :string, doc: "Id to be given to the input tag", default: nil)
  attr(:testid, :string, doc: "Data-testid attribute value", default: nil)

  attr(:disabled, :boolean,
    doc:
      "HTML disabled attribute for the input & some additional classes. Generally should be set by Form.Field or Form.InsetField component",
    default: nil
  )

  attr(:readonly, :boolean,
    doc:
      "Readonly - some additional classes and behaviour. Generally should be set by Form.Field or Form.InsetField component",
    default: nil
  )

  attr(:autocomplete, :string,
    doc: "Autocomplete HTML attribute for the input, use \"off\" to disable",
    default: nil
  )

  attr(:opts, :any, default: %{}, doc: "Keyword | Map of additional attributes for the input")

  attr(:on_change, Event,
    doc: "On change event for the input - don't use it inside phoenix forms",
    default: nil
  )

  attr(:on_keyup, Event, doc: "On keyup event for the input", default: nil)
  attr(:on_focus, Event, doc: "On focus event for the input", default: nil)
  attr(:on_blur, Event, doc: "On blur event for the input", default: nil)

  def input(assigns) do
    ~H"""
    <input
      class={
        merge(
          input_classes(assigns) ++
            input_size_classes(assigns) ++
            [@field_class, get_config(:default_class), @class]
        )
      }
      type={@type}
      placeholder={@placeholder}
      value={(@field && @field.value) || @value}
      id={(@field && @field.id) || @id}
      name={@field && @field.name}
      error={(@field && length(@field.errors) > 0) || @error}
      disabled={@disabled}
      readonly={@readonly}
      data-testid={@testid}
      autocomplete={@autocomplete}
      {@opts}
      phx-change={Event.from(@on_change).name}
      phx-keyup={Event.from(@on_keyup).name}
      phx-focus={Event.from(@on_focus).name}
      phx-blur={Event.from(@on_blur).name}
      phx-target={[@on_change, @on_keyup, @on_focus, @on_blur] |> Event.find_target()}
    />
    """
  end

  attr(:field, Phoenix.HTML.FormField, default: nil)

  attr(:options, :list,
    default: [],
    doc:
      "Options for select. Or list of keyword, either list of maps. Format: [%{key: shown_label, value: option_value, disabled: bool}], diisabled is optional"
  )

  attr(:value, :any,
    doc:
      "Selected option(s) value - do not use it inside the form, just for away-from-form components",
    default: nil
  )

  attr(:disabled, :boolean, doc: "Well, disabled", default: nil)
  attr(:size, :string, values: ~w(sm md lg), default: "md", doc: "Common moon size property")
  attr(:class, :any, doc: "Additional classes for the <select> tag", default: nil)

  attr(:field_class, :any,
    doc: "Additional classes for the <select> tag in case of floating label or Group",
    default: nil
  )

  attr(:prompt, :string, doc: "Some prompt to be shown on empty value", default: nil)
  attr(:id, :string, doc: "Id to be given to the select tag", default: nil)
  attr(:testid, :string, doc: "Data-testid attribute value", default: nil)

  attr(:error, :boolean,
    doc:
      "Some additional styling will be set to indicate the field is invalid. Generally should be set by Form.Field component",
    default: nil
  )

  attr(:is_multiple, :boolean,
    doc:
      "Experimental: If field does support multiselect, multiple attribute for select tag in HTML terms\n",
    default: nil
  )

  def select(assigns) do
    ~H"""
    <select
      multiple={@is_multiple}
      class={
        merge([
          "text-trunks flex justify-between w-full bg-goku border-0 duration-200 transition-shadow",
          "shadow-input hover:shadow-input-hov focus:shadow-input-focus focus:outline-none focus:ring-0",
          "items-start text-ellipsis whitespace-nowrap overflow-hidden",
          "moon-error:shadow-input-err moon-error:hover:shadow-input-err moon-error:focus:shadow-input-err",
          "invalid:shadow-input-err invalid:hover:shadow-input-err invalid:focus:shadow-input-err",
          [
            "py-0 px-4": !@is_multiple,
            "h-8 leading-8 rounded-moon-i-xs": @size == "sm",
            "h-10 leading-10 rounded-moon-i-sm": @size == "md",
            "h-12 leading-[3rem] rounded-moon-i-sm": @size == "lg",
            "cursor-not-allowed opacity-60": @disabled
          ],
          @field_class,
          get_config(:default_class),
          @class
        ])
      }
      name={@field && @field.name <> ((@is_multiple && "[]") || "")}
      error={(@field && length(@field.errors) > 0) || @error}
      id={(@field && @field.id) || @id}
      prompt={@prompt}
      disabled={@disabled}
      data-testid={@testid}
    >
      <option
        :for={option <- @options}
        value={option[:value]}
        selected={Enum.member?(make_list((@field && @field.value) || @value), option[:value])}
        disabled={option[:disabled]}
      >
        <%= option[:key] %>
      </option>
    </select>
    """
  end

  attr(:field, Phoenix.HTML.FormField, default: nil)
  attr(:class, :any, default: nil)

  attr(:attrs, :map,
    default: %{},
    doc: "Attributes directly passed to textarea html tag. see Surface.Components.Form.TextArea"
  )

  attr(:rows, :string, default: nil)
  attr(:cols, :string, default: nil)
  attr(:disabled, :boolean, default: false)
  attr(:placeholder, :string, default: "")
  attr(:error, :boolean, default: nil)
  attr(:testid, :string, default: nil)
  attr(:id, :string, default: nil)
  attr(:value, :string, default: nil, doc: "Value for the textarea")
  attr(:on_change, :string, doc: "On change event for the input", default: nil)
  attr(:on_keyup, :string, doc: "On keyup event for the input", default: nil)
  attr(:on_focus, :string, doc: "On focus event for the input", default: nil)
  attr(:on_blur, :string, doc: "On blur event for the input", default: nil)
  attr(:target, :any, doc: "phx-target attribute", default: nil)

  def text_area(assigns) do
    ~H"""
    <textarea
      class={
        merge([
          "appearance-none resize-none w-full p-4 text-moon-16 text-bulma bg-goku border-0",
          "rounded-moon-s-sm placeholder:text-trunks transition-shadow",
          "before:box-border after:box-border",
          "shadow-input hover:shadow-input-hov focus:shadow-input-focus focus:outline-none focus:ring-0",
          "moon-error:shadow-input-err moon-error:hover:shadow-input-err moon-error:focus:shadow-input-err",
          "invalid:shadow-input-err invalid:hover:shadow-input-err invalid:focus:shadow-input-err",
          @disabled && "opacity-60 cursor-not-allowed",
          @class
        ])
      }
      rows={@rows}
      cols={@cols}
      disabled={@disabled}
      placeholder={@placeholder}
      data-testid={@testid}
      name={@field && @field.name}
      error={(@field && length(@field.errors) > 0) || @error}
      id={(@field && @field.id) || @id}
      {@attrs}
      phx-change={@on_change}
      phx-keyup={@on_keyup}
      phx-focus={@on_focus}
      phx-blur={@on_blur}
      phx-target={@target}
    ><%= @field && @field.value || @value %></textarea>
    """
  end

  attr(:field, Phoenix.HTML.FormField, required: true)
  attr(:label, :string, default: nil)
  attr(:disabled, :boolean, default: false)
  attr(:readonly, :boolean, default: false)
  attr(:size, :string, values: ["2xs", "xs", "sm"], default: "sm")
  attr(:on_bg_color, :any, default: "bg-piccolo")
  attr(:off_bg_color, :any, default: "bg-beerus")
  attr(:class, :any, default: nil)
  attr(:testid, :string, default: nil)
  attr(:id, :string, default: nil)

  attr(:checked_value, :any,
    default: true,
    doc: "The value to be sent when the checkbox is checked. Defaults to \"true\""
  )

  attr(:hidden_input, :boolean,
    default: true,
    doc:
      "Controls if this function will generate a hidden input to submit the unchecked value or not, defaults to \"true\"."
  )

  attr(:unchecked_value, :any,
    default: false,
    doc: "The value to be sent when the checkbox is unchecked, defaults to \"false\"."
  )

  slot(:on_icon, [])
  slot(:off_icon, [])

  def switch(assigns) do
    ~H"""
    <div>
      <input :if={@hidden_input} type="hidden" value={"#{@unchecked_value}"} name={@field.name} />
      <input
        type="checkbox"
        value={"#{@checked_value}"}
        name={@field.name}
        id={@field.id}
        class="hidden"
        disabled={@disabled}
        readonly={@readonly}
        checked={@field.value}
      />
      <Moon.Light.switch
        size={@size}
        on_bg_color={@on_bg_color}
        off_bg_color={@off_bg_color}
        disabled={@disabled || @readonly}
        class={@class}
        testid={@testid}
        id={@id}
        on_change={
          JS.dispatch("moon2:update-switch",
            detail: %{checked: !@field.value},
            to: "##{@field.id}"
          )
        }
        checked={@field.value}
      >
        <:on_icon><%= render_slot(@on_icon) %></:on_icon>
        <:off_icon><%= render_slot(@off_icon) %></:off_icon>
      </Moon.Light.switch>
      <%= @label %>
    </div>
    """
  end

  @doc "Checkbox to be rendered on form. Label is the root component. For rebdering outside the from please use Moon.Lego.Checkbox"
  attr(:field, Phoenix.HTML.FormField, required: true)
  attr(:label, :string, doc: "Label to be shown to user", default: nil)
  attr(:disabled, :boolean, doc: "If the field is disabled", default: nil)
  attr(:readonly, :boolean, doc: "If the field is read-only", default: nil)
  attr(:id, :string, doc: "Id to be given to the HTML tag", default: nil)
  attr(:testid, :string, doc: "Data-testid attribute value", default: nil)
  attr(:class, :any, doc: "Class to be given to the visible checkbox", default: nil)

  attr(:checkbox_label_class, :any,
    doc: "Additional Tailwind classes for checkbox label",
    default: nil
  )

  attr(:on_click, Event, doc: "On_click event for the checkbox", default: nil)

  attr(:checked_value, :any,
    default: true,
    doc: "The value to be sent when the checkbox is checked. Defaults to \"true\""
  )

  attr(:hidden_input, :boolean,
    default: true,
    doc:
      "Controls if this function will generate a hidden input to submit the unchecked value or not, defaults to \"true\"."
  )

  attr(:unchecked_value, :any,
    default: false,
    doc: "The value to be sent when the checkbox is unchecked, defaults to \"false\"."
  )

  attr(:is_multiple, :boolean,
    doc: "Adding [] to the field name for support multiple checkboxes with the same name",
    default: nil
  )

  attr(:size, :string, values: ~w(sm md lg), default: "md", doc: "Size of the label")
  slot(:inner_block, doc: "Inner content - put label here")

  def checkbox(assigns) do
    ~H"""
    <Elixir.Moon.Light.Form.Field.label
      size={@size}
      class={
        merge([
          [
            "relative inline-flex items-center select-none font-normal",
            "opacity-disabled": @disabled,
            "cursor-not-allowed select-none": @readonly || @disabled
          ],
          get_config(:default_label_class),
          @checkbox_label_class
        ])
      }
      field={@field}
    >
      <input
        :if={@hidden_input}
        type="hidden"
        value={"#{@unchecked_value}"}
        name={@field.name <> ((@is_multiple && "[]") || "")}
      />
      <input
        type="checkbox"
        value={"#{@checked_value}"}
        name={@field.name <> ((@is_multiple && "[]") || "")}
        id={@id || @field.id}
        class="opacity-0"
        phx-click={(!@readonly && !@disabled && @on_click) || nil}
        readonly={@readonly}
        disabled={@disabled || @readonly}
        data-testid={@testid}
        checked={"#{@field.value}" == "#{@checked_value}"}
      />
      <Elixir.Moon.Light.Lego.checkbox
        is_selected={"#{@field.value}" == "#{@checked_value}"}
        class={
          merge([
            "absolute top-1 ltr:left-0 rtl:right-0",
            get_config(:default_class),
            @class
          ])
        }
      />
      <%= render_slot(@inner_block) || @label %>
    </Elixir.Moon.Light.Form.Field.label>
    """
  end
end
