import { AggregateRoot, UniqueID } from "@repo/rdx-ddd";
import { Result } from "@repo/rdx-utils";
import { DocType } from "../value-objects";

/**
 *
 *
 * const num1 = DocumentNumber.create(7, "2025", "{series}/{number:0003}");
 * console.log(num1.isSuccess, num1.data.getFormatted()); // true, "2025/007"
 *
 * const num2 = DocumentNumber.create(7, undefined, "{series}/{number:0003}");
 * console.log(num2.isFailure, num2.error.message); // true, error porque falta {series}
 *
 *. Número simple sin padding
 * DocumentNumber.create(123, undefined, "{number}")
 * // → "123"
 *
 * 2. Número con padding
 * DocumentNumber.create(7, undefined, "{number:0005}")
 * // → "00007"
 *
 * 3. Serie + número con padding
 * DocumentNumber.create(45, "2025", "{series}/{number:0003}")
 * // → "2025/045"
 *
 * 4. Serie + número sin padding
 * DocumentNumber.create(987, "Sucursal-01", "{series}-{number}")
 * // → "Sucursal-01-987"
 *
 * 5. Año + mes + día + número
 *
 * (Suponiendo fecha 2025-09-26 y rawValue=12)
 *
 * DocumentNumber.create(12, "2025", "{year}/{month}/{day}-{number:0002}")
 * // → "2025/09/26-12"
 *
 * 6. Escapes de llaves
 * DocumentNumber.create(33, "2025", "Factura N° {{{number}}}")
 * // → "Factura N° {33}"
 *
 * 7. Patrón sin número (no muy común, pero válido)
 * DocumentNumber.create(77, "2025", "SERIE-{series}")
 * // → "SERIE-2025"
 *
 */

export interface DocNumberProps {
  companyId: UniqueID;
  year: number;
  docType: DocType; // INVOICE, QUOTATION, DELIVERY_NOTE, PAYMENT...
  series: string; // opcional: "2025", "Sucursal-01"
  currentValue: number;
  formatPattern: string; // ej: "{year}/{number:000000}"
}

export class DocNumber extends AggregateRoot<DocNumberProps> {
  protected _formatted!: string;

  static create(props: DocNumberProps, id?: UniqueID): Result<DocNumber, Error> {
    const docNumber = new DocNumber(props, id);

    // Reglas de negocio / validaciones
    // ...
    // ...

    // 🔹 Disparar evento de dominio "CustomerAuthenticatedEvent"
    // ...
    // ...

    return Result.ok(docNumber);
  }

  protected constructor(props: DocNumberProps, id?: UniqueID) {
    super(props, id);

    this._formatted = this.applyFormat();
  }

  private applyFormat(): string {
    // 🔹 Preprocesar escapes de llaves
    const pattern = this.props.formatPattern
      .replace(/{{/g, "__LBRACE__")
      .replace(/}}/g, "__RBRACE__");

    const date = new Date();

    // 🔹 Expresión regular para tokens {token} o {token:0000}
    const tokenRegex = /{([a-zA-Z]+)(?::([0]+))?}/g;

    const formatted = pattern.replace(tokenRegex, (_match, token, pad) => {
      const name = token.toLowerCase();

      switch (name) {
        case "number": {
          const raw = String(this.props.currentValue);
          if (pad) {
            // validar que el especificador son sólo ceros
            if (!/^0+$/.test(pad)) {
              throw new Error(
                `DocumentNumber: especificador de padding inválido en patrón '${pad}'`
              );
            }
            return raw.padStart(pad.length, "0");
          }
          return raw;
        }

        case "series": {
          if (!this.props.series) {
            throw new Error(
              "DocumentNumber: el patrón requiere {series} pero no se proporcionó serie"
            );
          }
          return this.props.series;
        }

        case "year":
          return String(date.getFullYear());

        case "month":
          return String(date.getMonth() + 1).padStart(2, "0");

        case "day":
          return String(date.getDate()).padStart(2, "0");

        default:
          throw new Error(`DocumentNumber: token desconocido {${token}}`);
      }
    });

    // 🔹 Restaurar escapes
    return formatted.replace(/LBRACE/g, "{").replace(/RBRACE/g, "}");
  }

  getFormatted(): string {
    return this._formatted;
  }

  public get docType(): DocType {
    return this.props.docType;
  }

  public get series(): string {
    return this.props.series;
  }

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