# lib/moldb_web/live/home_live.html.heex
<.form for={@form} phx-change="change" id="search-form">
  <.live_select
    id="compound-search"
    field={@form[:compound]}
    label="search-form"
    options={@options}
    update_min_len={1}
    allow_clear
    placeholder="Search for compounds"
  />
</.form>

# lib/moldb_web/live/home_live.ex
defmodule MoldbWeb.HomeLive do
  use MoldbWeb, :live_view
  alias Moldb.Search

  @impl true
  def mount(_params, _session, socket) do
    {:ok, assign(socket,
                  form: to_form(%{"compound" => ""}),
                  options: []
           )}
  end

  @impl true
  def handle_event("live_select_change", %{"id" => "compound-search", "text" => search_text}, socket) do
    options = Search.search_options(search_text)

    send_update(LiveSelect.Component, id: "compound-search", options: options)

    {:noreply, assign(socket,
                      options: options,
                      form: to_form(%{"compound" => search_text})
             )}
  end

  @impl true
  def handle_event("change", %{"compound" => compound}, socket) do
    {:noreply, assign(socket, form: to_form(%{"compound" => compound}))}
  end

  @impl true
  def handle_event("blur", %{"id" => "compound-search"}, socket) do
    send_update(LiveSelect.Component, id: "compound-search", options: [])

    {:noreply, socket}
  end

  @impl true
  def handle_event("clear", %{"id" => "compound-search"}, socket) do
    send_update(LiveSelect.Component, id: "compound-search", options: [])

    {:noreply, assign(socket,
                      options: [],
                      form: to_form(%{"compound" => ""})
             )}
  end
end

# lib/moldb/search.ex
defmodule Moldb.Search do
  @moduledoc """
  The Search context.
  """

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

  @search_limit 10

  @spec search_options(String.t()) :: [map()]
  def search_options(""), do: []
  def search_options(query) do
    query
    |> search_query()
    |> Repo.all()
    |> Enum.map(&to_live_select_option/1)
    |> Enum.sort_by(& &1.label)
  end

  defp search_query(query) do
    compound_query = from(c in Compound, where: ilike(c.iupac_name, ^"%#{query}%"), select: %{label: c.iupac_name, value: c.iupac_name})
    substance_query = from(s in Substance, where: ilike(s.substance_name, ^"%#{query}%"), select: %{label: s.substance_name, value: s.substance_name})

    compound_query
    |> union_all(^substance_query)
    |> limit(@search_limit)
  end

  defp to_live_select_option(%{label: label, value: value}) do
    %{label: label, value: value}
  end
end

# lib/moldb/compounds/compound.ex
defmodule Moldb.Compounds.Compound do
  @moduledoc """
  Module for handling compounds in Moldb.
  """
  use Ecto.Schema
  import Ecto.Changeset

  @primary_key {:id, UUIDv7, autogenerate: true}
  schema "compounds" do
    field :iupac_name, :string
    field :molecular_formula, :string
    field :molecular_weight, :decimal
    field :canonical_smiles, :string
    field :inchi, :string
    field :inchi_key, :string

    has_many :substances, Moldb.Compounds.Substance
    has_many :properties, Moldb.Compounds.CompoundProperty
    has_many :synonyms, Moldb.Compounds.CompoundSynonym

    timestamps(type: :utc_datetime)
  end

  @doc false
  def changeset(compound, attrs) do
    compound
    |> cast(attrs, [
      :iupac_name,
      :molecular_formula,
      :molecular_weight,
      :canonical_smiles,
      :inchi,
      :inchi_key
    ])
    |> validate_required([:iupac_name, :molecular_formula, :canonical_smiles, :inchi, :inchi_key])
    |> unique_constraint(:canonical_smiles)
    |> unique_constraint(:inchi)
    |> unique_constraint(:inchi_key)
    |> cast_assoc(:properties, with: &Moldb.Compounds.CompoundProperty.changeset/2)
    |> cast_assoc(:synonyms, with: &Moldb.Compounds.CompoundSynonym.changeset/2)
  end
