<?php

namespace ModernFields\FieldGroups\Hooks;

use ModernFields\WordPress\Interfaces\Hooks;

use WP_Block_Type_Registry;
use WP_Post;

defined('ABSPATH') || exit;

/**
 * Indexes field blocks from a field group into individual mf_field posts.
 *
 * Parses blocks from the group content, persists each field as a mf_field
 * post, and keeps the fields_count meta in sync — all within a single
 * save_post pass.
 *
 * @since 0.7.2
 */
class FieldIndexer implements Hooks
{
  /**
   * @since 0.7.2
   */
  public function registerHooks(): void
  {
    add_action('save_post', [$this, 'indexFields'], 10, 2);
    add_action('init', [$this, 'registerMetaField']);
  }

  /**
   * Indexes each field in a field group as an individual mf_field post.
   *
   * For each modern-fields block found in the group content, block registry
   * defaults are merged with the saved block attributes so that fields with
   * untouched default values are still fully persisted. All previous mf_field
   * posts for the group are deleted before the new set is inserted, keeping
   * the index in sync. The fields_count meta is updated at the end to reflect
   * the actual number of indexed fields.
   *
   * @since 0.6
   * @param int     $post_id The post ID being saved
   * @param WP_Post $post    The post object being saved
   */
  public function indexFields(int $post_id, WP_Post $post): void
  {
    if ($post->post_type !== MODERN_FIELDS_CPT_SLUG) {
      return;
    }

    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
      return;
    }

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

    $existing = get_posts([
      'post_type'      => MODERN_FIELDS_FIELD_CPT_SLUG,
      'posts_per_page' => -1,
      'post_status'    => 'any',
      'post_parent'    => $post_id,
      'fields'         => 'ids',
    ]);

    foreach ($existing as $field_post_id) {
      wp_delete_post($field_post_id, true);
    }

    $content  = get_post_field('post_content', $post_id);
    $blocks   = parse_blocks($content);
    $registry = WP_Block_Type_Registry::get_instance();

    $field_blocks = [];
    $extract = function ($blocks) use (&$extract, &$field_blocks, $registry) {
      foreach ($blocks as $block) {
        $block_name = $block['blockName'] ?? '';
        $is_mf_block = isset($block['attrs']['name']) && str_starts_with($block_name, 'modern-fields/');

        if ($is_mf_block && $block_name === 'modern-fields/repeater') {
          # Build a flat map name → { blockName, attrs } for the JS renderer.
          # Registry defaults are merged so the frontend always has complete attrs.
          # Sub-fields are NOT indexed as standalone mf_field posts.
          $extract_fields = null;
          $extract_fields = function (array $inner_blocks) use (&$extract_fields, $registry): array {
            $fields = [];
            foreach ($inner_blocks as $inner) {
              $inner_name = $inner['blockName'] ?? '';
              if (isset($inner['attrs']['name']) && str_starts_with($inner_name, 'modern-fields/')) {
                $field_name       = $inner['attrs']['name'];
                $inner_block_type = $registry->get_registered($inner_name);
                $inner_defaults   = [];
                if ($inner_block_type) {
                  foreach ($inner_block_type->attributes as $k => $attr) {
                    if (array_key_exists('default', $attr)) {
                      $inner_defaults[$k] = $attr['default'];
                    }
                  }
                }
                $inner_attrs = array_merge($inner_defaults, $inner['attrs']);
                if (empty($inner_attrs['label'])) {
                  $inner_attrs['label'] = $inner_attrs['defaultLabel'] ?? ($inner_block_type ? $inner_block_type->title : '');
                }
                $fields[$field_name] = [
                  'blockName' => $inner_name,
                  'attrs'     => $inner_attrs,
                ];
              }
              if (!empty($inner['innerBlocks'])) {
                $fields = array_merge($fields, $extract_fields($inner['innerBlocks']));
              }
            }
            return $fields;
          };

          # Build REST schema properties from the already-computed $extract_fields map.
          $build_properties = function (array $fields) use ($registry): array {
            $properties = [];
            foreach ($fields as $name => $info) {
              $inner_block_type  = $registry->get_registered($info['blockName']);
              $properties[$name] = [
                'type' => $inner_block_type
                  ? ($inner_block_type->attributes['defaultValue']['type'] ?? 'string')
                  : 'string',
              ];
            }
            return $properties;
          };

          # render_block() on layout blocks (columns, group…) returns fully styled HTML
          # including all attrs (width, className, style…). On modern-fields/* blocks it
          # returns the static save() placeholder <div id="mf-field-{name}"> which the JS
          # renderer replaces with a React portal.
          $sub_html = implode('', array_map('render_block', $block['innerBlocks'] ?? []));

          $sub_fields = $extract_fields($block['innerBlocks'] ?? []);

          $block['attrs']['subHtml']   = $sub_html;
          $block['attrs']['subFields'] = $sub_fields;
          $block['attrs']['mfMeta']    = [
            'schema' => [
              'type'       => 'object',
              'properties' => $build_properties($sub_fields),
            ],
          ];
          $field_blocks[] = $block;
          # Don't recurse — innerBlocks are the sub-field config, not top-level fields
        } elseif ($is_mf_block) {
          $field_blocks[] = $block;
          if (!empty($block['innerBlocks'])) {
            $extract($block['innerBlocks']);
          }
        } else {
          if (!empty($block['innerBlocks'])) {
            $extract($block['innerBlocks']);
          }
        }
      }
    };

    $extract($blocks);

    $indexed = 0;

    foreach ($field_blocks as $index => $block) {
      $block_type = $registry->get_registered($block['blockName']);

      $defaults = [];
      if ($block_type) {
        foreach ($block_type->attributes as $key => $attr) {
          if (array_key_exists('default', $attr)) {
            $defaults[$key] = $attr['default'];
          }
        }
      }

      # Saved attrs take priority over block defaults
      $attrs = array_merge($defaults, $block['attrs']);

      # label has no declared default in JS so Gutenberg never writes it when
      # untouched. Fall back to defaultLabel (set by the block edit component)
      # then to the block title from the PHP registry.
      if (empty($attrs['label'])) {
        $attrs['label'] = $attrs['defaultLabel'] ?? ($block_type ? $block_type->title : '');
      }

      $field_post_id = wp_insert_post([
        'post_type'    => MODERN_FIELDS_FIELD_CPT_SLUG,
        'post_title'   => $block['attrs']['name'],
        'post_content' => maybe_serialize([
          'blockName'  => $block['blockName'],
          'type'       => $block_type ? ($block_type->attributes['defaultValue']['type'] ?? 'string') : 'string',
          'attributes' => $attrs,
        ]),
        'post_status'  => 'publish',
        'post_parent'  => $post_id,
        'menu_order'   => $index,
      ], true);

      if (!is_wp_error($field_post_id)) {
        $indexed++;
      }
    }

    update_post_meta($post_id, 'fields_count', $indexed);
  }

  /**
   * Registers the fields_count meta field for REST API exposure.
   *
   * @since 1.0.0
   */
  public function registerMetaField(): void
  {
    register_post_meta(MODERN_FIELDS_CPT_SLUG, 'fields_count', [
      'type'         => 'integer',
      'description'  => __('The number of modern-fields blocks in the field group', 'modern-fields'),
      'single'       => true,
      'show_in_rest' => true,
      'default'      => 0,
    ]);
  }
}
