<?php
declare(strict_types=1);
namespace Aen\TwigWorkshop;
final class Endpoint {
    public static function handle(string $method,string $contentType,string $body): array {
        if($method==='GET')return [200,['ok'=>true,'engine'=>'Twig PHP','version'=>\Twig\Environment::VERSION,'scenarios'=>array_keys(Workshop::TEMPLATES)]];
        if($method!=='POST')return [405,['ok'=>false,'code'=>'METHOD_NOT_ALLOWED','message'=>'Используйте GET или POST.']];
        if(!preg_match('~^application/json(?:\s*;|$)~i',$contentType))return [415,['ok'=>false,'code'=>'JSON_REQUIRED','message'=>'Нужен Content-Type application/json.']];
        if(strlen($body)>32768)return [413,['ok'=>false,'code'=>'REQUEST_TOO_LARGE','message'=>'Запрос больше 32 КиБ.']];
        try {
            $request=json_decode($body,false,16,JSON_THROW_ON_ERROR);
            if(!$request instanceof \stdClass)throw new \InvalidArgumentException('Запрос должен быть JSON-объектом.');
            foreach(array_keys(get_object_vars($request)) as $key)if(!in_array($key,['scenario','data','options'],true))throw new \InvalidArgumentException('Неизвестное поле запроса.');
            if(!is_string($request->scenario??null)||!isset(Workshop::TEMPLATES[$request->scenario]))throw new \InvalidArgumentException('Выберите существующий сценарий.');
            if(!($request->data??null) instanceof \stdClass)throw new \InvalidArgumentException('data должен быть JSON-объектом.');
            $count=0;self::inspect($request->data,0,$count);
            $data=json_decode(json_encode($request->data,JSON_THROW_ON_ERROR),true,16,JSON_THROW_ON_ERROR);
            if(strlen(json_encode($data,JSON_UNESCAPED_UNICODE|JSON_THROW_ON_ERROR))>16384)throw new \InvalidArgumentException('Данные больше 16 КиБ.');
            $options=$request->options??new \stdClass;
            if(!$options instanceof \stdClass)throw new \InvalidArgumentException('options должен быть объектом.');
            foreach(get_object_vars($options) as $key=>$value){
                if(!in_array($key,['strict','page'],true))throw new \InvalidArgumentException('Неизвестная настройка.');
                if($key==='strict'&&!is_bool($value))throw new \InvalidArgumentException('strict должен быть true или false.');
                if($key==='page'&&!in_array($value,['overview','details'],true))throw new \InvalidArgumentException('Недопустимая страница.');
            }
            $opts=get_object_vars($options);
            $result=Workshop::run($request->scenario,$data,$opts);
            if($result['ok'])$result['recipe']=Workshop::recipe($request->scenario,$data,$opts);
            return [$result['ok']?200:422,$result];
        }catch(\JsonException){return [400,['ok'=>false,'code'=>'INVALID_JSON','message'=>'JSON не читается: проверьте кавычки, запятые и глубину.']];}
        catch(\InvalidArgumentException $e){return [422,['ok'=>false,'code'=>'INVALID_INPUT','message'=>$e->getMessage()]];}
        catch(\Throwable){return [500,['ok'=>false,'code'=>'RENDER_FAILED','message'=>'Шаблон не удалось обработать. Верните исходный пример.']];}
    }
    private static function inspect(mixed $v,int $depth,int &$nodes): void {
        if(++$nodes>250||$depth>8)throw new \InvalidArgumentException('Не более 250 значений и 8 уровней.');
        if(is_string($v)&&strlen($v)>4096)throw new \InvalidArgumentException('Строка не должна быть больше 4 КиБ.');
        if(is_array($v)||$v instanceof \stdClass){
            $values=is_array($v)?$v:get_object_vars($v);
            if(count($values)>30)throw new \InvalidArgumentException('Не более 30 элементов в коллекции.');
            foreach($values as $key=>$value){if(is_string($key)&&strlen($key)>100)throw new \InvalidArgumentException('Слишком длинное имя поля.');self::inspect($value,$depth+1,$nodes);}
        }
    }
}
