<?php

/**
 * InternalHelpersTest.php
 *
 * @since       2026-04-19
 * @category    Library
 * @package     Barcode
 * @author      Nicola Asuni <info@tecnick.com>
 * @copyright   2015-2026 Nicola Asuni - Tecnick.com LTD
 * @license     https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT)
 * @link        https://github.com/tecnickcom/tc-lib-barcode
 *
 * This file is part of tc-lib-barcode software library.
 */

namespace Test;

use PHPUnit\Framework\Attributes\DataProvider;
use Test\Fixture\InternalBarcodeType;
use Test\Fixture\InternalQrByteStream;

class InternalHelpersTest extends TestUtil
{
    public function testBaseTypeDefaultHooksAreCovered(): void
    {
        $type = new InternalBarcodeType(true);
        $data = $type->getArray();

        $this->assertSame([], $data['params']);
        $this->assertSame([], $data['bars']);
        $this->assertSame(4, $data['width']);
        $this->assertSame(3, $data['height']);
        $this->assertSame(['T' => 6, 'R' => 2, 'B' => 0, 'L' => 1], $data['padding']);

        $type->setBackgroundColor('');
        $this->assertNull($type->getArray()['bg_color_obj']);
    }

    public function testQrInputItemPaddingAndAppendBranches(): void
    {
        $helper = new InternalQrByteStream(
            \Com\Tecnick\Barcode\Type\Square\QrCode\Data::ENC_MODES['8B'],
            1,
            \Com\Tecnick\Barcode\Type\Square\QrCode\Data::ECC_LEVELS['L']
        );

        $item = $helper->exposeNewInputItem(
            \Com\Tecnick\Barcode\Type\Square\QrCode\Data::ENC_MODES['NM'],
            3,
            ['1']
        );

        $this->assertSame(['1', '0', '0'], $item['data']);
        $this->assertSame(-1, $helper->exposeLookAnTable(128));

        $items = $helper->exposeAppendNewInputItem(
            [],
            \Com\Tecnick\Barcode\Type\Square\QrCode\Data::ENC_MODES['AN'],
            2,
            ['A', 'Z']
        );

        $this->assertCount(1, $items);
        $this->assertSame(['A', 'Z'], $items[0]['data']);
    }

    /**
     * @param array<int, string> $data
     */
    #[DataProvider('getInvalidQrInputItemProvider')]
    public function testQrInputItemInvalidBranches(int $mode, int $size, array $data): void
    {
        $this->bcExpectException('\\' . \Com\Tecnick\Barcode\Exception::class);

        /** @var array<int, string> $data */

        $helper = new InternalQrByteStream(
            \Com\Tecnick\Barcode\Type\Square\QrCode\Data::ENC_MODES['8B'],
            1,
            \Com\Tecnick\Barcode\Type\Square\QrCode\Data::ECC_LEVELS['L']
        );

        $helper->exposeNewInputItem($mode, $size, $data);
    }

    /**
     * @return array<array{int, int, array<int, string>}>
     */
    public static function getInvalidQrInputItemProvider(): array
    {
        return [
            [\Com\Tecnick\Barcode\Type\Square\QrCode\Data::ENC_MODES['AN'], 1, ["\x80"]],
            [\Com\Tecnick\Barcode\Type\Square\QrCode\Data::ENC_MODES['KJ'], 1, ["\x81"]],
            [999, 1, ['A']],
            [\Com\Tecnick\Barcode\Type\Square\QrCode\Data::ENC_MODES['NM'], 0, ['1']],
        ];
    }

    public function testQrByteStreamPaddingAndBitstreamBranches(): void
    {
        $helper = new InternalQrByteStream(
            \Com\Tecnick\Barcode\Type\Square\QrCode\Data::ENC_MODES['8B'],
            1,
            \Com\Tecnick\Barcode\Type\Square\QrCode\Data::ECC_LEVELS['L']
        );
        $spec = new \Com\Tecnick\Barcode\Type\Square\QrCode\Spec();
        $maxWords = $spec->getDataLength(1, \Com\Tecnick\Barcode\Type\Square\QrCode\Data::ECC_LEVELS['L']);
        $maxBits = ($maxWords * 8);

        $this->assertSame([], $helper->exposeAppendPaddingBit([]));

        $exact = \array_fill(0, $maxBits, 0);
        $this->assertSame($exact, $helper->exposeAppendPaddingBit($exact));

        $short = \array_fill(0, ($maxBits - 2), 1);
        $this->assertCount($maxBits, $helper->exposeAppendPaddingBit($short));

        $padded = $helper->exposeAppendPaddingBit([1, 0, 1, 0]);
        $this->assertCount($maxWords, $helper->exposeBitstreamToByte($padded));

        $this->assertSame([], $helper->exposeBitstreamToByte([]));
        $this->assertSame([21], $helper->exposeBitstreamToByte([1, 0, 1, 0, 1]));
    }

