import { Subject } from 'rxjs';
import { UiInputOptions } from './ui-input-options';
import { UiInputType } from './ui-input-type.enum';

export interface IUiOption {
  description?: string;
  enabled?: boolean;
  key?: string;
  label?: string;
  maxValue?: string;
  minValue?: string;
  placeholder?: string;
  selected?: boolean;
  value?: string;
  values?: Array<IUiOption>;
  expanded?: boolean;
}

export class UiOption implements IUiOption {
  //#region implements IUiFilterOptionDTO
  private _description?: string;
  get description() {
    return this._description;
  }

  private _enabled?: boolean;
  get enabled() {
    return this._enabled;
  }

  private _key?: string;
  get key() {
    return this._key;
  }

  private _label?: string;
  get label() {
    return this._label;
  }

  private _maxValue?: string;
  get maxValue() {
    return this._maxValue;
  }

  private _minValue?: string;
  get minValue() {
    return this._minValue;
  }

  private _placeholder?: string;
  get placeholder() {
    return this._placeholder;
  }

  private _selected?: boolean;
  get selected() {
    if (this.values?.length) {
      return this.values.filter((f) => f.selected).length === this.values.length;
    }

    return this._selected;
  }

  private _value?: string;
  get value() {
    return this._value;
  }

  private _values?: Array<UiOption>;
  get values() {
    return this._values;
  }

  private _expanded?: boolean;
  get expanded() {
    return this._expanded;
  }
  //#endregion

  readonly changes = new Subject<{ keys: (keyof IUiOption)[]; target: UiOption }>();

  constructor(public readonly parent?: UiOption | UiInputOptions) {}

  getParentInput() {
    return this.getParentInputOptions()?.parent;
  }

  getParentInputOptions() {
    let parent = this.parent;
    while (!!parent) {
      if (parent instanceof UiInputOptions) {
        return parent;
      }
      parent = parent.parent;
    }
  }

  hasChildren() {
    return !!this.values?.length;
  }

  getSelectedOptions(): UiOption[] {
    const selected: UiOption[] = [];

    if (this.selected) {
      selected.push(this);
    }

    const selectedChildren = this.values?.map((f) => f.getSelectedOptions()).reduce((agg, options) => [...agg, ...options], []);

    if (selectedChildren?.length) {
      selected.push(...selectedChildren);
    }

    return selected;
  }

  getUnselectedOptions(): UiOption[] {
    if (this?.getParentInput()?.type === UiInputType.TriState && this.selected === false) {
      return [this];
    } else if (this.hasChildren()) {
      return this.values.map((f) => f.getUnselectedOptions()).reduce((agg, options) => [...agg, ...options], []);
    }

    return [];
  }

  setSelected(value: boolean, { emitEvent } = { emitEvent: true }) {
    this._selected = value;

    const type = this.getParentInput()?.type;
    if (!value && (type === UiInputType.DateRange || type === UiInputType.NumberRange || type === UiInputType.IntegerRange)) {
      this.setValue(undefined, { emitEvent: false });
    }

    this.values?.forEach((option) => option.setSelected(value));
    if (emitEvent) {
      this.changes.next({ keys: ['selected', 'value'], target: this });
    }
  }

  setExpanded(value: boolean, { emitEvent } = { emitEvent: true }) {
    if (this.expanded !== value) {
      this._expanded = value;
      if (emitEvent) {
        this.changes.next({ keys: ['expanded'], target: this });
      }
    }
  }

  setValue(value: string | Date, { emitEvent } = { emitEvent: true }) {
    if (value instanceof Date) {
      value = value?.toJSON();
    }

    if (this.value !== value) {
      this._value = value;

      this.setSelected(!!this.value, { emitEvent: false });

      if (emitEvent) {
        this.changes.next({ keys: ['value', 'selected'], target: this });
      }
    }
  }

