<?php

declare(strict_types=1);

namespace Psalm\Type\Atomic;

use Override;
use Psalm\Codebase;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\Type\TemplateResult;
use Psalm\Storage\FunctionLikeParameter;
use Psalm\Storage\Mutations;
use Psalm\Storage\UnserializeMemoryUsageSuppressionTrait;
use Psalm\Type\Atomic;
use Psalm\Type\Union;

/**
 * Denotes the `callable` type. Can result from an `is_callable` check.
 *
 * @psalm-immutable
 */
final class TCallable extends Atomic
{
    use UnserializeMemoryUsageSuppressionTrait;
    use CallableTrait;

    public string $value = 'callable';

    /**
     * Constructs a new instance of a generic type
     *
     * @param list<FunctionLikeParameter> $params
     * @param Mutations::LEVEL_* $allowed_mutations
     */
    public function __construct(
        ?array $params = null,
        ?Union $return_type = null,
        int $allowed_mutations = Mutations::LEVEL_EXTERNAL,
        bool $from_docblock = false,
    ) {
        $this->params = $params;
        $this->return_type = $return_type;
        $this->allowed_mutations = $allowed_mutations;
        parent::__construct($from_docblock);
    }

    /**
     * @param  array<lowercase-string, string> $aliased_classes
     */
    #[Override]
    public function toPhpString(
        ?string $namespace,
        array $aliased_classes,
        ?string $this_class,
        int $analysis_php_version_id,
    ): string {
        return 'callable';
    }

    #[Override]
    public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool
    {
        return $this->params === null && $this->return_type === null;
    }

    /**
     * @return static
     */
    #[Override]
    public function replaceTemplateTypesWithArgTypes(TemplateResult $template_result, ?Codebase $codebase): self
    {
        $replaced = $this->replaceCallableTemplateTypesWithArgTypes($template_result, $codebase);
        if (!$replaced) {
            return $this;
        }
        return new static(
            $replaced[0],
            $replaced[1],
            $this->allowed_mutations,
        );
    }
    /**
     * @return static
     */
    #[Override]
    public function replaceTemplateTypesWithStandins(
        TemplateResult $template_result,
        Codebase $codebase,
        ?StatementsAnalyzer $statements_analyzer = null,
        ?Atomic $input_type = null,
        ?int $input_arg_offset = null,
        ?string $calling_class = null,
        ?string $calling_function = null,
        bool $replace = true,
        bool $add_lower_bound = false,
        int $depth = 0,
    ): self {
        $replaced = $this->replaceCallableTemplateTypesWithStandins(
            $template_result,
            $codebase,
            $statements_analyzer,
            $input_type,
            $input_arg_offset,
            $calling_class,
            $calling_function,
            $replace,
            $add_lower_bound,
            $depth,
        );
        if (!$replaced) {
            return $this;
        }
        return new static(
            $replaced[0],
            $replaced[1],
            $this->allowed_mutations,
        );
    }

    #[Override]
    protected function getChildNodeKeys(): array
    {
        return $this->getCallableChildNodeKeys();
    }

    /** @return true */
    #[Override]
    public function isCallableType(): bool
    {
        return true;
    }
}
