import { Component, DestroyRef, ElementRef, computed, inject, signal, viewChild } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import {
  CdkConnectedOverlay,
  CdkOverlayOrigin,
  ConnectedOverlayPositionChange,
  ConnectedPosition
} from '@angular/cdk/overlay';
import {
  FormBuilder,
  ReactiveFormsModule,
  Validators
} from '@angular/forms';
import { ToastrService } from 'ngx-toastr';
import { Subject, catchError, finalize, of, switchMap } from 'rxjs';

import {
  CreateCurrencyRequest,
  CurrencyCountryFlag,
  CurrencyDto,
  CurrencyIso2Value,
  CurrencyModalMode,
  UpdateCurrencyRequest
} from '../../models/currency.model';
import { CurrencyService } from '../../data-access/currency.service';
import { DataTableQueryState } from '../../../../../shared/components/data-table/data-table-query.state';
import {
  DataTableAction,
  DataTableActionEvent,
  DataTableColumn,
  DataTablePageEvent,
  DataTableQuery,
  DataTableRecord,
  DataTableSortEvent
} from '../../../../../shared/components/data-table/data-table.types';
import { DataTable } from '../../../../../shared/components/data-table/data-table';
import { DataTableCellDirective } from '../../../../../shared/directives/data-table-cell.directive';
import { Modal } from '../../../../../shared/components/modal/modal';
import { ConfirmDialog } from '../../../../../shared/components/confirm-dialog/confirm-dialog';
import { FormInput } from '../../../../../shared/components/form/form-input/form-input';

type Iso2TooltipPlacement = 'above' | 'below' | 'left' | 'right';
interface CurrencyTableRow extends DataTableRecord {
  id: string;
  code: string;
  iso2: CurrencyIso2Value;
  name: string;
  symbol: string;
  numericCode: number;
  decimalDigits: number;
  isActive: boolean;
  serialNumber: number;
  createdOn?: string;
  modifiedOn?: string | null;
}

@Component({
  selector: 'currency-list',
  standalone: true,
  imports: [DataTable, DataTableCellDirective, Modal, ReactiveFormsModule, FormInput, ConfirmDialog, CdkOverlayOrigin, CdkConnectedOverlay],
  templateUrl: './currency-list.html',
  styleUrl: './currency-list.scss',
})
export class CurrencyList {
  private readonly destroyRef = inject(DestroyRef);
  private readonly currencyApi = inject(CurrencyService);
  private readonly formBuilder = inject(FormBuilder);
  private readonly elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
  private readonly toastr = inject(ToastrService);
  private readonly currencyQueryRequests$ = new Subject<DataTableQuery>();

  readonly queryState = new DataTableQueryState();

  readonly currencies = signal<CurrencyTableRow[]>([]);
  readonly totalRecords = signal(0);
  readonly filteredRecords = signal(0);
  readonly saving = signal(false);

  readonly showCurrencyModal = signal(false);
  readonly currencyModalMode = signal<CurrencyModalMode>('create');
  readonly selectedCurrencyId = signal<string | null>(null);
  readonly selectedCurrency = signal<CurrencyDto | null>(null);
  readonly currencySubmitAttempted = signal(false);
  readonly pendingDeleteCurrency = signal<CurrencyDto | null>(null);
  readonly deleteConfirmDialog = viewChild(ConfirmDialog);
    readonly openIso2TooltipCurrencyId = signal<string | null>(null);
  readonly iso2TooltipPlacement = signal<Iso2TooltipPlacement>('right');
  readonly iso2TooltipPositions: ConnectedPosition[] = [
    {
    originX: 'end',
    originY: 'center',
    overlayX: 'start',
    overlayY: 'center',
    offsetX: 12
  }
  ];
  private iso2TooltipCloseTimer: ReturnType<typeof setTimeout> | null = null;

  readonly currencyForm = this.formBuilder.nonNullable.group({
    name: [
      '',
      [
        Validators.required,
        Validators.maxLength(100)
      ]
    ],
    code: [
      '',
      [
        Validators.required,
        Validators.minLength(3),
        Validators.maxLength(3),
        Validators.pattern(/^[A-Za-z]{3}$/)
      ]
    ],
    symbol: [
      '',
      [
        Validators.required,
        Validators.maxLength(8)
      ]
    ],
    numericCode: [
      0,
      [
        Validators.required,
        Validators.min(1),
        Validators.max(999)
      ]
    ],
    decimalDigits: [
      2,
      [
        Validators.required,
        Validators.min(0),
        Validators.max(4)
      ]
    ]
  });

  readonly currencyModalTitle = computed(() =>
    this.currencyModalMode() === 'create'
      ? 'Add Currency'
      : 'Edit Currency'
  );

