defmodule Raxol.UI.Layout.Responsive do
  @moduledoc """
  Responsive layout system with breakpoint support for terminal UI components.

  Provides breakpoint-based responsive design with container queries, responsive
  typography, and adaptive spacing. Supports terminal size detection and adaptation.

  ## Breakpoints

  Default breakpoints based on terminal columns: `:xs` (0-39), `:sm` (40-79),
  `:md` (80-119), `:lg` (120-159), `:xl` (160+).

  ## Usage

      %{
        type: :responsive,
        attrs: %{
          breakpoints: %{
            xs: %{flex_direction: :column, gap: 5},
            md: %{flex_direction: :row, gap: 15}
          }
        },
        children: children
      }
  """

  alias Raxol.UI.Layout.{CSSGrid, Engine, Flexbox}

  # Default breakpoints (column-based for terminals)
  @default_breakpoints %{
    xs: %{min_width: 0, max_width: 39},
    sm: %{min_width: 40, max_width: 79},
    md: %{min_width: 80, max_width: 119},
    lg: %{min_width: 120, max_width: 159},
    xl: %{min_width: 160, max_width: :infinity}
  }

  @doc """
  Processes a responsive container, adapting layout based on available space.
  """
  def process_responsive(
        %{type: :responsive, children: children} = responsive,
        space,
        acc
      )
      when is_list(children) do
    attrs = Map.get(responsive, :attrs, %{})

    # Determine current breakpoint
    current_breakpoint = get_current_breakpoint(space, attrs)

    # Get styles for current breakpoint
    responsive_styles = get_responsive_styles(attrs, current_breakpoint)

    # Apply container queries if specified
    final_styles = apply_container_queries(responsive_styles, attrs, space)

    # Create adapted container
    adapted_container = create_adapted_container(responsive, final_styles)

    # Process with appropriate layout engine
    case Map.get(final_styles, :layout_type, :flex) do
      :flex ->
        Flexbox.process_flex(adapted_container, space, acc)

      :css_grid ->
        CSSGrid.process_css_grid(adapted_container, space, acc)

      _ ->
        # Process children with responsive styles applied
        process_responsive_children(children, space, acc, current_breakpoint)
    end
  end

  def process_responsive(_, _space, acc), do: acc

  @doc """
  Processes a responsive grid with column breakpoints.
  """
  def process_responsive_grid(
        %{type: :responsive_grid, children: children} = grid,
        space,
        acc
      )
      when is_list(children) do
    attrs = Map.get(grid, :attrs, %{})

    # Determine current breakpoint
    current_breakpoint = get_current_breakpoint(space, attrs)

    # Get responsive configuration
    columns =
      get_breakpoint_value(Map.get(attrs, :columns, 1), current_breakpoint)

    gap = get_breakpoint_value(Map.get(attrs, :gap, 10), current_breakpoint)

    # Create CSS Grid configuration
    grid_template_columns = create_grid_columns(columns)

    adapted_grid = %{
      type: :css_grid,
      attrs:
        Map.merge(attrs, %{
          grid_template_columns: grid_template_columns,
          gap: gap
        }),
      children: children
    }

    CSSGrid.process_css_grid(adapted_grid, space, acc)
  end

  def process_responsive_grid(_, _space, acc), do: acc

  @doc """
  Measures responsive containers.
  """
  def measure_responsive(%{type: :responsive} = responsive, available_space) do
    attrs = Map.get(responsive, :attrs, %{})
    current_breakpoint = get_current_breakpoint(available_space, attrs)
    responsive_styles = get_responsive_styles(attrs, current_breakpoint)

    final_styles =
      apply_container_queries(responsive_styles, attrs, available_space)

    adapted_container = create_adapted_container(responsive, final_styles)

    case Map.get(final_styles, :layout_type, :flex) do
      :flex ->
        Flexbox.measure_flex(adapted_container, available_space)

      :css_grid ->
        CSSGrid.measure_css_grid(adapted_container, available_space)

      _ ->
        measure_responsive_children(
          Map.get(responsive, :children, []),
          available_space,
          current_breakpoint
        )
    end
  end

  def measure_responsive(%{type: :responsive_grid} = grid, available_space) do
    attrs = Map.get(grid, :attrs, %{})
    current_breakpoint = get_current_breakpoint(available_space, attrs)

    columns =
      get_breakpoint_value(Map.get(attrs, :columns, 1), current_breakpoint)

    gap = get_breakpoint_value(Map.get(attrs, :gap, 10), current_breakpoint)

    grid_template_columns = create_grid_columns(columns)

    adapted_grid = %{
      type: :css_grid,
      attrs:
        Map.merge(attrs, %{
          grid_template_columns: grid_template_columns,
          gap: gap
        }),
      children: Map.get(grid, :children, [])
    }

    CSSGrid.measure_css_grid(adapted_grid, available_space)
  end

  def measure_responsive(_, _available_space), do: %{width: 0, height: 0}

  @doc """
  Gets the current breakpoint based on available space.
  """
  def get_current_breakpoint(space, attrs \\ %{}) do
    breakpoints = Map.get(attrs, :custom_breakpoints, @default_breakpoints)
    width = Map.get(space, :width, 0)

    # Find matching breakpoint
    Enum.find(breakpoints, fn {_name, config} ->
      min_width = Map.get(config, :min_width, 0)
      max_width = Map.get(config, :max_width, :infinity)

      width >= min_width and (max_width == :infinity or width <= max_width)
    end)
    |> case do
      {name, _config} -> name
      # Default fallback
      nil -> :md
    end
  end

  @doc """
  Applies responsive typography scaling.
  """
  def apply_responsive_typography(attrs, breakpoint) do
    base_font_size = Map.get(attrs, :font_size, :medium)
    responsive_font = Map.get(attrs, :responsive_font_size, %{})

    final_font_size =
      get_breakpoint_value(responsive_font, breakpoint, base_font_size)

    # Apply scaling factors based on breakpoint
    scale_factor =
      case breakpoint do
        :xs -> 0.8
        :sm -> 0.9
        :md -> 1.0
        :lg -> 1.1
        :xl -> 1.2
      end

    scaled_font_size = apply_font_scale(final_font_size, scale_factor)

    Map.put(attrs, :font_size, scaled_font_size)
  end

  @doc """
  Creates responsive spacing based on breakpoint.
  """
  def get_responsive_spacing(spacing_config, breakpoint) do
    base_spacing =
      case breakpoint do
        :xs -> 4
        :sm -> 8
        :md -> 12
        :lg -> 16
        :xl -> 20
      end

    case spacing_config do
      value when is_integer(value) ->
        value

      %{} = config ->
        get_breakpoint_value(config, breakpoint, base_spacing)

      _ ->
        base_spacing
    end
  end

  # Private helper functions

  defp get_responsive_styles(attrs, breakpoint) do
    breakpoint_styles = Map.get(attrs, :breakpoints, %{})

    base_styles =
      Map.drop(attrs, [:breakpoints, :container_query, :custom_breakpoints])

    # Get styles for current breakpoint
    current_styles = Map.get(breakpoint_styles, breakpoint, %{})

    # Merge base styles with breakpoint-specific styles
    Map.merge(base_styles, current_styles)
  end

  defp apply_container_queries(styles, attrs, space) do
    container_query = Map.get(attrs, :container_query, %{})

    case container_query do
      %{} when container_query == %{} ->
        styles

      _ ->
        case evaluate_container_query(container_query, space) do
          true ->
            query_styles = Map.get(container_query, :styles, %{})
            Map.merge(styles, query_styles)

          false ->
            styles
        end
    end
  end

  defp evaluate_container_query(query, space) do
    conditions = [
      evaluate_size_condition(
        :min_width,
        Map.get(query, :min_width),
        space.width
      ),
      evaluate_size_condition(
        :max_width,
        Map.get(query, :max_width),
        space.width
      ),
      evaluate_size_condition(
        :min_height,
        Map.get(query, :min_height),
        space.height
      ),
      evaluate_size_condition(
        :max_height,
        Map.get(query, :max_height),
        space.height
      )
    ]

    # All non-nil conditions must be true
    conditions
    |> Enum.filter(&(&1 != nil))
    |> Enum.all?(&(&1 == true))
  end

  defp evaluate_size_condition(_condition, nil, _value), do: nil

  defp evaluate_size_condition(:min_width, min_width, width),
    do: width >= min_width

  defp evaluate_size_condition(:max_width, max_width, width),
    do: width <= max_width

  defp evaluate_size_condition(:min_height, min_height, height),
    do: height >= min_height

  defp evaluate_size_condition(:max_height, max_height, height),
    do: height <= max_height

  defp create_adapted_container(original, styles) do
    layout_type = Map.get(styles, :layout_type, :flex)

    adapted_type =
      case layout_type do
        :flex -> :flex
        :css_grid -> :css_grid
        _ -> :flex
      end

    %{original | type: adapted_type, attrs: styles}
  end

  defp get_breakpoint_value(config, breakpoint, default \\ nil)

  defp get_breakpoint_value(value, _breakpoint, _default)
       when not is_map(value) do
    value
  end

  defp get_breakpoint_value(config, breakpoint, default) when is_map(config) do
    # Try current breakpoint first
    case Map.get(config, breakpoint) do
      nil ->
        # Fall back to smaller breakpoints
        fallback_value = find_fallback_breakpoint(config, breakpoint)
        fallback_value || default

      value ->
        value
    end
  end

  defp find_fallback_breakpoint(config, breakpoint) do
    # Define breakpoint hierarchy for fallback
    hierarchy = [:xs, :sm, :md, :lg, :xl]
    current_index = Enum.find_index(hierarchy, &(&1 == breakpoint)) || 2

    # Look for values in smaller breakpoints
    hierarchy
    |> Enum.take(current_index)
    |> Enum.reverse()
    |> Enum.find_value(fn bp ->
      Map.get(config, bp)
    end)
  end

  defp create_grid_columns(column_count) do
    List.duplicate("1fr", column_count)
    |> Enum.join(" ")
  end

  defp process_responsive_children(children, space, acc, current_breakpoint) do
    Enum.reduce(children, acc, fn child, child_acc ->
      # Apply responsive attributes to child
      responsive_child = apply_child_responsiveness(child, current_breakpoint)

      # Process with layout engine
      Engine.process_element(responsive_child, space, child_acc)
    end)
  end

  defp apply_child_responsiveness(child, breakpoint) do
    child_attrs = Map.get(child, :attrs, %{})
    responsive_attrs = Map.get(child_attrs, :responsive, %{})

    case responsive_attrs do
      %{} when responsive_attrs == %{} ->
        child

      _ ->
        current_responsive_styles =
          get_breakpoint_value(responsive_attrs, breakpoint, %{})

        final_attrs =
          case Map.has_key?(current_responsive_styles, :font_size) or
                 Map.has_key?(child_attrs, :font_size) do
            true ->
              apply_responsive_typography(
                Map.merge(child_attrs, current_responsive_styles),
                breakpoint
              )

            false ->
              Map.merge(child_attrs, current_responsive_styles)
          end

        %{child | attrs: final_attrs}
    end
  end

  defp measure_responsive_children(children, space, current_breakpoint) do
    case children do
      [] ->
        %{width: 0, height: 0}

      _ ->
        responsive_children =
          Enum.map(children, fn child ->
            apply_child_responsiveness(child, current_breakpoint)
          end)

        total_height =
          Enum.reduce(responsive_children, 0, fn child, acc ->
            dims = Engine.measure_element(child, space)
            acc + dims.height
          end)

        max_width =
          Enum.reduce(responsive_children, 0, fn child, acc ->
            dims = Engine.measure_element(child, space)
            max(acc, dims.width)
          end)

        %{width: max_width, height: total_height}
    end
  end

  defp apply_font_scale(:small, scale), do: scale_font_size(:small, scale)
  defp apply_font_scale(:medium, scale), do: scale_font_size(:medium, scale)
  defp apply_font_scale(:large, scale), do: scale_font_size(:large, scale)
  defp apply_font_scale(size, _scale) when is_integer(size), do: size
  defp apply_font_scale(other, _scale), do: other

  defp scale_font_size(:small, scale) when scale < 0.9, do: :small
  defp scale_font_size(:small, scale) when scale >= 1.1, do: :medium
  defp scale_font_size(:small, _scale), do: :small

  defp scale_font_size(:medium, scale) when scale < 0.9, do: :small
  defp scale_font_size(:medium, scale) when scale >= 1.1, do: :large
  defp scale_font_size(:medium, _scale), do: :medium

  defp scale_font_size(:large, scale) when scale < 0.9, do: :medium
  defp scale_font_size(:large, _scale), do: :large
end