end

# lib/moldb/compounds/substance.ex
defmodule Moldb.Compounds.Substance do
  use Ecto.Schema
  import Ecto.Changeset

  @primary_key {:id, UUIDv7, autogenerate: true}
  schema "substances" do
    field :substance_name, :string
    field :physical_state, :string
    field :salt_form, :string
    field :stereochemistry, :string

    belongs_to :compound, Moldb.Compounds.Compound, type: UUIDv7

    timestamps(type: :utc_datetime)
  end

  @doc false
  def changeset(substance, attrs) do
    substance
    |> cast(attrs, [:substance_name, :physical_state, :salt_form, :stereochemistry, :compound_id])
    |> validate_required([:substance_name, :compound_id])
    |> foreign_key_constraint(:compound_id)
  end
end

# lib/moldb/compounds.ex
defmodule Moldb.Compounds do
  @moduledoc """
  The Compounds context handles all interactions with compounds and substances in the database.
  """
  import Ecto.Query, warn: false
  alias Moldb.Repo
  alias Moldb.Compounds.{Compound, Substance}

  # Compound Functions

  @doc """
  Returns the list of compounds.
  """
  def list_compounds, do: Repo.all(Compound)

  @doc """
  Gets a single compound.

  Raises `Ecto.NoResultsError` if the Compound does not exist.
  """
  def get_compound!(id), do: Repo.get!(Compound, id)

  @doc """
  Creates a compound.
  """
  def create_compound(attrs \\ %{}),
    do: %Compound{} |> Compound.changeset(attrs) |> Repo.insert()

  @doc """
  Updates a compound.
  """
  def update_compound(%Compound{} = compound, attrs),
    do: compound |> Compound.changeset(attrs) |> Repo.update()

  @doc """
  Deletes a compound.
  """
  def delete_compound(%Compound{} = compound), do: Repo.delete(compound)

  @doc """
  Returns an `%Ecto.Changeset{}` for tracking compound changes.
  """
  def change_compound(%Compound{} = compound, attrs \\ %{}),
    do: Compound.changeset(compound, attrs)

  # Substance Functions

  @doc """
  Returns the list of substances.
  """
  def list_substances, do: Repo.all(Substance)

  @doc """
  Gets a single substance.

  Raises `Ecto.NoResultsError` if the Substance does not exist.
  """
  def get_substance!(id), do: Repo.get!(Substance, id)

  @doc """
  Creates a substance.
  """
  def create_substance(attrs \\ %{}),
    do: %Substance{} |> Substance.changeset(attrs) |> Repo.insert()

  @doc """
  Updates a substance.
  """
  def update_substance(%Substance{} = substance, attrs),
    do: substance |> Substance.changeset(attrs) |> Repo.update()

  @doc """
  Deletes a substance.
  """
  def delete_substance(%Substance{} = substance), do: Repo.delete(substance)

  @doc """
  Returns an `%Ecto.Changeset{}` for tracking substance changes.
  """
  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

