<?php
declare(strict_types=1);

namespace AuxCF\Entities;

use AuxCF\Entities\Field\Enum\FieldTypeEnum;
use AuxCF\Entities\Field\GroupFieldEntity;
use AuxCF\Entities\Field\RepeaterFieldEntity;
use AuxCF\Entities\Field\Traits\FieldArrayTraits;
use AuxCF\Entities\Field\Traits\FieldConditionalLogicTraits;
use AuxCF\Entities\Settings\Enum\IntSwitchEnum;
use AuxCF\Entities\Settings\Traits\EnumPropertyTraits;
use AuxCF\Entities\Traits\FieldsCollectionTraits;

/**
 * An ACF field entity
 *
 * All fields of any type are based off of and extend this class.
 *
 * @since 1.0.0
 */
class FieldEntity
{
  use FieldArrayTraits, FieldsCollectionTraits, FieldConditionalLogicTraits, EnumPropertyTraits;

  /**
   * @var string Name of the field, used to save and load data. Single word, no spaces. Underscores and dashes allowed
   */
  public static string $name = '';
  /**
   * @var string For reusable fields with names generated dynamically
   * @see FieldGroupEntity::$fieldsWildcard
   */
  public static string $namePattern = '';
  /**
   * @var string More concise name of the field for use in templates and blocks
   */
  public static string $nickname = '';
  /**
   * @var array List of names this field used to have, from most recent to oldest
   */
  public static array $replacesFields = [];
  /**
   * @var string[] List of methods to call on the field entity to get the field array
   * @see FieldEntity::getArray()
   */
  protected static array $arrayPropertiesMethods = [];

  protected FieldGroupEntity|RepeaterFieldEntity|GroupFieldEntity|null $parent = null;

  /**
   * @var string The key is automatically set from the field name to match ACF specifications
   */
  protected string $key = '';
  /**
   * @var string Override the field name, for use with reusable fields with name patterns or in extraordinary cases
   */
  protected string $overrideName = '';
  /**
   * @var string The label of the field, used in the admin interface
   */
  protected string $label = '';
  /**
   * @var FieldTypeEnum ACF field type
   */
  protected FieldTypeEnum $type = FieldTypeEnum::DEFAULT;
  /**
   * @var string Instructions for the field, used in the admin interface
   */
  protected string $instructions = '';
  /**
   * @var IntSwitchEnum Whether the field is required or not
   */
  protected IntSwitchEnum $required = IntSwitchEnum::Off;

  /**
   * @var array An array of attributes for the field element
   */
  protected array $wrapper = [
    'width' => '',
    'class' => '',
    'id' => '',
  ];
  /**
   * @var mixed Default value for the field if null
   */
  protected mixed $defaultValue = '';

  /**
   * @var array Validation rules for the field
   * Format: ['required' => true, 'regex' => '/^GTM-[A-Z0-9]{1,10}$/', 'custom_messages' => [...]]
   */
  protected array $validation = [];

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

    if (static::$name) {
      $this->setKeyFromName();
    }

