<?php

declare(strict_types=1);

namespace HeroQR\Managers;

use Endroid\QrCode\Writer\WriterInterface;
use HeroQR\Contracts\Managers\AbstractWriterManager;

/**
 * Manages and creates QR code writer instances
 * 
 * This class handles the creation and validation of both standard and custom QR code writers.
 * It ensures proper initialization of writers based on the requested format and custom parameters.
 * 
 * @package HeroQR\Managers
 */
class WriterManager extends AbstractWriterManager
{
    /**
     * Maps standard format names to their respective writer classes
     */
    protected const STANDARD_WRITERS = [
        'png' => 'Endroid\\QrCode\\Writer\\PngWriter',
        'svg' => 'Endroid\\QrCode\\Writer\\SvgWriter',
        'eps' => 'Endroid\\QrCode\\Writer\\EpsWriter',
        'pdf' => 'Endroid\\QrCode\\Writer\\PdfWriter',
        'binary' => 'Endroid\\QrCode\\Writer\\BinaryWriter',
        'webp' => 'Endroid\\QrCode\\Writer\\WebPWriter',
        'gif' => 'Endroid\\QrCode\\Writer\\GifWriter'
    ];

    /**
     * Maps formats to their custom writer implementations
     */
    protected const CUSTOM_WRITERS = [
        'png' => 'HeroQR\Core\Writers\CustomPngWriter'
    ];

    /**
     * Defines valid prefixes for custom parameters
     */
    protected const CUSTOM_PREFIXES = [
        'Marker' => 'M',
        'Cursor' => 'C',
        'Shape' => 'S'
    ];

    /**
     * Returns a writer based on format and custom settings.
     *
     * @param string $format The desired output format.
     * @param array $customs (Optional) Custom settings for the writer.
     * @return WriterInterface A writer for the specified format and settings.
     */
    public function getWriter(string $format, array $customs = []): WriterInterface
    {
        $format = strtolower(trim($format));

        if (!isset(self::STANDARD_WRITERS[$format])) {
            throw new \InvalidArgumentException(
                "Unsupported format '{$format}'. Supported formats: " .
                    implode(', ', array_keys(self::STANDARD_WRITERS))
            );
        }

        return match (true) {
            $this->hasCustomParameters($customs) => $this->createCustomWriter($format, $customs),
            default => $this->createStandardWriter($format)
        };
    }

    /**
     * Returns a custom writer based on format and custom settings.
     *
     * @param string $format The desired output format.
     * @param array $customs Custom settings for the writer.
     * @return WriterInterface A custom writer for the specified format and settings.
     */
    protected function getCustomWriter(string $format, array $customs): WriterInterface
    {
        return $this->createCustomWriter($format, $customs);
    }

    /**
     * Returns a standard writer based on the format.
     *
     * @param string $format The desired output format.
     * @return WriterInterface A standard writer for the specified format.
     */
    protected function getStandardWriter(string $format): WriterInterface
    {
        return $this->createStandardWriter($format);
    }

    /**
     * Creates a custom writer instance with specified parameters
     * 
     * @param string $format The desired output format
     * @param array $customs Customization parameters
     * @return WriterInterface
     * @throws \InvalidArgumentException If custom writer is not supported for format
     * @throws \RuntimeException If writer class cannot be instantiated
     */
    protected function createCustomWriter(string $format, array $customs): WriterInterface
    {
        if (!isset(self::CUSTOM_WRITERS[$format])) {
            throw new \InvalidArgumentException("Custom writers not supported for '{$format}'");
        }

        $writerClass = self::CUSTOM_WRITERS[$format];

        if (!class_exists($writerClass)) {
            throw new \RuntimeException("Writer class '{$writerClass}' not found");
        }

        $parameters = array_map(
            fn(string $key, string $prefix) => $this->validatePattern($customs[$key] ?? "{$prefix}1", $prefix),
            array_keys(self::CUSTOM_PREFIXES),
            self::CUSTOM_PREFIXES
        );

        return new $writerClass(...$parameters);
    }

    /**
     * Creates a standard writer instance
     * 
     * @param string $format The desired output format
     * @return WriterInterface
     * @throws \RuntimeException If writer class cannot be instantiated
     */
    protected function createStandardWriter(string $format): WriterInterface
    {
        $writerClass = self::STANDARD_WRITERS[$format];

        if (!class_exists($writerClass)) {
            throw new \RuntimeException("Writer class '{$writerClass}' not found");
        }

        return new $writerClass();
    }

    /**
     * Checks if the customs array contains any valid custom parameters
     * 
     * @param array $customs The customs array to check
     * @return bool True if valid custom parameters exist
     */
    protected function hasCustomParameters(array $customs): bool
    {
        return (bool) array_intersect_key($customs, self::CUSTOM_PREFIXES);
    }

    /**
     * Validates and normalizes a custom pattern string
     * 
     * @param string $value The pattern value to validate
     * @param string $prefix The expected prefix (M, C, or S)
     * @return string The validated and normalized pattern
     * @throws \InvalidArgumentException If pattern is invalid
     */
    protected function validatePattern(string $value, string $prefix): string
    {
        $value = strtoupper(trim($value));
        if (!preg_match("/^{$prefix}\d{1,2}$/", $value)) {
            throw new \InvalidArgumentException("Invalid pattern '{$value}' for prefix '{$prefix}'");
        }
        return $value;
    }
}
