import { AfterContentInit, ChangeDetectionStrategy, Component, computed, ContentChild, ContentChildren, inject, input, output, QueryList, ViewChild } from '@angular/core';
import { FAST_KENDO_COMMON } from '../../app.config';
import { BrowserSupportService, CellClickEvent, CellCloseEvent, CellSelectionAggregateService, CellSelectionService, ChangeNotificationService, ColumnBase, ColumnComponent, ColumnInfoService, ColumnResizingService, ContextService, DataStateChangeEvent, DetailsService, DomEventsService, EditServiceClass, FilterableSettings, FilterService, FocusRoot, GridComponent, GridDataResult, GridNavigableSettings, GroupsService, IdService, LocalDataChangesService, NavigationRow, NavigationService, PageChangeEvent, PagerSettings, PDFService, ResizeService, RowArgs, RowClassFn, ScrollMode, ScrollRequestService, ScrollSyncService, SelectableSettings, SelectionEvent, SelectionService, SortSettings, SuspendService, ToolbarTemplateDirective } from '@progress/kendo-angular-grid';
import { CompositeFilterDescriptor, SortDescriptor } from '@progress/kendo-data-query';
import { FastTipDirective } from '../elements/fast-tip.directive';
import { FastGridColComponent } from './fast-grid-col.component';
import { MessageService } from '../services/message.service';
import { FastGridToolbar } from './fast-grid-toolbar.component';
import { getTextElemFromTableCell, handleError, isClipped } from '../utils/util';
import { L10N_PREFIX, LocalizationService } from '@progress/kendo-angular-l10n';
import { PagerContextService } from '@progress/kendo-angular-pager';

@Component({
  selector: 'fast-grid',
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [FAST_KENDO_COMMON, FastTipDirective],
  providers: [IdService, FilterService, SelectionService, DomEventsService, CellSelectionAggregateService, ContextService, LocalizationService, ColumnInfoService,
    LocalDataChangesService, NavigationService, PagerContextService, ScrollRequestService, GroupsService, DetailsService, FocusRoot, EditServiceClass, ColumnResizingService, CellSelectionService, ScrollSyncService, ChangeNotificationService, BrowserSupportService, SuspendService, ResizeService, PDFService, { provide: L10N_PREFIX, useValue: '' }],
  styles: `:host { @apply overflow-hidden }`,
  template: `
  <ng-template #tooltipGridTemplate>
    @for(text of tooltipItem.split(' • '); track $index) {
      <div><span>{{ text }}</span></div>
    }
  </ng-template>

  @if (gridBinding() !== undefined) {
    <kendo-grid fast-tip #testTooltip="fast-tip" tipEvent="none" [tipContent]="tooltipGridTemplate"
      [kendoGridBinding]="gridBinding()"
      #gridDirective="kendoGrid"
      [data]="data()"
      [filterable]="filterable()"
      [selectable]="selectable()"
      [sortable]="sortable()"
      [selectedKeys]="selectedKeys()"
      [kendoGridSelectBy]="selectBy()"
      [loading]="loading()"
      [class]="conditionalClasses()"
      [pageable]="pageable()"
      [pageSize]="pageSize()"
      [skip]="skip()"
      [sort]="sort()"
      [filter]="filter()"
      [resizable]="resizable()"
      [scrollable]="scrollable()"
      [navigable]="navigable()"
      [rowHeight]="rowHeight()"
      [rowClass]="rowClass()"
      (cellClick)="cellClick.emit($event)"
      (cellClose)="cellClose.emit($event)"
      (keydown)="keyDown.emit($event)"
      (dataStateChange)="dataStateChange.emit($event)"
      (mouseover)="showGridTooltip($event, testTooltip)"
      >
      @if (fastGridToolbar) {
        <ng-template kendoGridToolbarTemplate class='p0'></ng-template>
      }
    </kendo-grid>
  } @else {
    <kendo-grid fast-tip #testTooltip="fast-tip" tipEvent="none" [tipContent]="tooltipGridTemplate"
      #gridDirective="kendoGrid"
      [data]="data()"
      [filterable]="filterable()"
      [selectable]="selectable()"
      [sortable]="sortable()"
      [selectedKeys]="selectedKeys()"
      [kendoGridSelectBy]="selectBy()"
      [loading]="loading()"
      [class]="conditionalClasses()"
      [pageable]="pageable()"
      [pageSize]="pageSize()"
      [skip]="skip()"
      [sort]="sort()"
      [filter]="filter()"
      [resizable]="resizable()"
      [scrollable]="scrollable()"
      [navigable]="navigable()"
      [rowHeight]="rowHeight()"
      [rowClass]="rowClass()"
      (cellClick)="cellClick.emit($event)"
      (cellClose)="cellClose.emit($event)"
      (keydown)="keyDown.emit($event)"
      (dataStateChange)="dataStateChange.emit($event)"
      (mouseover)="showGridTooltip($event, testTooltip)">
      @if (fastGridToolbar) {
        <ng-template kendoGridToolbarTemplate class='p0'></ng-template>
      }
    </kendo-grid>
  }
  `
})
export class FastGridComponent implements AfterContentInit {
  @ContentChildren(FastGridColComponent) columns: QueryList<FastGridColComponent>;
  @ContentChildren(ColumnComponent) kendoColumns: QueryList<ColumnBase>;
  @ContentChild(FastGridToolbar) fastGridToolbar: FastGridToolbar;

