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

use nur\A;
use nur\b\params\Tparametrable;
use nur\b\values\Breaker;
use nur\base;
use nur\func;
use nur\v\v;

class ControlSelect extends ControlVisual {
  use Tparametrable;

  const PARAMETRABLE_PARAMS_SCHEMA = [
    "label" => ["?content", null, "libellé du champ"],
    "items" => ["?iterable", null, "liste d'éléments à afficher"],
    "items_func" => ["?callable", null, "fonction fournissant une liste d'éléments à afficher"],
    "item_value_key" => ["?key", null, "clé de la valeur d'un élément"],
    "item_value_pkey" => ["?key", null, "chemin de clé de la valeur d'un élément"],
    "item_value_func" => ["?callable", null, "fonction fournissant la valeur d'un élément"],
    "item_text_key" => ["?key", null, "clé du libellé d'un élément"],
    "item_text_pkey" => ["?key", null, "chemin de clé du libellé d'un élément"],
    "item_text_func" => ["?callable", null, "fonction fournissant le libellé d'un élément"],
    "no_item_value" => ["?string", null, "valeur si aucun élément n'est sélectionné"],
    "no_item_text" => ["?content", null, "libellé si aucun élément n'est sélectionné"],
    "group_key" => ["?key", null, "clé d'une valeur sur laquelle grouper les éléments"],
    "group_pkey" => ["?key", null, "chemin de clé d'une valeur sur laquelle grouper les éléments"],
    "group_func" => ["?callable", null, "fonction fournissant une valeur sur laquelle grouper les éléments"],
    "required" => ["?bool", null, "ce champ est-il requis?"],
    "show_required" => ["?bool", null, "faut-il afficher la marque de champ requis?"],
    "option_prefix" => ["?content", "\n", "contenu à afficher avant chaque option"],
    "option_suffix" => ["?content", null, "contenu à afficher après chaque option"],
  ];

  function __construct(Form $form, ?array $params=null) {
    parent::__construct($form, $params);
    base::update_n($this->ppShowRequired, $this->ppRequired);
  }

  /** @var array|string */
  protected $ppLabel;

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

  /** @var ?callable */
  protected $ppItemsFunc;

  /** @var string|int|null */
  protected $ppItemValueKey;

  /** @var string|int|null */
  protected $ppItemValuePkey;

  /** @var ?callable */
  protected $ppItemValueFunc;

  /** @var string|int|null */
  protected $ppItemTextKey;

  /** @var string|int|null */
  protected $ppItemTextPkey;

  /** @var ?callable */
  protected $ppItemTextFunc;

  /** @var ?string */
  protected $ppNoItemValue;

  /** @var array|string */
  protected $ppNoItemText;

  /** @var string|int|null */
  protected $ppGroupKey;

  /** @var string|int|null */
  protected $ppGroupPkey;

  /** @var ?callable */
  protected $ppGroupFunc;

  /** @var ?bool */
  protected $ppRequired;

  /** @var ?bool */
  protected $ppShowRequired;

  /** @var array|string */
  protected $ppOptionPrefix;

  /** @var array|string */
  protected $ppOptionSuffix;

  protected function vof(
    $value, ?callable $func,
    $item=null, $itemKey=null, $itemPkey = null, ?int $itemIndex=null,
    ...$funcArgs) {
    if ($value !== null) return $value;
    if ($func !== null) {
      func::ensure_func($func, $this, $funcArgs);
      $value = func::call($func, ...$funcArgs);
      if ($value !== null) return $value;
    }
    if (A::is_array($item)) {
      $item = A::with($item);
      if ($itemKey !== null) {
        $value = A::get($item, $itemKey);
        if ($value !== null) return $value;
      }
      if ($itemPkey !== null) {
        $value = A::pget($item, $itemPkey);
        if ($value !== null) return $value;
      }
      if ($itemIndex !== null) {
        $itemKeys = array_keys($item);
        return $item[$itemKeys[$itemIndex]] ?? null;
      }
      return "(unknown)";
    }
    return $item;
  }

  function getControl(): array {
    $name = $this->ppName;
    $value = $this->ppValue;
    $items = $this->vof($this->ppItems, $this->ppItemsFunc);
    $noItemValue = $this->ppNoItemValue;
    $noItemText = $this->ppNoItemText;

    $options = [];
    if ($noItemValue !== null) {
      $options[] = [
        "value" => $noItemValue,
        "text" => $noItemText,
        "selected" => $value == $noItemValue,
        "group" => false,
      ];
    }
    $haveGroups = $this->ppGroupKey !== null || $this->ppGroupFunc !== null;
    foreach ($items as $key => $item) {
      $itemValue = $this->vof(null, $this->ppItemValueFunc, $item, $this->ppItemValueKey, $this->ppItemValuePkey, 0, $item, $key);
      $itemText = $this->vof(null, $this->ppItemTextFunc, $item, $this->ppItemTextKey, $this->ppItemTextPkey, 1, $item, $key);
      $itemGroup = null;
      if ($haveGroups) {
        $itemGroup = $this->vof(null, $this->ppGroupFunc, $item, $this->ppGroupKey, $this->ppGroupPkey, null, $item, $key);
      }
      if (!$itemText) $itemText = $itemValue;
      $options[] = array(
        "value" => $itemValue,
        "text" => $itemText,
        "selected" => $value == $itemValue,
        "group" => $itemGroup,
      );
    }

    $control = [];
    $control[] = v::start("select", [
      "id" => $this->ppId,
      "name" => $name,
      "value" => $value,
      "readonly" => $this->ppReadonly? "readonly": false,
      "disabled" => $this->ppDisabled? "disabled": false,
      "required" => $this->ppRequired? "required": false,
      "class" => ["form-control", $this->ppClass],
      "style" => $this->ppStyle,
      $this->ppAttrs,
    ]);
    $breaker = new Breaker();
    $firstGroup = true;
    foreach ($options as $option) {
      $group = $option["group"];
      if ($haveGroups && $group !== false && $breaker->shouldBreakOn($group)) {
        if ($firstGroup) $firstGroup = false;
        else $control[] = v::end("optgroup");
        $control[] = v::start("optgroup", ["label" => $group]);
      }
      $control[] = $this->ppOptionPrefix;
      $control[] = v::tag("option", [
        "value" => $option["value"],
        "selected" => $option["selected"]? "selected": false,
        q($option["text"]),
      ]);
      $control[] = $this->ppOptionSuffix;
    }
    if (!$firstGroup) {
      $control[] = v::end("optgroup");
    }
    $control[] = v::end("select");
    if ($this->ppNaked) return $control;

    $label = $this->ppLabel;
    if ($label && $this->ppRequired && $this->ppShowRequired) {
      $label = [q($label), " <sup class=\"required\" title=\"required\">(*)</sup>"];
    } else {
      $label = q($label);
    }

    if ($name !== null) {
      [$fgClass, $feedback] = $this->getStdFeedback($name, $value);
    } else {
      $fgClass = $feedback = null;
    }
    return $this->getFglcLayout($label, true, $control, $fgClass, $feedback);
  }
}
