import { IOrderCollection, IOrderProps, Order, OrderCollection } from ".";
import { UndefinedOr } from "../../../../../../utilities";
import { RuleValidator } from "../../../RuleValidator";
import { Result } from "../../Result";
import { ResultCollection } from "../../ResultCollection";
import { StringValueObject } from "../../StringValueObject";

export interface IOrderCriteria {
  getOrderCollection(): IOrderCollection;
  toJSON(): string;
  toString(): string;
  toObject(): Record<string, any>;
}

export class OrderCriteria extends StringValueObject implements IOrderCriteria {
  private static parseOrderString = (
    orderString: UndefinedOr<string>
  ): string[][] => {
    // eslint-disable-next-line no-useless-escape
    //return OrderString.match(/(.+)(([\[])([\w]+)([\]]))([\w\W]+)*/i);

    const result: any[] = [];

    if (orderString) {
      const regex = /([+-]?)([\w_.]+)/gi;
      let matches: any;
      while ((matches = regex.exec(orderString)) !== null) {
        result.push([...matches]);
      }
    }

    return result;
  };

  protected static validate(value: UndefinedOr<string>) {
    let orderString = value;

    if (
      RuleValidator.validate(
        RuleValidator.RULE_NOT_NULL_OR_UNDEFINED,
        orderString
      ).isFailure
    ) {
      return Result.ok(value);
    }

    const stringOrError = RuleValidator.validate<string>(
      RuleValidator.RULE_IS_TYPE_STRING,
      orderString
    );

    if (stringOrError.isFailure) {
      return stringOrError;
    }

    orderString = String(value);

    const orderOrErrors = new ResultCollection();

    this.parseOrderString(orderString).forEach((token: string[]) => {
      const orderOrError = Order.create({
        type: token[1] || "+",
        field: String(token[2]),
      });

      orderOrErrors.add(orderOrError);
    });

    if (orderOrErrors.hasSomeFaultyResult()) {
      return orderOrErrors.getFirstFaultyResult() as Result<never, Error>;
    }

    return Result.ok<string>(orderString);
  }

  public static create(value: UndefinedOr<string>) {
    const validatedProps = this.validate(value);

    if (validatedProps.isFailure) {
      return Result.fail(validatedProps.error);
    }

    return Result.ok<OrderCriteria>(new OrderCriteria(validatedProps.object));
  }

  public getOrderCollection(): IOrderCollection {
    return this.buildOrderCollection();
  }

  public toJSON(): string {
    return JSON.stringify(this.toObject());
  }

  public toPrimitive(): string {
    throw new Error("NOT IMPLEMENT OrderCriteria.toPrimitive()");
  }

  public toObject(): Record<string, any> {
    return this.getOrderCollection().toObject();
  }

  protected buildOrderCollection(): IOrderCollection {
    let _orders: IOrderProps[] | undefined = undefined;

    if (!this.isEmpty()) {
      _orders = OrderCriteria.parseOrderString(this.props).map(
        (token: string[]) => {
          /** TOKEN
           * [1] => type + / - (opcional)
           * [2] => field / model.field
           */

          const type = token[1] ? String(token[1]) : "+";

          const orderOrError = Order.create({
            type,
            field: String(token[2]),
          });

          if (orderOrError.isFailure) {
            throw new Error(`Order '${token.join()}' is not valid`);
          }

          return orderOrError.object;
        }
      );
    }

    return (
      _orders === undefined
        ? OrderCollection.createEmpty()
        : OrderCollection.create(_orders as Order[])
    ) as IOrderCollection;
  }
}
