<?php

/**
 * CellTest.php
 *
 * @since       2002-08-03
 * @category    Library
 * @package     Pdf
 * @author      Nicola Asuni <info@tecnick.com>
 * @copyright   2002-2026 Nicola Asuni - Tecnick.com LTD
 * @license     https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
 * @link        https://github.com/tecnickcom/tc-lib-pdf
 *
 * This file is part of tc-lib-pdf software library.
 */

namespace Test;

/**
 * @phpstan-import-type TCellDef from \Com\Tecnick\Pdf\Cell
 */
class CellTest extends TestUtil
{
    /** @throws \Throwable */
    protected function getTestObject(): \Com\Tecnick\Pdf\Tcpdf
    {
        return new \Com\Tecnick\Pdf\Tcpdf();
    }

    /** @throws \Throwable */
    protected function getInternalTestObject(): TestableCell
    {
        return new TestableCell();
    }

    /** @throws \Throwable */
    public function testSetDefaultCellMarginStoresPointValues(): void
    {
        $obj = $this->getTestObject();
        $obj->setDefaultCellMargin(1.0, 2.0, 3.0, 4.0);

        /** @var TCellDef $defcell */
        $defcell = $this->getObjectProperty($obj, 'defcell');
        $this->bcAssertEqualsWithDelta($obj->toPoints(1.0), $defcell['margin']['T']);
        $this->bcAssertEqualsWithDelta($obj->toPoints(2.0), $defcell['margin']['R']);
        $this->bcAssertEqualsWithDelta($obj->toPoints(3.0), $defcell['margin']['B']);
        $this->bcAssertEqualsWithDelta($obj->toPoints(4.0), $defcell['margin']['L']);
    }

    /** @throws \Throwable */
    public function testSetDefaultCellPaddingStoresPointValues(): void
    {
        $obj = $this->getTestObject();
        $obj->setDefaultCellPadding(0.5, 1.5, 2.5, 3.5);

        /** @var TCellDef $defcell */
        $defcell = $this->getObjectProperty($obj, 'defcell');
        $this->bcAssertEqualsWithDelta($obj->toPoints(0.5), $defcell['padding']['T']);
        $this->bcAssertEqualsWithDelta($obj->toPoints(1.5), $defcell['padding']['R']);
        $this->bcAssertEqualsWithDelta($obj->toPoints(2.5), $defcell['padding']['B']);
        $this->bcAssertEqualsWithDelta($obj->toPoints(3.5), $defcell['padding']['L']);
    }

    /** @throws \Throwable */
    public function testSetDefaultCellBorderPosStoresValidValueAndDefaultsInvalid(): void
    {
        $obj = $this->getTestObject();
        $obj->setDefaultCellBorderPos(\Com\Tecnick\Pdf\Base::BORDERPOS_INTERNAL);

        /** @var TCellDef $defcell */
        $defcell = $this->getObjectProperty($obj, 'defcell');
        $this->assertSame(\Com\Tecnick\Pdf\Base::BORDERPOS_INTERNAL, $defcell['borderpos']);

        $obj->setDefaultCellBorderPos(99.0);
        /** @var TCellDef $defcell */
        $defcell = $this->getObjectProperty($obj, 'defcell');
        $this->assertSame(\Com\Tecnick\Pdf\Base::BORDERPOS_DEFAULT, $defcell['borderpos']);
    }

    /** @throws \Throwable */
    public function testAdjustMinCellPaddingIncreasesPaddingWithBorderStyle(): void
    {
        $obj = $this->getInternalTestObject();
        /** @var TCellDef $cell */
        $cell = $this->getObjectProperty($obj, 'defcell');
        $cell['padding'] = ['T' => 0.0, 'R' => 0.0, 'B' => 0.0, 'L' => 0.0];
        $styles = ['all' => ['lineWidth' => 1.0]];

        $out = $obj->exposeAdjustMinCellPadding($styles, $cell);

        $this->assertGreaterThanOrEqual(0.0, $out['padding']['T']);
        $this->assertGreaterThanOrEqual(0.0, $out['padding']['R']);
    }

