<?php
namespace nur\v\html5;

use nur\A;
use nur\b\ui\AbstractMessenger;
use nur\b\UserException;
use nur\data\types\md_utils;
use nur\data\types\Metadata;
use nur\v\v;
use nur\v\vo;

class Html5Messenger extends AbstractMessenger {
  const MESSAGE_OPTIONS_SCHEMA = [];

  /** @var Metadata */
  private static $message_md;

  protected static function message_md(): Metadata {
    return md_utils::ensure_md(self::$message_md
      , array_merge(self::MESSAGE_SCHEMA, self::MESSAGE_OPTIONS_SCHEMA));
  }

  const SECTION_LEVEL_CLASSES = [
    # les clés doivent être ordonnées de la plus grande à la plus petite
    self::LEVEL_CRITICAL => "msg-section-critical",
    self::LEVEL_MAJOR => "msg-section-major",
    self::LEVEL_NORMAL => "msg-section-normal",
    self::LEVEL_MINOR => "msg-section-minor",
  ];
  const SECTION_TYPE_CLASSES = [
    # les clés doivent être ordonnées de la plus grande à la plus petite
    self::TYPE_ERROR => "msg-section-error",
    self::TYPE_WARNING => "msg-section-warning",
    self::TYPE_INFO => "msg-section-info",
    self::TYPE_DEBUG => "msg-section-debug",
  ];
  const SECTION_TYPE_PREFIXES = [
    # les clés doivent être ordonnées de la plus grande à la plus petite
    self::LEVEL_CRITICAL => [
      self::TYPE_ERROR => "[CRITIQUE!] ",
      self::TYPE_WARNING => "[AVERTISSEMENT!] ",
      self::TYPE_DEBUG => "[IMPORTANT!] ",
    ],
    self::LEVEL_MAJOR => [
      self::TYPE_ERROR => "[ERREUR] ",
      self::TYPE_WARNING => "[AVERTISSEMENT] ",
      self::TYPE_DEBUG => "[NOTE] ",
    ],
    self::LEVEL_NORMAL => [
      self::TYPE_DEBUG => null,
    ],
    self::LEVEL_MINOR => [
      self::TYPE_ERROR => "[erreur] ",
      self::TYPE_WARNING => "[avertissement] ",
      self::TYPE_DEBUG => null,
    ],
  ];

  const GROUP_LEVEL_CLASSES = [
    # les clés doivent être ordonnées de la plus grande à la plus petite
    self::LEVEL_CRITICAL => "msg-group-critical",
    self::LEVEL_MAJOR => "msg-group-major",
    self::LEVEL_NORMAL => "msg-group-normal",
    self::LEVEL_MINOR => "msg-group-minor",
  ];
  const GROUP_TYPE_CLASSES = [
    # les clés doivent être ordonnées de la plus grande à la plus petite
    self::TYPE_ERROR => "msg-group-error",
    self::TYPE_WARNING => "msg-group-warning",
    self::TYPE_INFO => "msg-group-info",
    self::TYPE_DEBUG => "msg-group-debug",
  ];
  
  const LEVEL_CLASSES = [
    # les clés doivent être ordonnées de la plus grande à la plus petite
    self::LEVEL_CRITICAL => "msg-critical",
    self::LEVEL_MAJOR => "msg-major",
    self::LEVEL_NORMAL => "msg-normal",
    self::LEVEL_MINOR => "msg-minor",
  ];
  const TYPE_CLASSES = [
    # les clés doivent être ordonnées de la plus grande à la plus petite
    self::TYPE_ERROR => "msg-error",
    self::TYPE_WARNING => "msg-warning",
    self::TYPE_INFO => "msg-info",
    self::TYPE_DEBUG => "msg-debug",
  ];
  const TYPE_PREFIXES = [
    # les clés doivent être ordonnées de la plus grande à la plus petite
    self::LEVEL_CRITICAL => [
      self::TYPE_ERROR => "[CRITICAL!] ",
      self::TYPE_WARNING => "[ATTENTION!] ",
      self::TYPE_DEBUG => "[IMPORTANT!] ",
    ],
    self::LEVEL_MAJOR => [
      self::TYPE_ERROR => "[ERROR] ",
      self::TYPE_WARNING => "[WARN] ",
      self::TYPE_DEBUG => "[NOTE] ",
    ],
    self::LEVEL_NORMAL => [
      self::TYPE_DEBUG => null,
    ],
    self::LEVEL_MINOR => [
      self::TYPE_ERROR => "[error] ",
      self::TYPE_WARNING => "[warn] ",
      self::TYPE_INFO => null,
      self::TYPE_DEBUG => "[debug] ",
    ],
  ];
  
