find_matching_refs (GHashTable           *refs,
                    const char           *opt_name,
                    const char           *opt_branch,
                    const char           *opt_default_branch,
                    const char           *opt_arch,
                    const char           *opt_default_arch,
                    const char           *opt_collection_id,
                    FlatpakKinds          kinds,
                    FindMatchingRefsFlags flags,
                    GError              **error)
{
  g_autoptr(GPtrArray) matched_refs = NULL;
  const char **arches = flatpak_get_arches ();
  const char *opt_arches[] = {opt_arch, NULL};
  GHashTableIter hash_iter;
  gpointer key;
  g_autoptr(GError) local_error = NULL;
  gboolean found_exact_name_match = FALSE;
  gboolean found_default_branch_match = FALSE;
  gboolean found_default_arch_match = FALSE;

  if (opt_arch != NULL)
    arches = opt_arches;

  if (opt_name && !(flags & FIND_MATCHING_REFS_FLAGS_FUZZY) &&
      !flatpak_is_valid_name (opt_name, &local_error))
    {
      flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("'%s' is not a valid name: %s"), opt_name, local_error->message);
      return NULL;
    }

  if (opt_branch && !flatpak_is_valid_branch (opt_branch, &local_error))
    {
      flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("'%s' is not a valid branch name: %s"), opt_branch, local_error->message);
      return NULL;
    }

  matched_refs = g_ptr_array_new_with_free_func (g_free);

  g_hash_table_iter_init (&hash_iter, refs);
  while (g_hash_table_iter_next (&hash_iter, &key, NULL))
    {
      g_autofree char *ref = NULL;
      g_auto(GStrv) parts = NULL;
      gboolean is_app, is_runtime;
      FlatpakCollectionRef *coll_ref = key;

      /* Unprefix any remote name if needed */
      ostree_parse_refspec (coll_ref->ref_name, NULL, &ref, NULL);
      if (ref == NULL)
        continue;

      is_app = g_str_has_prefix (ref, "app/");
      is_runtime = g_str_has_prefix (ref, "runtime/");

      if ((!(kinds & FLATPAK_KINDS_APP) && is_app) ||
          (!(kinds & FLATPAK_KINDS_RUNTIME) && is_runtime) ||
          (!is_app && !is_runtime))
        continue;

      parts = flatpak_decompose_ref (ref, NULL);
      if (parts == NULL)
        continue;

      if ((flags & FIND_MATCHING_REFS_FLAGS_FUZZY) && !flatpak_id_has_subref_suffix (parts[1]))
        {
          /* See if the given name looks similar to this ref name. The
           * Levenshtein distance constant was chosen pretty arbitrarily. */
          if (opt_name != NULL && strcasestr (parts[1], opt_name) == NULL &&
              flatpak_levenshtein_distance (opt_name, parts[1]) > 2)
            continue;
        }
      else
        {
          if (opt_name != NULL && strcmp (opt_name, parts[1]) != 0)
            continue;
        }

      if (!g_strv_contains (arches, parts[2]))
        continue;

      if (opt_branch != NULL && strcmp (opt_branch, parts[3]) != 0)
        continue;

      if (opt_collection_id != NULL && strcmp (opt_collection_id, coll_ref->collection_id))
        continue;

      if (opt_name != NULL && strcmp (opt_name, parts[1]) == 0)
        found_exact_name_match = TRUE;

      if (opt_default_arch != NULL && strcmp (opt_default_arch, parts[2]) == 0)
        found_default_arch_match = TRUE;

      if (opt_default_branch != NULL && strcmp (opt_default_branch, parts[3]) == 0)
        found_default_branch_match = TRUE;

      if (flags & FIND_MATCHING_REFS_FLAGS_KEEP_REMOTE)
        g_ptr_array_add (matched_refs, g_strdup (coll_ref->ref_name));
      else
        g_ptr_array_add (matched_refs, g_steal_pointer (&ref));
    }

  /* Don't show fuzzy matches if we found at least one exact name match, and
   * enforce the default arch/branch */
  if (found_exact_name_match || found_default_arch_match || found_default_branch_match)
    {
      guint i;

      /* Walk through the array backwards so we can safely remove */
      for (i = matched_refs->len; i > 0; i--)
        {
          const char *matched_refspec = g_ptr_array_index (matched_refs, i - 1);
          g_auto(GStrv) matched_parts = NULL;
          g_autofree char *matched_ref = NULL;

          ostree_parse_refspec (matched_refspec, NULL, &matched_ref, NULL);
          matched_parts = flatpak_decompose_ref (matched_ref, NULL);

          if (found_exact_name_match && strcmp (matched_parts[1], opt_name) != 0)
            g_ptr_array_remove_index (matched_refs, i - 1);
          else if (found_default_arch_match && strcmp (matched_parts[2], opt_default_arch) != 0)
            g_ptr_array_remove_index (matched_refs, i - 1);
          else if (found_default_branch_match && strcmp (matched_parts[3], opt_default_branch) != 0)
            g_ptr_array_remove_index (matched_refs, i - 1);
        }
    }

  return g_steal_pointer (&matched_refs);
}