defmodule Mix.Tasks.OptimizeImages do
  @shortdoc "Generate optimized variants for product mockup images"
  @moduledoc """
  Generates responsive image variants (AVIF, WebP, JPEG) for mockup images
  in the priv/static/mockups directory.

  This task is useful for pre-generating optimized versions of product mockup
  images used in the store demo. It creates multiple sizes and formats:
  - AVIF (best compression for modern browsers)
  - WebP (good compression with broad support)
  - JPEG (fallback for legacy browsers)

  Only generates sizes smaller than or equal to the source image dimensions
  (no upscaling).

  ## Usage

      # Generate variants for all mockups
      mix optimize_images

      # Force regeneration of all variants
      mix optimize_images --force

      # Process a custom directory
      mix optimize_images --dir path/to/images

  ## Options

  * `--force` - Regenerate all variants, even if they already exist
  * `--dir PATH` - Process images in the specified directory instead of priv/static/mockups

  ## Output

  For each source image (e.g., `product.jpg`), generates:
  - `product-400.avif`, `product-400.webp`, `product-400.jpg`
  - `product-800.avif`, `product-800.webp`, `product-800.jpg`
  - `product-1200.avif`, `product-1200.webp`, `product-1200.jpg` (if source >= 1200px)
  """

  use Mix.Task

  @default_dir "priv/static/mockups"
  @widths [400, 800, 1200]
  @formats [:avif, :webp, :jpg]

  @impl Mix.Task
  def run(args) do
    Application.ensure_all_started(:image)

    {opts, _} = OptionParser.parse!(args, strict: [force: :boolean, dir: :string])
    force = opts[:force] || false
    dir = opts[:dir] || @default_dir

    unless File.dir?(dir) do
      Mix.shell().error("Directory not found: #{dir}")
      exit({:shutdown, 1})
    end

    originals = find_originals(dir)
    Mix.shell().info("Found #{length(originals)} original mockup images in #{dir}")

    if originals == [] do
      Mix.shell().info("No images to process. Done!")
    else
      results =
        originals
        |> Task.async_stream(&process(&1, force, dir),
          max_concurrency: System.schedulers_online(),
          timeout: :timer.minutes(2)
        )
        |> Enum.map(fn
          {:ok, result} -> result
          {:exit, reason} -> {:error, reason}
        end)

      # Report results
      Enum.each(results, fn
        {:generated, name, count} ->
          Mix.shell().info("  #{name}: generated #{count} variants")

        :skipped ->
          :ok

        {:error, reason} ->
          Mix.shell().error("  Error: #{inspect(reason)}")
      end)

      generated_count = Enum.count(results, &match?({:generated, _, _}, &1))
      skipped_count = Enum.count(results, &(&1 == :skipped))

      Mix.shell().info("")

      if generated_count > 0 do
        Mix.shell().info("Generated variants for #{generated_count} images.")
      end

      if skipped_count > 0 do
        Mix.shell().info("Skipped #{skipped_count} images (variants already exist).")
      end

      Mix.shell().info("Done!")
    end
  end

  defp find_originals(dir) do
    # Find all .jpg files that are NOT variants (don't end with -SIZE)
    dir
    |> Path.join("*.jpg")
    |> Path.wildcard()
    |> Enum.reject(&is_variant?/1)
  end

  defp is_variant?(path) do
    # A variant ends with -{number}.{ext}
    basename = Path.basename(path, ".jpg")
    String.match?(basename, ~r/-\d+$/)
  end

  defp process(path, force, dir) do
    name = Path.basename(path, ".jpg")

    if not force and all_variants_exist?(name, dir) do
      :skipped
    else
      with {:ok, image} <- Image.open(path),
           {width, _, _} <- Image.shape(image) do
        # Only generate sizes <= source width
        applicable = Enum.filter(@widths, &(&1 <= width))

        count =
          for w <- applicable, fmt <- @formats do
            generate(image, name, w, fmt, dir)
          end
          |> Enum.count(&(&1 == :generated))

        {:generated, name, count}
      else
        error ->
          {:error, {name, error}}
      end
    end
  end

  defp all_variants_exist?(name, dir) do
    Enum.all?(@widths, fn w ->
      Enum.all?(@formats, fn fmt ->
        File.exists?(Path.join(dir, "#{name}-#{w}.#{ext(fmt)}"))
      end)
    end)
  end

  defp generate(image, name, width, format, dir) do
    path = Path.join(dir, "#{name}-#{width}.#{ext(format)}")

    if File.exists?(path) do
      :cached
    else
      with {:ok, resized} <- Image.thumbnail(image, width),
           {:ok, _} <- write(resized, path, format) do
        :generated
      else
        _ -> :error
      end
    end
  end

  defp ext(:jpg), do: "jpg"
  defp ext(:webp), do: "webp"
  defp ext(:avif), do: "avif"

  defp write(image, path, :avif) do
    Image.write(image, path, effort: 5, minimize_file_size: true)
  end

  defp write(image, path, :webp) do
    Image.write(image, path, effort: 6, minimize_file_size: true)
  end

  defp write(image, path, :jpg) do
    Image.write(image, path, quality: 80, minimize_file_size: true)
  end
end
