import { ChangeDetectionStrategy, Component, computed, effect, input, output } from '@angular/core';
import { NgIcon, provideIcons } from '@ng-icons/core';
import { lucideCheck, lucideLink2 } from '@ng-icons/lucide';
import { HlmButtonImports } from '@spartan-ng/helm/button';
import { HlmCheckboxImports } from '@spartan-ng/helm/checkbox';
import { HlmDialogImports } from '@spartan-ng/helm/dialog';
import { HlmIconImports } from '@spartan-ng/helm/icon';
import {
  type CellContext,
  flexRenderComponent,
  type HeaderContext,
  injectFlexRenderContext,
  type Row,
} 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';

/**
 * Reusable server-paginated picker dialog. The host owns `data` (server-
 * paginated) and reacts to `stateChange` (sort/page/search) to fetch new
 * pages. The dialog is purely a presentation layer — no fetcher, no Apollo.
 *
 * Cross-page persistent selection by id (via `getRowId: r => r.id` and
 * `useTableSelection` atom inside `useDataTable`). `disabledIds` rows
 * render a done-icon and are non-selectable.
 *
 * Two modes via `multi`:
 *   - `multi=true` (default) — multi-row selection with Apply/Cancel footer.
 *   - `multi=false` — single-pick. Clicking a non-disabled row immediately
 *     emits `linkRequested([id])` and `closed`. The selection column is hidden.
 */
@Component({
  selector: 'au-grid-pick-dialog',
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [
    HlmButtonImports,
    HlmDialogImports,
    DataTable,
    TablePagination,
    TableSearch,
  ],
  providers: [provideIcons({ lucideCheck })],
  host: { class: 'block' },
  template: `
    <hlm-dialog
      [state]="open() ? 'open' : 'closed'"
      (closed)="onDialogClosed()"
    >
      <hlm-dialog-content
        *hlmDialogPortal="let ctx"
        class="sm:max-w-3xl"
      >
        <hlm-dialog-header>
          <h2 hlmDialogTitle>{{ title() }}</h2>
        </hlm-dialog-header>

        <!-- Toolbar: search + actions in a single row, matching the
             linked-table widget header (filter + Link/Unlink buttons). -->
        <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
          <au-table-search
            class="flex-1"
            [placeholder]="searchPlaceholder()"
            [value]="search.value()"
            (search)="onSearchEmit($event)"
          />
          <div class="flex items-center gap-2">
            <button
              type="button"
              hlmBtn
              size="sm"
              variant="outline"
              (click)="onCancel(ctx)"
            >
              {{ cancelLabel() }}
            </button>
            @if (multi()) {
              <button
                type="button"
                [disabled]="selectedCount() === 0"
                hlmBtn
                size="sm"
                (click)="onApply(ctx)"
              >
                {{ applyLabel() }}
                @if (selectedCount() > 0) {
                  ({{ selectedCount() }})
                }
              </button>
            }
          </div>
        </div>

        <au-data-table
          [table]="binding.table"
          [loading]="loading()"
          [emptyMessage]="emptyMessage()"
          [rowClickable]="canPickRow"
          [maxHeight]="'480px'"
          (rowClick)="onRowClick($event)"
        />

        <au-table-pagination [table]="binding.table" />
      </hlm-dialog-content>
    </hlm-dialog>
  `,
})
export class GridPickDialog<T extends { id: string }> {
  readonly data = input<DataTableData<T>>({ count: 0, total: 0, rows: [] });
  readonly columns = input.required<DataTableColumnDef<T>[]>();
  readonly disabledIds = input<Set<string>>(new Set<string>());
  readonly loading = input<boolean>(false);
  readonly multi = input<boolean>(true);
  readonly open = input<boolean>(false);
  readonly title = input<string>('');
  readonly applyLabel = input<string>('Add selected');
  readonly cancelLabel = input<string>('Cancel');
  readonly rowLinkActionLabel = input<string>('Link');
  readonly searchPlaceholder = input<string>('Filter...');
  readonly emptyMessage = input<string>('No results');
  readonly disabledRowTooltip = input<string>('Already linked');
  readonly defaultPageSize = input<number>(DEFAULT_PAGE_SIZE);

  readonly stateChange = output<ServerTableState>();
  readonly linkRequested = output<string[]>();
  readonly closed = output<void>();

  protected readonly search = useTableSearch();

