import { ChangeDetectionStrategy, Component, DestroyRef, ElementRef, inject, signal } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
import { ToastrService } from 'ngx-toastr';
import { catchError, of } from 'rxjs';

import {
  CurrencyLookupDto,
} from '../../../../global-masters/currencies/public-api';
import {
  LanguageLookupDto,
} from '../../../../global-masters/languages/public-api';
import {
  TimezoneLookupDto,
} from '../../../../global-masters/timezones/public-api';
import { Autocomplete } from '../../../../../shared/components/form/autocomplete/autocomplete';
import {
  AutocompleteDisplayFn,
  AutocompleteResolveValueFn,
  AutocompleteSearchFn,
  AutocompleteValueFn,
} from '../../../../../shared/components/form/autocomplete/autocomplete.types';
import { FormSelect } from '../../../../../shared/components/form/form-select/form-select';
import { FormSelectOption } from '../../../../../shared/components/form/models/form-select.models';
import {
  CountryLocalizationDefaults,
  DateFormatValue,
  FiscalYearConventionValue,
  NumberFormatValue,
  OnboardingLookupValue,
  OnboardingStepForm,
  OrganizationLocalizationValue,
  TimeFormatValue,
} from '../../models/organization-onboarding.model';
import { OrganizationOnboardingService } from '../../services/organization-onboarding.service';

interface OrganizationLocalizationFormModel {
  readonly timeZoneId: string | null;
  readonly currencyId: string | null;
  readonly defaultLanguageId: string | null;
  readonly additionalLanguageIds: readonly string[];
  readonly dateFormat: DateFormatValue | null;
  readonly timeFormat: TimeFormatValue | null;
  readonly numberFormat: NumberFormatValue | null;
  readonly fiscalYearConvention: FiscalYearConventionValue | null;
}