  readonly currencySubmitLabel = computed(() =>
    this.currencyModalMode() === 'create'
      ? 'Save'
      : 'Update'
  );

  readonly currencyLoadingLabel = computed(() =>
    this.currencyModalMode() === 'create'
      ? 'Saving...'
      : 'Updating...'
  );

  readonly currencySubmitAction = computed<'save' | 'update'>(() =>
    this.currencyModalMode() === 'create'
      ? 'save'
      : 'update'
  );

  readonly columns = signal<DataTableColumn<CurrencyTableRow>[]>([
    { key: 'serialNumber', label: 'Sr. No.', header: 'Sr.No.', sortable: false, width: '100px' },
    { key: 'name', label: 'Name', header: 'Name', sortable: true, align: 'left' },
    { key: 'iso2', label: 'Iso2 Code', header: 'Iso2 Code', sortable: true , align: 'left'},
    { key: 'code', label: 'Code', header: 'Code', sortable: true },
    { key: 'symbol', label: 'Symbol', header: 'Symbol', sortable: true },
    { key: 'numericCode', label: 'Numeric Code', header: 'Numeric Code', sortable: true },
    { key: 'decimalDigits', label: 'Decimal Digits', header: 'Decimal Digits', sortable: true },
    {
      key: 'isActive',
      label: 'Status',
      header: 'Status',
      sortable: true,
      badge: true,
      badgeClass: value =>
        value === true
          ? 'badge bg-success/10 text-success'
          : 'badge bg-danger/10 text-danger',
      formatter: value => value ? 'Active' : 'Inactive'
    }
  ]);

  readonly actions = signal<DataTableAction<CurrencyTableRow>[]>([
    {
      type: 'edit',
      label: 'Edit',
      icon: 'ti ti-edit',
      className: 'text-primary'
    },
    {
      type: 'delete',
      label: 'Delete',
      icon: 'ti ti-trash',
      className: 'text-danger',
      visible: row => row.isActive
    },
    {
      type: 'activate',
      label: 'Activate',
      icon: 'ti ti-check',
      className: 'text-success',
      visible: row => !row.isActive
    }
  ]);

  constructor() {
    this.currencyQueryRequests$
      .pipe(
        switchMap(query =>
          this.currencyApi.getCurrencyDataTable(query).pipe(
            catchError(() => {
              this.toastr.error('Unable to load currencies.');
              this.clearCurrencyGrid();
              return of(null);
            })
          )
        ),
        takeUntilDestroyed(this.destroyRef)
      )
      .subscribe(response => {
        if (!response) {
          return;
        }

        const query = this.queryState.getQuery();

        if (response.draw !== query.draw) {
          return;
        }

        const currenciesWithSerialNumbers: CurrencyTableRow[] = response.rows.map((currency, index) => ({
          ...currency,
          serialNumber: (query.page - 1) * query.pageSize + index + 1
        }));

        this.currencies.set(currenciesWithSerialNumbers);
        this.totalRecords.set(response.total);
        this.filteredRecords.set(response.filtered);
      });
  }

  ngOnInit(): void {
    this.loadCurrencies(this.queryState.getQuery());
  }

  loadCurrencies(query: DataTableQuery): void {
    this.currencyQueryRequests$.next(query);
  }

  onSearch(value: string): void {
    const query = this.queryState.setSearch(value.trim());
    this.loadCurrencies(query);
  }

  onPageChange(event: DataTablePageEvent): void {
    const query = this.queryState.setPage(event);
    this.loadCurrencies(query);
  }

  onSortChange(event: DataTableSortEvent): void {
    const query = this.queryState.setSort(event);
    this.loadCurrencies(query);
  }

  onRefresh(): void {
    const currentQuery = this.queryState.getQuery();

    this.loadCurrencies({
      ...currentQuery,
      draw: currentQuery.draw + 1
    });
  }

  onReset(): void {
    const query = this.queryState.reset();
    this.loadCurrencies(query);
  }

  onDeleteConfirmed(): void {
    const currency = this.pendingDeleteCurrency();

    if (!currency) {
      return;
    }

    this.pendingDeleteCurrency.set(null);
    this.deleteCurrency(currency);
  }

  onDeleteCancelled(): void {
    this.pendingDeleteCurrency.set(null);
  }

  onActionClick(event: DataTableActionEvent<CurrencyTableRow>): void {
    const currency = this.toCurrencyDto(event.row);

    switch (event.action.type) {
      case 'view':
        this.viewCurrency(currency);
        break;
      case 'edit':
        this.openEditCurrency(currency);
        break;
      case 'delete':
        this.requestDeleteCurrency(currency);
        break;
      case 'activate':
        this.activateCurrency(currency);
        break;
    }
  }

