Structures_AchievementBuilder.js

'user strict'
const { ErrorCodes } = require('../Errors/ErrorCodes')
const { capitalize } = require('../Utils/Capitalize')
const { AchievementType, AchievementStatus } = require('../Interfaces')
const { isNotPositiveInteger } = require('../Utils/ValidateInteger')
const { InvalidDataArgument, BuilderInvalidNumber, AchievementInvalidProgress, InvalidValue } = require('../Errors/LME')

/** @typedef {import('../../typings').AchievementData} AchievementData */
/** @typedef {import('../../typings').Achievement} Achievement */

/**
 * Represents an achievement that can be used to store in the guild or user.
 */
class AchievementBuilder {
  /** @type {AchievementData} - Data of the achievement. */
  data

  /**
   * @param {AchievementData} [data] - Data to build the achievement
   */
  constructor (data = {}) {
    if (typeof data !== 'object') throw new InvalidDataArgument(ErrorCodes.InvalidDataArgument)

    this.data = {
      status: AchievementStatus.Locked
    }

    // Call the setter methods to set the data.
    for (const [key, value] of Object.entries(data).filter(([keyName, _]) => keyName !== 'status')) this[`set${capitalize(key)}`](value)
  }

  /**
   * Sets the name of the achievement.
   * @param {String} name - Name of the achievement
   * @returns {AchievementBuilder}
   */
  setName (name) {
    if (typeof name !== 'string') throw new InvalidValue(ErrorCodes.InvalidValue, 'name', 'a string')

    this.data.name = name

    return this
  }

  /**
   * Sets the description of the achievement.
   * @param {String} description - Description of the achievement
   * @returns {AchievementBuilder}
   */
  setDescription (description) {
    if (typeof description !== 'string') throw new InvalidValue(ErrorCodes.InvalidValue, 'description', 'a string')

    this.data.description = description

    return this
  }

  /**
   * Sets the thumbnail of the achievement.
   * @param {String} thumbnail - Thumbnail of the achievement
   * @returns {AchievementBuilder}
   */
  setThumbnail (thumbnail) {
    if (typeof thumbnail !== 'string') throw new InvalidValue(ErrorCodes.InvalidValue, 'thumbnail', 'a string')

    this.data.thumbnail = thumbnail

    return this
  }

  /**
   * Sets the reward of the achievement.
   * @param {Number} reward - Reward of the achievement
   * @returns {AchievementBuilder}
   */
  setReward (reward) {
    if (isNotPositiveInteger(reward)) throw new BuilderInvalidNumber(ErrorCodes.BuilderInvalidNumber, 'reward')

    this.data.reward = reward

    return this
  }

  /**
   * Sets the type of the achievement.
   * @param {AchievementType} type - Type of the achievement
   * @returns {AchievementBuilder}
   */
  setType (type) {
    if (!Object.values(AchievementType).includes(type)) throw new InvalidValue(ErrorCodes.InvalidValue, 'type', 'a AchievementType')

    this.data.type = type

    return this
  }

  /**
   * Sets the progress of the achievement.
   * @param {Number[]} progress - Progress of the achievement
   * @returns {AchievementBuilder}
   */
  setProgress (progress) {
    if (!progress || !Array.isArray(progress) || progress.length !== 2) throw new AchievementInvalidProgress(ErrorCodes.AchievementInvalidProgress)

    if (progress.some(v => typeof v !== 'number' || v < 0 || isNaN(v) || !isFinite(v) || v % 1 !== 0)) throw new AchievementInvalidProgress(ErrorCodes.AchievementNegativeProgress)

    if (progress[0] > progress[1]) throw new AchievementInvalidProgress(ErrorCodes.AchievementGraterProgress)

    this.data.progress = progress

    return this
  }

  /**
   * Returns the JSON representation of the achievement.
   * @returns {AchievementData}
   */
  toJSON () {
    return {
      name: this.data.name,
      description: this.data.description,
      thumbnail: this.data.thumbnail,
      type: this.data.type,
      reward: this.data.reward,
      progress: this.data.progress
    }
  }
}

module.exports = { AchievementBuilder }