<?php

/**
 * Vala-IR function declaration emitted as a Vala static function.
 */
final class ValaIrFunctionDeclaration
{
    private int $startLine;
    private int $endLine;
    private array $comments;
    private string $name;
    private string $phpName;
    private array $params;
    private array $paramTypes;
    private array $stmts;
    private string $returnType;
    private array $valaParams;
    private ?object $defaultReturnExpr;
    private string $role;
    private ?array $tryCatch;
    private bool $throwsError;
    private string $qualifiedName;
    private string $visibility;

    /**
     * Create a function declaration.
     *
     * `$params` are PHP-level function parameters kept for registry metadata.
     * `$valaParams` are the concrete Vala signature parameters.
     */
    public function __construct(int $startLine, int $endLine, array $comments, string $name, array $params, array $stmts, string $phpName = '', string $returnType = 'Zval', ?array $valaParams = null, ?object $defaultReturnExpr = null, string $role = 'php_function', ?array $tryCatch = null, bool $throwsError = true, string $qualifiedName = '', string $visibility = 'public', array $paramTypes = [])
    {
        $this->startLine = $startLine;
        $this->endLine = $endLine;
        $this->comments = $comments;
        $this->name = $name;
        $this->phpName = $phpName;
        $this->params = $params;
        $this->paramTypes = self::normalizeParamTypes($params, $paramTypes);
        $this->stmts = $stmts;
        $this->returnType = $returnType;
        $this->valaParams = $valaParams ?? [['typeName' => 'Zval[]', 'name' => 'args']];
        $this->defaultReturnExpr = $defaultReturnExpr ?? ($returnType === 'Zval' ? new ValaIrStaticCallExpression(0, 0, [], 'Zval.from_null', []) : null);
        $this->role = $role;
        $this->tryCatch = $tryCatch;
        $this->throwsError = $throwsError;
        $this->qualifiedName = $qualifiedName;
        $this->visibility = self::normalizeVisibility($visibility);
    }

    /**
     * Return a copy of this function declaration with replaced body statements.
     */
    public function withStmts(array $stmts): self
    {
        return new self($this->startLine, $this->endLine, $this->comments, $this->name, $this->params, $stmts, $this->phpName, $this->returnType, $this->valaParams, $this->defaultReturnExpr, $this->role, $this->tryCatch, $this->throwsError, $this->qualifiedName, $this->visibility, $this->paramTypes);
    }

    /** Return this function's body statement nodes. */
    public function stmts(): array
    {
        return $this->stmts;
    }

    /**
     * Convert to a plain array for debugging/export.
     */
    public function toArray(): array
    {
        return [
            'kind' => 'FunctionDeclaration',
            'startLine' => $this->startLine,
            'endLine' => $this->endLine,
            'comments' => array_map(static fn (ValaIrComment $comment): array => $comment->toArray(), $this->comments),
            'name' => $this->name,
            'qualifiedName' => $this->qualifiedName !== '' ? $this->qualifiedName : $this->name,
            'phpName' => $this->phpName,
            'params' => $this->params,
            'paramTypes' => $this->paramTypes,
            'visibility' => $this->visibility,
            'returnType' => $this->returnType,
            'valaParams' => array_values(array_map(static function (array $param): array {
                return [
                    'name' => (string) ($param['name'] ?? ''),
                    'typeName' => (string) ($param['typeName'] ?? ''),
                ];
            }, $this->valaParams)),
            'stmts' => array_map(
                static fn (object $stmt): array => method_exists($stmt, 'toArray') ? $stmt->toArray() : ['kind' => get_debug_type($stmt)],
                $this->stmts,
            ),
            'defaultReturnExpr' => $this->defaultReturnExpr !== null && method_exists($this->defaultReturnExpr, 'toArray') ? $this->defaultReturnExpr->toArray() : null,
            'role' => $this->role,
            'tryCatch' => $this->tryCatchToArray(),
            'throwsError' => $this->throwsError,
        ];
    }
    private static function normalizeParamTypes(array $params, array $paramTypes): array
    {
        $out = [];
        foreach ($params as $param) {
            if (!is_string($param) || $param === '') {
                continue;
            }
            $type = strtolower(ltrim((string) ($paramTypes[$param] ?? ''), '\\'));
            $normalized = match ($type) {
                'int', 'integer' => 'int',
                'float', 'double' => 'float',
                default => '',
            };
            if ($normalized !== '') {
                $out[$param] = $normalized;
            }
        }
        return $out;
    }

    private function tryCatchToArray(): ?array
    {
        if ($this->tryCatch === null) {
            return null;
        }

        return [
            'stmts' => array_map(
                static fn (object $stmt): array => method_exists($stmt, 'toArray') ? $stmt->toArray() : ['kind' => get_debug_type($stmt)],
                is_array($this->tryCatch['stmts'] ?? null) ? $this->tryCatch['stmts'] : [],
            ),
            'returnExpr' => isset($this->tryCatch['returnExpr']) && is_object($this->tryCatch['returnExpr']) && method_exists($this->tryCatch['returnExpr'], 'toArray') ? $this->tryCatch['returnExpr']->toArray() : null,
            'catchType' => (string) ($this->tryCatch['catchType'] ?? 'Error'),
            'catchName' => (string) ($this->tryCatch['catchName'] ?? 'e'),
            'catchReturnExpr' => isset($this->tryCatch['catchReturnExpr']) && is_object($this->tryCatch['catchReturnExpr']) && method_exists($this->tryCatch['catchReturnExpr'], 'toArray') ? $this->tryCatch['catchReturnExpr']->toArray() : null,
        ];
    }

    private static function normalizeVisibility(string $visibility): string
    {
        $normalized = strtolower($visibility);
        if (!in_array($normalized, ['public', 'protected', 'private'], true)) {
            return 'public';
        }
        return $normalized;
    }
}
