import { computed, signal } from '@angular/core';
import { firstValueFrom } from 'rxjs';
import type { FindByIdVariables } from '../../modules/graphql/fetchers/types';
import { useGraphqlDetail } from './use-graphql-detail';
import { useGraphqlList } from './use-graphql-list';

/**
 * Options for {@link useAggregateShell}. The two configs have the same shape
 * that `useGraphqlList` / `useGraphqlDetail` expect.
 */
export interface UseAggregateShellConfig<T> {
  readonly listConfig: Parameters<typeof useGraphqlList<T>>[0];
  readonly detailConfig: Parameters<typeof useGraphqlDetail<T>>[0];
}

/**
 * Aggregate shell preset — single seam shared by the routed detail shell
 * (view mode) and the list-with-dialog shell (dialog mode) in generated
 * modules. Composes `useGraphqlList` + `useGraphqlDetail` and adds:
 *
 *  - `fetchForEdit(id)` — standardized fetch that returns the full aggregate
 *    (with relational includes from `detailConfig.fields`) for opening a
 *    dialog in edit mode.
 *  - `save(value, mode)` — dispatches `create` when `mode === 'new'` and
 *    `update` when `mode === 'edit'`.
 *  - `initial` / `setInitial` — signal mirroring the aggregate currently
 *    hydrated into the form.
 *  - `loading` — composite signal across all underlying ops.
 *  - `error` — last error observed; cleared on the next successful op.
 *  - `list` / `detail` — re-exposed underlying composables for direct use.
 */
export function useAggregateShell<T>(config: UseAggregateShellConfig<T>) {
  const detail = useGraphqlDetail<T>(config.detailConfig);
  const list = useGraphqlList<T>(config.listConfig);

  const initialSignal = signal<T | null>(null);
  const errorSignal = signal<Error | null>(null);

  const loading = computed(() => detail.loading() || list.loading());

  async function fetchForEdit(id: string, variables?: FindByIdVariables): Promise<T> {
    try {
      const result = await firstValueFrom(detail.findById(id, variables));
      if (result == null) {
        throw new Error(`useAggregateShell.fetchForEdit: aggregate not found for id "${id}"`);
      }
      initialSignal.set(result);
      errorSignal.set(null);
      return result;
    } catch (err) {
      errorSignal.set(err as Error);
      throw err;
    }
  }

  async function save(value: T, mode: 'edit' | 'new'): Promise<void> {
    try {
      const payload = value as unknown as Record<string, unknown>;
      const op = mode === 'new' ? detail.create(payload) : detail.update(payload);
      await firstValueFrom(op);
      errorSignal.set(null);
    } catch (err) {
      errorSignal.set(err as Error);
      throw err;
    }
  }

  function setInitial(value: T | null): void {
    initialSignal.set(value);
  }

  return {
    list,
    detail,
    fetchForEdit,
    save,
    setInitial,
    initial: initialSignal.asReadonly(),
    loading,
    error: errorSignal.asReadonly(),
  };
}

export type AggregateShell<T> = ReturnType<typeof useAggregateShell<T>>;
