<?php

namespace Drupal\rufi_versions;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Template\Attribute;
use Drupal\node\NodeInterface;

/**
 * Service to build renderable tree of nested section terms with rule nodes.
 */
class VersionsViewBuilderService {

  /**
   * Drupal\Core\Entity\EntityTypeManagerInterface definition.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * Constructs a new VersionsViewBuilderService object.
   */
  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
    $this->entityTypeManager = $entity_type_manager;
  }

  /**
   * Returns a rendered tree of nested section terms with rule nodes.
   *
   * @param \Drupal\node\NodeInterface $version
   *   The requested version of rules tree.
   *
   * @return array
   *   Renderable array of nested section terms with rules.
   */
  public function getRenderedView(NodeInterface $version) {
    $rules = $this->getRenderedRules($version);
    $items = [];
    foreach ($version->get('field_sections') as $item) {
      $value = ['#data' => $item->getValue()];
      $value['#theme'] = 'rufi_versions_tree';
      $value['#title'] = $value['#data']['name'];
      if (isset($rules[$value['#data']['term_id']])) {
        $child_nodes = [
          '#type' => 'container',
          '#attributes' => new Attribute([
            'class' => ['rufi-versions-tree--nodes'],
          ]),
        ];
        $value['#child_nodes'] = array_merge($child_nodes, $rules[$value['#data']['term_id']]);
      }
      $value['#children'] = [
        '#type' => 'container',
        '#attributes' => new Attribute([
          'class' => ['rufi-versions-tree--children'],
        ]),
      ];
      $items[] = $value;
    }
    return $this->buildTree($items);
  }

  /**
   * Tree builder for nested list of sections and child nodes.
   * !RECURSIVE METHODE
   *
   * @param array $elements
   *   Full list of terms.
   * @param int $parentId
   *   Branch id in nested term structure.
   * @param int $level
   *   The depth of nesting.
   *
   * @return array
   *   Renderable tree of terms and included nodes.
   */
  protected function buildTree(array &$elements, $parentId = 0, $level = 2) {
    $branch = [];

    foreach ($elements as &$element) {
      if ($element['#data']['term_parent_id'] == $parentId) {
        $children = $this->buildTree($elements, $element['#data']['term_id'], $level + 1);
        if ($children) {
          if (isset($element['#children'])) {
            $element['#children'] = array_merge($element['#children'], $children);
          }
          else {
            $element['#data']['children'] = $children;
          }
        }
        else {
          if (isset($element['#children'])) {
            unset($element['#children']);
          }
        }

        // Do not add sections with no content.
        if (isset($element['#children']) || isset($element['#child_nodes'])) {
          $element['#level'] = $level;
          $branch[$element['#data']['term_id']] = $element;
        }
        unset($element);
      }
    }
    return $branch;
  }

  /**
   * Get referred rule entities from field_rule.
   *
   * @param \Drupal\node\NodeInterface $version
   *   The version entity.
   *
   * @return array|\Drupal\node\NodeInterface[]
   *   Rules referred by version entity.
   */
  protected function getRules(NodeInterface $version): array
  {
    $rules = [];
    if ($version->hasField('field_rules')) {
      foreach ($version->get('field_rules')->referencedEntities() as $rule) {
        /** @var \Drupal\node\NodeInterface $rule */
        if ($rule->hasField('field_sector')) {
          $target_id = $rule->get('field_sector')->getString();
          $rules[$target_id][] = $rule;
        }
      };
    }
    // Sort by ordinal number.
    foreach ($rules as &$term_rules) {
      uasort($term_rules, self::sortRules(...));
    }
    return $rules;
  }

  protected static function sortRules($a, $b): int
  {
    if ($a instanceof NodeInterface && $b instanceof NodeInterface) {
      if ($a->hasField('field_ordinal_number') && $b->hasField('field_ordinal_number')) {
        $a_val = (int) $a->get('field_ordinal_number')->getString();
        $b_val = (int) $b->get('field_ordinal_number')->getString();
        if ($a_val == $b_val) {
          return 0;
        }
        return ($a_val < $b_val) ? -1 : 1;
      }
    }
    return 0;
  }

  /**
   * Get rendered rules from version entity.
   *
   * @param \Drupal\node\NodeInterface $version
   *   The version node entity.
   * @param string $view_mode
   *   The required view_mode.
   *
   * @return array
   *   Returns a list of rules.
   */
  protected function getRenderedRules(NodeInterface $version, string $view_mode = 'result'): array
  {
    $rendered_rules = [];
    $view_builder = $this->entityTypeManager->getViewBuilder('node');
    foreach ($this->getRules($version) as $key => $val) {
      foreach ($val as $rule) {
        $rendered_rules[$key][] = $view_builder->view($rule, $view_mode);
      }
    }
    return $rendered_rules;
  }

}
