import { signal } from '@angular/core';
import type { DocumentNode } from 'graphql';
import { firstValueFrom } from 'rxjs';
import { buildSearchWhere, buildSortOrder, DEFAULT_PAGE_SIZE } from '../components/data-table';
import { useGraphqlPaginate } from '../composables/atoms/graphql/use-graphql-paginate';

export interface UseAsyncSearchListConfig<T> {
  /** GraphQL list config of the target aggregate. */
  listConfig: {
    queryPagination: DocumentNode;
    paginationKey: string;
    fields?: string;
  };
  /** Searchable column ids — typically `getSearchableColumnIds(targetColumns)`. */
  searchColumns: string[];
  /** Initial rows seeded from the resolver (avoids extra fetch on first open). */
  initial?: T[];
  /** Page size for `paginate()` calls. Defaults to `DEFAULT_PAGE_SIZE`. */
  pageSize?: number;
}

/**
 * Async, paginated, searchable option list for relational widgets.
 *
 * Encapsulates the state and side effects needed to drive a search-select
 * combobox against a target aggregate's GraphQL list-config:
 *
 * - Tracks current `query` and `offset`.
 * - Replaces options on `onSearch(q)` (resets offset to 0).
 * - Appends options on `onLoadMore()` (offset += pageSize).
 * - Stops firing when a fetch returns fewer rows than `pageSize` (`hasMore` heuristic).
 *
 * Designed for `<au-async-search-select>` / `<au-async-multiple-search-select>`
 * hosts. Sync variants don't need this — their options are fully preloaded.
 */
export function useAsyncSearchList<T>(config: UseAsyncSearchListConfig<T>) {
  const pageSize = config.pageSize ?? DEFAULT_PAGE_SIZE;
  const initial = config.initial ?? [];

  const paginate = useGraphqlPaginate<T>(
    config.listConfig.queryPagination,
    config.listConfig.paginationKey,
    config.listConfig.fields,
  );

  const options = signal<T[]>(initial);
  const query = signal<string>('');
  const offset = signal<number>(0);
  // Heuristic: if the initial seed already filled a page, more may exist.
  const hasMore = signal<boolean>(initial.length >= pageSize);

  async function fetchPage(off: number): Promise<T[]> {
    const where = query() ? buildSearchWhere(query(), config.searchColumns) : undefined;
    const data = await firstValueFrom(
      paginate.paginate({
        query: { where, offset: off, limit: pageSize, order: buildSortOrder([]) },
      }),
    );
    return data.rows;
  }

  async function onSearch(q: string): Promise<void> {
    query.set(q);
    offset.set(0);
    hasMore.set(true);
    const rows = await fetchPage(0);
    options.set(rows);
    hasMore.set(rows.length >= pageSize);
  }

  async function onLoadMore(): Promise<void> {
    if (!hasMore()) return;
    if (paginate.loading()) return;
    const newOffset = offset() + pageSize;
    const rows = await fetchPage(newOffset);
    offset.set(newOffset);
    options.update((curr) => [...curr, ...rows]);
    hasMore.set(rows.length >= pageSize);
  }

  return {
    options: options.asReadonly(),
    loading: paginate.loading,
    hasMore: hasMore.asReadonly(),
    onSearch,
    onLoadMore,
  };
}

export type AsyncSearchList<T> = ReturnType<typeof useAsyncSearchList<T>>;
