<?php
declare(strict_types=1);

namespace WP_AUX\Services;

use AuxCF\Entities\FieldGroup\FieldGroupOptionsPageLocationEntity;
use WP_AUX\Entities\AuxCF\OptionsFieldGroups\AcfPostTypeOptionsGroupEntity;

/**
 * Utilities for working with custom post types
 *
 * Using these methods streamlines standard Wordpress custom post type registration
 * and provides useful functionality - especially for parent/child configuration -
 * for the AUX theme.
 *
 * @since 1.0.0
 */
class CustomPostTypesService
{
  /**
   * Create the Wordpress custom post type registration array with a minimum of arguments
   * @since 1.0.0
   */
  public static function createCustomPostTypeArray(
    string $slug,
    string $labelPlural,
    string $label,
    ?array $supports = null,
    array $taxonomies = [],
    ?string $menuIcon = null,
    ?int $menuPosition = null,
    bool|string $hasArchive = true
  ): array
  {
    $lcLabelPlural = strtolower($labelPlural);
    $lcLabel = strtolower($label);

    if (is_null($supports)) {
      $supports = [
        'title',
        'author',
        'editor',
        'excerpt',
        'revisions',
        'page-attributes',
        'thumbnail',
        'custom-fields',
      ];
    }

    if (empty($menuIcon)) {
      $menuIcon = 'dashicons-admin-post';
    }

    if (!empty($hasArchive)) {
      $rewrite = [
        'slug' => is_string($hasArchive) ? $hasArchive : $slug,
        'with_front' => false,
      ];

      $hasArchive = true;
    } else {
      $rewrite = [
        'slug' => $slug,
      ];
    }

    $args = [
      'labels' => [
        'name' => $labelPlural,
        'singular_name' => $label,
        'menu_name' => $labelPlural,
        'all_items' => 'All ' . $labelPlural,
        'edit_item' => 'Edit ' . $label,
        'view_item' => 'View ' . $label,
        'view_items' => 'View ' . $labelPlural,
        'add_new_item' => 'Add New ' . $label,
        'add_new' => 'Add New ' . $label,
        'new_item' => 'New ' . $label,
        'parent_item_colon' => 'Parent ' . $label . ':',
        'search_items' => 'Search ' . $labelPlural,
        'not_found' => 'No ' . $lcLabelPlural . ' found',
        'not_found_in_trash' => 'No ' . $lcLabelPlural . ' found in Trash',
        'archives' => $label . ' Archives',
        'attributes' => $label . ' Attributes',
        'insert_into_item' => 'Insert into ' . $lcLabel,
        'uploaded_to_this_item' => 'Uploaded to this ' . $lcLabel,
        'filter_items_list' => 'Filter ' . $lcLabelPlural . ' list',
        'filter_by_date' => 'Filter ' . $lcLabelPlural . ' by date',
        'items_list_navigation' => $labelPlural . ' list navigation',
        'items_list' => $labelPlural . ' list',
        'item_published' => $label . ' published.',
        'item_published_privately' => $label . ' published privately.',
        'item_reverted_to_draft' => $label . ' reverted to draft.',
        'item_scheduled' => $label . ' scheduled.',
        'item_updated' => $label . ' updated.',
        'item_link' => $label . ' Link',
        'item_link_description' => 'A link to a ' . $lcLabel . '.',
      ],
      'public' => true,
      'exclude_from_search' => false,
      'show_in_rest' => true,
      'show_in_menu' => true,
      'has_archive' => $hasArchive,
      'rewrite' => $rewrite,
      'menu_icon' => $menuIcon,
      'menu_position' => $menuPosition,
      'supports' => $supports,
      'taxonomies' => $taxonomies,
      'delete_with_user' => false,
    ];

    return apply_filters('aux/custom_post_type_args/' . $slug, $args);
  }

  /**
   * Register a custom post type with Wordpress and with WP_AUX
   *
   * WP_AUX uses registered post types to support use and further configuration - especially
   * for parent/child configuration - that Wordpress does not support.
   *
   * @since 1.0.0
   */
  public static function registerCustomPostType(
    string $slug,
    string $labelPlural,
    string $label,
    ?array $supports = null,
    array $taxonomies = [],
    ?string $menuIcon = null,
    ?int $menuPosition = null,
    bool|string $hasArchive = true
  ): void
  {
    $postTypeArray = self::createCustomPostTypeArray(
      $slug,
      $labelPlural,
      $label,
      $supports,
      $taxonomies,
      $menuIcon,
      $menuPosition,
      $hasArchive
    );

    register_post_type($slug, $postTypeArray);

    /**
     * If a post type has an archive, include the post type in the top-level RSS feed
     */
    if ($hasArchive) {
      add_post_type_support($slug, 'top-level-feed');
    }

    \WP_AUX::instance()->addCustomPostType($slug, $postTypeArray);
  }

