<?php
declare(strict_types=1);

namespace WP_AUX\Entities\AuxFramework;

use AuxCF\Entities\FieldEntity;
use AuxCF\Entities\FieldGroupEntity;

/**
 * Handles operations associated with AUX Component blocks, including cleaning data and rendering files.
 *
 * This class provides context awareness for AUX ACF fields, providing access to
 * AUX fields and field groups for better control over field rendering.
 *
 * @since 1.0.0
 */
class BlockHandlerEntity
{
  protected string $blockName;
  protected array $blockData;
  protected FieldGroupEntity $fieldGroup;
  protected static ?FieldEntity $currentField = null;

  public function __construct(array $block) {
    $this->blockData = $block;
    $this->blockName = str_replace('-', '_', substr(strstr($block['name'], '/'), 1));

    $fieldGroupEntity = @\WP_AUX::instance()->getActiveFieldGroupsEntities()[$this->blockName];
    if ($fieldGroupEntity) {
      $this->fieldGroup = new $fieldGroupEntity;
    }
  }

  /**
   * Get the block name
   * @since 1.0.1
   */
  public function getBlockName(): string
  {
    return $this->blockName;
  }

  /**
   * Get the block "slug" (everything after 'aux/' in the WP component name)
   * @since 1.0.1
   */
  public function getSlug(): string
  {
    return substr($this->blockData['name'], 4);
  }

  /**
   * Get the raw block data
   * @since 1.0.1
   */
  public function getBlockData(): array
  {
    return $this->blockData;
  }

  /**
   * Clean the value of any ACF fields, recursively
   * @since 1.0.0
   */
  public static function cleanValue($value, array $allowFilters = []) {
    if (is_array($value)) {
      foreach ($value as $subValueKey => $subValue) {
        $value[$subValueKey] = self::cleanValue($subValue, $allowFilters);
      }
    } elseif (is_scalar($value)) {
      $value = wp_kses_post($value);
    }

    return $value;
  }

  /**
   * Get the field value from the block data.
   *
   * Actions can be added before ('aux/get_field' and 'aux/get_field/{name}') and
   * after ('aux/after_get_field' and 'aux/after_get_field/{name}') a field is
   * retrieved. Useful for removing (and then adding back) any ACF filters that
   * are applied to the field value.
   *
   * @since 1.0.0
   * @todo Use the field for type-aware filters
   */
  public function getField(string $fieldName, array $allowFilters = []) {
    /**
     * Get the field entity with the field name. If the field name is prefixed
     * with 'field_', remove the prefix just for the sake of the lookup.
     */
    $fieldArrayName = $fieldName;

    if (strpos($fieldName, 'field_') === 0) {
      $fieldArrayName = substr($fieldName, 6);
    }

    $field = @$this->fieldGroup->getFields()[$fieldArrayName];

    self::setCurrentField($field);

    /**
     * Do AUX actions before getting the field for all fields, passing the field name and the field entity as arguments
     * The field entity may be null
     */
    do_action('aux/get_field', $fieldArrayName, $field);

    /**
     * Do AUX actions before getting the field for just fields with this name
     * The field entity may be null
     */
    do_action('aux/get_field/' . $fieldArrayName, $field);

    $value = get_field($fieldName);

    if (is_null($value) && $field && !empty($field::$replacesFields)) {
      foreach ($field::$replacesFields as $oldFieldName) {
        $value = get_field($oldFieldName);

        if (!is_null($value)) {
          break;
        }
      }
    }

    $return = self::cleanValue($value, $allowFilters);

    /**
     * Do AUX actions after getting the field for all fields, passing the field name and the field entity as arguments
     * The field entity may be null
     */
    do_action('aux/after_get_field', $fieldArrayName, $field);

    /**
     * Do AUX actions after getting the field for just fields with this name
     * The field entity may be null
     */
    do_action('aux/after_get_field/' . $fieldArrayName, $field);

    self::setCurrentField(null);

    return $return;
  }

  /**
   * Render a block file, relative to the blocks directory
   *
   * The block file that is included is in the format of:
   *
   * blocks/{blockPath}.php
   *
   * If this file is found in the child theme, it will be included. If not, the
   * parent theme's file will be included.
   *
   * For logged-in admins, the block will be wrapped in HTML comments to indicate
   * the beginning and end of the block.
   *
   * @since 1.0.0
   */
  protected function renderFile(string $blockPath): void
  {
    /**
     * $block and $blockHandlder should be available in the included block file
     */
    $block = $this->blockData;
    $blockHandler = $this;

    if (is_current_user_admin()) {
      echo '<!-- AUX BEGIN ' . $this->blockName . ' -->';
    }

    $blockFile = get_theme_file_path('blocks/' . $blockPath . '.php');

    if (file_exists($blockFile)) {
      ob_start();
      include $blockFile;
      $blockContent = ob_get_clean();

      echo do_shortcode($blockContent);
    }

    if (is_current_user_admin()) {
      echo '<!-- AUX END ' . $this->blockName . ' -->';
    }
  }

  /**
   * Render the block
   *
   * The block file that is included is in the format of:
   *
   * blocks/{block_name}/{block_name}.php
   *
   * If this file is found in the child theme, it will be included. If not, the
   * parent theme's file will be included.
   '
   * @since 1.0.0
   */
  public function render(): void
  {
    $this->renderFile($this->blockName . '/' . $this->blockName);
  }

  /**
   * Render a block variation
   *
   * The block variation file that is included is in the format of:
   *
   * blocks/{block_name}/variations/{variation}.php
   *
   * If this file is found in the child theme, it will be included. If not, the
   * parent theme's file will be included.
   *
   * If no variation is specified, the default variation will be used.
   '
   * @since 1.0.0
   */
  public function renderVariation(?string $variationField = null): void
  {
    if ($variationField) {
      $variation = aux_get_field($variationField);
      if (empty($variation)) {
        $variation = 'default';
      }
    } else {
      $variation = 'default';
    }

    $this->renderFile($this->blockName . '/variations/' . $variation);
  }

  /**
   * Set what current field is currently being "getField"-ed
   *
   * The current field is set just before the ACF get_field() call is made. This
   * makes the current field available (via self::getCurrentField()) for use in
   * AUX & ACF filters and actions.
   *
   * @since 1.0.0
   */
  public static function setCurrentField(?FieldEntity $field): void
  {
    self::$currentField = $field;
  }

  /**
   * Get what current field is currently being "getField"-ed
   *
   * For use in AUX & ACF filters and actions.
   *
   * <code>
   *  add_filter( 'acf/load_value', function($value, $postId, $field) {
   *    if ($currentAuxField = BlockHandlerEntity::getCurrentField()) {
   *      // do something based on the current AUX field entity
   *    }
   *    return $value;
   *  }, 10, 3);
   * </code>
   *
   * @since 1.0.0
   */
  public static function getCurrentField(): ?FieldEntity
  {
    return self::$currentField;
  }
}