  onAddCurrency(): void {
    this.currencyModalMode.set('create');
    this.selectedCurrencyId.set(null);
    this.selectedCurrency.set(null);
    this.currencySubmitAttempted.set(false);

    this.currencyForm.reset({
      name: '',
      code: '',
      symbol: '',
      numericCode: 0,
      decimalDigits: 2
    });
    this.resetCurrencyFormState();

    this.showCurrencyModal.set(true);
  }

  closeCurrencyModal(): void {
    if (this.saving()) {
      return;
    }

    this.showCurrencyModal.set(false);
    this.selectedCurrencyId.set(null);
    this.selectedCurrency.set(null);
    this.currencySubmitAttempted.set(false);
  }

  saveCurrency(): void {
    if (this.currencyForm.invalid) {
      this.currencySubmitAttempted.set(true);
      this.currencyForm.markAllAsTouched();
      this.focusFirstInvalidCurrencyControl();
      return;
    }

    if (this.saving()) {
      return;
    }

    this.saving.set(true);

    if (this.currencyModalMode() === 'create') {
      this.currencyApi
        .createCurrency(this.buildCreateCurrencyRequest())
        .pipe(finalize(() => this.saving.set(false)))
        .subscribe({
          next: () => {
            this.toastr.success('Currency saved successfully.');
            this.finishCurrencySave();
          }
        });

      return;
    }

    const currencyId = this.selectedCurrencyId();

    if (!currencyId) {
      this.saving.set(false);
      return;
    }

    this.currencyApi
      .updateCurrency(
        currencyId,
        this.buildUpdateCurrencyRequest(this.selectedCurrency()?.isActive ?? true)
      )
      .pipe(finalize(() => this.saving.set(false)))
      .subscribe({
        next: () => {
          this.toastr.success('Currency updated successfully.');
          this.finishCurrencySave();
        }
      });
  }

  private viewCurrency(currency: CurrencyDto): void {
    this.openEditCurrency(currency);
  }

  private openEditCurrency(currency: CurrencyDto): void {
    this.currencyModalMode.set('edit');
    this.selectedCurrencyId.set(currency.id);
    this.selectedCurrency.set(currency);
    this.currencySubmitAttempted.set(false);

    this.currencyApi
      .getCurrencyById(currency.id)
      .subscribe({
        next: currencyDetails => {
          this.selectedCurrency.set(currencyDetails);
          this.currencyForm.reset({
            name: currencyDetails.name ?? '',
            code: currencyDetails.code ?? '',
            symbol: currencyDetails.symbol ?? '',
            numericCode: currencyDetails.numericCode ?? '0',
            decimalDigits: currencyDetails.decimalDigits ?? 2
          });
          this.resetCurrencyFormState();

          this.showCurrencyModal.set(true);
        }
      });
  }

  private requestDeleteCurrency(currency: CurrencyDto): void {
    this.pendingDeleteCurrency.set(currency);
    this.deleteConfirmDialog()?.open();
  }

  private deleteCurrency(currency: CurrencyDto): void {
    this.updateCurrencyStatus(currency, false);
  }

  private activateCurrency(currency: CurrencyDto): void {
    this.updateCurrencyStatus(currency, true);
  }

  private buildCreateCurrencyRequest(): CreateCurrencyRequest {
    const value = this.currencyForm.getRawValue();

    return {
      name: value.name.trim(),
      code: value.code.trim().toUpperCase(),
      symbol: value.symbol.trim(),
      numericCode: value.numericCode,
      decimalDigits: value.decimalDigits
    };
  }

  private buildUpdateCurrencyRequest(isActive: boolean): UpdateCurrencyRequest {
    return {
      ...this.buildCreateCurrencyRequest(),
      isActive
    };
  }

  private currencyToUpdateRequest(currency: CurrencyDto, isActive: boolean): UpdateCurrencyRequest {
    return {
      name: currency.name?.trim() ?? '',
      code: currency.code?.trim().toUpperCase() ?? '',
      symbol: currency.symbol?.trim() ?? '',
      numericCode: currency.numericCode,
      decimalDigits: currency.decimalDigits,
      isActive
    };
  }

  private updateCurrencyStatus(currency: CurrencyDto, isActive: boolean): void {
    this.currencyApi
      .updateCurrency(currency.id, this.currencyToUpdateRequest(currency, isActive))
      .subscribe({
        next: () => {
          this.toastr.success(
            isActive
              ? 'Currency activated successfully.'
              : 'Currency deactivated successfully.'
          );
          this.loadCurrencies(this.queryState.getQuery());
        }
      });
  }

