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

  /**
   * Searchable single-select combobox for datasets.
   * @prop {string} envId - Environment ID for API calls
   * @prop {string} value - Bindable selected dataset 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 datasets...',
    disabled = false,
  } = $props();

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

  // Find the currently selected dataset from the loaded list
  $effect(() => {
    if (value && datasets.length > 0) {
      const found = datasets.find(d => String(d.id) === String(value));
      if (found) {
        selectedDataset = found;
        searchQuery = found.table_name;
      }
    } else if (!value) {
      selectedDataset = null;
      if (!isOpen) searchQuery = '';
    }
  });

  async function handleInput(e) {
    searchQuery = e.target.value;
    if (!isOpen) isOpen = true;
    await fetchDatasets(searchQuery);
  }

  async function fetchDatasets(search) {
    if (!envId) return;
    isLoading = true;
    try {
      const result = await api.getDatasets(envId, { search: search || undefined, page_size: 50 });
      datasets = result?.datasets || result || [];
      // Re-apply selection highlight
      if (value) {
        selectedDataset = datasets.find(d => String(d.id) === String(value)) || selectedDataset;
      }
    } catch (e) {
      datasets = [];
      addToast(e.message || 'Failed to load datasets', 'error');
    } finally {
      isLoading = false;
    }
  }

  function handleFocus() {
    if (!disabled) {
      isOpen = true;
      if (datasets.length === 0 && envId) {
        fetchDatasets(searchQuery);
      }
    }
  }

  function selectDataset(dataset) {
    value = String(dataset.id);
    selectedDataset = dataset;
    searchQuery = dataset.table_name;
    isOpen = false;
  }

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

  function handleClickOutside(e) {
    if (containerEl && !containerEl.contains(e.target)) {
      isOpen = false;
      // Restore search query to selected dataset name if any
      if (selectedDataset && !isOpen) {
        searchQuery = selectedDataset.table_name;
      } else if (!selectedDataset && !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) {
      datasets = [];
      selectedDataset = null;
      if (!value) searchQuery = '';
      if (isOpen) fetchDatasets(searchQuery);
    }
  });
</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 && datasets.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 datasets.length === 0}
          <div class="px-3 py-6 text-center text-sm text-gray-400 italic">
            {searchQuery ? 'No datasets match your search' : 'No datasets available'}
          </div>
        {:else}
          {#each datasets as ds (ds.id)}
            <button
              type="button"
              onclick={() => selectDataset(ds)}
              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(ds.id) === String(value) ? 'bg-blue-50' : ''}"
              role="option"
              aria-selected={String(ds.id) === String(value)}
            >
              <span class="font-medium text-gray-900">{ds.table_name}</span>
              <span class="text-gray-400 shrink-0">{ds.schema || ds.schema_name || 'public'}</span>
              <span class="ml-auto text-xs text-gray-400 shrink-0">{ds.database} · {ds.mapped_fields ? `${ds.mapped_fields.mapped}/${ds.mapped_fields.total}` : 'unknown'}</span>
            </button>
          {/each}
        {/if}
      </div>
    </div>
  {/if}
</div>
<!-- #endregion DatasetSearchCombobox -->
