<?php

namespace Drupal\pagedesigner\Plugin;

use Drupal\pagedesigner\ElementViewBuilder;
use Drupal\pagedesigner\Entity\Element;
use Drupal\pagedesigner\Service\Renderer;

/**
 * Povides the compound handler for structural pagedesigner elements.
 *
 * The structural handler provides methods for handling pagedesigner elements
 * like rows and components.
 * It is to be extended and the inherited class
 * to be annotated for the correct type of pagedesiger element.
 */
class CompoundHandlerBase extends HandlerPluginBase {

  /**
   * {@inheritdoc}
   */
  public function view(Element $entity, string $view_mode, array &$build = []) {
    $this->addStyles($entity, $view_mode == ElementViewBuilder::RENDER_MODE_PUBLIC);
  }

  /**
   * {@inheritdoc}
   */
  public function build(Element $entity, string $view_mode, array &$build = []) {
    $pattern = $entity->field_pattern->value;
    $build = [
      '#pagedesigner_element' => $entity,
      '#type' => 'pattern',
      '#id' => $pattern,
      '#fields' => [],
      '#view_mode' => $view_mode,
      '#post_render' => [
        [$this->viewBuilder, 'postRender'],
      ],
    ] + $build;
    unset($build['#pre_render']);
  }

  /**
   * Create a sub build for a child entity and add cache info to build.
   *
   * @param \Drupal\pagedesigner\Entity\Element $child
   *   The child entity.
   * @param string $view_mode
   *   The view mode.
   * @param array $build
   *   The build array.
   *
   * @return array
   *   The sub build.
   */
  protected function subBuild(Element $child, string $view_mode, array &$build) {
    $sub_build = $this->elementHandler->build($child, $view_mode);
    $this->renderer->addCacheableDependency($build, $child);
    $this->viewBuilder->adjustCacheTags($build, $view_mode);
    return $sub_build;
  }

  /**
   * Add the styles of the given entity to the renderer.
   *
   * @param \Drupal\pagedesigner\Entity\Element $entity
   *   The entity to add the styles of.
   * @param bool $public
   *   True, if rendering is for anonymous user, false otherwise.
   */
  protected function addStyles(Element $entity, $public = TRUE) {
    $sizes = ['large' => FALSE, 'medium' => FALSE, 'small' => FALSE];
    $styles = $entity->field_styles;
    if (empty($styles)) {
      return;
    }
    foreach ($styles as $item) {
      $style = $item->entity;
      if ($public && $style != NULL) {
        $style = $style->loadNewestPublished();
      }
      if (!empty($style)) {
        unset($sizes[$style->name->value]);
        Renderer::addStyle(
          $style->name->value,
          $style->field_css->value,
          $entity->id()
        );
      }
    }
    foreach ($sizes as $size => $used) {
      Renderer::addStyle(
        $size,
        '',
        $entity->id()
      );
    }

  }

  /**
   * {@inheritdoc}
   */
  public function serialize(Element $entity, array &$result = []) {
    $build = [
      'type' => 'structural',
      'id' => $entity->id(),
    ];
    $fields = $this->collectFields($entity, 'serialize');
    foreach ($fields as $placeholder => $value) {
      $fields[$placeholder] = $value;
    }
    $build['fields'] = $fields;
    $build['classes'] = $entity->field_classes->value;
    $styles = $entity->field_styles;
    foreach ($styles as $item) {
      $build['styles'][$item->entity->name->value] = $item->entity->field_css->value;
    }
    $result = $build + $result;
  }

