<?php
namespace nur\v\bs3\vc;

use Iterator;
use nur\A;
use nur\b\coll\BaseArray;
use nur\b\params\IParametrable;
use nur\b\params\Tparametrable1;
use nur\b\ValueException;
use nur\data\types\md_utils;
use nur\data\types\Metadata;
use nur\func;
use nur\iter;
use nur\v\base\ComponentPrintable;
use Traversable;

class _CItemList extends ComponentPrintable implements IParametrable {
  use Tparametrable1;

  /** @var ?array schéma des informations supplémentaires */
  const DATA_SCHEMA = null;

  const SHOW_EMPTY = null;
  const AUTOPRINT = null;

  const PARAMETRABLE_PARAMS_SCHEMA = [
    "items" => ["?iterable", null, "source des éléments à afficher"],
    "filter_func" => ["?callable", null, "fonction permettant de filtrer les éléments à afficher"],
    "map_func" => ["?callable", null, "fonction permettant de mapper les éléments"],
    "show_empty" => ["bool", false, "afficher même s'il n'y a pas d'éléments?"],
    "autoprint" => ["bool", false, "faut-il afficher automatiquement les éléments"],
    "data" => ["?array", null, "donnés supplémentaires utilisées pour l'affichage"],
  ];

  function __construct(?iterable $items=null, ?array $params=null) {
    self::set_parametrable_params_defaults($params, [
      "show_empty" => static::SHOW_EMPTY,
      "autoprint" => static::AUTOPRINT,
    ]);
    A::set_nn($params, "items", $items);
    [$params, $data] = $this->splitParametrableParams($params);
    A::merge($params["data"], $data);
    $this->initParametrableParams($params);
    if ($this->ppItems === null) $this->ppItems = $this->ITEMS();
    if ($this->ppAutoprint) $this->print();
  }

  /**
   * retourner la liste des éléments à afficher si elle n'est pas fournie par
   * l'utilisateur
   */
  protected function ITEMS(): ?iterable {
    return null;
  }

  /** @var ?iterable */
  protected $ppItems;

  /** @var array */
  protected $filterCtx;

  function pp_setFilterFunc(?callable $filterFunc): void {
    if ($filterFunc === null) $this->filterCtx = null;
    else $this->filterCtx = func::_prepare($filterFunc);
  }

  /** @var array */
  protected $mapCtx;

  function pp_setMapFunc(?callable $mapFunc): void {
    if ($mapFunc === null) $this->mapCtx = null;
    else $this->mapCtx = func::_prepare($mapFunc);
  }

  /** @var bool */
  protected $ppShowEmpty;

  /** @var bool */
  protected $ppAutoprint;

  /** @var Metadata */
  private $dataSchema;

  /** @var mixed données supplémentaires utilisées pour l'affichage */
  protected $data;

  function pp_setData($data): self {
    $dataSchema = static::DATA_SCHEMA;
    if ($dataSchema !== null && $this->dataSchema === null) {
      md_utils::ensure_md($this->dataSchema, $dataSchema);
    }
    if ($this->dataSchema !== null) {
      $this->dataSchema->ensureSchema($data);
    }
    $this->data = $data;
    return $this;
  }

  /** @var iterable */
  protected $items;

  /**
   * construire la liste brute effective des éléments. le filtrage et le mapping
   * se fait dans print()
   */
  protected function buildItems(): void {
    $this->items = $this->ppItems;
  }

  function haveItems(): bool {
    if ($this->items === null) {
      return false;
    } elseif ($this->items instanceof Traversable) {
      # on doit forcer l'énumération pour savoir s'il y a des éléments
      $this->items = iterator_to_array($this->items);
    }
    return boolval($this->items);
  }

  protected function rewindItems(): bool {
    return iter::rewind($this->items);
  }

  protected function validItem(): bool {
    return iter::valid($this->items);
  }

  protected function currentItem(&$key=null) {
    return iter::current($this->items, $key);
  }

  protected function nextItem(): void {
    iter::next($this->items);
  }

  protected function ensureArray($item): array {
    if (!is_array($item)) {
      if ($item instanceof BaseArray) $item = $item->array();
      elseif ($item instanceof Iterator) $item = iterator_to_array($item);
      else throw ValueException::unexpected_type("array", $item);
    }
    return $item;
  }

  /** @var int index de l'élément courant */
  protected $index;

  /** @var string|int clé de l'élément courant */
  protected $key;

  function print(): void {
    $this->buildItems();
    $filterCtx = $this->filterCtx;
    $mapCtx = $this->mapCtx;
    $this->rewindItems();
    $this->index = 0;
    $first = true;
    while ($this->validItem()) {
      try {
        $item = $this->currentItem($this->key);
        if ($filterCtx !== null) {
          if (!func::_call($filterCtx, [$item, $this->key])) continue;
        }
        if ($mapCtx !== null) $item = func::_call($mapCtx, [$item, $this->key]);
        else $item = $this->ensureArray($item);

        if ($first) {
          $first = false;
          $this->printStartContainer();
        }
        $this->printItem($item);
        $this->index++;
      } finally {
        $this->nextItem();
      }
    }
    if ($first && $this->ppShowEmpty) {
      $first = false;
      $this->printStartContainer();
    }
    if (!$first) {
      $this->printEndContainer();
    }
  }

  function printStartContainer(): void {
  }

  function printItem($item): void {
  }

  function printEndContainer(): void {
  }
}