import { signal } from '@angular/core';
import { Operator } from '@aurorajs.dev/core-common';
import type { DocumentNode } from 'graphql';
import { firstValueFrom } from 'rxjs';
import { useGraphqlDelete } from '../atoms/graphql/use-graphql-delete';
import { useGraphqlGet } from '../atoms/graphql/use-graphql-get';
import { useGraphqlInsert } from '../atoms/graphql/use-graphql-insert';

export interface PivotMembershipConfig {
  /**
   * GraphQL operations for the pivot aggregate. Expected shape:
   *   - `getQuery` (unpaginated read filtered by parent)
   *   - `insertMutation` (bulk create with `payload: [InputType]!`)
   *   - `deleteMutation` (where-based bulk delete with `query: { where }`)
   */
  readonly pivotConfig: {
    readonly getQuery: DocumentNode;
    readonly getKey: string;
    readonly insertMutation: DocumentNode;
    readonly deleteMutation: DocumentNode;
    readonly fields?: string;
  };
  /** Pivot column referencing the parent aggregate (e.g. `'roleId'`). */
  readonly parentField: string;
  /** Pivot column referencing the child aggregate (e.g. `'permissionId'`). */
  readonly childField: string;
  /**
   * Reactive id of the parent aggregate. When it returns null/empty,
   * `refresh()` resets `linkedIds` and `link`/`unlink` are no-ops.
   */
  readonly parentId: () => string | null | undefined;
}

/**
 * Preset: parent-scoped pivot membership. Composes `useGraphqlGet` (read all
 * pivot rows for the parent) + `useGraphqlInsert` (bulk link) +
 * `useGraphqlDelete` (where-based bulk unlink for composite keys via
 * `[Operator.or]`). Manages a `linkedIds: Set<string>` derived from the read,
 * and exposes `link`, `unlink`, `refresh`.
 *
 * Duplicates are silently filtered on `link`: child ids already in
 * `linkedIds` are dropped without error.
 */
export function usePivotMembership<TPivot extends Record<string, unknown>>(
  config: PivotMembershipConfig,
) {
  const linkedIdsSignal = signal<Set<string>>(new Set());
  const loadingSignal = signal(false);

  const getter = useGraphqlGet<TPivot>(config.pivotConfig.getQuery, config.pivotConfig.getKey);
  const inserter = useGraphqlInsert(config.pivotConfig.insertMutation);
  const deleter = useGraphqlDelete(config.pivotConfig.deleteMutation);

  async function refresh(): Promise<void> {
    const id = config.parentId();
    if (!id) {
      linkedIdsSignal.set(new Set());
      return;
    }
    loadingSignal.set(true);
    try {
      const items = await firstValueFrom(
        getter.get({ query: { where: { [config.parentField]: id } } }),
      );
      linkedIdsSignal.set(new Set((items ?? []).map((item) => String(item[config.childField]))));
    } finally {
      loadingSignal.set(false);
    }
  }

  async function link(childIds: string[]): Promise<void> {
    const id = config.parentId();
    if (!id || childIds.length === 0) return;

    const current = linkedIdsSignal();
    const toCreate = childIds.filter((cid) => !current.has(cid));
    if (toCreate.length === 0) return;

    loadingSignal.set(true);
    try {
      const payload = toCreate.map((cid) => ({
        [config.parentField]: id,
        [config.childField]: cid,
      }));
      await firstValueFrom(inserter.insert(payload));
      await refresh();
    } finally {
      loadingSignal.set(false);
    }
  }

  async function unlink(childIds: string[]): Promise<void> {
    const id = config.parentId();
    if (!id || childIds.length === 0) return;

    loadingSignal.set(true);
    try {
      const where =
        childIds.length === 1
          ? { [config.parentField]: id, [config.childField]: childIds[0] }
          : {
              [Operator.or]: childIds.map((cid) => ({
                [config.parentField]: id,
                [config.childField]: cid,
              })),
            };
      await firstValueFrom(deleter.delete({ where }));
      await refresh();
    } finally {
      loadingSignal.set(false);
    }
  }

  return {
    linkedIds: linkedIdsSignal.asReadonly(),
    loading: loadingSignal.asReadonly(),
    refresh,
    link,
    unlink,
  };
}

export type PivotMembership = ReturnType<typeof usePivotMembership>;
