<?php
declare(strict_types=1);

namespace ROITheme\Shared\Domain\Entities;

use ROITheme\Shared\Domain\ValueObjects\ComponentName;
use ROITheme\Shared\Domain\ValueObjects\ComponentConfiguration;
use ROITheme\Shared\Domain\ValueObjects\ComponentVisibility;
use ROITheme\Shared\Domain\Exceptions\InvalidComponentException;

/**
 * Component - Entidad del Dominio
 *
 * RESPONSABILIDAD: Representar un componente del tema con toda su lógica de negocio
 *
 * INVARIANTES (Reglas que SIEMPRE deben cumplirse):
 * 1. Un componente siempre tiene un nombre válido
 * 2. Un componente siempre tiene configuración (puede estar vacía)
 * 3. Si tiene CTA URL debe tener CTA text (validado en ComponentConfiguration)
 * 4. Los colores siempre están en formato hexadecimal válido
 * 5. El timestamp de updated_at es siempre >= created_at
 *
 * ENTIDAD vs VALUE OBJECT:
 * - Component es ENTIDAD porque tiene identidad (puede cambiar configuración pero sigue siendo el mismo componente)
 * - ComponentName es VALUE OBJECT (dos nombres iguales son indistinguibles)
 * - ComponentConfiguration es VALUE OBJECT (inmutable)
 *
 * USO:
 * ```php
 * $component = new Component(
 *     ComponentName::fromString('top_bar'),
 *     ComponentConfiguration::fromFlat([
 *         'enabled' => true,
 *         'message_text' => 'Welcome!'
 *     ]),
 *     ComponentVisibility::allDevices()
 * );
 *
 * // Actualizar configuración (inmutabilidad)
 * $updated = $component->updateConfiguration(
 *     $component->configuration()->withValue('content', 'message_text', 'Hello!')
 * );
 * ```
 *
 * @package ROITheme\Shared\Domain\Entities
 */
final class Component
{
    /**
     * @var ComponentName Nombre del componente (inmutable)
     */
    private ComponentName $name;

    /**
     * @var ComponentConfiguration Configuración del componente
     */
    private ComponentConfiguration $configuration;

    /**
     * @var ComponentVisibility Visibilidad del componente
     */
    private ComponentVisibility $visibility;

    /**
     * @var bool Componente habilitado
     */
    private bool $isEnabled;

    /**
     * @var string Versión del schema
     */
    private string $schemaVersion;

    /**
     * @var \DateTimeImmutable Fecha de creación
     */
    private \DateTimeImmutable $createdAt;

    /**
     * @var \DateTimeImmutable Fecha de última actualización
     */
    private \DateTimeImmutable $updatedAt;

    /**
     * Constructor
     *
     * @param ComponentName $name
     * @param ComponentConfiguration $configuration
     * @param ComponentVisibility $visibility
     * @param bool $isEnabled
     * @param string $schemaVersion
     * @param \DateTimeImmutable|null $createdAt
     * @param \DateTimeImmutable|null $updatedAt
     * @throws InvalidComponentException Si se violan invariantes
     */
    public function __construct(
        ComponentName $name,
        ComponentConfiguration $configuration,
        ComponentVisibility $visibility,
        bool $isEnabled = true,
        string $schemaVersion = '1.0.0',
        ?\DateTimeImmutable $createdAt = null,
        ?\DateTimeImmutable $updatedAt = null
    ) {
        $this->name = $name;
        $this->configuration = $configuration;
        $this->visibility = $visibility;
        $this->isEnabled = $isEnabled;
        $this->schemaVersion = $schemaVersion;
        $this->createdAt = $createdAt ?? new \DateTimeImmutable();
        $this->updatedAt = $updatedAt ?? new \DateTimeImmutable();

        $this->validateInvariants();
    }

    /**
     * Obtener nombre del componente
     *
     * @return ComponentName
     */
    public function name(): ComponentName
    {
        return $this->name;
    }

    /**
     * Obtener configuración del componente
     *
     * @return ComponentConfiguration
     */
    public function configuration(): ComponentConfiguration
    {
        return $this->configuration;
    }

    /**
     * Obtener visibilidad del componente
     *
     * @return ComponentVisibility
     */
    public function visibility(): ComponentVisibility
    {
        return $this->visibility;
    }

