defmodule Web.CoreComponents do
  @moduledoc """
  Provides core UI components.
  """
  use Phoenix.Component

  alias Phoenix.HTML.FormField

  attr :id, :any, default: nil
  attr :name, :any
  attr :label, :string, default: nil
  attr :value, :any
  attr :type, :string, default: "text", values: ~w[text password textarea]
  attr :field, FormField
  attr :errors, :list, default: []
  attr :rest, :global, include: ~w[disabled form pattern placeholder readonly required]

  def input(%{field: %FormField{} = field} = assigns) do
    errors =
      if Phoenix.Component.used_input?(field) do
        field.errors
      else
        []
      end

    assigns
    |> assign(
      field: nil,
      id: assigns.id || field.id,
      errors: Enum.map(errors, &translate_error/1)
    )
    |> assign_new(:name, fn -> field.name end)
    |> assign_new(:value, fn -> field.value end)
    |> input()
  end

  def input(%{type: "textarea"} = assigns) do
    ~H"""
    <div>
      <.label for={@id}>{@label}</.label>
      <textarea id={@id} name={@name}>{Phoenix.HTML.Form.normalize_value(@type, @value)}</textarea>
      <.error :for={error <- @errors}>{error}</.error>
    </div>
    """
  end

  def input(assigns) do
    ~H"""
    <div>
      <.label for={@id}>{@label}</.label>
      <input
        id={@id}
        type={@type}
        name={@name}
        value={Phoenix.HTML.Form.normalize_value(@type, @value)}
        {@rest}
      />
      <.error :for={error <- @errors}>{error}</.error>
    </div>
    """
  end

  attr :for, :string, default: nil
  slot :inner_block, required: true

  def label(assigns) do
    ~H"""
    <label for={@for}>
      {render_slot(@inner_block)}
    </label>
    """
  end

  slot :inner_block, required: true

  def error(assigns) do
    ~H"""
    <p>
      <.icon name="hero-exclamation-circle-mini" class="h-5 w-5 flex-none" />
      {render_slot(@inner_block)}
    </p>
    """
  end

  attr :name, :string, required: true
  attr :class, :string, default: nil

  def icon(%{name: "hero-" <> _} = assigns) do
    ~H"""
    <span class={[@name, @class]} />
    """
  end

  def translate_error({msg, opts}) do
    Enum.reduce(opts, msg, fn {key, value}, acc ->
      String.replace(acc, "%{#{key}}", fn _ -> to_string(value) end)
    end)
  end

  def translate_errors(errors, field) when is_list(errors) do
    for {^field, {msg, opts}} <- errors, do: translate_error({msg, opts})
  end

  attr :type, :string, default: "button", values: ~w[button submit]
  attr :rest, :global
  slot :inner_block, required: true

  def button(assigns) do
    ~H"""
    <button type={@type} {@rest}>
      {render_slot(@inner_block)}
    </button>
    """
  end

  attr :format, :string, required: true
  attr :value, :any, default: nil
  attr :formatter, :atom, default: :default
  attr :timezone, :string, default: "America/New_York"
  attr :global, :global

  def timex(%{value: nil} = assigns) do
    ~H"""
    <time datetime="">--</time>
    """
  end

  def timex(%{value: value, timezone: timezone} = assigns) do
    assigns =
      assign_new(assigns, :local_value, fn ->
        case value do
          %DateTime{} = datetime ->
            datetime

          %NaiveDateTime{} = naive ->
            naive
            |> DateTime.from_naive!("Etc/UTC")
            |> DateTime.shift_zone!(timezone)
        end
      end)

    ~H"""
    <time
      datetime={Timex.format!(@local_value, "{ISO:Extended}")}
      title={Timex.format!(@local_value, "{Mshort} {D}, {YYYY}, {h12}:{m} {AM} {Zabbr}")}
      {@global}
    >
      {Timex.format!(@local_value, @format, timex_formatter(@formatter))}
    </time>
    """
  end

  defp timex_formatter(formatter) do
    Module.concat(Timex.Format.DateTime.Formatters, :string.titlecase("#{formatter}"))
  end

  attr :id, :string, required: true
  attr :posts, :any, required: true

  slot :inner_block, required: true

  def post_list(assigns) do
    ~H"""
    <ol id={@id} phx-update={if is_struct(@posts, Phoenix.LiveView.LiveStream), do: "phx-update"}>
      <li
        :for={{dom_id, item} <- normalize_posts(@posts)}
        id={dom_id}
        class="flex flex-row justify-between"
      >
        <span>{render_slot(@inner_block, item)}</span>
        <span>
          <%= if item.deleted_at do %>
            deleted
          <% else %>
            <%= if item.published_at do %>
              <%= case item.kind do %>
                <% :blog -> %>
                  <.timex value={item.published_at} format="{YYYY}-{0M}-{0D}" />
                <% :status -> %>
                  <.timex value={item.published_at} format="{relative}" formatter={:relative} />
              <% end %>
            <% else %>
              draft
            <% end %>
          <% end %>
        </span>
      </li>
    </ol>
    """
  end

  defp normalize_posts(%Phoenix.LiveView.LiveStream{} = stream), do: stream

  defp normalize_posts(posts) when is_list(posts),
    do: Enum.with_index(posts, &{"#{&1.kind}-#{&2}", &1})
end