<?php

namespace LWVendor\PhpOffice\PhpSpreadsheet\Calculation\LookupRef;

use LWVendor\PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use LWVendor\PhpOffice\PhpSpreadsheet\Calculation\Functions;
use LWVendor\PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use LWVendor\PhpOffice\PhpSpreadsheet\Cell\Cell;
use LWVendor\PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use LWVendor\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class Offset
{
    /**
     * OFFSET.
     *
     * Returns a reference to a range that is a specified number of rows and columns from a cell or range of cells.
     * The reference that is returned can be a single cell or a range of cells. You can specify the number of rows and
     * the number of columns to be returned.
     *
     * Excel Function:
     *        =OFFSET(cellAddress, rows, cols, [height], [width])
     *
     * @param null|string $cellAddress The reference from which you want to base the offset.
     *                                     Reference must refer to a cell or range of adjacent cells;
     *                                     otherwise, OFFSET returns the #VALUE! error value.
     * @param mixed $rows The number of rows, up or down, that you want the upper-left cell to refer to.
     *                        Using 5 as the rows argument specifies that the upper-left cell in the
     *                        reference is five rows below reference. Rows can be positive (which means
     *                        below the starting reference) or negative (which means above the starting
     *                        reference).
     * @param mixed $columns The number of columns, to the left or right, that you want the upper-left cell
     *                           of the result to refer to. Using 5 as the cols argument specifies that the
     *                           upper-left cell in the reference is five columns to the right of reference.
     *                           Cols can be positive (which means to the right of the starting reference)
     *                           or negative (which means to the left of the starting reference).
     * @param mixed $height The height, in number of rows, that you want the returned reference to be.
     *                          Height must be a positive number.
     * @param mixed $width The width, in number of columns, that you want the returned reference to be.
     *                         Width must be a positive number.
     *
     * @return array|int|string An array containing a cell or range of cells, or a string on error
     */
    public static function OFFSET($cellAddress = null, $rows = 0, $columns = 0, $height = null, $width = null, ?Cell $cell = null)
    {
        $rows = Functions::flattenSingleValue($rows);
        $columns = Functions::flattenSingleValue($columns);
        $height = Functions::flattenSingleValue($height);
        $width = Functions::flattenSingleValue($width);
        if ($cellAddress === null || $cellAddress === '') {
            return ExcelError::VALUE();
        }
        if (!\is_object($cell)) {
            return ExcelError::REF();
        }
        [$cellAddress, $worksheet] = self::extractWorksheet($cellAddress, $cell);
        $startCell = $endCell = $cellAddress;
        if (\strpos($cellAddress, ':')) {
            [$startCell, $endCell] = \explode(':', $cellAddress);
        }
        [$startCellColumn, $startCellRow] = Coordinate::coordinateFromString($startCell);
        [$endCellColumn, $endCellRow] = Coordinate::coordinateFromString($endCell);
        $startCellRow += $rows;
        $startCellColumn = Coordinate::columnIndexFromString($startCellColumn) - 1;
        $startCellColumn += $columns;
        if ($startCellRow <= 0 || $startCellColumn < 0) {
            return ExcelError::REF();
        }
        $endCellColumn = self::adjustEndCellColumnForWidth($endCellColumn, $width, $startCellColumn, $columns);
        $startCellColumn = Coordinate::stringFromColumnIndex($startCellColumn + 1);
        $endCellRow = self::adustEndCellRowForHeight($height, $startCellRow, $rows, $endCellRow);
        if ($endCellRow <= 0 || $endCellColumn < 0) {
            return ExcelError::REF();
        }
        $endCellColumn = Coordinate::stringFromColumnIndex($endCellColumn + 1);
        $cellAddress = "{$startCellColumn}{$startCellRow}";
        if ($startCellColumn != $endCellColumn || $startCellRow != $endCellRow) {
            $cellAddress .= ":{$endCellColumn}{$endCellRow}";
        }
        return self::extractRequiredCells($worksheet, $cellAddress);
    }
    /** @return mixed */
    private static function extractRequiredCells(?Worksheet $worksheet, string $cellAddress)
    {
        return Calculation::getInstance($worksheet !== null ? $worksheet->getParent() : null)->extractCellRange($cellAddress, $worksheet, \false);
    }
    private static function extractWorksheet(?string $cellAddress, Cell $cell) : array
    {
        $cellAddress = self::assessCellAddress($cellAddress ?? '', $cell);
        $sheetName = '';
        if (\strpos($cellAddress, '!') !== \false) {
            [$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, \true);
            $sheetName = \trim($sheetName, "'");
        }
        $worksheet = $sheetName !== '' ? $cell->getWorksheet()->getParentOrThrow()->getSheetByName($sheetName) : $cell->getWorksheet();
        return [$cellAddress, $worksheet];
    }
    private static function assessCellAddress(string $cellAddress, Cell $cell) : string
    {
        if (\preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/mui', $cellAddress) !== \false) {
            $cellAddress = Functions::expandDefinedName($cellAddress, $cell);
        }
        return $cellAddress;
    }
    /**
     * @param mixed $width
     * @param mixed $columns
     */
    private static function adjustEndCellColumnForWidth(string $endCellColumn, $width, int $startCellColumn, $columns) : int
    {
        $endCellColumn = Coordinate::columnIndexFromString($endCellColumn) - 1;
        if ($width !== null && !\is_object($width)) {
            $endCellColumn = $startCellColumn + (int) $width - 1;
        } else {
            $endCellColumn += (int) $columns;
        }
        return $endCellColumn;
    }
    /**
     * @param mixed $height
     * @param mixed $rows
     * @param mixed $endCellRow
     */
    private static function adustEndCellRowForHeight($height, int $startCellRow, $rows, $endCellRow) : int
    {
        if ($height !== null && !\is_object($height)) {
            $endCellRow = $startCellRow + (int) $height - 1;
        } else {
            $endCellRow += (int) $rows;
        }
        return $endCellRow;
    }
}