  @ViewChild(GridComponent) grid: GridComponent;
  @ViewChild(FilterService) filterService: FilterService;
  messageService = inject(MessageService);

  gridBinding = input<unknown[]>();
  data = input<Array<unknown> | GridDataResult | null>();

  selectable = input<SelectableSettings>(null);
  sortable = input<SortSettings>(null);
  loading = input<boolean>(false);
  selectedKeys = input<unknown[]>(null);
  selectBy = input<string | ((context: RowArgs) => unknown)>();
  filterable = input<FilterableSettings>();
  pageable = input<boolean | PagerSettings>(false);
  pageSize = input<number>();
  skip = input<number>();
  sort = input<SortDescriptor[]>();
  filter = input<CompositeFilterDescriptor>();
  resizable = input<boolean>(false);
  scrollable = input<ScrollMode>('none');
  navigable = input<GridNavigableSettings>(false);
  rowHeight = input<number>();
  rowClass = input<RowClassFn>(() => ({}));

  tooltipItem: string;
  activeRow: NavigationRow

  cellClick = output<CellClickEvent>();
  cellClose = output<CellCloseEvent>();
  keyDown = output<KeyboardEvent>();
  dataStateChange = output<DataStateChangeEvent>();
  selectionChange = output<SelectionEvent>();
  pageChange = output<PageChangeEvent>();

  ngAfterContentInit(): void {
    setTimeout(() => {
      if (this.kendoColumns?.length >= 1)
        handleError("Kendo grid columns are not allowed in a fast-grid. Use fast-grid-col instead.", this.messageService);

      const cols: (ColumnBase[]) = this.columns.map((col) => col.getKendoCol());
      this.grid.columns.reset(cols);
      if (this.fastGridToolbar)
        this.grid.toolbarTemplate = new ToolbarTemplateDirective(this.fastGridToolbar.templateRefForGrid);
    });

    setTimeout(() => {
      this.columns.forEach(col => {
        const hasFilterRow = this.filterable() === 'row' || this.filterable() === 'menu, row';
        if (!hasFilterRow)
          return;

        const isDateCol = col.type().includes('date-');
        const isNumCol = col.type().includes('num-');

        if (isDateCol)
          col.setDateFilterService(this.filterService);
        else if (isNumCol)
          col.setNumFilterService(this.filterService);
      });
    }, 1000);
  }

  conditionalClasses = computed(() => {
    const classes = [] as string[];

    classes.push(...this.getCommonClasses());
    classes.push(...this.getLightClasses());
    classes.push(...this.getDarkClasses());
    classes.push(...this.getLightHoverClasses());
    classes.push(...this.getDarkHoverClasses());

    const conditionalClasses = this.getConditionalClassesFromArrays(classes);
    return conditionalClasses;
  });


  getConditionalClassesFromArrays(classArray: string[]): { [key: string]: boolean } {
    const classes: { [key: string]: boolean } = {};
    classArray.forEach(className => {
      classes[className] = true;
    });
    return classes;
  }

