<?php

declare(strict_types=1);

namespace App\Framework\Vite\ValueObjects;

use App\Framework\Vite\Exceptions\ViteManifestException;

/**
 * Value Object representing Vite's manifest.json
 *
 * Parses and provides access to Vite's build manifest,
 * which maps source files to their compiled output files.
 */
final readonly class ViteManifest
{
    /**
     * @param array<string, ViteEntry> $entries Map of entry names to ViteEntry objects
     */
    public function __construct(
        public array $entries
    ) {
    }

    /**
     * Create ViteManifest from manifest file path
     */
    public static function fromFile(string $path): self
    {
        if (! file_exists($path)) {
            throw ViteManifestException::fileNotFound($path);
        }

        $content = file_get_contents($path);
        if ($content === false) {
            throw ViteManifestException::cannotRead($path);
        }

        $data = json_decode($content, true);
        if (json_last_error() !== JSON_ERROR_NONE) {
            throw ViteManifestException::invalidJson($path, json_last_error_msg());
        }

        return self::fromArray($data);
    }

    /**
     * Create ViteManifest from array data
     *
     * @param array<string, array> $data
     */
    public static function fromArray(array $data): self
    {
        $entries = [];

        foreach ($data as $name => $entry) {
            if (! is_array($entry)) {
                continue;
            }

            $entries[$name] = ViteEntry::fromManifestEntry($name, $entry);
        }

        return new self($entries);
    }

    /**
     * Get entry by name
     */
    public function getEntry(string $name): ?ViteEntry
    {
        return $this->entries[$name] ?? null;
    }

    /**
     * Check if entry exists
     */
    public function hasEntry(string $name): bool
    {
        return isset($this->entries[$name]);
    }

    /**
     * Get all entry point entries (isEntry === true)
     *
     * @return array<string, ViteEntry>
     */
    public function getEntryPoints(): array
    {
        return array_filter(
            $this->entries,
            fn (ViteEntry $entry) => $entry->isEntry
        );
    }

    /**
     * Get entry with all its dependencies resolved
     *
     * @return array<ViteEntry>
     */
    public function getEntryWithDependencies(string $name): array
    {
        $entry = $this->getEntry($name);
        if ($entry === null) {
            return [];
        }

        $resolved = [$entry];

        // Recursively resolve imports
        foreach ($entry->imports as $importName) {
            $importEntries = $this->getEntryWithDependencies($importName);
            foreach ($importEntries as $importEntry) {
                // Avoid duplicates
                if (! in_array($importEntry, $resolved, true)) {
                    $resolved[] = $importEntry;
                }
            }
        }

        return $resolved;
    }

    /**
     * Get total number of entries
     */
    public function count(): int
    {
        return count($this->entries);
    }
}
