import { RuleValidator } from "../../RuleValidator";
import { Result } from "../Result";

import Joi from "joi";

import { UndefinedOr } from "../../../../../utilities";
import { LANGUAGES_LIST } from "../../../../../utilities/languages_data";
import { DomainError, handleDomainError } from "../../errors";
import { INullableValueObjectOptions, NullableValueObject } from "../NullableValueObject";

export interface ILanguage {
  code: string;
  name: string;
  nativeName: string;
}

export interface ILanguageOptions extends INullableValueObjectOptions {}

export class Language extends NullableValueObject<ILanguage> {
  public static readonly DEFAULT_LANGUAGE_CODE = "es";
  public static readonly LANGUAGES = LANGUAGES_LIST;

  protected static validate(value: UndefinedOr<string>, options: INullableValueObjectOptions) {
    const rule = Joi.alternatives(
      RuleValidator.RULE_ALLOW_EMPTY.default(""),
      Joi.string()
        .lowercase()
        .valid(...Object.keys(LANGUAGES_LIST))
        .label(String(options.label))
    );

    return RuleValidator.validate<string>(rule, value);
  }

  private static sanitize(value: UndefinedOr<string>) {
    return value ? String(value).toLowerCase() : undefined;
  }

  public static createFromCode(languageCode: string, options: ILanguageOptions = {}) {
    const _options = {
      ...options,
      label: options.label ? options.label : "lang_code",
    };

    const validationResult = Language.validate(languageCode, _options);

    if (validationResult.isFailure) {
      return Result.fail(
        handleDomainError(DomainError.INVALID_INPUT_DATA, validationResult.error.message, _options)
      );
    }

    const code = Language.sanitize(validationResult.object);

    const props = code
      ? {
          ...LANGUAGES_LIST[validationResult.object],
          code,
        }
      : undefined;

    return Result.ok(new Language(props));
  }

  public static createDefaultCode() {
    return Language.createFromCode(this.DEFAULT_LANGUAGE_CODE);
  }

  get name(): string {
    return this.props ? String(this.props.name) : "";
  }

  get nativeName(): string {
    return this.props ? String(this.props.nativeName) : "";
  }

  get code(): string {
    return this.props ? String(this.props.code) : "";
  }

  public isEmpty(): boolean {
    return this.isNull() || this.props === undefined;
  }

  public toString = (): string => {
    return this.code;
  };

  public toPrimitive(): string {
    return this.toString();
  }
}
