<?php
namespace nur\v;

use nur\A;
use nur\c;
use nur\config;
use nur\func;
use nur\interp;
use nur\str;

/**
 * Class v: méthodes outils pour produire du contenu pour le web
 *
 * NB: si on ajoute des méthodes ici, les ajouter aussi dans {@link vo}
 * et {@link ly}
 */
class v {
  /** retourner [$attr => $attr] si $cond est vrai */
  static final function attrif(string $attr, ?bool $cond): ?array {
    return $cond? [$attr => $attr]: null;
  }
  static final function selectedif($cond): ?array { return self::attrif("selected", $cond); }
  static final function checkedif($cond): ?array { return self::attrif("checked", $cond); }
  static final function requiredif($cond): ?array { return self::attrif("required", $cond); }
  static final function disabledif($cond): ?array { return self::attrif("disabled", $cond); }

  private static function _start_tag_attrs(string $tag, ?array $attrs): array {
    $parts = ["<$tag"];
    if ($attrs !== null) {
      foreach ($attrs as $key => $value) {
        if ($value === null || $value === false) continue;
        if (is_array($value)) {
          $value = str::join3($value);
          if (!$value) continue;
        }
        $parts[] = " ";
        $parts[] = $key;
        $parts[] = "=\"";
        $parts[] = htmlspecialchars($value);
        $parts[] = "\"";
      }
    }
    return $parts;
  }
  /** retourner un tag ouvrant avec ses attributs et son texte */
  static final function _start(string $tag, $vs=null): array {
    if ($vs === null || $vs === false) return ["<$tag>"];

    list($seq, $attrs) = A::split_assoc(A::with(c::q($vs)));
    # tout d'abord les attributs
    $parts = self::_start_tag_attrs($tag, $attrs);
    $parts[] = ">";
    # puis rajouter le contenu
    if ($seq !== null) $parts = [$parts, $seq];
    return $parts;
  }

  /** retourner un tag fermant */
  static final function _end(string $tag): array {
    return ["</$tag>"];
  }

  /**
   * retourner un tag avec ses attributs et son texte
   * toujours retourner un tag ouvrant et fermant
   *
   * si $require_content==true et que tous les contenus du tag sont null, ne pas
   * afficher le tag
   */
  static final function _tag(string $tag, $vs=null, bool $require_content=false): array {
    if ($vs === null || $vs === false) {
      return $require_content? []: ["<$tag></$tag>"];
    }

    list($seq, $attrs) = A::split_assoc(A::with(c::q($vs)));
    $count = $seq !== null? count($seq): 0;
    if ($require_content && $count > 0 && A::all_n($seq)) {
      # si tous les contenus sont null, ne pas afficher le tag
      return [];
    } else {
      # sinon tag normal
      $parts = self::_start_tag_attrs($tag, $attrs);
      $parts[] = ">";
      if ($seq !== null) $parts[] = $seq;
      $parts[] = "</$tag>";
      return $parts;
    }
  }

  /**
   * retourner un tag avec ses attributs et son texte.
   * si le tag n'a pas de contenu, faire un tag simple <tag/>
   *
   * si $require_content==true et que tous les contenus du tag sont null, ne pas
   * afficher le tag
   */
  static final function _tag1(string $tag, $vs=null, bool $require_content=false): array {
    if ($vs === null || $vs === false) {
      return $require_content? []: ["<$tag/>"];
    }

    list($seq, $attrs) = A::split_assoc(A::with(c::q($vs)));
    $count = A::count($seq);
    if ($require_content && $count > 0 && A::all_n($seq)) {
      # si tous les contenus sont null, ne pas afficher le tag
      return [];
    } elseif ($count == 0 || A::all_n($seq)) {
      # si pas de contenu, faire un tag simple
      $parts = self::_start_tag_attrs($tag, $attrs);
      $parts[] = "/>";
      return $parts;
    } else {
      # sinon tag normal
      $parts = self::_start_tag_attrs($tag, $attrs);
      $parts[] = ">";
      if ($seq !== null) $parts[] = $seq;
      $parts[] = "</$tag>";
      return $parts;
    }
  }

