defmodule Berrypod.Theme.CSSGenerator do
  @moduledoc """
  Generates CSS custom properties (Layer 2: Theme Tokens) from theme settings.

  This module converts ThemeSettings into CSS variables that bridge the gap
  between fixed primitives (Layer 1) and semantic aliases (Layer 3).

  For the shop (public pages), this generates ALL theme tokens inline, so the
  shop doesn't need the attribute-based selectors in theme-layer2-attributes.css.
  The theme editor still uses those selectors for live preview switching.
  """

  alias Berrypod.Settings.ThemeSettings
  alias Berrypod.Theme.Fonts

  @doc """
  Generates CSS for theme settings.

  Returns a string of CSS custom properties that can be injected into a <style> tag.
  This includes ALL theme tokens (mood, typography, shape, density) so the shop
  pages don't need the attribute-based CSS selectors.

  Also includes @font-face declarations for the fonts used by the typography preset.

  Accepts an optional path_resolver function for digested font paths.
  In production, pass `&BerrypodWeb.Endpoint.static_path/1`.
  """
  def generate(%ThemeSettings{} = settings, path_resolver \\ fn path -> path end) do
    """
    /* Font faces for #{settings.typography} typography */
    #{Fonts.generate_font_faces(settings.typography, path_resolver)}

    /* Theme Tokens - Layer 2 (dynamically generated) */
    .themed {
      /* Mood colors */
      #{generate_mood(settings.mood)}

      /* Typography */
      #{generate_typography(settings.typography)}

      /* Shape (border radii) */
      #{generate_shape(settings.shape)}

      /* Density */
      #{generate_density(settings.density)}

      /* Slider-controlled values */
      #{generate_accent(settings.accent_color)}
      #{generate_secondary_colors(settings)}
      #{generate_font_size(settings.font_size)}
      #{generate_heading_weight(settings.heading_weight)}
      #{generate_layout_width(settings.layout_width)}
      #{generate_button_style(settings.button_style)}
      #{generate_product_text_align(settings.product_text_align)}
      #{generate_image_aspect_ratio(settings.image_aspect_ratio)}
    }
    """
    |> String.trim()
  end

  # Mood colors - surface, text, and border colors
  defp generate_mood("neutral") do
    """
    color-scheme: light;
      --t-surface-base: #ffffff;
      --t-surface-raised: #ffffff;
      --t-surface-sunken: #f5f5f5;
      --t-surface-overlay: rgba(255, 255, 255, 0.95);
      --t-text-primary: #171717;
      --t-text-secondary: #525252;
      --t-text-tertiary: #737373;
      --t-text-inverse: #ffffff;
      --t-border-default: #e5e5e5;
      --t-border-subtle: #f0f0f0;
      --t-border-input: #767676;
    """
  end

  defp generate_mood("warm") do
    """
    color-scheme: light;
      --t-surface-base: #fdf8f3;
      --t-surface-raised: #fffcf8;
      --t-surface-sunken: #f5ebe0;
      --t-surface-overlay: rgba(253, 248, 243, 0.95);
      --t-text-primary: #1c1917;
      --t-text-secondary: #57534e;
      --t-text-tertiary: #78716c;
      --t-text-inverse: #ffffff;
      --t-border-default: #e7e0d8;
      --t-border-subtle: #f0ebe4;
      --t-border-input: #706860;
    """
  end

  defp generate_mood("cool") do
    """
    color-scheme: light;
      --t-surface-base: #f4f7fb;
      --t-surface-raised: #f8fafc;
      --t-surface-sunken: #e8eff7;
      --t-surface-overlay: rgba(244, 247, 251, 0.95);
      --t-text-primary: #0f172a;
      --t-text-secondary: #475569;
      --t-text-tertiary: #64748b;
      --t-text-inverse: #ffffff;
      --t-border-default: #d4dce8;
      --t-border-subtle: #e8eff5;
      --t-border-input: #606a7a;
    """
  end

  defp generate_mood("dark") do
    """
    color-scheme: dark;
      --t-surface-base: #0a0a0a;
      --t-surface-raised: #171717;
      --t-surface-sunken: #000000;
      --t-surface-overlay: rgba(23, 23, 23, 0.95);
      --t-text-primary: #fafafa;
      --t-text-secondary: #a3a3a3;
      --t-text-tertiary: #737373;
      --t-text-inverse: #171717;
      --t-border-default: #262626;
      --t-border-subtle: #1c1c1c;
      --t-border-input: #808080;
    """
  end

  # Fallback for any other mood value
  defp generate_mood(_), do: generate_mood("neutral")

  # Typography style settings (weight and tracking per preset)
  @typography_styles %{
    "clean" => %{weight: 600, tracking: "-0.02em"},
    "editorial" => %{weight: 500, tracking: "-0.01em"},
    "modern" => %{weight: 500, tracking: "-0.03em"},
    "classic" => %{weight: 500, tracking: "0"},
    "friendly" => %{weight: 600, tracking: "-0.01em"},
    "minimal" => %{weight: 500, tracking: "0"},
    "impulse" => %{weight: 300, tracking: "0.02em"}
  }

  # Typography - font families, weights, and tracking
  # Uses Fonts module for DRY font-family declarations
  defp generate_typography(typography) do
    %{heading: heading_key, body: body_key} = Fonts.fonts_for_typography(typography)
    style = Map.get(@typography_styles, typography, @typography_styles["clean"])

    """
    --t-font-heading: #{Fonts.font_family(heading_key)};
      --t-font-body: #{Fonts.font_family(body_key)};
      --t-heading-weight: #{style.weight};
      --t-heading-tracking: #{style.tracking};
    """
  end

  # Shape - border radii
  defp generate_shape("soft") do
    """
    --t-radius-sm: 0.25rem;
      --t-radius-md: 0.5rem;
      --t-radius-lg: 0.75rem;
      --t-radius-button: 0.5rem;
      --t-radius-card: 0.75rem;
      --t-radius-input: 0.5rem;
      --t-radius-image: 0.5rem;
    """
  end

  defp generate_shape("sharp") do
    """
    --t-radius-sm: 0;
      --t-radius-md: 0;
      --t-radius-lg: 0;
      --t-radius-button: 0;
      --t-radius-card: 0;
      --t-radius-input: 0;
      --t-radius-image: 0;
    """
  end

  defp generate_shape("round") do
    """
    --t-radius-sm: 0.5rem;
      --t-radius-md: 0.75rem;
      --t-radius-lg: 1rem;
      --t-radius-button: 0.75rem;
      --t-radius-card: 1rem;
      --t-radius-input: 0.75rem;
      --t-radius-image: 0.75rem;
    """
  end

  defp generate_shape("pill") do
    """
    --t-radius-sm: 9999px;
      --t-radius-md: 9999px;
      --t-radius-lg: 1rem;
      --t-radius-button: 9999px;
      --t-radius-card: 1rem;
      --t-radius-input: 9999px;
      --t-radius-image: 0.75rem;
    """
  end

  # Fallback for any other shape value
  defp generate_shape(_), do: generate_shape("soft")

  # Density - spacing multiplier
  defp generate_density("balanced") do
    """
    --t-density: 1;
      --space-xs: 0.5rem;
      --space-sm: 0.75rem;
      --space-md: 1rem;
      --space-lg: 1.5rem;
      --space-xl: 2rem;
      --space-2xl: 3rem;
    """
  end

  defp generate_density("spacious") do
    """
    --t-density: 1.25;
      --space-xs: 0.625rem;
      --space-sm: 0.9375rem;
      --space-md: 1.25rem;
      --space-lg: 1.875rem;
      --space-xl: 2.5rem;
      --space-2xl: 3.75rem;
    """
  end

  defp generate_density("compact") do
    """
    --t-density: 0.85;
      --space-xs: 0.425rem;
      --space-sm: 0.6375rem;
      --space-md: 0.85rem;
      --space-lg: 1.275rem;
      --space-xl: 1.7rem;
      --space-2xl: 2.55rem;
    """
  end

  # Fallback for any other density value
  defp generate_density(_), do: generate_density("balanced")

  # Accent color with oklch breakdown
  defp generate_accent(hex_color) do
    {l, c, h} = hex_to_oklch(hex_color)

    """
      --t-accent-l: #{l}%;
      --t-accent-c: #{c};
      --t-accent-h: #{h};
    """
  end

  # Secondary colors
  defp generate_secondary_colors(settings) do
    """
      --t-secondary-accent: #{settings.secondary_accent_color};
      --t-sale-color: #{settings.sale_color};
    """
  end

  # Font size variations
  # Using 18px as base for better accessibility (WCAG recommends 18px+)
  # Small: 18px, Medium: 19px, Large: 20px
  defp generate_font_size("small") do
    "--t-font-size-scale: 1.125;"
  end

  defp generate_font_size("medium") do
    "--t-font-size-scale: 1.1875;"
  end

  defp generate_font_size("large") do
    "--t-font-size-scale: 1.25;"
  end

  # Heading weight (override typography default)
  defp generate_heading_weight("regular") do
    "--t-heading-weight-override: 400;"
  end

  defp generate_heading_weight("medium") do
    "--t-heading-weight-override: 500;"
  end

  defp generate_heading_weight("bold") do
    "--t-heading-weight-override: 700;"
  end

  # Layout width
  defp generate_layout_width("contained") do
    "--t-layout-max-width: 1100px;"
  end

  defp generate_layout_width("wide") do
    "--t-layout-max-width: 1400px;"
  end

  defp generate_layout_width("full") do
    "--t-layout-max-width: 100%;"
  end

  # Button style
  defp generate_button_style("filled") do
    "--t-button-style: filled;"
  end

  defp generate_button_style("outline") do
    "--t-button-style: outline;"
  end

  defp generate_button_style("soft") do
    "--t-button-style: soft;"
  end

  # Product text alignment
  defp generate_product_text_align("left") do
    "--t-product-text-align: left;"
  end

  defp generate_product_text_align("center") do
    "--t-product-text-align: center;"
  end

  # Image aspect ratio
  defp generate_image_aspect_ratio("square") do
    "--t-image-aspect-ratio: 1 / 1;"
  end

  defp generate_image_aspect_ratio("portrait") do
    "--t-image-aspect-ratio: 3 / 4;"
  end

  defp generate_image_aspect_ratio("landscape") do
    "--t-image-aspect-ratio: 4 / 3;"
  end

  # Convert hex colour to oklch (lightness%, chroma, hue°)
  # Pipeline: hex → sRGB → linear RGB → XYZ (D65) → oklab → oklch
  defp hex_to_oklch("#" <> hex), do: hex_to_oklch(hex)

  defp hex_to_oklch(hex) when byte_size(hex) == 6 do
    {r, ""} = Integer.parse(String.slice(hex, 0..1), 16)
    {g, ""} = Integer.parse(String.slice(hex, 2..3), 16)
    {b, ""} = Integer.parse(String.slice(hex, 4..5), 16)

    # sRGB to linear RGB (inverse gamma)
    lr = srgb_to_linear(r / 255)
    lg = srgb_to_linear(g / 255)
    lb = srgb_to_linear(b / 255)

    # Linear RGB to oklab via the Björn Ottosson method
    # First: linear sRGB → LMS (cube root of cone response)
    l_ = :math.pow(0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb, 1 / 3)
    m_ = :math.pow(0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb, 1 / 3)
    s_ = :math.pow(0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb, 1 / 3)

    # LMS to oklab
    ok_l = 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_
    ok_a = 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_
    ok_b = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_

    # oklab to oklch
    c = :math.sqrt(ok_a * ok_a + ok_b * ok_b)

    h =
      if c < 0.0001 do
        0.0
      else
        h_rad = :math.atan2(ok_b, ok_a)
        h_deg = h_rad * 180 / :math.pi()
        if h_deg < 0, do: h_deg + 360, else: h_deg
      end

    # Return as {lightness%, chroma, hue°} rounded for CSS
    {
      Float.round(ok_l * 100, 2),
      Float.round(c, 4),
      Float.round(h, 2)
    }
  end

  # Handle invalid hex values (neutral grey fallback)
  defp hex_to_oklch(_), do: {50.0, 0.0, 0.0}

  # sRGB gamma decompression (IEC 61966-2-1)
  defp srgb_to_linear(v) when v <= 0.04045, do: v / 12.92
  defp srgb_to_linear(v), do: :math.pow((v + 0.055) / 1.055, 2.4)
end
