<?php

declare(strict_types=1);

namespace Jane\Component\JsonSchema\Guesser\Guess;

use Jane\Component\JsonSchema\Generator\Context\Context;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar;
use PhpParser\Node\VariadicPlaceholder;

/**
 * Represent a DateTime type.
 */
class DateTimeType extends ObjectType implements \Stringable
{
    use CheckNullableTrait;

    /**
     * Format of the date to use when normalized.
     *
     * @var string
     */
    private $outputFormat;

    /**
     * Format of the date to use when denormalized.
     *
     * @var string
     */
    private $inputFormat;

    /**
     * Indicator whether to use DateTime or DateTimeInterface as type hint.
     *
     * @var bool
     */
    private $preferInterface;

    public function __construct(object $object, string $outputFormat = \DateTime::RFC3339, ?string $inputFormat = null, ?bool $preferInterface = null)
    {
        parent::__construct($object, '\DateTime', '', []);

        $this->outputFormat = $outputFormat;
        $this->inputFormat = $inputFormat ?? $outputFormat;
        $this->preferInterface = $preferInterface ?? false;
    }

    public function __toString(): string
    {
        return '\DateTime';
    }

    /**
     * ({@inheritDoc}.
     */
    public function createConditionStatement(Expr $input): Expr
    {
        return new Expr\BinaryOp\LogicalAnd(new Expr\FuncCall(
            new Name('is_string'), [
                new Arg($input),
            ]),
            new Expr\BinaryOp\NotIdentical(
                new Expr\ConstFetch(new Name('false')),
                $this->generateParseExpression($input),
            ),
        );
    }

    public function getTypeHint(string $namespace): Name
    {
        return new Name($this->preferInterface ? '\DateTimeInterface' : '\DateTime');
    }

    /**
     * ({@inheritDoc}.
     */
    protected function createDenormalizationValueStatement(Context $context, Expr $input, bool $normalizerFromObject = true): Expr
    {
        return $this->generateParseExpression($input);
    }

    /**
     * ({@inheritDoc}.
     */
    protected function createNormalizationValueStatement(Context $context, Expr $input, bool $normalizerFromObject = true): Expr
    {
        //        if ($this->isNullable($this->object)) {
        // $object?->format($format);
        return new Expr\NullsafeMethodCall($input, 'format', [
            new Arg(new Scalar\String_($this->outputFormat)),
        ]);
        //        }

        // $object->format($format);
        //        return new Expr\MethodCall($input, 'format', [
        //            new Arg(new Scalar\String_($this->outputFormat)),
        //        ]);
    }

    protected function generateParseExpression(Expr $input): Expr
    {
        // new \DateTimeZone('UTC')
        /** @var Expr|VariadicPlaceholder $variadicPlaceholder */
        $variadicPlaceholder = new Scalar\String_('UTC');

        if (empty($this->inputFormat)) {
            // new \DateTime($data)
            $new = new Expr\New_(new Name('\DateTime'), [new Arg($input)]);
            // (new \DateTime($data))->getTimezone()->getName()
            $timezoneName = new Expr\MethodCall(
                new Expr\MethodCall($new, 'getTimezone'),
                'getName',
            );

            // (new \DateTime($data))->getTimezone()->getName() === 'Z' ? (new \DateTime($data))->setTimezone(new \DateTimeZone('GMT')) : \DateTime($data)
            return new Expr\Ternary(
                new Expr\BinaryOp\Equal($timezoneName, new Scalar\String_('Z')),
                new Expr\MethodCall($new, 'setTimezone', [new Arg($variadicPlaceholder)]),
                $new,
            );
        }

        // \DateTime::createFromFormat($format, $data)
        $new = new Expr\StaticCall(new Name('\DateTime'), 'createFromFormat', [
            new Arg(new Scalar\String_($this->inputFormat)),
            new Arg($input),
        ]);

        return new Expr\MethodCall(
            $new,
            new Name('setTimezone'),
            [
                new Arg(
                    new Expr\New_(
                        new Name('\DateTimeZone'),
                        [
                            $variadicPlaceholder,
                        ],
                    ),
                ),
            ],
        );
    }
}