@Component({
  selector: 'app-organization-localization-step',
  standalone: true,
  imports: [ReactiveFormsModule, Autocomplete, FormSelect],
  templateUrl: './organization-localization.html',
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class OrganizationLocalizationStepComponent implements OnboardingStepForm<OrganizationLocalizationValue> {
  private readonly destroyRef = inject(DestroyRef);
  private readonly formBuilder = inject(FormBuilder);
  private readonly onboardingService = inject(OrganizationOnboardingService);
  private readonly toastr = inject(ToastrService);
  private readonly elementRef = inject<ElementRef<HTMLElement>>(ElementRef);

  readonly submitAttempted = signal(false);
  readonly timeZoneSelection = signal<TimezoneLookupDto | null>(null);
  readonly currencySelection = signal<CurrencyLookupDto | null>(null);
  readonly defaultLanguageSelection = signal<LanguageLookupDto | null>(null);
  readonly additionalLanguageOptions = signal<readonly FormSelectOption<string>[]>([]);

  readonly dateFormatOptions = this.onboardingService.getDateFormatOptions();
  readonly timeFormatOptions = this.onboardingService.getTimeFormatOptions();
  readonly numberFormatOptions = this.onboardingService.getNumberFormatOptions();
  readonly fiscalYearOptions = this.onboardingService.getFiscalYearOptions();

  readonly form = this.formBuilder.group({
    timeZoneId: this.formBuilder.control<string | null>(null, [Validators.required]),
    currencyId: this.formBuilder.control<string | null>(null, [Validators.required]),
    defaultLanguageId: this.formBuilder.control<string | null>(null, [Validators.required]),
    additionalLanguageIds: this.formBuilder.nonNullable.control<readonly string[]>([]),
    dateFormat: this.formBuilder.control<DateFormatValue | null>(null, [Validators.required]),
    timeFormat: this.formBuilder.control<TimeFormatValue | null>(null, [Validators.required]),
    numberFormat: this.formBuilder.control<NumberFormatValue | null>(null, [Validators.required]),
    fiscalYearConvention: this.formBuilder.control<FiscalYearConventionValue | null>(null, [Validators.required]),
  });

  readonly searchTimezones: AutocompleteSearchFn<TimezoneLookupDto> = (term, limit) =>
    this.onboardingService.searchTimezones(term, limit).pipe(
      catchError(() => {
        this.toastr.error('Unable to load timezones.');
        return of<readonly TimezoneLookupDto[]>([]);
      })
    );

  readonly searchCurrencies: AutocompleteSearchFn<CurrencyLookupDto> = (term, limit) =>
    this.onboardingService.searchCurrencies(term, limit).pipe(
      catchError(() => {
        this.toastr.error('Unable to load currencies.');
        return of<readonly CurrencyLookupDto[]>([]);
      })
    );

  readonly searchLanguages: AutocompleteSearchFn<LanguageLookupDto> = (term, limit) =>
    this.onboardingService.searchLanguages(term, limit).pipe(
      catchError(() => {
        this.toastr.error('Unable to load languages.');
        return of<readonly LanguageLookupDto[]>([]);
      })
    );

  readonly displayTimezone: AutocompleteDisplayFn<TimezoneLookupDto> = item =>
    [item.ianaId, item.displayName].filter(Boolean).join(' - ');

  readonly timeZoneValue: AutocompleteValueFn<TimezoneLookupDto, string> = item => item.id;

  readonly resolveTimezone: AutocompleteResolveValueFn<TimezoneLookupDto, string> = value =>
    this.onboardingService.resolveTimezone(value);

  readonly displayCurrency: AutocompleteDisplayFn<CurrencyLookupDto> = item =>
    [item.code, item.name, item.symbol ? `(${item.symbol})` : null].filter(Boolean).join(' ');

  readonly currencyValue: AutocompleteValueFn<CurrencyLookupDto, string> = item => item.id;

  readonly resolveCurrency: AutocompleteResolveValueFn<CurrencyLookupDto, string> = value =>
    this.onboardingService.resolveCurrency(value);

  readonly displayLanguage: AutocompleteDisplayFn<LanguageLookupDto> = item =>
    [item.code, item.name].filter(Boolean).join(' - ');

  readonly languageValue: AutocompleteValueFn<LanguageLookupDto, string> = item => item.id;

  readonly resolveLanguage: AutocompleteResolveValueFn<LanguageLookupDto, string> = value =>
    this.onboardingService.resolveLanguage(value);

  constructor() {
    // this.onboardingService.loadLanguageOptions(100)
    //   .pipe(
    //     catchError(() => {
    //       this.toastr.error('Unable to load language options.');
    //       return of<readonly FormSelectOption<string>[]>([]);
    //     }),
    //     takeUntilDestroyed(this.destroyRef)
    //   )
    //   .subscribe(options => {
    //     this.additionalLanguageOptions.update(current => this.mergeLanguageOptions(current, options));
    //   });

    this.form.controls.defaultLanguageId.valueChanges
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe(defaultLanguageId => {
        if (!defaultLanguageId) {
          return;
        }

        const filteredIds = this.form.controls.additionalLanguageIds.value.filter(id => id !== defaultLanguageId);

        if (filteredIds.length !== this.form.controls.additionalLanguageIds.value.length) {
          this.form.controls.additionalLanguageIds.setValue(filteredIds, { emitEvent: false });
        }
      });
  }

  validate(): boolean {
    this.submitAttempted.set(true);

    if (this.form.valid) {
      return true;
    }

    this.form.markAllAsTouched();
    this.focusFirstInvalidControl();
    return false;
  }

  getValue(): OrganizationLocalizationValue {
    const value = this.form.getRawValue() as OrganizationLocalizationFormModel;
    const timeZone = this.timeZoneSelection();
    const currency = this.currencySelection();
    const defaultLanguage = this.defaultLanguageSelection();

    if (!timeZone || !currency || !defaultLanguage || !value.dateFormat || !value.timeFormat || !value.numberFormat || !value.fiscalYearConvention) {
      throw new Error('Organization localization values are incomplete.');
    }

    const additionalLanguageSelections = this.resolveAdditionalLanguageSelections(value.additionalLanguageIds);

    return {
      timeZone: { id: timeZone.id, label: timeZone.displayName, secondaryLabel: timeZone.ianaId },
      currency: { id: currency.id, label: currency.name, secondaryLabel: currency.code },
      defaultLanguage: { id: defaultLanguage.id, label: defaultLanguage.name, secondaryLabel: defaultLanguage.code },
      additionalLanguageIds: value.additionalLanguageIds.filter(id => id !== defaultLanguage.id),
      additionalLanguageSelections,
      dateFormat: value.dateFormat,
      timeFormat: value.timeFormat,
      numberFormat: value.numberFormat,
      fiscalYearConvention: value.fiscalYearConvention,
      localizationDefaultsCountryId: null,
    };
  }

  getDraftValue(): Partial<OrganizationLocalizationValue> {
    const value = this.form.getRawValue() as OrganizationLocalizationFormModel;

    return {
      timeZone: this.timeZoneSelection()
        ? {
            id: this.timeZoneSelection()!.id,
            label: this.timeZoneSelection()!.displayName,
            secondaryLabel: this.timeZoneSelection()!.ianaId,
          }
        : undefined,
      currency: this.currencySelection()
        ? {
            id: this.currencySelection()!.id,
            label: this.currencySelection()!.name,
            secondaryLabel: this.currencySelection()!.code,
          }
        : undefined,
      defaultLanguage: this.defaultLanguageSelection()
        ? {
            id: this.defaultLanguageSelection()!.id,
            label: this.defaultLanguageSelection()!.name,
            secondaryLabel: this.defaultLanguageSelection()!.code,
          }
        : undefined,
      additionalLanguageIds: value.additionalLanguageIds,
      additionalLanguageSelections: this.resolveAdditionalLanguageSelections(value.additionalLanguageIds),
      dateFormat: value.dateFormat ?? undefined,
      timeFormat: value.timeFormat ?? undefined,
      numberFormat: value.numberFormat ?? undefined,
      fiscalYearConvention: value.fiscalYearConvention ?? undefined,
      localizationDefaultsCountryId: null,
    };
  }

  patchValue(value: Partial<OrganizationLocalizationValue>): void {
    this.form.patchValue({
      timeZoneId: value.timeZone?.id ?? null,
      currencyId: value.currency?.id ?? null,
      defaultLanguageId: value.defaultLanguage?.id ?? null,
      additionalLanguageIds: value.additionalLanguageIds ?? [],
      dateFormat: value.dateFormat ?? null,
      timeFormat: value.timeFormat ?? null,
      numberFormat: value.numberFormat ?? null,
      fiscalYearConvention: value.fiscalYearConvention ?? null,
    }, { emitEvent: false });

    this.timeZoneSelection.set(
      value.timeZone
        ? {
            id: value.timeZone.id,
            ianaId: value.timeZone.secondaryLabel ?? value.timeZone.label,
            displayName: value.timeZone.label,
          }
        : null
    );
    this.currencySelection.set(
      value.currency
        ? {
            id: value.currency.id,
            code: value.currency.secondaryLabel ?? value.currency.label,
            name: value.currency.label,
            symbol: '',
          }
        : null
    );
    this.defaultLanguageSelection.set(
      value.defaultLanguage
        ? {
            id: value.defaultLanguage.id,
            code: value.defaultLanguage.secondaryLabel ?? value.defaultLanguage.label,
            name: value.defaultLanguage.label,
            nativeName: value.defaultLanguage.label,
            isRightToLeft: false,
          }
        : null
    );
    this.additionalLanguageOptions.update(current => this.mergeSelectionOptions(current, value.additionalLanguageSelections ?? []));
    this.submitAttempted.set(false);
  }

  applyCountryDefaults(defaults: CountryLocalizationDefaults, replaceExisting: boolean): void {
    const nextValue = {
      timeZoneId: replaceExisting || !this.form.controls.timeZoneId.value ? defaults.timeZone.id : this.form.controls.timeZoneId.value,
      currencyId: replaceExisting || !this.form.controls.currencyId.value ? defaults.currency.id : this.form.controls.currencyId.value,
      defaultLanguageId: replaceExisting || !this.form.controls.defaultLanguageId.value ? defaults.defaultLanguage.id : this.form.controls.defaultLanguageId.value,
      dateFormat: replaceExisting || !this.form.controls.dateFormat.value ? defaults.dateFormat : this.form.controls.dateFormat.value,
      timeFormat: replaceExisting || !this.form.controls.timeFormat.value ? defaults.timeFormat : this.form.controls.timeFormat.value,
      numberFormat: replaceExisting || !this.form.controls.numberFormat.value ? defaults.numberFormat : this.form.controls.numberFormat.value,
      fiscalYearConvention: replaceExisting || !this.form.controls.fiscalYearConvention.value ? defaults.fiscalYearConvention : this.form.controls.fiscalYearConvention.value,
    };

    this.form.patchValue(nextValue, { emitEvent: false });
    this.timeZoneSelection.set(
      replaceExisting || !this.timeZoneSelection()
        ? {
            id: defaults.timeZone.id,
            ianaId: defaults.timeZone.secondaryLabel ?? defaults.timeZone.label,
            displayName: defaults.timeZone.label,
          }
        : this.timeZoneSelection()
    );
    this.currencySelection.set(
      replaceExisting || !this.currencySelection()
        ? {
            id: defaults.currency.id,
            code: defaults.currency.secondaryLabel ?? defaults.currency.label,
            name: defaults.currency.label,
            symbol: '',
          }
        : this.currencySelection()
    );
    this.defaultLanguageSelection.set(
      replaceExisting || !this.defaultLanguageSelection()
        ? {
            id: defaults.defaultLanguage.id,
            code: defaults.defaultLanguage.secondaryLabel ?? defaults.defaultLanguage.label,
            name: defaults.defaultLanguage.label,
            nativeName: defaults.defaultLanguage.label,
            isRightToLeft: false,
          }
        : this.defaultLanguageSelection()
    );

    if (replaceExisting) {
      const filteredIds = this.form.controls.additionalLanguageIds.value.filter(id => id !== defaults.defaultLanguage.id);
      this.form.controls.additionalLanguageIds.setValue(filteredIds, { emitEvent: false });
    }
  }

  hasSavedOrEnteredValues(): boolean {
    const value = this.form.getRawValue() as OrganizationLocalizationFormModel;

    return !!(
      value.timeZoneId ||
      value.currencyId ||
      value.defaultLanguageId ||
      value.additionalLanguageIds.length ||
      value.dateFormat ||
      value.timeFormat ||
      value.numberFormat ||
      value.fiscalYearConvention
    );
  }

  onTimeZoneSelected(item: TimezoneLookupDto): void {
    this.timeZoneSelection.set(item);
  }

  onTimeZoneCleared(): void {
    this.timeZoneSelection.set(null);
  }

  onCurrencySelected(item: CurrencyLookupDto): void {
    this.currencySelection.set(item);
  }

  onCurrencyCleared(): void {
    this.currencySelection.set(null);
  }

  onDefaultLanguageSelected(item: LanguageLookupDto): void {
    this.defaultLanguageSelection.set(item);
  }

  onDefaultLanguageCleared(): void {
    this.defaultLanguageSelection.set(null);
  }

  private resolveAdditionalLanguageSelections(languageIds: readonly string[]): readonly OnboardingLookupValue[] {
    const uniqueIds = [...new Set(languageIds)];
    const optionMap = new Map(this.additionalLanguageOptions().map(option => [option.value, option.label]));

    return uniqueIds.map(languageId => ({
      id: languageId,
      label: optionMap.get(languageId) ?? languageId,
    }));
  }

  private mergeLanguageOptions(
    current: readonly FormSelectOption<string>[],
    incoming: readonly FormSelectOption<string>[],
  ): readonly FormSelectOption<string>[] {
    const merged = new Map<string, FormSelectOption<string>>();

    [...current, ...incoming].forEach(option => {
      merged.set(option.value, option);
    });

    return [...merged.values()];
  }

  private mergeSelectionOptions(
    current: readonly FormSelectOption<string>[],
    selections: readonly OnboardingLookupValue[],
  ): readonly FormSelectOption<string>[] {
    return this.mergeLanguageOptions(
      current,
      selections.map(selection => ({ value: selection.id, label: selection.label }))
    );
  }

  private focusFirstInvalidControl(): void {
    queueMicrotask(() => {
      const invalidElement = this.elementRef.nativeElement.querySelector<HTMLElement>(
        '.ng-invalid [data-form-control], [data-form-control].ng-invalid, .ng-invalid .ng-select-container'
      );

      invalidElement?.focus();
    });
  }
}