<?php

use AuxCF\Entities\FieldGroupEntity;
use WP_AUX\Entities\AcfFieldGroupsViewerEntity;
use WP_AUX\Entities\AuxCF\OptionsFields\Global\GlobalAuxVersionNextSwitchFieldEntity;
use WP_AUX\Entities\AuxFramework\BlockHandlerEntity;
use WP_AUX\Entities\RssHandlerEntity;
use WP_AUX\Entities\ThemeEntity;
use WP_AUX\Services\AuxFramework\PageFieldGroupsService;
use WP_AUX\Services\AuxCF\AuxBlocksService;
use WP_AUX\Services\AuxCF\DeprecatedFieldsService;
use WP_AUX\Services\CustomPostTypesService;
use WP_AUX\Services\HtmlService;
use WP_AUX\Services\PostService;
use WP_AUX\Services\RestService;
use WP_AUX\Services\SearchService;
use WP_AUX\Services\SecurityService;
use WP_AUX\Services\ShortcodeService;
use WP_AUX\Services\StoryService;
use WP_AUX\Services\TaxonomyTermsService;

class WP_AUX
{
  /**
   * @var string WP AUX Theme version
   */
  public const VERSION = 'v1.0.3';

  /**
   * @var string Version of AUX for production
   */
  public const AUX_VERSION = 'v1.13.2';

  /**
   * @var string Combined WP AUX theme and AUX version for cache busting
   */
    public const CACHE_CONTROL_VERSION = 'auxwptheme-' . self::VERSION . '+aux-' . self::AUX_VERSION;

  /**
   * @var string Next version of AUX, used for logged-in site admins
   *
   * The @next version points to the latest version when there is not an active @next version.
   */
  public const NEXT_AUX_VERSION = '@next';
  public const INCLUDES_DIR = 'inc';
  public const FIELDS_DIR = 'Entities/AuxCF/Fields';
  public const FIELD_GROUPS_DIR = 'Entities/AuxCF/FieldGroups';
  public const FIELDS_COLLECTIONS_DIR = 'Entities/AuxCF/FieldsCollections';
  public const PAGE_FIELD_GROUPS_DIR = 'Entities/AuxCF/PageFieldGroups';
  public const PAGE_FIELDS_DIR = 'Entities/AuxCF/PageFields';
  public const CHILD_THEME_OVERRIDE = 'childOverride';
  public const CHILD_THEME_PREPEND = 'childFirst';
  public const CHILD_THEME_APPEND = 'childAlso';
  public const CHILD_THEME_IGNORE = 'ignoreChild';

  /** @var FieldGroupEntity[] */
  protected $activeFieldGroupsEntities = [];

  /** @var FieldGroupEntity[] */
  protected $activePageFieldGroupsEntities = [];

  /** @var array[] Registered custom post types as associative array */
  protected $customPostTypes = [];

  protected ThemeEntity $theme;
  protected ?BlockHandlerEntity $currentBlockHandler = null;

  protected bool $hasRequirementsMet = false;

  protected function __construct()
  {
    $this->theme = new ThemeEntity();
    $this->prepareFieldGroups();
    $this->preparePageFieldGroups();
    $this->hasRequirementsMet = is_plugin_active('advanced-custom-fields-pro/acf.php');
  }

  /**
   * Registers field group entities by scanning for "*Entity.php" files in the
   * field groups directory in the child theme (if applicable) and the parent theme
   *
   * @see self::FIELD_GROUPS_DIR
   * @since 1.0.0
   */
  protected function prepareFieldGroups(): void
  {
    $tryDirectories = self::tryDirectories(
      self::INCLUDES_DIR .
      '/WP_AUX/' .
      self::FIELD_GROUPS_DIR
    );

    foreach ($tryDirectories as $tryDirectory) {
      $groupsDirContents = @glob(
        $tryDirectory .
        '/*Entity.php'
      );

      if (!empty($groupsDirContents)) {
        foreach ($groupsDirContents as $groupEntityPath) {
          $groupEntity = self::fileObjectPath($groupEntityPath);
          $this->registerFieldGroup($groupEntity);
        }
      }
    }
  }

  /**
   * Registers page field group entities by scanning for "*Entity.php" files in the
   * page field groups directory in the child theme (if applicable) and the parent theme
   *
   * @see self::PAGE_FIELD_GROUPS_DIR
   * @since 1.0.0
   */
  protected function preparePageFieldGroups(): void
  {
    $tryDirectories = self::tryDirectories(
      self::INCLUDES_DIR .
      '/WP_AUX/' .
      self::PAGE_FIELD_GROUPS_DIR
    );

    foreach ($tryDirectories as $tryDirectory) {
      $pageGroupsDirContents = @glob(
        $tryDirectory .
        '/*Entity.php'
      );

      if (!empty($pageGroupsDirContents)) {
        foreach ($pageGroupsDirContents as $pageGroupEntityPath) {
          $pageGroupEntity = self::fileObjectPath($pageGroupEntityPath);
          $this->registerPageFieldGroup($pageGroupEntity);
        }
      }
    }
  }

