<?php

declare(strict_types=1);

namespace apexl\entityCore\traits;

use apexl\entityCore\enums\Casts;
use DateTimeImmutable;
use DateTimeInterface;
use Exception;

trait hasCasts
{
    /**
     * @return Casts[]
     */
    abstract public function casts(): array;

    protected function withCasts(array $fieldConfig = []): array
    {
        return array_merge_recursive($fieldConfig, array_map(
            fn (Casts $cast) => ['cast' => $cast],
            $this->casts()
        ));
    }

    /** @noinspection PhpUnused */
    protected function fieldConfig(): array
    {
        return $this->withCasts([]);
    }

    public function __get($name)
    {
        return $this->getCasted($name);
    }

    protected function getCasted($name)
    {
        $value = parent::__get($name);

        if ($value !== null && $cast = $this->getCast($name)) {
            switch (true) {
                case $cast->equals(Casts::DATETIME()):
                    try {
                        return new DateTimeImmutable($value);
                    } catch (Exception $e) {
                        user_error(sprintf('Error parsing DateTime: %s', $e->getMessage()), E_USER_WARNING);
                        return null;
                    }
                case $cast->equals(Casts::SERIALIZE()):
                    return unserialize($value);
                default:
                    settype($value, $cast->getValue());
            }
        }

        return $value;
    }

    public function __set($name, $value): void
    {
        $this->setCasted($name, $value);
    }

    protected function setCasted($name, $value): void
    {
        if ($value !== null && $cast = $this->getCast($name)) {
            switch (true) {
                case $cast->equals(Casts::DATETIME()):
                    if ($value instanceof DateTimeInterface) {
                        $value = $value->format('Y-m-d H:i:s');
                    }
                    break;
                case $cast->equals(Casts::SERIALIZE()):
                    $value = serialize($value);
                    break;
                default:
                    break;
            }
        }

        parent::__set($name, $value);
    }

    private function getCast(string $name): ?Casts
    {
        $cast = $this->fieldConfig()[$name]['cast'] ?? null;

        return $cast instanceof Casts ? $cast : null;
    }
}
