defmodule Raxol.Terminal.Theme.Manager do
  @moduledoc """
  Manages terminal themes with advanced features:
  - Theme loading from files and presets
  - Theme customization and modification
  - Dynamic theme switching
  - Theme persistence and state management

  ## Unified Theme System

  Themes are sourced from `Raxol.Core.Theming.ThemeRegistry`, which
  provides a single source of truth for all Raxol themes. Use
  `load_from_registry/2` to load any registered theme.

  ## Usage

      manager = Raxol.Terminal.Theme.Manager.new()

      # Load a theme from the unified registry
      {:ok, manager} = Raxol.Terminal.Theme.Manager.load_from_registry(manager, :dracula)

      # Get a style
      {:ok, style, manager} = Raxol.Terminal.Theme.Manager.get_style(manager, :normal)
  """

  # ThemeRegistry lives in main raxol; guarded at runtime
  @compile {:no_warn_undefined, Raxol.Core.Theming.ThemeRegistry}

  @type color :: %{
          r: integer(),
          g: integer(),
          b: integer(),
          a: float()
        }

  @type style :: %{
          foreground: color(),
          background: color(),
          bold: boolean(),
          italic: boolean(),
          underline: boolean()
        }

  @type theme :: %{
          name: String.t(),
          description: String.t(),
          author: String.t(),
          version: String.t(),
          colors: %{
            background: color(),
            foreground: color(),
            cursor: color(),
            selection: color(),
            black: color(),
            red: color(),
            green: color(),
            yellow: color(),
            blue: color(),
            magenta: color(),
            cyan: color(),
            white: color(),
            bright_black: color(),
            bright_red: color(),
            bright_green: color(),
            bright_yellow: color(),
            bright_blue: color(),
            bright_magenta: color(),
            bright_cyan: color(),
            bright_white: color()
          },
          styles: %{
            normal: style(),
            bold: style(),
            italic: style(),
            underline: style(),
            cursor: style(),
            selection: style()
          }
        }

  @type t :: %__MODULE__{
          current_theme: theme(),
          themes: %{String.t() => theme()},
          custom_styles: %{String.t() => style()},
          metrics: %{
            theme_switches: integer(),
            style_applications: integer(),
            customizations: integer(),
            load_operations: integer()
          }
        }

  defstruct [
    :current_theme,
    :themes,
    :custom_styles,
    :metrics
  ]

  @doc """
  Creates a new theme manager with the given options.
  """
  @spec new(keyword()) :: t()
  def new(_opts \\ []) do
    default_theme = %{
      name: "default",
      description: "Default terminal theme",
      author: "Raxol",
      version: "1.0.1",
      colors: %{
        background: %{r: 0, g: 0, b: 0, a: 1.0},
        foreground: %{r: 255, g: 255, b: 255, a: 1.0},
        cursor: %{r: 255, g: 255, b: 255, a: 1.0},
        selection: %{r: 51, g: 51, b: 51, a: 1.0},
        black: %{r: 0, g: 0, b: 0, a: 1.0},
        red: %{r: 255, g: 0, b: 0, a: 1.0},
        green: %{r: 0, g: 255, b: 0, a: 1.0},
        yellow: %{r: 255, g: 255, b: 0, a: 1.0},
        blue: %{r: 0, g: 0, b: 255, a: 1.0},
        magenta: %{r: 255, g: 0, b: 255, a: 1.0},
        cyan: %{r: 0, g: 255, b: 255, a: 1.0},
        white: %{r: 255, g: 255, b: 255, a: 1.0},
        bright_black: %{r: 128, g: 128, b: 128, a: 1.0},
        bright_red: %{r: 255, g: 128, b: 128, a: 1.0},
        bright_green: %{r: 128, g: 255, b: 128, a: 1.0},
        bright_yellow: %{r: 255, g: 255, b: 128, a: 1.0},
        bright_blue: %{r: 128, g: 128, b: 255, a: 1.0},
        bright_magenta: %{r: 255, g: 128, b: 255, a: 1.0},
        bright_cyan: %{r: 128, g: 255, b: 255, a: 1.0},
        bright_white: %{r: 255, g: 255, b: 255, a: 1.0}
      },
      styles: %{
        normal: %{
          foreground: %{r: 255, g: 255, b: 255, a: 1.0},
          background: %{r: 0, g: 0, b: 0, a: 1.0},
          bold: false,
          italic: false,
          underline: false
        },
        bold: %{
          foreground: %{r: 255, g: 255, b: 255, a: 1.0},
          background: %{r: 0, g: 0, b: 0, a: 1.0},
          bold: true,
          italic: false,
          underline: false
        },
        italic: %{
          foreground: %{r: 255, g: 255, b: 255, a: 1.0},
          background: %{r: 0, g: 0, b: 0, a: 1.0},
          bold: false,
          italic: true,
          underline: false
        },
        underline: %{
          foreground: %{r: 255, g: 255, b: 255, a: 1.0},
          background: %{r: 0, g: 0, b: 0, a: 1.0},
          bold: false,
          italic: false,
          underline: true
        },
        cursor: %{
          foreground: %{r: 0, g: 0, b: 0, a: 1.0},
          background: %{r: 255, g: 255, b: 255, a: 1.0},
          bold: false,
          italic: false,
          underline: false
        },
        selection: %{
          foreground: %{r: 255, g: 255, b: 255, a: 1.0},
          background: %{r: 51, g: 51, b: 51, a: 1.0},
          bold: false,
          italic: false,
          underline: false
        }
      }
    }

    %__MODULE__{
      current_theme: default_theme,
      themes: %{"default" => default_theme},
      custom_styles: %{},
      metrics: %{
        theme_switches: 0,
        style_applications: 0,
        customizations: 0,
        load_operations: 0
      }
    }
  end

  @doc """
  Loads a theme from a file or preset.
  """
  @spec load_theme(t(), String.t()) :: {:ok, t()} | {:error, term()}
  def load_theme(manager, theme_name) do
    case Map.get(manager.themes, theme_name) do
      nil ->
        {:error, :theme_not_found}

      theme ->
        updated_manager = %{
          manager
          | current_theme: theme,
            metrics: update_metrics(manager.metrics, :theme_switches)
        }

        {:ok, updated_manager}
    end
  end

  @doc """
  Loads a theme from the unified theme registry.

  This is the preferred way to load themes as it uses the single
  source of truth for all Raxol theme definitions.

  ## Examples

      {:ok, manager} = Manager.load_from_registry(manager, :dracula)
      {:ok, manager} = Manager.load_from_registry(manager, :synthwave84)
  """
  @dialyzer {:nowarn_function, load_from_registry: 2}
  @spec load_from_registry(t(), atom()) ::
          {:ok, t()} | {:error, :theme_not_found}
  def load_from_registry(manager, theme_name) when is_atom(theme_name) do
    case Raxol.Core.Theming.ThemeRegistry.to_terminal_format(theme_name) do
      nil ->
        {:error, :theme_not_found}

      theme when is_map(theme) ->
        # Add to available themes and set as current
        updated_themes = Map.put(manager.themes, theme.name, theme)

        updated_manager = %{
          manager
          | current_theme: theme,
            themes: updated_themes,
            metrics: update_metrics(manager.metrics, :theme_switches)
        }

        {:ok, updated_manager}
    end
  end

  @doc """
  Lists all themes available in the unified registry.
  """
  @spec list_registry_themes() :: [atom()]
  def list_registry_themes do
    Raxol.Core.Theming.ThemeRegistry.list()
  end

  @doc """
  Adds a custom style to the current theme.
  """
  @spec add_custom_style(t(), String.t(), style()) ::
          {:ok, t()} | {:error, term()}
  def add_custom_style(manager, name, style) do
    case validate_style(style) do
      :ok ->
        new_styles = Map.put(manager.custom_styles, name, style)

        updated_manager = %{
          manager
          | custom_styles: new_styles,
            metrics: update_metrics(manager.metrics, :customizations)
        }

        {:ok, updated_manager}

      {:error, reason} ->
        {:error, reason}
    end
  end

  @doc """
  Gets a style from the current theme or custom styles.
  """
  @spec get_style(t(), String.t() | atom()) ::
          {:ok, style(), t()} | {:error, term()}
  def get_style(manager, style_name) do
    # Try atom and string keys for theme styles
    style =
      Map.get(manager.current_theme.styles, style_name) ||
        Map.get(manager.current_theme.styles, String.to_atom(style_name))

    case style do
      nil ->
        case Map.get(manager.custom_styles, style_name) do
          nil ->
            {:error, :style_not_found}

          style ->
            updated_manager = %{
              manager
              | metrics: update_metrics(manager.metrics, :style_applications)
            }

            {:ok, style, updated_manager}
        end

      style ->
        updated_manager = %{
          manager
          | metrics: update_metrics(manager.metrics, :style_applications)
        }

        {:ok, style, updated_manager}
    end
  end

  @doc """
  Gets the current theme metrics.
  """
  @spec get_metrics(t()) :: map()
  def get_metrics(manager) do
    manager.metrics
  end

  @doc """
  Saves the current theme state for persistence.
  """
  @spec save_theme_state(t()) :: {:ok, map()}
  def save_theme_state(manager) do
    state = %{
      current_theme: manager.current_theme.name,
      custom_styles: manager.custom_styles
    }

    {:ok, state}
  end

  @doc """
  Restores a theme state from saved data.
  """
  @spec restore_theme_state(t(), map()) :: {:ok, t()} | {:error, term()}
  def restore_theme_state(manager, state) do
    with {:ok, manager} <- load_theme(manager, state.current_theme) do
      updated_manager = %{
        manager
        | custom_styles: state.custom_styles,
          metrics: update_metrics(manager.metrics, :load_operations)
      }

      {:ok, updated_manager}
    end
  end

  # Private helper functions

  defp validate_style(style) do
    required_fields = [:foreground, :background, :bold, :italic, :underline]

    case Enum.all?(required_fields, &Map.has_key?(style, &1)) do
      true -> :ok
      false -> {:error, :invalid_style}
    end
  end

  defp update_metrics(metrics, :theme_switches) do
    %{metrics | theme_switches: metrics.theme_switches + 1}
  end

  defp update_metrics(metrics, :style_applications) do
    %{metrics | style_applications: metrics.style_applications + 1}
  end

  defp update_metrics(metrics, :customizations) do
    %{metrics | customizations: metrics.customizations + 1}
  end

  defp update_metrics(metrics, :load_operations) do
    %{metrics | load_operations: metrics.load_operations + 1}
  end
end
