import {
  ChangeDetectionStrategy,
  Component,
  ElementRef,
  EventEmitter,
  forwardRef,
  Input,
  AfterViewInit,
  Output,
  Renderer2,
  ViewChild,
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { DropdownTriggerForDirective } from '@msfa-shared/components/dropdown/dropdown-trigger-for.directive';
import { MultiselectFilterOption } from '@msfa-shared/components/multiselect/multiselect-filter-option';
import { uuid } from '@msfa-utils/uuid';

interface PropagateChangeFn {
  (_: unknown): void;
}

interface PropagateTouchedFn {
  (): void;
}

@Component({
  selector: 'msfa-multiselect',
  templateUrl: './multiselect.component.html',
  styleUrls: ['./multiselect.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => MultiselectComponent),
      multi: true,
    },
  ],
})
export class MultiselectComponent implements AfterViewInit, ControlValueAccessor {
  @ViewChild(DropdownTriggerForDirective) dropdownRef: DropdownTriggerForDirective;
  panelId = `panel-${uuid()}`;
  @Input() buttonElementId: string = uuid();
  @Input() isInvalid = false;
  @Input() showValidation = false;
  @Input() confirmButtonText = 'Spara';
  @Input() description: string;
  @Input() multiselectTitle: string;
  @Input() availableOptions: MultiselectFilterOption[];
  @Input() selectedOptions: MultiselectFilterOption[];
  @Output() selectedOptionsChange = new EventEmitter<MultiselectFilterOption[]>();
  @ViewChild('multiselectButton') multiselectButton: ElementRef;
  isDisabled = false;

  private propagateChange: PropagateChangeFn;
  private propagateTouched: PropagateTouchedFn;

  constructor(private renderer: Renderer2) {}

  // Allows Angular to update the model.
  // Update the model and changes needed for the view here.
  writeValue(selectedOptions: MultiselectFilterOption[]): void {
    this.selectedOptions = selectedOptions;
  }

  // Allows Angular to register a function to call when the model changes.
  // Save the function as a property to call later here.
  registerOnChange(fn: PropagateChangeFn): void {
    this.propagateChange = fn;
  }

  // Allows Angular to register a function to call when the input has been touched.
  // Save the function as a property to call later here.
  registerOnTouched(fn: PropagateTouchedFn): void {
    this.propagateTouched = fn;
  }

  // Allows Angular to disable the input.
  setDisabledState?(isDisabled: boolean): void {
    this.renderer.setProperty(this.multiselectButton.nativeElement, 'disabled', isDisabled);
    this.dropdownRef.closeDropdown();
  }

  emitSelectedOptions(selectedOptions: MultiselectFilterOption[]): void {
    // If used with FormsModule/ReactiveFormsModule
    if (this.propagateChange && this.propagateTouched) {
      this.propagateChange(selectedOptions);
      this.propagateTouched();
    } else {
      this.selectedOptionsChange.emit(selectedOptions);
    }

    this.dropdownRef.closeDropdown();
  }

  ngAfterViewInit(): void {
    if (!this.dropdownRef) {
      return;
    }
    this.dropdownRef.onOpen(() => {
      // force refresh of selected options if it's reopened
      this.selectedOptions = [...(this.selectedOptions || [])];
    });
  }
}
