// #region DatasetsHubModel [C:4] [TYPE Model] [SEMANTICS datasets,list,filter,pagination,bulk-action,model]
// @ingroup Models
// @BRIEF Screen model for the datasets hub — manages list state, selection, detail preview, modals, and WebSocket updates.
// @INVARIANT Changing filter or search resets pagination to page 1.
// @INVARIANT selectedIds is always a subset of loaded datasets — orphan IDs cleaned after each load.
// @INVARIANT Detail panel opens only when selectedDatasetId is truthy; closing clears detailData.
// @STATE idle      — No environment selected.
// @STATE loading   — Dataset list is loading.
// @STATE loaded    — Datasets loaded, list rendered.
// @STATE empty     — No datasets match current filters.
// @STATE error     — Load failed, error shown.
// @ACTION loadDatasets(filter?, page?)   — Fetches paginated list with current search/filter.
// @ACTION loadDetail(datasetId)          — Fetches detail preview for selected dataset.
// @ACTION setFilter(filterKey)           — Sets active filter, resets pagination, reloads.
// @ACTION setSearch(query)              — Debounced search with pagination reset.
// @ACTION setPage(n)                    — Navigates to page.
// @ACTION toggleSelect(dataset, checked) — Toggles single dataset in selection set.
// @ACTION selectAll() / clearAll()       — Bulk selection operations.
// @ACTION handleAction(dataset, action)  — Opens map-columns or generate-docs modal.
// @ACTION handleBulkMapColumns()         — Runs bulk map-columns task.
// @ACTION handleBulkGenerateDocs()       — Runs bulk generate-docs task.
// @ACTION selectDataset(id)              — Opens detail preview panel.
// @ACTION closeDetail()                  — Closes detail preview panel.
// @RELATION DEPENDS_ON -> [ApiModule]
// @RELATION CALLS -> [CotLogger]
// @INVARIANT DECOMPOSITION GATE: 20+ atoms. If model exceeds 400 lines or 40 methods, split into submodels.

import { SvelteSet } from 'svelte/reactivity';
import { api } from '$lib/api.js';
import { log } from '$lib/cot-logger';
import { debounce } from '$lib/utils/debounce.js';

// ── Types ───────────────────────────────────────────────────────

type ScreenState = 'idle' | 'loading' | 'loaded' | 'empty' | 'error';

interface DatasetItem {
  id: string;
  table_name: string;
  schema: string;
  database: string;
  column_count: number;
  metric_count: number;
  mappedFields: { total: number; mapped: number } | null;
  lastTask: { status: string; id: string } | null;
}

interface DatasetStats {
  total?: number;
  mapped?: number;
  [key: string]: unknown;
}

interface MapColumnsResponse {
  task_id: string;
  [key: string]: unknown;
}

export class DatasetsHubModel {
  // ── Environment ───────────────────────────────────────────────
  selectedEnv: string | null = $state(null);

  // ── List state ────────────────────────────────────────────────
  datasets: DatasetItem[] = $state([]);
  stats: DatasetStats | null = $state(null);
  screenState: ScreenState = $state('idle');
  dsError: string | null = $state(null);
  page: number = $state(1);
  total: number = $state(0);
  totalPages: number = $state(1);
  pageSize: number = $state(10);
  searchQuery: string = $state('');
  activeFilter: string | null = $state(null);

  // ── Selection ─────────────────────────────────────────────────
  selectedIds: Set<string> = $state(new SvelteSet());

  // ── Detail preview ────────────────────────────────────────────
  selectedDatasetId: string | null = $state(null);
  detailData: Record<string, unknown> | null = $state(null);
  detailLoading: boolean = $state(false);
  detailError: string | null = $state(null);

  // ── Modals ────────────────────────────────────────────────────
  showMapColumnsModal: boolean = $state(false);
  showGenerateDocsModal: boolean = $state(false);
  mapSourceType: string = $state('sqllab');
  mapDatabases: unknown[] = $state([]);
  mapDatabaseId: string | null = $state(null);
  mapSqlQuery: string = $state('');
  mapFileData: unknown = $state(null);
  llmProvider: string = $state('');
  llmOptions: Record<string, unknown> = $state({});