  const RESULT_CLASS_PREFIXES = [
    self::RESULT_FAILURE => ["msg-result-failure", "✘ "],
    self::RESULT_SUCCESS => ["msg-result-success", "✔ "],
    self::RESULT_NEUTRAL => ["msg-result-neutral", ". "],
    self::RESULT_NONE => [null, null],
  ];

  protected function getClassPrefix(
    ?int $type, ?int $level
    , array $levelClasses
    , array $typeClasses
    , ?array $typePrefixes=null
  ): array {
    $class = null;
    $prefix = false;
    
    if ($type !== null) {
      $class = [];
      foreach ($levelClasses as $classLevel => $classValue) {
        if ($level >= $classLevel) {
          $class[] = $classValue;
          break;
        }
      }
      foreach ($typeClasses as $classType => $classValue) {
        if ($type >= $classType) {
          $class[] = $classValue;
          break;
        }
      }
  
      if ($level !== null && $typePrefixes !== null) {
        foreach ($typePrefixes as $prefixLevel => $prefixes) {
          if ($level >= $prefixLevel) {
            foreach ($prefixes as $prefixType => $prefixValue) {
              if ($type >= $prefixType) {
                $prefix = $prefixValue;
                break;
              }
            }
          }
          if ($prefix !== false) break;
        }
      }
    }
    return [$class, $prefix];
  }

  protected function getResultClassPrefix(int $result): array {
    return static::RESULT_CLASS_PREFIXES[$result];
  }

  protected $showSectionHeading = false;

  function setShowSectionHeading(bool $showSectionHeading=true): self {
    $this->showSectionHeading = $showSectionHeading;
    return $this;
  }

  protected function printStartSection($title, ?int $msgType, ?int $msgLevel): void {
    if ($msgType !== null) {
      $result = $msgType & self::RESULT_MASK;
      $msgType = $msgType & self::TYPE_MASK;
    } else {
      $result = self::RESULT_NONE;
    }
    [$typeClass, $typePrefix] = $this->getClassPrefix($msgType, $msgLevel
      , static::SECTION_LEVEL_CLASSES
      , static::SECTION_TYPE_CLASSES
      , static::SECTION_TYPE_PREFIXES
    );
    [$resultClass, $resultPrefix] = $this->getResultClassPrefix($result);
    $sectionHeading = null;
    if ($title && $this->showSectionHeading) {
      $sectionHeading = v::div([
        "class" => "msg-section-heading",
        v::h3([
          "class" => "msg-section-title",
          $typePrefix, $resultPrefix, q($title),
        ]),
      ]);
    }
    vo::sdiv([
      "class" => ["msg-section", $typeClass, $resultClass],
      $sectionHeading,
      v::sdiv(["class" => "msg-section-body"]),
    ]);
  }

  protected function printEndSection(): void {
    vo::ediv();
    vo::ediv();
  }

  protected function getGroupCount(?array $group): int {
    if ($group === null ) return 0;
    $count = $group["count"];
    return $count !== null? $count: 2;
  }

  protected function getGroupPrefix(?array $group, int $maxCount=1): string {
    if ($group === null) return "";
    return $this->getGroupCount($group) > $maxCount? "": $group["prefix"]."&nbsp;: ";
  }

  protected function printStartGroup(array $group, ?array $groups): void {
    if ($this->getGroupCount($group) > 1) {
      $type = $group["type"];
      $level = $group["level"];
      $prefix = $group["prefix"];
      if ($type !== null) $type = $type & self::TYPE_MASK;
      [$typeClass, $typePrefix] = $this->getClassPrefix($type, $level
        , static::GROUP_LEVEL_CLASSES
        , static::GROUP_TYPE_CLASSES
      );
      vo::sdiv([
        "class" => ["msg-group", $typeClass],
        v::div([
          "class" => "msg-group-heading",
          v::h4([
            "class" => "msg-group-title",
            $typePrefix, q($prefix),
          ]),
        ]),
        v::sdiv(["class" => "msg-group-body"]),
      ]);
    }
  }

