<?php
namespace nur\tools\compctl;

use nur\A;
use nur\b\ValueException;
use nur\data\types\md_utils;
use nur\data\types\Metadata;
use nur\path;
use nur\str;
use nur\yaml;

class ConfigFile {
  # préfixes de projets dont on tient compte
  const PREFIXES = ["ur/", "nur/", "lib/", "nulib/"];

  const DEFAULT_DEP = "dev-master";
  const DEFAULT_DEPS = [
    "wip" => "dev-wip",
    "develop" => "dev-develop",
  ];
  const DEFAULT_ALLOW_LINK = false;
  const DEFAULT_ALLOW_LINKS = [
    "wip" => true,
    "develop" => true,
  ];
  const DEFAULT_CONFIG = [
    # cette définition tient compte des valeurs DEFAULT_* ci-dessus
    "defaults" => [
      "wip" => "dev-wip",
      "develop" => "dev-develop",
      "master" => [
        "version" => "dev-master",
        "allow_link" => false,
      ],
    ],
  ];
  const PROJNAMES = [
    # noms de projets particuliers
    "nulib/php" => "nulib",
  ];

  const DEP_SCHEMA = [
    "libname" => ["string", null, "nom de la librairie"],
    "version" => ["?string", null, "version de la librairie"],
    "projname" => ["?string", null, "nom du répertoire du projet sur le système de fichier"],
    "type" => ["?string", null, "type de dépendance"],
    "url" => ["?string", null, "url de la dépendance"],
    "allow_link" => ["?bool", null, "peut-on lier vers le projet sur disque?"],
    "link" => ["?bool", null, "faut-il ajouter l'url dans [repositories]"],
    "branch" => ["?string", null, "branche de la dépendance dans le projet"],
  ];

  function __construct(string $configFile, ?string $projdir=null, bool $ensureExists=true) {
    if ($projdir === null) $projdir = dirname($configFile);
    if ($ensureExists && !file_exists($configFile)) {
      $message = path::ppath($projdir).": aucune configuration n'a été trouvée";
      throw new ValueException($message);
    }
    $this->configFile = $configFile;
    $this->projdir = $projdir;
  }

  /** @var string */
  protected $configFile;

  /** @var string */
  protected $projdir;

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

  protected function load(): array {
    if ($this->data === null) {
      $this->data = yaml::load($this->configFile);
    }
    return $this->data;
  }

  function getv(string $pkey="", $default=null) {
    $this->load();
    return A::pget($this->data, $pkey, $default);
  }

  function geta(string $pkey="", array $default=[]): array {
    return A::with($this->getv($pkey, $default));
  }

  function getBranchDeps(): array {
    return $this->geta("defaults");
  }

  function getRequires(): array {
    return $this->geta("require");
  }

  function getBranches(): array {
    return $this->geta("branch");
  }

  private static $dep_md;
  private static function dep_md(): Metadata {
    return md_utils::ensure_md(self::$dep_md, self::DEP_SCHEMA);
  }

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

  function getDeps(string $branch): array {
    $dun = A::get($this->duns, $branch);
    if ($dun !== null) return $dun;

    $branches = $this->getBranches();
    $source = $branch;
    $valid = array_key_exists($source, $branches);
    if (!$valid && ($pos = strpos($branch, "/")) !== false) {
      # pas de définition pour cette branche, essayer avec la branche de base
      # si la branche est de la forme base/xxx
      $source = substr($branch, 0, $pos);
      $valid = array_key_exists($source, $branches);
    }
    if (!$valid && array_key_exists("master", $branches)) {
      # la branche master est utilisée par défaut si elle existe
      $source = "master";
      $valid = true;
    }
    if (!$valid) {
      $dun = [null, null, null];

    } else {
      $projdir = $this->projdir;

      $branchDeps = $this->getBranchDeps() + self::DEFAULT_DEPS;
      $defaultDep = null;
      foreach ($branchDeps as $dbranch => $ddep) {
        if ($branch == $dbranch) {
          $defaultDep = $ddep;
          break;
        }
      }
      if ($defaultDep === null) {
        foreach ($branchDeps as $dbranch => $ddep) {
          if (str::starts_with("$dbranch/", $branch)) {
            $defaultDep = $ddep;
            break;
          }
        }
      }
      if ($defaultDep === null) $defaultDep = self::DEFAULT_DEP;
      self::dep_md()->ensureSchema($defaultDep, "");

      $defaultAllowLink = null;
      foreach (self::DEFAULT_ALLOW_LINKS as $albranch => $alvalue) {
        if ($branch == $albranch || str::starts_with("$albranch/", $branch)) {
          $defaultAllowLink = $alvalue;
          break;
        }
      }
      if ($defaultAllowLink === null) $defaultAllowLink = self::DEFAULT_ALLOW_LINK;

      # tout d'abord, lister les dépendances
      $bdeps = [];
      foreach ($this->getRequires() as $libname => $dep) {
        if (strpos($libname, "/") === false) continue;
        if ($dep === false) continue;
        if ($dep !== null) {
          self::dep_md()->ensureSchema($dep, $libname);
        }
        $bdeps[$libname] = $dep;
      }
      foreach ($this->geta("branch.$source") as $libname => $dep) {
        if ($dep === false) {
          # une définition par défaut peut être supprimée avec la valeur false
          A::del($bdeps, $libname);
          continue;
        }
        if ($dep !== null) {
          self::dep_md()->ensureSchema($dep, $libname);
          if (!array_key_exists($libname, $bdeps)) $bdeps[$libname] = $dep;
          elseif ($bdeps[$libname] === null) $bdeps[$libname] = $dep;
          else A::merge_nn($bdeps[$libname], $dep);
        }
      }

      $deps = [];
      $isUrls = [];
      $notFoundUrls = [];
      foreach ($bdeps as $libname => $dep) {
        if ($dep === null) {
          $dep = $defaultDep;
          $dep["libname"] = $libname;
        }
        self::dep_md()->ensureSchema($dep, $libname);
        $version = $dep["version"];
        if ($version === null) $version = $defaultDep["version"];
        $projname = $dep["projname"];
        if ($projname === null) $projname = A::get(self::PROJNAMES, $libname);
        if ($projname === null) {
          $projname = str_replace("/", "-", $libname);
        }
        $type = $dep["type"];
        if ($type === null) $type = "path";
        $url = $dep["url"];
        if ($url === null) $url = "../$projname";
        $allowLink = $dep["allow_link"];
        if ($allowLink === null) $allowLink = $defaultDep["allow_link"];
        if ($allowLink === null) $allowLink = $defaultAllowLink;
        $link = $dep["link"];
        if ($link === null) {
          $isDev = str::starts_with("dev-", $version)
            || str::ends_with("-dev", $version);
          $link = $allowLink && $isDev;
        }
        if ($link && (
            $type !== "path" ||
            !path::is_dir("$projdir/$url", true)
          )) {
          $notFoundUrls[] = $url;
          $link = false;
        }
        $vbranch = $version;
        if (!str::del_prefix($vbranch, "dev-")
          && !str::del_suffix($vbranch, "-dev")) {
          $vbranch = null;
        }
        A::merge($dep, [
          "projname" => $projname,
          "type" => $type,
          "url" => $url,
          "allow_link" => $allowLink,
          "link" => $link,
          "branch" => $vbranch,
        ]);
        $deps[$libname] = $dep;
        $isUrls[$url] = true;
      }
      $dun = [$deps, $isUrls, $notFoundUrls];
    }
    return $this->duns[$branch] = $dun;
  }
}
