defmodule Raxol.Plugins.CellProcessor do
  @moduledoc """
  Handles the processing of rendered cells, allowing plugins to modify or replace them.
  Especially targets placeholder cells generated by the rendering pipeline.
  """

  require Raxol.Core.Runtime.Log
  alias Raxol.Plugins.Manager

  @doc """
  Processes a list of cells, allowing relevant plugins to handle placeholders.

  Iterates through the list of cells. If a cell is a placeholder (e.g.,
  `%{type: :placeholder, value: :image, ...}`), it identifies the responsible
  plugin (based on `placeholder_value`) and calls that plugin's `handle_cells/3`
  callback.

  The plugin's callback receives the placeholder cell map, the current emulator state,
  and its own current plugin state. It should return:
  - `{:ok, updated_plugin_state, list_of_replacement_cells, list_of_commands}`:
    The placeholder is replaced by `list_of_replacement_cells`, and the plugin's
    state is updated. `list_of_commands` are collected.
  - `{:cont, updated_plugin_state}`: The plugin declines to handle the placeholder.
    Its state is updated, but the placeholder is effectively removed (replaced by []).
  - `{:error, reason}`: An error occurred.

  Regular cells (non-placeholders) are passed through unchanged.

  Returns `{:ok, updated_manager_state, final_cell_list, collected_commands}` where
  `updated_manager_state` reflects any changes to plugin states.
  """
  @spec process(Manager.t(), list(map()), map()) ::
          {:ok, Manager.t(), list(map()), list(binary())}
  def process(%Manager{} = manager, cells, emulator_state)
      when is_list(cells) do
    Raxol.Core.Runtime.Log.debug(
      "[CellProcessor.process] Processing #{length(cells)} cells..."
    )

    {final_manager, final_cells_rev, final_commands} =
      process_cells(manager, cells, emulator_state)

    final_cells = Enum.reverse(final_cells_rev)

    Raxol.Core.Runtime.Log.debug(
      "[CellProcessor.process] Finished. Final Cells: #{length(final_cells)}, Commands: #{length(final_commands)}"
    )

    {:ok, final_manager, final_cells, final_commands}
  end

  defp process_cells(manager, cells, emulator_state) do
    Enum.reduce(cells, {manager, [], []}, fn cell,
                                             {acc_manager, processed_cells_rev,
                                              acc_commands} ->
      process_single_cell(
        cell,
        acc_manager,
        processed_cells_rev,
        acc_commands,
        emulator_state
      )
    end)
  end

  defp process_single_cell(
         %{type: :placeholder, value: placeholder_value} = placeholder_cell,
         acc_manager,
         processed_cells_rev,
         acc_commands,
         emulator_state
       ) do
    plugin_name = determine_plugin_for_placeholder(placeholder_value)

    {manager_after_processing, replacement_cells, new_commands} =
      handle_plugin_lookup(
        plugin_name,
        acc_manager,
        placeholder_cell,
        emulator_state,
        placeholder_value
      )

    {manager_after_processing, replacement_cells ++ processed_cells_rev,
     acc_commands ++ new_commands}
  end

  defp process_single_cell(
         valid_cell,
         acc_manager,
         processed_cells_rev,
         acc_commands,
         _emulator_state
       ) do
    {acc_manager, [valid_cell | processed_cells_rev], acc_commands}
  end

  defp log_unknown_placeholder(placeholder_value) do
    Raxol.Core.Runtime.Log.warning_with_context(
      "[CellProcessor.process] Unknown placeholder value: #{placeholder_value}. Skipping.",
      %{placeholder_value: placeholder_value}
    )
  end

  # --- Private Helper Functions ---

  # Pattern matching for plugin lookup instead of if statement
  defp handle_plugin_lookup(
         nil,
         acc_manager,
         _placeholder_cell,
         _emulator_state,
         placeholder_value
       ) do
    log_unknown_placeholder(placeholder_value)
    {acc_manager, [], []}
  end

  defp handle_plugin_lookup(
         plugin_name,
         acc_manager,
         placeholder_cell,
         emulator_state,
         _placeholder_value
       ) do
    handle_placeholder_with_plugin(
      acc_manager,
      plugin_name,
      placeholder_cell,
      emulator_state
    )
  end

  # Pattern matching for plugin availability instead of if statement
  defp handle_plugin_availability(
         {true, true},
         manager,
         plugin_name,
         plugin,
         placeholder_cell,
         emulator_state
       ) do
    log_plugin_call_details(plugin_name, plugin, placeholder_cell)

    call_plugin_handle_cells(
      manager,
      plugin_name,
      plugin,
      placeholder_cell,
      emulator_state
    )
  end

  defp handle_plugin_availability(
         _availability,
         manager,
         plugin_name,
         _plugin,
         _placeholder_cell,
         _emulator_state
       ) do
    Raxol.Core.Runtime.Log.debug(
      "[CellProcessor.process] Plugin #{plugin_name} disabled or does not implement handle_cells/3. Skipping."
    )

    {manager, [], []}
  end

  # Pattern matching for image plugin logging instead of if statements
  defp log_image_plugin_state("image", plugin) do
    Raxol.Core.Runtime.Log.debug(
      "[CellProcessor.process] Before calling ImagePlugin.handle_cells. sequence_just_generated: #{inspect(Map.get(plugin, :sequence_just_generated))}"
    )
  end

  defp log_image_plugin_state(_plugin_name, _plugin), do: :ok

  defp log_image_plugin_success("image", updated_plugin_state) do
    Raxol.Core.Runtime.Log.debug(
      "[CellProcessor.process] After ImagePlugin.handle_cells returned {:ok, ...}. sequence_just_generated: #{inspect(Map.get(updated_plugin_state, :sequence_just_generated))}"
    )
  end

  defp log_image_plugin_success(_plugin_name, _updated_plugin_state), do: :ok

  defp log_image_plugin_decline("image", updated_plugin_state) do
    Raxol.Core.Runtime.Log.debug(
      "[CellProcessor.process] After ImagePlugin.handle_cells returned {:cont, ...}. sequence_just_generated: #{inspect(Map.get(updated_plugin_state, :sequence_just_generated))}"
    )
  end

  defp log_image_plugin_decline(_plugin_name, _updated_plugin_state), do: :ok

  @spec determine_plugin_for_placeholder(atom()) :: String.t() | nil
  defp determine_plugin_for_placeholder(:image), do: "image"
  defp determine_plugin_for_placeholder(:chart), do: "visualization"
  defp determine_plugin_for_placeholder(:treemap), do: "visualization"
  # Add other placeholder types and their corresponding plugin names here
  defp determine_plugin_for_placeholder(_other), do: nil

  # Handles the interaction with a specific plugin for a given placeholder.
  # Returns {updated_manager, replacement_cells, new_commands}
  @spec handle_placeholder_with_plugin(
          map(),
          String.t(),
          map(),
          map()
        ) ::
          {map(), list(map()), list(binary())}
  defp handle_placeholder_with_plugin(
         manager,
         plugin_name,
         placeholder_cell,
         emulator_state
       ) do
    case Map.get(manager.plugins, plugin_name) do
      nil ->
        handle_missing_plugin(manager, plugin_name, placeholder_cell)

      plugin ->
        handle_plugin_call(
          manager,
          plugin_name,
          plugin,
          placeholder_cell,
          emulator_state
        )
    end
  end

  defp handle_missing_plugin(manager, plugin_name, placeholder_cell) do
    placeholder_value = Map.get(placeholder_cell, :value)

    Raxol.Core.Runtime.Log.warning_with_context(
      "[CellProcessor.process] Plugin \"#{plugin_name}\" not loaded " <>
        "for placeholder \"#{placeholder_value}\". Skipping.",
      %{plugin_name: plugin_name, placeholder_cell: placeholder_cell}
    )

    {manager, [], []}
  end

  defp handle_plugin_call(
         manager,
         plugin_name,
         plugin,
         placeholder_cell,
         emulator_state
       ) do
    handle_plugin_availability(
      {plugin.enabled, function_exported?(plugin.__struct__, :handle_cells, 3)},
      manager,
      plugin_name,
      plugin,
      placeholder_cell,
      emulator_state
    )
  end

  defp call_plugin_handle_cells(
         manager,
         plugin_name,
         plugin,
         placeholder_cell,
         emulator_state
       ) do
    case Raxol.Core.ErrorHandling.safe_call(fn ->
           handle_cells_result =
             plugin.__struct__.handle_cells(
               placeholder_cell,
               emulator_state,
               plugin
             )

           process_plugin_handle_cells_result(
             manager,
             plugin_name,
             handle_cells_result
           )
         end) do
      {:ok, result} ->
        result

      {:error, reason} ->
        Raxol.Core.Runtime.Log.error_with_stacktrace(
          "[CellProcessor.process] Error calling #{plugin_name}.handle_cells: #{inspect(reason)}. Placeholder was: #{inspect(placeholder_cell)}",
          reason,
          nil,
          %{plugin_name: plugin_name, placeholder_cell: placeholder_cell}
        )

        {manager, [], []}
    end
  end

  # Logs details before calling the plugin's handle_cells
  defp log_plugin_call_details(plugin_name, plugin, placeholder_cell) do
    # Log state *before* calling plugin, especially for ImagePlugin
    log_image_plugin_state(plugin_name, plugin)

    # Log the opts specifically
    Raxol.Core.Runtime.Log.debug(
      "[CellProcessor.process] Placeholder opts: #{inspect(Map.get(placeholder_cell, :opts))}"
    )

    Raxol.Core.Runtime.Log.debug(
      "[CellProcessor.process] Calling #{plugin_name}.handle_cells for placeholder...
CELL DATA: #{inspect(placeholder_cell)}"
    )
  end

  # Processes the return value from the plugin's handle_cells function.
  # Returns {updated_manager, replacement_cells, new_commands}
  @spec process_plugin_handle_cells_result(map(), String.t(), any()) ::
          {map(), list(map()), list(binary())}
  defp process_plugin_handle_cells_result(
         manager,
         plugin_name,
         handle_cells_result
       ) do
    case handle_cells_result do
      {:ok, updated_plugin_state, plugin_cells, plugin_commands}
      when is_list(plugin_cells) ->
        handle_successful_result(
          manager,
          plugin_name,
          updated_plugin_state,
          plugin_cells,
          plugin_commands
        )

      {:ok, updated_plugin_state, invalid_cells_param, plugin_commands} ->
        handle_invalid_cells(
          manager,
          plugin_name,
          updated_plugin_state,
          invalid_cells_param,
          plugin_commands
        )

      {:cont, updated_plugin_state} ->
        handle_declined_result(manager, plugin_name, updated_plugin_state)

      _ ->
        handle_unexpected_result(manager, plugin_name, handle_cells_result)
    end
  end

  defp handle_successful_result(
         manager,
         plugin_name,
         plugin_state,
         cells,
         commands
       ) do
    log_plugin_handled_details(plugin_name, plugin_state, cells, commands)
    {update_manager_state(manager, plugin_name, plugin_state), cells, commands}
  end

  defp handle_invalid_cells(
         manager,
         plugin_name,
         plugin_state,
         invalid_cells,
         commands
       ) do
    log_invalid_cells(plugin_name, invalid_cells, commands)
    {update_manager_state(manager, plugin_name, plugin_state), [], commands}
  end

  defp handle_declined_result(manager, plugin_name, plugin_state) do
    log_plugin_declined_details(plugin_name, plugin_state)
    {update_manager_state(manager, plugin_name, plugin_state), [], []}
  end

  defp handle_unexpected_result(manager, plugin_name, result) do
    log_unexpected_result(plugin_name, result)
    {manager, [], []}
  end

  defp update_manager_state(manager, plugin_name, plugin_state) do
    %{manager | plugins: Map.put(manager.plugins, plugin_name, plugin_state)}
  end

  defp log_invalid_cells(plugin_name, invalid_cells_param, plugin_commands) do
    Raxol.Core.Runtime.Log.warning_with_context(
      "[CellProcessor.process] Plugin #{plugin_name} handled placeholder but returned invalid cell format. Treating as decline.",
      %{
        plugin_name: plugin_name,
        invalid_cells: invalid_cells_param,
        plugin_commands: plugin_commands
      }
    )
  end

  defp log_unexpected_result(plugin_name, handle_cells_result) do
    Raxol.Core.Runtime.Log.warning_with_context(
      "[CellProcessor.process] Plugin #{plugin_name} returned unexpected value from handle_cells. Skipping.",
      %{plugin_name: plugin_name, handle_cells_result: handle_cells_result}
    )
  end

  # Logs details when a plugin successfully handles a placeholder
  defp log_plugin_handled_details(
         plugin_name,
         updated_plugin_state,
         plugin_cells,
         plugin_commands
       ) do
    log_image_plugin_success(plugin_name, updated_plugin_state)

    Raxol.Core.Runtime.Log.debug(
      "[CellProcessor.process] Plugin #{plugin_name} handled placeholder. Cells: #{length(plugin_cells)}, Commands: #{length(plugin_commands)}"
    )
  end

  # Logs details when a plugin declines to handle a placeholder
  defp log_plugin_declined_details(plugin_name, updated_plugin_state) do
    Raxol.Core.Runtime.Log.debug(
      "[CellProcessor.process] Plugin #{plugin_name} returned :cont. State Flag: #{inspect(Map.get(updated_plugin_state, :sequence_just_generated))}"
    )

    log_image_plugin_decline(plugin_name, updated_plugin_state)

    Raxol.Core.Runtime.Log.debug(
      "[CellProcessor.process] Plugin #{plugin_name} declined placeholder."
    )
  end
end
