<?php

declare(strict_types=1);

namespace App\Framework\Search;

use App\Framework\Exception\ErrorCode;
use App\Framework\Exception\FrameworkException;

/**
 * Service for automatic indexing without modifying entities
 */
final readonly class SearchIndexingService
{
    public function __construct(
        private SearchService $searchService,
        private SearchableMappingRegistry $mappingRegistry
    ) {
    }

    /**
     * Index a single entity if it's searchable
     */
    public function indexEntity(object $entity): bool
    {
        $adapter = $this->mappingRegistry->createAdapter($entity);

        if (! $adapter) {
            return false; // Entity not searchable or disabled
        }

        try {
            $document = $adapter->toSearchDocument();

            return $this->searchService->index(
                $adapter->getEntityType(),
                $adapter->getId(),
                $document
            );

        } catch (\Exception $e) {
            throw FrameworkException::create(
                ErrorCode::SEARCH_INDEX_FAILED,
                'Failed to index entity: ' . $e->getMessage()
            )->withData([
                'entity_type' => $adapter->getEntityType(),
                'entity_id' => $adapter->getId(),
                'entity_class' => get_class($entity),
                'error' => $e->getMessage(),
            ]);
        }
    }

    /**
     * Update entity in search index
     */
    public function updateEntity(object $entity): bool
    {
        $adapter = $this->mappingRegistry->createAdapter($entity);

        if (! $adapter) {
            return false;
        }

        try {
            $document = $adapter->toSearchDocument();

            return $this->searchService->update(
                $adapter->getEntityType(),
                $adapter->getId(),
                $document
            );

        } catch (\Exception $e) {
            throw FrameworkException::create(
                ErrorCode::SEARCH_UPDATE_FAILED,
                'Failed to update entity in search index: ' . $e->getMessage()
            )->withData([
                'entity_type' => $adapter->getEntityType(),
                'entity_id' => $adapter->getId(),
                'entity_class' => get_class($entity),
            ]);
        }
    }

    /**
     * Remove entity from search index
     */
    public function removeEntity(object $entity): bool
    {
        $adapter = $this->mappingRegistry->createAdapter($entity);

        if (! $adapter) {
            return false;
        }

        return $this->searchService->delete(
            $adapter->getEntityType(),
            $adapter->getId()
        );
    }

    /**
     * Bulk index multiple entities
     */
    public function bulkIndexEntities(array $entities): BulkIndexResult
    {
        $documents = [];

        foreach ($entities as $entity) {
            $adapter = $this->mappingRegistry->createAdapter($entity);

            if (! $adapter) {
                continue; // Skip non-searchable entities
            }

            try {
                $documents[] = new SearchDocument(
                    id: $adapter->getId(),
                    entityType: $adapter->getEntityType(),
                    data: $adapter->toSearchDocument()
                );
            } catch (\Exception) {
                // Skip entities that can't be converted to search documents
                continue;
            }
        }

        if (empty($documents)) {
            return BulkIndexResult::empty();
        }

        return $this->searchService->bulkIndex($documents);
    }

    /**
     * Check if entity should be auto-indexed
     */
    public function shouldAutoIndex(object $entity): bool
    {
        $mapping = $this->mappingRegistry->getByEntityClass(get_class($entity));

        return $mapping && $mapping->enabled && $mapping->autoIndex;
    }

    /**
     * Get all searchable entity types
     */
    public function getSearchableEntityTypes(): array
    {
        return $this->mappingRegistry->getEntityTypes();
    }

    /**
     * Reindex all entities of a specific type using callback
     */
    public function reindexEntityType(string $entityType, callable $entityProvider): BulkIndexResult
    {
        $mapping = $this->mappingRegistry->getByEntityType($entityType);

        if (! $mapping) {
            throw FrameworkException::create(
                ErrorCode::SEARCH_CONFIG_INVALID,
                "No mapping found for entity type '{$entityType}'"
            );
        }

        // Get entities from provider (e.g., repository)
        $entities = $entityProvider();

        if (! is_array($entities) && ! ($entities instanceof \Traversable)) {
            throw FrameworkException::create(
                ErrorCode::SEARCH_CONFIG_INVALID,
                'Entity provider must return array or Traversable'
            );
        }

        return $this->bulkIndexEntities($entities instanceof \Traversable ? iterator_to_array($entities) : $entities);
    }
}
