<?php

declare(strict_types=1);

namespace App\Framework\QrCode\ValueObjects;

use App\Framework\Exception\FrameworkException;

/**
 * QR Code Matrix
 *
 * Represents the complete QR Code matrix with all modules
 * Immutable - any modifications return a new instance
 */
final readonly class QrCodeMatrix
{
    /**
     * @param array<int, array<int, Module>> $modules 2D array of modules
     */
    private function __construct(
        private QrCodeVersion $version,
        private array $modules
    ) {}

    /**
     * Create empty matrix for given version
     */
    public static function create(QrCodeVersion $version): self
    {
        $size = $version->getMatrixSize();
        $modules = [];

        for ($row = 0; $row < $size; $row++) {
            $modules[$row] = [];
            for ($col = 0; $col < $size; $col++) {
                $modules[$row][$col] = Module::light(); // Default: light
            }
        }

        return new self($version, $modules);
    }

    /**
     * Get QR Code version
     */
    public function getVersion(): QrCodeVersion
    {
        return $this->version;
    }

    /**
     * Get matrix size
     */
    public function getSize(): int
    {
        return $this->version->getMatrixSize();
    }

    /**
     * Get module at position
     */
    public function getModule(ModulePosition $position): Module
    {
        if (!$position->isWithinBounds($this->getSize())) {
            throw FrameworkException::simple(
                "Position {$position->toString()} is out of bounds for matrix size {$this->getSize()}"
            );
        }

        return $this->modules[$position->row][$position->column];
    }

    /**
     * Get module at coordinates
     */
    public function getModuleAt(int $row, int $column): Module
    {
        return $this->getModule(ModulePosition::at($row, $column));
    }

    /**
     * Set module at position (returns new instance)
     */
    public function setModule(ModulePosition $position, Module $module): self
    {
        if (!$position->isWithinBounds($this->getSize())) {
            throw FrameworkException::simple(
                "Position {$position->toString()} is out of bounds for matrix size {$this->getSize()}"
            );
        }

        $modules = $this->modules;
        $modules[$position->row][$position->column] = $module;

        return new self($this->version, $modules);
    }

    /**
     * Set module at coordinates (returns new instance)
     */
    public function setModuleAt(int $row, int $column, Module $module): self
    {
        return $this->setModule(ModulePosition::at($row, $column), $module);
    }

    /**
     * Set dark module at position
     */
    public function setDark(ModulePosition $position): self
    {
        return $this->setModule($position, Module::dark());
    }

    /**
     * Set light module at position
     */
    public function setLight(ModulePosition $position): self
    {
        return $this->setModule($position, Module::light());
    }

    /**
     * Check if position is dark
     */
    public function isDark(ModulePosition $position): bool
    {
        return $this->getModule($position)->isDark();
    }

    /**
     * Get all modules as 2D array
     */
    public function toArray(): array
    {
        return $this->modules;
    }

    /**
     * Convert to binary string (for debugging)
     */
    public function toBinaryString(): string
    {
        $result = '';
        $size = $this->getSize();

        for ($row = 0; $row < $size; $row++) {
            for ($col = 0; $col < $size; $col++) {
                $result .= $this->modules[$row][$col]->toBit();
            }
            $result .= "\n";
        }

        return $result;
    }

    /**
     * Convert to ASCII art (for debugging)
     */
    public function toAsciiArt(): string
    {
        $result = '';
        $size = $this->getSize();

        for ($row = 0; $row < $size; $row++) {
            for ($col = 0; $col < $size; $col++) {
                $result .= $this->modules[$row][$col]->toString();
            }
            $result .= "\n";
        }

        return $result;
    }

    /**
     * Count dark modules
     */
    public function countDarkModules(): int
    {
        $count = 0;
        $size = $this->getSize();

        for ($row = 0; $row < $size; $row++) {
            for ($col = 0; $col < $size; $col++) {
                if ($this->modules[$row][$col]->isDark()) {
                    $count++;
                }
            }
        }

        return $count;
    }
}