  trySetFromValue(value: string) {
    const type = this.getParentInput()?.type;

    switch (type) {
      case UiInputType.Bool:
      case UiInputType.InputSelector:
        this.trySetBoolFromValue(value);
        break;
      case UiInputType.TriState:
        this.trySetTriStateFromValue(value);
        break;
      case UiInputType.NumberRange:
      case UiInputType.IntegerRange:
        this.trySetFromNumberRange(value);
        break;
      case UiInputType.DateRange:
        this.trySetFromDateRange(value);
        break;
    }
  }

  trySetFromNumberRange(value: string) {
    const split = value?.split('-');
    if (this.key === 'start') {
      this.setValue(split[0]);
    } else if (this.key === 'stop') {
      this.setValue(split[1]);
    }
  }

  trySetFromDateRange(value: string) {
    let split: string[] = [];

    if (value) {
      if (value?.includes('"-"')) {
        split = value.split('"-"');
      } else if (value?.includes('-"')) {
        split = value.split('-"');
      } else if (value?.includes('"-')) {
        split = value.split('"-');
      }
    }

    if (this.key === 'start') {
      if (split[0]) {
        split[0] = split[0].replace(new RegExp('"', 'g'), '');
      }
      this.setValue(split[0]);
    } else if (this.key === 'stop') {
      if (split[1]) {
        split[1] = split[1].replace(new RegExp('"', 'g'), '');
      }
      this.setValue(split[1]);
    }
  }

  trySetBoolFromValue(value: string) {
    const valueSplits = value?.split(';');
    if (valueSplits.includes(this.value)) {
      this.setSelected(true);
    } else {
      this.setSelected(undefined);
      this.values?.forEach((option) => option?.trySetBoolFromValue(value));
    }
  }

  trySetTriStateFromValue(value: string) {
    const valueSplits = value?.split(';');
    if (valueSplits.includes(this.value)) {
      this.setSelected(true);
    } else if (valueSplits.includes(`!${this.value}`)) {
      this.setSelected(false);
    } else {
      this.setSelected(undefined);
    }
  }

  validate(): string | null {
    const input = this.getParentInput();

    switch (input?.type) {
      // NumberRange
      case UiInputType.NumberRange:
      case UiInputType.IntegerRange:
        return this.validateNumber() || this.validateNumberRange();
    }

    return null;
  }

  validateNumber(): string | null {
    if (+this.value !== NaN) {
      if (+this.minValue !== NaN && +this.value < +this.minValue) {
        return `Der Wert darf nicht kleiner als ${this.minValue} sein.`;
      } else if (+this.maxValue !== NaN && +this.value > +this.maxValue) {
        return `Der Wert darf nicht größer als ${this.maxValue} sein.`;
      } else if (!!this.value && !/^[0-9]+$/.exec(this.value)) {
        return `Es werden nur ganzzahlige Werte akzeptiert.`;
      }
    }

    return null;
  }

  validateNumberRange(): string | null {
    if (+this.value !== NaN) {
      const start = this.parent.values.find((v) => v.key === 'start');
      const stop = this.parent.values.find((v) => v.key === 'stop');

      if (+start.value !== NaN && +stop.values !== NaN && +start.value > +stop.value) {
        return `Der Wert "${start.value}" darf nicht gößer sein als "${stop.value}"`;
      }
    }

    return null;
  }

  toObject(): IUiOption {
    return {
      description: this.description,
      enabled: this.enabled,
      expanded: this.expanded,
      key: this.key,
      label: this.label,
      maxValue: this.maxValue,
      minValue: this.minValue,
      placeholder: this.placeholder,
      selected: this.selected,
      value: this.value,
      values: this.values?.map((value) => value.toObject()),
    };
  }

  static create(option: IUiOption, parent?: UiOption | UiInputOptions) {
    const target = new UiOption(parent);

    target._description = option?.description;
    target._enabled = option?.enabled;
    target._key = option?.key;
    target._label = option?.label;
    target._maxValue = option?.maxValue;
    target._minValue = option?.minValue;
    target._placeholder = option?.placeholder;
    target._selected = option?.selected;
    target._value = option?.value;
    target._values = option?.values?.map((value) => UiOption.create(value, target));
    target._expanded = option?.expanded;

    return target;
  }
}
