<?php

declare(strict_types=1);

use App\Framework\QrCode\ValueObjects\Module;
use App\Framework\QrCode\ValueObjects\ModulePosition;
use App\Framework\QrCode\ValueObjects\QrCodeMatrix;
use App\Framework\QrCode\ValueObjects\QrCodeVersion;

test('can create empty matrix', function () {
    $version = QrCodeVersion::fromNumber(1);
    $matrix = QrCodeMatrix::create($version);

    expect($matrix->getSize())->toBe(21)
        ->and($matrix->getVersion()->getVersionNumber())->toBe(1);
});

test('empty matrix has all light modules', function () {
    $version = QrCodeVersion::fromNumber(1);
    $matrix = QrCodeMatrix::create($version);

    $module = $matrix->getModuleAt(0, 0);

    expect($module->isLight())->toBeTrue();
});

test('can set and get module', function () {
    $version = QrCodeVersion::fromNumber(1);
    $matrix = QrCodeMatrix::create($version);

    $position = ModulePosition::at(5, 5);
    $matrix = $matrix->setModule($position, Module::dark());

    expect($matrix->getModule($position)->isDark())->toBeTrue();
});

test('setting module returns new instance', function () {
    $version = QrCodeVersion::fromNumber(1);
    $matrix1 = QrCodeMatrix::create($version);

    $position = ModulePosition::at(5, 5);
    $matrix2 = $matrix1->setModule($position, Module::dark());

    // Original matrix unchanged (immutability)
    expect($matrix1->getModule($position)->isLight())->toBeTrue()
        ->and($matrix2->getModule($position)->isDark())->toBeTrue();
});

test('can set module using coordinates', function () {
    $version = QrCodeVersion::fromNumber(1);
    $matrix = QrCodeMatrix::create($version);

    $matrix = $matrix->setModuleAt(10, 10, Module::dark());

    expect($matrix->getModuleAt(10, 10)->isDark())->toBeTrue();
});

test('can use helper methods setDark and setLight', function () {
    $version = QrCodeVersion::fromNumber(1);
    $matrix = QrCodeMatrix::create($version);

    $pos1 = ModulePosition::at(5, 5);
    $pos2 = ModulePosition::at(10, 10);

    $matrix = $matrix->setDark($pos1);
    $matrix = $matrix->setLight($pos2);

    expect($matrix->getModule($pos1)->isDark())->toBeTrue()
        ->and($matrix->getModule($pos2)->isLight())->toBeTrue();
});

test('can check if position is dark', function () {
    $version = QrCodeVersion::fromNumber(1);
    $matrix = QrCodeMatrix::create($version);

    $position = ModulePosition::at(5, 5);
    $matrix = $matrix->setDark($position);

    expect($matrix->isDark($position))->toBeTrue();
});

test('throws exception for out of bounds position', function () {
    $version = QrCodeVersion::fromNumber(1); // Size = 21
    $matrix = QrCodeMatrix::create($version);

    $position = ModulePosition::at(25, 25); // Out of bounds

    $matrix->getModule($position);
})->throws(\App\Framework\Exception\FrameworkException::class);

test('can convert to array', function () {
    $version = QrCodeVersion::fromNumber(1);
    $matrix = QrCodeMatrix::create($version);

    $array = $matrix->toArray();

    expect($array)->toBeArray()
        ->and(count($array))->toBe(21)
        ->and(count($array[0]))->toBe(21);
});

test('can convert to binary string', function () {
    $version = QrCodeVersion::fromNumber(1);
    $matrix = QrCodeMatrix::create($version);

    $matrix = $matrix->setModuleAt(0, 0, Module::dark());
    $matrix = $matrix->setModuleAt(0, 1, Module::light());

    $binary = $matrix->toBinaryString();

    expect($binary)->toBeString()
        ->and($binary)->toContain('1')
        ->and($binary)->toContain('0')
        ->and($binary)->toContain("\n");
});

test('can convert to ASCII art', function () {
    $version = QrCodeVersion::fromNumber(1);
    $matrix = QrCodeMatrix::create($version);

    $matrix = $matrix->setModuleAt(0, 0, Module::dark());
    $matrix = $matrix->setModuleAt(0, 1, Module::light());

    $ascii = $matrix->toAsciiArt();

    expect($ascii)->toBeString()
        ->and($ascii)->toContain('█')
        ->and($ascii)->toContain('░');
});

test('can count dark modules', function () {
    $version = QrCodeVersion::fromNumber(1);
    $matrix = QrCodeMatrix::create($version);

    // Add some dark modules
    $matrix = $matrix->setModuleAt(0, 0, Module::dark());
    $matrix = $matrix->setModuleAt(1, 1, Module::dark());
    $matrix = $matrix->setModuleAt(2, 2, Module::dark());

    $count = $matrix->countDarkModules();

    expect($count)->toBe(3);
});

test('matrix size scales with version', function () {
    $matrix1 = QrCodeMatrix::create(QrCodeVersion::fromNumber(1));
    $matrix2 = QrCodeMatrix::create(QrCodeVersion::fromNumber(2));
    $matrix3 = QrCodeMatrix::create(QrCodeVersion::fromNumber(3));

    expect($matrix1->getSize())->toBe(21)
        ->and($matrix2->getSize())->toBe(25)
        ->and($matrix3->getSize())->toBe(29);
});