    /** @throws \Throwable */
    public function testAdjustMinCellPaddingSupportsSideSpecificWidths(): void
    {
        $obj = $this->getInternalTestObject();
        /** @var TCellDef $cell */
        $cell = $this->getObjectProperty($obj, 'defcell');
        $cell['padding'] = ['T' => 0.0, 'R' => 0.0, 'B' => 0.0, 'L' => 0.0];

        $styles = [
            0 => ['lineWidth' => 0.5],
            1 => ['lineWidth' => 1.0],
            2 => ['lineWidth' => 1.5],
            3 => ['lineWidth' => 2.0],
        ];

        $out = $obj->exposeAdjustMinCellPadding($styles, $cell);

        $this->assertGreaterThan(0.0, $out['padding']['T']);
        $this->assertGreaterThan(0.0, $out['padding']['R']);
        $this->assertGreaterThan(0.0, $out['padding']['B']);
        $this->assertGreaterThan(0.0, $out['padding']['L']);
    }

    /** @throws \Throwable */
    public function testAdjustMinCellPaddingUsesCurrentStyleAndInvalidStyleFallback(): void
    {
        $obj = $this->getInternalTestObject();
        /** @var TCellDef $cell */
        $cell = $this->getObjectProperty($obj, 'defcell');

        $outCurrent = $obj->exposeAdjustMinCellPadding([], $cell);
        $outInvalid = $obj->exposeAdjustMinCellPadding(['all' => []], $cell);

        $this->assertArrayHasKey('padding', $outCurrent);
        $this->assertSame($cell, $outInvalid);
    }

    /** @throws \Throwable */
    public function testCellMinHeightReturnsPositiveForCenterAlign(): void
    {
        $obj = $this->getInternalTestObject();
        $this->initFontAndPage($obj);

        $out = $obj->exposeCellMinHeight(10.0, 'C');

        $this->assertGreaterThan(0.0, $out);
    }

    /** @throws \Throwable */
    public function testCellMinWidthHandlesCenterAlignment(): void
    {
        $obj = $this->getInternalTestObject();

        $out = $obj->exposeCellMinWidth(20.0, 'C');

        $this->assertGreaterThanOrEqual(20.0, $out);
    }

    /** @throws \Throwable */
    public function testCellPositionHelpersReturnFloats(): void
    {
        $obj = $this->getInternalTestObject();
        /** @var TCellDef $cell */
        $cell = $this->getObjectProperty($obj, 'defcell');

        $verticalPos = $obj->exposeCellVPos(10.0, 5.0, 'T', $cell);
        $horizontalPos = $obj->exposeCellHPos(10.0, 5.0, 'L', $cell);

        $this->assertGreaterThan(-1000000.0, $verticalPos);
        $this->assertGreaterThan(-1000000.0, $horizontalPos);
    }

    /** @throws \Throwable */
    public function testCellTextAlignHelpersReturnFloats(): void
    {
        $obj = $this->getInternalTestObject();
        $this->initFontAndPage($obj);
        /** @var TCellDef $cell */
        $cell = $this->getObjectProperty($obj, 'defcell');

        $verticalAlign = $obj->exposeCellTextVAlign(20.0, 10.0, 'C', $cell);
        $horizontalAlign = $obj->exposeCellTextHAlign(30.0, 12.0, 'C', $cell);

        $this->assertGreaterThan(-1000000.0, $verticalAlign);
        $this->assertGreaterThan(-1000000.0, $horizontalAlign);
    }

