defmodule Raxol.UI.Components.Input.TextWrappingCached do
  @moduledoc """
  Cached version of text wrapping utilities with performance optimizations.

  This module extends the basic TextWrapping functionality with caching
  for improved performance on repeated operations.
  """

  # Cache for visual widths to avoid recalculating
  @cache_name :text_wrapping_cache

  @doc """
  Initializes the cache if not already present.
  """
  def ensure_cache do
    Raxol.Performance.ETS.TableHelper.ensure_table(@cache_name)
  end

  @doc """
  Wraps text by visual width with caching.
  """
  def wrap_line_by_visual_width(text, width)
      when is_binary(text) and is_integer(width) and width > 0 do
    _ = ensure_cache()

    cache_key = {:visual_width_wrap, text, width}

    case :ets.lookup(@cache_name, cache_key) do
      [{^cache_key, result}] ->
        result

      [] ->
        # Use basic word wrapping as approximation for visual width
        result = wrap_by_visual_width_impl(text, width)
        :ets.insert(@cache_name, {cache_key, result})
        result
    end
  end

  def wrap_line_by_visual_width("", _width), do: []

  @doc """
  Wraps text by word boundaries with caching.
  """
  def wrap_line_by_word(text, width)
      when is_binary(text) and is_integer(width) and width > 0 do
    _ = ensure_cache()

    cache_key = {:word_wrap, text, width}

    case :ets.lookup(@cache_name, cache_key) do
      [{^cache_key, result}] ->
        result

      [] ->
        result = wrap_by_word_impl(text, width)
        :ets.insert(@cache_name, {cache_key, result})
        result
    end
  end

  def wrap_line_by_word("", _width), do: []

  @doc """
  Gets the visual width of text with caching.
  """
  def get_visual_width(text) when is_binary(text) do
    _ = ensure_cache()

    cache_key = {:visual_width, text}

    case :ets.lookup(@cache_name, cache_key) do
      [{^cache_key, width}] ->
        width

      [] ->
        width = calculate_visual_width(text)
        :ets.insert(@cache_name, {cache_key, width})
        width
    end
  end

  @doc """
  Warms up the cache with common strings.
  """
  def warmup_cache(strings) when is_list(strings) do
    _ = ensure_cache()

    Enum.each(strings, fn string ->
      # Pre-calculate and cache visual width
      get_visual_width(string)

      # Pre-calculate common wrap widths
      for width <- [20, 40, 60, 80] do
        wrap_line_by_word(string, width)
        wrap_line_by_visual_width(string, width)
      end
    end)

    :ok
  end

  @doc """
  Wraps text based on pixel width and font metrics.
  """
  def wrap_to_pixel_width(text, pixel_width, font_manager) do
    _ = ensure_cache()

    cache_key = {:pixel_wrap, text, pixel_width, font_manager}

    case :ets.lookup(@cache_name, cache_key) do
      [{^cache_key, result}] ->
        result

      [] ->
        # Estimate character width from pixel width and font
        char_width = estimate_char_width_from_pixels(pixel_width, font_manager)
        result = wrap_line_by_word(text, char_width)
        :ets.insert(@cache_name, {cache_key, result})
        result
    end
  end

  # Private implementation functions

  defp wrap_by_visual_width_impl("", _width), do: []

  defp wrap_by_visual_width_impl(text, width) do
    # Split text into words and wrap based on visual width
    words = String.split(text, " ")
    wrap_words_by_visual_width(words, width, [], [])
  end

  defp wrap_words_by_visual_width([], _width, [], lines) do
    Enum.reverse(lines)
  end

  defp wrap_words_by_visual_width([], _width, current_line, lines) do
    completed_line = Enum.join(Enum.reverse(current_line), " ")
    Enum.reverse([completed_line | lines])
  end

  defp wrap_words_by_visual_width([word | rest], width, current_line, lines) do
    new_text = join_with_word(current_line, word)

    if calculate_visual_width(new_text) <= width do
      wrap_words_by_visual_width(rest, width, [word | current_line], lines)
    else
      handle_visual_overflow(word, rest, width, current_line, lines)
    end
  end

  defp join_with_word([], word), do: word

  defp join_with_word(current_line, word),
    do: Enum.join(Enum.reverse(current_line), " ") <> " " <> word

  defp handle_visual_overflow(word, rest, width, [], lines) do
    if calculate_visual_width(word) > width do
      break_and_continue_visual(word, rest, width, lines)
    else
      wrap_words_by_visual_width(rest, width, [word], lines)
    end
  end

  defp handle_visual_overflow(word, rest, width, current_line, lines) do
    completed_line = Enum.join(Enum.reverse(current_line), " ")

    wrap_words_by_visual_width([word | rest], width, [], [
      completed_line | lines
    ])
  end

  defp break_and_continue_visual(word, rest, width, lines) do
    [first_part | remaining_parts] = break_word_by_visual_width(word, width)
    updated_rest = remaining_parts ++ rest
    wrap_words_by_visual_width(updated_rest, width, [], [first_part | lines])
  end

  defp break_word_by_visual_width(word, width) do
    graphemes = String.graphemes(word)
    break_graphemes_by_visual_width(graphemes, width, [], [], 0)
  end

  defp break_graphemes_by_visual_width(
         [],
         _width,
         current_part,
         parts,
         _current_width
       ) do
    final_parts =
      if current_part != [],
        do: [Enum.join(Enum.reverse(current_part), "") | parts],
        else: parts

    Enum.reverse(final_parts)
  end

  defp break_graphemes_by_visual_width(
         [char | rest],
         width,
         current_part,
         parts,
         current_width
       ) do
    char_width = calculate_visual_width(char)
    new_width = current_width + char_width

    if new_width <= width do
      break_graphemes_by_visual_width(
        rest,
        width,
        [char | current_part],
        parts,
        new_width
      )
    else
      # Complete current part
      completed_part =
        if current_part != [],
          do: Enum.join(Enum.reverse(current_part), ""),
          else: ""

      new_parts =
        if completed_part != "", do: [completed_part | parts], else: parts

      break_graphemes_by_visual_width([char | rest], width, [], new_parts, 0)
    end
  end

  defp wrap_by_word_impl(text, width) do
    # Simple word wrapping implementation
    words = String.split(text, " ")
    wrap_words(words, width, [], [])
  end

  defp wrap_words([], _width, current_line, lines) do
    final_lines =
      if current_line != [],
        do: [Enum.join(Enum.reverse(current_line), " ") | lines],
        else: lines

    Enum.reverse(final_lines)
  end

  defp wrap_words([word | rest], width, current_line, lines) do
    new_text = join_with_word(current_line, word)

    if Raxol.UI.TextMeasure.display_width(new_text) <= width do
      wrap_words(rest, width, [word | current_line], lines)
    else
      handle_word_overflow(word, rest, width, current_line, lines)
    end
  end

  defp handle_word_overflow(word, rest, width, current_line, lines) do
    new_lines = flush_current_line(current_line, lines)

    if Raxol.UI.TextMeasure.display_width(word) > width do
      word_chunks = break_long_word(word, width)

      wrap_words(
        rest,
        width,
        [List.last(word_chunks)],
        new_lines ++ Enum.reverse(Enum.drop(word_chunks, -1))
      )
    else
      wrap_words(rest, width, [word], new_lines)
    end
  end

  defp flush_current_line([], lines), do: lines

  defp flush_current_line(current_line, lines),
    do: [Enum.join(Enum.reverse(current_line), " ") | lines]

  defp break_long_word(word, width) do
    graphemes = String.graphemes(word)
    chunk_graphemes(graphemes, width, [], [])
  end

  defp chunk_graphemes([], _width, current_chunk, chunks) do
    final_chunks =
      if current_chunk != [],
        do: [Enum.join(Enum.reverse(current_chunk), "") | chunks],
        else: chunks

    Enum.reverse(final_chunks)
  end

  defp chunk_graphemes([char | rest], width, current_chunk, chunks) do
    current_text = Enum.join(Enum.reverse(current_chunk), "")
    char_w = Raxol.UI.TextMeasure.char_display_width(char)

    if Raxol.UI.TextMeasure.display_width(current_text) + char_w > width do
      chunk_graphemes([char | rest], width, [], [current_text | chunks])
    else
      chunk_graphemes(rest, width, [char | current_chunk], chunks)
    end
  end

  defp calculate_visual_width(text) do
    Raxol.UI.TextMeasure.display_width(text)
  end

  defp estimate_char_width_from_pixels(pixel_width, font_manager) do
    # Rough estimation: assume monospace font with ~8 pixels per character
    char_pixel_width =
      case font_manager do
        %{size: size} when is_number(size) -> max(size * 0.6, 6)
        _ -> 8
      end

    max(div(pixel_width, trunc(char_pixel_width)), 1)
  end
end
