<?php

namespace PhpOffice\PhpSpreadsheet\Calculation\LookupRef;

use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;

class ChooseRowsEtc
{
    /**
     * Transpose 2-dimensional array.
     * See https://stackoverflow.com/questions/797251/transposing-multidimensional-arrays-in-php
     * especially the comment from user17994717.
     *
     * @param mixed[] $array
     *
     * @return mixed[]
     */
    public static function transpose(array $array): array
    {
        return empty($array) ? [] : (array_map((count($array) === 1) ? (fn ($x) => [$x]) : null, ...$array)); // @phpstan-ignore-line
    }

    /** @return mixed[] */
    private static function arrayValues(mixed $array): array
    {
        return is_array($array) ? array_values($array) : [$array];
    }

    /**
     * CHOOSECOLS.
     *
     * @param mixed $input expecting two-dimensional array
     *
     * @return mixed[]|string
     */
    public static function chooseCols(mixed $input, mixed ...$args): array|string
    {
        if (!is_array($input)) {
            $input = [[$input]];
        }
        $retval = self::chooseRows(self::transpose($input), ...$args);

        return is_array($retval) ? self::transpose($retval) : $retval;
    }

    /**
     * CHOOSEROWS.
     *
     * @param mixed $input expecting two-dimensional array
     *
     * @return mixed[]|string
     */
    public static function chooseRows(mixed $input, mixed ...$args): array|string
    {
        if (!is_array($input)) {
            $input = [[$input]];
        }
        $inputArray = [[]]; // no row 0
        $numRows = 0;
        foreach ($input as $inputRow) {
            $inputArray[] = self::arrayValues($inputRow);
            ++$numRows;
        }
        $outputArray = [];
        foreach (Functions::flattenArray2(...$args) as $arg) {
            if (!is_numeric($arg)) {
                return ExcelError::VALUE();
            }
            $index = (int) $arg;
            if ($index < 0) {
                $index += $numRows + 1;
            }
            if ($index <= 0 || $index > $numRows) {
                return ExcelError::VALUE();
            }
            $outputArray[] = $inputArray[$index];
        }

        return $outputArray;
    }

    /**
     * @param mixed[] $array
     *
     * @return mixed[]|string
     */
    private static function dropRows(array $array, mixed $offset): array|string
    {
        if ($offset === null) {
            return $array;
        }
        if (!is_numeric($offset)) {
            return ExcelError::VALUE();
        }
        $offset = (int) $offset;
        $count = count($array);
        if (abs($offset) >= $count) {
            // In theory, this should be #CALC!, but Excel treats
            // #CALC! as corrupt, and it's not worth figuring out why
            return ExcelError::VALUE();
        }
        if ($offset === 0) {
            return $array;
        }
        if ($offset > 0) {
            return array_slice($array, $offset);
        }

        return array_slice($array, 0, $count + $offset);
    }

    /**
     * DROP.
     *
     * @param mixed $input expect two-dimensional array
     *
     * @return mixed[]|string
     */
    public static function drop(mixed $input, mixed $rows = null, mixed $columns = null): array|string
    {
        if (!is_array($input)) {
            $input = [[$input]];
        }
        $inputArray = []; // no row 0
        foreach ($input as $inputRow) {
            $inputArray[] = self::arrayValues($inputRow);
        }
        $outputArray1 = self::dropRows($inputArray, $rows);
        if (is_string($outputArray1)) {
            return $outputArray1;
        }
        $outputArray2 = self::transpose($outputArray1);
        $outputArray3 = self::dropRows($outputArray2, $columns);
        if (is_string($outputArray3)) {
            return $outputArray3;
        }

        return self::transpose($outputArray3);
    }

    /**
     * @param mixed[] $array
     *
     * @return mixed[]|string
     */
    private static function takeRows(array $array, mixed $offset): array|string
    {
        if ($offset === null) {
            return $array;
        }
        if (!is_numeric($offset)) {
            return ExcelError::VALUE();
        }
        $offset = (int) $offset;
        if ($offset === 0) {
            // should be #CALC! - see above
            return ExcelError::VALUE();
        }
        $count = count($array);
        if (abs($offset) >= $count) {
            return $array;
        }
        if ($offset > 0) {
            return array_slice($array, 0, $offset);
        }

        return array_slice($array, $count + $offset);
    }

    /**
     * TAKE.
     *
     * @param mixed $input expecting two-dimensional array
     *
     * @return mixed[]|string
     */
    public static function take(mixed $input, mixed $rows, mixed $columns = null): array|string
    {
        if (!is_array($input)) {
            $input = [[$input]];
        }
        if ($rows === null && $columns === null) {
            return $input;
        }
        $inputArray = [];
        foreach ($input as $inputRow) {
            $inputArray[] = self::arrayValues($inputRow);
        }
        $outputArray1 = self::takeRows($inputArray, $rows);
        if (is_string($outputArray1)) {
            return $outputArray1;
        }
        $outputArray2 = self::transpose($outputArray1);
        $outputArray3 = self::takeRows($outputArray2, $columns);
        if (is_string($outputArray3)) {
            return $outputArray3;
        }

        return self::transpose($outputArray3);
    }

    /**
     * EXPAND.
     *
     * @param mixed $input expecting two-dimensional array
     *
     * @return mixed[]|string
     */
    public static function expand(mixed $input, mixed $rows, mixed $columns = null, mixed $pad = '#N/A'): array|string
    {
        if (!is_array($input)) {
            $input = [[$input]];
        }
        if ($rows === null && $columns === null) {
            return $input;
        }
        $numRows = count($input);
        $rows ??= $numRows;
        if (!is_numeric($rows)) {
            return ExcelError::VALUE();
        }
        $rows = (int) $rows;
        if ($rows < count($input)) {
            return ExcelError::VALUE();
        }
        $numCols = 0;
        foreach ($input as $inputRow) {
            $numCols = max($numCols, is_array($inputRow) ? count($inputRow) : 1);
        }
        $columns ??= $numCols;
        if (!is_numeric($columns)) {
            return ExcelError::VALUE();
        }
        $columns = (int) $columns;
        if ($columns < $numCols) {
            return ExcelError::VALUE();
        }
        $inputArray = [];
        foreach ($input as $inputRow) {
            $inputArray[] = array_pad(self::arrayValues($inputRow), $columns, $pad);
        }
        $outputArray = [];
        $padRow = array_pad([], $columns, $pad);
        for ($count = 0; $count < $rows; ++$count) {
            $outputArray[] = ($count >= $numRows) ? $padRow : $inputArray[$count];
        }

        return $outputArray;
    }
}
