lib/money/input/visualizer/format_view.ex

defmodule Money.Input.Visualizer.FormatView do
  @moduledoc false

  # Cross-locale format table. Take one parsed value and show how
  # `Localize.Number.to_string/2` (for `:number` mode) and
  # `Money.to_string/2` (for `:money` mode) render it in every
  # demo locale. Shows decimal-separator inversion,
  # symbol-position swap, native digit systems
  # (Arabic-Indic, Persian).

  alias Money.Input.Visualizer.Render

  def render(params, base) do
    amount = params.amount
    currency = params.currency
    mode = params.mode

    rows =
      for {locale, label} <- Render.locale_options() do
        case mode do
          :number ->
            decimal = parse_decimal(amount)

            {locale, label, decimal && Localize.Number.to_string!(decimal, locale: locale)}

          :money ->
            money =
              with decimal when not is_nil(decimal) <- parse_decimal(amount),
                   true <- currency not in [nil, ""] do
                Money.new(currency, decimal)
              else
                _ -> nil
              end

            {locale, label, money && Money.to_string!(money, locale: locale)}
        end
      end

    body = [
      "<section class=\"mi-card\">",
      "<h2>Cross-locale formatting</h2>",
      "<p class=\"mi-desc\">Same parsed value, every locale. Watch the ",
      "decimal/grouping separators invert, the currency symbol position ",
      "swap, and the native digit system change in Arabic and Persian.</p>",
      "<form method=\"get\" action=\"",
      Render.escape(base),
      "/format\" class=\"mi-form\">",
      Render.field(
        "Amount (canonical form)",
        [
          "<input type=\"text\" name=\"amount\" value=\"",
          Render.escape(amount),
          "\" autocomplete=\"off\">"
        ],
        hint: "Period as decimal, no grouping — e.g. 1234567.89"
      ),
      Render.field("Mode", mode_select(mode)),
      Render.field("Currency (money mode)", Render.currency_select("currency", currency)),
      "<div class=\"mi-actions\">",
      "<button class=\"mi-btn\" type=\"submit\">Format across locales</button>",
      "</div>",
      "</form>",
      result_table(rows, mode),
      "</section>"
    ]

    Render.page(
      title: "Format",
      active: "format",
      base: base,
      body: body
    )
  end

  defp mode_select(selected) do
    options = [{"number", "Plain number"}, {"money", "Money"}]

    [
      "<select name=\"mode\">",
      Enum.map(options, fn {value, label} ->
        sel = if value == to_string(selected), do: " selected", else: ""

        [
          "<option value=\"",
          Render.escape(value),
          "\"",
          sel,
          ">",
          Render.escape(label),
          "</option>"
        ]
      end),
      "</select>"
    ]
  end

  defp result_table(rows, mode) do
    [
      "<table class=\"mi-table\">",
      "<thead><tr><th>Locale</th><th>",
      Render.escape(
        if(mode == :money, do: "Money.to_string/2", else: "Localize.Number.to_string/2")
      ),
      "</th></tr></thead>",
      "<tbody>",
      Enum.map(rows, &result_row/1),
      "</tbody></table>"
    ]
  end

  defp result_row({locale, label, nil}) do
    [
      "<tr><td>",
      Render.escape(locale),
      " <small>(",
      Render.escape(label),
      ")</small></td>",
      "<td><em>(invalid input)</em></td></tr>"
    ]
  end

  defp result_row({locale, label, formatted}) do
    [
      "<tr><td>",
      Render.escape(locale),
      " <small>(",
      Render.escape(label),
      ")</small></td>",
      "<td class=\"mi-mono\">",
      Render.escape(formatted),
      "</td></tr>"
    ]
  end

  defp parse_decimal(""), do: nil
  defp parse_decimal(nil), do: nil

  defp parse_decimal(value) when is_binary(value) do
    case Decimal.parse(value) do
      {decimal, ""} -> decimal
      _ -> nil
    end
  end
end