    /** @throws \Throwable */
    public function testCellAlignmentBranchesHandleAllVariants(): void
    {
        $obj = $this->getInternalTestObject();
        $this->initFontAndPage($obj);
        /** @var TCellDef $cell */
        $cell = $this->getObjectProperty($obj, 'defcell');

        $this->assertGreaterThan(-1000000.0, $obj->exposeCellMinHeight(10.0, 'T', $cell));
        $this->assertGreaterThan(-1000000.0, $obj->exposeCellMinHeight(0.0, 'C'));
        $this->assertGreaterThan(-1000000.0, $obj->exposeCellMinHeight(10.0, 'L', $cell));
        $this->assertGreaterThan(-1000000.0, $obj->exposeCellMinHeight(10.0, 'A', $cell));
        $this->assertGreaterThan(-1000000.0, $obj->exposeCellMinHeight(10.0, 'D', $cell));

        $this->assertGreaterThanOrEqual(20.0, $obj->exposeCellMinWidth(20.0, 'J'));

        $this->assertGreaterThan(-1000000.0, $obj->exposeCellVPos(10.0, 5.0, 'C', $cell));
        $this->assertGreaterThan(-1000000.0, $obj->exposeCellVPos(10.0, 5.0, 'B', $cell));
        $this->assertGreaterThan(-1000000.0, $obj->exposeCellVPos(10.0, 5.0, 'X', $cell));
        $this->assertGreaterThan(-1000000.0, $obj->exposeCellVPos(10.0, 5.0, 'T'));

        $this->assertGreaterThan(-1000000.0, $obj->exposeCellHPos(10.0, 5.0, 'R', $cell));
        $this->assertGreaterThan(-1000000.0, $obj->exposeCellHPos(10.0, 5.0, 'C', $cell));
        $this->assertGreaterThan(-1000000.0, $obj->exposeCellHPos(10.0, 5.0, 'J', $cell));
        $this->assertGreaterThan(-1000000.0, $obj->exposeCellHPos(10.0, 5.0, 'L'));

        $this->assertGreaterThan(-1000000.0, $obj->exposeCellTextVAlign(20.0, 10.0, 'T', $cell));
        $this->assertGreaterThan(-1000000.0, $obj->exposeCellTextVAlign(20.0, 10.0, 'B', $cell));
        $this->assertGreaterThan(-1000000.0, $obj->exposeCellTextVAlign(20.0, 10.0, 'L', $cell));
        $this->assertGreaterThan(-1000000.0, $obj->exposeCellTextVAlign(20.0, 10.0, 'A', $cell));
        $this->assertGreaterThan(-1000000.0, $obj->exposeCellTextVAlign(20.0, 10.0, 'D', $cell));
        $this->assertGreaterThan(-1000000.0, $obj->exposeCellTextVAlign(20.0, 0.0, 'C'));

        $this->assertGreaterThan(-1000000.0, $obj->exposeCellTextHAlign(30.0, 12.0, 'R', $cell));
        $this->assertGreaterThan(-1000000.0, $obj->exposeCellTextHAlign(30.0, 12.0, 'J', $cell));
        $this->assertGreaterThan(-1000000.0, $obj->exposeCellTextHAlign(30.0, 12.0, 'L'));

        $this->assertGreaterThan(0.0, $obj->exposeCellMaxWidth(0.0));
        $this->assertGreaterThan(0.0, $obj->exposeTextMaxWidth(50.0));
        $this->assertGreaterThan(-1000000.0, $obj->exposeTextMaxHeight(50.0, 'B', $cell));
        $this->assertGreaterThan(-1000000.0, $obj->exposeTextMaxHeight(50.0, 'L', $cell));
        $this->assertGreaterThan(-1000000.0, $obj->exposeTextMaxHeight(50.0, 'A', $cell));
        $this->assertGreaterThan(-1000000.0, $obj->exposeTextMaxHeight(50.0, 'D', $cell));
        $this->assertGreaterThan(-1000000.0, $obj->exposeTextMaxHeight(50.0, 'C', $cell));
    }

