<?php

declare(strict_types=1);

namespace App\Framework\View\Dom;

final class ElementNode implements Node
{
    use NodeTrait;

    /** @var array<string, Attribute> */
    private array $attributes = [];

    /** @var array<string> Self-closing HTML tags */
    private const VOID_ELEMENTS = [
        'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
        'link', 'meta', 'param', 'source', 'track', 'wbr'
    ];

    public function __construct(
        private readonly string $tagName
    ) {}

    public function getNodeType(): NodeType
    {
        return NodeType::ELEMENT;
    }

    public function getNodeName(): string
    {
        return $this->tagName;
    }

    public function getTagName(): string
    {
        return $this->tagName;
    }

    public function setAttribute(string $name, string $value): void
    {
        $this->attributes[$name] = new Attribute($name, $value);
    }

    public function getAttribute(string $name): ?string
    {
        return $this->attributes[$name]->value ?? null;
    }

    public function hasAttribute(string $name): bool
    {
        return isset($this->attributes[$name]);
    }

    public function removeAttribute(string $name): void
    {
        unset($this->attributes[$name]);
    }

    /**
     * @return array<string, Attribute>
     */
    public function getAttributes(): array
    {
        return $this->attributes;
    }

    public function isVoidElement(): bool
    {
        return in_array(strtolower($this->tagName), self::VOID_ELEMENTS, true);
    }

    public function accept(NodeVisitor $visitor): void
    {
        $visitor->visitElement($this);

        foreach ($this->children as $child) {
            $child->accept($visitor);
        }
    }

    public function clone(): Node
    {
        $cloned = new self($this->tagName);

        foreach ($this->attributes as $attribute) {
            $cloned->setAttribute($attribute->name, $attribute->value);
        }

        foreach ($this->children as $child) {
            $cloned->appendChild($child->clone());
        }

        return $cloned;
    }
}
