<?php

declare(strict_types=1);

namespace BookiFlex\Extensions\Taxation\Service;

use BookiFlex\Core\Contract\Adjustment\AdjustableInterface;
use BookiFlex\Core\Contract\Taxation\TaxCalculatorInterface;
use BookiFlex\Core\Contract\Taxation\TaxableInterface;
use BookiFlex\Core\Contract\Taxation\TaxRuleProviderInterface;
use BookiFlex\Core\Model\Reservation\Adjustment;
use BookiFlex\Extensions\Taxation\Models\TaxRule;

defined( 'ABSPATH' ) || exit;

/**
 * Calculate tax amounts.
 * Add tax adjustments via AdjustableInterface.
 */
class TaxCalculator implements TaxCalculatorInterface
{
    public const SERVICE_NAME = 'taxation.tax_calculator';

    public function __construct(
        private readonly TaxRuleProviderInterface $ruleProvider
    ) {
    }

    /**
     * @param TaxableInterface[]|AdjustableInterface[] $items
     */
    public function applyTaxes(array $items): void
    {
        /** @var TaxableInterface[][]|AdjustableInterface[][] $types */
        $types        = $this->sortItemsByType($items);
        $groupedTaxes = $this->findAndGroupTaxes($types);

        // Apply taxes to objects
        foreach ($types as $type => $typeItems) {
            if (!isset($groupedTaxes[$type])) {
                continue;
            }

            foreach ($typeItems as $item) {
                /** @var TaxRule $tax */
                foreach ($groupedTaxes[$type] as $tax) {
                    if (!$tax->appliesTo($item)) {
                        continue;
                    }

                    $amount     = $tax->calculate($item);
                    $adjustment = new Adjustment(
                        $tax->getName(),
                        $tax->getCode(),
                        $amount,
                        $tax->getType(),
                        $tax->getStrategy(),
                        $tax->getMode(),
                        self::SERVICE_NAME,
                        $tax->isRefundable()
                    );
                    $item->addAdjustment($adjustment);
                }
            }
        }
    }

    private function sortItemsByType(array $items): array
    {
        $types = [];
        foreach ($items as $item) {
            if ($item instanceof TaxableInterface) {
                $types[$item->getTaxableType()][] = $item;
            }
        }

        return $types;
    }

    private function findAndGroupTaxes(array $types): array
    {
        $allTaxes     = $this->ruleProvider->getTaxesByEntityTypes(array_keys($types));
        $groupedTaxes = [];
        foreach ($allTaxes as $tax) {
            $groupedTaxes[$tax->getEntityType()][] = $tax;
        }

        return $groupedTaxes;
    }
}
