import {
  INullableValueObjectOptions,
  NullableValueObject,
  Result,
  RuleValidator,
} from "@shared/contexts";
import { NullOr } from "@shared/utilities";
import Joi from "joi";

export interface IArticleIdentifierOptions extends INullableValueObjectOptions {}

export class ArticleIdentifier extends NullableValueObject<number> {
  protected static validate(
    value: NullOr<number | string>,
    options: IArticleIdentifierOptions = {}
  ) {
    const ruleNull = RuleValidator.RULE_ALLOW_NULL_OR_UNDEFINED.default(null);

    const ruleEmpty = RuleValidator.RULE_ALLOW_EMPTY.default(null);

    const ruleNumber = RuleValidator.RULE_IS_TYPE_NUMBER.label(
      options.label ? options.label : "ArticleIdentifier"
    );

    const ruleString = RuleValidator.RULE_IS_TYPE_STRING.regex(/^[-]?\d+$/).label(
      options.label ? options.label : "ArticleIdentifier"
    );

    const rules = Joi.alternatives(ruleNull, ruleEmpty, ruleNumber, ruleString);

    return RuleValidator.validate<NullOr<number>>(rules, value);
  }

  private static sanitize(value: NullOr<number | string>): NullOr<number> {
    return value ? Number(value) : null;
  }

  public static create(value: NullOr<number | string>, options: IArticleIdentifierOptions = {}) {
    const _options = {
      label: "ArticleIdentifier",
      ...options,
    };

    const validationResult = ArticleIdentifier.validate(value, _options);

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

    let _value: NullOr<number> = null;

    if (typeof validationResult.object === "string") {
      _value = parseInt(validationResult.object, 10);
    } else {
      _value = validationResult.object;
    }

    return Result.ok<ArticleIdentifier>(new ArticleIdentifier(ArticleIdentifier.sanitize(_value)));
  }

  public toNumber(): number {
    return this.isNull() ? 0 : Number(this.value);
  }

  public toString(): string {
    return this.isNull() ? "" : String(this.value);
  }

  public toPrimitive(): number | null {
    return this.isNull() ? null : this.toNumber();
  }

  public increment(amount: number = 1) {
    const validationResult = ArticleIdentifier.validate(amount);

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

    if (this.value === null) {
      return ArticleIdentifier.create(amount);
    }

    return ArticleIdentifier.create(this.value + amount);
  }

  public decrement(amount: number = 1) {
    const validationResult = ArticleIdentifier.validate(amount);

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

    if (this.value === null) {
      return ArticleIdentifier.create(amount);
    }

    return ArticleIdentifier.create(this.value - amount);
  }
}