  /**
   * {@inheritdoc}
   */
  public function describe(Element $entity, array &$result = []) {
    parent::describe($entity, $result);
    $result['fields'] = [];

    // Collect field description.
    $patternDefinition = $this->getPatternDefinition($entity->field_pattern->value);
    if ($patternDefinition != NULL) {
      foreach ($entity->children as $item) {
        if ($item->entity != NULL) {
          $fieldData = $this->elementHandler->describe($item->entity);
          $fieldData = $fieldData + $patternDefinition->getField($item->entity->field_placeholder->value)->toArray();
          $result['fields'][] = $fieldData;
        }
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  protected function collectChildren(Element $entity, $view_mode) {
    $children = [];
    $pattern = $entity->field_pattern->value;
    $patternDefinition = $this->getPatternDefinition($pattern);
    if ($patternDefinition != NULL) {
      $numFields = count($patternDefinition->getFields());
      $i = 0;
      foreach ($entity->children as $item) {
        if ($item->entity != NULL && $item->entity->hasField('field_placeholder')) {
          $child = $item->entity;
          if ($i >= $numFields) {
            break;
          }
          if ($view_mode == ElementViewBuilder::RENDER_MODE_PUBLIC) {
            $child = $child->loadNewestPublished();
          }
          if (!empty($child)) {
            $placeholder = $child->field_placeholder->value;
            if ($patternDefinition->hasField($placeholder)) {
              $child->setHandlerType($patternDefinition->getField($placeholder)->getType());
              $children[] = $child;
            }
          }
          $i++;
        }
      }
    }
    return $children;
  }

  /**
   * {@inheritdoc}
   */
  protected function collectFields(Element $entity, $function = 'get') {
    $fields = [];
    $pattern = $entity->field_pattern->value;
    $patternDefinition = $this->getPatternDefinition($pattern);
    if ($patternDefinition != NULL) {
      $numFields = count($patternDefinition->getFields());
      $i = 0;
      foreach ($entity->children as $item) {
        if ($item->entity != NULL && $item->entity->hasField('field_placeholder')) {
          if ($i >= $numFields) {
            break;
          }
          $placeholder = $item->entity->field_placeholder->value;
          if ($patternDefinition->hasField($placeholder)) {
            $item->entity->setHandlerType($patternDefinition->getField($placeholder)->getType());
            $fields[$placeholder] = $this->elementHandler->{$function}($item->entity);
          }
          $i++;
        }
      }
    }
    return $fields;
  }

  /**
   * {@inheritdoc}
   */
  public function getContent(Element $entity, array &$list = [], $published = TRUE) {
    foreach ($entity->children as $item) {
      if ($item->entity != NULL) {
        $child = ($published) ? $item->entity->loadNewestPublished() : $item->entity;
        if ($child != NULL) {
          $this->elementHandler->getContent($child, $list, $published);
        }
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function publish(Element $entity) {
    parent::publish($entity);
    foreach ($entity->field_styles as $style) {
      if (!$style->entity->isPublished()) {
        $style->entity->setPublished(TRUE);
        $style->entity->save();
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function unpublish(Element $entity) {
    parent::unpublish($entity);
    foreach ($entity->field_styles as $style) {
      if ($style->entity->isPublished()) {
        $style->entity->setPublished(FALSE);
        $style->entity->save();
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function generate($definition, array $data, ?Element &$entity = NULL) {
    // Creating entity.
    parent::generate(
      [
        'type' => $definition['type'],
        'name' => $definition['id'],
      ],
      $data,
      $entity
    );
    $entity->field_pattern->value = $definition['id'];
    $entity->parent->target_id = $data['parent'];
    $entity->container->target_id = $data['container'];
    $entity->entity->target_id = $data['entity'];
    $entity->saveEdit();

    // Creating field entities.
    $fields = $definition['patternDefinition']->getFields();
    foreach ($fields as $key => $field) {
      // Create field info array.
      $fieldData = $field->toArray();
      $fieldData['parent'] = $entity->id();
      $fieldData['container'] = $data['container'];
      $fieldData['entity'] = $data['entity'];
      $fieldData['placeholder'] = $key;

      // Create field entity and add as child.
      $fieldElement = NULL;
      $fieldElement = $this->elementHandler->generate(['type' => $field->getType()], $fieldData);
      $fieldElement->saveEdit();
      $entity->children->appendItem($fieldElement);
    }
    $entity->saveEdit();

    // Adding entity to parent cell if provided.
    $cell = $entity->parent->entity;
    if ($cell != NULL) {
      $cell = $cell->getTranslation($entity->langcode->value);
      $cell->children->appendItem($entity);
      $cell->saveEdit();
    }
  }

  /**
   * {@inheritdoc}
   */
  public function copy(Element $entity, ?Element $container = NULL, ?Element &$clone = NULL) {
    parent::copy($entity, $container, $clone);
    $styles = [];
    foreach ($clone->field_styles as $style) {
      $styleCopy = $style->entity->createDuplicate();
      $styleCopy->parent->entity = $clone;
      $styleCopy->container->entity = $container;
      if ($container != NULL) {
        $styleCopy->entity->entity = $container->entity->entity;
      }
      else {
        $styleCopy->entity->entity = NULL;
      }
      $styleCopy->langcode->value = $clone->langcode->value;
      $styleCopy->setPublished(FALSE);
      $styleCopy->save();
      $styles[] = $styleCopy;
    }
    $clone->field_styles->setValue($styles);
    $clone->save();
  }

  /**
   * {@inheritdoc}
   */
  public function patch(Element $entity, array $data) {
    $storedFields = [];
    if (!empty($data['fields'])) {
      // Updating fields.
      $pattern = $entity->field_pattern->value;
      $patternDefinition = $this->getPatternDefinition($pattern);
      if ($patternDefinition != NULL) {
        foreach ($entity->children as $item) {
          $fieldName = $item->entity->field_placeholder->value;
          if ($patternDefinition->hasField($fieldName)) {
            $fieldData = $data['fields'][$fieldName];
            if (!\is_array($fieldData)) {
              $fieldData = [$fieldData];
            }
            $item->entity->setHandlerType($patternDefinition->getField($fieldName)->getType());
            $this->elementHandler->patch($item->entity, $fieldData);
            $storedFields[$fieldName] = 1;
          }
        }

        // Add missing fields.
        $patternFields = $patternDefinition->getFieldsAsOptions();
        $missingFields = array_diff(array_keys($patternFields), array_keys($storedFields));
        foreach ($missingFields as $fieldName) {

          // Get field definition, set parent, entity, container and fieldname.
          $field = $patternDefinition->getField($fieldName);
          $fieldData = $field->toArray();
          $fieldData['parent'] = $entity->id();
          $fieldData['container'] = $entity->container->target_id;
          $fieldData['entity'] = $entity->entity->target_id;
          $fieldData['placeholder'] = $fieldName;

          // Get handler, create field entity and add as child.
          $fieldElement = $this->elementHandler->generate(['type' => $field->getType()], $fieldData);
          if (!empty($data['fields'][$fieldName])) {
            $fieldData = $data['fields'][$fieldName];
            if (!\is_array($fieldData)) {
              $fieldData = [$fieldData];
            }
            $this->elementHandler->patch($fieldElement, $fieldData);
          }
          $fieldElement->saveEdit();
          $entity->children->appendItem($fieldElement);
        }
      }
    }

    $styles = $entity->field_styles;
    $stylingPermission = \Drupal::currentUser()->hasPermission('edit pagedesigner styles');
    if (!empty($data['styles']) && $stylingPermission) {
      $saved = [];

      foreach ($styles as $item) {
        $saved[$item->entity->name->value] = $item->entity;
      }
      foreach ($data['styles'] as $key => $css) {
        $style = NULL;
        if (!empty($saved[$key])) {
          $style = $saved[$key];
        }
        else {
          $style = Element::create(['type' => 'style', 'name' => $key]);
          $entity->field_styles->appendItem($style);
          $style->langcode->value = $entity->langcode->value;
        }
        $style->field_css->value = $css;
        $style->entity->target_id = $entity->entity->target_id;
        $style->container->target_id = $entity->container->target_id;
        $style->parent->target_id = $entity->id();
        $style->saveEdit();
      }
    }
    if (!empty($data['classes'])) {
      $entity->field_classes->value = implode(' ', $data['classes']);
    }
    $entity->saveEdit();
  }

  /**
   * {@inheritdoc}
   */
  public function restore(Element $entity) {
    parent::restore($entity);
    foreach ($entity->field_styles as $item) {
      if (!empty($item->entity)) {
        $item->entity->deleted->value = FALSE;
        $item->entity->save();
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function delete(Element $entity, bool $remove = FALSE) {
    parent::delete($entity, $remove);
    foreach ($entity->field_styles as $item) {
      if (!empty($item->entity)) {
        if ($remove) {
          $item->entity->delete();
        }
        else {
          $item->entity->deleted->value = TRUE;
          $item->entity->status->value = FALSE;
          $item->entity->saveEdit();
        }
      }
    }
  }

}
