<?php

namespace Dedoc\Scramble\Support\Generator\Types;

use Dedoc\Scramble\Support\Generator\MissingValue;
use Dedoc\Scramble\Support\Generator\WithAttributes;
use Dedoc\Scramble\Support\Generator\WithExtensions;

abstract class Type
{
    use WithAttributes;
    use WithExtensions;

    public string $type;

    public string $format = '';

    public string $description = '';

    public string $contentMediaType = '';

    public string $contentEncoding = '';

    /** @var array|scalar|null|MissingValue */
    public $example;

    /** @var array|scalar|null|MissingValue */
    public $default;

    /** @var array<array|scalar|null|MissingValue> */
    public $examples = [];

    public array $enum = [];

    /** @var scalar|null */
    public $const = null;

    public bool $nullable = false;

    public bool $deprecated = false;

    public ?string $pattern = null;

    public function __construct(string $type)
    {
        $this->type = $type;
        $this->example = new MissingValue;
        $this->default = new MissingValue;
    }

    public function clone(): static
    {
        return clone $this;
    }

    /**
     * @return $this
     */
    public function nullable(bool $nullable): self
    {
        $this->nullable = $nullable;

        return $this;
    }

    /**
     * @return $this
     */
    public function format(string $format): self
    {
        $this->format = $format;

        return $this;
    }

    /**
     * @return $this
     */
    public function contentMediaType(string $mediaType): self
    {
        $this->contentMediaType = $mediaType;

        return $this;
    }

    /**
     * @return $this
     */
    public function contentEncoding(string $encoding): self
    {
        $this->contentEncoding = $encoding;

        return $this;
    }

    /**
     * @return $this
     */
    public function addProperties(Type $fromType): self
    {
        $this->attributes = $fromType->attributes;
        $this->format = $fromType->format;
        $this->description = $fromType->description;
        $this->contentMediaType = $fromType->contentMediaType;
        $this->contentEncoding = $fromType->contentEncoding;
        $this->example = $fromType->example;
        $this->default = $fromType->default;
        $this->examples = $fromType->examples;
        $this->enum = $fromType->enum;
        $this->const = $fromType->const;
        $this->nullable = $fromType->nullable;
        $this->deprecated = $fromType->deprecated;
        $this->pattern = $fromType->pattern;

        return $this;
    }

    public function toArray()
    {
        return array_merge(
            array_filter([
                'type' => $this->nullable ? [$this->type, 'null'] : $this->type,
                'format' => $this->format,
                'contentMediaType' => $this->contentMediaType,
                'contentEncoding' => $this->contentEncoding,
                'description' => $this->description,
                'deprecated' => $this->deprecated,
                'pattern' => $this->pattern,
                'enum' => count($this->enum) ? $this->enum : null,
                'const' => ! is_null($this->const) ? $this->const : null,
            ]),
            $this->example instanceof MissingValue ? [] : ['example' => $this->example],
            $this->default instanceof MissingValue ? [] : ['default' => $this->default],
            count(
                $examples = collect($this->examples)
                    ->reject(fn ($example) => $example instanceof MissingValue)
                    ->values()
                    ->toArray()
            ) ? ['examples' => $examples] : [],
            $this->extensionPropertiesToArray(),
        );
    }

    /**
     * @return $this
     */
    public function setDescription(string $description): self
    {
        $this->description = $description;

        return $this;
    }

    /**
     * @return $this
     */
    public function enum(array $enum): self
    {
        $this->enum = $enum;

        return $this;
    }

    /**
     * @param  scalar  $const
     * @return $this
     */
    public function const($const): self
    {
        $this->const = $const;

        return $this;
    }

    /**
     * @param  array|scalar|null|MissingValue  $example
     * @return $this
     */
    public function example($example): self
    {
        $this->example = $example;

        return $this;
    }

    /**
     * @param  array|scalar|null|MissingValue  $default
     * @return $this
     */
    public function default($default): self
    {
        $this->default = $default;

        return $this;
    }

    /**
     * @param  array<array|scalar|null|MissingValue>  $examples
     * @return $this
     */
    public function examples(array $examples): self
    {
        $this->examples = $examples;

        return $this;
    }

    /** @return $this */
    public function deprecated(bool $deprecated): self
    {
        $this->deprecated = $deprecated;

        return $this;
    }

    /** @return $this */
    public function pattern(?string $pattern): self
    {
        $this->pattern = $pattern;

        return $this;
    }
}
