<!-- #region DatasetHub [C:5] [TYPE Page] [SEMANTICS sveltekit,dataset,workspace] -->
<!-- @ingroup Routes -->
<!-- @BRIEF Datasets hub — renders DatasetsHubModel state, delegates all logic to the model. -->
<!-- @LAYER Page -->
<!-- @RELATION BINDS_TO -> [DatasetsHubModel] -->
<!-- @UX_STATE loading -> Skeleton, stats bar pulsing. -->
<!-- @UX_STATE loaded -> Dataset list with detail preview split view. -->
<!-- @UX_STATE empty -> No datasets match filters. -->
<!-- @UX_STATE error -> Error banner with retry. -->
<script lang="ts">
  import { onMount, onDestroy } from 'svelte';
  import { SvelteSet } from 'svelte/reactivity';
  import { t } from '$lib/i18n/index.svelte.js';
  import { api } from '$lib/api.js';
  import HelpTooltip from '$lib/ui/HelpTooltip.svelte';
  import { environmentContextStore, initializeEnvironmentContext, setSelectedEnvironment } from '$lib/stores/environmentContext.svelte.js';
  import { openDrawerForTaskIfPreferred } from '$lib/stores/taskDrawer.svelte.js';
  import StatsBar from './StatsBar.svelte';
  import DatasetList from './DatasetList.svelte';
  import DatasetPreview from './DatasetPreview.svelte';
  import { DatasetsHubModel } from '$lib/models/DatasetsHubModel.svelte';

  const model = new DatasetsHubModel();

  // ── WebSocket ─────────────────────────────────────────────────
  let ws: WebSocket | null = null;
  let _envForWs: string | null = null;

  function connectWs(envId: string) {
    if (_envForWs === envId) return;
    disconnectWs();
    const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
    let wsUrl = `${proto}//${window.location.host}/ws/datasets/${envId}`;
    const token = localStorage.getItem('auth_token');
    if (token) wsUrl += `?token=${encodeURIComponent(token)}`;
    ws = new WebSocket(wsUrl);
    ws.onmessage = (e) => {
      try {
        const msg = JSON.parse(e.data);
        if (msg.type === 'dataset.updated') {
          model.loadDatasets();
          if (model.selectedDatasetId && msg.payload?.dataset_ids?.includes(model.selectedDatasetId))
            model.loadDetail(model.selectedDatasetId);
        }
      } catch { /* ignore */ }
    };
    _envForWs = envId;
  }

  function disconnectWs() { if (ws) { ws.close(); ws = null; _envForWs = null; } }

  // ── Mobile ────────────────────────────────────────────────────
  let resizeHandler: (() => void) | null = null;

  function handleEscape(e: KeyboardEvent) {
    if (e.key === 'Escape' && model.mobileSlideOpen) model.closeDetail();
  }

  onMount(async () => {
    await initializeEnvironmentContext();
    const envId = environmentContextStore.value?.selectedEnvId;
    if (envId) {
      model.selectedEnv = envId;
      setSelectedEnvironment(envId);
      await model.loadDatasets();
      connectWs(envId);
    }
    model.isMobile = window.innerWidth < 768;
    resizeHandler = () => { model.isMobile = window.innerWidth < 768; };
    window.addEventListener('resize', resizeHandler);
    document.addEventListener('keydown', handleEscape);
  });

  onDestroy(() => {
    disconnectWs();
    if (resizeHandler) window.removeEventListener('resize', resizeHandler);
    document.removeEventListener('keydown', handleEscape);
  });

  // ── Environment switch reactivity ─────────────────────────────
  $effect(() => {
    const next = environmentContextStore.value?.selectedEnvId;
    if (next && next !== model.selectedEnv) {
      model.selectedEnv = next;
      setSelectedEnvironment(next);
      model.page = 1;
      model.selectedIds = new SvelteSet();
      model.closeDetail();
      disconnectWs();
      model.loadDatasets();
      connectWs(next);
    }
  });

  // ── Task drawer hook ──────────────────────────────────────────
  async function runBulkWithTask(apiCall: () => Promise<{ task_id?: string }>) {
    try {
      const resp = await apiCall();
      model.showMapColumnsModal = false;
      model.showGenerateDocsModal = false;
      model.selectedIds = new SvelteSet();
      if (resp?.task_id) openDrawerForTaskIfPreferred(resp.task_id);
    } catch {
      alert(t.datasets?.mapping_task_failed);
    }
  }
