<?php
declare(strict_types=1);

namespace AuxCF\Entities;

use AuxCF\Entities\Field\GroupFieldEntity;
use AuxCF\Entities\Field\RepeaterFieldEntity;
use AuxCF\Entities\Traits\FieldsCollectionTraits;

/**
 * The FieldsSetEntity class represents a collection of FieldEntity objects
 * associated with a parent field group, repeater, or group field entity. It provides
 * methods for managing and manipulating the fields within this collection.
 *
 * @since 1.0.0
 */
class FieldsSetEntity
{
  use FieldsCollectionTraits;

  public const BEFORE_FIELD = 'before';
  public const AFTER_FIELD = 'after';
  public const TOP = 'top';
  public const BOTTOM = 'bottom';

  /**
   * @var FieldEntity[]
   */
  protected $fields = [];
  protected FieldGroupEntity|RepeaterFieldEntity|GroupFieldEntity $parent;

  public function __construct(FieldGroupEntity|RepeaterFieldEntity|GroupFieldEntity $parent) {
    $this->parent = $parent;
  }

  /**
   * Get the parent group or collection
   * @since 1.0.0
   */
  public function getParent(): FieldGroupEntity|RepeaterFieldEntity|GroupFieldEntity|null
  {
    return $this->parent;
  }

  /**
   * Helper function to add a field to a group or collection relative to another
   * field, identified by field key
   *
   * @since 1.0.0
   */
  protected function addFieldRelativeTo(
    string|FieldEntity $field,
    string $addWhere = self::AFTER_FIELD,
    ?string $relativeToField = null
  ): bool
  {
    $fieldKeys = array_keys($this->fields);
    $insertIndex = array_search($relativeToField, $fieldKeys);

    if (false === $insertIndex) {
      return false;
    }

    if (self::AFTER_FIELD === $addWhere) {
      $insertIndex++;
    }

    $fieldsBefore = array_slice($this->fields, 0, $insertIndex);
    $fieldsAfter = array_slice($this->fields, $insertIndex);

    $this->fields = array_merge($fieldsBefore, [
      $field->getEffectiveName() => $field
    ], $fieldsAfter);

    return true;
  }

  /**
   * Get the root field group
   * @since 1.0.0
   */
  public function getRoot(): FieldGroupEntity|RepeaterFieldEntity|GroupFieldEntity|null
  {
    $node = $this;
    $parent = null;

    while (method_exists($node, 'getParent') && $parent = $node->getParent()) {
      $node = $parent;
    }

    return $parent;
  }

  /**
   * Get the wildcard of the root field group, if applicable
   * @since 1.0.0
   */
  public function getRootWildcard(): ?string
  {
    $root = $this->getRoot();

    if (
      $root &&
      method_exists($root, 'getFieldsWildcard') &&
      $fieldsWildcard = $root->getFieldsWildcard()
    ) {
      return $fieldsWildcard;
    }

    return null;
  }

  /**
   * Add a field to the group or collection
   * @since 1.0.0
   */
  public function addField(
    string|FieldEntity $field,
    string $addWhere = self::BOTTOM,
    ?string $relativeToField = null
  ): FieldEntity|null
  {
    if (is_string($field)) {
      $field = new FieldEntity($this->parent);
    }

    if ($fieldsWildcard = $this->getRootWildcard()) {
      if ($overrideName = sprintf($field::$namePattern, $fieldsWildcard)) {
        $field->setOverrideName($overrideName);
      }
    }

    $name = $field->getEffectiveName();

    $fieldAdded = false;

    /**
     * Attempt to add a field relative to another field in the set if the
     * addWhere value is relative to another field
     */
    if (in_array($addWhere, [self::AFTER_FIELD, self::BEFORE_FIELD])) {
      $fieldAdded = $this->addFieldRelativeTo($field, $addWhere, $relativeToField);
    }

    /**
     * If the field was not added relative to another field, add it to the set
     */
    if (!$fieldAdded) {
      if ('top' === $addWhere) {
        $this->fields = array_replace_recursive([$name => $field], $this->fields);
      } else {
        $this->fields[$name] = $field;
      }
    }

    return $this->fields[$name];
  }

  /**
   * Remove a field from the set by field name
   * @since 1.0.0
   * @see \AuxCF\Entities\FieldEntity::$name
   */
  public function removeField(string $fieldName): static
  {
    if (isset($this->fields[$fieldName])) {
      unset($this->fields[$fieldName]);
    }

    return $this;
  }

  /**
   * Get all of the fields in the set
   * @return \AuxCF\Entities\FieldEntity[]
   * @since 1.0.0
   */
  public function getFields(): array
  {
    return $this->fields;
  }

  /**
   * Get a field from the set by name
   * @since 1.0.0
   * @see \AuxCF\Entities\FieldEntity::$name
   */
  public function getFieldByName(string $name): ?FieldEntity
  {
    return @$this->fields[$name];
  }

  /**
   * Get a field from the set by nickname
   * @since 1.0.0
   * @see \AuxCF\Entities\FieldEntity::$nickname
   */
  public function getFieldByNickname(string $name): ?FieldEntity
  {
    foreach ($this->fields as $field) {
      if ($name === $field::$nickname) {
        return $field;
      }
    }

    return null;
  }

  /**
   * Get the ACF array representation of the fields in the set
   * @since 1.0.0
   */
  public function getSetArray(): array
  {
    $array = [];

    foreach ($this->fields as $field) {
      $fieldArray = $field->getArray();

      if ($this->parent instanceof RepeaterFieldEntity) {
        $fieldArray['parent_repeater'] = $this->parent->getKey();
      }

      $array[] = $fieldArray;
    }

    return $array;
  }
}
