import { Subject } from 'rxjs';
import { InputGroup } from './input-group';
import { IInputOptions, InputOptions } from './input-options';
import { InputType } from './input-type.enum';

export interface IInput {
  readonly constraint?: string;
  readonly description?: string;
  readonly key?: string;
  readonly label?: string;
  readonly maxValue?: string;
  readonly minValue?: string;
  readonly options?: IInputOptions;
  readonly placeholder?: string;
  readonly target?: string;
  readonly type: InputType;
  readonly value?: string;
  readonly selected?: boolean;
}

export class FilterInput implements IInput {
  //#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;
  }

  get subKeys() {
    return this._options?.values?.filter((value) => !!value.key).map((value) => value.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?: InputOptions;
  get options() {
    return this._options;
  }

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

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

  private _type = InputType.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 IInput)[]; target: FilterInput }>();

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

  constructor(public readonly parent?: InputGroup) {}

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

  fromStringValue(key: string, value: string) {
    if (this.key === key) {
      if (this.type === InputType.Text || this.type === InputType.Integer) {
        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 ?? options.key)
        .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 ? String(this.value) : undefined;
  }

  private triStateToString() {
    if (this.hasOptions()) {
      const selected = this.getSelectedOptions()?.map((options) => options.value ?? options.key);
      const unselected = this.getUnselectedOptions()?.map((options) => `!${options.value ?? options.key}`);
      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;
  }

  hasFilterInputOptionsSelected() {
    return this.hasSelectedOptions() || this.hasUnselectedOptions() || this.selected;
  }

  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(): IInput {
    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: IInput, parent?: InputGroup) {
    const target = new FilterInput(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 ? InputOptions.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;
  }
}
