defmodule PhilomenaWeb.TextileRenderer do
  alias Philomena.Textile.Parser
  alias Philomena.Images.Image
  alias Philomena.Repo
  import Phoenix.HTML
  import Phoenix.HTML.Link
  import Ecto.Query

  # Kill bogus compile time dependency on ImageView
  @image_view Module.concat(["PhilomenaWeb.ImageView"])

  def render_one(post, conn) do
    hd(render_collection([post], conn))
  end

  def render_collection(posts, conn) do
    opts = %{image_transform: &Camo.Image.image_url/1}
    parsed = Enum.map(posts, &Parser.parse(opts, &1.body))

    images =
      parsed
      |> Enum.flat_map(fn tree ->
        tree
        |> Enum.flat_map(fn
          {:text, text} ->
            [text]

          _ ->
            []
        end)
      end)
      |> find_images

    parsed
    |> Enum.map(fn tree ->
      tree
      |> Enum.map(fn
        {:text, text} ->
          text
          |> replacement_entities()
          |> replacement_images(conn, images)

        {_k, markup} ->
          markup
      end)
      |> Enum.join()
    end)
  end

  defp replacement_entities(t) do
    t
    |> String.replace("->", "→")
    |> String.replace("--", "—")
    |> String.replace("...", "…")
    |> String.replace(~r|(\s)-(\s)|, "\\1—\\2")
    |> String.replace("(tm)", "&tm;")
    |> String.replace("(c)", "©")
    |> String.replace("(r)", "®")
    |> String.replace("'", "’")
  end

  defp replacement_images(t, conn, images) do
    t
    |> String.replace(~r|>>(\d+)([pts])?|, fn match ->
      # Stupid, but the method doesn't give us capture group information
      match_data = Regex.run(~r|>>(\d+)([pts])?|, match, capture: :all_but_first)
      [image_id | rest] = match_data
      image = images[String.to_integer(image_id)]

      case [image | rest] do
        [nil, _] ->
          match

        [nil] ->
          match

        [image, "p"] ->
          Phoenix.View.render(@image_view, "_image_target.html",
            image: image,
            size: :medium,
            conn: conn
          )
          |> safe_to_string()

        [image, "t"] ->
          Phoenix.View.render(@image_view, "_image_target.html",
            image: image,
            size: :small,
            conn: conn
          )
          |> safe_to_string()

        [image, "s"] ->
          Phoenix.View.render(@image_view, "_image_target.html",
            image: image,
            size: :thumb_small,
            conn: conn
          )
          |> safe_to_string()

        [image] ->
          link(">>#{image.id}", to: "/#{image.id}")
          |> safe_to_string()
      end
    end)
  end

  defp find_images(text_segments) do
    text_segments
    |> Enum.flat_map(fn t ->
      Regex.scan(~r|>>(\d+)|, t, capture: :all_but_first)
      |> Enum.map(fn [first] -> String.to_integer(first) end)
      |> Enum.filter(&(&1 < 2_147_483_647))
    end)
    |> load_images()
  end

  defp load_images([]), do: %{}

  defp load_images(ids) do
    Image
    |> where([i], i.id in ^ids)
    |> where([i], i.hidden_from_users == false)
    |> preload(:tags)
    |> Repo.all()
    |> Map.new(&{&1.id, &1})
  end
end