/**
 * @aurora-catalyst-generated
 * @source cliter/iam/role.aurora.yaml
 */
import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import {
  getRelationIncludes,
  getSearchableColumnIds,
  GridSelectMultipleElements,
  useAggregateShell,
  useBreadcrumbs,
  useGraphqlList,
  usePaginatedDataTable,
  usePivotMembership,
  type DataTableColumnDef,
  type ServerTableState,
} from '@aurora';
import { Operator } from '@aurorajs.dev/core-common';
import { provideTranslocoScope, TranslocoDirective, TranslocoService } from '@jsverse/transloco';
import { NgIcon, provideIcons } from '@ng-icons/core';
import { lucideUsers } from '@ng-icons/lucide';
import { toast } from '@spartan-ng/brain/sonner';
import { HlmButtonImports } from '@spartan-ng/helm/button';
import { HlmCardImports } from '@spartan-ng/helm/card';
import { HlmSpinnerImports } from '@spartan-ng/helm/spinner';
import {
  type IamPermission,
  type IamPermissionRole,
  type IamRole,
} from '@src/src/app/aurora.types';
import { of } from 'rxjs';
import {
  deleteMutation as permissionRoleDeleteMutation,
  fields as permissionRoleFields,
  getQuery as permissionRoleGetQuery,
  insertMutation as permissionRoleInsertMutation,
} from '../../permission-role/data-access/permission-role.graphql';
import { PERMISSION_LIST_CONFIG } from '../../permission/data-access/permission.list-config';
import { getPermissionStandaloneColumns } from '../../permission/permission.columns';
import { ROLE_DETAIL_CONFIG } from '../data-access/role.detail-config';
import { ROLE_LIST_CONFIG } from '../data-access/role.list-config';
import { RoleFormComponent } from './role-form.component';

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [
    TranslocoDirective,
    NgIcon,
    HlmButtonImports,
    HlmCardImports,
    HlmSpinnerImports,
    RoleFormComponent,
    GridSelectMultipleElements,
  ],
  providers: [provideIcons({ lucideUsers }), provideTranslocoScope('iam')],
  template: `
    <ng-container *transloco="let t; scope: 'iam'">
      <div class="space-y-3 px-6 pt-2 pb-6">
        <!-- Header -->
        <div class="mb-2 flex items-center justify-between">
          <h1 class="flex items-center gap-2 text-2xl font-bold tracking-tight">
            <ng-icon
              name="lucideUsers"
              class="text-[1em]"
            />
            {{ mode() === 'new' ? t('Create') : t('Edit') }} {{ t('iam.role.Role') }}
          </h1>
        </div>

        <!-- Toolbar -->
        <div class="flex items-center justify-end gap-2">
          <button
            hlmBtn
            variant="outline"
            size="sm"
            (click)="onCancel()"
          >
            {{ t('Cancel') }}
          </button>
          <button
            [disabled]="form.signalForm.invalid || form.signalForm.pristine || shell.loading()"
            hlmBtn
            size="sm"
            (click)="form.submit()"
          >
            @if (shell.loading()) {
              <hlm-spinner class="text-sm" />
            }
            {{ mode() === 'new' ? t('Create') : t('Save') }}
          </button>
        </div>

        <!-- Form card -->
        <section
          class="max-w-3xl"
          hlmCard
        >
          <div hlmCardHeader>
            <h3 hlmCardTitle>{{ t('iam.role.RoleDetails') }}</h3>
            <p hlmCardDescription>{{ t('iam.role.RoleDetailsDescription') }}</p>
          </div>
          <div hlmCardContent>
            <au-role-form
              #form
              [initial]="shell.initial()"
              [mode]="mode()"
              (save)="onSave($event)"
              (cancel)="onCancel()"
            />
          </div>
        </section>

        @if (mode() === 'edit') {
          <section
            class="mt-4"
            hlmCard
          >
            <div hlmCardHeader>
              <h3 hlmCardTitle>{{ t('iam.role.Permissions') }}</h3>
            </div>
            <div hlmCardContent>
              <au-grid-select-multiple-elements
                [linkedData]="permissionPivot.linked.data()"
                [linkedColumns]="permissionLinkedColumns"
                [candidatesData]="permissionPivot.candidates.data()"
                [candidatesColumns]="permissionCandidatesColumns"
                [linkedIds]="permissionPivot.membership.linkedIds()"
                [loading]="{
                  linked: permissionPivot.linked.loading(),
                  candidates: permissionPivot.candidates.loading(),
                }"
                [dialogTitle]="t('iam.role.Permissions')"
                [linkButtonLabel]="t('Link')"
                [bulkUnlinkButtonLabel]="t('UnlinkSelected')"
                [rowUnlinkActionLabel]="t('Unlink')"
                [searchPlaceholder]="t('SearchPlaceholder')"
                [emptyLinkedMessage]="t('NoLinkedItems')"
                [emptyCandidatesMessage]="t('NoCandidates')"
                (linkedStateChange)="permissionPivot.setLinkedState($event)"
                (candidatesStateChange)="permissionPivot.setCandidatesState($event)"
                (linkRequested)="permissionPivot.link($event)"
                (unlinkRequested)="permissionPivot.unlink($event)"
              />
            </div>
          </section>
        }
      </div>
    </ng-container>
  `,
})
export default class RoleDetailComponent {
  // injections
  private readonly route = inject(ActivatedRoute);
  private readonly router = inject(Router);
  private readonly transloco = inject(TranslocoService);

