<?php
namespace nur\v\bs3;

use nur\A;
use nur\b\UserException;
use nur\b\ValueException;
use nur\md;
use nur\str;
use nur\v\base\TagsManager;
use nur\v\fo;
use nur\v\model\IFormManager;
use nur\v\model\INavbarManager;
use nur\v\page;
use nur\v\v;

class Bs3NavbarManager extends TagsManager implements INavbarManager {
  const TYPE_FIXED = "fixed-top";
  const TYPE_STATIC = "static-top";
  const TYPE_NORMAL = "normal";

  static final function get_class_style(?string $navbar): array {
    if ($navbar === null) $navbar = self::TYPE_NORMAL;
    $class = false;
    $style = false;
    switch ($navbar) {
    case "fixed-top":
    case "fixed":
    case "f":
      $class = "navbar-fixed-top";
      $style = '<style>body { margin-top: 70px; }</style>';
      break;
    case "static-top":
    case "static":
    case "s":
      $class = "navbar-static-top";
      break;
    case "normal":
    case "n":
      break;
    default:
      throw new ValueException("$navbar: invalid navbar type");
    }
    return [$class, $style];
  }

  private static function get_align_class($align): ?string {
    if ($align === null) return null;
    switch ($align) {
    case "navbar-left":
    case "align-left":
    case "left":
    case "l":
      $align_class = "navbar-left";
      break;
    case "navbar-right":
    case "align-right":
    case "right":
    case "r":
      $align_class = "navbar-right";
      break;
    default:
      throw new UserException("$align: invalid align");
    }
    return $align_class;
  }

  const NAVBAR_OPTIONS_SCHEMA = [
    "navbar" => ["?string", self::TYPE_NORMAL, "type de barre de navigation"],
    "container" => ["?string", _container::CONTAINER_NORMAL, "type de container"],
    "brand" => ["?string", null, "Marque"],
    "show_brand" => [null, true, "Faut-il afficher la marque?"],
    "class" => ["?string", null, "classe CSS"],
  ];

  protected $formManager;

  function setFormManager(IFormManager $formManager): void {
    $this->formManager = $formManager;
  }

  function getFormManager(): IFormManager {
    $formManager = $this->formManager;
    if ($formManager !== null) return $formManager;
    else return fo::manager();
  }

  function start($options=null): array {
    md::ensure_schema($options, self::NAVBAR_OPTIONS_SCHEMA);

    [$navbarClass, $navbarStyle] = self::get_class_style($options["navbar"]);
    $class = $options["class"];
    $containerClass = _container::get_class($options["container"]);

    $brand = $options["brand"];
    $showBrand = $options["show_brand"];
    if (!$brand) {
      $brand = false;
    } elseif ($showBrand && $showBrand !== "asis") {
      $brand = v::span(["class" => "navbar-brand", $brand]);
    }

    $start = [
      $navbarStyle,
      v::sdiv([
        "class" => ["navbar navbar-default", $navbarClass, $class],
        "role" => "navigation",
      ]),
      v::sdiv(["class" => $containerClass]),
      v::div([
        "class" => "navbar-header",
        v::tag("button", [
          "class" => "navbar-toggle",
          "data-toggle" => "collapse",
          "data-target" => ".navbar-collapse",
          v::span(["class" => "sr-only", "Toggle navigation"]),
          v::span(["class" => "icon-bar"]),
          v::span(["class" => "icon-bar"]),
          v::span(["class" => "icon-bar"]),
        ]),
        $brand,
      ]),
      v::sdiv(["class" => "navbar-collapse collapse"]),
    ];
    $end = [
      v::ediv(),
      v::ediv(),
      v::ediv(),
    ];
    $content = $this->push("navbar", $start, $end);
    #$this->getFormManager()->push(["type" => Bs3FormManager::TYPE_NAVBAR]);
    return $content;
  }

  function end(): array {
    $content = $this->pop("navbar");
    #$this->getFormManager()->pop();
    return $content;
  }

  function started(): bool {
    return $this->haveMark("navbar");
  }
  
  const NAV_OPTIONS_SCHEMA = [
    "type" => ["?string", "nav", "type de section"],
    "align" => ["?string", "left", "alignement de la section"],
    "class" => ["?string", null, "classe CSS"],
  ];

  function startNav($options=null): array {
    md::ensure_schema($options, self::NAV_OPTIONS_SCHEMA);
    $type = $options["type"];
    switch ($type) {
    case "nav":
    case "n":
      $typeClass = "navbar-nav";
      break;
    case "form":
    case "f":
      $typeClass = "navbar-form";
      break;
    default:
      throw new UserException("$type: invalid type");
    }
    $alignClass = self::get_align_class($options["align"]);
    $class = $options["class"];
    $start = v::sul([
      "class" => ["nav", $typeClass, $alignClass, $class],
    ]);
    $end = v::eul();

    $content = null;
    A::append_nn($content, $this->pop("nav"));
    A::append_nn($content, $this->push("nav", $start, $end));
    return $content;
  }

