import { HttpErrorResponse } from '@angular/common/http';
import { Component, DestroyRef, ElementRef, computed, inject, signal, viewChild } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
import { ToastrService } from 'ngx-toastr';
import { Subject, catchError, finalize, of, switchMap } from 'rxjs';

import {
  CreateLanguageRequest,
  LanguageDto,
  LanguageModalMode,
  UpdateLanguageRequest
} from '../../models/language.model';
import { LanguageService } from '../../data-access/language.service';
import { DataTable } from '../../../../../shared/components/data-table/data-table';
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 { FormInput } from '../../../../../shared/components/form/form-input/form-input';
import { Modal } from '../../../../../shared/components/modal/modal';
import { DataTableCellDirective } from '../../../../../shared/directives/data-table-cell.directive';
import { ConfirmDialog } from '../../../../../shared/components/confirm-dialog/confirm-dialog';

interface LanguageTableRow extends DataTableRecord {
  id: string;
  code: string;
  name: string;
  nativeName: string;
  isRightToLeft: boolean;
  isActive: boolean;
  serialNumber: number;
  createdOn: string;
  modifiedOn: string | null;
}

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

  readonly queryState = new DataTableQueryState();
  readonly languages = signal<LanguageTableRow[]>([]);
  readonly totalRecords = signal(0);
  readonly modalLoading = signal(false);
  readonly saving = signal(false);
  readonly statusChangingId = signal<string | null>(null);
  readonly showModal = signal(false);
  readonly modalMode = signal<LanguageModalMode>('create');
  readonly selectedLanguageId = signal<string | null>(null);
  readonly selectedLanguage = signal<LanguageDto | null>(null);
  readonly submitAttempted = signal(false);
  readonly pendingDeleteLanguageId = signal<string | null>(null);
  readonly deleteConfirmDialog = viewChild(ConfirmDialog);

  readonly languageForm = this.formBuilder.nonNullable.group({
    name: ['', [Validators.required, Validators.maxLength(100), Validators.pattern(/.*\S.*/)]],
    code: ['', [
      Validators.required,
      Validators.maxLength(35),
      Validators.pattern(/^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/)
    ]],
    nativeName: ['', [Validators.required, Validators.maxLength(100), Validators.pattern(/.*\S.*/)]],
    isRightToLeft: [false]
  });

  readonly modalTitle = computed(() =>
    this.modalMode() === 'create' ? 'Add Language' : 'Edit Language'
  );
  readonly submitLabel = computed(() =>
    this.modalMode() === 'create' ? 'Save' : 'Update'
  );
  readonly loadingLabel = computed(() =>
    this.modalMode() === 'create' ? 'Saving...' : 'Updating...'
  );
  readonly submitAction = computed<'save' | 'update'>(() =>
    this.modalMode() === 'create' ? 'save' : 'update'
  );

  readonly columns = signal<DataTableColumn<LanguageTableRow>[]>([
    { key: 'serialNumber', label: 'Sr. No.', header: 'Sr.No.', sortable: false, width: '100px' },
    { key: 'name', label: 'Language Name', header: 'Language Name', sortable: true, align: 'left' },
    { key: 'code', label: 'Language Code', header: 'Language Code', sortable: true },
    { key: 'nativeName', label: 'Native Name', header: 'Native Name', sortable: true },
    {
      key: 'isRightToLeft', label: 'Direction', header: 'Direction', sortable: true,
      formatter: value => value ? 'RTL' : 'LTR'
    },
    {
      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<LanguageTableRow>[]>([
    {
      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,
      disabled: row => this.statusChangingId() === row.id
    },
    {
      type: 'activate',
      label: 'Activate',
      icon: 'ti ti-check',
      className: 'text-success',
      visible: row => !row.isActive,
      disabled: row => this.statusChangingId() === row.id
    }
  ]);

  constructor() {
    this.queryRequests$.pipe(
      switchMap(query => this.languageApi.getDataTable(query).pipe(
        catchError(() => {
          this.toastr.error('Unable to load languages.');
          this.languages.set([]);
          this.totalRecords.set(0);
          return of(null);
        })
      )),
      takeUntilDestroyed(this.destroyRef)
    ).subscribe(response => {
      if (!response || response.draw !== this.queryState.getQuery().draw) {
        return;
      }
      const query = this.queryState.getQuery();
      this.languages.set(response.rows.map((language, index) => ({
        ...language,
        serialNumber: (query.page - 1) * query.pageSize + index + 1
      })));
      this.totalRecords.set(response.total);
    });
  }

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

  loadLanguages(query: DataTableQuery): void { this.queryRequests$.next(query); }
  onSearch(value: string): void { this.loadLanguages(this.queryState.setSearch(value.trim())); }
  onPageChange(event: DataTablePageEvent): void { this.loadLanguages(this.queryState.setPage(event)); }
  onSortChange(event: DataTableSortEvent): void { this.loadLanguages(this.queryState.setSort(event)); }

  onDeleteConfirmed(): void {
    const languageId = this.pendingDeleteLanguageId();

    if (!languageId) {
      return;
    }

    this.pendingDeleteLanguageId.set(null);
    this.changeLanguageStatus(languageId, false);
  }

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

  onActionClick(event: DataTableActionEvent<LanguageTableRow>): void {
    if (event.action.type === 'edit') {
      this.openEditLanguage(event.row.id);
    } else if (event.action.type === 'delete') {
      this.requestDeleteLanguage(event.row.id);
    } else if (event.action.type === 'activate') {
      this.changeLanguageStatus(event.row.id, true);
    }
  }

  private requestDeleteLanguage(id: string): void {
    this.pendingDeleteLanguageId.set(id);
    this.deleteConfirmDialog()?.open();
  }

  onAddLanguage(): void {
    this.modalMode.set('create');
    this.selectedLanguageId.set(null);
    this.selectedLanguage.set(null);
    this.submitAttempted.set(false);
    this.languageForm.reset({ name: '', code: '', nativeName: '', isRightToLeft: false });
    this.resetFormState();
    this.showModal.set(true);
  }

  openEditLanguage(id: string): void {
    this.modalMode.set('edit');
    this.selectedLanguageId.set(id);
    this.selectedLanguage.set(null);
    this.submitAttempted.set(false);
    this.languageForm.reset({ name: '', code: '', nativeName: '', isRightToLeft: false });
    this.resetFormState();
    this.modalLoading.set(true);
    this.showModal.set(true);

    this.languageApi.getById(id).pipe(
      finalize(() => this.modalLoading.set(false)),
      takeUntilDestroyed(this.destroyRef)
    ).subscribe({
      next: language => {
        if (this.selectedLanguageId() !== language.id || !this.showModal()) {
          return;
        }
        this.selectedLanguage.set(language);
        this.languageForm.reset({
          name: language.name,
          code: language.code,
          nativeName: language.nativeName,
          isRightToLeft: language.isRightToLeft
        });
        this.resetFormState();
      },
      error: () => this.showModal.set(false)
    });
  }

  closeModal(): void {
    if (this.saving()) return;
    this.showModal.set(false);
    this.selectedLanguageId.set(null);
    this.selectedLanguage.set(null);
    this.submitAttempted.set(false);
  }

  saveLanguage(): void {
    if (this.languageForm.invalid) {
      this.submitAttempted.set(true);
      this.languageForm.markAllAsTouched();
      this.focusFirstInvalidControl();
      return;
    }
    if (this.saving() || this.modalLoading()) return;

    this.saving.set(true);
    const request = this.buildCreateRequest();
    const operation = this.modalMode() === 'create'
      ? this.languageApi.create(request)
      : this.languageApi.update(
          this.selectedLanguageId() ?? '',
          { ...request, isActive: this.selectedLanguage()?.isActive ?? true }
        );

    operation.pipe(
      finalize(() => this.saving.set(false)),
      takeUntilDestroyed(this.destroyRef)
    ).subscribe({
      next: () => {
        this.toastr.success(
          this.modalMode() === 'create'
            ? 'Language saved successfully.'
            : 'Language updated successfully.'
        );
        this.finishSave();
      },
      error: (error: HttpErrorResponse) => this.handleSaveError(error)
    });
  }

  private buildCreateRequest(): CreateLanguageRequest {
    const value = this.languageForm.getRawValue();
    return {
      code: this.normalizeCode(value.code),
      name: value.name.trim(),
      nativeName: value.nativeName.trim(),
      isRightToLeft: value.isRightToLeft
    };
  }

  private normalizeCode(code: string): string {
    return code.trim().split('-').map((part, index) =>
      index === 0 ? part.toLowerCase() : part.toUpperCase()
    ).join('-');
  }

  private changeLanguageStatus(id: string, isActive: boolean): void {
    if (this.statusChangingId()) return;
    this.statusChangingId.set(id);
    this.languageApi.getById(id).pipe(
      switchMap(language => this.languageApi.update(id, {
        code: language.code,
        name: language.name,
        nativeName: language.nativeName,
        isRightToLeft: language.isRightToLeft,
        isActive
      })),
      finalize(() => this.statusChangingId.set(null)),
      takeUntilDestroyed(this.destroyRef)
    ).subscribe({
      next: () => {
        this.toastr.success(isActive
          ? 'Language activated successfully.'
          : 'Language deleted successfully.');
        this.loadLanguages(this.queryState.getQuery());
      },
      error: (error: HttpErrorResponse) => {
        if (error.status === 404) this.toastr.error('The language is no longer available.');
      }
    });
  }

  private handleSaveError(error: HttpErrorResponse): void {
    if (error.status === 409) {
      this.toastr.error('A language with this code already exists.', 'Duplicate language code');
    }
  }

  private finishSave(): void {
    this.showModal.set(false);
    this.selectedLanguageId.set(null);
    this.selectedLanguage.set(null);
    this.submitAttempted.set(false);
    this.loadLanguages(this.queryState.getQuery());
  }

  private resetFormState(): void {
    this.languageForm.markAsPristine();
    this.languageForm.markAsUntouched();
    this.languageForm.updateValueAndValidity();
  }

  private focusFirstInvalidControl(): void {
    queueMicrotask(() => {
      const control = this.elementRef.nativeElement.querySelector<HTMLElement>(
        'modal [data-form-control][aria-invalid="true"]'
      );
      control?.focus();
      control?.scrollIntoView({ behavior: 'smooth', block: 'center' });
    });
  }
}
