<?php

namespace ModernFields\FieldGroups\Hooks;

use ModernFields\FieldGroups\Entities\Media;
use ModernFields\WordPress\Interfaces\Hooks;

use WP_Block;

defined('ABSPATH') || exit;

/**
 * Registers WordPress block binding sources for Modern Fields.
 *
 * This class provides binding sources that allow WordPress blocks to bind
 * their attributes to Modern Fields field values.
 * 
 * @since 1.0.0
 */
class BindingSources implements Hooks
{
  /**
   * Registers WordPress hooks for binding sources.
   *
   * @return void
   * @since 1.0.0
   */
  public function registerHooks(): void
  {
    add_action('init', [$this, 'registerBindingSources']);
  }

  /**
   * Registers block binding sources for Modern Fields.
   *
   * Registers two binding sources:
   * - modern-fields/field: For general field values
   * - modern-fields/field-image: For image field attributes (id, url, alt, caption)
   *
   * @return void
   * @since 1.0.0
   */
  public function registerBindingSources()
  {
    # Main Modern Fields field binding source
    register_block_bindings_source(
      "modern-fields/field",
      [
        "label" => __("Modern Fields value", "modern-fields"),
        "get_value_callback" => [$this, "DisplayFieldValue"],
        # Receive the looped/queried post so the post context resolves per item
        "uses_context" => ["postId", "postType"],
      ]
    );

    # Core Post Meta binding source
    register_block_bindings_source(
      "modern-fields/field-image",
      [
        "label" => __("Modern Fields image", "modern-fields"),
        "get_value_callback" => [$this, "DisplayImageFieldValue"],
        "uses_context" => ["postId", "postType"],
      ]
    );
  }

  /**
   * Callback for modern-fields/field binding source.
   *
   * Retrieves a Modern Fields field value based on the provided source arguments.
   *
   * For the post context, the looped/queried post is read from the block
   * context (postId) so the value resolves per item inside a Query Loop or a
   * template, falling back to the current post when no context is provided.
   *
   * @param array<string, mixed> $source_args Source arguments containing:
   *                                          - 'key' (string, required): The field key/name
   *                                          - 'context' (string|null, optional): The context for the field
   *                                          - 'type' (string, optional): 'time' expands "HH:MM" values to a full datetime
   * @param WP_Block|null $block_instance The block instance (carries postId context)
   * @return string The field value as a string, or empty string if not found
   * @since 1.0.0
   */
  public function DisplayFieldValue($source_args, $block_instance = null, $attribute_name = ''): string
  {
    $context = $this->resolveContext($source_args, $block_instance);
    $value = (string) ModernFields()->fields->getField($source_args['key'] ?? '', $context);

    if (($source_args['type'] ?? '') === 'time') {
      return $this->expandTimeValue($value);
    }

    return $value;
  }

  /**
   * Expands a stored "HH:MM" time value into a full ISO 8601 datetime.
   *
   * core/post-date runs its bound datetime through strtotime() (UTC) then
   * wp_date() (site timezone): a bare time string would come out shifted by
   * the site offset. Pinning a fixed date and the site timezone keeps the
   * rendered hour identical to the stored one; the date part never shows
   * because the block is created with a time-only format.
   *
   * @param string $value The stored field value.
   * @return string An ISO 8601 datetime, or the raw value when not a time.
   * @since 1.3.0
   */
  protected function expandTimeValue(string $value): string
  {
    if (!preg_match('/^\d{1,2}:\d{2}(:\d{2})?$/', $value)) {
      return $value;
    }

    $datetime = date_create('1970-01-01 ' . $value, wp_timezone());

    return $datetime ? $datetime->format('c') : $value;
  }

  /**
   * Resolves the context passed to getField().
   *
   * For the post context, the looped/queried post is read from the block
   * context (postId) so the value resolves per item inside a Query Loop or a
   * template, falling back to the declared context (resolved against the current
   * post by getField) when no block context is available.
   *
   * @param array<string, mixed> $source_args Source arguments (key, context).
   * @param WP_Block|null $block_instance The block instance carrying postId context.
   * @return mixed The post ID (int) for the post context, or the raw context.
   * @since 1.0.0
   */
  protected function resolveContext($source_args, $block_instance)
  {
    $context = $source_args['context'] ?? null;

    if (($context === null || $context === 'post') && $block_instance instanceof WP_Block) {
      $post_id = $block_instance->context['postId'] ?? null;

      if ($post_id) {
        return (int) $post_id;
      }
    }

    return $context;
  }

  /**
   * Callback for modern-fields/field-image binding source.
   *
   * Retrieves image field attributes (id, url, alt, caption) based on the field key.
   *
   * @param array<string, mixed> $source_args Source arguments containing:
   *                                          - 'key' (string, required): The field key/name
   * @param WP_Block|null $block_instance The block instance (unused in this implementation)
   * @param string $attribute_name The attribute name to retrieve: 'id', 'url', 'alt', or 'caption'
   * @return string The requested image attribute value, or empty string if not found
   * @since 1.0.0
   */
  public function DisplayImageFieldValue($source_args, $block_instance, $attribute_name): string
  {
    $meta_name = $source_args['key'];
    $context = $this->resolveContext($source_args, $block_instance);

    $image_id = ModernFields()->fields->getField($meta_name, $context);
    $post = get_post($image_id);

    if (!$post) {
      return "";
    }

    $image = new Media($post);

    if ($attribute_name === "id") {
      return $image->getId();
    }

    if ($attribute_name === "url") {
      return $image->getUrl();
    }

    if ($attribute_name === "alt") {
      return $image->getAlt();
    }

    if ($attribute_name === "caption") {
      return $image->getCaption();
    }

    return "";
  }
}
