<?php

namespace ModernFields\FieldGroups\Locations\UseCases;

use ModernFields\FieldGroups\Entities\User;
use ModernFields\FieldGroups\Entities\Attachment;
use ModernFields\FieldGroups\Entities\Term;
use ModernFields\FieldGroups\Entities\Post;
use ModernFields\FieldGroups\Services\MetaService;

/**
 * Use case class responsible for saving field values to WordPress meta.
 *
 * This service handles the processing and persistence of custom field values
 * submitted via POST data for user, media (attachment), term and classic-editor
 * post entities.
 * It validates field groups, sanitizes values based on field types, and
 * saves them to the appropriate WordPress meta tables.
 *
 * @since 1.0.0
 */
class SaveFieldsUseCase
{

  /**
   * Initialize the save fields use case.
   *
   * @since 1.0.0
   * @param User|Attachment|Term|Post $entity The entity instance to save fields for.
   * @param array $field_groups Array of field group entities to process.
   */
  public function __construct(
    protected User|Attachment|Term|Post $entity,
    protected array $field_groups = [],
  ) {}

  /**
   * Save post field values for the configured entity.
   *
   * Processes submitted field values from POST data, validates them against
   * field groups, sanitizes based on field type, and saves to WordPress meta.
   * Only processes fields that belong to valid field groups for the entity.
   *
   * @since 1.0.0
   * @return void
   */
  public function execute(): void
  {
    # Retrieve all submitted field values from POST data
    $submitted_fields = $this->getSubmittedFields();

    # Early return if essential data is missing
    if (!$this->entity || empty($submitted_fields) || empty($this->field_groups)) {
      return;
    }

    # Process each field group
    foreach ($this->field_groups as $field_group) {

      # Skip if field group has no content/fields
      if (!$field_group->getContent()) {
        continue;
      }

      # Process each field in the group
      foreach ($field_group->getFields() as $field) {
        # Skip if field wasn't submitted
        if (!array_key_exists($field->getName(), $submitted_fields)) {
          continue;
        }

        # Determine value type and sanitization
        $meta_service = new MetaService($field);
        $sanitize_callback = $meta_service->getSanitizeCallback();

        # Get the raw submitted value
        $raw_value = $submitted_fields[$field->getName()];

        # Convert non-array values to string for consistent processing
        if (!is_array($raw_value)) {
          $raw_value = (string) $raw_value;
        }

        # Sanitize the value using the appropriate callback for the field type
        $value = is_callable($sanitize_callback)
          ? call_user_func($sanitize_callback, $raw_value)
          : $raw_value;

        if ($this->entity instanceof User) {
          update_user_meta($this->entity->getId(), $field->getName(), $value);
        } else if ($this->entity instanceof Attachment) {
          update_post_meta($this->entity->getId(), $field->getName(), $value);
        } else if ($this->entity instanceof Term) {
          update_term_meta($this->entity->getId(), $field->getName(), $value);
        } else if ($this->entity instanceof Post) {
          update_post_meta($this->entity->getId(), $field->getName(), $value);
        }
      }
    }
  }

  /**
   * Retrieve submitted field values from prefixed POST variables.
   *
   * Reads every POST key prefixed with `modern_fields_` and strips the prefix
   * to map them back to the registered field names. This method handles the
   * extraction of field values that were submitted with the naming convention
   * used by the frontend form fields.
   *
   * @since 1.0.0
   * @return array<string, mixed> Associative array of field names to their submitted values.
   */
  protected function getSubmittedFields(): array
  {
    # Return empty array if no POST data exists
    if (empty($_POST)) {
      return [];
    }

    $submitted_fields = [];
    $post_data = wp_unslash($_POST);

    foreach ($post_data as $key => $value) {
      # Only process keys that are strings and start with the field prefix
      if (!is_string($key) || !str_starts_with($key, 'modern_fields_')) {
        continue;
      }

      # Extract the actual field name by removing the prefix
      $field_name = substr($key, strlen('modern_fields_'));

      # Skip if field name is empty after prefix removal
      if (!$field_name) {
        continue;
      }

      # Map the field name to its submitted value
      $submitted_fields[$field_name] = $value;
    }

    return $submitted_fields;
  }
}
