import { Result } from "@repo/rdx-utils";
import { z } from "zod/v4";
import { translateZodValidationError } from "../helpers";
import { ValueObject } from "./value-object";

const DEFAULT_SCALE = 2;

const DEFAULT_MIN_VALUE = 0;
const DEFAULT_MAX_VALUE = 100;

const DEFAULT_MIN_SCALE = 0;
const DEFAULT_MAX_SCALE = 2;

export interface PercentageProps {
  value: number;
  scale: number;
}

export class Percentage extends ValueObject<PercentageProps> {
  static DEFAULT_SCALE = DEFAULT_SCALE;
  static MIN_VALUE = DEFAULT_MIN_VALUE;
  static MAX_VALUE = DEFAULT_MAX_VALUE;

  static MIN_SCALE = DEFAULT_MIN_SCALE;
  static MAX_SCALE = DEFAULT_MAX_SCALE;

  protected static validate(values: PercentageProps) {
    const schema = z.object({
      value: z.number().int().min(Percentage.MIN_VALUE, "La cantidad no puede ser negativa."),
      scale: z
        .number()
        .int()
        .min(Percentage.MIN_SCALE)
        .max(
          Percentage.MAX_SCALE,
          `La escala debe estar entre ${Percentage.MIN_SCALE} y ${Percentage.MAX_SCALE}.`
        ),
    });

    return schema.safeParse(values);
  }

  static create(props: { value: number; scale?: number }): Result<Percentage> {
    const { value, scale = Percentage.DEFAULT_SCALE } = props;

    const valueIsValid = Percentage.validate({ value, scale });

    if (!valueIsValid.success) {
      return Result.fail(
        translateZodValidationError("Percentage creation failed", valueIsValid.error)
      );
    }

    // Cálculo del valor real del porcentaje
    const realValue = value / 10 ** scale; // ** => Math.pow

    // Validación de rango
    if (realValue > Percentage.MAX_VALUE) {
      return Result.fail(new Error("El porcentaje no puede ser mayor a 100%."));
    }

    return Result.ok(new Percentage({ value, scale }));
  }

  get value(): number {
    return this.props.value;
  }

  get scale(): number {
    return this.props.scale;
  }

  getProps(): PercentageProps {
    return this.props;
  }

  toPrimitive() {
    return this.getProps();
  }

  toNumber(): number {
    return this.value / 10 ** this.scale; // ** => Math.pow
  }

  toString(): string {
    return `${this.toNumber().toFixed(this.scale)}%`;
  }

  toObjectString() {
    return {
      value: String(this.value),
      scale: String(this.scale),
    };
  }

  format(locale: string): string {
    const scale = this.scale;

    return this.toNumber().toLocaleString(locale, {
      style: "percent",
      minimumFractionDigits: scale,
      maximumSignificantDigits: scale,
    });
  }
}
