<?php

namespace Licenzo\Casts;

use Licenzo\Framework\Contracts\Database\Eloquent\CastsAttributes;
use Licenzo\Framework\Database\Eloquent\Model;

/**
 * Currency-aware conversion between integer minor units (DB) and decimal float (PHP).
 *
 * Resolves the model's `currency` attribute to determine the correct ISO 4217
 * minor-unit exponent. Defaults to 2 (standard for USD, EUR, GBP, etc.).
 *
 * @implements CastsAttributes<float, float|int|string>
 */
class MoneyCast implements CastsAttributes
{
    /**
     * ISO 4217 currencies whose minor-unit exponent differs from the default (2).
     * Zero-decimal and three-decimal currencies only; all others default to 2.
     */
    private const MINOR_UNIT_MAP = [
        'BIF' => 0, 'CLP' => 0, 'DJF' => 0, 'GNF' => 0,
        'ISK' => 0, 'JPY' => 0, 'KMF' => 0, 'KRW' => 0,
        'PYG' => 0, 'RWF' => 0, 'UGX' => 0, 'VND' => 0,
        'VUV' => 0, 'XAF' => 0, 'XOF' => 0, 'XPF' => 0,
        'BHD' => 3, 'IQD' => 3, 'JOD' => 3, 'KWD' => 3,
        'LYD' => 3, 'OMR' => 3, 'TND' => 3,
    ];

    private const DEFAULT_EXPONENT = 2;

    public function get(Model $model, string $key, mixed $value, array $attributes): ?float
    {
        if (is_null($value)) {
            return null;
        }

        $factor = static::factor(static::resolveCurrency($model, $attributes));

        return (float) ((int) $value / $factor);
    }

    public function set(Model $model, string $key, mixed $value, array $attributes): ?int
    {
        if (is_null($value)) {
            return null;
        }

        $factor = static::factor(static::resolveCurrency($model, $attributes));

        return (int) round((float) $value * $factor);
    }

    public static function minorUnits(string $currency): int
    {
        return self::MINOR_UNIT_MAP[strtoupper($currency)] ?? self::DEFAULT_EXPONENT;
    }

    private static function factor(string $currency): int
    {
        return (int) pow(10, static::minorUnits($currency));
    }

    private static function resolveCurrency(Model $model, array $attributes): string
    {
        return strtoupper(trim((string) ($attributes['currency'] ?? $model->getAttribute('currency') ?? 'USD')));
    }
}