  /**
   * Check if the theme has all requirements met to function properly
   * @since 1.0.0
   */
  public function hasRequirementsMet(): bool
  {
    return $this->hasRequirementsMet;
  }

  /**
   * Registers custom post types with WP_AUX for reference in theme-specific
   * actions and filters
   *
   * @see CustomPostTypesService::registerCustomPostTypesForArchive()
   * @since 1.0.0
   */
  public function addCustomPostType(string $slug, array $params): void
  {
    $this->customPostTypes[$slug] = $params;
  }

  /**
   * Get the theme entity
   *
   * @since 1.0.0
   */
  public function getTheme(): ThemeEntity
  {
    return $this->theme;
  }

  /**
   * Get all of the active field groups
   *
   * @return FieldGroupEntity[]
   * @since 1.0.0
   */
  public function getActiveFieldGroupsEntities(): array
  {
    return $this->activeFieldGroupsEntities;
  }

  /**
   * Get a specific field group by key, or null if field d
   *
   * @see FieldGroupEntity::$key
   * @since 1.0.0
   */
  public function getFieldGroupEntity(string $key): ?FieldGroupEntity
  {
    return @$this->activeFieldGroupsEntities[$key];
  }

  /**
   * Register a field group if the field group is active
   *
   * @param string $groupEntity Namespace path to the group entity object
   * @since 1.0.0
   */
  public function registerFieldGroup(string $groupEntity): void
  {
    if ($groupEntity::isActive()) {
      $this->activeFieldGroupsEntities[$groupEntity::$key] = new $groupEntity;
    }
  }

  /**
   * Register a page builder field group if the field group is active
   *
   * @since 1.0.0
   */
  public function registerPageFieldGroup($pageGroupEntity): void
  {
    if ($pageGroupEntity::isActive()) {
      $this->activePageFieldGroupsEntities[$pageGroupEntity::$key] = new $pageGroupEntity;
    }
  }

  /**
   * Get all of the active field groups
   *
   * @return FieldGroupEntity[]
   *
   * @since 1.0.0
   */
  public function getActivePageFieldGroupsEntities() {
    return $this->activePageFieldGroupsEntities;
  }

  /**
   * Get a specific page builder field group by key, or null if field d
   *
   * @see FieldGroupEntity::$key
   * @since 1.0.0
   */
  public function getPageFieldGroupEntity(string $key): ?FieldGroupEntity
  {
    return @$this->activePageFieldGroupsEntities[$key];
  }

  /**
   * Get a specific field group by key, or null if field d
   *
   * @see FieldGroupEntity::$key
   * @since 1.0.0
   */
  public static function requireThemeFile(string $relativePath, string $overrideStrategy = self::CHILD_THEME_APPEND): void
  {
    $require = [
      get_parent_theme_file_path($relativePath),
    ];

    if (is_child_theme() && self::CHILD_THEME_IGNORE !== $overrideStrategy) {
      if (self::CHILD_THEME_PREPEND === $overrideStrategy) {
        array_unshift($require, get_theme_file_path($relativePath));
      } elseif (self::CHILD_THEME_APPEND === $overrideStrategy) {
        $require[] = get_theme_file_path($relativePath);
      } elseif (self::CHILD_THEME_OVERRIDE === $overrideStrategy) {
        $require = [ get_theme_file_path($relativePath) ];
      }
    }

    $require = array_unique($require);

    foreach ($require as $requireFile) {
      if (empty($requireFile)) {
        continue;
      }

      require_once $requireFile;
    }
  }

  /**
   * Get absolute paths to a file or directory in the child theme (if applicable)
   * and the parent theme
   *
   * @param string $relativePath Relative path to the file or directory
   * @return string[] Absolute paths to the file or directory
   *
   * @since 1.0.0
   * @uses get_template_directory()
   * @uses get_stylesheet_directory()
   */
  public static function tryDirectories(string $relativePath = ''): array
  {
    if (!empty($relativePath)) {
      $relativePath = '/' . $relativePath;
    }

    $tryDirectories = [
      get_template_directory(self::INCLUDES_DIR) . $relativePath,
    ];

    // If using a child theme, try the child directory first
    if (is_child_theme()) {
      array_unshift($tryDirectories, get_stylesheet_directory() . $relativePath);
    }

    return $tryDirectories;
  }

  /**
   * Get absolute path to a file in the WP_AUX library
   *
   * @param string $relativePath Relative path to the file
   * @return string Absolute path to the file
   *
   * @since 1.0.0
   */
  public static function filePath(string $relativePath): string
  {
    return __DIR__ . '/' . $relativePath;
  }