  // make sure to put classes totally separately, otherwise it will not work (i.e. "flex flex-col h-full" will not work, but "flex" "flex-col" "h-full" will work)
  getCommonClasses() {
    return [
      "flex",
      "flex-col",
      "h-full",
      "[&_.k-grid-header_.k-header]:text-md",
      "[&_.k-grid-header_.k-header]:h-3",
      "[&_.k-grid-header_.k-header]:pb-1",
      "[&_.k-filter-row]:h-3",
      "[&_.k-grid-header_.k-grid-header-wrap_.k-filter-row_td]:p-1",
      "[&_.k-input-inner]:h-6.5",
      "[&_tbody_td]:p-1.5",
      "[&_.k-grid-content]:whitespace-nowrap",
      "[&_.k-grid-content-locked]:whitespace-nowrap",
      "[&_.k-input-inner]:font-bold",
      "[&_.k-grid-header_.k-filtercell_.k-filtercell-wrapper_.k-filtercell-operator]:hidden",
      "[&_.k-grid-header_.k-header>.k-cell-inner>.k-link]:pl-1",
      "[&_.k-grid-header_.k-header>.k-link]:text-clip",
      "[&_.k-grid-header_.k-header.k-filterable>.k-cell-inner>.k-link_.k-sort-icon]:m-0",
      "[&_.k-grid-header_.k-header.k-filterable>.k-cell-inner>.k-link_.k-sort-icon_.k-icon]:ml-0",
      "[&_.k-grid-header_.k-header.k-filterable>.k-cell-inner>.k-link_.k-sort-icon_.k-icon]:absolute",
      "[&_.k-grid-header_.k-header.k-filterable>.k-cell-inner>.k-link_.k-sort-icon_.k-icon]:top-0",
      "[&_.k-grid-header_.k-header.k-filterable>.k-cell-inner>.k-link_.k-sort-icon_.k-icon]:right-4",
      "[&_.k-grid-header_.k-header.k-filterable>.k-cell-inner>.k-link_.k-sort-icon_.k-icon]:font-bold",
      "[&_.k-grid-header_.k-header.k-filterable>.k-cell-inner>.k-link_.k-sort-icon_.k-icon]:pointer-events-none",
      "[&_.k-grid-header_.k-header.k-filterable>.k-cell-inner>.k-link_.k-sort-order]:m-0",
      "[&_.k-grid-header_.k-header.k-filterable>.k-cell-inner>.k-link_.k-sort-order]:absolute",
      "[&_.k-grid-header_.k-header.k-filterable>.k-cell-inner>.k-link_.k-sort-order]:top-0",
      "[&_.k-grid-header_.k-header.k-filterable>.k-cell-inner>.k-link_.k-sort-order]:right-2.5",
      "[&_.k-grid-header_.k-header.k-filterable>.k-cell-inner>.k-link_.k-sort-order]:font-bold",
      "[&_.k-grid-header_.k-header.k-filterable>.k-cell-inner>.k-link_.k-sort-order]:pointer-events-none",
      "[&_.k-svg-i-filter]:mt-1",
      "[&_.k-svg-i-filter]:-mr-2",
      "[&_.k-grid-filter-menu]:w-6",
      "[&_.k-grid_.k-grid-filter-popup_.k-filter-menu_.k-filter-menu-container_kendo-grid-numeric-filter-menu]:gap-2",
      "[&_.k-grid_.k-grid-filter-popup_.k-filter-menu_.k-filter-menu-container_kendo-grid-numeric-filter-menu]:flex",
      "[&_.k-grid_.k-grid-filter-popup_.k-filter-menu_.k-filter-menu-container_kendo-grid-numeric-filter-menu]:flex-col",
      "[&_.k-grid_.k-table-th.k-filterable_.k-cell-inner_.k-grid-filter-menu]:flex",
      "[&_.k-grid_.k-table-th.k-filterable_.k-cell-inner_.k-grid-filter-menu]:mr-0",
      "[&_.k-grid_.k-filter-row_td]:px-0",
      "[&_.k-grid_.k-filter-row_td]:py-1",
      "[&_kendo-pager-input]:flex",
      "[&_.k-input-button]:border-none",
      "[&_.k-input-button]:w-6",
      "[&_.k-input-button]:bg-transparent",
      "[&_.k-datepicker]:h-7",
      "[&_.k-numerictextbox]:h-7",
      "[&_.k-grid-toolbar]:p-0",
      "[&_.k-grid-toolbar]:flex-nowrap",
      "[&_.k-grid-table]:h-fit", // https://stackoverflow.com/a/73399942
      "[&_.k-master-row]:h-full",
      "[&_.k-grid-header_.k-header.checkbox-header]:p-0",
      "[&_.k-grid-header_.k-header.checkbox-header_.k-cell-inner]:m-0",
      "[&_.k-grid-header_.k-header.checkbox-header_.k-cell-inner]:h-full",
      "[&_.k-grid-header_.k-header.checkbox-header_.k-cell-inner]:justify-center",
      "[&_.k-grid-header_.k-header.checkbox-header>.k-cell-inner_.k-link]:p-0",
      "[&_.k-grid-header_.k-header.checkbox-header_.k-cell-inner_.k-link]:h-full",
    ]
  }

