# lib/moldb/search_supervisor.ex
defmodule Moldb.SearchSupervisor do
  use GenServer

  # Client API

  def start_link(opts) do
    GenServer.start_link(__MODULE__, opts, name: __MODULE__)
  end

  def execute_query(query_fn) do
    GenServer.call(__MODULE__, {:execute, query_fn})
  end

  # Server Callbacks

  def init(_opts) do
    {:ok, %{failures: 0, last_failure: nil}}
  end

  def handle_call({:execute, query_fn}, _from, state) do
    if circuit_open?(state) do
      {:reply, {:error, :circuit_open}, state}
    else
      try do
        result = query_fn.()
        {:reply, {:ok, result}, %{state | failures: 0}}
      rescue
        _ ->
          new_state = %{state | failures: state.failures + 1, last_failure: System.system_time(:second)}
          {:reply, {:error, :query_failed}, new_state}
      end
    end
  end

  # Helper functions

  defp circuit_open?(%{failures: failures, last_failure: last_failure}) do
    failures >= 3 and last_failure && System.system_time(:second) - last_failure < 30
  end
end

# lib/moldb_web/live/search_live.ex
defmodule MoldbWeb.SearchLive do
  use MoldbWeb, :live_view
  alias Moldb.Search
  alias Moldb.SearchSupervisor
  alias MoldbWeb.SearchComponent

  @per_page 20
  @debounce_ms 300
  @rate_limit_interval_ms 1000
  @rate_limit_max_requests 5

  @impl true
  def mount(_params, _session, socket) do
    {:ok,
     socket
     |> assign(results: [], query: "", page: 1)
     |> assign(search_ref: nil, rate_limit_state: init_rate_limit_state())}
  end

  @impl true
  def handle_info({:search, query}, socket) do
    case check_rate_limit(socket.assigns.rate_limit_state) do
      {:ok, new_state} ->
        handle_search(query, assign(socket, rate_limit_state: new_state))
      {:error, _} ->
        {:noreply, put_flash(socket, :error, "Too many requests. Please wait before trying again.")}
    end
  end

  @impl true
  def handle_event("load_more", _, %{assigns: %{query: query, page: page}} = socket) do
    case SearchSupervisor.execute_query(fn -> Search.search(query, page + 1, @per_page) end) do
      {:ok, new_results} ->
        {:noreply, assign(socket, results: socket.assigns.results ++ new_results, page: page + 1)}
      {:error, _reason} ->
        {:noreply, put_flash(socket, :error, "Unable to load more results. Please try again later.")}
    end
  end

  @impl true
  def handle_event("navigate", %{"id" => id, "type" => type}, socket) do
    path = if type == "compound", do: ~p"/compound/#{id}", else: ~p"/substance/#{id}"
    {:noreply, push_navigate(socket, to: path)}
  end

  defp handle_search(query, socket) do
    case SearchSupervisor.execute_query(fn -> Search.search(query, 1, @per_page) end) do
      {:ok, results} ->
        {:noreply, assign(socket, results: results, query: query, page: 1)}
      {:error, _reason} ->
        {:noreply, put_flash(socket, :error, "Search is temporarily unavailable. Please try again later.")}
    end
  end

  defp init_rate_limit_state do
    %{last_reset: System.monotonic_time(:millisecond), request_count: 0}
  end

  defp check_rate_limit(state) do
    now = System.monotonic_time(:millisecond)
    if now - state.last_reset >= @rate_limit_interval_ms do
      {:ok, %{last_reset: now, request_count: 1}}
    else
      if state.request_count < @rate_limit_max_requests do
        {:ok, %{state | request_count: state.request_count + 1}}
      else
        {:error, :rate_limit_exceeded}
      end
    end
  end
end