# mix.exs
defmodule Moldb.MixProject do
  use Mix.Project

  def project do
    [
      app: :moldb,
      version: "0.1.0",
      elixir: "~> 1.14",
      elixirc_paths: elixirc_paths(Mix.env()),
      start_permanent: Mix.env() == :prod,
      aliases: aliases(),
      deps: deps()
    ]
  end

  # Configuration for the OTP application.
  #
  # Type `mix help compile.app` for more information.
  def application do
    [
      mod: {Moldb.Application, []},
      extra_applications: [:logger, :runtime_tools, :os_mon]
    ]
  end

  # Specifies which paths to compile per environment.
  defp elixirc_paths(:test), do: ["lib", "test/support"]
  defp elixirc_paths(_), do: ["lib"]

  # Specifies your project dependencies.
  #
  # Type `mix help deps` for examples and options.
  defp deps do
    [
      {:argon2_elixir, "~> 3.0"},
      {:phoenix, "~> 1.7.14"},
      {:phoenix_ecto, "~> 4.5"},
      {:ecto_sql, "~> 3.10"},
      {:postgrex, ">= 0.0.0"},
      {:phoenix_html, "~> 4.1"},
      {:phoenix_live_reload, "~> 1.2", only: :dev},
      # TODO bump on release to {:phoenix_live_view, "~> 1.0.0"},
      {:phoenix_live_view, "~> 1.0.0-rc.1", override: true},
      {:floki, ">= 0.30.0", only: :test},
      {:phoenix_live_dashboard, "~> 0.8.3"},
      {:esbuild, "~> 0.8", runtime: Mix.env() == :dev},
      {:tailwind, "~> 0.2", runtime: Mix.env() == :dev},
      {:heroicons,
       github: "tailwindlabs/heroicons",
       tag: "v2.1.1",
       sparse: "optimized",
       app: false,
       compile: false,
       depth: 1},
      {:swoosh, "~> 1.5"},
      {:finch, "~> 0.13"},
      {:telemetry_metrics, "~> 1.0"},
      {:telemetry_poller, "~> 1.0"},
      {:gettext, "~> 0.20"},
      {:jason, "~> 1.2"},
      {:dns_cluster, "~> 0.1.1"},
      {:bandit, "~> 1.5"},
      {:uuidv7, "~> 0.2.1"},
      {:sobelow, "~> 0.13", only: [:dev, :test], runtime: false},
      {:ex_doc, "~> 0.31", only: :dev, runtime: false},
      {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false},
      {:credo, "~> 1.7", only: [:dev, :test], runtime: false},
      {:ecto_psql_extras, "~> 0.7"},
      {:live_select, "~> 1.4"}
    ]
  end

  # Aliases are shortcuts or tasks specific to the current project.
  # For example, to install project dependencies and perform other setup tasks, run:
  #
  #     $ mix setup
  #
  # See the documentation for `Mix` for more info on aliases.
  defp aliases do
    [
      setup: ["deps.get", "ecto.setup", "assets.setup", "assets.build"],
      "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
      "ecto.reset": ["ecto.drop", "ecto.setup"],
      test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"],
      "assets.setup": ["tailwind.install --if-missing", "esbuild.install --if-missing"],
      "assets.build": ["tailwind moldb", "esbuild moldb"],
      "assets.deploy": [
        "tailwind moldb --minify",
        "esbuild moldb --minify",
        "phx.digest"
      ]
    ]
  end
end

# lib/moldb/compounds/compound_property.ex
defmodule Moldb.Compounds.CompoundProperty do
  @moduledoc """
  Defines the CompoundProperty schema and changeset functions.
  """
  use Ecto.Schema
  import Ecto.Changeset

  @primary_key {:id, UUIDv7, autogenerate: true}
  @foreign_key_type :binary_id

  schema "compound_properties" do
    field :property_name, :string
    field :property_value, :decimal
    field :property_unit, :string
    field :property_category, :string

    belongs_to :compound, Moldb.Compounds.Compound

    timestamps()
  end

  @doc false
  def changeset(property, attrs) do
    property
    |> cast(attrs, [
      :property_name,
      :property_value,
      :property_unit,
      :property_category,
      :compound_id
    ])
    |> validate_required([:property_name, :property_value, :property_category])
    |> validate_inclusion(:property_category, [
      "physical",
      "chemical",
      "thermodynamic",
      "mechanical"
    ])
    |> assoc_constraint(:compound)
  end
end