  const NAV_ITEM_SCHEMA = [
    "value" => [null, null, "la valeur de l'élément"],
    "item" => ["string", "text", "type d'élément"],
  ];

  const ITEM_BRAND = "brand";
  const ITEM_TEXT = "text";
  const ITEM_LINK = "link";
  const ITEM_BUTTON = "button";
  const ITEM_MENU = "menu";
  const ITEM_LITERAL = "literal";

  function navItem($item): array {
    md::ensure_schema($item, self::NAV_ITEM_SCHEMA);
    $type = $item["item"];
    $inNav = $this->haveMark("nav");
    switch ($type) {
    case self::ITEM_BRAND:
      return $this->navBrand($item, $inNav);
    case self::ITEM_TEXT:
    case "t":
      #$item["type"] = self::ITEM_TEXT;
      return $this->navText($item, $inNav);
    case self::ITEM_LINK:
    case "l":
      $item["type"] = self::ITEM_LINK;
      return $this->navLink($item);
    case self::ITEM_BUTTON:
    case "b":
      $item["type"] = self::ITEM_BUTTON;
      if (!$inNav) return $this->navLink($item);
      break;
    case self::ITEM_MENU:
    case "m":
      #$item["type"] = self::ITEM_MENU;
      return $this->navMenu($item, $inNav);
    case self::ITEM_LITERAL:
    case "asis":
      #$item["type"] = self::ITEM_LITERAL;
      return $item["value"];
    }
    throw new ValueException("$type: invalid item type");
  }

  const NAV_BRAND_SCHEMA = [
    "value" => [null, null, "la marque à afficher"],
    "class" => ["?array", null, "classe CSS de l'élément"],
    # class: type ?array pour le moment, à remplacer par "?items"
    "style" => ["?string", null, "style CSS de l'élément"],
    "tag" => ["?string", null, "tag à utiliser pour l'élément"],
    "attrs" => ["?array", null, "attributs HTML génériques de l'élément"],
    "brand" => [null, null, "la marque à afficher"],
    # hors nav
    "align" => ["?string", null, "alignement de l'élément"],
  ];

  function navBrand($brand, bool $inNav): array {
    md::ensure_schema($brand, self::NAV_BRAND_SCHEMA);
    A::replace_n_indirect($brand, "value", "brand");
    $tag = $brand["tag"];
    if ($tag === null) $tag = $inNav? "li": "span";
    $alignClass = self::get_align_class($brand["align"]);
    return v::tag($tag, [
      "class" => ["navbar-brand", $alignClass, $brand["class"]],
      "style" => $brand["style"],
      $brand["attrs"],
      q($brand["value"]),
    ]);
  }

  const NAV_TEXT_SCHEMA = [
    "value" => [null, null, "le texte à afficher"],
    "class" => ["?array", null, "classe CSS de l'élément"],
    # class: type ?array pour le moment, à remplacer par "?items"
    "style" => ["?string", null, "style CSS de l'élément"],
    "tag" => ["?string", null, "tag à utiliser pour l'élément"],
    "attrs" => ["?array", null, "attributs HTML génériques de l'élément"],
    "text" => [null, null, "le texte à afficher"],
    # hors nav
    "align" => ["?string", null, "alignement de l'élément"],
  ];

  function navText($text, bool $inNav): array {
    md::ensure_schema($text, self::NAV_TEXT_SCHEMA);
    A::replace_n_indirect($text, "value", "text");
    $tag = $text["tag"];
    if ($tag === null) $tag = $inNav? "li": "p";
    $alignClass = self::get_align_class($text["align"]);
    return v::tag($tag, [
      "class" => ["navbar-text", $alignClass, $text["class"]],
      "style" => $text["style"],
      $text["attrs"],
      q($text["value"]),
    ]);
  }

  const NAV_LINK_SCHEMA = [
    "url" => ["?string", null, "l'url du lien, à adapter avec page::bu()"],
    "value" => [null, null, "le texte à afficher"],
    "active" => ["?bool", false, "le lien est-il actif?"],
    "show_active" => ["?bool", false, "faut-il afficher le lien comme actif (même s'il n'est pas actif)?"],
    "accesskey" => ["?string", null, "touche d'accès rapide"],
    "target" => ["?string", null, "destination du lien"],
    "params" => ["?array", null, "des paramètres à ajouter à l'url le cas échéant"],
    "class" => ["?array", null, "classe CSS du lien"],
    # class: type ?array pour le moment, à remplacer par "?items"
    "style" => ["?string", null, "style CSS du lien"],
    "attrs" => ["?array", null, "attributs HTML génériques du lien"],
    "text" => [null, null, "le texte à afficher"],
    "literal_url" => ["?string", null, "l'url du lien, à utiliser sans modification"],
    # hors nav, i.e type button
    "align" => ["?string", null, "alignement de l'élément"],
  ];