  // ── Mobile ────────────────────────────────────────────────────
  isMobile: boolean = $state(false);
  mobileSlideOpen: boolean = $state(false);

  // ── Derived ───────────────────────────────────────────────────
  isLoading = $derived(this.screenState === 'loading');
  hasSelection = $derived(this.selectedIds.size > 0);
  selectionCount = $derived(this.selectedIds.size);

  // ── Private ───────────────────────────────────────────────────
  private _ws: unknown = null;
  private _debouncedSearch: ReturnType<typeof debounce>;

  constructor() {
    this._debouncedSearch = debounce((q: string) => {
      this.searchQuery = q;
      this.page = 1;
      this.loadDatasets();
    }, 300);
  }

  // ── Actions ───────────────────────────────────────────────────

  async loadDatasets(filter?: string | null, p?: number): Promise<void> {
    if (!this.selectedEnv) return;

    log('DatasetsHubModel', 'REASON', 'Loading datasets', {
      env: this.selectedEnv, filter: filter ?? this.activeFilter, page: p ?? this.page,
    });

    this.screenState = 'loading';
    this.dsError = null;
    try {
      const resp = await api.getDatasets(this.selectedEnv, {
        search: this.searchQuery || undefined,
        filter: (filter ?? this.activeFilter) ?? undefined,
        page: p ?? this.page,
        page_size: this.pageSize,
      });

      this.datasets = (resp.datasets as DatasetItem[]).map((d: { id: string; table_name: string; schema: string; database: string; column_count?: number; metric_count?: number; mapped_fields?: { total: number; mapped: number }; last_task?: { status: string; task_id: string } }) => ({
        id: d.id,
        table_name: d.table_name,
        schema: d.schema,
        database: d.database,
        column_count: d.mapped_fields?.total ?? 0,
        metric_count: d.metric_count ?? 0,
        mappedFields: d.mapped_fields ? { total: d.mapped_fields.total, mapped: d.mapped_fields.mapped } : null,
        lastTask: d.last_task ? { status: d.last_task.status, id: d.last_task.task_id } : null,
      }));

      this.stats = resp.stats ?? null;
      this.total = resp.total ?? 0;
      this.totalPages = resp.total_pages ?? 1;
      this.page = resp.page ?? 1;

      // Clean orphan selection IDs
      const keepIds = new SvelteSet<string>();
      this.datasets.forEach(d => { if (this.selectedIds.has(d.id)) keepIds.add(d.id); });
      this.selectedIds = keepIds;

      this.screenState = this.datasets.length === 0 ? 'empty' : 'loaded';

      log('DatasetsHubModel', 'REFLECT', 'Datasets loaded', {
        count: this.datasets.length, total: this.total, page: this.page,
      });
    } catch (e: unknown) {
      this.dsError = e instanceof Error ? e.message : 'Failed to load datasets';
      this.screenState = 'error';
      log('DatasetsHubModel', 'EXPLORE', 'Failed to load datasets', { env: this.selectedEnv }, String(this.dsError));
    }
  }

  setFilter(filterKey: string): void {
    this.activeFilter = filterKey;
    this.page = 1;
    this.loadDatasets(filterKey, 1);
  }

  setSearch(q: string): void {
    this._debouncedSearch(q);
  }

  setPage(p: number): void {
    this.page = p;
    this.loadDatasets(undefined, p);
  }

  toggleSelect(dataset: DatasetItem, checked: boolean | 'all' | 'clear-all'): void {
    if (checked === 'all') {
      this.datasets.forEach(d => this.selectedIds.add(d.id));
      this.selectedIds = new SvelteSet(this.selectedIds);
    } else if (checked === 'clear-all') {
      this.selectedIds = new SvelteSet();
    } else if (checked) {
      this.selectedIds.add(dataset.id);
      this.selectedIds = new SvelteSet(this.selectedIds);
    } else {
      this.selectedIds.delete(dataset.id);
      this.selectedIds = new SvelteSet(this.selectedIds);
    }
  }

