import { CommonModule } from '@angular/common';
import {
  AfterViewInit,
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  ElementRef,
  forwardRef,
  inject,
  Input,
  OnDestroy,
  ViewChild
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { BehaviorSubject, Subject, takeUntil } from 'rxjs';
import { Utils } from '../../utils/utils.util';

@Component({
  selector: 'nj-slider',
  templateUrl: './slider.component.html',
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => SliderComponent),
      multi: true
    }
  ],
  changeDetection: ChangeDetectionStrategy.OnPush,
  standalone: true,
  imports: [CommonModule]
})
export class SliderComponent implements AfterViewInit, OnDestroy, ControlValueAccessor {
  private _initialValue: number;

  private _currentValue: number;
  private _max = 100;
  private _min = 0;
  private inputValue$ = new BehaviorSubject<number>(this._min);

  private unsubscribe = new Subject<void>();
  private cdr = inject(ChangeDetectorRef);

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

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

  /**
   * Slider Id, required if label is set
   */
  @Input() sliderId?: string;

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

  /**
   * Slider label
   */
  @Input() label?: string;

  /**
   * Minimum value
   */
  @Input()
  set min(min: number) {
    if (!Utils.isUndefinedOrNull(min)) {
      this._min = min;
      this.updateInputCssPropWithValue(this._currentValue);
    }
  }

  get min(): number {
    return this._min;
  }

  /**
   * Maximum value
   */
  @Input()
  set max(max: number) {
    if (!Utils.isUndefinedOrNull(max)) {
      this._max = max;
      this.updateInputCssPropWithValue(this._currentValue);
    }
  }

  get max(): number {
    return this._max;
  }

  /**
   * Input value
   */
  @Input()
  set value(val: number) {
    if (!Utils.isUndefinedOrNull(val)) {
      this._initialValue = val;
    } else {
      this._initialValue = this._min;
    }
    this._currentValue = this._initialValue;
    this.inputValue$.next(this._currentValue);
    this.cdr.markForCheck();
  }

  get value(): number {
    return this._initialValue;
  }

  /**
   * Slider input step
   */
  @Input() step: number = 0.1;

  /**
   * Whether input is disabled or not
   */
  @Input() isDisabled = false;

  @ViewChild('input') input: ElementRef;

  ngAfterViewInit() {
    this.inputValue$.pipe(takeUntil(this.unsubscribe)).subscribe((val) => {
      this.updateInputCssPropWithValue(val);
    });
  }

  ngOnDestroy() {
    this.unsubscribe.next();
    this.unsubscribe.complete();
  }

  /**
   * Implemented as part of ControlValueAccessor.
   * @ignore
   */
  writeValue(value: number): void {
    this.value = 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
   */
  updateValue(event: Event) {
    // @ts-ignore
    const newVal = event?.target?.value;
    if (newVal !== this._currentValue) {
      this._currentValue = newVal;
      this.writeValue(newVal);
      this._onChange(newVal);
    }
  }

  /**
   * @ignore
   */
  updateInputCssPropWithValue(value: number) {
    if (!this.input?.nativeElement || Utils.isUndefinedOrNull(value)) {
      return;
    }
    let percentage = (100 * (value - this.min)) / (this.max - this.min);
    percentage = isNaN(percentage) ? 0 : percentage;
    this.input.nativeElement.style.setProperty('--nj-slider-track-position', `${percentage}% 100%`);
  }
}