  /** retourner un tag ouvrant avec ses attributs et son texte */
  static final function start(string $tag, $vs=null): array {
    if ($vs !== null) $vs = c::fq($vs);
    return self::_start($tag, $vs);
  }

  /** retourner un tag fermant */
  static final function end(string $tag): array {
    return self::_end($tag);
  }

  /**
   * retourner un tag avec ses attributs et son texte
   * toujours retourner un tag ouvrant et fermant
   *
   * si $require_content==true et que tous les contenus du tag sont null, ne pas
   * afficher le tag
   */
  static final function tag(string $tag, $vs=null, bool $require_content=false): array {
    if ($vs !== null) $vs = c::fq($vs);
    return self::_tag($tag, $vs, $require_content);
  }

  /**
   * retourner un tag avec ses attributs et son texte.
   * si le tag n'a pas de contenu, faire un tag simple <tag/>
   *
   * si $require_content==true et que tous les contenus du tag sont null, ne pas
   * afficher le tag
   */
  static final function tag1(string $tag, $vs=null, bool $require_content=false): array {
    if ($vs !== null) $vs = c::fq($vs);
    return self::_tag1($tag, $vs, $require_content);
  }

  /** le tag h1 n'est pas généré s'il n'y a pas de contenu */
  static final function h1($vs): array { return self::tag("h1", $vs, true); }
  /** le tag h2 n'est pas généré s'il n'y a pas de contenu */
  static final function h2($vs): array { return self::tag("h2", $vs, true); }
  /** le tag h3 n'est pas généré s'il n'y a pas de contenu */
  static final function h3($vs): array { return self::tag("h3", $vs, true); }
  /** le tag h4 n'est pas généré s'il n'y a pas de contenu */
  static final function h4($vs): array { return self::tag("h4", $vs, true); }
  /** le tag h5 n'est pas généré s'il n'y a pas de contenu */
  static final function h5($vs): array { return self::tag("h5", $vs, true); }
  /** le tag h6 n'est pas généré s'il n'y a pas de contenu */
  static final function h6($vs): array { return self::tag("h6", $vs, true); }

  static final function hr($vs=null): array { return self::tag1("hr", $vs); }

  static final function div($vs): array { return self::tag("div", $vs); }
  static final function sdiv($vs=null): array { return self::start("div", $vs); }
  static final function ediv(): array { return self::end("div"); }
  static final function mdiv($key, ?array $data=null, $vs=null): array {
    $class = "m-".str_replace(".", "-", $key);
    $text = interp::string(config::m($key), $data);
    return self::div([$text, "class" => $class, $vs]);
  }

  /** le tag pre n'est pas généré s'il n'y a pas de contenu */
  static final function pre($vs): array { return self::tag("pre", $vs, true); }
  static final function spre($vs=null): array { return self::start("pre", $vs); }
  static final function epre(): array { return self::end("pre"); }

  /** le tag p n'est pas généré s'il n'y a pas de contenu */
  static final function p($vs): array { return self::tag("p", $vs, true); }
  static final function sp($vs=null): array { return self::start("p", $vs); }
  static final function ep(): array { return self::end("p"); }

  static final function br($vs=null): array { return self::tag1("br", $vs); }

  /** le tag span n'est pas généré s'il n'y a pas de contenu */
  static final function span($vs): array { return self::tag("span", $vs, true); }
  static final function mspan(string $key, ?array $data=null, $vs=null): array {
    $class = "m-".str_replace(".", "-", $key);
    $text = interp::string(config::m($key), $data);
    return self::span([$text, "class" => $class, $vs]);
  }
  static final function a($vs, ?string $url=null): array {
    if ($url !== null) $vs = ["href" => page::bu($url), c::q($vs)];
    return self::tag("a", $vs);
  }
  static final function b($vs): array { return self::tag("b", $vs); }
  static final function i($vs): array { return self::tag("i", $vs); }
  static final function em($vs): array { return self::tag("em", $vs); }
  static final function strong($vs): array { return self::tag("strong", $vs); }

