defmodule Raxol.Terminal.Commands.CommandServer.BufferLineOps do
  @moduledoc false
  @compile {:no_warn_undefined, Raxol.Terminal.Commands.CommandServer.Helpers}

  require Raxol.Core.Runtime.Log

  alias Raxol.Terminal.Cell
  alias Raxol.Terminal.Commands.CommandServer.Helpers
  alias Raxol.Terminal.Emulator

  def handle_insert_lines(emulator, %{params: params}, _context) do
    count = Helpers.get_param(params, 0, 1)
    perform_insert_lines(emulator, count)
  end

  def handle_delete_lines(emulator, %{params: params}, _context) do
    count = Helpers.get_param(params, 0, 1)
    perform_delete_lines(emulator, count)
  end

  def handle_delete_characters(emulator, %{params: params}, _context) do
    count = Helpers.get_param(params, 0, 1)
    perform_delete_characters(emulator, count)
  end

  def handle_insert_characters(emulator, %{params: params}, _context) do
    count = Helpers.get_param(params, 0, 1)
    perform_insert_characters(emulator, count)
  end

  defp perform_insert_lines(emulator, count) do
    active_buffer = Emulator.get_screen_buffer(emulator)
    {y, _} = Helpers.get_cursor_position(emulator)
    style = Helpers.get_default_style(active_buffer)

    case Map.get(emulator, :scroll_region) do
      {scroll_top, scroll_bottom} when y >= scroll_top and y <= scroll_bottom ->
        updated_buffer =
          insert_lines_within_scroll_region(
            active_buffer,
            y,
            count,
            style,
            scroll_top,
            scroll_bottom
          )

        {:ok, Helpers.update_emulator_buffer(emulator, updated_buffer)}

      _ ->
        updated_buffer = insert_lines_normal(active_buffer, y, count, style)
        {:ok, Helpers.update_emulator_buffer(emulator, updated_buffer)}
    end
  end

  defp perform_delete_lines(emulator, count) do
    active_buffer = Emulator.get_screen_buffer(emulator)
    {y, _} = Helpers.get_cursor_position(emulator)
    style = Helpers.get_default_style(active_buffer)

    case Map.get(emulator, :scroll_region) do
      {scroll_top, scroll_bottom} when y >= scroll_top and y <= scroll_bottom ->
        updated_buffer =
          delete_lines_within_scroll_region(
            active_buffer,
            y,
            count,
            style,
            scroll_top,
            scroll_bottom
          )

        {:ok, Helpers.update_emulator_buffer(emulator, updated_buffer)}

      {_scroll_top, _scroll_bottom} ->
        {:ok, emulator}

      nil ->
        updated_buffer = delete_lines_normal(active_buffer, y, count, style)
        {:ok, Helpers.update_emulator_buffer(emulator, updated_buffer)}
    end
  end

  defp perform_delete_characters(emulator, count) do
    active_buffer = Emulator.get_screen_buffer(emulator)
    {y, x} = Helpers.get_cursor_position(emulator)
    style = Helpers.get_default_style(active_buffer)
    updated_buffer = delete_characters(active_buffer, y, x, count, style)
    {:ok, Helpers.update_emulator_buffer(emulator, updated_buffer)}
  end

  defp perform_insert_characters(emulator, count) do
    active_buffer = Emulator.get_screen_buffer(emulator)
    {y, x} = Helpers.get_cursor_position(emulator)
    style = Helpers.get_default_style(active_buffer)
    updated_buffer = insert_characters(active_buffer, y, x, count, style)
    {:ok, Helpers.update_emulator_buffer(emulator, updated_buffer)}
  end

  defp insert_lines_normal(buffer, y, count, style) do
    blank_cell = Cell.new(" ", style)
    blank_line = List.duplicate(blank_cell, buffer.width)
    blank_lines = List.duplicate(blank_line, count)

    {lines_before, lines_after} = Enum.split(buffer.cells, y)
    lines_to_keep = max(0, buffer.height - y - count)
    kept_lines = Enum.take(lines_after, lines_to_keep)

    final_cells = lines_before ++ blank_lines ++ kept_lines
    %{buffer | cells: final_cells}
  end

  defp insert_lines_within_scroll_region(
         buffer,
         y,
         count,
         style,
         scroll_top,
         scroll_bottom
       ) do
    blank_cell = Cell.new(" ", style)
    blank_line = List.duplicate(blank_cell, buffer.width)
    blank_lines_to_insert = List.duplicate(blank_line, count)

    {lines_before_scroll, rest} = Enum.split(buffer.cells, scroll_top)

    {scroll_region_lines, lines_after_scroll} =
      Enum.split(rest, scroll_bottom - scroll_top + 1)

    insertion_point = y - scroll_top

    {scroll_before, scroll_after} =
      Enum.split(scroll_region_lines, insertion_point)

    max_lines_in_region = scroll_bottom - scroll_top + 1
    available_space_after = max_lines_in_region - insertion_point - count

    kept_lines =
      if available_space_after > 0 do
        Enum.take(scroll_after, available_space_after)
      else
        []
      end

    new_scroll_region = scroll_before ++ blank_lines_to_insert ++ kept_lines

    padded_scroll_region =
      if length(new_scroll_region) < max_lines_in_region do
        new_scroll_region ++
          List.duplicate(
            blank_line,
            max_lines_in_region - length(new_scroll_region)
          )
      else
        Enum.take(new_scroll_region, max_lines_in_region)
      end

    final_cells =
      lines_before_scroll ++ padded_scroll_region ++ lines_after_scroll

    %{buffer | cells: final_cells}
  end

  defp delete_lines_within_scroll_region(
         buffer,
         y,
         count,
         style,
         scroll_top,
         scroll_bottom
       ) do
    blank_cell = Cell.new(" ", style)
    blank_line = List.duplicate(blank_cell, buffer.width)
    blank_lines_to_add = List.duplicate(blank_line, count)

    {lines_before_scroll, rest} = Enum.split(buffer.cells, scroll_top)

    {scroll_region_lines, lines_after_scroll} =
      Enum.split(rest, scroll_bottom - scroll_top + 1)

    deletion_point = y - scroll_top

    {scroll_before, scroll_after} =
      Enum.split(scroll_region_lines, deletion_point)

    remaining_lines = Enum.drop(scroll_after, count)

    max_lines_in_region = scroll_bottom - scroll_top + 1
    new_scroll_region = scroll_before ++ remaining_lines ++ blank_lines_to_add

    padded_scroll_region =
      if length(new_scroll_region) < max_lines_in_region do
        new_scroll_region ++
          List.duplicate(
            blank_line,
            max_lines_in_region - length(new_scroll_region)
          )
      else
        Enum.take(new_scroll_region, max_lines_in_region)
      end

    final_cells =
      lines_before_scroll ++ padded_scroll_region ++ lines_after_scroll

    %{buffer | cells: final_cells}
  end

  defp delete_lines_normal(buffer, y, count, style) do
    blank_cell = Cell.new(" ", style)
    blank_line = List.duplicate(blank_cell, buffer.width)

    {lines_before, lines_after} = Enum.split(buffer.cells, y)
    remaining_lines = Enum.drop(lines_after, count)

    blank_lines_needed = min(count, buffer.height - y)
    blank_lines = List.duplicate(blank_line, blank_lines_needed)

    final_cells = lines_before ++ remaining_lines ++ blank_lines
    %{buffer | cells: final_cells}
  end

  defp delete_characters(buffer, y, x, count, style) do
    case Enum.at(buffer.cells, y) do
      nil ->
        buffer

      line ->
        {chars_before, chars_after} = Enum.split(line, x)
        remaining_chars = Enum.drop(chars_after, count)

        blank_cell = Cell.new(" ", style)
        blank_chars = List.duplicate(blank_cell, count)

        new_line = chars_before ++ remaining_chars ++ blank_chars
        final_line = Enum.take(new_line, buffer.width)

        updated_cells = List.replace_at(buffer.cells, y, final_line)
        %{buffer | cells: updated_cells}
    end
  end

  defp insert_characters(buffer, y, x, count, style) do
    case Enum.at(buffer.cells, y) do
      nil ->
        buffer

      line ->
        {chars_before, chars_after} = Enum.split(line, x)

        blank_cell = Cell.new(" ", style)
        blank_chars = List.duplicate(blank_cell, count)

        new_line = chars_before ++ blank_chars ++ chars_after
        final_line = Enum.take(new_line, buffer.width)

        updated_cells = List.replace_at(buffer.cells, y, final_line)
        %{buffer | cells: updated_cells}
    end
  end
end
