<?php
namespace nur\config;

use nur\A;
use nur\b\ValueException;
use nur\str;

/**
 * Class Ref: une référence à une clé de configuration
 *
 * La référence peut être relative, e.g '..x.y' qui correspond à 'a.b.x.y' si
 * la référence est située au chemin de clé 'a.b.w.t' (le nombre de '.' au
 * début indique le nombre d'éléments de chemin à enlever)
 */
class Ref {
  /** @var string */
  public $pkey;
  /** @var array */
  public $merge;
  /** @var array */
  public $appends;
  /** @var array */
  public $prepends;

  function __construct(string $pkey) {
    $this->pkey = $pkey;
  }

  /** calculer le chemin effectif à partir du chemin relative $pkey */
  static final function abs_pkey(string $rel_pkey, string $base_pkey): string {
    if (substr($rel_pkey, 0, 1) == ".") {
      $base = explode(".", $base_pkey);
      ## c'est un chemin relatif
      $parts = $rel_pkey;
      # monter
      while ($parts != "" && substr($parts, 0, 1) == ".") {
        A::del($base, count($base) - 1);
        $parts = substr($parts, 1);
      }
      # puis descendre
      $parts = explode(".", $parts);
      A::merge($base, $parts);
      $pkey = implode(".", $base);
    } else {
      ## c'est un chemin absolu
      $pkey = $rel_pkey;
    }
    if ($pkey == $base_pkey) {
      throw new ValueException("rel_pkey($rel_pkey) must be different of $base_pkey");
    } elseif (str::starts_with("$base_pkey.", $pkey)) {
      throw new ValueException("rel_pkey($rel_pkey) shall not be a child of $base_pkey");
    } elseif (str::starts_with("$pkey.", $base_pkey)) {
      throw new ValueException("rel_pkey($rel_pkey) shall not be a direct parent of $base_pkey");
    }
    return $pkey;
  }

  /** résoudre cette référence, qui est située au chemin $pkey */
  function resolve(IConfigManager $cm, string $base_pkey, string $profile) {
    $abs_pkey = $this->abs_pkey($this->pkey, $base_pkey);
    $value = $cm->getValue($abs_pkey, null, $profile);
    if ($this->merge !== null) A::merge($value, $this->merge);
    if ($this->appends !== null) A::merge($value, $this->appends);
    if ($this->prepends !== null) {
      $prepends = $this->prepends;
      A::merge($prepends, $value);
      $value = $prepends;
    }
    return $value;
  }
}