<?php

declare(strict_types=1);

namespace App\Framework\LiveComponents\ValueObjects;

/**
 * Fragment Collection Value Object
 *
 * Immutable collection of ComponentFragments.
 * Used when returning multiple fragments from partial rendering.
 */
final readonly class FragmentCollection
{
    /**
     * @param array<ComponentFragment> $fragments
     */
    private function __construct(
        private array $fragments
    ) {
        $this->validate();
    }

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

    /**
     * Create from fragments array
     *
     * @param array<ComponentFragment> $fragments
     */
    public static function fromFragments(array $fragments): self
    {
        return new self($fragments);
    }

    /**
     * Create from array of [name => html] pairs
     *
     * @param array<string, string> $data
     */
    public static function fromArray(array $data): self
    {
        $fragments = [];

        foreach ($data as $name => $html) {
            $fragments[] = ComponentFragment::create($name, $html);
        }

        return new self($fragments);
    }

    /**
     * Get fragment by name
     */
    public function get(string $name): ?ComponentFragment
    {
        foreach ($this->fragments as $fragment) {
            if ($fragment->name === $name) {
                return $fragment;
            }
        }

        return null;
    }

    /**
     * Check if fragment exists
     */
    public function has(string $name): bool
    {
        return $this->get($name) !== null;
    }

    /**
     * Get all fragments
     *
     * @return array<ComponentFragment>
     */
    public function all(): array
    {
        return $this->fragments;
    }

    /**
     * Get all fragment names
     *
     * @return array<string>
     */
    public function names(): array
    {
        return array_map(
            fn (ComponentFragment $fragment) => $fragment->name,
            $this->fragments
        );
    }

    /**
     * Count fragments
     */
    public function count(): int
    {
        return count($this->fragments);
    }

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

    /**
     * Add fragment (returns new instance)
     */
    public function add(ComponentFragment $fragment): self
    {
        return new self([...$this->fragments, $fragment]);
    }

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

    /**
     * Filter fragments by names (returns new instance)
     *
     * @param array<string> $names
     */
    public function only(array $names): self
    {
        $filtered = array_filter(
            $this->fragments,
            fn (ComponentFragment $fragment) => in_array($fragment->name, $names, true)
        );

        return new self(array_values($filtered));
    }

    /**
     * Convert to array for JSON serialization
     *
     * @return array<array{name: string, html: string}>
     */
    public function toArray(): array
    {
        return array_map(
            fn (ComponentFragment $fragment) => $fragment->toArray(),
            $this->fragments
        );
    }

    /**
     * Convert to associative array [name => html]
     *
     * @return array<string, string>
     */
    public function toAssociativeArray(): array
    {
        $result = [];

        foreach ($this->fragments as $fragment) {
            $result[$fragment->name] = $fragment->html;
        }

        return $result;
    }

    /**
     * Validate fragments
     */
    private function validate(): void
    {
        // Check for duplicate fragment names
        $names = $this->names();
        $uniqueNames = array_unique($names);

        if (count($names) !== count($uniqueNames)) {
            $duplicates = array_diff_assoc($names, $uniqueNames);

            throw new \InvalidArgumentException(
                'Duplicate fragment names found: ' . implode(', ', $duplicates)
            );
        }
    }
}