  // signals
  readonly mode = signal<'new' | 'edit'>(this.route.snapshot.data['mode']);
  readonly parentId = signal<string | null>(this.route.snapshot.paramMap.get('id'));

  readonly shell = useAggregateShell<IamRole>({
    listConfig: ROLE_LIST_CONFIG,
    detailConfig: ROLE_DETAIL_CONFIG,
  });

  // Permission manager (decomposed atoms — see template comment):
  //   - permissionMembership tracks linkedIds + provides link/unlink to the pivot table.
  //   - permissionLinkedTable shows target rows filtered by linkedIds.
  //   - permissionCandidatesTable shows all target rows (the manager
  //     widget disables already-linked entries via [linkedIds]).
  //   - permissionPivot bundles the three atoms into the contract the
  //     manager widget consumes (linked / candidates / membership / link / unlink).
  protected readonly permissionLinkedColumns: DataTableColumnDef<IamPermission>[] =
    getPermissionStandaloneColumns({ translator: this.transloco });
  protected readonly permissionCandidatesColumns: DataTableColumnDef<IamPermission>[] =
    getPermissionStandaloneColumns({ translator: this.transloco });
  // Linked + candidates share the same target columns factory, so the FK
  // include tree and searchable column ids are identical for both tables.
  // Computed once and reused.
  private readonly permissionRelationIncludes = getRelationIncludes(this.permissionLinkedColumns);
  private readonly permissionSearchColumns = getSearchableColumnIds(this.permissionLinkedColumns);
  private readonly permissionList = useGraphqlList<IamPermission>(PERMISSION_LIST_CONFIG);
  private readonly permissionMembership = usePivotMembership<IamPermissionRole>({
    pivotConfig: {
      getQuery: permissionRoleGetQuery,
      getKey: 'objects',
      insertMutation: permissionRoleInsertMutation,
      deleteMutation: permissionRoleDeleteMutation,
      fields: permissionRoleFields,
    },
    parentField: 'roleId',
    childField: 'permissionId',
    parentId: () => this.parentId(),
  });
  private readonly permissionLinkedTable = usePaginatedDataTable<IamPermission>({
    paginate: (input) =>
      // Short-circuit when membership is empty: no point hitting the backend
      // with `id IN (...)` over an empty set. Sequelize/Postgres reject empty
      // arrays in `IN` (or any sentinel value cast against the UUID column),
      // so we just return an empty page locally. Caller paths covered:
      // `init()` for roles with no linked rows, `unlink()` that removes the
      // last row, and any user-driven refetch (search/sort/paginate) on a
      // freshly-emptied table.
      this.permissionMembership.linkedIds().size === 0
        ? of({ count: 0, total: 0, rows: [] })
        : this.permissionList.paginate(input),
    columns: this.permissionLinkedColumns,
    searchableColumnIds: this.permissionSearchColumns,
    constraint: () => ({
      id: { [Operator.in]: Array.from(this.permissionMembership.linkedIds()) },
    }),
    relationIncludes: () =>
      this.permissionRelationIncludes.length > 0 ? this.permissionRelationIncludes : [],
  });
  private readonly permissionCandidatesTable = usePaginatedDataTable<IamPermission>({
    paginate: (input) => this.permissionList.paginate(input),
    columns: this.permissionCandidatesColumns,
    searchableColumnIds: this.permissionSearchColumns,
    relationIncludes: () =>
      this.permissionRelationIncludes.length > 0 ? this.permissionRelationIncludes : [],
  });
  protected readonly permissionPivot = {
    linked: this.permissionLinkedTable,
    candidates: this.permissionCandidatesTable,
    membership: this.permissionMembership,
    link: async (childIds: string[]): Promise<void> => {
      await this.permissionMembership.link(childIds);
      await this.permissionLinkedTable.refetch();
    },
    unlink: async (childIds: string[]): Promise<void> => {
      await this.permissionMembership.unlink(childIds);
      await this.permissionLinkedTable.refetch();
    },
    // Widget emits a single ServerTableState aggregating sort + pagination
    // + filters + search. We set the underlying atoms DIRECTLY (bypassing
    // setSearch / setFilters wrappers, which reset pagination as a UX
    // convenience for *user-initiated* search/filter — that convenience is
    // wrong here because the widget already provides the desired pagination
    // in `state.pagination`). Then refetch ONCE with the final state.
    // Note: the atom `set()` mutators do NOT trigger the table's
    // onSortingChange / onPaginationChange callbacks (those fire only on
    // TanStack-driven mutations), so manual refetch is required.
    setLinkedState: (state: ServerTableState): void => {
      this.permissionLinkedTable.sort.set(state.sorting);
      this.permissionLinkedTable.pagination.set(state.pagination);
      this.permissionLinkedTable.filters.set(state.columnFilters);
      this.permissionLinkedTable.search.set(state.search);
      void this.permissionLinkedTable.refetch();
    },
    setCandidatesState: (state: ServerTableState): void => {
      this.permissionCandidatesTable.sort.set(state.sorting);
      this.permissionCandidatesTable.pagination.set(state.pagination);
      this.permissionCandidatesTable.filters.set(state.columnFilters);
      this.permissionCandidatesTable.search.set(state.search);
      void this.permissionCandidatesTable.refetch();
    },
    init: async (): Promise<void> => {
      // SEQUENTIAL on purpose: linkedTable's paginate / constraint read
      // membership.linkedIds() — refetching linked BEFORE membership has
      // populated linkedIds short-circuits to an empty page (per the
      // paginate guard above) and the user sees no rows even though
      // permissions are assigned. Refresh the membership Set first, then
      // refetch linked with the populated set. Differs from the runtime
      // useRelationshipPivot.init() (which assumes linked.paginate hits
      // the pivot directly with a parentField filter — independent of
      // membership state).
      // Candidates table is independent (no membership constraint) so it
      // can refetch in parallel with the membership refresh.
      const candidatesPromise = this.permissionCandidatesTable.refetch();
      await this.permissionMembership.refresh();
      await this.permissionLinkedTable.refetch();
      await candidatesPromise;
    },
  };

  // breadcrumb
  private readonly breadcrumb = useBreadcrumbs([
    { translation: 'navigation.Roles', link: '/iam/role' },
    { translation: this.mode() === 'new' ? 'Create' : 'Edit' },
  ]);

  constructor() {
    const data = this.route.snapshot.data['item'] as
      | {
          item?: IamRole | null;
        }
      | null
      | undefined;
    if (data?.item) {
      this.shell.setInitial(data.item);
    }
    void this.permissionPivot.init();
  }

  async onSave(value: IamRole): Promise<void> {
    try {
      await this.shell.save(value, this.mode());
      const verb = this.mode() === 'new' ? 'Created.M' : 'Saved.M';
      toast.success(
        `${this.transloco.translate(verb)} ${this.transloco.translate('Successfully')}`,
      );
      this.router.navigate(['/iam/role']);
    } catch {
      // error displayed by GRAPHQL_ERROR_HANDLER
    }
  }

  onCancel(): void {
    this.router.navigate(['/iam/role']);
  }
}
