 #!/usr/bin/env elixir
 
 #
-# Print the Nerves System compatibility information based on Nerves Project's
-# Github repos. By default, the Github API only allows us 60 requests per hour.
+# Write the Nerves System compatibility information to local `tmp` directory.
+# The information is gathered from Nerves Project's Github repos.
+# By default, the Github API only allows us 60 requests per hour.
 # With an API token, we could get our rate limit bumped to 5000 requests an hour.
 # See https://docs.github.com/en/rest/guides/getting-started-with-the-rest-api#authentication
 #
 # ## Usage
 #
-#   nerves_system_compatibility.exs
+#   scripts/nerves_system_compatibility.exs
 #
 #   GITHUB_API_TOKEN=xxxxxx nerves_system_compatibility.exs
 #
 
-Mix.install([:req])
+Mix.install([:req, :earmark])
 
 defmodule NervesSystemCompatibility do
-  @doc """
-  OTP versions that will be table rows.
-  """
-  @spec otp_versions :: [binary]
-  def otp_versions do
-    ~w[
-      23.2.4
-      23.2.7
-      23.3.1
-      24.0.2
-      24.0.5
-      24.1
-      24.1.2
-      24.1.4
-      24.1.7
-      24.2
-      24.2.2
-      24.3.2
-      25.0
-    ]
-  end
-
-  @doc """
-  The Nerves System targets that are officially supported.
-  """
-  @spec target_systems :: [atom]
-  def target_systems do
-    ~w[
-      bbb
-      rpi
-      rpi0
-      rpi2
-      rpi3
-      rpi3a
-      rpi4
-      osd32mp1
-      x86_64
-      grisp2
-    ]a
-  end
+  @targets [:bbb, :rpi, :rpi0, :rpi2, :rpi3, :rpi3a, :rpi4, :osd32mp1, :x86_64, :grisp2]
+  def targets, do: @targets
 
   def run do
-    NervesSystemCompatibility.Data.get()
-    |> NervesSystemCompatibility.Table.build()
-    |> IO.puts()
+    compatibility_data = NervesSystemCompatibility.Data.get()
+    data_by_target = NervesSystemCompatibility.Data.group_data_by_target(compatibility_data)
+
+    html =
+      for target <- @targets do
+        table_html =
+          NervesSystemCompatibility.Table.build(
+            target,
+            Access.fetch!(data_by_target, target),
+            NervesSystemCompatibility.Data.list_target_system_versions(compatibility_data, target)
+          )
+          |> to_string()
+          |> Earmark.as_html!()
+
+        "<details><summary>#{target}</summary>#{table_html}</details>"
+        |> String.replace(~r/\s+/, " ")
+        |> String.replace(~r/> </, "><")
+        |> String.replace(~r/ style="text-align: left;"/, "")
+      end
+      |> Enum.join("\n")
+
+    File.mkdir("tmp")
+    file = "tmp/nerves_system_compatibility_#{DateTime.to_unix(DateTime.utc_now())}.html"
+    IO.puts("writing the Nerves System compatibility information to #{file}")
+    File.write!(file, html)
   end
 end
 

end

defmodule NervesSystemCompatibility.Table do
  alias NervesSystemCompatibility.Data

  @doc """
  Converts the compatibility data to a markdown table.
  """
  @spec build([%{binary => any}]) :: binary
  def build(compatibility_data) do
    targets = NervesSystemCompatibility.target_systems()
    otp_versions = NervesSystemCompatibility.otp_versions()

    [
      header_row(targets),
      divider_row(targets),
      data_rows(targets, otp_versions, compatibility_data)
    ]
    |> Enum.join("\n")
  end

  defp header_row(targets) when is_list(targets) do
    [
      "|",
      [cell("", 12) | Enum.map(targets, &cell/1)] |> Enum.intersperse("|"),
      "|"
    ]
    |> Enum.join()
  end

  defp divider_row(targets) when is_list(targets) do
    [
      "|",
      [cell("---", 12) | List.duplicate(cell("---"), length(targets))] |> Enum.intersperse("|"),
      "|"
    ]
    |> Enum.join()
  end

  defp data_rows(targets, otp_versions, compatibility_data) do
    grouped_by_otp_and_target = compatibility_data |> Data.group_data_by_otp_and_target()

    for otp_version <- otp_versions, reduce: [] do
      acc ->
        target_versions =
          for target <- targets do
            get_in(grouped_by_otp_and_target, [otp_version, target, "target_version"])
          end

        [data_row(otp_version, target_versions) | acc]
    end
    |> Enum.join("\n")
  end

  defp data_row(otp_version, row_values) when is_list(row_values) do
    [
      "|",
      [cell("OTP #{otp_version}", 12) | Enum.map(row_values, &cell/1)] |> Enum.intersperse("|"),
      "|"
    ]
    |> Enum.join()
  end

  defp cell(value, count \\ 10) do
    (" " <> to_string(value)) |> String.pad_trailing(count)
  end
end


    end)
  end

  @spec group_data_by_target(compatibility_data) :: %{binary => %{atom => any}}
  def group_data_by_target(compatibility_data) do
    compatibility_data
    |> Enum.group_by(&Map.fetch!(&1, "target"))
    |> Map.new(fn {target, target_entries} ->
      {
        target,
        target_entries
        |> Enum.reject(&String.match?(&1["target_version"], ~r/-rc/))
        |> Enum.group_by(&Map.fetch!(&1, "target_version"))
        |> Map.new(fn {target_version, target_version_entries} ->
          {
            target_version,
            target_version_entries
            |> Enum.max_by(&normalize_version(&1["target_version"]), Version)
          }
        end)
      }
    end)
  end

  @spec filter_by(compatibility_data, any, any) :: compatibility_data
  def filter_by(compatibility_data, key, value) do
    compatibility_data |> Enum.filter(&Kernel.==(&1[key], value))
  end

  @spec list_target_system_versions(compatibility_data, atom) :: [binary]
  def list_target_system_versions(compatibility_data, target) do
    compatibility_data
    |> filter_by("target", target)
    |> Enum.map(&Access.fetch!(&1, "target_version"))
    |> Enum.reject(&String.match?(&1, ~r/-rc/))
    |> Enum.uniq()
    |> Enum.sort({:desc, Version})
  end

  defp normalize_version(version) do
    case version |> String.split(".") |> Enum.count(&String.to_integer/1) do
      1 -> version <> ".0.0"
      2 -> version <> ".0"