    $this->initConditionalLogic();
    $this->init();
  }

  /**
   * Default initialization method for the field entity. Inheritors CAN provide special initialization
   * logic in this method.
   *
   * @since 1.0.0
   */
  protected function init(): void {}

  public function getParent(): FieldGroupEntity|RepeaterFieldEntity|GroupFieldEntity|null
  {
    return $this->parent;
  }

  /**
   * Get the root field group of the field
   * @since 1.0.0
   */
  public function getRoot(): FieldGroupEntity|RepeaterFieldEntity|GroupFieldEntity|null
  {
    if (
      $this->parent &&
      method_exists($this->parent, 'getRoot') &&
      $root = $this->parent->getRoot()
    ) {
      return $root;
    }

    return $this->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;
  }

  /**
   * Set the field name, overriding the static name value
   *
   * When this method is used to set a field name, the key is automatically regenerated from
   * the new override name.
   *
   * @since 1.0.0
   */
  public function setOverrideName(string $name): static
  {
    $this->overrideName = $name;
    $this->setKeyFromName();

    return $this;
  }

  public function getOverrideName(): string
  {
    return $this->overrideName;
  }

  /**
   * Get the final effective name of the field, either the override name or the
   * static name value if no override name is set.
   *
   * @since 1.0.0
   */
  public function getEffectiveName(): string
  {
    return $this->overrideName ? $this->overrideName : static::$name;
  }

  /**
   * Set the key for the field to ACF specifications from the effective name
   *
   * @since 1.0.0
   */
  protected function setKeyFromName(): void
  {
    $name = $this->getEffectiveName();
    $this->key = "field_{$name}";
  }

  /**
   * @since 1.0.0
   */
  public function getKey(): string
  {
    return $this->key;
  }

  /**
   * @since 1.0.0
   */
  public function getLabel(): string
  {
    return $this->label;
  }

  /**
   * Set the label for the field in the admin interface
   * @since 1.0.0
   */
  public function setLabel(string $label): self
  {
    $this->label = $label;
    return $this;
  }

  /**
   * @since 1.0.0
   */
  public function getType(): FieldTypeEnum
  {
    return $this->type;
  }

  /**
   * Set the type of the field
   *
   * If type value is a string, the matching enum value will be found. If no match
   * is found, an exception will be thrown.
   *
   * @since 1.0.0
   */
  public function setType(string|FieldTypeEnum $type): self
  {
    $this->setEnumProperty(FieldTypeEnum::class, $type, 'type');
    return $this;
  }

  /**
   * @since 1.0.0
   */
  public function getInstructions(): string
  {
    return $this->instructions;
  }

  /**
   * Set the instructions for the field in the admin interface
   * @since 1.0.0
   */
  public function setInstructions(string $instructions): self
  {
    $this->instructions = $instructions;
    return $this;
  }

  /**
   * @since 1.0.0
   */
  public function getRequired(): IntSwitchEnum
  {
    return $this->required;
  }

  /**
   * Set whether the field is required or not
   *
   * If required value is an integer, the matching enum value will be found. If no match
   * is found, an exception will be thrown.
   *
   * @since 1.0.0
   */
  public function setRequired(int|IntSwitchEnum $required): self
  {
    $this->setEnumProperty(IntSwitchEnum::class, $required, 'required');
    return $this;
  }

  /**
   * Set the wrapper attributes for the field element
   *
   * The wrapper attributes are used to set the width, class, and id of the field element.
   *
   * @since 1.0.0
   */
  public function setWrapper(array $wrapper): self
  {
    $this->wrapper = $wrapper;
    return $this;
  }

  /**
   * Set the default value of the field if null
   * @since 1.0.0
   */
  public function setDefaultValue(mixed $defaultValue): self
  {
    $this->defaultValue = $defaultValue;
    return $this;
  }

  /**
   * Get the validation rules for the field
   * @since 1.0.0
   */
  public function getValidation(): array
  {
    return $this->validation;
  }

  /**
   * Set the validation rules for the field
   * @since 1.0.0
   */
  public function setValidation(array $validation): self
  {
    $this->validation = $validation;
    return $this;
  }

  /**
   * Get custom error messages for validation rules
   * Override this method in child classes to provide custom messages
   *
   * @return array Array of validation rule => custom message mappings
   * @since 1.0.0
   */
  public function getCustomValidationMessages(): array
  {
    return [];
  }

  /**
   * Get whether or not the field is conditionally displayed
   * @since 1.0.0
   */
  public function hasConditions(): bool
  {
    return $this->conditionalLogicSet->hasConditions();
  }

  /**
   * Get the return format of the field's value
   * @since 1.0.0
   */
  public function getReturnFormat(): string
  {
    if (property_exists($this, 'returnFormat')) {
      return $this->returnFormat->value;
    } elseif (in_array($this->type->value, [
      FieldTypeEnum::Repeater->value,
      FieldTypeEnum::Group->value
    ])) {
      return 'array';
    } else {
      return 'string';
    }
  }

  /**
   * Static method available for all fields to create a new instance of the field
   * @since 1.0.0
   */
  public static function create(FieldGroupEntity|RepeaterFieldEntity|GroupFieldEntity|null $parent = null): static
  {
    return new static($parent);
  }
}