  private resetCurrencyFormState(): void {
    this.currencyForm.markAsPristine();
    this.currencyForm.markAsUntouched();
    this.currencyForm.updateValueAndValidity();
  }

  private finishCurrencySave(): void {
    this.showCurrencyModal.set(false);
    this.selectedCurrencyId.set(null);
    this.selectedCurrency.set(null);
    this.currencySubmitAttempted.set(false);
    this.loadCurrencies(this.queryState.getQuery());
  }

  private clearCurrencyGrid(): void {
    this.currencies.set([]);
    this.totalRecords.set(0);
    this.filteredRecords.set(0);
  }

  private focusFirstInvalidCurrencyControl(): void {
    queueMicrotask(() => {
      const firstInvalidControl =
        this.elementRef.nativeElement.querySelector<HTMLElement>(
          'modal [data-form-control][aria-invalid="true"]'
        );

      firstInvalidControl?.focus();
      firstInvalidControl?.scrollIntoView({
        behavior: 'smooth',
        block: 'center'
      });
    });
  }

  private toCurrencyDto(row: CurrencyTableRow): CurrencyDto {
    return {
      id: row.id,
      code: row.code,
      iso2: row.iso2,
      name: row.name,
      symbol: row.symbol,
      numericCode: row.numericCode,
      decimalDigits: row.decimalDigits,
      isActive: row.isActive,
      createdOn: row.createdOn,
      modifiedOn: row.modifiedOn
    };
  }

  visibleCountries(row: CurrencyTableRow): readonly CurrencyCountryFlag[] {
    return this.normalizeIso2Codes(row.iso2).slice(0, 1);
  }

  remainingCountries(row: CurrencyTableRow): readonly CurrencyCountryFlag[] {
    return this.normalizeIso2Codes(row.iso2).slice(1);
  }

  remainingCountryCount(row: CurrencyTableRow): number {
    return this.remainingCountries(row).length;
  }

  openIso2Tooltip(row: CurrencyTableRow): void {
    this.cancelIso2TooltipClose();
    this.openIso2TooltipCurrencyId.set(row.id);
  }

  scheduleIso2TooltipClose(): void {
    this.cancelIso2TooltipClose();
    this.iso2TooltipCloseTimer = setTimeout(() => this.closeIso2Tooltip(), 120);
  }

  isIso2TooltipOpen(row: CurrencyTableRow): boolean {
    return this.openIso2TooltipCurrencyId() === row.id;
  }

  iso2TooltipId(row: CurrencyTableRow): string {
    return `currency-iso2-tooltip-${row.id}`;
  }

  closeIso2Tooltip(): void {
    this.cancelIso2TooltipClose();
    this.openIso2TooltipCurrencyId.set(null);
  }

  onIso2TooltipKeydown(event: KeyboardEvent): void {
    if (event.key === 'Escape') {
      event.preventDefault();
      this.closeIso2Tooltip();
    }
  }

  onIso2TooltipPositionChange(event: ConnectedOverlayPositionChange): void {
    this.iso2TooltipPlacement.set(
      event.connectionPair.overlayY === 'bottom' ? 'above' : 'below'
    );
  }

  normalizeIso2Codes(value: unknown): CurrencyCountryFlag[] {
    const items: readonly unknown[] = Array.isArray(value)
      ? value
      : typeof value === 'string'
        ? value.split(/[,;|]/)
        : [];
    const codes = new Set<string>();

    for (const item of items) {
      const rawCode = typeof item === 'string'
        ? item
        : item && typeof item === 'object' && 'iso2' in item
          ? String(item.iso2)
          : '';
      const iso2 = rawCode.trim().toUpperCase();

      if (/^[A-Z]{2}$/.test(iso2)) {
        codes.add(iso2);
      }
    }

    return [...codes].map(iso2 => ({ iso2 }));
  }

  private cancelIso2TooltipClose(): void {
    if (this.iso2TooltipCloseTimer !== null) {
      clearTimeout(this.iso2TooltipCloseTimer);
      this.iso2TooltipCloseTimer = null;
    }
  }

  getFlagUrl(value: unknown): string {
    const code = this.normalizeIso2Codes(value)[0]?.iso2.toLowerCase();

    return code && /^[a-z]{2}$/.test(code)
      ? `https://flagcdn.com/24x18/${code}.png`
      : '';
  }
  onFlagError(event: Event): void {
    const image = event.target as HTMLImageElement;
    image.classList.add('hidden');
    image.nextElementSibling?.classList.remove('hidden');
  }
}
