defmodule HtmlSanitizeEx.Scrubber.CSS do
  @moduledoc """
  Scrub CSS.
  """

  def scrub(nil), do: ""

  def scrub(text) do
    text = String.replace(text, ~r/(\/\*|\*\/|<!--|-->)/, " ")

    Regex.replace(~r/([-\w]+)\s*:\s*([^:;]*)/, text, fn _all, a, b ->
      case scrub_css(a, b) do
        {property, value} -> "#{property}: #{value}"
        nil -> ""
      end
    end)
  end

  defp scrub_css("azimuth", val), do: validate({"azimuth", scrub_val(val)})

  defp scrub_css("background-color", val),
    do: validate({"background-color", scrub_val(val)})

  defp scrub_css("border-bottom-color", val),
    do: validate({"border-bottom-color", scrub_val(val)})

  defp scrub_css("border-collapse", val),
    do: validate({"border-collapse", scrub_val(val)})

  defp scrub_css("border-color", val),
    do: validate({"border-color", scrub_val(val)})

  defp scrub_css("border-left-color", val),
    do: validate({"border-left-color", scrub_val(val)})

  defp scrub_css("border-right-color", val),
    do: validate({"border-right-color", scrub_val(val)})

  defp scrub_css("border-top-color", val),
    do: validate({"border-top-color", scrub_val(val)})

  defp scrub_css("clear", val), do: validate({"clear", scrub_val(val)})
  defp scrub_css("color", val), do: validate({"color", scrub_val(val)})
  defp scrub_css("cursor", val), do: validate({"cursor", scrub_val(val)})
  defp scrub_css("direction", val), do: validate({"direction", scrub_val(val)})
  defp scrub_css("display", val), do: validate({"display", scrub_val(val)})
  defp scrub_css("elevation", val), do: validate({"elevation", scrub_val(val)})
  defp scrub_css("float", val), do: validate({"float", scrub_val(val)})
  defp scrub_css("font", val), do: validate({"font", scrub_val(val)})

  defp scrub_css("font-family", val),
    do: validate({"font-family", scrub_val(val)})

  defp scrub_css("font-size", val), do: validate({"font-size", scrub_val(val)})

  defp scrub_css("font-style", val),
    do: validate({"font-style", scrub_val(val)})

  defp scrub_css("font-variant", val),
    do: validate({"font-variant", scrub_val(val)})

  defp scrub_css("font-weight", val),
    do: validate({"font-weight", scrub_val(val)})

  defp scrub_css("height", val), do: validate({"height", scrub_val(val)})

  defp scrub_css("letter-spacing", val),
    do: validate({"letter-spacing", scrub_val(val)})

  defp scrub_css("line-height", val),
    do: validate({"line-height", scrub_val(val)})

  defp scrub_css("overflow", val), do: validate({"overflow", scrub_val(val)})
  defp scrub_css("pause", val), do: validate({"pause", scrub_val(val)})

  defp scrub_css("pause-after", val),
    do: validate({"pause-after", scrub_val(val)})

  defp scrub_css("pause-before", val),
    do: validate({"pause-before", scrub_val(val)})

  defp scrub_css("pitch", val), do: validate({"pitch", scrub_val(val)})

  defp scrub_css("pitch-range", val),
    do: validate({"pitch-range", scrub_val(val)})

  defp scrub_css("richness", val), do: validate({"richness", scrub_val(val)})
  defp scrub_css("speak", val), do: validate({"speak", scrub_val(val)})

  defp scrub_css("speak-header", val),
    do: validate({"speak-header", scrub_val(val)})

  defp scrub_css("speak-numeral", val),
    do: validate({"speak-numeral", scrub_val(val)})

  defp scrub_css("speak-punctuation", val),
    do: validate({"speak-punctuation", scrub_val(val)})

  defp scrub_css("speech-rate", val),
    do: validate({"speech-rate", scrub_val(val)})

  defp scrub_css("stress", val), do: validate({"stress", scrub_val(val)})

  defp scrub_css("text-align", val),
    do: validate({"text-align", scrub_val(val)})

  defp scrub_css("text-decoration", val),
    do: validate({"text-decoration", scrub_val(val)})

  defp scrub_css("text-indent", val),
    do: validate({"text-indent", scrub_val(val)})

  defp scrub_css("unicode-bidi", val),
    do: validate({"unicode-bidi", scrub_val(val)})

  defp scrub_css("vertical-align", val),
    do: validate({"vertical-align", scrub_val(val)})

  defp scrub_css("voice-family", val),
    do: validate({"voice-family", scrub_val(val)})

  defp scrub_css("volume", val), do: validate({"volume", scrub_val(val)})

  defp scrub_css("white-space", val),
    do: validate({"white-space", scrub_val(val)})

  defp scrub_css("width", val), do: validate({"width", scrub_val(val)})

  defp scrub_css("background", val),
    do: validate({"background", scrub_val(val)})

  defp scrub_css("background-" <> prop, val),
    do: validate({"background-#{prop}", scrub_val(val)})

  defp scrub_css("border", val), do: validate({"border", scrub_val(val)})

  defp scrub_css("border-" <> prop, val),
    do: validate({"border-#{prop}", scrub_val(val)})

  defp scrub_css("margin", val), do: validate({"margin", scrub_val(val)})

  defp scrub_css("margin-" <> prop, val),
    do: validate({"margin-#{prop}", scrub_val(val)})

  defp scrub_css("padding", val), do: validate({"padding", scrub_val(val)})

  defp scrub_css("padding-" <> prop, val),
    do: validate({"padding-#{prop}", scrub_val(val)})

  defp scrub_css(_, _), do: nil

  defp validate({_property, ""}), do: nil
  defp validate({property, val}), do: {property, val}

  defp scrub_val(val) do
    val = if String.match?(val, ~r/(\\|&)/), do: "", else: val

    Regex.replace(~r/(\S+)/, val, fn _all, a ->
      if(allowed_keyword?(a) || measured_unit?(a), do: a, else: "")
    end)
  end

  @allowed_keywords [
    "auto",
    "aqua",
    "black",
    "block",
    "blue",
    "bold",
    "both",
    "bottom",
    "brown",
    "center",
    "collapse",
    "dashed",
    "dotted",
    "fuchsia",
    "gray",
    "green",
    "!important",
    "italic",
    "left",
    "lime",
    "maroon",
    "medium",
    "middle",
    "none",
    "navy",
    "normal",
    "nowrap",
    "olive",
    "pointer",
    "purple",
    "red",
    "right",
    "solid",
    "silver",
    "teal",
    "top",
    "transparent",
    "underline",
    "white",
    "yellow"
  ]

  def allowed_keyword?(val) do
    Enum.member?(@allowed_keywords, String.downcase(val))
  end

  defp measured_unit?(val) do
    String.match?(
      val,
      ~r/\A(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|-?\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)\z/
    )
  end
end
