import { inject, Injector, runInInjectionContext, signal, type Signal } from '@angular/core';
import { firstValueFrom, type Observable } from 'rxjs';
import type { DataTableData } from '../../../components/data-table/types';

export interface TableDataFetchInput {
  readonly query: Record<string, unknown>;
  readonly constraint?: Record<string, unknown>;
}

export interface TableDataConfig<T> {
  /**
   * Server-paginated fetch function. Typically `useGraphqlList(...).paginate`
   * or `useGraphqlPaginate(...).paginate` or any equivalent that accepts a
   * `{ query, constraint? }` input and emits `Observable<DataTableData<T>>`.
   * Generic by design — `useTableData` does not depend on GraphQL.
   */
  readonly fetch: (input: TableDataFetchInput) => Observable<DataTableData<T>>;
}

export interface TableDataAtom<T> {
  readonly data: Signal<DataTableData<T>>;
  readonly loading: Signal<boolean>;
  refetch(input: TableDataFetchInput): Promise<void>;
  seed(d: DataTableData<T>): void;
}

/**
 * Atom: server-paginated data loader. Single responsibility — owns `data`
 * and `loading` signals plus an imperative `refetch(input)` and a `seed(d)`
 * to preserve resolver prefetch.
 *
 * The atom does NOT build the query — that's the orchestrator's job (e.g.
 * `usePaginatedDataTable` builds `{ query, constraint }` from the search,
 * sort, pagination, filters atoms and passes it to `refetch`). This keeps
 * `useTableData` agnostic to query-builder helpers.
 *
 * MUST be invoked from an injection context — uses `inject(Injector)` so
 * that `firstValueFrom` runs in a child context that exists for the
 * lifetime of the host component.
 */
export function useTableData<T>(config: TableDataConfig<T>): TableDataAtom<T> {
  const injector = inject(Injector);
  const data = signal<DataTableData<T>>({ count: 0, total: 0, rows: [] });
  const loading = signal(false);

  async function refetch(input: TableDataFetchInput): Promise<void> {
    loading.set(true);
    try {
      const result = await runInInjectionContext(injector, () =>
        firstValueFrom(config.fetch(input)),
      );
      data.set(result);
    } finally {
      loading.set(false);
    }
  }

  function seed(d: DataTableData<T>): void {
    data.set(d);
  }

  return {
    data: data.asReadonly(),
    loading: loading.asReadonly(),
    refetch,
    seed,
  };
}
