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

export interface IUiInput {
  readonly constraint?: string;
  readonly description?: string;
  readonly key?: string;
  readonly label?: string;
  readonly maxValue?: string;
  readonly minValue?: string;
  readonly options?: IUiInputOptions;
  readonly placeholder?: string;
  readonly target?: string;
  readonly type: UiInputType;
  readonly value?: string;
  readonly selected?: boolean;
}

export class UiInput implements IUiInput {
  //#region implements IUiFilterInputDTO
  private _constraint?: string;
  get constraint() {
    return this._constraint;
  }

  private _description?: string;
  get description() {
    return this._description;
  }

  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 _options?: UiInputOptions;
  get options() {
    return this._options;
  }

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

  private _target?: string;
  get target() {
    return this._target;
  }

  private _type = UiInputType.NotSet;
  get type() {
    return this._type;
  }

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

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

    return this._selected;
  }
  //#endregion

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

  private _errors: Record<string, string> | null;
  get errors() {
    return this._errors;
  }

  constructor(public readonly parent?: UiInputGroup) {}

  toStringValue() {
    switch (this.type) {
      case UiInputType.Bool:
      case UiInputType.InputSelector:
        return this.boolToStringValue();
      case UiInputType.Text:
        return this.textToStringValue();
      case UiInputType.DateRange:
        return this.dateRangeToStringValue();
      case UiInputType.IntegerRange:
      case UiInputType.NumberRange:
        return this.rangeToStringValue();
      case UiInputType.TriState:
        return this.triStateToString();
    }
  }

  fromStringValue(key: string, value: string) {
    if (this.key === key) {
      if (this.type === UiInputType.Text) {
        this.setValue(value);
      } else {
        if (this.options) {
          this.options?.values?.forEach((option) => {
            option.trySetFromValue(value);
          });
        } else {
          this.setValue(value);
        }
      }
    }
  }

  private boolToStringValue() {
    if (this.hasOptions()) {
      return this.getSelectedOptions()
        .map((options) => options.value)
        .join(';');
    }
    return this.selected ? String(this.selected) : undefined;
  }

  private dateRangeToStringValue() {
    if (this.hasOptions()) {
      const selected = this.getSelectedOptions();

      if (selected?.length) {
        const range = selected.reduce((res, option) => {
          const cp: Record<string, string> = { ...res };
          cp[option.key] = option.value;
          return cp;
        }, {});

        if (range['start'] && range['stop']) {
          return `"${range['start']}"-"${range['stop']}"`;
        } else if (range['start']) {
          return `"${range['start']}"-`;
        } else if (range['stop']) {
          return `-"${range['stop']}"`;
        }
      }
    }

    return this.selected ? this.value : undefined;
  }

  private rangeToStringValue() {
    if (this.hasOptions()) {
      const selected = this.getSelectedOptions();

      if (selected?.length) {
        const range = selected.reduce((res, option) => {
          const cp: Record<string, string> = { ...res };
          cp[option.key] = option.value;
          return cp;
        }, {});

        if (range['start'] && range['stop']) {
          return `${range['start']}-${range['stop']}`;
        } else if (range['start']) {
          return `${range['start']}-`;
        } else if (range['stop']) {
          return `-${range['stop']}`;
        }
      }
    }

    return this.selected ? this.value : undefined;
  }

  private textToStringValue() {
    if (this.hasOptions()) {
      return this.getSelectedOptions()
        .map((v) => v.value)
        .join(';');
    }

    return this.selected ? this.value : undefined;
  }

  private triStateToString() {
    if (this.hasOptions()) {
      const selected = this.getSelectedOptions()?.map((options) => options.value);
      const unselected = this.getUnselectedOptions()?.map((options) => `!${options.value}`);
      return [...selected, ...unselected].join(';');
    }
  }

  hasOptions() {
    return !!this.options?.values?.length || false;
  }

  getSelectedOptions() {
    return this.options?.getSelectedOptions() || [];
  }

  getUnselectedOptions() {
    return this.options?.getUnselectedOptions() || [];
  }

  hasSelectedOptions() {
    return this.getSelectedOptions().length > 0;
  }

  hasUnselectedOptions() {
    return this.getUnselectedOptions().length > 0;
  }

  setValue(value: string, { emitEvent } = { emitEvent: true }) {
    if (this.value !== value) {
      this._value = value;

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

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

      this.options?.values?.forEach((f) => f.setSelected(value, { emitEvent }));

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

  toObject(): IUiInput {
    return {
      type: this.type,
      constraint: this.constraint,
      description: this.description,
      key: this.key,
      label: this.label,
      maxValue: this.maxValue,
      minValue: this.minValue,
      options: this.options?.toObject(),
      placeholder: this.placeholder,
      selected: this.selected,
      target: this.target,
      value: this.value,
    };
  }

  static create(input: IUiInput, parent?: UiInputGroup) {
    const target = new UiInput(parent);

    target._constraint = input?.constraint;
    target._description = input?.description;
    target._key = input?.key;
    target._label = input?.label;
    target._maxValue = input?.maxValue;
    target._minValue = input?.minValue;
    target._options = input?.options ? UiInputOptions.create(input.options, target) : undefined;
    target._placeholder = input?.placeholder;
    target._target = input?.target;
    target._type = input?.type;
    target._value = input?.value;
    target._selected = input?.selected;
    return target;
  }
}
