defmodule Microwaveprop.Radio.TextSanitizer do
  @moduledoc """
  Strip non-printable characters from user-uploaded log text. Removes BOM,
  zero-width spaces/joiners, C0 control bytes (except CR/LF/TAB), and DEL;
  collapses non-breaking and other unicode whitespace into a regular space.

  Used by CSV and ADIF importers because field values frequently arrive
  with stray NBSPs from website copy-paste, BOM markers from Windows
  exports, etc.
  """

  @spec sanitize(nil) :: nil
  @spec sanitize(String.t()) :: String.t()
  def sanitize(nil), do: nil
  def sanitize(""), do: ""

  def sanitize(string) when is_binary(string) do
    string
    |> :unicode.characters_to_list()
    |> Enum.reduce([], fn cp, acc ->
      case sanitize_codepoint(cp) do
        :strip -> acc
        replacement -> [replacement | acc]
      end
    end)
    |> Enum.reverse()
    |> :unicode.characters_to_binary()
  end

  defp sanitize_codepoint(?\t), do: ?\t
  defp sanitize_codepoint(?\n), do: ?\n
  defp sanitize_codepoint(?\r), do: ?\r

  # C0 control characters and DEL — strip.
  defp sanitize_codepoint(cp) when cp < 0x20 or cp == 0x7F, do: :strip

  # NBSP and other unicode whitespace → regular space.
  defp sanitize_codepoint(0x00A0), do: ?\s
  defp sanitize_codepoint(cp) when cp >= 0x2000 and cp <= 0x200A, do: ?\s
  defp sanitize_codepoint(0x202F), do: ?\s
  defp sanitize_codepoint(0x205F), do: ?\s
  defp sanitize_codepoint(0x3000), do: ?\s

  # Zero-width characters (ZWSP, ZWNJ, ZWJ, word-joiner, BOM) — strip.
  defp sanitize_codepoint(0x200B), do: :strip
  defp sanitize_codepoint(0x200C), do: :strip
  defp sanitize_codepoint(0x200D), do: :strip
  defp sanitize_codepoint(0x2060), do: :strip
  defp sanitize_codepoint(0xFEFF), do: :strip

  defp sanitize_codepoint(cp), do: cp
end
