<?php

declare(strict_types=1);

namespace App\Framework\LiveComponents\ValueObjects;

use InvalidArgumentException;

/**
 * EventPayload Value Object
 *
 * Represents immutable event payload data for LiveComponent events.
 * Provides type-safe access to event data with validation.
 */
final readonly class EventPayload
{
    /**
     * @param array<string, mixed> $data Event payload data
     */
    private function __construct(
        private array $data
    ) {
        $this->validate();
    }

    /**
     * Create EventPayload from array
     */
    public static function fromArray(array $data): self
    {
        return new self($data);
    }

    /**
     * Create empty EventPayload
     */
    public static function empty(): self
    {
        return new self([]);
    }

    /**
     * Get value by key
     */
    public function get(string $key, mixed $default = null): mixed
    {
        return $this->data[$key] ?? $default;
    }

    /**
     * Check if key exists
     */
    public function has(string $key): bool
    {
        return array_key_exists($key, $this->data);
    }

    /**
     * Get string value
     */
    public function getString(string $key, ?string $default = null): ?string
    {
        $value = $this->get($key, $default);

        if ($value === null) {
            return null;
        }

        return is_string($value) ? $value : (string) $value;
    }

    /**
     * Get required string value
     */
    public function requireString(string $key): string
    {
        if (! $this->has($key)) {
            throw new InvalidArgumentException("Required event payload key '{$key}' is missing");
        }

        $value = $this->getString($key);

        if ($value === null) {
            throw new InvalidArgumentException("Event payload key '{$key}' cannot be null");
        }

        return $value;
    }

    /**
     * Get integer value
     */
    public function getInt(string $key, ?int $default = null): ?int
    {
        $value = $this->get($key, $default);

        if ($value === null) {
            return null;
        }

        return is_int($value) ? $value : (int) $value;
    }

    /**
     * Get required integer value
     */
    public function requireInt(string $key): int
    {
        if (! $this->has($key)) {
            throw new InvalidArgumentException("Required event payload key '{$key}' is missing");
        }

        $value = $this->getInt($key);

        if ($value === null) {
            throw new InvalidArgumentException("Event payload key '{$key}' cannot be null");
        }

        return $value;
    }

    /**
     * Get float value
     */
    public function getFloat(string $key, ?float $default = null): ?float
    {
        $value = $this->get($key, $default);

        if ($value === null) {
            return null;
        }

        return is_float($value) ? $value : (float) $value;
    }

    /**
     * Get boolean value
     */
    public function getBool(string $key, ?bool $default = null): ?bool
    {
        $value = $this->get($key, $default);

        if ($value === null) {
            return null;
        }

        return is_bool($value) ? $value : (bool) $value;
    }

    /**
     * Get array value
     */
    public function getArray(string $key, ?array $default = null): ?array
    {
        $value = $this->get($key, $default);

        if ($value === null) {
            return null;
        }

        return is_array($value) ? $value : [$value];
    }

    /**
     * Convert to array
     */
    public function toArray(): array
    {
        return $this->data;
    }

    /**
     * Create new instance with additional data (immutable update)
     */
    public function with(string $key, mixed $value): self
    {
        return new self([...$this->data, $key => $value]);
    }

    /**
     * Create new instance with multiple values
     */
    public function withMany(array $data): self
    {
        return new self([...$this->data, ...$data]);
    }

    /**
     * Create new instance without key
     */
    public function without(string $key): self
    {
        $newData = $this->data;
        unset($newData[$key]);

        return new self($newData);
    }

    /**
     * Create new instance with only specified keys
     */
    public function only(array $keys): self
    {
        $filtered = array_filter(
            $this->data,
            fn ($key) => in_array($key, $keys, true),
            ARRAY_FILTER_USE_KEY
        );

        return new self($filtered);
    }

    /**
     * Create new instance without specified keys
     */
    public function except(array $keys): self
    {
        $filtered = array_filter(
            $this->data,
            fn ($key) => ! in_array($key, $keys, true),
            ARRAY_FILTER_USE_KEY
        );

        return new self($filtered);
    }

    /**
     * Merge with another EventPayload
     */
    public function merge(self $other): self
    {
        return new self([...$this->data, ...$other->data]);
    }

    /**
     * Check if equals another EventPayload
     */
    public function equals(self $other): bool
    {
        return $this->data === $other->data;
    }

    /**
     * Check if payload is empty
     */
    public function isEmpty(): bool
    {
        return empty($this->data);
    }

    /**
     * Get payload size (number of keys)
     */
    public function size(): int
    {
        return count($this->data);
    }

    /**
     * Get all keys
     */
    public function keys(): array
    {
        return array_keys($this->data);
    }

    /**
     * Validate payload data
     */
    private function validate(): void
    {
        // All keys must be strings
        foreach ($this->data as $key => $value) {
            if (! is_string($key)) {
                throw new InvalidArgumentException(
                    'All event payload keys must be strings, got: ' . gettype($key)
                );
            }
        }
    }
}
