lib/foundry/context/compact.ex

defmodule Foundry.Context.Compact do
  @moduledoc """
  Generic recursive compact serialization that filters empty values from maps and structs.

  Recursively strips nil, false, [], and "" from any map at any nesting depth.
  Also sanitizes strings by replacing non-standard characters with ASCII equivalents.
  """

  def compact(struct) when is_struct(struct) do
    struct |> Map.from_struct() |> compact()
  end

  def compact(map) when is_map(map) do
    map
    |> Enum.reduce(%{}, fn {k, v}, acc ->
      compacted = compact(v)
      if empty?(compacted), do: acc, else: Map.put(acc, k, compacted)
    end)
  end

  def compact(list) when is_list(list), do: Enum.map(list, &compact/1)
  
  def compact(string) when is_binary(string) do
    # Replace common non-standard characters with safe ASCII equivalents
    # using explicit unicode codepoints to avoid encoding mismatches.
    string
    |> String.replace("\u2014", "-")   # em-dash
    |> String.replace("\u2013", "-")   # en-dash
    |> String.replace("\u00D7", "*")   # multiplication sign
    |> String.replace("\u201C", "\"")  # left double quote
    |> String.replace("\u201D", "\"")  # right double quote
    |> String.replace("\u2018", "'")   # left single quote
    |> String.replace("\u2019", "'")   # right single quote
    |> String.replace("\u2026", "...") # ellipsis
  end

  def compact(v), do: v

  defp empty?(nil), do: true
  defp empty?(false), do: true
  defp empty?([]), do: true
  defp empty?(""), do: true
  defp empty?(map) when is_map(map) and map_size(map) == 0, do: true
  defp empty?(_), do: false
end