<?php
namespace nur\sery\wip\schema\types;

use nur\sery\cl;
use nur\sery\ValueException;
use nur\sery\wip\schema\_scalar\ScalarResult;
use nur\sery\wip\schema\_scalar\ScalarSchema;
use nur\sery\wip\schema\input\Input;
use nur\sery\wip\schema\Result;
use nur\sery\wip\schema\Schema;

class tbool extends _tsimple {
  /** liste de valeurs chaines à considérer comme 'OUI' */
  const YES_VALUES = [
    # IMPORTANT: ordonner par taille décroissante pour compatibilité avec parse()
    "true", "vrai", "yes", "oui",
    "t", "v", "y", "o", "1",
  ];

  /** liste de valeurs chaines à considérer comme 'NON' */
  const NO_VALUES = [
    # IMPORTANT: ordonner par taille décroissante pour compatibilité avec parse()
    "false", "faux", "non", "no",
    "f", "n", "0",
  ];

  /** Vérifier si $value est 'OUI' */
  static final function is_yes(?string $value): bool {
    if ($value === null) return false;
    $value = strtolower(trim($value));
    if (in_array($value, self::YES_VALUES, true)) return true;
    // n'importe quelle valeur numérique
    if (is_numeric($value)) return $value != 0;
    return false;
  }

  /** Vérifier si $value est 'NON' */
  static final function is_no(?string $value): bool {
    if ($value === null) return true;
    $value = strtolower(trim($value));
    return in_array($value, self::NO_VALUES, true);
  }

  static function ensure_bool(&$bool): void {
    if (is_string($bool)) {
      if (self::is_yes($bool)) $bool = true;
      elseif (self::is_no($bool)) $bool = false;
    }
    if (!is_bool($bool)) $bool = boolval($bool);
  }

  static function ensure_nbool(&$bool): void {
    if ($bool !== null) self::ensure_bool($bool);
  }

  function isAvailable(Input $input, $destKey): bool {
    return $input->isAvailable($destKey);
  }

  function isValid($value, ?bool &$normalized=null): bool {
    $normalized = is_bool($value);
    if (is_string($value)) {
      $value = trim($value);
      $valid = self::is_yes($value) || self::is_no($value);
    } else {
      $valid = is_scalar($value);
    }
    return $valid;
  }

  /**
   * @var ScalarResult $result
   * @var ScalarSchema $schema
   */
  function verifix(&$value, Result &$result, Schema $schema): bool {
    if (is_bool($value)) {
      $result->setNormalized();
      return false;
    } elseif (is_string($value)) {
      $bool = trim($value);
      if (self::is_yes($bool)) $value = true;
      elseif (self::is_no($bool)) $value = false;
      else return $result->setInvalid($value, $schema);
    } elseif (is_scalar($value)) {
      $value = boolval($value);
    } else {
      return $result->setInvalid($value, $schema);
    }
    $result->setValid();
    return true;
  }

  const OUINON_FORMAT = ["Oui", "Non", false];
  const OUINONNULL_FORMAT = ["Oui", "Non", ""];
  const ON_FORMAT = ["O", "N", false];
  const ONN_FORMAT = ["O", "N", ""];
  const XN_FORMAT = ["X", "", false];
  const OZ_FORMAT = ["1", "", false];
  const FORMATS = [
    "ouinon" => self::OUINON_FORMAT,
    "ouinonnull" => self::OUINONNULL_FORMAT,
    "on" => self::ON_FORMAT,
    "onn" => self::ONN_FORMAT,
    "xn" => self::XN_FORMAT,
    "oz" => self::OZ_FORMAT,
  ];

  const DEFAULT_FORMAT = self::OUINON_FORMAT;

  function format($value, $format=null): string {
    if ($format === null) $format = static::DEFAULT_FORMAT;
    if (is_string($format)) {
      $oformat = $format;
      $format = cl::get(self::FORMATS, strtolower($oformat));
      if ($format === null) throw ValueException::invalid_kind($oformat, "format");
    }
    if ($value === null) {
      $null = $format[2];
      if ($null !== false) return $null;
    }
    return $value? $format[0]: $format[1];
  }
}