</script>

<div class="max-w-screen-2xl mx-auto px-4 pt-4 pb-6">
  <!-- Header -->
  <div class="flex items-center justify-between mb-4">
    <h1 class="text-2xl font-bold text-text">{$t.nav?.datasets}</h1>
    <div class="flex items-center gap-3">
      <span class="px-2 py-1 text-xs rounded border border-border-strong bg-surface-muted text-text-muted">{$t.datasets?.all}: {model.stats?.total ?? 0}</span>
      <button class="px-3 py-1 text-sm bg-primary text-white rounded hover:bg-primary-hover" title={$t.datasets?.refresh_tip} onclick={() => model.loadDatasets()}>{$t.common?.refresh ?? '⟳'}</button>
    </div>
  </div>

  <!-- Error Banner -->
  {#if model.dsError}
    <div class="bg-destructive-light border border-destructive-ring text-destructive px-3 py-2 rounded mb-4 text-sm flex items-center justify-between" role="alert">
      <span>{model.dsError}</span>
      <button class="px-2 py-0.5 bg-destructive text-white rounded text-xs" onclick={() => model.loadDatasets()}>{$t.common?.retry ?? 'Retry'}</button>
    </div>
  {/if}

  <!-- Stats Bar -->
  <StatsBar stats={model.stats} activeFilter={model.activeFilter} isLoading={model.isLoading} onfilter={(k: string) => model.setFilter(k)} />

  <!-- Split View -->
  <div class="grid gap-4 {model.isMobile ? 'block' : 'grid-cols-[40%_60%]'}" style="min-height: 60vh">
    <!-- Left Panel: Dataset List -->
    <div class={model.isMobile && model.mobileSlideOpen ? 'hidden' : ''}>
      <DatasetList
        datasets={model.datasets}
        selectedIds={model.selectedIds}
        isLoading={model.isLoading}
        error={null}
        total={model.total}
        page={model.page}
        totalPages={model.totalPages}
        pageSize={model.pageSize}
        searchQuery={model.searchQuery}
        onselect={(id: string) => model.selectDataset(id)}
        onaction={(ds: Record<string, unknown>, action: string) => model.handleAction(ds, action)}
        onpagechange={(p: number) => model.setPage(p)}
        onsearch={(q: string) => model.setSearch(q)}
        oncheckbox={(ds: Record<string, unknown>, checked: boolean | 'all' | 'clear-all') => model.toggleSelect(ds as { id: string } & typeof ds, checked)}
      />
    </div>

    <!-- Right Panel / Mobile slide-over -->
    {#if model.isMobile && model.mobileSlideOpen}
      <button class="fixed inset-0 bg-black/30 z-40" onclick={() => model.closeDetail()} aria-label="Close detail"></button>
      <div class="fixed inset-y-0 right-0 w-[90vw] max-w-lg bg-surface-card shadow-xl z-50 overflow-y-auto" role="dialog" aria-modal="true">
        <div class="sticky top-0 bg-surface-card border-b border-border px-4 py-2 flex items-center justify-between z-10">
          <span class="font-medium text-sm">{model.detailData?.table_name ?? $t.datasets?.no_selection}</span>
          <button class="text-text-subtle hover:text-text text-lg" onclick={() => model.closeDetail()} aria-label="Close">&times;</button>
        </div>
        <DatasetPreview dataset={model.detailData} isLoading={model.detailLoading} error={model.detailError} onretry={() => model.selectedDatasetId && model.loadDetail(model.selectedDatasetId)} envId={model.selectedEnv} />
      </div>
    {:else if !model.isMobile}
      <div class="bg-surface-card rounded-lg border border-border overflow-hidden">
        <DatasetPreview dataset={model.detailData} isLoading={model.detailLoading} error={model.detailError} onretry={() => model.selectedDatasetId && model.loadDetail(model.selectedDatasetId)} envId={model.selectedEnv} />
      </div>
    {/if}
  </div>

  <!-- Bulk Action Panel -->
  {#if model.selectionCount >= 2}
    <div class="fixed bottom-0 left-0 right-0 bg-surface-card border-t border-border shadow-lg p-3 z-50">
      <div class="max-w-screen-2xl mx-auto flex items-center justify-between">
        <span class="font-medium text-sm">✓ {model.selectionCount} {$t.datasets?.selected}</span>
        <div class="flex gap-2">
          <button class="px-3 py-1.5 text-sm bg-primary text-white rounded hover:bg-primary-hover" onclick={() => { model.showMapColumnsModal = true; model.mapSourceType = 'sqllab'; model.mapDatabaseId = null; model.mapSqlQuery = ''; model.mapFileData = null; if (model.selectedEnv) api.getEnvironmentDatabases(model.selectedEnv).then(dbs => { model.mapDatabases = (dbs ?? []) as unknown[]; }).catch(() => {}); }}>🗺 {$t.datasets?.action_map_columns}</button>
          <button class="px-3 py-1.5 text-sm bg-primary text-white rounded hover:bg-primary-hover" onclick={() => { model.showGenerateDocsModal = true; }}>📄 {$t.datasets?.generate_docs}</button>
          <button class="px-3 py-1.5 text-sm border border-border-strong rounded hover:bg-surface-muted" onclick={() => model.selectedIds = new SvelteSet()}>&times; {$t.common?.cancel ?? 'Clear'}</button>
        </div>
      </div>
    </div>
  {/if}

  <!-- Map Columns Modal -->
  {#if model.showMapColumnsModal}
    <div class="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4" role="dialog" aria-modal="true" onclick={(e) => { if (e.target === e.currentTarget) model.showMapColumnsModal = false; }}>
      <div class="bg-surface-card rounded-lg shadow-xl p-6 max-w-md w-full" style="max-height: 90vh; overflow-y: auto;" onclick={(e) => e.stopPropagation()}>
        <h3 class="text-lg font-semibold mb-4">{$t.datasets?.bulk_map_columns}</h3>
        <p class="text-sm text-text-muted mb-3">{$t.datasets?.selected_count?.replace('{count}', String(model.selectionCount))}</p>

        <label class="text-sm font-medium text-text mb-1.5 flex items-center gap-1.5">{$t.datasets?.source_type ?? 'Тип источника'} <HelpTooltip text={$t.datasets?.source_type_tip || ''} /></label>
        <select class="w-full border rounded px-2 py-1.5 mb-3" bind:value={model.mapSourceType} onchange={() => { if (model.mapSourceType === 'sqllab' && model.mapDatabases.length === 0 && model.selectedEnv) { api.getEnvironmentDatabases(model.selectedEnv).then(dbs => { model.mapDatabases = (dbs ?? []) as unknown[]; }).catch(() => {}); } }}>
          <option value="sqllab">{$t.datasets?.source_sqllab ?? 'Superset SQL Lab'}</option>
          <option value="xlsx">{$t.datasets?.source_xlsx}</option>
        </select>

        {#if model.mapSourceType === 'sqllab'}
          <label class="text-sm font-medium text-text mb-1.5 flex items-center gap-1.5" for="map-database-select">{$t.datasets?.source_database ?? 'База данных'} <HelpTooltip text={$t.datasets?.database_tip || ''} /></label>
          <select id="map-database-select" class="w-full border rounded px-2 py-1.5 mb-3" bind:value={model.mapDatabaseId}>
            <option value="">-- {$t.datasets?.select_database ?? 'выберите базу данных'} --</option>
            {#each model.mapDatabases as db}
              <option value={(db as { id: string }).id}>{(db as { database_name?: string; name?: string }).database_name || (db as { database_name?: string; name?: string }).name}</option>
            {/each}
          </select>

          <label class="text-sm font-medium text-text mb-1.5 flex items-center gap-1.5" for="map-sql-query">{$t.datasets?.sql_query ?? 'SQL-запрос'} <span class="text-xs text-text-subtle font-normal ml-1">({$t.datasets?.sql_query_hint ?? 'оставьте пустым для information_schema'})</span> <HelpTooltip text={$t.datasets?.sql_query_tip || ''} /></label>
          <div class="flex flex-wrap gap-1.5 mb-2">
            <span class="text-xs text-text-subtle mr-0.5 self-center">{$t.datasets?.sql_templates ?? 'Шаблоны'}:</span>
            <button type="button" class="px-2 py-0.5 text-xs border border-border-strong rounded-full text-text-muted hover:bg-primary-light hover:border-primary-ring hover:text-primary transition-colors" onclick={() => { model.mapSqlQuery = "SELECT c.column_name, c.data_type, pgd.description\nFROM information_schema.columns c\nLEFT JOIN pg_catalog.pg_description pgd ON pgd.objsubid = c.ordinal_position\n  AND pgd.objoid = (SELECT attrelid FROM pg_attribute WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = c.table_name) AND attname = c.column_name)\nWHERE c.table_schema = 'public'\n  AND c.table_name = 'your_table_name'\nORDER BY c.ordinal_position;"; }}>information_schema</button>
            <button type="button" class="px-2 py-0.5 text-xs border border-border-strong rounded-full text-text-muted hover:bg-primary-light hover:border-primary-ring hover:text-primary transition-colors" onclick={() => { model.mapSqlQuery = "SELECT column_name, description AS verbose_name\nFROM my_column_mappings\nWHERE table_name = 'your_table_name'\nORDER BY column_name;"; }}>{$t.datasets?.sql_template_custom ?? 'Кастомная таблица'}</button>
            <button type="button" class="px-2 py-0.5 text-xs border border-border-strong rounded-full text-text-subtle hover:border-destructive-ring hover:text-destructive transition-colors" onclick={() => { model.mapSqlQuery = ''; }}>{$t.datasets?.sql_template_clear ?? 'Очистить'}</button>
          </div>
          <textarea id="map-sql-query" class="w-full border rounded px-2 py-1.5 mb-4 font-mono text-xs" rows="4" placeholder="SELECT column_name, description AS verbose_name FROM my_mappings WHERE table_name = 'my_table';" bind:value={model.mapSqlQuery}></textarea>
        {:else}
          <label class="text-sm font-medium text-text mb-1.5 flex items-center gap-1.5" for="map-file-input">{$t.datasets?.xlsx_file ?? 'XLSX-файл'} <HelpTooltip text={$t.datasets?.file_input_tip || ''} /></label>
          <input id="map-file-input" type="file" accept=".xlsx" class="w-full mb-4" onchange={(e) => model.mapFileData = (e.target as HTMLInputElement).files} />
        {/if}

        <div class="flex justify-end gap-2">
          <button class="px-3 py-1.5 border rounded hover:bg-surface-muted" onclick={() => model.showMapColumnsModal = false}>{$t.common?.cancel}</button>
          <button class="px-3 py-1.5 bg-primary text-white rounded hover:bg-primary-hover" onclick={() => runBulkWithTask(() => model.handleBulkMapColumns())}>{$t.datasets?.start_mapping}</button>
        </div>
      </div>
    </div>
  {/if}

  <!-- Generate Docs Modal -->
  {#if model.showGenerateDocsModal}
    <div class="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4" role="dialog" aria-modal="true" onclick={(e) => { if (e.target === e.currentTarget) model.showGenerateDocsModal = false; }}>
      <div class="bg-surface-card rounded-lg shadow-xl p-6 max-w-md w-full" onclick={(e) => e.stopPropagation()}>
        <h3 class="text-lg font-semibold mb-4">{$t.datasets?.bulk_docs_generation}</h3>
        <p class="text-sm text-text-muted mb-3">{$t.datasets?.selected_count?.replace('{count}', String(model.selectionCount))}</p>
        <label class="text-sm font-medium text-text mb-1.5 flex items-center gap-1.5">{$t.datasets?.select_llm_provider ?? 'LLM-провайдер'} <HelpTooltip text={$t.datasets?.llm_provider_tip || ''} /></label>
        <select class="w-full border rounded px-2 py-1.5 mb-4" bind:value={model.llmProvider}>
          <option value="">{$t.datasets?.select_llm_provider_option}</option>
          <option value="openai">OpenAI</option>
          <option value="anthropic">Anthropic</option>
          <option value="local">Local LLM</option>
        </select>
        <div class="flex justify-end gap-2">
          <button class="px-3 py-1.5 border rounded hover:bg-surface-muted" onclick={() => model.showGenerateDocsModal = false}>{$t.common?.cancel}</button>
          <button class="px-3 py-1.5 bg-primary text-white rounded hover:bg-primary-hover" onclick={() => runBulkWithTask(() => model.handleBulkGenerateDocs())}>{$t.datasets?.generate_docs}</button>
        </div>
      </div>
    </div>
  {/if}
</div>
<!-- #endregion DatasetHub -->
