<?php

namespace ModernFields\FieldGroups\Locations\UseCases;

use ModernFields\FieldGroups\Services\MetaService;
use ModernFields\WordPress\Security\SecurityHelper;

/**
 * Use case class responsible for registering custom fields with WordPress.
 * 
 * This use case processes field groups and registers their fields as WordPress
 * meta fields or settings, making them accessible via the REST API. The registration
 * method varies based on the field group location (post, user, term, media, or options).
 */
class RegisterFieldsUseCase
{
  protected array $fields = [];

  /**
   * Constructor.
   * 
   * @param array $field_groups Array of field group entities to register fields for.
   */
  public function __construct(
    protected array $field_groups = []
  ) {}

  /**
   * Registers all fields from the configured field groups with WordPress.
   * 
   * This method iterates through each field group, extracts field definitions
   * from their content, and registers each field with WordPress using the
   * appropriate registration function based on the field group location.
   * 
   * For each field, it:
   * - Determines the field type from block attributes
   * - Configures REST API access and sanitization
   * - Registers the field using the appropriate WordPress function
   * 
   * @since 1.0.0
   * @return void
   */
  public function execute(): void
  {
    # Register meta fields for each field group
    foreach ($this->field_groups as $field_group) {
      # Skip field groups that don't have any content
      if (!$field_group->getContent()) {
        continue;
      }

      # Register each field as a setting accessible via REST API
      foreach ($field_group->getFields() as $field) {
        $type = $field->getType();

        # Meta Service handles the sanitization and schema for the field
        $meta_service = new MetaService($field);

        # Show in rest
        # For object types, show_in_rest needs to be an array with schema definition
        # For array types, show_in_rest needs items schema to describe each element
        if ($field->getType() === 'object') {
          $properties = $meta_service->getSchema();
          if (!empty($properties)) {
            $show_in_rest = [
              'schema' => [
                'type' => 'object',
                'properties' => $properties,
              ],
            ];
          } else {
            $show_in_rest = true;
          }
        } elseif ($field->getType() === 'array') {
          $items = $meta_service->getSchema();
          if (!empty($items)) {
            $show_in_rest = [
              'schema' => [
                'type' => 'array',
                'items' => $items,
              ],
            ];
          } else {
            $show_in_rest = true;
          }
        } else {
          $show_in_rest = true;
        }

        # Prepare registration arguments for WordPress meta/option registration
        $args = [
          'type' => $type,                                                  # Data type (string, number, boolean, etc.)
          'label' => $field->getLabel(),                                    # Human-readable label
          'description' => $field->getDescription(),                        # Human-readable description
          'single' => true,                                                 # Store as single value (not array)
          'show_in_rest' => $show_in_rest,                                  # Make available via REST API
          'sanitize_callback' => $meta_service->getSanitizeCallback(),      # Sanitization function
          'auth_callback' => function () use ($field_group) {               # Authentication callback
            return SecurityHelper::checkRoles($field_group->getRoles());
          },
        ];

        # Register the configured default so get_post_meta / get_term_meta /
        # get_user_meta / get_option return it for records that have never saved
        # this field — instead of the WP type empty ("", 0, false, []).
        $default_value = $field->getAttribute('defaultValue');
        $type_empty    = match ($type) {
          'boolean' => false,
          'number'  => 0,
          'array'   => [],
          default   => '',
        };

        if ($default_value !== null && $default_value !== $type_empty) {
          # Relation fields: ensure defaults are clean integer post IDs.
          if ($field->getBlockName() === 'modern-fields/relation' && is_array($default_value)) {
            $args['default'] = array_values(array_filter(array_map('intval', $default_value)));
          } else {
            $args['default'] = $default_value;
          }
        }

        # Field values are private by default: unless the field opts in through
        # exposeInRest, the value is only readable in the permission-gated
        # `edit` context (the block editor). Writes are never affected. The
        # callback delegates to core prepare_value() for authorized readers so
        # the schema type cast (e.g. "1" → true) is preserved. Options keep the
        # plain $args: /wp/v2/settings already requires manage_options.
        $meta_args = $args;

        if ($field->getAttribute('exposeInRest') !== true) {
          if (!is_array($meta_args['show_in_rest'])) {
            $meta_args['show_in_rest'] = [];
          }

          $meta_args['show_in_rest']['prepare_callback'] = function ($value, $request, $rest_args) {
            if ($request['context'] === 'edit') {
              return \WP_REST_Meta_Fields::prepare_value($value, $request, $rest_args);
            }

            return null;
          };
        }

        # Register as post meta for the specific post type
        if (in_array($field_group->getLocation(), ['post', 'page', 'custom-post-type'])) {
          # Scope the meta to the exact post type: post/page groups map to
          # their built-in type, custom-post-type groups to the targeted CPT
          # (mf_cpt is only set for those). An empty subtype would register
          # the meta on every post type.
          $post_type = $field_group->getLocation() === 'custom-post-type'
            ? $field_group->getPostType()
            : $field_group->getLocation();

          # WordPress only exposes `meta` in the REST schema of custom post
          # types that support custom-fields — without it, block editor saves
          # silently drop the field values. Forced here so third-party CPTs
          # registered without it keep working once a field group targets them.
          if ($post_type && !post_type_supports($post_type, 'custom-fields')) {
            add_post_type_support($post_type, 'custom-fields');
          }

          register_post_meta(
            $post_type,
            $field->getName(),
            $meta_args
          );
        }

        # Register as user meta
        if ($field_group->getLocation() === 'user') {
          register_meta(
            'user',
            $field->getName(),
            $meta_args
          );
        }

        # Register as attachment (media) meta
        if ($field_group->getLocation() === 'media') {
          register_post_meta(
            'attachment',
            $field->getName(),
            $meta_args
          );
        }

        # Register as term meta
        if ($field_group->getLocation() === 'taxonomy') {
          register_meta(
            'term',
            $field->getName(),
            $meta_args
          );
        }

        # Register as a WordPress option (site-wide setting)
        if (in_array($field_group->getLocation(), ['options-page', 'dashboard'], true)) {
          register_setting(
            'modern_fields_options',
            $field->getName(),
            $args
          );
        }
      }
    }
  }
}
