<?php
declare(strict_types=1);

require_once __DIR__ . '/mvcFunction.php';
require_once __DIR__ . '/ExceptionHandler.php';
require_once __DIR__ . '/Action.php';
require_once __DIR__ . '/Model.php';
require_once __DIR__ . '/View.php';
require_once __DIR__ . '/Lang.php';
require_once __DIR__ . '/composer/vendor/autoload.php';

// lib/ 目录下的类库自动加载（类名 = 文件名，扁平存放或按功能分子目录均可，按类名递归查找）
spl_autoload_register(static function (string $class): void {
    $file = __DIR__ . '/lib/' . $class . '.php';
    if (is_file($file)) {
        require_once $file;
    }
});

// pdc/model/ 全站共享 Model 类自动加载（类名 = 文件名）
spl_autoload_register(static function (string $class): void {
    $file = (Mvc::$cfg['projectDir'] ?? '') . '/model/' . $class . '.php';
    if (is_file($file)) {
        require_once $file;
    }
});

/**
 * MVC 核心调度类
 * 负责解析 PATH_INFO、绑定参数、分发到对应 action 类的方法
 */
final class Mvc
{
    public static array $cfg = [];

    /** 合并后的请求参数（GET + POST + php://input + /k/键值风格） */
    public static array $req = [];

    /** /v/ 纯值风格的网址传参，按下标存放 */
    private static array $uriValues = [];

    public static ?string $class = null;
    public static ?string $method = null;

    /** 没有指定/没有 lang 参数也没有 session 记录时的默认语言 */
    private const DEFAULT_LOCALE = 'zh-CN';

    /**
     * 初始化
     * @param array $cfg 至少包含 site, actionDir, projectDir；可选 langDir 用于多语言
     */
    public static function init(array $cfg): void
    {
        self::$cfg = $cfg;
        ExceptionHandler::register();

        if (!empty($cfg['langDir'])) {
            self::$cfg['locale'] = self::resolveLocale();
            Lang::load(self::$cfg['locale'], $cfg['langDir']);
        }

        self::parseRequest();
    }

    /**
     * 确定当前语言：网址传参 lang 优先，写入 SESSION 并立即生效；
     * 没有传 lang 时取 SESSION 里上一次记录的值；都没有时用默认语言
     */
    private static function resolveLocale(): string
    {
        if (PHP_SAPI !== 'cli' && session_status() !== PHP_SESSION_ACTIVE) {
            session_start();
        }

        if (!empty($_GET['lang'])) {
            $_SESSION['lang'] = $_GET['lang'];
        }

        return $_SESSION['lang'] ?? self::DEFAULT_LOCALE;
    }

    /**
     * 解析 PATH_INFO，确定目标类、方法、请求参数
     */
    private static function parseRequest(): void
    {
        $pathInfo = $_SERVER['PATH_INFO'] ?? '';
        [$segments, $uriParams] = self::splitPathInfo($pathInfo);

        if (empty($segments)) {
            $segments = ['index'];
        }

        $method = array_pop($segments);

        // .html 伪静态后缀支持
        $isHtml = false;
        if (str_ends_with($method, '.html')) {
            $method = substr($method, 0, -5);
            $isHtml = true;
        }
        if ($method === '') {
            $method = 'index';
        }

        // 规则：除最后两段外都是目录，倒数第二段是文件/类名，最后一段是方法名
        $classBase = $segments ? array_pop($segments) : 'index';
        $dir = $segments ? implode('/', $segments) . '/' : '';

        self::$cfg['routeDir'] = $dir;
        self::$cfg['routeIsHtml'] = $isHtml;
        self::$class = $classBase . 'Action';
        self::$method = $method;

        if ($uriParams['type'] === 'v') {
            self::$uriValues = $uriParams['values'];
        }

        self::$req = self::buildParams($uriParams);
    }

    /**
     * 拆分 PATH_INFO
     * 支持 /v/纯值 与 /k/键值 风格的网址传参
     * @return array{0: array<int,string>, 1: array{type:string, values:array}}
     */
    private static function splitPathInfo(string $pathInfo): array
    {
        $pathInfo = trim($pathInfo, '/');
        $parts = preg_split('#/(v|k)/#', $pathInfo, 2, PREG_SPLIT_DELIM_CAPTURE);

        $main = $parts[0] ?? '';
        $segments = $main === '' ? [] : explode('/', $main);

        $uriParams = ['type' => '', 'values' => []];
        if (isset($parts[1], $parts[2])) {
            $uriParams['type'] = $parts[1]; // 'v' 或 'k'
            $uriParams['values'] = $parts[2] === '' ? [] : explode('/', trim($parts[2], '/'));
        }

        return [$segments, $uriParams];
    }

    /**
     * 合并 GET / POST / php://input / URL传参(/k/风格) 为统一请求参数
     */
    private static function buildParams(array $uriParams): array
    {
        $req = $_GET;

        if (!empty($_POST)) {
            $req = array_merge($req, $_POST);
        } else {
            $input = file_get_contents('php://input');
            if ($input !== '' && ($decoded = json_decode($input, true))) {
                $req = array_merge($req, $decoded);
            }
        }

        if ($uriParams['type'] === 'k') {
            $values = $uriParams['values'];
            for ($i = 0, $count = count($values); $i < $count - 1; $i += 2) {
                $req[$values[$i]] = $values[$i + 1];
            }
        }

        return $req;
    }

    /**
     * 执行路由分发：加载文件、实例化类、绑定参数、调用方法
     */
    public static function dispatch(): void
    {
        $dir = self::$cfg['routeDir'];
        $classBase = substr(self::$class, 0, -6); // 去掉 'Action' 后缀
        $file = rtrim(self::$cfg['actionDir'], '/') . '/' . $dir . $classBase . '.php';

        if (!is_file($file)) {
            throw new MvcException("找不到路由文件: {$file}");
        }
        require_once $file;

        $class = self::$class;
        if (!class_exists($class)) {
            throw new MvcException("找不到控制器类: {$class}");
        }
        if (!method_exists($class, self::$method)) {
            throw new MvcException("控制器 {$class} 不存在方法: " . self::$method);
        }

        $action = new $class();
        if (method_exists($action, 'setRequest')) {
            $action->setRequest(self::$req);
        }

        $params = self::bindParams($class, self::$method);

        if (method_exists($action, 'callBefore')) {
            $action->callBefore(self::$method, $params);
        }

        call_user_func_array([$action, self::$method], $params);

        if (method_exists($action, 'callEnd')) {
            $action->callEnd(self::$method, $params);
        }
    }

    /**
     * 按方法形参名从请求参数中取值绑定（反射），取不到时按 /v/ 风格的位置取值兜底
     */
    private static function bindParams(string $class, string $method): array
    {
        $reflector = new \ReflectionMethod($class, $method);
        $params = [];

        foreach ($reflector->getParameters() as $i => $p) {
            $name = $p->getName();
            if (array_key_exists($name, self::$req)) {
                $params[$i] = self::$req[$name];
            } elseif (array_key_exists($i, self::$uriValues)) {
                $params[$i] = self::$uriValues[$i];
            } elseif ($p->isDefaultValueAvailable()) {
                $params[$i] = $p->getDefaultValue();
            } else {
                throw new MvcException("{$class}::{$method} 参数 \${$name} 没有传值");
            }
        }

        return $params;
    }
}
