<?php

namespace xmvc;

/**
 * 验证类
 *
 * @version 1.0
 */

class Valid
{
    /** 错误标识 */
    protected string $errorCustomTag = '';
    /** 是否直接抛出验证错误 */
    public bool $throw = false;
    /** 是否返回所有规则的错误, false 只返回第一条检测到的错误 */
    public bool $backAllError = false;
    /**
     * 构造
     *
     * @param boolean $throw 是否直接抛出验证错误
     */
    public function __construct()
    {
        $this->errorCustomTag = __CLASS__ . uniqid();
    }

    /**
     * 验证 & 将符合规则的值转换成对应的变量
     * - check($var, ['type'=>'int']); check(变量,规则) 两个参数
     * - check($arr, $key, ['type'=>'int']); check(数组, 数组的键, 规则) 三个参数
     * - check($a, ['type'=>'int']); 如果 $a = '123' 符合规则, 并被转化成 $a = 123;
     * @param string $key
     * @param mixed $value 值
     * @param array $rules 规则
     * @return boolean
     */
    public function check(string $key, mixed &$value, array $rules): bool
    {
        $isValided = true;
        $rules['type'] = $rules['type'] ?? 'string';
        $msg = [];

        if (isset($rules['must']) && $rules['must'] && ($value === null || trim($value) === '')) {
            $this->error($rules['msgMust'] ?? lg('必须传入一个非空值'), $key, $value);
            return false; // 必须项, 如果是空值 则直接返回
        }

        switch ($rules['type']) {
            case 'int':
            case 'double':
            case 'number':
                if (!is_numeric($value)) {
                    $msg[] =  lg('必须为数字');
                    $isValided = false;
                } else {
                    $value *= 1;
                    if ($rules['type'] == 'int' && !is_int($value)) {
                        $msg[] = lg('必须为整型数字');
                        $isValided = false;
                    }
                    break;
                }
                break;
            case 'date':
                $format = cfg('date>dateFormat');
                $d = \DateTime::createFromFormat($format, $value);
                if (!$d || $d->format($format) !== $value) {
                    $msg[] = lg('必须为日期格式') . ' ' . $format;
                    $isValided = false;
                }
                break;
            case 'hourMinute':
                if (!preg_match('/\d{2}:\d{2}/', $value)) {
                    $msg[] = lg('必须小时分钟格式') . ' H:i';
                    $isValided = false;
                }
                break;
            case 'time':
                $format = cfg('date>timeFormat');
                $d = \DateTime::createFromFormat($format, $value);
                if (!$d || $d->format($format) !== $value) {
                    $msg[] = lg('必须为时间格式') . ' ' . $format;
                    $isValided = false;
                }
                break;
            case 'datetime':
                $format = cfg('date>dateTimeFormat');
                $d = \DateTime::createFromFormat($format, $value);
                if (!$d || $d->format($format) !== $value) {
                    $msg[] = lg('必须为日期时间格式') . ' ' . $format;
                    $isValided = false;
                }
                break;
            case 'var':
                if (!preg_match('/^[a-zA-Z0-9_]+$/', $value)) {
                    $msg[] = lg('必须是字母,数字,下划线');
                    $isValided = false;
                }
                break;
            case 'alpha':
                if (!preg_match('/^[a-zA-Z]+$/', $value)) {
                    $msg[] = lg('必须是字母');
                    $isValided = false;
                }
                break;
            case 'alpha_num':
                if (!preg_match('/^[a-zA-Z0-9]+$/', $value)) {
                    $msg[] = lg('必须是字母,数字');
                    $isValided = false;
                }
                break;
            case 'alpha_dash':
                if (!preg_match('/^[a-zA-Z0-9]+$/', $value)) {
                    $msg[] = lg('必须是字母,数字,下划线,中划线');
                    $isValided = false;
                }
                break;
            case 'password':
                $tc = '\!\@\#\$\%\^\&\*\(\)\-\_\+\=\{\[\]\}\|\;\:\'\"\<\,\>\.\?\/`\\\\~\s';
                $pattern = '/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[' . $tc . '])[A-Za-z\d' . $tc . ']{9,}$/';
                if (!preg_match($pattern, $value)) {
                    $msg[] = lg('密码不符合规则, 必须有小写大写字母,数字,特殊符号,长度 9 位及以上');
                    $isValided = false;
                }
                break;
            case 'enum':
                if (!in_array($value, $rules['enum'])) {
                    $msg[] = lg('值不在枚举范围内') . " ['" . implode("', '", $rules['enum']) . "']";
                    $isValided = false;
                }
                break;
            case 'json':
                if (!isJson($value)) {
                    $msg[] = lg('不是一个有效的 JSON 格式');
                    $isValided = false;
                }
                break;
            case 'email':
                if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
                    $msg[] = lg('电子邮箱地址格式错误');
                    $isValided = false;
                }
                break;
            case 'ip':
                if (!filter_var($value, FILTER_VALIDATE_IP)) {
                    $msg[] = lg('IP地址格式错误');
                    $isValided = false;
                }
                break;
            case 'url':
                if (!filter_var($value, FILTER_VALIDATE_URL)) {
                    $msg[] = lg('网址格式错误');
                    $isValided = false;
                }
                break;
            case 'regex':
                if (!preg_match($rules['regex'], $value)) {
                    $msg[] = lg('不符合规则');
                    $isValided = false;
                }
                break;
            case 'callback':
                if (strpos($rules['callback'], '::') !== false) {
                    list($class, $function) = explode('::', $rules['callback']); // 静态调用 例 \xmvc\test\UrlTest::mycheck1
                } else {
                    list($class, $function) = explode('->', $rules['callback']); // 类实例调用 例 \xmvc\test\UrlTest->mycheck2
                    $class = new $class();
                }
                $back = call_user_func_array([$class, $function], [$key, $value, $rules]);
                if ($back !== true) {
                    $msg[] = $back;
                    $isValided = false;
                }
                break;
            case 'html':
                break;
            case 'string':
            default:
                $value = htmlspecialchars($value);
        }
        if (($this->backAllError || $isValided) && isset($rules['min'])) {
            if ($value < $rules['min']) {
                $msg[] = lg('必须') . ' >= ' . $rules['min'];
                $isValided = false;
            }
        }
        if (($this->backAllError || $isValided) && isset($rules['max'])) {
            if ($value > $rules['max']) {
                $msg[] = lg('必须') . ' <= ' . $rules['max'];
                $isValided = false;
            }
        }
        if (($this->backAllError || $isValided) && isset($rules['minLen'])) {
            if (strlen($value) < $rules['minLen']) {
                $msg[] = lg('长度') . ' ' . lg('必须') . ' >= ' . $rules['minLen'];
                $isValided = false;
            }
        }
        if (($this->backAllError || $isValided) && isset($rules['maxLen'])) {
            if (strlen($value) > $rules['maxLen']) {
                $msg[] = lg('长度') . ' ' . lg('必须') . ' <= ' . $rules['maxLen'];
                $isValided = false;
            }
        }
        if (!$isValided) {
            $msg = $rules['msg'] ?? implode(";\n", $msg);
            $this->error($msg, $key, $value);
        }
        return $isValided;
    }

    /**
     * 错误记录
     *
     * @param string $message
     * @param string $key
     * @param mixed $value
     * @param boolean $throw 是否直接抛出错误
     */
    public function error(string $message, string $key, mixed $value)
    {
        if ($this->throw) {
            ErrorClass::errorMake(errorInfo($message, ['key' => $key, 'value' => $value], [__FILE__], [], true));
            exit();
        } else {
            ErrorClass::setErrorCustomPool($this->errorCustomTag, $message, ['key' => $key, 'value' => $value], [__FILE__], [], false);
        }
    }

    /**
     * 获取验证错误信息
     */
    public function errorInfo(): array|null
    {
        return ErrorClass::getErrorCustomPool($this->errorCustomTag);
    }
}
