import {
  ChangeDetectionStrategy,
  Component,
  computed,
  effect,
  input,
  output,
  signal,
} from '@angular/core';
import { NgIcon, provideIcons } from '@ng-icons/core';
import { lucideLink2Off, lucidePlus } from '@ng-icons/lucide';
import { HlmButtonImports } from '@spartan-ng/helm/button';
import { HlmCheckboxImports } from '@spartan-ng/helm/checkbox';
import { HlmIconImports } from '@spartan-ng/helm/icon';
import {
  type CellContext,
  flexRenderComponent,
  type HeaderContext,
  injectFlexRenderContext,
} from '@tanstack/angular-table';
import { useTableSearch } from '../../composables/atoms/data-table/use-table-search';
import { useDataTable } from '../../composables/presets/use-data-table';
import { DataTable } from '../data-table/components/data-table.component';
import { TablePagination } from '../data-table/components/table-pagination.component';
import { TableSearch } from '../data-table/components/table-search.component';
import {
  type DataTableColumnDef,
  type DataTableData,
  DEFAULT_PAGE_SIZE,
  type ServerTableState,
} from '../data-table/types';
import { GridPickDialog } from '../grid-pick-dialog/grid-pick-dialog.component';

export interface ManagerLoadingState {
  readonly linked?: boolean;
  readonly candidates?: boolean;
}

/**
 * Many-to-many relationship manager. Renders a persistent server-paginated
 * outer table of LINKED items + an internal `<au-grid-pick-dialog>` for
 * adding CANDIDATE items. Every interaction (link, unlink, paginate, sort,
 * search) emits an event — the host owns the data and mutations.
 *
 * The outer-table selection persists by id across paginate/sort/search,
 * EVEN if previously-selected rows are no longer in the current
 * `linkedData.rows`. `unlinkRequested(ids[])` may emit ids the user is not
 * currently seeing — the consumer SHOULD confirm with a count before
 * mutating (e.g. via `hlm-alert-dialog`).
 *
 * Discards the previous Apply/Cancel picker contract entirely.
 */
@Component({
  selector: 'au-grid-select-multiple-elements',
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [
    NgIcon,
    HlmButtonImports,
    HlmCheckboxImports,
    HlmIconImports,
    DataTable,
    TablePagination,
    TableSearch,
    GridPickDialog,
  ],
  providers: [provideIcons({ lucideLink2Off, lucidePlus })],
  host: { class: 'block' },
  template: `
    <section class="space-y-3">
      @if (title()) {
        <h2 class="text-base font-semibold tracking-tight">{{ title() }}</h2>
      }

      <!-- Toolbar -->
      <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
        <au-table-search
          [placeholder]="searchPlaceholder()"
          [value]="search.value()"
          (search)="onLinkedSearch($event)"
        />
        <div class="flex items-center gap-2">
          <button
            type="button"
            [disabled]="disabled()"
            hlmBtn
            size="sm"
            (click)="onOpenDialog()"
          >
            <ng-icon
              name="lucidePlus"
              class="sm:mr-1"
              hlm
              size="sm"
            />
            <span class="hidden sm:inline">{{ linkButtonLabel() }}</span>
          </button>
          <button
            type="button"
            [disabled]="disabled() || selectedCount() === 0"
            hlmBtn
            size="sm"
            variant="outline"
            (click)="onBulkUnlink()"
          >
            <ng-icon
              name="lucideLink2Off"
              class="sm:mr-1"
              hlm
              size="sm"
            />
            <span class="hidden sm:inline">
              {{ bulkUnlinkButtonLabel() }}
              @if (selectedCount() > 0) {
                ({{ selectedCount() }})
              }
            </span>
          </button>
        </div>
      </div>

      <!-- Linked table -->
      <au-data-table
        [table]="binding.table"
        [loading]="!!loading().linked"
        [emptyMessage]="emptyLinkedMessage()"
        surface="card"
      />

      <au-table-pagination [table]="binding.table" />

      <!-- Candidates dialog -->
      <au-grid-pick-dialog
        [open]="dialogOpen()"
        [data]="candidatesData()"
        [columns]="candidatesColumns()"
        [disabledIds]="linkedIds()"
        [loading]="!!loading().candidates"
        [multi]="true"
        [title]="dialogTitle() || linkButtonLabel()"
        [searchPlaceholder]="searchPlaceholder()"
        [emptyMessage]="emptyCandidatesMessage()"
        (stateChange)="candidatesStateChange.emit($event)"
        (linkRequested)="onDialogLinkRequested($event)"
        (closed)="onDialogClosed()"
      />
    </section>
  `,
})
export class GridSelectMultipleElements<
  TLinked extends { id: string },
  TCandidate extends { id: string } = TLinked,