    /**
     * Verificar si el componente está habilitado
     *
     * @return bool
     */
    public function isEnabled(): bool
    {
        return $this->isEnabled;
    }

    /**
     * Obtener versión del schema
     *
     * @return string
     */
    public function schemaVersion(): string
    {
        return $this->schemaVersion;
    }

    /**
     * Obtener fecha de creación
     *
     * @return \DateTimeImmutable
     */
    public function createdAt(): \DateTimeImmutable
    {
        return $this->createdAt;
    }

    /**
     * Obtener fecha de última actualización
     *
     * @return \DateTimeImmutable
     */
    public function updatedAt(): \DateTimeImmutable
    {
        return $this->updatedAt;
    }

    /**
     * Crear nuevo componente con configuración actualizada
     * (Inmutabilidad: retorna nueva instancia)
     *
     * @param ComponentConfiguration $configuration Nueva configuración
     * @return self Nueva instancia
     */
    public function updateConfiguration(ComponentConfiguration $configuration): self
    {
        return new self(
            $this->name,
            $configuration,
            $this->visibility,
            $this->isEnabled,
            $this->schemaVersion,
            $this->createdAt,
            new \DateTimeImmutable()
        );
    }

    /**
     * Crear nuevo componente con visibilidad actualizada
     * (Inmutabilidad: retorna nueva instancia)
     *
     * @param ComponentVisibility $visibility Nueva visibilidad
     * @return self Nueva instancia
     */
    public function updateVisibility(ComponentVisibility $visibility): self
    {
        return new self(
            $this->name,
            $this->configuration,
            $visibility,
            $this->isEnabled,
            $this->schemaVersion,
            $this->createdAt,
            new \DateTimeImmutable()
        );
    }

    /**
     * Crear nuevo componente habilitado
     * (Inmutabilidad: retorna nueva instancia)
     *
     * @return self Nueva instancia con isEnabled=true
     */
    public function enable(): self
    {
        return new self(
            $this->name,
            $this->configuration,
            $this->visibility,
            true,
            $this->schemaVersion,
            $this->createdAt,
            new \DateTimeImmutable()
        );
    }

    /**
     * Crear nuevo componente deshabilitado
     * (Inmutabilidad: retorna nueva instancia)
     *
     * @return self Nueva instancia con isEnabled=false
     */
    public function disable(): self
    {
        return new self(
            $this->name,
            $this->configuration,
            $this->visibility,
            false,
            $this->schemaVersion,
            $this->createdAt,
            new \DateTimeImmutable()
        );
    }

    /**
     * Comparar con otro componente
     * (Dos componentes son iguales si tienen el mismo nombre)
     *
     * @param Component $other Otro componente
     * @return bool True si son el mismo componente
     */
    public function equals(Component $other): bool
    {
        return $this->name->equals($other->name);
    }

    /**
     * Obtener datos del componente para renderizado
     * Retorna la configuración completa en formato array
     *
     * @return array
     */
    public function getData(): array
    {
        return $this->configuration->all();
    }

    /**
     * Convertir a array para serialización
     *
     * @return array
     */
    public function toArray(): array
    {
        return [
            'name' => $this->name->value(),
            'configuration' => $this->configuration->all(),
            'visibility' => $this->visibility->toArray(),
            'is_enabled' => $this->isEnabled,
            'schema_version' => $this->schemaVersion,
            'created_at' => $this->createdAt->format('Y-m-d H:i:s'),
            'updated_at' => $this->updatedAt->format('Y-m-d H:i:s')
        ];
    }

    /**
     * Convertir a string para debugging
     *
     * @return string
     */
    public function __toString(): string
    {
        return sprintf(
            'Component(%s, enabled=%s, schema=%s)',
            $this->name->value(),
            $this->isEnabled ? 'true' : 'false',
            $this->schemaVersion
        );
    }

    /**
     * Validar invariantes del componente
     *
     * @throws InvalidComponentException Si se viola algún invariante
     * @return void
     */
    private function validateInvariants(): void
    {
        // Invariante: updated_at >= created_at
        if ($this->updatedAt < $this->createdAt) {
            throw new InvalidComponentException(
                'Updated timestamp cannot be before created timestamp'
            );
        }
    }
}
