<?php

/**
 * JCOGS Image Pro - Provider Registry
 * Provider Architecture Foundation
 * 
 * @category   ExpressionEngine Add-on
 * @package    JCOGS Image Pro
 * @author     JCOGS Design <contact@jcogs.net>
 * @copyright  Copyright (c) 2021 - 2025 JCOGS Design
 * @license    https://jcogs.net/add-ons/license/jcogs_img_pro
 * @version    2.0.6
 * @link       https://JCOGS.net/
 * @since      Provider Architecture v1.0
 */

namespace JCOGSDesign\JCOGSImagePro\Service;

use JCOGSDesign\JCOGSImagePro\Service\Provider\FaceRecognitionProviderInterface;
use JCOGSDesign\JCOGSImagePro\Service\Provider\FieldProviderInterface;
use JCOGSDesign\JCOGSImagePro\Service\Provider\VariableProviderInterface;
use JCOGSDesign\JCOGSImagePro\Service\Provider\FilterProviderInterface;
use JCOGSDesign\JCOGSImagePro\Service\Provider\WatermarkPlacementProviderInterface;
use JCOGSDesign\JCOGSImagePro\Service\Provider\CompressionProviderInterface;

/**
 * Provider Registry
 * 
 * Central registry for companion add-on providers. Manages provider registration,
 * selection by priority, and fallback logic.
 * 
 * Companion add-ons register their providers via the 'jcogs_img_pro_register_providers'
 * extension hook. The registry automatically selects the best available provider
 * based on priority and availability.
 */
class ProviderRegistry
{
    /**
     * Registered face recognition providers
     * @var FaceRecognitionProviderInterface[]
     */
    private array $faceRecognitionProviders = [];
    
    /**
     * Registered field providers
     * @var FieldProviderInterface[]
     */
    private array $fieldProviders = [];
    
    /**
     * Registered variable providers (v2.0.0+)
     * @var VariableProviderInterface[]
     */
    private array $variableProviders = [];
    
    /**
     * Registered filter providers (v2.0.0+)
     * @var FilterProviderInterface[][]
     */
    private array $filterProviders = [];
    
    /**
     * Registered watermark placement providers (v2.0.0+ stub, full v2.1.0)
     * @var WatermarkPlacementProviderInterface[][]
     */
    private array $watermarkPlacementProviders = [];
    
    /**
     * Registered compression providers (v2.0.0+ stub, full v2.1.0)
     * @var CompressionProviderInterface[]
     */
    private array $compressionProviders = [];
    
    /**
     * Cached best available face recognition provider
     * @var FaceRecognitionProviderInterface|null
     */
    private ?FaceRecognitionProviderInterface $bestFaceProvider = null;
    
    /**
     * Register face recognition provider
     * 
     * Providers are automatically sorted by priority (highest first).
     * 
     * @param FaceRecognitionProviderInterface $provider Provider instance
     * @return void
     */
    public function registerFaceRecognitionProvider(FaceRecognitionProviderInterface $provider): void
    {
        $this->faceRecognitionProviders[$provider->getName()] = $provider;
        
        // Sort by priority descending (highest priority first)
        uasort($this->faceRecognitionProviders, function($a, $b) {
            return $b->getPriority() <=> $a->getPriority();
        });
        
        // Clear cached best provider
        $this->bestFaceProvider = null;
    }
    
    /**
     * Get best available face recognition provider
     * 
     * Returns highest-priority provider that reports isAvailable() = true.
     * Results are cached until registry is modified.
     * 
     * @return FaceRecognitionProviderInterface|null Provider or null if none available
     */
    public function getFaceRecognitionProvider(): ?FaceRecognitionProviderInterface
    {
        // Return cached result if available
        if ($this->bestFaceProvider !== null) {
            return $this->bestFaceProvider;
        }
        
        // Find first available provider (already sorted by priority)
        foreach ($this->faceRecognitionProviders as $provider) {
            if ($provider->isAvailable()) {
                $this->bestFaceProvider = $provider;
                return $provider;
            }
        }
        
        return null;
    }
    
    /**
     * Get all registered face recognition providers
     * 
     * @param bool $availableOnly Only return available providers
     * @return FaceRecognitionProviderInterface[] Providers indexed by name
     */
    public function getAllFaceRecognitionProviders(bool $availableOnly = false): array
    {
        if (!$availableOnly) {
            return $this->faceRecognitionProviders;
        }
        
        return array_filter($this->faceRecognitionProviders, function($provider) {
            return $provider->isAvailable();
        });
    }
    
    /**
     * Register field provider
     * 
     * @param FieldProviderInterface $provider Provider instance
     * @return void
     */
    public function registerFieldProvider(FieldProviderInterface $provider): void
    {
        $this->fieldProviders[$provider->getName()] = $provider;
    }
    
    /**
     * Get all available field providers
     * 
     * Only returns providers that report isAvailable() = true.
     * 
     * @return FieldProviderInterface[] Available providers indexed by name
     */
    public function getAvailableFieldProviders(): array
    {
        return array_filter($this->fieldProviders, function($provider) {
            return $provider->isAvailable();
        });
    }
    
    /**
     * Get field provider by name
     * 
     * @param string $name Provider name
     * @return FieldProviderInterface|null Provider or null if not found
     */
    public function getFieldProvider(string $name): ?FieldProviderInterface
    {
        return $this->fieldProviders[$name] ?? null;
    }
    
    /**
     * Get all registered field providers
     * 
     * @param bool $availableOnly Only return available providers
     * @return FieldProviderInterface[] Providers indexed by name
     */
    public function getAllFieldProviders(bool $availableOnly = false): array
    {
        if (!$availableOnly) {
            return $this->fieldProviders;
        }
        
        return $this->getAvailableFieldProviders();
    }
    