  selectAll(): void { this.toggleSelect({ id: '' } as DatasetItem, 'all'); }
  clearAll(): void { this.toggleSelect({ id: '' } as DatasetItem, 'clear-all'); }

  async loadDetail(datasetId: string): Promise<void> {
    if (!datasetId) return;
    this.detailLoading = true;
    this.detailError = null;
    try {
      const d = await api.getDatasetDetail(this.selectedEnv!, datasetId);
      this.detailData = { ...d, schema: (d as { schema_name?: string; schema?: string }).schema_name ?? (d as { schema_name?: string; schema?: string }).schema };
    } catch (e: unknown) {
      this.detailError = e instanceof Error ? e.message : 'Failed to load detail';
    } finally {
      this.detailLoading = false;
    }
  }

  selectDataset(id: string): void {
    this.selectedDatasetId = id;
    this.detailData = null;
    this.detailError = null;
    if (this.isMobile) this.mobileSlideOpen = true;
    this.loadDetail(id);
  }

  closeDetail(): void {
    this.selectedDatasetId = null;
    this.detailData = null;
    this.detailError = null;
    this.mobileSlideOpen = false;
  }

  handleAction(dataset: DatasetItem, action: string): void {
    if (action === 'map_columns') {
      this.selectedIds = new SvelteSet([dataset.id]);
      this.showMapColumnsModal = true;
      this.mapSourceType = 'sqllab';
      this.mapDatabaseId = null;
      this.mapSqlQuery = '';
      this.mapFileData = null;
      if (this.selectedEnv) {
        api.getEnvironmentDatabases(this.selectedEnv).then((dbs: unknown) => { this.mapDatabases = (dbs as unknown[]) ?? []; }).catch(() => {});
      }
    } else if (action === 'generate_docs') {
      this.selectedIds = new SvelteSet([dataset.id]);
      this.showGenerateDocsModal = true;
      this.llmProvider = '';
      this.llmOptions = {};
    }
  }

  async handleBulkMapColumns(): Promise<void> {
    if (this.selectedIds.size === 0) return;
    if (this.mapSourceType === 'sqllab' && !this.mapDatabaseId) return;
    if (this.mapSourceType === 'xlsx' && (!this.mapFileData || (this.mapFileData as unknown[]).length === 0)) return;

    try {
      let fileData = null;
      if (this.mapSourceType === 'xlsx' && (this.mapFileData as unknown[])?.length)
        fileData = (this.mapFileData as unknown[])[0] as { name: string };

      const resp = await api.postApi('/datasets/map-columns', {
        env_id: this.selectedEnv,
        dataset_ids: [...this.selectedIds],
        source_type: this.mapSourceType,
        database_id: this.mapDatabaseId,
        sql_query: this.mapSqlQuery,
        file_data: fileData?.name,
      }) as MapColumnsResponse;

      log('DatasetsHubModel', 'REFLECT', 'Bulk map columns started', { taskId: resp.task_id });
      this.showMapColumnsModal = false;
      this.selectedIds = new SvelteSet();
    } catch (e: unknown) {
      log('DatasetsHubModel', 'EXPLORE', 'Bulk map columns failed', {}, String(e));
    }
  }

  async handleBulkGenerateDocs(): Promise<void> {
    if (this.selectedIds.size === 0) return;
    try {
      const resp = await api.postApi('/datasets/generate-docs', {
        env_id: this.selectedEnv,
        dataset_ids: [...this.selectedIds],
        llm_provider: this.llmProvider,
        llm_options: this.llmOptions,
      }) as MapColumnsResponse;

      log('DatasetsHubModel', 'REFLECT', 'Bulk docs generation started', { taskId: resp.task_id });
      this.showGenerateDocsModal = false;
      this.selectedIds = new SvelteSet();
    } catch (e: unknown) {
      log('DatasetsHubModel', 'EXPLORE', 'Bulk docs failed', {}, String(e));
    }
  }
}
// #endregion DatasetsHubModel
