import {
  Component,
  ElementRef,
  EventEmitter,
  Input,
  OnChanges,
  Output,
  SimpleChanges,
  ViewChild,
} from '@angular/core';

import { v4 as uuid } from 'uuid';

@Component({
  selector: 'app-input-number',
  templateUrl: './input-number.component.html',
  styleUrls: ['./input-number.component.scss'],
})
export class InputNumberComponent implements OnChanges {
  @ViewChild('input') input!: ElementRef<HTMLInputElement>;

  @Input() label = '';
  @Input() value = 0;
  @Input() step = 1;
  @Input() decimalPlaces = 0;
  @Input() decimalSeparator: ',' | '.' = ',';
  @Input() showControls = true;
  @Input() min?: number;
  @Input() max?: number;
  @Input() prefix = '';
  @Input() suffix = '';

  @Output() valueChange = new EventEmitter<number>();

  unmaskedValue = 0;
  maskedValue = '';
  id = uuid();

  @ViewChild('input') inputElement!: ElementRef<HTMLInputElement>;

  ngOnChanges(changes: SimpleChanges): void {
    if (changes['value']?.currentValue !== undefined) {
      const value =
        (changes['value']?.currentValue ?? 0) *
        Math.pow(10, this.decimalPlaces);

      this.unmaskedValue = Number(value.toFixed(this.decimalPlaces));
      this.maskedValue = this.maskValue(this.unmaskedValue);

      this.updateInputValue();
    }
  }

  validateValue(value: number): boolean {
    if (typeof this.min === 'number' && value < this.min) return false;
    if (typeof this.max === 'number' && value > this.max) return false;

    return true;
  }

  handleKeyDown(ev: Event) {
    const keyboardEvent = ev as KeyboardEvent;
    const key = keyboardEvent.key;
    const numberInserted = Number(key);

    keyboardEvent.preventDefault();

    //To prevent bugs in mobile devices
    if (key.toLowerCase() === 'unidentified') return;

    if (isNaN(numberInserted)) {
      this.handleKeyFunctions(key.toLowerCase());
      return;
    }

    const newUnmaskedValue = parseFloat(
      `${this.unmaskedValue}${numberInserted}`,
    );
    const maskedNewValue = this.maskValue(newUnmaskedValue);

    if (!this.validateValue(this.maskedToNumber(maskedNewValue))) return;

    this.unmaskedValue = newUnmaskedValue;
    this.maskedValue = maskedNewValue;
    this.emitValueChange();
  }

  handleKeyFunctions(key: string) {
    const keysFunctions: {
      [key: string]: () => void;
    } = {
      backspace: this.removeLastDigit,
    };

    const functionToExecute = keysFunctions[key]?.bind(this);
    functionToExecute?.();
  }

  removeLastDigit() {
    let newValue = parseFloat(`${this.unmaskedValue}`.slice(0, -1));
    if (isNaN(newValue)) newValue = this.min ? Math.max(this.min, 0) : 0;

    this.unmaskedValue = newValue;
    this.maskedValue = this.maskValue(this.unmaskedValue);
    this.emitValueChange();
  }

  formatValueToDecimal(value: number): string {
    if (!Number.isInteger(value))
      return value.toString().replace('.', this.decimalSeparator);

    const decimalValue = (value / Math.pow(10, this.decimalPlaces)).toFixed(
      this.decimalPlaces,
    );
    const formatedValue = decimalValue.replace('.', this.decimalSeparator);

    return formatedValue;
  }

  maskValue(value: number): string {
    const formatedValue = this.formatValueToDecimal(value);

    return `${this.prefix}${formatedValue}${this.suffix}`;
  }

  maskedToNumber(maskedValue: string): number {
    const value = maskedValue
      .replace(this.prefix, '')
      .replace(this.suffix, '')
      .replace(this.decimalSeparator, '.');
    return parseFloat(value);
  }

  emitValueChange() {
    const number = this.maskedToNumber(this.maskedValue);
    this.valueChange.emit(number);
  }

  updateInputValue() {
    if (!this.input) return;

    //To prevent bugs in mobile devices
    this.input.nativeElement.value = this.maskValue(this.unmaskedValue);
  }

  handleButtonClick(buttonType: 'increase' | 'decrease') {
    const step = this.step * Math.pow(10, this.decimalPlaces);

    const buttonFunctions: {
      [key: string]: () => void;
    } = {
      increase: () => {
        const increased = this.unmaskedValue + step;
        const masked = this.maskValue(increased);
        const number = this.maskedToNumber(masked);

        if (!this.validateValue(number)) return;

        this.unmaskedValue = increased;
        this.maskedValue = masked;
        this.emitValueChange();
      },
      decrease: () => {
        const decreased = this.unmaskedValue - step;
        const masked = this.maskValue(decreased);
        const number = this.maskedToNumber(masked);

        if (!this.validateValue(number)) return;

        this.unmaskedValue = decreased;
        this.maskedValue = masked;
        this.emitValueChange();
      },
    };

    const functionToExecute = buttonFunctions[buttonType]?.bind(this);
    functionToExecute?.();
  }
}