    /**
     * The text is aligned inside the padding box, so an asymmetric padding
     * must not be collapsed to the largest of the two opposite sides.
     *
     * @throws \Throwable
     */
    public function testCellTextVAlignKeepsTextInsideAsymmetricPadding(): void
    {
        $obj = $this->getInternalTestObject();
        $this->initFontAndPage($obj);
        /** @var TCellDef $cell */
        $cell = $this->getObjectProperty($obj, 'defcell');
        $cell['padding'] = ['T' => 60.0, 'R' => 0.0, 'B' => 10.0, 'L' => 0.0];

        $cellheight = 100.0;
        $txtheight = 10.0;

        // 60 + ((100 - 60 - 10 - 10) / 2)
        $this->bcAssertEqualsWithDelta(70.0, $obj->exposeCellTextVAlign($cellheight, $txtheight, 'C', $cell));

        foreach (['T', 'C', 'B', 'A', 'L', 'D'] as $align) {
            $ypos = $obj->exposeCellTextVAlign($cellheight, $txtheight, $align, $cell);
            $this->assertGreaterThanOrEqual($cell['padding']['T'], $ypos, 'align: ' . $align);
            $this->assertLessThanOrEqual($cellheight - $cell['padding']['B'], $ypos + $txtheight, 'align: ' . $align);
        }
    }

    /**
     * The horizontal counterpart: a centered text must start at the left
     * padding edge when the padding box exactly fits the text.
     *
     * @throws \Throwable
     */
    public function testCellTextHAlignKeepsTextInsideAsymmetricPadding(): void
    {
        $obj = $this->getInternalTestObject();
        $this->initFontAndPage($obj);
        /** @var TCellDef $cell */
        $cell = $this->getObjectProperty($obj, 'defcell');
        $cell['padding'] = ['T' => 0.0, 'R' => 0.0, 'B' => 0.0, 'L' => 40.0];

        // 40 + ((100 - 40 - 0 - 50) / 2)
        $this->bcAssertEqualsWithDelta(45.0, $obj->exposeCellTextHAlign(100.0, 50.0, 'C', $cell));
        $this->bcAssertEqualsWithDelta(40.0, $obj->exposeCellTextHAlign(100.0, 60.0, 'C', $cell));
    }

    /**
     * The minimum cell height must leave room for the text and both paddings,
     * and textMaxHeight() must be its inverse.
     *
     * @throws \Throwable
     */
    public function testCellMinHeightAndTextMaxHeightAreConsistentWithAsymmetricPadding(): void
    {
        $obj = $this->getInternalTestObject();
        $this->initFontAndPage($obj);
        /** @var TCellDef $cell */
        $cell = $this->getObjectProperty($obj, 'defcell');
        $cell['padding'] = ['T' => 60.0, 'R' => 0.0, 'B' => 10.0, 'L' => 0.0];

        $txtheight = 10.0;

        foreach (['T', 'C', 'B', 'A', 'L', 'D'] as $align) {
            $cellheight = $obj->exposeCellMinHeight($txtheight, $align, $cell);

            $this->bcAssertEqualsWithDelta($txtheight, $obj->exposeTextMaxHeight($cellheight, $align, $cell));

            $ypos = $obj->exposeCellTextVAlign($cellheight, $txtheight, $align, $cell);
            $this->assertGreaterThanOrEqual($cell['padding']['T'], $ypos, 'align: ' . $align);
            $this->assertLessThanOrEqual($cellheight - $cell['padding']['B'], $ypos + $txtheight, 'align: ' . $align);
        }
    }

    /** @throws \Throwable */
    public function testCellAndTextPositionConversionsAreCallable(): void
    {
        $obj = $this->getInternalTestObject();
        $this->initFontAndPage($obj);
        /** @var TCellDef $cell */
        $cell = $this->getObjectProperty($obj, 'defcell');

        $cellVerticalPos = $obj->exposeCellVPosFromText(10.0, 20.0, 10.0, 'C', $cell);
        $cellHorizontalPos = $obj->exposeCellHPosFromText(10.0, 30.0, 12.0, 'L', $cell);
        $textVerticalPos = $obj->exposeTextVPosFromCell(10.0, 20.0, 10.0, 'C', $cell);
        $textHorizontalPos = $obj->exposeTextHPosFromCell(10.0, 30.0, 12.0, 'L', $cell);

        $this->assertGreaterThan(-1000000.0, $cellVerticalPos);
        $this->assertGreaterThan(-1000000.0, $cellHorizontalPos);
        $this->assertGreaterThan(-1000000.0, $textVerticalPos);
        $this->assertGreaterThan(-1000000.0, $textHorizontalPos);
    }

