# An example that shows how to make a game with Raxol by using the canvas
# element.
#
# Usage:
#   elixir examples/snippets/advanced/snake.exs

defmodule Snake do
  # Use the correct Application behaviour and View DSL
  use Raxol.Core.Runtime.Application
  import Raxol.View.Elements

  alias Raxol.Core.Runtime.Events.Subscription
  alias Raxol.Core.Events.Event
  alias Raxol.Core.Commands.Command

  # Arrow key chars might vary by terminal, handle common patterns or codes if needed
  @up [:key_up]
  @down [:key_down]
  @left [:key_left]
  @right [:key_right]
  # Consider adding WASD or hjkl keys for wider compatibility
  @arrows @up ++ @down ++ @left ++ @right

  @initial_length 4

  @impl true
  def init(context) do
    Raxol.Core.Runtime.Log.debug("Snake: init/1 context: \#{inspect(context)}")

    # Assume context provides initial window dimensions if needed, or use defaults
    # Using fixed size for simplicity now, context inspection needed for dynamic size
    height = context[:height] || 20
    width = context[:width] || 40

    # Return :ok tuple
    {:ok,
     %{
       direction: :right,
       # Ensure coordinates start within bounds (e.g., 0-based)
       chain: Enum.map(@initial_length..1, fn x -> {x, 0} end),
       # Ensure food starts within bounds
       food: {7, 7},
       alive: true,
       # Adjust for potential border/padding
       height: height - 2,
       # Adjust for potential border/padding
       width: width - 2
     }}
  end

  @impl true
  def update(message, model) do
    Raxol.Core.Runtime.Log.debug(
      "Snake: update/2 received message: \#{inspect(message)}"
    )

    case message do
      # Use Event struct for key presses
      %Event{type: :key, data: %{key: key_name}} when key_name in @arrows ->
        new_dir = key_to_dir(key_name)
        # Return :ok tuple
        {:ok, %{model | direction: next_dir(model.direction, new_dir)}, []}

      # Handle quit keys
      %Event{type: :key, data: %{key: :char, char: "q"}} ->
        {:ok, model, [Command.new(:quit)]}

      %Event{type: :key, data: %{key: :char, char: "c", ctrl: true}} ->
        {:ok, model, [Command.new(:quit)]}

      :tick ->
        new_model = move_snake(model)
        # Return :ok tuple
        {:ok, new_model, []}

      _ ->
        # Return :ok tuple
        {:ok, model, []}
    end
  end

  # Renamed from subscribe/1
  @impl true
  def subscriptions(%{alive: alive}) do
    Raxol.Core.Runtime.Log.debug("Snake: subscriptions/1 alive: \#{alive}")
    # Only subscribe to ticks if the game is active
    if alive do
      Subscription.interval(100, :tick)
    else
      Subscription.none()
    end
  end

  # Renamed from render/1
  @impl true
  def view(%{chain: chain} = model) do
    Raxol.Core.Runtime.Log.debug("Snake: view/1")
    score = length(chain) - @initial_length

    view do
      # Use box instead of panel
      box(
        title: "SNAKE (Move with Arrows) Score=#{score} | q/Ctrl+C to Quit",
        style: [[:height, :fill], [:padding, 0], [:border, :single]]
      ) do
        if model.alive do
          render_board(model)
        else
          # Center Game Over message
          box style: [
                [:height, :fill],
                [:width, :fill],
                [:align_items, :center],
                [:justify_content, :center]
              ] do
            text(content: "Game Over! Score: #{score}")
          end
        end
      end
    end
  end

  defp render_board(
         %{
           chain: [{head_x, head_y} | tail],
           food: {food_x, food_y}
         } = model
       ) do
    # Assuming canvas and its syntax is correct
    # Head is often different from tail segments
    head_cell = %{
      x: head_x,
      y: head_y,
      char: "@",
      style: %{color: :green, bg_color: :dark_green}
    }

    # Tail segments
    tail_cells =
      for {x, y} <- tail, do: %{x: x, y: y, char: "o", style: %{color: :green}}

    # Food
    food_cell = %{x: food_x, y: food_y, char: "X", style: %{color: :red}}

    canvas(
      height: model.height,
      width: model.width,
      cells: [food_cell, head_cell | tail_cells]
    )
  end

  defp move_snake(model) do
    # Tail used via model later
    [head | _tail] = model.chain
    next = next_link(head, model.direction)

    cond do
      not next_valid?(next, model) ->
        Raxol.Core.Runtime.Log.info(
          "Snake: Game Over - Collision detected at \#{inspect(next)}"
        )

        %{model | alive: false}

      next == model.food ->
        Raxol.Core.Runtime.Log.debug("Snake: Food eaten at \#{inspect(next)}")

        new_food =
          random_food(model.width - 1, model.height - 1, [next | model.chain])

        Raxol.Core.Runtime.Log.debug("Snake: New food at \#{inspect(new_food)}")
        # Grow snake by prepending head and keeping old tail
        %{model | chain: [next | model.chain], food: new_food}

      true ->
        # Move snake by prepending head and dropping last element of old chain
        %{model | chain: [next | Enum.drop(model.chain, -1)]}
    end
  end

  # Ensure new food doesn't spawn on the snake
  defp random_food(max_x, max_y, occupied) do
    food = {Enum.random(0..max_x), Enum.random(0..max_y)}

    if food in occupied do
      Raxol.Core.Runtime.Log.debug("Snake: Random food conflict, retrying...")
      random_food(max_x, max_y, occupied)
    else
      food
    end
  end

  # Map key names (atoms) to directions
  defp key_to_dir(:key_up), do: :up
  defp key_to_dir(:key_down), do: :down
  defp key_to_dir(:key_left), do: :left
  defp key_to_dir(:key_right), do: :right

  # Check bounds and self-collision
  defp next_valid?({x, y}, model)
       when x < 0 or y < 0 or x >= model.width or y >= model.height do
    false
  end

  defp next_valid?(next, %{chain: chain}), do: next not in chain

  # Prevent moving directly opposite
  defp next_dir(:up, :down), do: :up
  defp next_dir(:down, :up), do: :down
  defp next_dir(:left, :right), do: :left
  defp next_dir(:right, :left), do: :right
  defp next_dir(_current, new), do: new

  # Calculate next head position
  defp next_link({x, y}, :up), do: {x, y - 1}
  defp next_link({x, y}, :down), do: {x, y + 1}
  defp next_link({x, y}, :left), do: {x - 1, y}
  defp next_link({x, y}, :right), do: {x + 1, y}
end

Raxol.Core.Runtime.Log.info("Snake: Starting Raxol...")

{:ok, _pid} = Raxol.start_link(Snake, [])
Raxol.Core.Runtime.Log.info("Snake: Raxol started. Running...")