  getLightClasses() {
    return [
      "[&_.k-button-text]:text-base-black-1000",
      "[&_.k-grid-filter-menu.k-grid-header-menu.k-active]:bg-base-gray-500/50",
      "[&_.k-grid-filter-menu.k-grid-header-menu.k-active_.k-svg-i-filter.k-svg-icon.k-icon]:text-base-black-1000",
      "[&_.k-svg-i-calendar]:text-base-black-1000",
      "[&_.k-input-button]:active:bg-base-gray-1000",
      "[&_button]:text-base-black-1000",
      "[&_button]:bg-base-white-250",
      "[&_button]:active:bg-base-white-1000",
      "[&_.k-spinner-increase]:border-base-gray-500",
      "[&_.k-spinner-decrease]:border-base-gray-500",];
  }

  getDarkClasses() {
    return [
      "dark:[&_.k-sort-order]:text-base-yellow-250",
      "dark:[&_.k-svg-i-sort-asc-small]:text-base-yellow-250",
      "dark:[&_.k-svg-i-sort-desc-small]:text-base-yellow-250",
      "dark:[&_.k-selected]:bg-alt-blue-250",
      "dark:[&_.k-button-text]:text-base-white-500",
      "dark:[&_.k-svg-i-calendar]:text-base-white-500",
      "dark:[&_.k-input-button]:active:bg-base-blue-1000",
      "dark:[&_button]:text-base-white-500",
      "dark:[&_button]:bg-alt-gray-1000",
      "dark:[&_button]:active:bg-alt-blue-250",
      "dark:[&_.k-spinner-increase]:border-alt-blue-500",
      "dark:[&_.k-spinner-decrease]:border-alt-blue-500",];
  }

  getLightHoverClasses() {
    return [
      "[&_.k-grid-filter-menu]:hover:bg-base-white-1000",
      "[&_.k-sort-order]:hover:text-alt-blue-1000",
      "[&_.k-svg-i-sort-asc-small]:hover:text-alt-blue-1000",
      "[&_.k-svg-i-sort-desc-small]:hover:text-alt-blue-1000",
      "[&_.k-input-button]:hover:bg-base-white-1000",];
  }

  getDarkHoverClasses() {
    return [
      "dark:[&_.k-grid-filter-menu]:hover:bg-alt-blue-250",
      "dark:[&_.k-sort-order]:hover:text-base-yellow-750",
      "dark:[&_.k-svg-i-sort-asc-small]:hover:text-base-yellow-750",
      "dark:[&_.k-svg-i-sort-desc-small]:hover:text-base-yellow-750",
      "dark:[&_.k-input-button]:hover:bg-alt-blue-750",];
  }

  showGridTooltip(e: MouseEvent, tooltip: FastTipDirective) {
    let previousTableCellId: string = null;
    const element = e.target as HTMLElement;

    const td = element.closest('td');
    const th = element.closest('th');
    const tableCellElem = th ?? td;
    const textElem = tableCellElem ? getTextElemFromTableCell(tableCellElem) : null;
    const isTextClipped = textElem ? isClipped(textElem, 14) : null;

    if (!tableCellElem || !textElem || !isTextClipped) {
      tooltip.hide();
      this.tooltipItem = null;
      return;
    };

    const parentRow = tableCellElem.parentElement as HTMLTableRowElement;
    const currentTableCellId = `cell-${parentRow.rowIndex}-${tableCellElem.cellIndex}`;
    if (previousTableCellId !== currentTableCellId) {
      tooltip.hide();
      this.tooltipItem = null;
      previousTableCellId = currentTableCellId;
    }
    setTimeout(() => {
      if (textElem instanceof HTMLInputElement)
        this.tooltipItem = textElem.value;
      else
        this.tooltipItem = textElem.innerText;
      if (this.tooltipItem != null) {
        tooltip.show(tableCellElem);
      }
    });

  }
}
