<?php
namespace nulib\schema;

use nulib\cl;
use nulib\ref\schema\ref_analyze;
use nulib\ref\schema\ref_schema;
use nulib\ValueException;
use Throwable;

/**
 * Class Result: résultat de l'analyse ou de la normalisation d'une valeur
 *
 * @property bool $resultAvailable le résultat est-il disponible?
 * @property bool $present la valeur existe-t-elle?
 * @property bool $available si la valeur existe, est-elle disponible?
 * @property bool $null si la valeur est disponible, est-elle nulle?
 * @property bool $valid si la valeur est disponible, est-elle valide?
 * @property bool $normalized si la valeur est valide, est-elle normalisée?
 * @property string|null $messageKey clé de message si la valeur n'est pas valide
 * @property string|null $message message si la valeur n'est pas valide
 * @property Throwable|null $exception l'exception qui a fait échouer la
 * validation le cas échéant
 * @property string|null $origValue valeur originale avant extraction et analyse
 * @property mixed|null $normalizedValue la valeur normalisée si elle est
 *  disponible, null sinon. ce champ est utilisé comme optimisation si la valeur
 *  normalisée a déjà été calculée
 */
class Result {
  function __construct() {
    $this->reset();
  }

  public bool $resultAvailable;
  public bool $present;
  public bool $available;
  public bool $null;
  public bool $valid;
  public bool $normalized;
  public ?string $messageKey;
  public ?string $message;
  public ?Throwable $exception;
  public $origValue;
  public $normalizedValue;

  /** réinitialiser tous les objets résultats accessibles via cet objet */
  function reset(): void {
    $this->resultAvailable = false;
    $this->present = false;
    $this->available = false;
    $this->null = false;
    $this->valid = false;
    $this->normalized = false;
    $this->messageKey = null;
    $this->message = null;
    $this->exception = null;
    $this->origValue = null;
    $this->normalizedValue = null;
  }

  protected function getMessage(string $key,  Schema $schema): string {
    $message = cl::get($schema->messages, $key);
    if ($message !== null) return $message;
    return cl::get(ref_schema::MESSAGES, $key);
  }

  function setMissing( Schema $schema): int {
    $this->resultAvailable = true;
    $this->present = false;
    $this->available = false;
    if (!$schema->required) {
      $this->null = false;
      $this->valid = true;
      $this->normalized = true;
      return ref_analyze::NORMALIZED;
    } else {
      $this->messageKey = $messageKey = "missing";
      $this->message = $this->getMessage($messageKey, $schema);
      return ref_analyze::MISSING;
    }
  }

  function setUnavailable( Schema $schema): int {
    $this->resultAvailable = true;
    $this->present = true;
    $this->available = false;
    if (!$schema->required) {
      $this->null = false;
      $this->valid = true;
      $this->normalized = true;
      return ref_analyze::NORMALIZED;
    } else {
      $this->messageKey = $messageKey = "unavailable";
      $this->message = $this->getMessage($messageKey, $schema);
      return ref_analyze::UNAVAILABLE;
    }
  }

  function setNull( Schema $schema): int {
    $this->resultAvailable = true;
    $this->present = true;
    $this->available = true;
    $this->null = true;
    if ($schema->nullable) {
      $this->valid = true;
      $this->normalized = true;
      return ref_analyze::NORMALIZED;
    } else {
      $this->messageKey = $messageKey = "null";
      $this->message = $this->getMessage($messageKey, $schema);
      return ref_analyze::NULL;
    }
  }

  function setInvalid($value,  Schema $schema, ?Throwable $exception=null): int {
    $this->resultAvailable = true;
    $this->present = true;
    $this->available = true;
    $this->null = false;
    $this->valid = false;
    $this->origValue = $value;
    $this->messageKey = $messageKey = "invalid";
    $message = null;
    if ($exception !== null) $message = ValueException::get_message($exception);
    if (!$message) $message = $this->getMessage($messageKey, $schema);
    $this->message = $message;
    $this->exception = $exception;
    return ref_analyze::INVALID;
  }

  function setValid($normalizedValue=null): int {
    $this->resultAvailable = true;
    $this->present = true;
    $this->available = true;
    $this->null = false;
    $this->valid = true;
    $this->normalizedValue = $normalizedValue;
    return ref_analyze::VALID;
  }

  function setNormalized(): int {
    $this->resultAvailable = true;
    $this->present = true;
    $this->available = true;
    $this->null = false;
    $this->valid = true;
    $this->normalized = true;
    return ref_analyze::NORMALIZED;
  }

  function throw(bool $throw): void {
    if ($throw) {
      $exception = $this->exception;
      if ($exception !== null) throw $exception;
      else throw new ValueException($this->message);
    }
  }
}