# lib/moldb/compounds/compound_synonym.ex
defmodule Moldb.Compounds.CompoundSynonym do
  @moduledoc """
  Defines the CompoundSynonym schema and changeset functions.
  """
  use Ecto.Schema
  import Ecto.Changeset

  @primary_key {:id, UUIDv7, autogenerate: true}
  @foreign_key_type :binary_id

  schema "compound_synonyms" do
    field :synonym, :string

    belongs_to :compound, Moldb.Compounds.Compound

    timestamps()
  end

  @doc false
  def changeset(synonym, attrs) do
    synonym
    |> cast(attrs, [:synonym, :compound_id])
    |> validate_required([:synonym])
    |> assoc_constraint(:compound)
  end
end

# lib/moldb/accounts.ex
defmodule Moldb.Accounts do
  @moduledoc """
  The Accounts context.
  """

  import Ecto.Query, warn: false
  alias Moldb.Repo

  alias Moldb.Accounts.{User, UserToken, UserNotifier}

  ## Database getters

  @doc """
  Gets a user by email.

  ## Examples

      iex> get_user_by_email("foo@example.com")
      %User{}

      iex> get_user_by_email("unknown@example.com")
      nil

  """
  def get_user_by_email(email) when is_binary(email) do
    Repo.get_by(User, email: email)
  end

  @doc """
  Gets a user by email and password.

  ## Examples

      iex> get_user_by_email_and_password("foo@example.com", "correct_password")
      %User{}

      iex> get_user_by_email_and_password("foo@example.com", "invalid_password")
      nil

  """
  def get_user_by_email_and_password(email, password)
      when is_binary(email) and is_binary(password) do
    user = Repo.get_by(User, email: email)
    if User.valid_password?(user, password), do: user
  end

  @doc """
  Gets a single user.

  Raises `Ecto.NoResultsError` if the User does not exist.

  ## Examples

      iex> get_user!(123)
      %User{}

      iex> get_user!(456)
      ** (Ecto.NoResultsError)

  """
  def get_user!(id), do: Repo.get!(User, id)

  ## User registration

  @doc """
  Registers a user.

  ## Examples

      iex> register_user(%{field: value})
      {:ok, %User{}}

      iex> register_user(%{field: bad_value})
      {:error, %Ecto.Changeset{}}

  """
  def register_user(attrs) do
    %User{}
    |> User.registration_changeset(attrs)
    |> Repo.insert()
  end

  @doc """
  Returns an `%Ecto.Changeset{}` for tracking user changes.

  ## Examples

      iex> change_user_registration(user)
      %Ecto.Changeset{data: %User{}}

  """
  def change_user_registration(%User{} = user, attrs \\ %{}) do
    User.registration_changeset(user, attrs, hash_password: false, validate_email: false)
  end

  ## Settings

  @doc """
  Returns an `%Ecto.Changeset{}` for changing the user email.

  ## Examples

      iex> change_user_email(user)
      %Ecto.Changeset{data: %User{}}

  """
  def change_user_email(user, attrs \\ %{}) do
    User.email_changeset(user, attrs, validate_email: false)
  end

  @doc """
  Emulates that the email will change without actually changing
  it in the database.

  ## Examples

      iex> apply_user_email(user, "valid password", %{email: ...})
      {:ok, %User{}}

      iex> apply_user_email(user, "invalid password", %{email: ...})
      {:error, %Ecto.Changeset{}}

  """
  def apply_user_email(user, password, attrs) do
    user
    |> User.email_changeset(attrs)
    |> User.validate_current_password(password)
    |> Ecto.Changeset.apply_action(:update)
  end

  @doc """
  Updates the user email using the given token.

  If the token matches, the user email is updated and the token is deleted.
  The confirmed_at date is also updated to the current time.
  """
  def update_user_email(user, token) do
    context = "change:#{user.email}"

    with {:ok, query} <- UserToken.verify_change_email_token_query(token, context),
         %UserToken{sent_to: email} <- Repo.one(query),
         {:ok, _} <- Repo.transaction(user_email_multi(user, email, context)) do
      :ok
    else
      _ -> :error
    end
  end

  defp user_email_multi(user, email, context) do
    changeset =
      user
      |> User.email_changeset(%{email: email})
      |> User.confirm_changeset()

    Ecto.Multi.new()
    |> Ecto.Multi.update(:user, changeset)
    |> Ecto.Multi.delete_all(:tokens, UserToken.by_user_and_contexts_query(user, [context]))
  end

  @doc ~S"""
  Delivers the update email instructions to the given user.

  ## Examples

      iex> deliver_user_update_email_instructions(user, current_email, &url(~p"/users/settings/confirm_email/#{&1}"))
      {:ok, %{to: ..., body: ...}}

  """
  def deliver_user_update_email_instructions(%User{} = user, current_email, update_email_url_fun)
      when is_function(update_email_url_fun, 1) do
    {encoded_token, user_token} = UserToken.build_email_token(user, "change:#{current_email}")

    Repo.insert!(user_token)
    UserNotifier.deliver_update_email_instructions(user, update_email_url_fun.(encoded_token))
  end

  @doc """
  Returns an `%Ecto.Changeset{}` for changing the user password.

  ## Examples

      iex> change_user_password(user)
      %Ecto.Changeset{data: %User{}}

  """
  def change_user_password(user, attrs \\ %{}) do
    User.password_changeset(user, attrs, hash_password: false)
  end

  @doc """
  Updates the user password.

  ## Examples

      iex> update_user_password(user, "valid password", %{password: ...})
      {:ok, %User{}}

      iex> update_user_password(user, "invalid password", %{password: ...})
      {:error, %Ecto.Changeset{}}

  """
  def update_user_password(user, password, attrs) do
    changeset =
      user
      |> User.password_changeset(attrs)
      |> User.validate_current_password(password)

    Ecto.Multi.new()
    |> Ecto.Multi.update(:user, changeset)
    |> Ecto.Multi.delete_all(:tokens, UserToken.by_user_and_contexts_query(user, :all))
    |> Repo.transaction()
    |> case do
      {:ok, %{user: user}} -> {:ok, user}
      {:error, :user, changeset, _} -> {:error, changeset}
    end
  end

  ## Session

  @doc """
  Generates a session token.
  """
  def generate_user_session_token(user) do
    {token, user_token} = UserToken.build_session_token(user)
    Repo.insert!(user_token)
    token
  end

  @doc """
  Gets the user with the given signed token.
  """
  def get_user_by_session_token(token) do
    {:ok, query} = UserToken.verify_session_token_query(token)
    Repo.one(query)
  end

  @doc """
  Deletes the signed token with the given context.
  """
  def delete_user_session_token(token) do
    Repo.delete_all(UserToken.by_token_and_context_query(token, "session"))
    :ok
  end

  ## Confirmation

  @doc ~S"""
  Delivers the confirmation email instructions to the given user.

  ## Examples

      iex> deliver_user_confirmation_instructions(user, &url(~p"/users/confirm/#{&1}"))
      {:ok, %{to: ..., body: ...}}

      iex> deliver_user_confirmation_instructions(confirmed_user, &url(~p"/users/confirm/#{&1}"))
      {:error, :already_confirmed}

  """
  def deliver_user_confirmation_instructions(%User{} = user, confirmation_url_fun)
      when is_function(confirmation_url_fun, 1) do
    if user.confirmed_at do
      {:error, :already_confirmed}
    else
      {encoded_token, user_token} = UserToken.build_email_token(user, "confirm")
      Repo.insert!(user_token)
      UserNotifier.deliver_confirmation_instructions(user, confirmation_url_fun.(encoded_token))
    end
  end

  @doc """
  Confirms a user by the given token.

  If the token matches, the user account is marked as confirmed
  and the token is deleted.
  """
  def confirm_user(token) do
    with {:ok, query} <- UserToken.verify_email_token_query(token, "confirm"),
         %User{} = user <- Repo.one(query),
         {:ok, %{user: user}} <- Repo.transaction(confirm_user_multi(user)) do
      {:ok, user}
    else
      _ -> :error
    end
  end

  defp confirm_user_multi(user) do
    Ecto.Multi.new()
    |> Ecto.Multi.update(:user, User.confirm_changeset(user))
    |> Ecto.Multi.delete_all(:tokens, UserToken.by_user_and_contexts_query(user, ["confirm"]))
  end

  ## Reset password

  @doc ~S"""
  Delivers the reset password email to the given user.

  ## Examples

      iex> deliver_user_reset_password_instructions(user, &url(~p"/users/reset_password/#{&1}"))
      {:ok, %{to: ..., body: ...}}

  """
  def deliver_user_reset_password_instructions(%User{} = user, reset_password_url_fun)
      when is_function(reset_password_url_fun, 1) do
    {encoded_token, user_token} = UserToken.build_email_token(user, "reset_password")
    Repo.insert!(user_token)
    UserNotifier.deliver_reset_password_instructions(user, reset_password_url_fun.(encoded_token))
  end

  @doc """
  Gets the user by reset password token.

  ## Examples

      iex> get_user_by_reset_password_token("validtoken")
      %User{}

      iex> get_user_by_reset_password_token("invalidtoken")
      nil

  """
  def get_user_by_reset_password_token(token) do
    with {:ok, query} <- UserToken.verify_email_token_query(token, "reset_password"),
         %User{} = user <- Repo.one(query) do
      user
    else
      _ -> nil
    end
  end

  @doc """
  Resets the user password.

  ## Examples

      iex> reset_user_password(user, %{password: "new long password", password_confirmation: "new long password"})
      {:ok, %User{}}

      iex> reset_user_password(user, %{password: "valid", password_confirmation: "not the same"})
      {:error, %Ecto.Changeset{}}

  """
  def reset_user_password(user, attrs) do
    Ecto.Multi.new()
    |> Ecto.Multi.update(:user, User.password_changeset(user, attrs))
    |> Ecto.Multi.delete_all(:tokens, UserToken.by_user_and_contexts_query(user, :all))
    |> Repo.transaction()
    |> case do
      {:ok, %{user: user}} -> {:ok, user}
      {:error, :user, changeset, _} -> {:error, changeset}
    end
  end
