<?php

namespace Psalm\Tests\Loop;

use Psalm\Tests\TestCase;
use Psalm\Tests\Traits\InvalidCodeAnalysisTestTrait;
use Psalm\Tests\Traits\ValidCodeAnalysisTestTrait;

use const DIRECTORY_SEPARATOR;

class ForTest extends TestCase
{
    use InvalidCodeAnalysisTestTrait;
    use ValidCodeAnalysisTestTrait;

    public function providerValidCodeParse(): iterable
    {
        return [
            'forTrue' => [
                'code' => '<?php
                    function ret(): int {
                        for (;;) {
                            return 1;
                        }
                    }',
            ],
            'implicitFourthLoop' => [
                'code' => '<?php
                    function test(): int {
                      $x = 0;
                      $y = 1;
                      $z = 2;
                      for ($i = 0; $i < 3; $i++) {
                        $x = $y;
                        $y = $z;
                        $z = 5;
                      }
                      return $x;
                    }',
            ],
            'falseToBoolInContinueAndBreak' => [
                'code' => '<?php
                    $a = false;

                    for ($i = 0; $i < 4; $i++) {
                      $j = rand(0, 10);

                      if ($j === 2) {
                        $a = true;
                        continue;
                      }

                      if ($j === 3) {
                        $a = true;
                        break;
                      }
                    }',
                'assertions' => [
                    '$a' => 'bool',
                ],
            ],
            'forLoopwithOKChange' => [
                'code' => '<?php
                    $j = 5;
                    for ($i = $j; $i < 4; $i++) {
                      $j = 9;
                    }',
            ],
            'preventNegativeZeroScrewingThingsUp' => [
                'code' => '<?php
                    function foo() : void {
                      /** @var array<int, int> $v */
                      $v = [1 => 0];
                      for ($d = 0; $d <= 10; $d++) {
                        for ($k = -$d; $k <= $d; $k += 2) {
                          if ($k === -$d || ($k !== $d && $v[$k-1] < $v[$k+1])) {
                            $x = $v[$k+1];
                          } else {
                            $x = $v[$k-1] + 1;
                          }

                          $v[$k] = $x;
                        }
                      }
                    }',
            ],
            'whileTrueWithBreak' => [
                'code' => '<?php
                    for (;;) {
                        $a = "hello";
                        break;
                    }
                    for (;;) {
                        $b = 5;
                        break;
                    }',
                'assertions' => [
                    '$a' => 'string',
                    '$b' => 'int',
                ],
            ],
            'continueOutsideLoop' => [
                'code' => '<?php
                    class Node {
                        /** @var Node|null */
                        public $next;
                    }

                    /** @return void */
                    function test(Node $head) {
                        for ($node = $head; $node; $node = $next) {
                            $next = $node->next;
                            $node->next = null;
                        }
                    }',
            ],
            'echoAfterFor' => [
                'code' => '<?php
                    for ($i = 0; $i < 5; $i++);
                    echo $i;',
            ],
            'nestedEchoAfterFor' => [
                'code' => '<?php
                    for ($i = 1; $i < 2; $i++) {
                        for ($j = 1; $j < 2; $j++) {}
                    }

                    echo $i * $j;',
            ],
            'reconcileOuterVars' => [
                'code' => '<?php
                    for ($i = 0; $i < 2; $i++) {
                        if ($i === 0) {
                            continue;
                        }
                    }',
            ],
            'noException' => [
                'code' => '<?php
                    /**
                     * @param list<int> $arr
                     */
                    function cartesianProduct(array $arr) : void {
                        for ($i = 20; $arr[$i] === 5 && $i > 0; $i--) {}
                    }',
            ],
            'noCrashOnLongThing' => [
                'code' => '<?php
                    /**
                     * @param list<array{a: array{int, int}}> $data
                     */
                    function makeData(array $data) : array {
                        while (rand(0, 1)) {
                            while (rand(0, 1)) {
                                while (rand(0, 1)) {
                                    if (rand(0, 1)) {
                                        continue;
                                    }

                                    /** @psalm-suppress PossiblyUndefinedArrayOffset */
                                    $data[0]["a"] = array_merge($data[0]["a"], $data[0]["a"]);
                                }
                            }
                        }

                        return $data;
                    }',
            ],
            'InfiniteForLoop' => [
                'code' => '<?php
                    /**
                     * @return int
                     */
                    function g() {
                        for (;;) {
                            return 1;
                        }
                    }

                    /**
                     * @return int
                     */
                    function h() {
                        for (;1;) {
                            return 1;
                        }
                    }',
            ],
        ];
    }

    public function providerInvalidCodeParse(): iterable
    {
        return [
            'possiblyUndefinedArrayInWhileAndForeach' => [
                'code' => '<?php
                    for ($i = 0; $i < 4; $i++) {
                        while (rand(0,10) === 5) {
                            $array[] = "hello";
                        }
                    }

                    echo $array;',
                'error_message' => 'PossiblyUndefinedGlobalVariable - src' . DIRECTORY_SEPARATOR . 'somefile.php:4:29 - Possibly undefined ' .
                    'global variable $array, first seen on line 4',
            ],
            'forLoopInvalidation' => [
                'code' => '<?php
                    for ($i = 0; $i < 4; $i++) {
                      foreach ([1, 2, 3] as $i) {}
                    }',
                'error_message' => 'LoopInvalidation',
            ],
            'forInfiniteNoBreak' => [
                'code' => '<?php
                    for (;;) {
                        $a = "hello";
                    }

                    echo $a;',
                'error_message' => 'UndefinedGlobalVariable',
            ],
            'nestedEchoAfterFor' => [
                'code' => '<?php
                    for ($i = 1; $i < 2; $i++) {
                        if (rand(0, 1)) break;
                        for ($j = 1; $j < 2; $j++) {}
                    }

                    echo $i * $j;',
                'error_message' => 'PossiblyUndefinedGlobalVariable',
            ],
        ];
    }
}