> {
  readonly linkedData = input<DataTableData<TLinked>>({ count: 0, total: 0, rows: [] });
  readonly linkedColumns = input.required<DataTableColumnDef<TLinked>[]>();
  readonly candidatesData = input<DataTableData<TCandidate>>({
    count: 0,
    total: 0,
    rows: [],
  });
  readonly candidatesColumns = input.required<DataTableColumnDef<TCandidate>[]>();
  readonly linkedIds = input<Set<string>>(new Set<string>());
  readonly loading = input<ManagerLoadingState>({});
  readonly disabled = input<boolean>(false);
  readonly defaultPageSize = input<number>(DEFAULT_PAGE_SIZE);

  readonly title = input<string>('');
  readonly dialogTitle = input<string>('');
  readonly linkButtonLabel = input<string>('Link');
  readonly bulkUnlinkButtonLabel = input<string>('Unlink selected');
  readonly rowUnlinkActionLabel = input<string>('Unlink');
  readonly searchPlaceholder = input<string>('Filter...');
  readonly emptyLinkedMessage = input<string>('No linked items');
  readonly emptyCandidatesMessage = input<string>('No candidates');

  readonly linkedStateChange = output<ServerTableState>();
  readonly candidatesStateChange = output<ServerTableState>();
  readonly linkRequested = output<string[]>();
  readonly unlinkRequested = output<string[]>();

  protected readonly dialogOpen = signal(false);
  protected readonly search = useTableSearch();

  protected readonly effectiveLinkedColumns = computed<DataTableColumnDef<TLinked>[]>(() => {
    const userColumns = this.linkedColumns();
    const onUnlinkRow = (row: unknown) => this.unlinkRequested.emit([(row as { id: string }).id]);
    const unlinkLabel = this.rowUnlinkActionLabel();
    return [
      {
        id: 'manager-actions',
        size: 50,
        sticky: 'left',
        enableHiding: false,
        enableSorting: false,
        header: '',
        cell: () =>
          flexRenderComponent(GridManagerRowActionsCell, {
            inputs: {
              onRequestUnlink: onUnlinkRow,
              unlinkLabel,
            },
          }),
      },
      {
        id: 'manager-selection',
        size: 50,
        sticky: 'left',
        enableHiding: false,
        enableSorting: false,
        header: () => flexRenderComponent(GridManagerHeadCell),
        cell: () => flexRenderComponent(GridManagerRowCell),
      },
      ...userColumns,
    ];
  });

  protected readonly binding = useDataTable<TLinked>({
    data: () => this.linkedData().rows,
    totalRows: () => this.linkedData().total,
    columns: this.effectiveLinkedColumns,
    pageSize: this.defaultPageSize(),
    manualSorting: true,
    manualPagination: true,
    getRowId: (row) => row.id,
    onSortingChange: () => this.emitLinkedStateChange(),
    onPaginationChange: () => this.emitLinkedStateChange(),
  });

  protected readonly selectedCount = computed(() => this.binding.selection.ids().size);

  constructor() {
    effect(() => {
      this.search.debouncedValue();
      this.emitLinkedStateChange();
    });
  }

  protected onLinkedSearch(value: string): void {
    this.search.set(value);
    // Reset pagination to page 0 — without this, a search after navigating to
    // page N keeps pageIndex=N and the host queries offset=N*pageSize against
    // a result set that no longer has that many rows, returning 0 rows. Same
    // UX as `usePaginatedDataTable.setSearch`, which also calls resetPageIndex.
    const current = this.binding.pagination.state();
    this.binding.pagination.set({ pageIndex: 0, pageSize: current.pageSize });
  }

  protected onOpenDialog(): void {
    if (this.disabled()) return;
    this.dialogOpen.set(true);
  }

  protected onDialogClosed(): void {
    this.dialogOpen.set(false);
  }

  protected onDialogLinkRequested(ids: string[]): void {
    if (ids.length === 0) {
      this.dialogOpen.set(false);
      return;
    }
    this.linkRequested.emit(ids);
    this.dialogOpen.set(false);
  }

  protected onBulkUnlink(): void {
    const ids = Array.from(this.binding.selection.ids());
    if (ids.length === 0) return;
    this.unlinkRequested.emit(ids);
    this.binding.selection.clear();
  }

  private emitLinkedStateChange(): void {
    this.linkedStateChange.emit({
      sorting: this.binding.sort.state(),
      pagination: this.binding.pagination.state(),
      columnFilters: [],
      search: this.search.debouncedValue(),
    });
  }
}

@Component({
  selector: 'au-grid-manager-row-actions-cell',
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [NgIcon, HlmButtonImports, HlmIconImports],
  providers: [provideIcons({ lucideLink2Off })],
  host: { class: 'flex' },
  template: `
    <button
      type="button"
      class="h-6 w-6 p-0.5"
      [attr.aria-label]="unlinkLabel()"
      hlmBtn
      variant="ghost"
      (click)="onClick()"
    >
      <ng-icon
        name="lucideLink2Off"
        hlm
        size="sm"
      />
    </button>
  `,
})
export class GridManagerRowActionsCell {
  protected readonly context = injectFlexRenderContext<CellContext<unknown, unknown>>();
  readonly onRequestUnlink = input.required<(row: unknown) => void>();
  readonly unlinkLabel = input<string>('Unlink');

  protected onClick(): void {
    this.onRequestUnlink()(this.context.row.original);
  }
}

@Component({
  selector: 'au-grid-manager-row-cell',
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [HlmCheckboxImports],
  host: { class: 'flex' },
  template: `
    <hlm-checkbox
      [checked]="context.row.getIsSelected()"
      (checkedChange)="context.row.getToggleSelectedHandler()($event)"
    />
  `,
})
export class GridManagerRowCell {
  protected readonly context = injectFlexRenderContext<CellContext<unknown, unknown>>();
}

@Component({
  selector: 'au-grid-manager-head-cell',
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [HlmCheckboxImports],
  host: { class: 'flex' },
  template: `
    <hlm-checkbox
      [checked]="context.table.getIsAllRowsSelected()"
      [indeterminate]="context.table.getIsSomeRowsSelected()"
      (checkedChange)="context.table.toggleAllRowsSelected()"
    />
  `,
})
export class GridManagerHeadCell {
  protected readonly context = injectFlexRenderContext<HeaderContext<unknown, unknown>>();
}
