defmodule MusicLibrary.Colors.EdgeWeightedExtractor do
  @moduledoc """
  Simple edge-weighted color extraction using Vix.

  This approach weights colors based on their proximity to edges,
  giving more importance to colors from visually significant regions
  without requiring complex algorithms.

  Extraction is slow, so it should always be done asynchronously.

  Generated by Claude, using Sonnet 4.
  """

  alias Vix.Vips.{Image, Operation}

  @doc """
  Extracts dominant colors weighted by edge proximity.
  Colors near edges (important features) are given higher importance.
  """
  def extract_dominant_colors(image_data, num_colors \\ 5) do
    with {:ok, image} <- Image.new_from_buffer(image_data),
         {:ok, resized} <- Operation.thumbnail_image(image, 200),
         {:ok, rgb_image} <- ensure_rgb_channels(resized),
         {:ok, edge_map} <- create_edge_map(rgb_image),
         {:ok, colors} <- extract_edge_weighted_colors(rgb_image, edge_map, num_colors) do
      hex_colors = Enum.map(colors, &rgb_to_hex/1)
      {:ok, hex_colors}
    end
  end

  @doc """
  Same as `extract_dominant_colors/2`, but raises an error on failure.
  """
  @spec extract_dominant_colors!(binary(), integer()) :: [String.t()] | no_return
  def extract_dominant_colors!(image_data, num_colors \\ 5) do
    case extract_dominant_colors(image_data, num_colors) do
      {:ok, colors} -> colors
      {:error, reason} -> raise "Failed to extract dominant colors: #{reason}"
    end
  end

  defp create_edge_map(image) do
    # Convert to grayscale for edge detection
    # Apply Sobel edge detection - built into Vix!
    with {:ok, gray} <- Operation.colourspace(image, :VIPS_INTERPRETATION_B_W) do
      Operation.sobel(gray)
    end
  end

  defp ensure_rgb_channels(image) do
    bands = Image.bands(image)

    cond do
      bands >= 3 -> {:ok, image}
      bands == 1 -> Operation.bandjoin([image, image, image])
      true -> {:error, "Unsupported image format"}
    end
  end

  defp extract_edge_weighted_colors(rgb_image, edge_map, num_colors) do
    width = Image.width(rgb_image)
    height = Image.height(rgb_image)

    # Sample pixels with edge-based weighting
    # Sample every 3rd pixel for performance
    weighted_pixels =
      for y <- 0..(height - 1)//3,
          x <- 0..(width - 1)//3 do
        with {:ok, [r, g, b | _]} <- Operation.getpoint(rgb_image, x, y),
             {:ok, [edge_strength]} <- Operation.getpoint(edge_map, x, y) do
          # Weight this pixel by edge strength
          # Normalize edge strength
          weight = max(1, trunc(edge_strength / 50))
          color = {trunc(r), trunc(g), trunc(b)}

          # Return the color multiple times based on edge weight
          List.duplicate(color, weight)
        else
          _ -> []
        end
      end
      |> List.flatten()
      |> Enum.reject(fn {r, g, b} ->
        # Filter out very dark/light colors
        brightness = (r + g + b) / 3
        brightness < 15 || brightness > 240
      end)

    if Enum.empty?(weighted_pixels) do
      {:error, "No valid edge-weighted pixels found"}
    else
      colors = cluster_weighted_colors(weighted_pixels, num_colors)
      {:ok, colors}
    end
  end

  defp cluster_weighted_colors(pixels, num_colors) do
    pixels
    # Slightly larger buckets for better clustering
    |> group_similar_colors(40)
    |> Enum.frequencies()
    |> Enum.sort_by(fn {_color, count} -> count end, :desc)
    |> Enum.take(num_colors)
    |> Enum.map(fn {{r, g, b}, _count} -> {r, g, b} end)
  end

  defp group_similar_colors(pixels, bucket_size) do
    Enum.map(pixels, fn {r, g, b} ->
      {
        div(r, bucket_size) * bucket_size,
        div(g, bucket_size) * bucket_size,
        div(b, bucket_size) * bucket_size
      }
    end)
  end

  defp rgb_to_hex({r, g, b}) do
    "#" <>
      (r |> Integer.to_string(16) |> String.pad_leading(2, "0") |> String.upcase()) <>
      (g |> Integer.to_string(16) |> String.pad_leading(2, "0") |> String.upcase()) <>
      (b |> Integer.to_string(16) |> String.pad_leading(2, "0") |> String.upcase())
  end
end
