<?php
declare(strict_types=1);

namespace WP_AUX\Services;

use AuxCF\Entities\FieldEntity;

/**
 * Methods for setting up Wordpress posts queries, and for special posts queries
 * @since 1.0.0
 */
class PostsQueryService
{
  /**
   * Expand related/recent posts results.
   *
   * If enough posts are already found, or 'aux/related_posts_expand' filter
   * is not provided, results are not expanded.
   *
   * @since 1.0.0
   */
  public static function expandRelatedPosts($posts, $limit, $args): array
  {
    $postsNeeded = $limit - count($posts);

    if ($postsNeeded > 0) {
      $expandArgs = $args;
      $expandArgs['numberposts'] = $postsNeeded;
      $expandArgs['post__not_in'] = array_merge(
        (array) @$expandArgs['post__not_in'],
        wp_list_pluck($posts, 'ID')
      );

      $expandArgsOriginal = $expandArgs;
      $expandArgs = apply_filters('aux/related_posts_expand', $expandArgs);

      if ($expandArgsOriginal === $expandArgs) {
        return $posts;
      }

      if ($expandedPosts = get_posts($expandArgs)) {
        $posts = array_merge($posts, $expandedPosts);
      }
    }

    return $posts;
  }

  /**
   * Create meta_query argument value for get_posts() for ACF field values
   * @since 1.0.0
   */
  public static function metaQueryArgs(
    string|FieldEntity $field,
    mixed $fieldValue,
  ): array
  {
    $fieldKey = (is_string($field)) ? $field : $field::$name;

    $fieldValues = is_array($fieldValue) ? $fieldValue : [$fieldValue];

    $metaConditions = [];

    foreach ($fieldValues as $fieldValue) {
      $values = !is_array($fieldValue) ? [ $fieldValue ] : $fieldValue;

      $inValues = [];
      $likeValues = [];

      foreach ($values as $value) {
        $value = (string) $value;

        if ('' === $value) {
          continue;
        }

        $likeValue = '"' . $value . '"';

        $inValues[] = $value;
        $likeValues[] = [
          'key' => $fieldKey,
          'value' => $likeValue,
          'compare' => 'LIKE',
        ];
      }

      if (!empty($inValues)) {
        $metaCondition = $likeValues;

        $metaCondition[] = [
          'key' => $fieldKey,
          'value' => $value,
          'compare' => 'IN',
        ];

        $metaCondition['relation'] = 'OR';

        $metaConditions[] = $metaCondition;
      }
    }

    if (count($metaConditions) > 1) {
      $metaConditions['relation'] = 'OR';
    }

    return $metaConditions;
  }

  /**
   * Get all posts with a specific ACF field's value
   * @param string $returnFormat 'OBJECTS' or 'IDS'
   * @return \WP_Post[]|int[]
   * @since 1.0.0
   */
  public static function getPostsWithFieldValue(
    string|FieldEntity $field,
    mixed $fieldValue,
    string $returnFormat = 'OBJECTS'
  ): array
  {
    $metaConditions = PostsQueryService::metaQueryArgs($field, $fieldValue);

    $args = [
      'numberposts' => -1,
      'posts_per_page' => -1,
      'post_type' => 'any',
      'post_status' => ['publish'],
      'meta_query' => $metaConditions,
    ];

    if ('IDS' === $returnFormat) {
      $args['fields'] = 'ids';
    }

    return get_posts($args);
  }

  /**
   * Create base arguments for related posts from get_posts()
   * @since 1.0.0
   */
  public static function relatedPostsQueryBaseArgs(
    int $limit,
    ?array $postTypes = null
  ): array
  {
    $post = get_post();

    $relatedArgs = array(
      'numberposts' => $limit,
      'post_status' => 'publish',
      'post__not_in' => [ $post->ID ],
      'orderby' => 'date',
      'order' => 'DESC',
    );

    if (!empty($postTypes)) {
      $relatedArgs['post_type'] = $postTypes;
    }

    return $relatedArgs;
  }

  /**
   * Create tax_query value for get_posts()
   *
   * Taxonomies provided just by taxonomy slug will consult the current post to build a list of values for that taxonomy
   * <code>
   *   PostQueryService::taxonomiesQueryArgs(['category', 'post_tag'])
   * </code>
   *
   * Taxonomies can be provided as matching values for a taxonomy
   * <code>
   *   PostQueryService::taxonomiesQueryArgs([
   *    'category' => [
   *      'category_1', 'category_2'
   *    ],
   *  ])
   * </code>
   *
   * Taxonomies can be provided in a mixed form, like this for getting posts with 'category' matching the current post,
   * and with specific 'post_tag' values
   * <code>
   *  PostQueryService::taxonomiesQueryArgs([
   *    'category',
   *    'post_tag' => [
   *      'term_1', 'term_2'
   *    ],
   *  ])
   * </code>
   *
   * @since 1.0.0
   */
  public static function taxonomiesQueryArgs(array $taxonomies): array
  {
    $taxQuery = [];

    $post = get_post();

    foreach ($taxonomies as $taxonomyName => $taxonomyValues) {
      if (is_numeric($taxonomyName)) {
        $taxonomyName = $taxonomyValues;
        $taxonomyValues = wp_get_post_terms($post->ID, $taxonomyName);
      }

      if (!empty($taxonomyValues)) {
        $taxonomyValues = array_filter($taxonomyValues, function ($value) {
          if ($value instanceof \WP_Error) {
            return false;
          }

          return true;
        });
      }

      if (empty($taxonomyValues)) {
        continue;
      }

      $taxQueryItem = [
        'taxonomy' => $taxonomyName,
        'field' => 'slug',
        'include_children' => true,
        'operator' => 'IN',
        'terms' => wp_list_pluck($taxonomyValues, 'slug'),
      ];

      $taxQuery[] = $taxQueryItem;
    }

    if (count($taxQuery) > 1) {
      $taxQuery['relation'] = 'OR';
    }

    return $taxQuery;
  }
}
