# This example shows how to render and scroll multi-line content, as
# well as how to asynchronously perform updates, by implementing a
# documentation browser for Elixir modules.
#
# The browser is intended to be relatively simplistic for the sake of
# readability. But it might be fun to add:
#
#  - Searching
#  - Text reflowing at narrow screen widths
#  - Markdown formatting
#  - Code highlighting
#  - vi-style pagination shortcuts (gg, G, etc.)
#
# Usage:
#   elixir examples/snippets/advanced/documentation_browser.exs

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

  alias Raxol.Core.Commands.Command
  alias Raxol.Core.Events.Event
  require Raxol.Core.Runtime.Log

  @header "Docs Browser (UP/DOWN: Select Module | j/k: Scroll Content | q/Ctrl+C: Quit)"

  @impl true
  def init(_context) do
    Raxol.Core.Runtime.Log.debug("DocumentationBrowser: init/1")
    # Get loaded Elixir modules
    modules =
      Code.available_modules()
      |> Enum.sort()

    model = %{
      content: "Loading...",
      # Store content as lines for easier scrolling
      content_lines: [],
      # Top visible line index
      scroll_offset: 0,
      module_cursor: 0,
      modules: modules,
      # Default/Estimate, could be updated via resize events
      view_height: 20
    }

    # Fetch initial content
    command = update_cmd(model)
    # Return :ok tuple
    {:ok, model, [command]}
  end

  @impl true
  def update(
        message,
        %{
          content_lines: content_lines,
          scroll_offset: scroll_offset,
          module_cursor: module_cursor,
          modules: modules,
          view_height: view_height
        } = model
      ) do
    Raxol.Core.Runtime.Log.debug(
      "DocumentationBrowser: update/2 received message: \#{inspect(message)}"
    )

    case message do
      # Use Event struct for keys
      # Scroll content down (move view up)
      %Event{type: :key, data: %{key: :char, char: "j"}} ->
        max_offset = max(0, length(content_lines) - view_height)
        new_offset = min(scroll_offset + 1, max_offset)
        {:ok, %{model | scroll_offset: new_offset}, []}

      # Scroll content up (move view down)
      %Event{type: :key, data: %{key: :char, char: "k"}} ->
        new_offset = max(scroll_offset - 1, 0)
        {:ok, %{model | scroll_offset: new_offset}, []}

      # Select module up
      %Event{type: :key, data: %{key: :key_up}} ->
        new_cursor = max(module_cursor - 1, 0)

        new_model = %{
          model
          | module_cursor: new_cursor,
            scroll_offset: 0,
            content: "Loading..."
        }

        command = update_cmd(new_model)
        {:ok, new_model, [command]}

      # Select module down
      %Event{type: :key, data: %{key: :key_down}} ->
        new_cursor = min(module_cursor + 1, length(modules) - 1)

        new_model = %{
          model
          | module_cursor: new_cursor,
            scroll_offset: 0,
            content: "Loading..."
        }

        command = update_cmd(new_model)
        {:ok, new_model, [command]}

      # 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)]}

      # Assume command results are wrapped
      {:command_result, :content_updated, content} ->
        Raxol.Core.Runtime.Log.debug("DocumentationBrowser: Content updated.")
        lines = String.split(content, "\n")
        # Reset scroll on new content
        {:ok,
         %{model | content: content, content_lines: lines, scroll_offset: 0},
         []}

      # Handle resize event to update view_height
      %Event{type: :resize, data: %{height: height}} ->
        # Subtract 2 for header and border
        new_height = max(1, height - 2)
        {:ok, %{model | view_height: new_height}, []}

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

  # Renamed from render/1
  @impl true
  def view(%{view_height: view_height} = model) do
    Raxol.Core.Runtime.Log.debug("DocumentationBrowser: view/1")

    view do
      # Use box and column/row from Elements DSL
      # Use column for header + main row
      column style: %{gap: 0} do
        box style: %{width: :fill, padding: [0, 1]} do
          text(content: @header, style: %{color: :blue})
        end

        # Main content row
        row style: %{height: :fill} do
          box title: "Modules",
              style: [[:width, "30%"], [:height, :fill], [:border, :single]] do
            # Basic list rendering, could use a list component if available
            # Highlight selected module
            module_list_content =
              for {module, idx} <- Enum.with_index(model.modules) do
                prefix = if idx == model.module_cursor, do: "> ", else: "  "
                style = if idx == model.module_cursor, do: [:bold], else: []
                text(content: prefix <> inspect(module), style: style)
              end

            # Need to handle scrolling for the module list as well if it gets long
            column(content: module_list_content, style: %{padding: 1})
          end

          box title: inspect(Enum.at(model.modules, model.module_cursor)),
              style: [[:width, "70%"], [:height, :fill], [:border, :single]] do
            # Implement scrolling by slicing lines
            visible_lines =
              Enum.slice(model.content_lines, model.scroll_offset, view_height)

            visible_content = Enum.join(visible_lines, "\n")
            text(content: visible_content, style: %{padding: 1})
          end
        end
      end
    end
  end

  defp update_cmd(model) do
    Raxol.Core.Runtime.Log.debug(
      "DocumentationBrowser: Creating content update command for module index \#{model.module_cursor}"
    )

    # Pass only needed data to the command function
    module_index = model.module_cursor
    modules = model.modules

    Command.new(
      fn -> fetch_content(module_index, modules) end,
      :content_updated
    )
  end

  defp fetch_content(module_index, modules) do
    selected = Enum.at(modules, module_index)

    Raxol.Core.Runtime.Log.debug(
      "DocumentationBrowser: Fetching docs for \#{inspect(selected)}"
    )

    # Use Code.get_docs for beam chunks
    case Code.get_docs(selected, :docs) do
      {:docs_v1, _annotation, :elixir, _format, %{"en" => docs}, _meta} ->
        docs

      {:docs_v1, _annotation, :elixir, _format, docs_map, _meta}
      when map?(docs_map) ->
        # Handle cases where default lang might not be 'en'
        elem(List.first(Map.to_list(docs_map)), 1)

      nil ->
        "(No documentation found for \#{inspect(selected)})"

      _ ->
        "(Error fetching documentation for \#{inspect(selected)})"
    end
  catch
    kind, reason ->
      Raxol.Core.Runtime.Log.error(
        "Error fetching docs for \#{inspect(selected)}: \#{kind} - \#{inspect(reason)}"
      )

      "(Error fetching documentation for \#{inspect(selected)})"
  end
end

Raxol.Core.Runtime.Log.info("DocumentationBrowser: Starting Raxol...")
# Use standard startup
{:ok, _pid} = Raxol.start_link(DocumentationBrowser, [])
Raxol.Core.Runtime.Log.info("DocumentationBrowser: Raxol started. Running...")
