<?php

namespace Drupal\features_ui\Controller;

use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Drupal\features\FeaturesManagerInterface;
use Drupal\features\FeaturesAssignerInterface;

/**
 * Returns ajax responses for the Features UI.
 */
class FeaturesUIController implements ContainerInjectionInterface {

  /**
   * The features manager.
   *
   * @var \Drupal\features\FeaturesManagerInterface
   */
  protected $featuresManager;

  /**
   * The package assigner.
   *
   * @var array
   */
  protected $assigner;

  /**
   * Constructs a new FeaturesUIController object.
   *
   * @param \Drupal\features\FeaturesManagerInterface $features_manager
   *   The features manager.
   * @param \Drupal\features\FeaturesAssignerInterface $assigner
   *   The feature assigner.
   */
  public function __construct(FeaturesManagerInterface $features_manager, FeaturesAssignerInterface $assigner) {
    $this->featuresManager = $features_manager;
    $this->assigner = $assigner;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('features.manager'),
      $container->get('features_assigner')
    );
  }

  /**
   * Returns a list of auto-detected config items for a feature.
   *
   * @param string $name
   *   Short machine name of feature to process.
   *
   * @return array
   *   List of auto-detected config items, keyed by type and short name.
   */
  public function detect($name) {
    $detected = [];
    $this->assigner->assignConfigPackages();
    $config_collection = $this->featuresManager->getConfigCollection();

    $items = $_POST['items'];
    if (!empty($items)) {
      $excluded = (!empty($_POST['excluded'])) ? $_POST['excluded'] : [];
      $selected = [];
      foreach ($items as $key) {
        preg_match('/^([^\[]+)(\[.+\])?\[(.+)\]\[(.+)\]$/', $key, $matches);
        if (!empty($matches[1]) && !empty($matches[4])) {
          $component = $matches[1];
          $item = $this->domDecode($matches[4]);
          if (!isset($excluded[$component][$item])) {
            $selected[] = $this->featuresManager->getFullName($component, $item);
          }
        }
      }
      $detected = !empty($selected) ? $this->getConfigDependents($selected, $name) : [];
      $detected = array_merge($detected, $selected);
    }

    $result = [];
    foreach ($detected as $config_name) {
      $item = $config_collection[$config_name];
      $result[$item->getType()][$item->getShortName()] = $item->getName();
    }
    return new JsonResponse($result);
  }

  /**
   * Returns the configuration dependent on given items.
   *
   * @param array $item_names
   *   An array of item names.
   * @param string $package_name
   *   Short machine name of feature to process.
   *
   * @return array
   *   An array of config items.
   */
  protected function getConfigDependents(array $item_names, $package_name) {
    $result = [];
    $config_collection = $this->featuresManager->getConfigCollection();
    $packages = $this->featuresManager->getPackages();
    $settings = $this->featuresManager->getSettings();
    $allow_conflicts = $settings->get('conflicts');

    if (empty($item_names)) {
      $item_names = array_keys($config_collection);
    }

    // Add any existing auto-detected items already in the package config.
    $this->package = $packages[$package_name];
    $package_config = isset($this->package) ? $this->package->getConfig() : [];
    $package_config = !empty($package_config) ? array_unique(array_merge($package_config, $item_names)) : $item_names;
    foreach ($package_config as $config_name) {
      if (!$config_collection[$config_name]->getPackageExcluded()) {
        $result[] = $config_name;
      }
    }

    // Now add dependents of the items selected.
    foreach ($item_names as $item_name) {
      if ($config_collection[$item_name]->getPackage()) {
        foreach ($config_collection[$item_name]->getDependents() as $dependent_item_name) {
          if (isset($config_collection[$dependent_item_name])) {
            $allow = TRUE;
            if (!$allow_conflicts && $config_collection[$dependent_item_name]->getPackage()) {
              if ($packages[$config_collection[$dependent_item_name]->getPackage()]) {
                $allow = ($packages[$config_collection[$dependent_item_name]->getPackage()]->getStatus() == FeaturesManagerInterface::STATUS_NO_EXPORT)
                  || ($config_collection[$item_name]->getPackage() == $config_collection[$dependent_item_name]->getPackage());
              }
            }
            if ($allow) {
              $result[] = $dependent_item_name;
            }
          }
        }
      }
    }
    return $result;
  }

  /**
   * Encodes a given key.
   *
   * @param string $key
   *   The key to encode.
   *
   * @return string
   *   The encoded key.
   */
  protected function domEncode($key) {
    $replacements = $this->domEncodeMap();
    return strtr($key, $replacements);
  }

  /**
   * Decodes a given key.
   *
   * @param string $key
   *   The key to decode.
   *
   * @return string
   *   The decoded key.
   */
  protected function domDecode($key) {
    $replacements = array_flip($this->domEncodeMap());
    return strtr($key, $replacements);
  }

  /**
   * Returns encoding map for decode and encode options.
   *
   * @return array
   *   An encoding map.
   */
  protected function domEncodeMap() {
    return [
      ':' => '__' . ord(':') . '__',
      '/' => '__' . ord('/') . '__',
      ',' => '__' . ord(',') . '__',
      '.' => '__' . ord('.') . '__',
      '<' => '__' . ord('<') . '__',
      '>' => '__' . ord('>') . '__',
      '%' => '__' . ord('%') . '__',
      ')' => '__' . ord(')') . '__',
      '(' => '__' . ord('(') . '__',
    ];
  }

}