  protected readonly effectiveColumns = computed<DataTableColumnDef<T>[]>(() => {
    if (!this.multi()) return this.columns();
    // Multi-mode prepends two action columns:
    //   - 'pick-link' : a per-row Link button for one-click linking
    //     (alternative to checkbox + "Apply").
    //   - 'pick-selection' : the multi-select checkbox.
    // In single-mode neither is needed (row-click picks immediately).
    const onLinkRow = (row: unknown) => this.linkRequested.emit([(row as { id: string }).id]);
    const linkLabel = this.rowLinkActionLabel();
    return [
      {
        id: 'pick-link',
        size: 50,
        sticky: 'left',
        enableHiding: false,
        enableSorting: false,
        header: '',
        cell: () =>
          flexRenderComponent(GridPickDialogRowLinkCell, {
            inputs: { onRequestLink: onLinkRow, linkLabel },
          }),
      },
      {
        id: 'pick-selection',
        size: 50,
        sticky: 'left',
        enableHiding: false,
        enableSorting: false,
        header: () => flexRenderComponent(GridPickDialogHeadCell),
        cell: () => flexRenderComponent(GridPickDialogRowCell),
      },
      ...this.columns(),
    ];
  });

  protected readonly binding = useDataTable<T>({
    data: () => this.data().rows,
    totalRows: () => this.data().total,
    columns: this.effectiveColumns,
    pageSize: this.defaultPageSize(),
    manualSorting: true,
    manualPagination: true,
    getRowId: (row) => row.id,
    enableRowSelection: (row) => !this.disabledIds().has(row.original.id),
    onSortingChange: () => this.emitStateChange(),
    onPaginationChange: () => this.emitStateChange(),
  });

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

  /** Predicate consumed by `<au-data-table [rowClickable]>`. Single-pick mode
   * lights up rows that are still selectable (i.e. NOT in `disabledIds`). In
   * multi-pick mode we never want row-click — selection happens via the
   * checkbox column, so the predicate returns false. */
  protected readonly canPickRow = (row: Row<T>): boolean => !this.multi() && row.getCanSelect();

  constructor() {
    // Emit stateChange when debounced search settles.
    effect(() => {
      this.search.debouncedValue();
      this.emitStateChange();
    });

    // Reset selection and search when the dialog closes.
    effect(() => {
      if (!this.open()) {
        this.binding.selection.clear();
        this.search.set('');
      }
    });
  }

  protected onSearchEmit(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.
    const current = this.binding.pagination.state();
    this.binding.pagination.set({ pageIndex: 0, pageSize: current.pageSize });
  }

  protected onRowClick(row: T): void {
    if (this.multi()) return;
    if (this.disabledIds().has(row.id)) return;
    this.linkRequested.emit([row.id]);
    this.closed.emit();
  }

  protected onApply(ctx: { close: () => void }): void {
    const ids = Array.from(this.binding.selection.ids());
    this.linkRequested.emit(ids);
    ctx.close();
  }

  protected onCancel(ctx: { close: () => void }): void {
    ctx.close();
  }

  protected onDialogClosed(): void {
    this.closed.emit();
  }

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

@Component({
  selector: 'au-grid-pick-dialog-row-cell',
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [NgIcon, HlmCheckboxImports, HlmIconImports],
  providers: [provideIcons({ lucideCheck })],
  host: { class: 'flex' },
  template: `
    @if (context.row.getCanSelect()) {
      <hlm-checkbox
        [checked]="context.row.getIsSelected()"
        (checkedChange)="context.row.getToggleSelectedHandler()($event)"
      />
    } @else {
      <ng-icon
        name="lucideCheck"
        aria-label="Already linked"
        class="text-muted-foreground"
        hlm
        size="sm"
      />
    }
  `,
})
export class GridPickDialogRowCell {
  protected readonly context = injectFlexRenderContext<CellContext<unknown, unknown>>();
}

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

@Component({
  selector: 'au-grid-pick-dialog-row-link-cell',
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [NgIcon, HlmButtonImports, HlmIconImports],
  providers: [provideIcons({ lucideLink2 })],
  host: { class: 'flex h-6 items-center' },
  template: `
    @if (context.row.getCanSelect()) {
      <button
        type="button"
        class="h-6 w-6 p-0.5"
        [attr.aria-label]="linkLabel()"
        hlmBtn
        variant="ghost"
        (click)="onClick()"
      >
        <ng-icon
          name="lucideLink2"
          hlm
          size="sm"
        />
      </button>
    }
  `,
})
export class GridPickDialogRowLinkCell {
  protected readonly context = injectFlexRenderContext<CellContext<unknown, unknown>>();
  readonly onRequestLink = input.required<(row: unknown) => void>();
  readonly linkLabel = input<string>('Link');

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