<?php

declare(strict_types=1);

namespace Psalm\Tests;

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

class ArrayKeysTest extends TestCase
{
    use InvalidCodeAnalysisTestTrait;
    use ValidCodeAnalysisTestTrait;

    public function providerValidCodeParse(): iterable
    {
        return [
            'arrayKeysOfEmptyArrayReturnsListOfEmpty' => [
                'code' => '<?php
                    $keys = array_keys([]);
                ',
                'assertions' => [
                    '$keys' => 'list<never>',
                ],
            ],
            'arrayKeysOfKeyedArrayReturnsNonEmptyListOfStrings' => [
                'code' => '<?php
                    $keys = array_keys(["foo" => "bar"]);
                ',
                'assertions' => [
                    '$keys' => 'non-empty-list<string>',
                ],
            ],
            'arrayKeysOfListReturnsNonEmptyListOfInts' => [
                'code' => '<?php
                    $keys = array_keys(["foo", "bar"]);
                ',
                'assertions' => [
                    '$keys' => 'non-empty-list<int<0, 1>>',
                ],
            ],
            'arrayKeysOfKeyedStringIntArrayReturnsNonEmptyListOfIntsOrStrings' => [
                'code' => '<?php
                    $keys = array_keys(["foo" => "bar", 42]);
                ',
                'assertions' => [
                    '$keys' => 'non-empty-list<int|string>',
                ],
            ],
            'arrayKeysOfArrayConformsToArrayKeys' => [
                'code' => '<?php
                    /**
                     * @return list<array-key>
                     */
                    function getKeys(array $array) {
                        return array_keys($array);
                    }
                ',
            ],
            'arrayKeysOfKeyedArrayConformsToCorrectLiteralStringList' => [
                'code' => '<?php
                    /**
                     * @return non-empty-list<"foo"|"bar">
                     */
                    function getKeys() {
                        return array_keys(["foo" => 42, "bar" => 42]);
                    }
                ',
            ],
            'arrayKeysOfLiteralListConformsToCorrectLiteralOffsets' => [
                'code' => '<?php
                    /**
                     * @return non-empty-list<0|1>
                     */
                    function getKeys() {
                        return array_keys(["foo", "bar"]);
                    }
                ',
            ],
            'arrayKeyFirstOfLiteralListConformsToCorrectLiteralOffsets' => [
                'code' => '<?php
                    /**
                     * @return 0|1
                     */
                    function getKey() {
                        return array_key_first(["foo", "bar"]);
                    }
                ',
            ],
            'arrayKeyLastOfLiteralListConformsToCorrectLiteralOffsets' => [
                'code' => '<?php
                    /**
                     * @return 0|1
                     */
                    function getKey() {
                        return array_key_last(["foo", "bar"]);
                    }
                ',
            ],
            'literalStringAsIntArrayKey' => [
                'code' => '<?php
                    class a {
                        private const REDIRECTS = [
                            "a" => [
                                "from" => "79268724911",
                                "to" => "74950235931",
                            ],
                            "b" => [
                                "from" => "79313044964",
                                "to" => "78124169167",
                            ],
                        ];

                        private const SIP_FORMAT = "sip:%s@voip.test.com:9090";

                        /** @return array<int, string> */
                        public function test(): array {
                            $redirects = [];
                            foreach (self::REDIRECTS as $redirect) {
                                $redirects[$redirect["from"]] = sprintf(self::SIP_FORMAT, $redirect["to"]);
                            }

                            return $redirects;
                        }
                    }',
            ],
            'variousArrayKeys' => [
                'code' => '<?php
                    /**
                     * @psalm-type TAlias = 123
                     */
                    class a {}
                    
                    /**
                     * @psalm-import-type TAlias from a
                     * @template TKey as array-key
                     * @template TValue as array-key
                     * @template T as array<TKey, TValue>
                     * 
                     * @template TOrig as a|b
                     * @template TT as class-string<TOrig>
                     * 
                     * @template TBool as bool
                     */
                    class b {
                        /** 
                         * @var array<TAlias, int> 
                         */
                        private array $a = [123 => 123];
                        
                        /** @var array<value-of<T>, int> */
                        public array $c = [];
                        
                        /** @var array<key-of<T>, int> */
                        public array $d = [];
                        
                        /** @var array<TT, int> */
                        public array $e = [];
                        
                        /** @var array<key-of<array<int, string>>, int> */
                        private array $f = [123 => 123];
                        
                        /** @var array<value-of<array<int, string>>, int> */
                        private array $g = ["test" => 123];

                        /** @var array<TBool is true ? string : int, int> */
                        private array $h = [123 => 123];

                        /**
                         * @return array<$v is true ? "a" : 123, 123>
                         */
                        public function test(bool $v): array {
                            return $v ? ["a" => 123] : [123 => 123];
                        }
                    }
                    
                    /** @var b<"testKey", "testValue", array<"testKey", "testValue">, b, class-string<b>, true> */
                    $b = new b;
                    $b->d["testKey"] = 123;

                    // TODO
                    //$b->c["testValue"] = 123;
                    //$b->e["b"] = 123;
                    ',
            ],
        ];
    }

    public function providerInvalidCodeParse(): iterable
    {
        return [
            'arrayKeysOfStringArrayDoesntConformsToIntList' => [
                'code' => '<?php
                    /**
                     * @param array<string, mixed> $array
                     * @return list<int>
                     */
                    function getKeys(array $array) {
                        return array_keys($array);
                    }
                ',
                'error_message' => 'InvalidReturnStatement',
            ],
            'arrayKeysOfStringKeyedArrayDoesntConformToIntList' => [
                'code' => '<?php
                    /**
                     * @return list<int>
                     */
                    function getKeys() {
                        return array_keys(["foo" => 42, "bar" => 42]);
                    }
                ',
                'error_message' => 'InvalidReturnStatement',
            ],
            'literalStringAsIntArrayKey' => [
                'code' => '<?php
                    class a {
                        private const REDIRECTS = [
                            "a" => [
                                "from" => "79268724911",
                                "to" => "74950235931",
                            ],
                            "b" => [
                                "from" => "79313044964",
                                "to" => "78124169167",
                            ],
                        ];

                        private const SIP_FORMAT = "sip:%s@voip.test.com:9090";

                        /** @return array<string, string> */
                        public function test(): array {
                            $redirects = [];
                            foreach (self::REDIRECTS as $redirect) {
                                $redirects[$redirect["from"]] = sprintf(self::SIP_FORMAT, $redirect["to"]);
                            }

                            return $redirects;
                        }
                    }',
                'error_message' => 'InvalidReturnStatement',
            ],
        ];
    }
}
