<?php
namespace nur\b\ui;

use nur\b\params\IParametrable;
use Throwable;

/**
 * Interface IMessenger: interface pour un objet qui centralise des informations
 * à afficher à l'utilisateur et/ou à logguer dans un fichier
 */
interface IMessenger extends IParametrable {
  # Types et destination de messages
  const TYPE_MASK = 0b11;
  const TYPE_ERROR = 3, TYPE_WARNING = 2, TYPE_INFO = 1, TYPE_DEBUG = 0;
  const RESULT_MASK = 0b1100;
  const RESULT_FAILURE = 3 << 2, RESULT_SUCCESS = 2 << 2, RESULT_NEUTRAL = 1 << 2, RESULT_NONE = 0 << 2;
  const DEST_MASK = 0b110000;
  const DEST_LOG = 2 << 4, DEST_DISPLAY = 1 << 4, DEST_ALL = self::DEST_DISPLAY + self::DEST_LOG;

  # Niveaux de messages
  const LEVEL_NEVER = PHP_INT_MAX;
  const LEVEL_CRITICAL = 100, LEVEL_MAJOR = 90, LEVEL_NORMAL = 30, LEVEL_MINOR = 0;
  const LEVEL_ALWAYS = PHP_INT_MIN;

  # Composantes d'un message
  const KEY_USER = "user", KEY_TECH = "tech", KEY_EXCEPTION = "exception";

  const LEVELS_SCHEMA = [
    self::KEY_USER => [null, null, "niveau minimum pour afficher des messages utilisateurs"],
    self::KEY_TECH => [null, null, "niveau minimum pour afficher des messages techniques"],
    self::KEY_EXCEPTION => [null, null, "niveau minimum pour afficher les traces d'exception"],
  ];

  /**
   * spécifier les niveaux minimum pour que soient affichés les messages de type
   * user, tech et exception.
   *
   * $levels doit être conforme au schéma {@link LEVELS_SCHEMA}.
   * chaque valeur peut être un niveau numérique ou un tableau de la forme
   * [$default_level, $profile => $level, ...]
   */
  function setLevels(?array $printLevels, ?array $logLevels=null, $typeLevels=null): IMessenger;

  /**
   * démarrer une nouvelle section dans laquelle ajouter des messages. si une
   * section est en cours, la terminer d'abord (il n'est pas prévu de pouvoir
   * imbriquer des sections)
   *
   * @param int|null $msgType le type des messages de cette section. c'est une
   * information qui peut être utilisée pour mettre en forme la section
   * @param int|null $msgLevel le niveau maximum des messages de cette section.
   * c'est une information heuristique pour voir si la section sera affichée ou
   * non. si $level===null, la section est toujours affichée
   */
  function startSection($title, ?int $msgType=null, ?int $msgLevel=null): IMessenger;

  /** retourner true si on est actuellement dans une section */
  function isInSection(): bool;

  /** terminer explicitement la section démarrée par {@link startSection()} */
  function endSection(): IMessenger;

  /**
   * démarrer un nouveau groupe de messages. contrairement aux sections, les
   * groupes peuvent être imbriqués. $count indique le nombre de messages prévus
   * dans ce groupe, ou null, si le nombre est inconnu. l'implémentation peut
   * choisir d'afficher un groupe différemment en fonction du nombre de ses
   * messages
   *
   * @param int|null $msgType le type des messages de ce groupe. c'est une
   * information qui peut être utilisée pour mettre en forme le groupe
   * @param int|null $msgLevel le niveau maximum des messages de ce groupe.
   * c'est une information heuristique pour voir si le groupe sera affiché ou
   * non. si $level===null, le groupe est toujours affiché
   */
  function startGroup($prefix, ?int $count=null, ?int $msgType=null, ?int $msgLevel=null): IMessenger;

  /** retourner true si on est actuellement dans un groupe */
  function isInGroup(): bool;

  /** terminer le groupe démarré par {@link startGroup()} */
  function endGroup(): IMessenger;

  /**
   * terminer le groupe ou la section en cours, le cas échéant. si $all==true
   * alors terminer tous les groupes en cours, puis terminer le section courante
   */
  function end(bool $all=false): IMessenger;

  const MESSAGE_SCHEMA = [
    self::KEY_USER => [null, null, "message à afficher à l'utilisateur",
      # il peut s'agir d'une chaine ou d'une instance de Exception
    ],
    self::KEY_TECH => [null, null, "message technique consultable par l'utilisateur",
      # il peut s'agir d'une chaine ou d'une instance de Exception
    ],
    self::KEY_EXCEPTION => [null, null, "instance de Exception qui a provoqué ce message"],
  ];

  # options dépendantes de l'implémentation
  const MESSAGE_OPTIONS_SCHEMA = [];

  /** normaliser $message pour qu'il soit conforme au schéma */
  function ensureMessage(&$message): void;

  /**
   * ajouter un message de type $type et de niveau $level. le message n'est pris
   * en compte que si les niveaux sont suffisants.
   *
   * C'est à l'implémentation de décider si le message sera affiché de suite ou
   * à la fin du groupe ou de la section.
   *
   * @param array|string|Throwable $message le message à afficher, conforme à
   * {@link MESSAGE_SCHEMA} et {@link MESSAGE_OPTIONS_SCHEMA}
   * @param int $type le type de message
   * @param int $level le niveau du message
   */
  function addMessage($message, int $type, int $level): IMessenger;

  /**
   * méthode de convenance pour démarrer un groupe d'un seul message, puis, si
   * le résultat est fourni, le terminer avec le résultat spécifié.
   */
  function action($message, $result=null, ?array $args=null, ?int $type=null, ?int $level=null): IMessenger;

  /**
   * méthode de convenance qui termine le groupe courant avec le résultat
   * spécifié
   *
   * - result peut être booléen: true pour succès, false pour échec
   * - si c'est une chaine de caractère, alors c'est un succès
   * - si c'est une exception, alors c'est un échec
   * - si un callable, alors la fonction est appelée avec les arguments $args et
   * le résultat est évalué selon les règles précédentes
   *
   * $type n'est utilisé que pour spécifier la destination le cas échéant
   */
  function aresult($result, ?array $args=null, $message=null, ?int $type=null): IMessenger;

  /**
   * méthode de convenance qui appelle
   *   aresult(true, null, $message, $type + NEUTRAL);
   */
  function astep($message=null, ?int $type=null): IMessenger;

  /**
   * méthode de convenance qui appelle
   *   aresult(true, null, $message, $type);
   */
  function asuccess($message=null, ?int $type=null): IMessenger;

  /**
   * méthode de convenance qui appelle
   *     aresult(false, null, [...$message, "exception" => $e], $type);
   */
  function afailure($message=null, ?Throwable $e=null, ?int $type=null): IMessenger;
}