  /**
   * Get the namespaced path to an object from a relative file path, consulting
   * the child theme (if applicable) and the parent theme
   *
   * @param string $filePath Relative path to the file
   * @return string Namespaced object path
   *
   * @uses self::tryDirectories()
   * @since 1.0.0
   */
  public static function fileObjectPath(string $filePath): string
  {
    $objectPath = '';

    $filePath = str_replace('/', '\\', $filePath);

    $tryDirectories = self::tryDirectories( self::INCLUDES_DIR );

    foreach ($tryDirectories as $tryDirectory) {
      $tryDirectory = str_replace('/', '\\', $tryDirectory);

      if (strpos($filePath, $tryDirectory) === 0) {
        $objectPath = substr(
          substr($filePath, 0, -4),
          strlen($tryDirectory)
        );
      }
    }

    return str_replace('/', '\\', $objectPath);
  }

  /**
   * Get the active version of the AUX framework
   *
   * @since 1.0.0
   * @uses is_current_user_admin()
   */
  public static function getAuxVersion(): string
  {
    $auxVersion = self::AUX_VERSION;

    if (
      is_current_user_admin() &&
      !empty(self::NEXT_AUX_VERSION) &&
      aux_get_field(GlobalAuxVersionNextSwitchFieldEntity::$name, [], 'option')
    ) {
      $auxVersion = self::NEXT_AUX_VERSION;
    }

    return $auxVersion;
  }

  /**
   * Register required filters and actions
   *
   * @since 1.0.0
   */
  public static function registerFiltersAndActions(): void
  {
    AuxBlocksService::prepareHooks();
    DeprecatedFieldsService::prepareHooks();
    CustomPostTypesService::prepareHooks();
    HtmlService::prepareHooks();
    PostService::prepareHooks();
    RestService::prepareHooks();
    RssHandlerEntity::prepareHooks();
    ShortcodeService::prepareHooks();
    AcfFieldGroupsViewerEntity::register();
    StoryService::prepareHooks();
    SearchService::prepareHooks();
    SecurityService::prepareHooks();
    TaxonomyTermsService::prepareHooks();
  }

  /**
   * Get registered custom post types
   *
   * @since 1.0.0
   */
  public function getCustomPostTypes(): array
  {
    return $this->customPostTypes;
  }

  /**
   * Renders an AUX component with the block handler
   *
   * @since 1.0.0
   */
  public static function renderAuxBlock(array $block): void
  {
    $blockHandler = new BlockHandlerEntity($block);
    \WP_AUX::instance()->setCurrentBlockHandler($blockHandler);

    $blockHandler->render();

    \WP_AUX::instance()->setCurrentBlockHandler(null);
  }

  /**
   * Set the current block handler that is being rendered
   * @since 1.0.0
   */
  public function setCurrentBlockHandler(?BlockHandlerEntity $blockHandler): void
  {
    $this->currentBlockHandler = $blockHandler;
  }

  /**
   * Get the block handler for the block we are currently rendering
   * @since 1.0.0
   */
  public function getCurrentBlockHandler(): ?BlockHandlerEntity
  {
    return $this->currentBlockHandler;
  }

  /**
   * Shorthand access to the theme entity
   *
   * @since 1.0.0
   */
  public static function theme(): ThemeEntity
  {
    return self::instance()->getTheme();
  }

  /**
   * Get the "singleton" instance of WP_AUX
   *
   * @since 1.0.0
   */
  public static function instance(): self
  {
    static $instance;

    if (is_null($instance)) {
      $instance = new self();
    }

    return $instance;
  }
}

/**
 * Get the ACF field value
 *
 * @return mixed The value of the field
 *
 * @since 1.0.0
 * @uses get_field()
 * @uses BlockHandlerEntity::cleanValue()
 * @uses BlockHandlerEntity::getField()
 */
function aux_get_field(string $fieldName, array $allowFilters = [], $postId = null) {
  global $blockHandler;

  if (!\WP_AUX::instance()->hasRequirementsMet()) {
    return null;
  }

  if (strpos($fieldName, 'field_') !== 0) {
    $fieldName = 'field_' . $fieldName;
  }

  if (!$blockHandler) {
    return BlockHandlerEntity::cleanValue(get_field($fieldName, $postId), $allowFilters);
  }

  return $blockHandler->getField($fieldName, $allowFilters);
}

/**
 * Get the ACF field value of a known array field with array keys cleaned
 *
 * @return mixed The value of the field
 *
 * @since 1.0.0
 * @uses aux_get_field()
 * @uses PageFieldGroupsService::reindexSettingsFromRequest()
 */
function aux_get_field_array(string $fieldName, array $allowFilters = [], $postId = null) {
  $value = aux_get_field($fieldName, $allowFilters, $postId);

  $value = PageFieldGroupsService::reindexSettingsFromRequest($value, $fieldName);

  return $value;
}
