<!-- #region DatabaseSearchCombobox [C:3] [TYPE Component] [SEMANTICS ui, database, search, combobox, single-select] -->
<!-- @BRIEF Searchable single-select combobox for Superset databases. Shows name and engine. -->
<!-- @UX_STATE default -> Input with dropdown hidden, selected db name shown if selected -->
<!-- @UX_STATE dropdown_open -> Dropdown visible with filtered database options -->
<!-- @UX_STATE empty -> No databases match the search filter -->
<!-- @UX_STATE loading -> Spinner in input while fetching databases -->
<!-- @UX_STATE error -> Toast shown on API failure -->
<!-- @UX_FEEDBACK Toast on API fetch error -->
<!-- @UX_REACTIVITY envId -> resets databases on change, triggers refetch; Props -> $props(); LocalState -> $state(...) -->
<!-- @RELATION DEPENDS_ON -> [EXT:frontend:api] -->
<!-- @RELATION CALLED_BY -> [MapperTool] -->
<script lang="ts">
  import { api } from '$lib/api';
  import { addToast } from '$lib/toasts.svelte.js';

  /**
   * Searchable single-select combobox for Superset databases.
   * @prop {string} envId - Environment ID for API calls
   * @prop {string} value - Bindable selected database ID
   * @prop {string} label - Label text above input
   * @prop {string} placeholder - Placeholder for search input
   * @prop {boolean} disabled - Disable the input
   */
  let {
    envId = '',
    value = $bindable(''),
    label = '',
    placeholder = 'Search databases...',
    disabled = false,
  } = $props();

  let searchQuery = $state('');
  let isOpen = $state(false);
  let databases = $state([]);
  let isLoading = $state(false);
  let containerEl = $state(null);
  let selectedDb = $state(null);

  // Find the currently selected database
  $effect(() => {
    if (value && databases.length > 0) {
      const found = databases.find(d => String(d.id) === String(value));
      if (found) {
        selectedDb = found;
        searchQuery = found.database_name;
      }
    } else if (!value) {
      selectedDb = null;
      if (!isOpen) searchQuery = '';
    }
  });

  function handleInput(e) {
    searchQuery = e.target.value;
    if (!isOpen) isOpen = true;
    // Filtering is handled by $derived(filteredDatabases)
  }

  async function fetchDatabases() {
    if (!envId) return;
    isLoading = true;
    try {
      const result = await api.getEnvironmentDatabases(envId);
      databases = result || [];
      if (value) {
        selectedDb = databases.find(d => String(d.id) === String(value)) || selectedDb;
        if (selectedDb) searchQuery = selectedDb.database_name;
      }
    } catch (e) {
      databases = [];
      addToast(e.message || 'Failed to load databases', 'error');
    } finally {
      isLoading = false;
    }
  }

  function handleFocus() {
    if (!disabled) {
      isOpen = true;
      if (databases.length === 0 && envId) {
        fetchDatabases();
      }
    }
  }

  function selectDatabase(db) {
    value = String(db.id);
    selectedDb = db;
    searchQuery = db.database_name;
    isOpen = false;
  }

  function clearSelection() {
    value = '';
    selectedDb = null;
    searchQuery = '';
    isOpen = true;
    if (envId) fetchDatabases();
  }

  function handleClickOutside(e) {
    if (containerEl && !containerEl.contains(e.target)) {
      isOpen = false;
      if (selectedDb && !isOpen) {
        searchQuery = selectedDb.database_name;
      } else if (!selectedDb && !isOpen) {
        searchQuery = '';
      }
    }
  }

  $effect(() => {
    if (typeof document !== 'undefined') {
      document.addEventListener('click', handleClickOutside);
      return () => document.removeEventListener('click', handleClickOutside);
    }
  });

  // Reset when envId changes — clear cache so handleFocus triggers refetch
  $effect(() => {
    if (envId) {
      databases = [];
      selectedDb = null;
      if (!value) searchQuery = '';
      if (isOpen) fetchDatabases();
    }
  });

  let filteredDatabases = $derived(
    searchQuery
      ? databases.filter(
          (db) =>
            db.database_name?.toLowerCase().includes(searchQuery.toLowerCase()) ||
            (db.engine && db.engine.toLowerCase().includes(searchQuery.toLowerCase())),
        )
      : databases,
  );
</script>

<div bind:this={containerEl} class="relative">
  {#if label}
    <label class="block text-sm font-medium text-gray-700 mb-1">{label}</label>
  {/if}

  <!-- Search Input -->
  <div class="relative">
    <input
      type="text"
      value={searchQuery}
      oninput={handleInput}
      onfocus={handleFocus}
      placeholder={placeholder}
      disabled={disabled}
      class="w-full px-3 py-2 pr-8 border border-gray-300 rounded-lg text-sm focus-visible:ring-2 focus-visible:ring-primary-ring focus-visible:border-primary-ring {disabled ? 'bg-gray-50 cursor-not-allowed' : ''}"
      role="combobox"
      aria-expanded={isOpen}
      aria-haspopup="listbox"
    />
    {#if isLoading}
      <div class="absolute right-2.5 top-2.5 animate-spin h-4 w-4 border-2 border-blue-500 border-t-transparent rounded-full"></div>
    {:else if value && !isOpen}
      <button
        onclick={clearSelection}
        class="absolute right-2.5 top-2.5 text-gray-400 hover:text-gray-600"
        aria-label="Clear selection"
        type="button"
      >
        <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
        </svg>
      </button>
    {:else if !isOpen}
      <div class="absolute right-2.5 top-2.5 text-gray-400">
        <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
        </svg>
      </div>
    {/if}
  </div>

  <!-- Dropdown -->
  {#if isOpen}
    <div class="absolute z-50 mt-1 w-full bg-white border border-gray-200 rounded-lg shadow-lg" role="listbox">
      <div class="max-h-60 overflow-y-auto">
        {#if isLoading && databases.length === 0}
          <div class="px-3 py-6 text-center text-sm text-gray-400">
            <div class="inline-block animate-spin h-5 w-5 border-2 border-blue-500 border-t-transparent rounded-full mb-2"></div>
            <p>Loading...</p>
          </div>
        {:else if filteredDatabases.length === 0}
          <div class="px-3 py-6 text-center text-sm text-gray-400 italic">
            {searchQuery ? 'No databases match your search' : 'No databases available'}
          </div>
        {:else}
          {#each filteredDatabases as db (db.id)}
            <button
              type="button"
              onclick={() => selectDatabase(db)}
              class="w-full flex items-center gap-2 px-3 py-2 text-sm hover:bg-blue-50 border-b border-gray-100 last:border-b-0 transition-colors {String(db.id) === String(value) ? 'bg-blue-50' : ''}"
              role="option"
              aria-selected={String(db.id) === String(value)}
            >
              <span class="font-medium text-gray-900">{db.database_name}</span>
              {#if db.engine}
                <span class="ml-auto text-xs text-gray-400">{db.engine}</span>
              {/if}
            </button>
          {/each}
        {/if}
      </div>
    </div>
  {/if}
</div>
<!-- #endregion DatabaseSearchCombobox -->
