defmodule Mix.Tasks.GenerateAdminIcons do
  @moduledoc """
  Generates static CSS for heroicon classes used in admin templates.

  Scans `lib/` for `hero-*` class references, reads the matching SVGs from
  `deps/heroicons/optimized/`, and writes `assets/css/admin/icons.css` with
  CSS mask-based icon classes that match the old Tailwind plugin output.

  ## Usage

      mix generate_admin_icons
  """

  @shortdoc "Generate admin/icons.css from heroicons SVGs"

  use Mix.Task

  @icons_dir "deps/heroicons/optimized"
  @output_path "assets/css/admin/icons.css"

  # Maps suffix to subdirectory and default size
  @variants %{
    "" => {"24/outline", "1.5rem"},
    "-solid" => {"24/solid", "1.5rem"},
    "-mini" => {"20/solid", "1.25rem"},
    "-micro" => {"16/solid", "1rem"}
  }

  @impl Mix.Task
  def run(_args) do
    icons = scan_used_icons()
    Mix.shell().info("Found #{length(icons)} icon references in lib/")

    css_rules =
      icons
      |> Enum.sort()
      |> Enum.map(&generate_rule/1)
      |> Enum.reject(&is_nil/1)

    missing = length(icons) - length(css_rules)
    if missing > 0, do: Mix.shell().info("Skipped #{missing} (SVG not found or not an icon)")

    content = """
    /* Generated by mix generate_admin_icons — do not edit by hand */

    @layer admin {

    #{Enum.join(css_rules, "\n\n")}

    } /* @layer admin */
    """

    File.mkdir_p!(Path.dirname(@output_path))
    File.write!(@output_path, content)
    Mix.shell().info("Wrote #{length(css_rules)} icon rules to #{@output_path}")
  end

  defp scan_used_icons do
    Path.wildcard("lib/**/*.{ex,heex}")
    |> Enum.flat_map(fn path ->
      path
      |> File.read!()
      |> then(&Regex.scan(~r/hero-[a-z][a-z0-9-]+/, &1))
      |> List.flatten()
    end)
    |> Enum.uniq()
    |> Enum.reject(&shop_class?/1)
  end

  # Filter out CSS class names from shop components that start with "hero-"
  # but aren't actual heroicon references
  defp shop_class?(name) do
    name in [
      "hero-section",
      "hero-description",
      "hero-cta",
      "hero-cta-group",
      "hero-pre-title",
      "hero-error",
      "hero-image",
      "hero-section--page"
    ]
  end

  defp generate_rule(class_name) do
    {suffix, {dir, size}} = detect_variant(class_name)
    base = String.trim_leading(class_name, "hero-")
    svg_name = if suffix == "", do: base, else: String.trim_trailing(base, suffix)
    svg_path = Path.join([@icons_dir, dir, "#{svg_name}.svg"])

    if File.exists?(svg_path) do
      content =
        svg_path
        |> File.read!()
        |> String.replace(~r/\r?\n|\r/, "")
        |> URI.encode(&uri_allowed?/1)

      """
      .#{class_name} {
        --hero-#{class_name |> String.trim_leading("hero-")}: url('data:image/svg+xml;utf8,#{content}');
        -webkit-mask: var(--hero-#{class_name |> String.trim_leading("hero-")});
        mask: var(--hero-#{class_name |> String.trim_leading("hero-")});
        mask-repeat: no-repeat;
        background-color: currentColor;
        vertical-align: middle;
        display: inline-block;
        width: #{size};
        height: #{size};
      }\
      """
    else
      Mix.shell().info("  warning: SVG not found for #{class_name} (expected #{svg_path})")
      nil
    end
  end

  defp detect_variant(class_name) do
    cond do
      String.ends_with?(class_name, "-micro") -> {"-micro", @variants["-micro"]}
      String.ends_with?(class_name, "-mini") -> {"-mini", @variants["-mini"]}
      String.ends_with?(class_name, "-solid") -> {"-solid", @variants["-solid"]}
      true -> {"", @variants[""]}
    end
  end

  # Characters that don't need percent-encoding in data URIs
  defp uri_allowed?(char) do
    URI.char_unreserved?(char) or char in [?<, ?>, ?/, ?=, ?", ?', ?:, ?;, ?(, ?), ?!, ?@, ?,, ?.]
  end
end