    public function testQrByteStreamCreateAndConvertBranches(): void
    {
        $helper = new InternalQrByteStream(
            \Com\Tecnick\Barcode\Type\Square\QrCode\Data::ENC_MODES['8B'],
            1,
            \Com\Tecnick\Barcode\Type\Square\QrCode\Data::ECC_LEVELS['L']
        );

        $item = $helper->exposeNewInputItem(
            \Com\Tecnick\Barcode\Type\Square\QrCode\Data::ENC_MODES['NM'],
            3,
            ['1', '2', '3']
        );
        $stream = $helper->exposeCreateBitStream([$item]);
        $this->assertGreaterThan(0, $stream[1]);

        $helper->forcedEstVer = 2;
        $helper->forcedMinVer = 2;
        $encoded = $helper->encodeBitStream($item, 2);
        $helper->queuedCbs = [
            [[$encoded], \count($encoded['bstream'])],
        ];

        $converted = $helper->exposeConvertData([$item]);
        $this->assertSame($encoded['bstream'], $converted[0]['bstream']);
    }

    public function testQrByteStreamNegativeBitsThrowsException(): void
    {
        $this->bcExpectException('\\' . \Com\Tecnick\Barcode\Exception::class);

        $helper = new InternalQrByteStream(
            \Com\Tecnick\Barcode\Type\Square\QrCode\Data::ENC_MODES['8B'],
            1,
            \Com\Tecnick\Barcode\Type\Square\QrCode\Data::ECC_LEVELS['L']
        );
        $helper->forcedEstVer = 1;
        $helper->queuedCbs = [
            [[], -1],
        ];

        $helper->exposeConvertData([]);
    }

    public function testQrByteStreamEncodeBranches(): void
    {
        $helper = new InternalQrByteStream(
            \Com\Tecnick\Barcode\Type\Square\QrCode\Data::ENC_MODES['8B'],
            1,
            \Com\Tecnick\Barcode\Type\Square\QrCode\Data::ECC_LEVELS['L']
        );

        $spec = new \Com\Tecnick\Barcode\Type\Square\QrCode\Spec();
        $words = $spec->maximumWords(\Com\Tecnick\Barcode\Type\Square\QrCode\Data::ENC_MODES['8B'], 1);
        $item = $helper->exposeNewInputItem(
            \Com\Tecnick\Barcode\Type\Square\QrCode\Data::ENC_MODES['8B'],
            ($words + 1),
            \array_fill(0, ($words + 1), 'A')
        );

        $encoded = $helper->encodeBitStream($item, 1);
        $this->assertNotEmpty($encoded['bstream']);
    }

    public function testQrByteStreamEncodeInvalidModeThrowsException(): void
    {
        $this->bcExpectException('\\' . \Com\Tecnick\Barcode\Exception::class);

        $helper = new InternalQrByteStream(
            \Com\Tecnick\Barcode\Type\Square\QrCode\Data::ENC_MODES['8B'],
            1,
            \Com\Tecnick\Barcode\Type\Square\QrCode\Data::ECC_LEVELS['L']
        );

        $helper->encodeBitStream([
            'mode' => 999,
            'size' => 1,
            'data' => ['A'],
            'bstream' => [],
        ], 1);
    }

    public function testDatamatrixPaddingSizeHelper(): void
    {
        $params = \Com\Tecnick\Barcode\Type\Square\Datamatrix\Data::getPaddingSize('S', 1);
        $this->assertCount(16, $params);

        $this->bcExpectException('\\' . \Com\Tecnick\Barcode\Exception::class);
        \Com\Tecnick\Barcode\Type\Square\Datamatrix\Data::getPaddingSize('S', PHP_INT_MAX);
    }
}
