defmodule Raxol.Terminal.ANSI.KittyParser do
  @moduledoc """
  Handles the parsing logic for Kitty graphics protocol sequences.

  The Kitty graphics protocol uses APC (Application Program Command) sequences
  with the format: `<ESC>_G<control-data>;<payload><ESC>\\`

  ## Control Data Format

  Control data consists of key=value pairs separated by commas:
  - `a` - Action: t (transmit), T (transmit+display), d (delete), etc.
  - `f` - Format: 24 (RGB), 32 (RGBA), 100 (PNG)
  - `o` - Compression: z (zlib)
  - `t` - Transmission: d (direct), f (file), t (temp file), s (shared memory)
  - `i` - Image ID
  - `p` - Placement ID
  - `q` - Quiet mode (0, 1, or 2)
  - `s` - Width in pixels
  - `v` - Height in pixels
  - `m` - More data follows (0 or 1)

  ## Example

      iex> state = KittyParser.ParserState.new()
      iex> {:ok, state} = KittyParser.parse("a=t,f=32,s=100,v=100;base64data", state)
  """

  alias Raxol.Core.Runtime.Log

  defmodule ParserState do
    @moduledoc """
    Represents the state during parsing of a Kitty graphics data stream.
    Tracks control parameters, chunked data, and image buffers.
    """

    @type action ::
            :transmit | :transmit_display | :display | :delete | :query | :frame
    @type format :: :rgb | :rgba | :png | :unknown
    @type compression :: :none | :zlib
    @type transmission :: :direct | :file | :temp_file | :shared_memory

    @type t :: %__MODULE__{
            action: action(),
            format: format(),
            compression: compression(),
            transmission: transmission(),
            image_id: non_neg_integer() | nil,
            placement_id: non_neg_integer() | nil,
            width: non_neg_integer() | nil,
            height: non_neg_integer() | nil,
            x_offset: non_neg_integer(),
            y_offset: non_neg_integer(),
            cell_x: non_neg_integer() | nil,
            cell_y: non_neg_integer() | nil,
            z_index: integer(),
            quiet: 0 | 1 | 2,
            more_data: boolean(),
            chunk_data: binary(),
            pixel_buffer: binary(),
            errors: [term()],
            raw_control: binary()
          }

    defstruct action: :transmit,
              format: :rgba,
              compression: :none,
              transmission: :direct,
              image_id: nil,
              placement_id: nil,
              width: nil,
              height: nil,
              x_offset: 0,
              y_offset: 0,
              cell_x: nil,
              cell_y: nil,
              z_index: 0,
              quiet: 0,
              more_data: false,
              chunk_data: <<>>,
              pixel_buffer: <<>>,
              errors: [],
              raw_control: <<>>

    @doc """
    Create a new parser state with default values.
    """
    @spec new() :: %__MODULE__{
            action: :transmit,
            format: :rgba,
            compression: :none,
            transmission: :direct,
            image_id: nil,
            placement_id: nil,
            width: nil,
            height: nil,
            x_offset: 0,
            y_offset: 0,
            cell_x: nil,
            cell_y: nil,
            z_index: 0,
            quiet: 0,
            more_data: false,
            chunk_data: <<>>,
            pixel_buffer: <<>>,
            errors: [],
            raw_control: <<>>
          }
    def new, do: %__MODULE__{}

    @doc """
    Create a new parser state with initial dimensions.
    """
    @spec new(pos_integer(), pos_integer()) :: t()
    def new(width, height) do
      %__MODULE__{width: width, height: height}
    end

    @doc """
    Reset the parser state for a new image while preserving accumulated data.
    """
    @spec reset(t()) :: t()
    def reset(%__MODULE__{} = state) do
      %{
        state
        | action: :transmit,
          format: :rgba,
          compression: :none,
          more_data: false,
          errors: [],
          raw_control: <<>>
      }
    end
  end

  # ============================================================================
  # Public API
  # ============================================================================

  @doc """
  Parse a Kitty graphics sequence.

  Expects data in the format: `<control-data>;<payload>` (without the APC wrapper).

  ## Examples

      iex> state = KittyParser.ParserState.new()
      iex> {:ok, state} = KittyParser.parse("a=t,f=32,s=100,v=100;base64data", state)
      iex> state.action
      :transmit
  """
  @spec parse(binary(), ParserState.t()) ::
          {:ok, ParserState.t()} | {:error, atom(), ParserState.t()}
  def parse(<<>>, state), do: {:ok, state}

  def parse(data, state) when is_binary(data) do
    case split_control_payload(data) do
      {control_data, payload} ->
        state = %{state | raw_control: control_data}

        case parse_control_data(control_data, state) do
          {:ok, parsed_state} ->
            handle_payload(payload, parsed_state)
        end

      :error ->
        {:error, :invalid_format, add_error(state, :invalid_format)}
    end
  end

  @doc """
  Parse only the control data portion of a Kitty sequence.

  ## Examples

      iex> {:ok, state} = KittyParser.parse_control_data("a=t,f=32,s=100,v=100", %ParserState{})
      iex> state.action
      :transmit
  """
  @spec parse_control_data(binary(), ParserState.t()) ::
          {:ok, ParserState.t()} | {:error, atom(), ParserState.t()}
  def parse_control_data(<<>>, state), do: {:ok, state}

  def parse_control_data(data, state) do
    pairs = String.split(data, ",", trim: true)
    parse_key_value_pairs(pairs, state)
  end

  @doc """
  Decode a base64-encoded payload.

  ## Examples

      iex> {:ok, decoded} = KittyParser.decode_base64_payload("SGVsbG8=")
      iex> decoded
      "Hello"
  """
  @spec decode_base64_payload(binary()) ::
          {:ok, binary()} | {:error, :invalid_base64}
  def decode_base64_payload(payload) when is_binary(payload) do
    case Base.decode64(payload) do
      {:ok, decoded} -> {:ok, decoded}
      :error -> {:error, :invalid_base64}
    end
  end

  @doc """
  Handle chunked data accumulation for multi-part transmissions.

  When `m=1` is set, data is accumulated. When `m=0`, the complete
  data is finalized.

  ## Examples

      iex> state = %ParserState{more_data: true, chunk_data: "part1"}
      iex> state = KittyParser.handle_chunked_data("part2", state)
      iex> state.chunk_data
      "part1part2"
  """
  @spec handle_chunked_data(binary(), ParserState.t()) :: ParserState.t()
  def handle_chunked_data(new_data, %ParserState{more_data: true} = state) do
    %{state | chunk_data: state.chunk_data <> new_data}
  end

  def handle_chunked_data(new_data, %ParserState{more_data: false} = state) do
    complete_data = state.chunk_data <> new_data
    %{state | chunk_data: <<>>, pixel_buffer: complete_data}
  end

  @doc """
  Decompress data if compression is enabled.

  ## Examples

      iex> compressed = :zlib.compress("test data")
      iex> {:ok, decompressed} = KittyParser.decompress(compressed, :zlib)
      iex> decompressed
      "test data"
  """
  @spec decompress(binary(), ParserState.compression()) ::
          {:ok, binary()} | {:error, term()}
  def decompress(data, :none), do: {:ok, data}

  def decompress(data, :zlib) do
    {:ok, :zlib.uncompress(data)}
  rescue
    e -> {:error, {:decompression_failed, e}}
  end

  @doc """
  Extract image dimensions from PNG data.

  PNG header format: 8-byte signature + IHDR chunk with width/height.
  """
  @spec extract_png_dimensions(binary()) ::
          {:ok, {pos_integer(), pos_integer()}} | {:error, :invalid_png}
  def extract_png_dimensions(
        <<0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, _length::32, "IHDR", width::32,
          height::32, _rest::binary>>
      ) do
    {:ok, {width, height}}
  end

  def extract_png_dimensions(_), do: {:error, :invalid_png}

  # ============================================================================
  # Private Functions
  # ============================================================================

  defp split_control_payload(data) do
    case :binary.split(data, ";") do
      [control, payload] -> {control, payload}
      [control] -> {control, <<>>}
      _ -> :error
    end
  end

  defp parse_key_value_pairs([], state), do: {:ok, state}

  defp parse_key_value_pairs([pair | rest], state) do
    case parse_single_pair(pair, state) do
      {:ok, new_state} ->
        parse_key_value_pairs(rest, new_state)

      {:error, reason} ->
        Log.warning("[KittyParser] Invalid key-value pair: #{pair} - #{inspect(reason)}")

        # Continue parsing but record the error
        parse_key_value_pairs(rest, add_error(state, {:invalid_pair, pair}))
    end
  end

  defp parse_single_pair(pair, state) do
    case String.split(pair, "=", parts: 2) do
      [key, value] -> apply_key_value(key, value, state)
      _ -> {:error, :malformed_pair}
    end
  end

  defp apply_key_value("a", value, state) do
    action = parse_action(value)
    {:ok, %{state | action: action}}
  end

  defp apply_key_value("f", value, state) do
    format = parse_format(value)
    {:ok, %{state | format: format}}
  end

  defp apply_key_value("o", value, state) do
    compression = parse_compression(value)
    {:ok, %{state | compression: compression}}
  end

  defp apply_key_value("t", value, state) do
    transmission = parse_transmission(value)
    {:ok, %{state | transmission: transmission}}
  end

  defp apply_key_value("i", value, state) do
    case Integer.parse(value) do
      {id, ""} when id >= 0 -> {:ok, %{state | image_id: id}}
      _ -> {:error, :invalid_image_id}
    end
  end

  defp apply_key_value("p", value, state) do
    case Integer.parse(value) do
      {id, ""} when id >= 0 -> {:ok, %{state | placement_id: id}}
      _ -> {:error, :invalid_placement_id}
    end
  end

  defp apply_key_value("s", value, state) do
    case Integer.parse(value) do
      {width, ""} when width > 0 -> {:ok, %{state | width: width}}
      _ -> {:error, :invalid_width}
    end
  end

  defp apply_key_value("v", value, state) do
    case Integer.parse(value) do
      {height, ""} when height > 0 -> {:ok, %{state | height: height}}
      _ -> {:error, :invalid_height}
    end
  end

  defp apply_key_value("x", value, state) do
    case Integer.parse(value) do
      {x, ""} when x >= 0 -> {:ok, %{state | x_offset: x}}
      _ -> {:error, :invalid_x_offset}
    end
  end

  defp apply_key_value("y", value, state) do
    case Integer.parse(value) do
      {y, ""} when y >= 0 -> {:ok, %{state | y_offset: y}}
      _ -> {:error, :invalid_y_offset}
    end
  end

  defp apply_key_value("X", value, state) do
    case Integer.parse(value) do
      {x, ""} when x >= 0 -> {:ok, %{state | cell_x: x}}
      _ -> {:error, :invalid_cell_x}
    end
  end

  defp apply_key_value("Y", value, state) do
    case Integer.parse(value) do
      {y, ""} when y >= 0 -> {:ok, %{state | cell_y: y}}
      _ -> {:error, :invalid_cell_y}
    end
  end

  defp apply_key_value("z", value, state) do
    case Integer.parse(value) do
      {z, ""} -> {:ok, %{state | z_index: z}}
      _ -> {:error, :invalid_z_index}
    end
  end

  defp apply_key_value("q", value, state) do
    case Integer.parse(value) do
      {q, ""} when q in [0, 1, 2] -> {:ok, %{state | quiet: q}}
      _ -> {:error, :invalid_quiet}
    end
  end

  defp apply_key_value("m", value, state) do
    more_data = value in ["1", "true"]
    {:ok, %{state | more_data: more_data}}
  end

  # Ignore unknown keys (forward compatibility)
  defp apply_key_value(_key, _value, state) do
    {:ok, state}
  end

  defp parse_action("t"), do: :transmit
  defp parse_action("T"), do: :transmit_display
  defp parse_action("p"), do: :display
  defp parse_action("d"), do: :delete
  defp parse_action("q"), do: :query
  defp parse_action("f"), do: :frame
  defp parse_action("a"), do: :frame
  defp parse_action("c"), do: :frame
  defp parse_action("s"), do: :frame
  defp parse_action(_), do: :transmit

  defp parse_format("24"), do: :rgb
  defp parse_format("32"), do: :rgba
  defp parse_format("100"), do: :png
  defp parse_format(_), do: :unknown

  defp parse_compression("z"), do: :zlib
  defp parse_compression(_), do: :none

  defp parse_transmission("d"), do: :direct
  defp parse_transmission("f"), do: :file
  defp parse_transmission("t"), do: :temp_file
  defp parse_transmission("s"), do: :shared_memory
  defp parse_transmission(_), do: :direct

  defp handle_payload(<<>>, state), do: {:ok, state}

  defp handle_payload(payload, state) do
    case decode_base64_payload(payload) do
      {:ok, decoded} ->
        decompressed_result = decompress(decoded, state.compression)
        handle_decompressed_payload(decompressed_result, state)

      {:error, :invalid_base64} ->
        {:error, :invalid_base64, add_error(state, :invalid_base64)}
    end
  end

  defp handle_decompressed_payload({:ok, data}, state) do
    updated_state = handle_chunked_data(data, state)
    {:ok, updated_state}
  end

  defp handle_decompressed_payload({:error, reason}, state) do
    {:error, :decompression_failed, add_error(state, {:decompression_failed, reason})}
  end

  defp add_error(state, error) do
    %{state | errors: [error | state.errors]}
  end
end
