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 :class, :string, default: nil
  attr :type, :string, default: "text", values: ~w[text password textarea datetime-local]
  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 class={["flex flex-col", @class]}>
      <.label for={@id}>{@label}</.label>
      <textarea id={@id} name={@name} class="h-80">{Phoenix.HTML.Form.normalize_value(@type, @value)}</textarea>
      <.error :for={error <- @errors}>{error}</.error>
    </div>
    """
  end

  def input(assigns) do
    ~H"""
    <div class="flex flex-col">
      <.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

  @doc """
  Renders markdown content as HTML.

  ## Examples

      <.markdown content={@post.body} />
  """
  attr :content, :string, required: true
  attr :class, :string, default: ""

  def markdown(assigns) do
    ~H"""
    <div class={["prose", @class]}>
      {Phoenix.HTML.raw(Web.Markdown.to_html(@content))}
    </div>
    """
  end

  @doc """
  Renders pagination controls for navigating through a paginated list.

  ## Examples

      <.pagination meta={@meta} path={~p"/blog"} schema={Schema.Post} />
  """
  attr :meta, :map, required: true, doc: "the pagination metadata from Flop"
  attr :path, :string, required: true, doc: "the base path for pagination links"
  attr :schema, :atom, required: true, doc: "the schema module for Flop.Phoenix.build_path"
  attr :class, :string, default: "mt-4", doc: "additional CSS classes"

  def pagination(assigns) do
    ~H"""
    <div class={["flex justify-between", @class]}>
      <%= if @meta.has_previous_page? do %>
        <.link
          navigate={Flop.Phoenix.build_path(@path, Flop.to_previous_cursor(@meta), for: @schema)}
          class="text-gray-500 hover:text-gray-800"
        >
          Newer posts
        </.link>
      <% else %>
        <div></div>
      <% end %>

      <%= if @meta.has_next_page? do %>
        <.link
          navigate={Flop.Phoenix.build_path(@path, Flop.to_next_cursor(@meta), for: @schema)}
          class="text-gray-500 hover:text-gray-800"
        >
          Older posts
        </.link>
      <% else %>
        <div></div>
      <% end %>
    </div>
    """
  end

  attr :id, :string, required: true
  attr :stream, Phoenix.LiveView.LiveStream, required: true
  attr :class, :string, default: nil
  attr :rest, :global

  slot :col do
    attr :label, :string
    attr :class, :string
  end

  def table(assigns) do
    ~H"""
    <table id={@id} class={["border-collapse", @class]} {@rest}>
      <thead>
        <tr>
          <%= for col <- @col do %>
            <th class="border p-2">{col[:label]}</th>
          <% end %>
        </tr>
      </thead>
      <tbody id={"#{@id}-stream"} phx-update="stream">
        <%= for {dom_id, item} <- @stream do %>
          <tr id={dom_id}>
            <%= for col <- @col do %>
              <td class={["border p-2", col[:class]]}>{render_slot(col, item)}</td>
            <% end %>
          </tr>
        <% end %>
      </tbody>
    </table>
    """
  end
end