    /** @throws \Throwable */
    public function testCellAndTextMaxHelpersReturnPositiveValues(): void
    {
        $obj = $this->getInternalTestObject();
        $this->initFontAndPage($obj);
        /** @var TCellDef $cell */
        $cell = $this->getObjectProperty($obj, 'defcell');

        $cellMax = $obj->exposeCellMaxWidth(0.0, $cell);
        $txtW = $obj->exposeTextMaxWidth(50.0, $cell);
        $txtH = $obj->exposeTextMaxHeight(50.0, 'T', $cell);

        $this->assertGreaterThan(0.0, $cellMax);
        $this->assertGreaterThan(0.0, $txtW);
        $this->assertGreaterThan(0.0, $txtH);
    }

    /** @throws \Throwable */
    public function testDrawCellReturnsEmptyWhenNoFillOrBorder(): void
    {
        $obj = $this->getInternalTestObject();
        $this->initFontAndPage($obj);

        $out = $obj->exposeDrawCell(10.0, 10.0, 20.0, 8.0, ['all' => []]);

        $this->assertSame('', $out);
    }

    /** @throws \Throwable */
    public function testDrawCellHandlesFillAndBorderBranches(): void
    {
        $obj = $this->getInternalTestObject();
        $this->initFontAndPage($obj);

        $fillAndBorder = $obj->exposeDrawCell(10.0, 10.0, 20.0, 8.0, ['all' => [
            'fillColor' => 'gray',
            'lineWidth' => 0.2,
        ]]);
        $this->assertNotSame('', $fillAndBorder);

        $fillOnly = $obj->exposeDrawCell(10.0, 10.0, 20.0, 8.0, ['all' => ['fillColor' => 'gray']]);
        $this->assertNotSame('', $fillOnly);

        $perSideBorder = $obj->exposeDrawCell(10.0, 10.0, 20.0, 8.0, [
            0 => ['lineWidth' => 0.2],
            1 => ['lineWidth' => 0.3],
            2 => ['lineWidth' => 0.4],
            3 => ['lineWidth' => 0.5],
            'all' => ['fillColor' => ''],
        ]);
        $this->assertNotSame('', $perSideBorder);

        $fallbackAdjustBorder = $obj->exposeDrawCell(10.0, 10.0, 20.0, 8.0, [
            1 => ['lineWidth' => 0.3],
            'all' => [],
        ]);
        $this->assertNotSame('', $fallbackAdjustBorder);
    }

    /** @throws \Throwable */
    public function testDrawCellIgnoresZeroWidthSideBorders(): void
    {
        $obj = $this->getInternalTestObject();
        $this->initFontAndPage($obj);

        $rightSideOnly = $obj->exposeDrawCell(10.0, 10.0, 20.0, 8.0, [
            1 => ['lineWidth' => 0.3],
            'all' => ['fillColor' => ''],
        ]);

        $rightWithZeroSides = $obj->exposeDrawCell(10.0, 10.0, 20.0, 8.0, [
            0 => ['lineWidth' => 0.0],
            1 => ['lineWidth' => 0.3],
            2 => ['lineWidth' => 0.0],
            3 => ['lineWidth' => 0.0],
            'all' => ['fillColor' => ''],
        ]);

        $this->assertNotSame('', $rightSideOnly);
        $this->assertSame($rightSideOnly, $rightWithZeroSides);
    }

    /** @throws \Throwable */
    public function testGetOutTextStringReturnsEscapedStringAndBomChangesOutput(): void
    {
        $obj = $this->getInternalTestObject();

        $textWithoutBom = $obj->exposeGetOutTextString('Hello', 1, false);
        $textWithBom = $obj->exposeGetOutTextString('Hello', 1, true);

        $this->assertNotSame('', $textWithoutBom);
        $this->assertNotSame('', $textWithBom);
        $this->assertNotSame($textWithoutBom, $textWithBom);
    }
}
