import {CommonModule} from '@angular/common';
import {
  booleanAttribute,
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  ElementRef,
  EventEmitter,
  forwardRef,
  Input,
  Output,
  ViewChild
} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
import {IconComponent} from '../icon/icon.component';
import {CheckboxSize} from './checkbox.model';

@Component({
  selector: 'nj-checkbox',
  templateUrl: './checkbox.component.html',
  styleUrls: ['./checkbox.component.scss'],
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => CheckboxComponent),
    multi: true
  }],
  changeDetection: ChangeDetectionStrategy.OnPush,
  standalone: true,
  imports: [IconComponent, CommonModule]
})
export class CheckboxComponent implements ControlValueAccessor {
  private readonly checkboxClassName = 'nj-checkbox';

  private _checked: boolean;

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

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

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

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

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

  /**
   * Checkbox size
   */
  @Input() size?: CheckboxSize = 'md';

  /**
   * Input value
   */
  @Input() value?: string;

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

  /**
   * Whether the checkbox is in error state
   */
  @Input() hasError?: boolean;

  /**
   * Whether the checkbox is in success state
   */
  @Input() hasSuccess?: boolean;

  /**
   * Message to display as hint below checkbox
   */
  @Input() subscriptMessage?: string;

  /**
   * @Deprecated
   * Message to display if checkbox is in error state
   */
  @Input() errorMessage?: string;

  /**
   * Aria label, for accessibility reasons
   */
  @Input() ariaLabel?: string;

  /**
   * Aria labelled by, for accessibility reasons
   */
  @Input() ariaLabelledby?: string;

  /**
   * Whether checkbox is only presentational and state handled by a parent, can be useful for lists of checkboxes.
   * When set to `true`, the checkbox won't have any interaction.
   */
  @Input() isPresentational? = false;

  /**
   * Whether the checkbox is in indeterminate state or not.
   */
  @Input({transform: booleanAttribute}) indeterminate: boolean;

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

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

  constructor(private cdr: ChangeDetectorRef) {
  }

  /**
   * @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
   */
  getSubscriptId(): string {
    return `${this.inputId}-hint`;
  }

  private get checkboxSizeClass(): string {
    return this.size !== 'md' ? `${this.checkboxClassName}--${this.size}` : '';
  }

  protected get checkboxClasses() {
    return {
      'nj-checkbox--error': this.hasError,
      'nj-checkbox--success': this.hasSuccess,
      [this.checkboxSizeClass]: true
    };
  }
}