# Update your application.ex to include the SearchSupervisor
defmodule YourApp.Application do
  use Application

  def start(_type, _args) do
    children = [
      # ... other children
      Moldb.SearchSupervisor
    ]

    opts = [strategy: :one_for_one, name: YourApp.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

#search_live.html.heex 
<div class="max-w-2xl mx-auto p-6">
  <h1 class="text-3xl font-bold mb-6 text-center text-gray-800">Molecular Database Search</h1>
  
  <.live_component module={SearchComponent} id="search" query={@query} />

  <div id="search-results" class="space-y-4 mt-8">
    <%= if Enum.empty?(@results) do %>
      <p class="text-center text-gray-600">No results found. Try a different search term.</p>
    <% else %>
      <%= for result <- @results do %>
        <div class="result bg-white p-4 rounded-lg shadow hover:shadow-md transition-shadow duration-200 ease-in-out cursor-pointer"
             phx-click="navigate"
             phx-value-id={result.id}
             phx-value-type={result.type}>
          <h3 class="text-lg font-semibold text-gray-800 mb-2"><%= result.name %></h3>
          <p class="text-sm text-gray-600"><%= result.description %></p>
          <span class="inline-block mt-2 px-2 py-1 text-xs font-semibold text-gray-700 bg-gray-200 rounded-full">
            <%= String.capitalize(result.type) %>
          </span>
        </div>
      <% end %>
    <% end %>
  </div>

  <%= if length(@results) >= @page * @per_page do %>
    <div class="mt-4 text-center">
      <button phx-click="load_more" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors">
        Load More
      </button>
    </div>
  <% end %>
</div>

#search_component.ex
defmodule MoldbWeb.SearchComponent do
  use MoldbWeb, :live_component
  alias Moldb.Search
  alias Moldb.SearchSupervisor

  @suggestion_limit 5
  @debounce_ms 300

  def render(assigns) do
    ~H"""
    <div>
      <form phx-submit="search" phx-change="suggest" phx-target={@myself} class="relative">
        <div class="relative">
          <input type="text"
                 name="query"
                 value={@query}
                 placeholder="Search for compounds or substances..."
                 autocomplete="off"
                 phx-debounce={@debounce_ms}
                 class="w-full p-4 pr-12 text-sm border-gray-200 rounded-lg shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
          />
          <button type="submit" class="absolute inset-y-0 right-0 px-4 text-gray-600 hover:text-blue-600">
            <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
              <path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" />
            </svg>
          </button>
        </div>

        <%= if @suggestions != [] do %>
          <div class="absolute z-10 w-full mt-2 bg-white border border-gray-200 rounded-lg shadow-lg">
            <%= for suggestion <- @suggestions do %>
              <div class="px-4 py-2 cursor-pointer hover:bg-gray-100" phx-click="select_suggestion" phx-value-query={suggestion} phx-target={@myself}>
                <%= suggestion %>
              </div>
            <% end %>
          </div>
        <% end %>
      </form>
    </div>
    """
  end

  def update(assigns, socket) do
    {:ok, assign(socket, query: assigns[:query] || "", suggestions: [], debounce_ms: @debounce_ms)}
  end

  def handle_event("suggest", %{"query" => query}, socket) when byte_size(query) >= 2 do
    case SearchSupervisor.execute_query(fn -> Search.suggest(query, @suggestion_limit) end) do
      {:ok, suggestions} ->
        {:noreply, assign(socket, suggestions: suggestions, query: query)}
      {:error, _reason} ->
        {:noreply, assign(socket, suggestions: [], query: query)}
    end
  end
  def handle_event("suggest", _, socket), do: {:noreply, assign(socket, suggestions: [])}

  def handle_event("search", %{"query" => query}, socket) do
    send(self(), {:search, query})
    {:noreply, assign(socket, query: query, suggestions: [])}
  end

  def handle_event("select_suggestion", %{"query" => query}, socket) do
    send(self(), {:search, query})
    {:noreply, assign(socket, query: query, suggestions: [])}
  end
end
ANALYZE AND REVIEW