end

# core_components.ex LiveSelect
def live_select(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do
    assigns =
      assigns
      |> assign(:errors, Enum.map(field.errors, &translate_error(&1)))
      |> assign(:live_select_opts, assigns_to_attributes(assigns, [:errors, :label]))

    ~H"""
    <div <PRESIDIO_ANONYMIZED_EMAIL_ADDRESS>}>
      <.label <PRESIDIO_ANONYMIZED_EMAIL_ADDRESS>}><%= @label %></.label>
      <LiveSelect.live_select
        field={@field}
        options={@options}
        text_input_class={[
          "mt-2 block w-full rounded-lg border-zinc-300 py-[7px] px-[11px]",
          "text-zinc-900 focus:outline-none focus:ring-4 sm:text-sm sm:leading-6",
          "phx-no-feedback:border-zinc-300 phx-no-feedback:focus:border-zinc-400 phx-no-feedback:focus:ring-zinc-800/5",
          "border-zinc-300 focus:border-zinc-400 focus:ring-zinc-800/5",
          @errors != [] && "border-rose-400 focus:border-rose-400 focus:ring-rose-400/10"
        ]}
        {@live_select_opts}
      />

      <.error :for={msg <- @errors}><%= msg %></.error>
    </div>
    """
  end
Analyze this elixir code from phoenix framework web application, using liveview and liveselect library. Refactor it to achieve fully valid, working code that adheres to SOLID, DRY and functional programming principles, newest syntax, best practices and most recent documentation. Remove redundant, unused code. Ensure state and events are handlel properly.