<?php
namespace nur\sery\php\content;

use nur\sery\cl;
use nur\sery\php\func;

/**
 * Class content: gestionnaire de contenu (statique ou dynamique)
 */
class content {
  /** quoter $vs si c'est un scalaire. sinon retourner le tableau tel quel */
  static final function quote($contents): iterable {
    if ($contents === null || $contents === false) return [];
    elseif ($contents instanceof IContent) return [$contents];
    elseif ($contents instanceof IPrintable) return [$contents];
    elseif (is_iterable($contents)) return $contents;
    else return [htmlspecialchars(strval($contents))];
  }

  /**
   * applatir $contents, et retourner une liste de valeurs scalaires:
   * - les instances de IContent et IPrintable sont remplacées par leur contenu
   * - les chaines sont ajoutées telles quelles
   * - les autres objets sont transformés en chaine de caractère avec strval()
   */
  static function flatten($contents, ?array &$dest=null): array {
    if ($dest === null) $dest = [];
    if ($contents === null) return $dest;
    if ($contents instanceof IContent) {
      $contents = $contents->getContent();
    } elseif ($contents instanceof IPrintable) {
      ob_start(null, 0, PHP_OUTPUT_HANDLER_STDFLAGS ^ PHP_OUTPUT_HANDLER_FLUSHABLE);
      $contents->print();
      $dest[] = ob_get_clean();
      return $dest;
    } elseif (is_scalar($contents)) {
      $dest[] = $contents;
      return $dest;
    } elseif (!is_iterable($contents)) {
      $dest[] = strval($contents);
      return $dest;
    }
    # sinon parcourir $contents
    foreach ($contents as $value) {
      if ($value === null) continue;
      elseif (is_scalar($value)) $dest[] = $value;
      else self::flatten($value, $dest);
    }
    return $dest;
  }

  /**
   * résoudre tout le contenu statique: il ne restera plus qu'une liste de
   * chaines ou d'instances de IContent, IPrintable ou des objets quelconques
   */
  static function resolve_static($contents, $dest_class=null, bool $quote=true, ?array &$dest=null): array {
    if ($dest === null) $dest = [];
    if ($contents === null) return $dest;
    if (is_string($contents)) {
      if ($quote) $contents = htmlspecialchars($contents);
      $dest[] = $contents;
      return $dest;
    } elseif ($contents instanceof IStaticContent) {
      $contents = $contents->getContent();
      $dest = cl::merge($dest, self::resolve_static($contents, $dest_class, false));
      return $dest;
    } elseif ($contents instanceof IContent) {
      $dest[] = $contents;
      return $dest;
    } elseif ($contents instanceof IPrintable) {
      $dest[] = $contents;
      return $dest;
    } elseif (!is_iterable($contents)) {
      $dest[] = $contents;
      return $dest;
    }
    foreach ($contents as $content) {
      if ($content === null) continue;
      elseif ($contents instanceof IStaticContent) {
        $contents = $content->getContent();
        $dest = cl::merge($dest, self::resolve_static($contents, $dest_class, false));
      } elseif ($contents instanceof IContent) $dest[] = $content;
      elseif ($contents instanceof IPrintable) $dest[] = $content;
      elseif (!is_array($content)) $dest[] = $content;
      else {
        # ici, $content est soit:
        # - un appel de fonction ou méthode statique d'une des formes suivantes:
        #   - [class, method, ...args]
        #   - [null, method, ...args]
        #   - [[class, method], ...args]
        #   - [[null, method], ...args]
        # - ou une définition d'objet d'une des formes suivantes
        #   - [class, ...args]
        #   - [[class], ...args]
        $func_isa_class = false;
        if (array_key_exists(0, $content) && is_array($content[0])) {
          # $content est de la forme [func_or_class, ...args]
          $func = $content[0];
          $args = array_slice($content, 1);
          if (func::is_static($func)) {
            func::ensure_func($func, $dest_class, $args);
          } elseif (func::is_class($func)) {
            $func_isa_class = true;
            func::fix_class_args($func, $args);
          } else {
            func::ensure_func($func, $dest_class, $args);
          }
        } else {
          # $content est de la forme $func
          $func = $content;
          if (func::is_static($func)) {
            func::ensure_func($func, $dest_class, $args);
          } elseif (func::is_class($func)) {
            $func_isa_class = true;
            func::fix_class_args($func, $args);
          } else {
            func::ensure_func($func, $dest_class, $args);
          }
        }
        # chaque argument est un contenu statique
        foreach ($args as &$arg) {
          $array = is_array($arg);
          $arg = self::resolve_static($arg, $dest_class, false);
          $arg = self::flatten($arg);
          if ($array) {
            $arg = [implode("", $arg)];
          } else {
            switch (count($arg)) {
            case 0: $arg = null; break;
            case 1: $arg = $arg[0]; break;
            default: $arg = implode("", $arg);
            }
          }
        }; unset($arg);
        # puis appeler la fonction
        if ($func_isa_class) $content = func::cons($func, ...$args);
        else $content = func::call($func, ...$args);
        $dest[] = $content;
      }
    }
    return $dest;
  }

  static function to_string($contents, $dest_class=null, bool $quote=true): string {
    return implode("", self::flatten(self::resolve_static($contents, $dest_class, $quote)));
  }
}