  static final function ul($vs): array { return self::tag("ul", $vs); }
  static final function sul($vs=null): array { return self::start("ul", $vs); }
  static final function eul(): array { return self::end("ul"); }

  static final function ol($vs): array { return self::tag("ol", $vs); }
  static final function sol($vs=null): array { return self::start("ol", $vs); }
  static final function eol(): array { return self::end("ol"); }

  static final function li($vs): array { return self::tag("li", $vs); }
  static final function sli($vs=null): array { return self::start("li", $vs); }
  static final function eli(): array { return self::end("li"); }

  static final function table($vs): array { return self::tag("table", $vs); }
  static final function stable($vs=null): array { return self::start("table", $vs); }
  static final function etable(): array { return self::end("table"); }

  static final function thead($vs): array { return self::tag("thead", $vs); }
  static final function sthead($vs=null): array { return self::start("thead", $vs); }
  static final function ethead(): array { return self::end("thead"); }

  static final function tbody($vs): array { return self::tag("tbody", $vs); }
  static final function stbody($vs=null): array { return self::start("tbody", $vs); }
  static final function etbody(): array { return self::end("tbody"); }

  static final function tr($vs): array { return self::tag("tr", $vs); }
  static final function str($vs=null): array { return self::start("tr", $vs); }
  static final function etr(): array { return self::end("tr"); }

  static final function th($vs): array { return self::tag("th", $vs); }
  static final function td($vs): array { return self::tag("td", $vs); }

  static final function if($cond, $vs): iterable { return $cond? c::q($vs) : []; }
  static final function unless($cond, $vs): iterable { return !$cond? c::q($vs) : []; }
  static final function callif($cond, ?callable $function, ...$args): iterable {
    if (!$cond || $function === null) return [];
    return c::q(func::call($function, ...$args));
  }

  static final function foreach(?iterable $values, ?callable $function): array {
    $pieces = [];
    if ($values !== null) {
      if ($function !== null) {
        $index = 0;
        foreach ($values as $key => $value) {
          $pieces[] = func::call($function, $value, $key, $index++);
        }
      } else {
        foreach ($values as $value) {
          $pieces[] = $value;
        }
      }
    }
    return $pieces;
  }

  private static function _list(?iterable $values, ?string $sep=", ", ?string $prefix=null, ?string $suffix=null, ?callable $function): array {
    $vs = [];
    if ($values !== null) {
      $functx = $function !== null? func::_prepare($function): null;
      $first = true;
      $index = 0;
      foreach ($values as $key => $value) {
        if ($first) {
          $first = false;
          $vs[] = $prefix;
        } else {
          $vs[] = $sep;
        }
        if ($functx !== null) {
          $value = func::_call($functx, [$value, $key, $index++]);
        }
        $vs[] = $value;
      }
      if (!$first) $vs[] = $suffix;
    }
    return $vs;
  }

  static final function simple_list(?iterable $values, ?string $sep=", ", ?string $prefix=null, ?string $suffix=null): array {
    return self::_list($values, $sep, $prefix, $suffix, null);
  }

  const LIST_SCHEMA = [
    "sep" => ["?string", ", ", "séparateur entre les valeur"],
    "prefix" => ["?array", null, "préfixe à rajouter s'il y a des valeurs"],
    "suffix" => ["?array", null, "suffixe à rajouter s'il y a des valeurs"],
    "function" => ["?callable", null, "fonction qui calcule la valeur à ajouter"],
  ];
  static final function list(?iterable $values, ?array $params): array {
    $sep = A::get($params, "sep", ", ");
    $prefix = A::get($params, "prefix");
    $suffix = A::get($params, "suffix");
    $function = A::get($params, "function");
    return self::_list($values, $sep, $prefix, $suffix, $function);
  }
}