    // =========================================================================
    // Variable Provider Methods (v2.0.0+)
    // =========================================================================
    
    /**
     * Register variable provider
     * 
     * Providers are automatically sorted by priority (highest first).
     * 
     * @param VariableProviderInterface $provider Provider instance
     * @return void
     */
    public function registerVariableProvider(VariableProviderInterface $provider): void
    {
        $this->variableProviders[$provider->getName()] = $provider;
        
        // Sort by priority descending (highest priority first)
        uasort($this->variableProviders, function($a, $b) {
            return $b->getPriority() <=> $a->getPriority();
        });
    }
    
    /**
     * Get all available variable providers
     * 
     * Returns all providers that report isAvailable() = true, sorted by priority.
     * Multiple providers can be active simultaneously (variables merged).
     * 
     * @return VariableProviderInterface[] Available providers indexed by name
     */
    public function getVariableProviders(): array
    {
        return array_filter($this->variableProviders, function($provider) {
            return $provider->isAvailable();
        });
    }
    
    // =========================================================================
    // Filter Provider Methods (v2.0.0+)
    // =========================================================================
    
    /**
     * Register filter provider
     * 
     * Multiple providers can register the same filter name. They are sorted by
     * priority, with highest priority selected when filter is used.
     * 
     * @param FilterProviderInterface $provider Provider instance
     * @return void
     */
    public function registerFilterProvider(FilterProviderInterface $provider): void
    {
        $filterName = $provider->getFilterName();
        
        if (!isset($this->filterProviders[$filterName])) {
            $this->filterProviders[$filterName] = [];
        }
        
        $this->filterProviders[$filterName][] = $provider;
        
        // Sort by priority descending (highest priority first)
        usort($this->filterProviders[$filterName], function($a, $b) {
            return $b->getPriority() <=> $a->getPriority();
        });
    }
    
    /**
     * Get filter provider by name
     * 
     * Returns highest-priority available provider for the specified filter name.
     * Returns null if no provider found or none available.
     * 
     * @param string $filterName Filter name (e.g., 'oil_painting', 'ascii_art')
     * @return FilterProviderInterface|null Provider or null if not found/available
     */
    public function getFilterProvider(string $filterName): ?FilterProviderInterface
    {
        $providers = $this->filterProviders[$filterName] ?? [];
        
        foreach ($providers as $provider) {
            if ($provider->isAvailable()) {
                return $provider;
            }
        }
        
        return null;
    }
    
    // =========================================================================
    // Watermark Placement Provider Methods (v2.0.0+ stub, full v2.1.0)
    // =========================================================================
    
    /**
     * Register watermark placement provider
     * 
     * STUB METHOD - Full implementation in v2.1.0
     * 
     * Multiple providers can register the same strategy name. They are sorted by
     * priority, with highest priority selected when strategy is used.
     * 
     * @param WatermarkPlacementProviderInterface $provider Provider instance
     * @return void
     */
    public function registerWatermarkPlacementProvider(WatermarkPlacementProviderInterface $provider): void
    {
        $strategyName = $provider->getStrategyName();
        
        if (!isset($this->watermarkPlacementProviders[$strategyName])) {
            $this->watermarkPlacementProviders[$strategyName] = [];
        }
        
        $this->watermarkPlacementProviders[$strategyName][] = $provider;
        
        // Sort by priority descending (highest priority first)
        usort($this->watermarkPlacementProviders[$strategyName], function($a, $b) {
            return $b->getPriority() <=> $a->getPriority();
        });
    }
    
    /**
     * Get watermark placement provider by strategy name
     * 
     * STUB METHOD - Full implementation in v2.1.0
     * 
     * Returns highest-priority available provider for the specified strategy.
     * Returns null if no provider found or none available.
     * 
     * @param string $strategyName Strategy name (e.g., 'smart', 'face_aware', 'rule_thirds')
     * @return WatermarkPlacementProviderInterface|null Provider or null if not found/available
     */
    public function getWatermarkPlacementProvider(string $strategyName): ?WatermarkPlacementProviderInterface
    {
        $providers = $this->watermarkPlacementProviders[$strategyName] ?? [];
        
        foreach ($providers as $provider) {
            if ($provider->isAvailable()) {
                return $provider;
            }
        }
        
        return null;
    }
    
    // =========================================================================
    // Compression Provider Methods (v2.0.0+ stub, full v2.1.0)
    // =========================================================================
    
    /**
     * Register compression provider
     * 
     * STUB METHOD - Full implementation in v2.1.0
     * 
     * Providers are automatically sorted by priority (highest first).
     * 
     * @param CompressionProviderInterface $provider Provider instance
     * @return void
     */
    public function registerCompressionProvider(CompressionProviderInterface $provider): void
    {
        $this->compressionProviders[$provider->getName()] = $provider;
        
        // Sort by priority descending (highest priority first)
        uasort($this->compressionProviders, function($a, $b) {
            return $b->getPriority() <=> $a->getPriority();
        });
    }
    
    /**
     * Get best compression provider for image format
     * 
     * STUB METHOD - Full implementation in v2.1.0
     * 
     * Returns highest-priority available provider that supports the specified format.
     * Returns null if no provider found for format.
     * 
     * @param string $format Image MIME type (e.g., 'image/jpeg', 'image/png')
     * @return CompressionProviderInterface|null Provider or null if not found
     */
    public function getCompressionProvider(string $format): ?CompressionProviderInterface
    {
        foreach ($this->compressionProviders as $provider) {
            if ($provider->isAvailable() && 
                in_array($format, $provider->getSupportedFormats())) {
                return $provider;
            }
        }
        
        return null;
    }
}
