import {CommonModule} from '@angular/common';
import {
  AfterViewInit,
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  ElementRef,
  EventEmitter,
  forwardRef,
  Input,
  Output,
  ViewChild,
  ViewEncapsulation
} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';

@Component({
  selector: 'nj-toggle',
  templateUrl: './toggle.component.html',
  styleUrls: ['./toggle.component.scss'],
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => ToggleComponent),
    multi: true
  }],
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  standalone: true,
  imports: [CommonModule]
})

export class ToggleComponent implements ControlValueAccessor, AfterViewInit {
  /**
   * @ignore
   */
  private toggleClassName = 'nj-toggle';

  /**
   * @ignore
   */
  private _checked: boolean;

  /**
   * Input id
   */
  @Input() inputId: string;

  /**
   * Input name
   */
  @Input() name: string;

  /**
   * Input size
   */
  @Input() size: 'md' | 'lg' = 'md';

  /**
   * Whether input is required or not
   */
  @Input() required: boolean;

  /**
   * Whether the toggle is checked or not
   */
  @Input()
  set isChecked(value: boolean) {
    this._checked = value;
    this.cdr.markForCheck();
  }

  get isChecked(): boolean {
    return this._checked;
  }

  /**
   * Whether the toggle is disabled or not
   */
  @Input() isDisabled: boolean;

  /**
   * Whether the toggle color is inherited from parent
   */
  @Input() isColorInherited?: boolean;

  /**
   * Text alternative for assistive technologies
   * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-label
   */
  @Input() ariaLabel: string;

  /**
   * Text alternative for assistive technologies based on visible text
   * @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-labelledby
   */
  @Input() ariaLabelledby: string;

  /**
   * Output that emits checked value on change only
   */
  @Output() valueChange: EventEmitter<boolean> = new EventEmitter<boolean>();

  /**
   * @ignore
   */
  @ViewChild('input')
  private _inputElement: ElementRef<HTMLInputElement>;

  /**
   * @ignore
   */
  @ViewChild('iconWrapper') iconWrapper;

  /**
   * Whether toggle has an icon
   */
  hasIcon: boolean;

  constructor(private cdr: ChangeDetectorRef) {
  }

  // Check if icon is provided on load
  ngAfterViewInit() {
    this.hasIcon = this.iconWrapper?.nativeElement && this.iconWrapper.nativeElement.innerHTML !== '';
  }

  /**
   * @ignore
   */
  private _onChange = (_: any): void => {
  };

  /**
   * @ignore
   */
  private _onTouched = (): void => {
  };

  /**
   * @ignore
   */
  _onChangeEvent(event: Event) {
    event.stopPropagation();
    if (this._inputElement?.nativeElement) {
      this.isChecked = this._inputElement.nativeElement.checked;
      this._onChange(this.isChecked);
      this.valueChange.emit(this.isChecked);
    }
  }

  /**
   * @ignore
   */
  _onInputClick(event: Event) {
    // We have to stop propagation for click events on the visually hidden input element.
    // By default, when a user clicks on a label element, a generated click event will be
    // dispatched on the associated input element. Since we are using a label element as our
    // root container, the click event on the `slide-toggle` will be executed twice.
    // The real click event will bubble up, and the generated click event also tries to bubble up.
    // This will lead to multiple click events.
    // Preventing bubbling for the second event will solve that issue.
    event.stopPropagation();
  }

  /**
   * Implemented as part of ControlValueAccessor.
   * @ignore
   */
  writeValue(value: any): void {
    this.isChecked = !!value;
  }

  /**
   * Implemented as part of ControlValueAccessor.
   * @ignore
   */
  registerOnChange(fn: any): void {
    this._onChange = fn;
  }

  /**
   * Implemented as part of ControlValueAccessor.
   * @ignore
   */
  registerOnTouched(fn: any): void {
    this._onTouched = fn;
  }

  /**
   * Implemented as part of ControlValueAccessor.
   * @ignore
   */
  setDisabledState(isDisabled: boolean): void {
    this.isDisabled = isDisabled;
    this.cdr.markForCheck();
  }

  /**
   * @ignore
   */
  getToggleSizeClass(): string {
    let sizeModifier: string;
    switch (this.size) {
      case 'lg':
        sizeModifier = 'lg';
        break;
      default:
        break;
    }
    return sizeModifier ? `${this.toggleClassName}--${sizeModifier}` : '';
  }

  /**
   * @ignore
   */
  getToggleIsDisabledClass(): string {
    return this.isDisabled ? `${this.toggleClassName}--disabled` : '';
  }

  getToggleInheritColorClass(): string {
    return this.isColorInherited ? `${this.toggleClassName}--inherit` : '';
  }
}