  /**
   * Register date-based permalink structure for a custom post type
   * @since 1.0.0
   */
  public static function registerPermalinkStructure(string $postType, string $structure = 'date'): void
  {
    $postType = get_post_type_object($postType);
    $archiveSlug = $postType->rewrite['slug'];

    add_permastruct(
      $postType->name,
      "/{$archiveSlug}/%year%/%monthnum%/%day%/%{$postType->name}%/",
      [ 'with_front' => false ]
    );

    add_rewrite_rule(
      '^' . $archiveSlug . '/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/?$',
      'index.php?post_type=' . $postType->name . '&year=$matches[1]&monthnum=$matches[2]&day=$matches[3]',
      'top'
    );

    add_rewrite_rule(
      '^' . $archiveSlug . '/([0-9]{4})/([0-9]{1,2})/?$',
      'index.php?post_type=' . $postType->name . '&year=$matches[1]&monthnum=$matches[2]',
      'top'
    );

    add_rewrite_rule(
      '^' . $archiveSlug . '/([0-9]{4})/?$',
      'index.php?post_type=' . $postType->name . '&year=$matches[1]',
      'top'
    );

    add_filter( 'post_type_link', function($url, $post) use ($postType) {
      if ( $postType->name === get_post_type( $post )) {
        $year = get_the_date('Y', $post);

        if (is_bool($year)) {
          return $url;
        }

        $url = str_replace( "%year%", $year, $url );
        $url = str_replace( "%monthnum%", get_the_date('m', $post), $url );
        $url = str_replace( "%day%", get_the_date('d', $post), $url );
      }

      return $url;
    }, 10, 2 );
  }

  /**
   * Add custom post types to taxonomy archives
   * @since 1.0.0
   */
  public static function registerCustomPostTypesForArchive(\WP_Query $query): void
  {
    if (
      is_admin() ||
      (!$query->is_main_query() && !$query->is_feed()) ||
      $query->is_post_type_archive() ||
      ($query->is_search() && !empty($query->get('post_type'))) ||
      !empty( $query->query_vars['suppress_filters'] )
    ) {
      return;
    }

    $auxPostTypes = \WP_AUX::instance()->getCustomPostTypes();
    $allPostTypes = get_post_types(
      [
        'exclude_from_search' => false,
      ],
      'objects'
    );
    $combinedPostTypes = array_unique(array_merge(array_keys($auxPostTypes), array_keys($allPostTypes)));

    if (empty($combinedPostTypes)) {
      return;
    }

    if (
      (is_feed() || is_category() || is_tax() || is_tag())
    ) {
      $postTypes = $query->get('post_type');

      if (empty($postTypes)) {
        $postTypes = ['post'];
      } else {
        $postTypes = (array) $postTypes;
      }

      foreach ($combinedPostTypes as $postSlug) {
        $auxPostParams = @$auxPostTypes[$postSlug];
        $postParams = @$allPostTypes[$postSlug];

        if (
          is_feed() && !post_type_supports($postSlug, 'top-level-feed')
        ) {
          continue;
        }

        if (
          (isset($auxPostParams) && empty($auxPostParams['exclude_from_search'])) ||
          (isset($postParams) && empty($postParams->exclude_from_search))
        ) {
          $postTypes[] = $postSlug;
        }
      }

      if ($postTypes) {
        $query->set(
          'post_type',
          $postTypes
        );
      }
    }
  }

  /**
   * Register post type options pages for all post types with an archive
   * @since 1.0.0
   */
  public static function registerOptionsPageGroups(): void
  {
    if (!\WP_AUX::instance()->hasRequirementsMet()) {
      return;
    }

    $archivePostTypes = get_post_types(
      [
        'has_archive' => true,
      ],
      'objects'
    );

    foreach ($archivePostTypes as $archivePostType) {
      $pageKey = 'aux-' . $archivePostType->name . '-post-type-options';

      acf_add_options_page([
        'page_title' => $archivePostType->labels->menu_name . ' Options',
        'menu_title' => $archivePostType->labels->menu_name . ' Options',
        'menu_slug' => $pageKey,
        'parent_slug' => 'edit.php?post_type=' . $archivePostType->name,
        'capability'  => 'edit_posts',
        'position'  => 10,
        'icon_url' => false,
      ]);

      $fieldGroup = AcfPostTypeOptionsGroupEntity::createToConfigure();
      $fieldGroup->setFieldsWildcard($archivePostType->name);
      $fieldGroup->setOverrideKey(AcfPostTypeOptionsGroupEntity::$key . '-' . $archivePostType->name);
      $fieldGroup->init();
      $fieldGroup->addLocation(
        FieldGroupOptionsPageLocationEntity::create($pageKey)
      );

      $fieldGroup->register();
    }
  }

  public static function prepareHooks(): void
  {
    add_action('pre_get_posts', [ self::class, "registerCustomPostTypesForArchive" ]);
    add_action('init', [ self::class, 'registerOptionsPageGroups' ], 4000);
  }
}