  protected function printEndGroup(array $group): void {
    if ($this->getGroupCount($group) > 1) {
      vo::ediv();
      vo::ediv();
    }
  }

  protected function getUserMsg($msg): array {
    return v::span([
      "class" => "msg-user",
      q($msg),
    ]);
  }

  protected function getTechMsg($msg): array {
    return v::div([
      "class" => "msg-tech",
      "style" => "margin-top: 1em;",
      "Le message d'erreur technique est&nbsp;: ", q($msg),
    ]);
  }

  protected function getExceptionMsg($exception, $user, $tech, bool $haveTechMsgOrSummary): array {
    $msg = [
      v::sdiv(["class" => "msg-exception"]),
      v::div([
        "class" => "msg-exception-summary",
        "style" => "margin-top: 1em;",
        "D'autres informations techniques sont disponibles. Le service informatique sera peut-être amené à vous les demander. ",
        v::a([
          "href" => "",
          "accesskey" => "d",
          "onclick" => 'javascript: var $me = jQuery(this).closest("div.msg-exception"); $me.find("div.msg-exception-details").removeClass("hidden"); $me.find("div.msg-exception-summary").addClass("hidden"); return false;',
          "Afficher les détails..."
        ]),
      ]),
      v::sdiv(["class" => "msg-exception-details hidden"]),
    ];
    if (!$haveTechMsgOrSummary || ($exception !== $user && $exception !== $tech)) {
      $msg[] = v::div([
        "class" => "msg-exception-tech",
        "style" => "margin-top: 1em;",
        "Le message d'erreur technique détaillé est: ",
        q(UserException::get_summary($exception)),
      ]);
    }
    $msg[] = v::div([
      "class" => "msg-exception-traceback",
      "style" => "margin-top: 1em;",
      "Traceback:",
      v::pre([UserException::get_traceback($exception)]),
    ]);
    $msg[] = v::ediv();
    $msg[] = v::ediv();
    return $msg;
  }

  protected function printMsg(
    ?array $groups,
    bool $printUser, $userMsg,
    bool $printTech, $techMsg,
    bool $printException, $exceptionMsg,
    int $type, int $level, array $options
  ): void {
    $group = A::last($groups);
    if ($group === false) {
      # groupe neutralisé
      return;
    }
    $groupPrefix = $this->getGroupPrefix($group);

    $result = $type & self::RESULT_MASK;
    $type = $type & self::TYPE_MASK;
    [$typeClass, $typePrefix] = $this->getClassPrefix($type, $level
      , static::LEVEL_CLASSES
      , static::TYPE_CLASSES
      , static::TYPE_PREFIXES
    );
    [$resultClass, $resultPrefix] = $this->getResultClassPrefix($result);

    if ($printUser || $printTech || $printException) {
      $lines = v::sdiv([
        "class" => ["msg", $typeClass, $resultClass],
        $typePrefix, $resultPrefix, $groupPrefix,
      ]);
      if ($printUser) $lines[] = $userMsg;
      elseif ($userMsg) $lines[] = ["\n<!--", $userMsg, "-->"];
      if ($printTech) $lines[] = $techMsg;
      elseif ($techMsg) $lines[] = ["\n<!--", $techMsg, "-->"];
      if ($printException) $lines[] = $exceptionMsg;
      elseif ($exceptionMsg) $lines[] = ["\n<!--", $exceptionMsg, "-->"];
      $lines[] = v::ediv();
    } else {
      $lines = ["\n<!--", $typePrefix, $resultPrefix];
      if ($userMsg) $lines[] = ["\n", $userMsg];
      if ($techMsg) $lines[] = ["\n", $techMsg];
      if ($exceptionMsg) $lines[] = ["\n", $exceptionMsg];
      $lines[] = "-->";
    }
    vo::print($lines);
  }
}
