<?php
namespace nulib\schema\types;

use nulib\cl;
use nulib\php\func;
use nulib\ValueException;

class Registry {
  const TYPES = [
    "rawstring" => trawstring::class,
    "string" => tstring::class,
    "text" => ttext::class,
    "bool" => tbool::class, "boolean" => tbool::class,
    "int" => tint::class, "integer" => tint::class,
    "float" => tfloat::class, "flt" => tfloat::class, "double" => tfloat::class, "dbl" => tfloat::class,
    "array" => tarray::class,
    "func" => tfunc::class, "function" => tfunc::class, "callable" => tfunc::class,
    # types spéciaux
    "raw" => traw::class,
    "mixed" => tmixed::class,
    "key" => tkey::class,
    "pkey" => tpkey::class,
    "content" => tcontent::class,
  ];

  function __construct() {
    $this->types = [];
  }

  /** @var IType[] */
  protected $types;

  function get(bool $nullable, ?string $name, ?array $args=null, ?array $definition=null): IType {
    if (cl::is_list($args)) {
      $key = array_key_last($args);
      $params = $args[$key];
      unset($args[$key]);
    } else {
      $params = $args;
      $args = null;
    }
    $name ??= "raw";
    $class = cl::get(self::TYPES, $name);
    if ($class === null) {
      $class = $name;
      if (!class_exists($class)) {
        throw ValueException::invalid_type($class, IType::class);
      } elseif (!is_subclass_of($class, IType::class)) {
        return new tgeneric($class, $nullable, $params);
      }
    }
    $params = cl::merge($class::get_params_from_definition($definition), $params);
    if ($args || $params !== null) {
      $args ??= [];
      return func::with([$class, false, ...$args, $nullable, $params])->invoke();
    }
    if ($nullable) $name = "?$name";
    $type = cl::get($this->types, $name);
    if ($type === null) $type = $this->types[$name] = new $class($nullable);
    return $type;
  }
}