  private static function ensure_link(array &$link): void {
    A::replace_n_indirect($link, "value", "text");
    A::replace_n_indirect($link, "value", "url");
  }
  private static function is_sep(array $link): bool {
    $value = $link["value"];
    $url = $link["url"];
    if ($url !== null) $sep = $url;
    elseif ($value !== null) $sep = $value;
    else $sep = null;
    if (is_array($sep)) $sep = str::join("", $sep);
    $literal_url = $link["literal_url"];
    return $sep !== null && $literal_url == null && preg_match('/^-{3,}/', $sep);
  }
  private function _navSep(array $link): array {
    return v::li([
      "role" => "separator",
      "class" => "divider",
      $link["attrs"],
    ]);
  }
  private function _navLinkShared(array $link): array {
    $url = $link["url"];
    $params = $link["params"];
    $literal_url = $link["literal_url"];
    if ($literal_url !== null) $url = $literal_url;
    elseif ($url === null) $url = "#";
    else $url = page::bu($url, $params);

    return v::a([
      "href" => $url,
      "accesskey" => $link["accesskey"],
      "target" => $link["target"],
      "class" => $link["class"],
      "style" => $link["style"],
      $link["attrs"],
      q($link["value"]),
    ]);
  }
  private function _navLink(array $link): array {
    return v::li([
      "class" => ["active" => $link["active"] || $link["show_active"]],
      $this->_navLinkShared($link),
    ]);
  }
  private function _navButton(array $button): array {
    $a = $this->_navLinkShared($button);
    return $a;
  }

  function navLink($link): array {
    md::ensure_schema($link, self::NAV_LINK_SCHEMA);
    self::ensure_link($link);
    switch ($link["type"]) {
    case self::ITEM_LINK:
      if (self::is_sep($link)) return $this->_navSep($link);
      else return $this->_navLink($link);
    case self::ITEM_BUTTON:
      $alignClass = self::get_align_class($link["align"]);
      $active = $link["active"];
      $class = $link["class"];
      if ($active !== null) {
        $activeClass = $active ? "btn-primary" : "btn-default";
      } elseif ($class === null) {
        $activeClass = "btn-default";
      } else {
        $activeClass = null;
      }
      $link["class"] = ["navbar-btn btn", $activeClass, $alignClass, $class];
      return $this->_navButton($link);
    default:
      return [];
    }
  }

  const NAV_MENU_SCHEMA = [
    "value" => [null, null, "le texte à afficher"],
    "links" => ["?array[]", null, "les liens à afficher"],
    "active" => ["?bool", null, "le lien est-il actif?"],
    "class" => ["?array", null, "classe CSS du lien"],
    # class: type ?array pour le moment, à remplacer par "?items"
    "style" => ["?string", null, "style CSS du lien"],
    "attrs" => ["?array", null, "attributs HTML génériques du lien"],
    "text" => [null, null, "le texte à afficher"],
    # hors nav
    "align" => ["?string", null, "alignement de l'élément"],
  ];

  function navMenu($menu): array {
    md::ensure_schema($menu, self::NAV_MENU_SCHEMA);
    A::replace_n_indirect($menu, "value", "text");
    $active = $menu["active"];
    $navLinks = [];
    foreach ($menu["links"] as $link) {
      $itemType = A::get($link, "item");
      if ($itemType == self::ITEM_TEXT) {
        # support minimal du type texte
        $text = $link;
        md::ensure_schema($text, self::NAV_TEXT_SCHEMA);
        $navLinks[] = v::p([
          "class" => ["navbar-text", $text["class"]],
          "style" => $text["style"],
          $text["attrs"],
          q($text["value"]),
        ]);
      } elseif ($itemType == self::ITEM_LITERAL) {
        # support minimal du type literal
        $literal = $link;
        md::ensure_schema($literal, self::NAV_ITEM_SCHEMA);
        $navLinks[] = $literal["value"];
      } else {
        md::ensure_schema($link, self::NAV_LINK_SCHEMA);
        self::ensure_link($link);
        if (self::is_sep($link)) {
          $navLinks[] = $this->_navSep($link);
        } else {
          $navLinks[] = $this->_navLink($link);
        }
        if ($active === null && $link["active"]) {
          $active = true;
        }
      }
    }
    return v::li([
      "class" => ["dropdown", $active? "active": null],
      v::a([
        "href" => "#", "role" => "button",
        "class" => "dropdown-toggle", "data-toggle" => "dropdown",
        "aria-haspopup" => "true", "aria-expanded" => "false",
        q($menu["value"]),
        " <span class=\"caret\"></span>",
      ]),
      v::ul(["class" => "dropdown-menu", $navLinks]),
    ]);
  }

  function endNav(): array {
    $endnav = $this->pop("nav");
    if ($endnav === null) $endnav = [];
    return $endnav;
  }
}
