defmodule Raxol.Terminal.ScreenBuffer.DataAdapter do
  @moduledoc """
  Data structure adapter for ScreenBuffer operations.

  Provides bidirectional conversion between the two buffer formats:
  - ScreenBuffer.Core format: `:cells` (list of lists of Cell structs)
  - LineOperations format: `:lines` (map with integer keys to line lists)

  This adapter allows seamless interoperability between different buffer
  operation layers without requiring architectural changes.
  """

  @default_width Raxol.Core.Defaults.terminal_width()
  @default_height Raxol.Core.Defaults.terminal_height()

  alias Raxol.Terminal.ANSI.TextFormatting
  alias Raxol.Terminal.Cell

  @doc """
  Convert buffer from cells format to lines format.

  Transforms `buffer.cells` (list of lists) into a `:lines` map
  where keys are row indices and values are cell lists.
  """
  def cells_to_lines(buffer) when is_map(buffer) do
    case Map.get(buffer, :cells) do
      nil ->
        # Already has lines format or invalid buffer
        buffer

      cells when is_list(cells) ->
        lines_map =
          cells
          |> Enum.with_index()
          |> Enum.into(%{}, fn {line_cells, index} -> {index, line_cells} end)

        Map.put(buffer, :lines, lines_map)

      _ ->
        # Invalid cells format
        buffer
    end
  end

  @doc """
  Convert buffer from lines format back to cells format.

  Transforms `:lines` map back into `buffer.cells` (list of lists).
  """
  def lines_to_cells(buffer, lines_map)
      when is_map(buffer) and is_map(lines_map) do
    height = Map.get(buffer, :height, @default_height)

    cells =
      0..(height - 1)
      |> Enum.map(fn row_index ->
        Map.get(lines_map, row_index, [])
      end)

    buffer
    |> Map.put(:cells, cells)
    # Remove temporary lines map
    |> Map.delete(:lines)
  end

  @doc """
  Execute an operation function with the buffer temporarily in lines format.

  This is the key function that allows LineOperations to work with
  ScreenBuffer.Core structures without permanent conversion.

  The operation function can return either:
  - A modified buffer map
  - A tuple where the second element is the modified buffer map
  """
  def with_lines_format(buffer, operation_fn)
      when is_map(buffer) and is_function(operation_fn, 1) do
    # Convert to lines format
    buffer_with_lines = cells_to_lines(buffer)

    # Execute operation
    result = operation_fn.(buffer_with_lines)

    # Handle different return types
    case result do
      {data, result_buffer} when is_map(result_buffer) ->
        # Operation returned a tuple with buffer as second element
        case Map.get(result_buffer, :lines) do
          lines_map when is_map(lines_map) ->
            converted_buffer = lines_to_cells(result_buffer, lines_map)
            {data, converted_buffer}

          _ ->
            {data, result_buffer}
        end

      result_buffer when is_map(result_buffer) ->
        # Operation returned just a buffer
        case Map.get(result_buffer, :lines) do
          lines_map when is_map(lines_map) ->
            lines_to_cells(result_buffer, lines_map)

          _ ->
            # Operation didn't modify lines, return as-is
            result_buffer
        end

      other ->
        # Return as-is for any other case
        other
    end
  end

  @doc """
  Check if buffer uses cells format (list of lists).
  """
  def has_cells_format?(buffer) when is_map(buffer) do
    case Map.get(buffer, :cells) do
      cells when is_list(cells) -> true
      _ -> false
    end
  end

  @doc """
  Check if buffer uses lines format (map).
  """
  def has_lines_format?(buffer) when is_map(buffer) do
    case Map.get(buffer, :lines) do
      lines when is_map(lines) -> true
      _ -> false
    end
  end

  @doc """
  Get a line from buffer regardless of format.
  """
  def get_line(buffer, y, default) when is_map(buffer) and is_integer(y) do
    cond do
      has_lines_format?(buffer) ->
        Map.get(buffer.lines, y, default)

      has_cells_format?(buffer) ->
        if y >= 0 and y < length(buffer.cells) do
          Enum.at(buffer.cells, y, default)
        else
          default
        end

      true ->
        default
    end
  end

  @doc """
  Set a line in buffer regardless of format.
  """
  def set_line(buffer, y, line)
      when is_map(buffer) and is_integer(y) and is_list(line) do
    cond do
      has_lines_format?(buffer) ->
        %{buffer | lines: Map.put(buffer.lines, y, line)}

      has_cells_format?(buffer) ->
        replace_cell_line(buffer, y, line)

      true ->
        initialize_and_set_line(buffer, y, line)
    end
  end

  defp replace_cell_line(buffer, y, line) do
    if y >= 0 and y < length(buffer.cells) do
      %{buffer | cells: List.replace_at(buffer.cells, y, line)}
    else
      buffer
    end
  end

  defp initialize_and_set_line(buffer, y, line) do
    height = Map.get(buffer, :height, @default_height)
    width = Map.get(buffer, :width, @default_width)
    default_style = Map.get(buffer, :default_style, %TextFormatting{})
    cells = create_empty_cells(width, height, default_style)

    updated_cells =
      if y >= 0 and y < height do
        List.replace_at(cells, y, line)
      else
        cells
      end

    Map.put(buffer, :cells, updated_cells)
  end

  @doc """
  Create empty cells structure for initialization.
  """
  def create_empty_cells(width, height, default_style) do
    empty_line = create_empty_line(width, default_style)
    List.duplicate(empty_line, height)
  end

  @doc """
  Create an empty line with the specified width and style.
  """
  def create_empty_line(width, default_style) do
    blank_cell = %Cell{
      char: " ",
      style: default_style || %TextFormatting{},
      dirty: false,
      wide_placeholder: false,
      sixel: false
    }

    List.duplicate(blank_cell, width)
  end
end
