# Moldb.Search

defmodule Moldb.Search do
  import Ecto.Query
  alias Moldb.Repo
  alias Moldb.Compounds.{Compound, Substance}

  @type search_result :: %{
    type: String.t(),
    id: integer(),
    name: String.t(),
    description: String.t()
  }

  @spec search(String.t(), integer(), integer()) :: [search_result()]
  def search(query, page, per_page) when is_binary(query) and is_integer(page) and is_integer(per_page) do
    offset = (page - 1) * per_page

    [
      search_schema(Compound, :iupac_name, query, offset, per_page),
      search_schema(Substance, :substance_name, query, offset, per_page)
    ]
    |> Task.async_stream(&(&1), max_concurrency: 2, timeout: 15_000)
    |> Enum.flat_map(fn {:ok, results} -> results end)
    |> Enum.sort_by(&rank(&1, query), :desc)
    |> Enum.take(per_page)
  end

  @spec suggest(String.t(), integer()) :: [String.t()]
  def suggest(query, limit) when is_binary(query) and is_integer(limit) do
    [
      suggest_schema(Compound, :iupac_name, query, limit),
      suggest_schema(Substance, :substance_name, query, limit)
    ]
    |> Task.async_stream(&(&1), max_concurrency: 2, timeout: 5_000)
    |> Enum.flat_map(fn {:ok, suggestions} -> suggestions end)
    |> Enum.sort_by(&String.jaro_distance(&1, query), :desc)
    |> Enum.take(limit)
  end

  defp search_schema(schema, field, query, offset, limit) do
    base_query(schema, field, query)
    |> select_search_fields(schema, field)
    |> limit(^limit)
    |> offset(^offset)
    |> Repo.all()
  end

  defp suggest_schema(schema, field, query, limit) do
    base_suggestion_query(schema, field, query)
    |> limit(^limit)
    |> Repo.all()
  end

  defp base_query(schema, field, query) do
    from(s in schema,
      where: ilike(field(s, ^field), ^"%#{query}%"),
      order_by: [desc: fragment("SIMILARITY(?, ?)", field(s, ^field), ^query)])
  end

  defp select_search_fields(query, schema, field) do
    type = schema |> Module.split() |> List.last() |> String.downcase()
    query
    |> select([s], %{
      type: ^type,
      id: s.id,
      name: field(s, ^field),
      description: fragment("LEFT(?, 100)", s.description)
    })
  end

  defp base_suggestion_query(schema, field, query) do
    from(s in schema,
      where: ilike(field(s, ^field), ^"#{query}%"),
      select: field(s, ^field))
  end

  defp rank(%{type: type, name: name}, query) do
    base_score = if type == "compound", do: 1.0, else: 0.8
    String.jaro_distance(name, query) * base_score
  end
end

# MoldbWeb.SearchLive

defmodule MoldbWeb.SearchLive do
  use MoldbWeb, :live_view
  alias Moldb.Search
  alias Moldb.SearchSupervisor

  @per_page 20

  @impl true
  def mount(_params, _session, socket) do
    {:ok,
     socket
     |> assign(results: [], query: "", page: 1, per_page: @per_page)
     |> assign(rate_limit_state: Moldb.RateLimiter.init_state())}
  end

  @impl true
  def handle_params(params, _url, socket) do
    {:noreply, apply_action(socket, socket.assigns.live_action, params)}
  end

  defp apply_action(socket, :index, _params) do
    socket
    |> assign(:page_title, "Search Molecular Database")
  end

  @impl true
  def handle_event("load_more", _, %{assigns: %{query: query, page: page, per_page: per_page}} = socket) do
    case execute_search(query, page + 1, per_page) 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

  @impl true
  def handle_info({:search, query}, socket) do
    case Moldb.RateLimiter.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

  defp handle_search(query, socket) do
    case execute_search(query, 1, socket.assigns.per_page) 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 execute_search(query, page, per_page) do
    SearchSupervisor.execute_query(fn -> Search.search(query, page, per_page) end)
  end
end

# MoldbWeb.SearchComponent

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

  @suggestion_limit 5
  @debounce_ms 300

  @impl true
  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

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

  @impl true
  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

  @impl true
  def handle_event("suggest", _, socket), do: {:noreply, assign(socket, suggestions: [])}

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

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

# Moldb.SearchSupervisor

defmodule Moldb.SearchSupervisor do
  use GenServer

  @max_failures 3
  @reset_interval 30

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

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

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

  @impl true
  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

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

# Moldb.RateLimiter

defmodule Moldb.RateLimiter do
  @rate_limit_interval_ms 1000
  @rate_limit_max_requests 5

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

  def 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

# Moldb.Application

defmodule Moldb.Application do
  use Application

  @impl true
  def start(_type, _args) do
    children = [
      MoldbWeb.Telemetry,
      Moldb.Repo,
      {DNSCluster, query: Application.get_env(:moldb, :dns_cluster_query) || :ignore},
      {Phoenix.PubSub, name: Moldb.PubSub},
      {Finch, name: Moldb.Finch},
      MoldbWeb.Endpoint,
      Moldb.SearchSupervisor
    ]

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

  @impl true
  def config_change(changed, _new, removed) do
    MoldbWeb.Endpoint.config_change(changed, removed)
    :ok
  end
end

# Moldb.Compounds

defmodule Moldb.Compounds do
  import Ecto.Query, warn: false
  alias Moldb.Repo
  alias Moldb.Compounds.{Compound, Substance}

  # Compound Functions
  def list_compounds, do: Repo.all(Compound)
  def get_compound!(id), do: Repo.get!(Compound, id)
  def create_compound(attrs \\ %{}), do: %Compound{} |> Compound.changeset(attrs) |> Repo.insert()
  def update_compound(%Compound{} = compound, attrs), do: compound |> Compound.changeset(attrs) |> Repo.update()
  def delete_compound(%Compound{} = compound), do: Repo.delete(compound)
  def change_compound(%Compound{} = compound, attrs \\ %{}), do: Compound.changeset(compound, attrs)

  # Substance Functions
  def list_substances, do: Repo.all(Substance)
  def get_substance!(id), do: Repo.get!(Substance, id)
  def create_substance(attrs \\ %{}), do: %Substance{} |> Substance.changeset(attrs) |> Repo.insert()
  def update_substance(%Substance{} = substance, attrs), do: substance |> Substance.changeset(attrs) |> Repo.update()
  def delete_substance(%Substance{} = substance), do: Repo.delete(substance)
  def change_substance(%Substance{} = substance, attrs \\ %{}), do: Substance.changeset(substance, attrs)

  # Helpers
  def compound_name(%Compound{} = compound), do: compound.iupac_name || "Unknown"
  def substance_name(%Substance{} = substance), do: substance.substance_name || "Unknown"

  def list_compounds_with_assocs, do: Repo.all(from(c in Compound, preload: [:properties, :synonyms]))
  def get_compound_with_assocs!(id), do: Repo.get!(Compound, id) |> Repo.preload([:properties, :synonyms])
end Analyze and review this elixir code from phoenix framework web app using liveview. Refactor code for high efficiency and performance, scalability, concurrency and resilency. Follow SOLID, DRY programming principles and keep state minimal. Dont implement cache, indexes or any third party tools, libraries or software. Remove redundant code and unnecessary overhead. Ensure code consistency across codebase Provide consistent, robust solution that complies with highest code standards, most recent versions and documentations and syntax and is fully valid.