<?php
/**
 * BookiFlex Core
 *
 * @package    BookiFlex
 * @subpackage Core
 * @author     BookiFlex
 * @copyright  Copyright (c) 2025 BookiFlex
 * @license    GPL-2.0-or-later
 *
 * @link       https://bookiflex.com
 */

declare(strict_types=1);

namespace BookiFlex\Core\ValueObject;

use BookiFlex\Core\Exception\MoneyCalculationException;
use Brick\Math\Exception\MathException;
use Brick\Money\Exception\MoneyMismatchException;
use Brick\Money\Money;
use Vaened\PriceEngine\Summary as TotalSummary;
use Vaened\Support\Types\SecureList;

defined( 'ABSPATH' ) || exit;

final class Totalizer extends SecureList
{
    private string $currency;

    public function __construct(iterable $items, string $currency)
    {
        parent::__construct($items);
        $this->currency = $currency;
    }

    public static function of(iterable $summaries, string $currency): self
    {
        return new self($summaries, $currency);
    }

    /**
     * @throws MoneyCalculationException
     */
    public function summary(Money $additionalCharges = null, Money $additionalDiscounts = null): Summary
    {
        try {
            $subtotal  = $this->subtotal();
            $taxes     = $this->totalTaxes();
            $charges   = $this->totalCharges()->plus(null === $additionalCharges ? 0 : $additionalCharges);
            $discounts = $this->totalDiscounts()->plus(null === $additionalDiscounts ? 0 : $additionalDiscounts);

            return new Summary(
                subtotal      : $subtotal,
                totalTaxes    : $taxes,
                totalCharges  : $charges,
                totalDiscounts: $discounts,
                total         : $subtotal
                    ->plus($taxes)
                    ->plus($charges)
                    ->minus($discounts),
            );
        } catch (MoneyMismatchException|MathException $e) {
            throw new MoneyCalculationException(
                sprintf(
                    'Failed to calculate money: %s',
                    $e->getMessage()
                ),
                0,
                $e,
                ['error' => $e->getMessage()]
            );
        }
    }

    public function total(): Money
    {
        return $this->sum(
            static fn (TotalSummary $summary) => $summary->total()
        );
    }

    private function subtotal(): Money
    {
        return $this->sum(
            static fn (TotalSummary $summary) => $summary->subtotal()->net()
        );
    }

    private function totalTaxes(): Money
    {
        return $this->sum(
            static fn (TotalSummary $summary) => $summary->taxes()->total()
        );
    }

    private function totalCharges(): Money
    {
        return $this->sum(
            static fn (TotalSummary $summary) => $summary->charges()->total()
        );
    }

    private function totalDiscounts(): Money
    {
        return $this->sum(
            static fn (TotalSummary $summary) => $summary->discounts()->total()
        );
    }

    private function sum(callable $callback): Money
    {
        return $this->reduce(
            static fn (?Money $money, TotalSummary $summary) => null === $money
                ? $callback($summary)
                : $money->plus($callback($summary))
        ) ?? $this->zero();
    }

    private function zero(): Money
    {
        return Money::zero($this->currency);
    }

    public static function type(): string
    {
        return TotalSummary::class;
    }
}
