<?php

namespace ModernFields\FieldGroups\Locations\UseCases;

use ModernFields\FieldGroups\Repositories\FieldRepository;

use WP_Theme_JSON;
use WP_Theme_JSON_Resolver;
use WP_Block_Type_Registry;

/**
 * Use case for retrieving editor styles for field groups.
 * 
 * This class handles the extraction and compilation of CSS styles needed to render
 * field groups in various contexts (post editor, term editor, user editor, etc.).
 * It combines global styles, block-specific styles, and layout styles.
 * 
 * @since 1.0.0
 */
class GetEditorStylesUseCase
{
  /**
   * Constructor.
   *
   * @since 1.0.0
   * @param array $groups Array of field group entities to extract styles from.
   * @param bool $scope Whether to wrap the CSS in @scope server-side. Pass false
   *                    to return raw CSS when scoping is delegated to the client
   *                    via wp.blockEditor.transformStyles.
   */
  public function __construct(
    protected array $groups = [],
    protected bool $scope = true,
  ) {}

  /**
   * Get all editor styles for the field groups.
   * 
   * Compiles a complete set of CSS styles including:
   * - Global theme styles (from WordPress and plugin theme.json)
   * - Block-specific styles for all blocks used in the groups
   * - Auto-generated layout styles (wp-container-* classes)
   * 
   * @since 1.0.0
   * @return string Combined CSS content of all styles.
   */
  public function execute(): string
  {
    $styles = [];

    # Global styles
    $styles['mf-global-styles'] = $this->getGlobalStyles();

    # Get all blocks used across all groups
    foreach ($this->groups as $group) {
      # Block styles
      $block_names = $this->getBlockNamesFromContent($group->getRawContent());

      foreach ($block_names as $block_name) {
        if (!isset($styles['mf-block-' . sanitize_title($block_name)])) {
          $styles['mf-block-' . sanitize_title($block_name)] = $this->getBlockStyles($block_name);
        }
      }
    }

    # Get auto-generated layout styles (wp-container-* classes)
    $layout_styles = $this->getLayoutStyles();
    if (!empty($layout_styles)) {
      $styles['mf-layout-styles'] = $layout_styles;
    }

    # Return the combined styles
    return implode("\n", array_filter($styles));
  }

  /**
   * Wraps CSS with @scope to prevent style leakage.
   * 
   * All styles are scoped to .modern-fields-content-area to ensure they only
   * apply within field group containers and don't affect the WordPress admin UI.
   * 
   * @since 1.0.0
   * @param string $css The CSS content to wrap with scoping.
   * @return string The scoped CSS, or empty string if input is empty.
   */
  protected function applyScopeWrapper(string $css): string
  {
    if (empty($css)) {
      return '';
    }

    # When scoping is delegated to the client (transformStyles), return the CSS
    # untouched so its original root selectors (:root, body) are preserved and
    # remapped correctly to the render container by postcss-prefix-selector.
    if (!$this->scope) {
      return $css;
    }

    # Replace :root with :scope for proper scoping behavior
    $scoped_css = str_replace(':root', ':scope', $css);

    # Wrap with @scope directive
    return "@scope (.modern-fields-content-area) { " . $scoped_css . " }";
  }

  /**
   * Extract unique block names from block content.
   * Gets all blocks (fields + standard blocks).
   * 
   * Recursively parses the block content to find all block names used,
   * including blocks nested within inner blocks.
   * 
   * @since 1.0.0
   * @param string $content The serialized block content to parse.
   * @return array Array of unique block names found in the content.
   */
  protected function getBlockNamesFromContent($content): array
  {
    $block_names = [];
    $blocks = parse_blocks($content);

    # Recursively extract unique block names from blocks and their inner blocks
    $extract_block_names = function ($blocks) use (&$extract_block_names, &$block_names) {
      foreach ($blocks as $block) {
        if (!empty($block['blockName'])) {
          $block_names[] = $block['blockName'];
        }

        if (!empty($block['innerBlocks'])) {
          $extract_block_names($block['innerBlocks']);
        }
      }
    };

    $extract_block_names($blocks);

    # Get unique block names
    return array_unique($block_names);
  }

  /**
   * Extract CSS content from registered style handle(s).
   * 
   * Retrieves the CSS file content from WordPress's registered style handles.
   * Handles both single handles and arrays of handles.
   * 
   * @since 1.0.0
   * @param string|array|null $handles Style handle or array of handles registered in WordPress.
   * @return string Combined CSS content from all handles, or empty string if none found.
   */
  protected function getStyleContentFromHandles($handles): string
  {
    if (empty($handles)) {
      return '';
    }

    # Normalize to array
    $handles = (array) $handles;
    $css_content = [];

    foreach ($handles as $handle) {
      $css = $this->getStyleContentFromHandle($handle);
      if (!empty($css)) {
        $css_content[] = $css;
      }
    }

    return implode("\n", $css_content);
  }

  /**
   * Extract CSS content from a single registered style handle.
   * 
   * Accesses WordPress's style registry to get the file path for a registered
   * style handle and reads the CSS content. Handles RTL styles and minification
   * based on WordPress settings.
   * 
   * @since 1.0.0
   * @param string $handle Style handle registered in WordPress.
   * @return string CSS content from the handle, or empty string if not found.
   */
  protected function getStyleContentFromHandle(string $handle): string
  {
    # Access WordPress styles registry
    $wp_styles = wp_styles();

    # Check if handle is registered
    if (!isset($wp_styles->registered[$handle])) {
      return '';
    }

    $style_obj = $wp_styles->registered[$handle];

    # Get the source URL or path
    $src = $style_obj->src;

    if (empty($src)) {
      return '';
    }

    # Convert URL to file path
    $file_path = $this->convertUrlToFilePath($src);

    # Read and return the CSS content
    if (!empty($file_path) && file_exists($file_path)) {
      return file_get_contents($file_path);
    }

    return '';
  }

  /**
   * Convert a style URL to a file system path.
   * 
   * Converts WordPress asset URLs to their corresponding file system paths
   * for reading CSS content.
   * 
   * @since 1.0.0
   * @param string $url The URL to convert.
   * @return string|null The file system path, or null if conversion fails.
   */
  protected function convertUrlToFilePath(string $url): ?string
  {
    # Handle relative URLs
    if (strpos($url, '//') === false) {
      # Relative URL - try to resolve it
      if (strpos($url, '/wp-includes/') !== false) {
        return ABSPATH . ltrim($url, '/');
      }
      if (strpos($url, '/wp-content/') !== false) {
        return ABSPATH . ltrim($url, '/');
      }
    }

    # Handle absolute URLs - convert to file path
    $wp_content_url = content_url();
    $wp_includes_url = includes_url();

    # Check if URL is in wp-content
    if (strpos($url, $wp_content_url) === 0) {
      return str_replace($wp_content_url, WP_CONTENT_DIR, $url);
    }

    # Check if URL is in wp-includes
    if (strpos($url, $wp_includes_url) === 0) {
      return str_replace($wp_includes_url, ABSPATH . WPINC . '/', $url);
    }

    # Try to resolve from site URL
    $site_url = site_url();
    if (strpos($url, $site_url) === 0) {
      return str_replace($site_url, ABSPATH, $url);
    }

    return null;
  }

  /**
   * Get the CSS styles for a specific block.
   * 
   * Uses WordPress's native block registry to access registered style handles,
   * then retrieves the CSS content via the style queue system. This respects
   * WordPress's block registration system, handles RTL automatically, and works
   * with any block registered via block.json.
   * 
   * Falls back to direct file reading if block is not properly registered.
   * 
   * @since 1.0.0
   * @param string $block_name The full block name (e.g., 'core/paragraph', 'modern-fields/text').
   * @return string|null The scoped CSS content of the block, or null if not found.
   */
  protected function getBlockStyles($block_name)
  {
    # Try to get block from WordPress registry (native WordPress way)
    $registry = WP_Block_Type_Registry::get_instance();
    $block_type = $registry->get_registered($block_name);

    if ($block_type && !empty($block_type->style)) {
      # Get CSS from registered style handles
      $css = $this->getStyleContentFromHandles($block_type->style);

      if (!empty($css)) {
        # Apply scoping to prevent style leakage
        return $this->applyScopeWrapper($css);
      }
    }

    # Fallback: Try direct file reading for blocks that aren't properly registered
    $css = $this->getBlockStylesFallback($block_name);

    if (!empty($css)) {
      # Apply scoping to prevent style leakage
      return $this->applyScopeWrapper($css);
    }

    return null;
  }

  /**
   * Fallback method to load block styles directly from files.
   * 
   * Used when blocks are not properly registered in WordPress's block registry.
   * This ensures backward compatibility and handles edge cases.
   * 
   * @since 1.0.0
   * @param string $block_name The full block name (e.g., 'core/paragraph', 'modern-fields/text').
   * @return string|null The CSS content of the block, or null if not found.
   */
  protected function getBlockStylesFallback($block_name): ?string
  {
    # Handle core blocks
    if (is_string($block_name) && str_starts_with($block_name, 'core/')) {
      $rtl = is_rtl() ? '-rtl' : '';
      $file_path = ABSPATH . WPINC . '/blocks/' . substr($block_name, 5) . '/style' . $rtl . '.min.css';

      if (file_exists($file_path)) {
        return file_get_contents($file_path);
      }
    }

    # Handle modern-fields blocks
    if (is_string($block_name) && str_starts_with($block_name, 'modern-fields/')) {
      $file_path = MODERN_FIELDS_PLUGIN_DIR . 'build/blocks/block-types/' . substr($block_name, 14) . '/index.css';

      if (file_exists($file_path)) {
        return file_get_contents($file_path);
      }
    }

    return null;
  }

  /**
   * Get global styles from WordPress theme and plugin theme.json.
   * 
   * Merges WordPress core theme JSON settings with the plugin's custom theme.json
   * to create a complete set of global styles. The styles are scoped to the
   * .modern-fields-content-area class to avoid conflicts with the site's theme.
   * 
   * @since 1.0.0
   * @return string The scoped global CSS styles.
   */
  protected function getGlobalStyles(): string
  {
    # Start with WordPress core defaults
    $theme_json = WP_Theme_JSON_Resolver::get_merged_data();

    # Load and merge plugin's theme.json
    $plugin_theme_json_data = wp_json_file_decode(MODERN_FIELDS_PLUGIN_DIR . 'assets/json/theme.json', ['associative' => true]);

    if ($plugin_theme_json_data) {
      $plugin_theme_json = new WP_Theme_JSON($plugin_theme_json_data, 'theme');
      $theme_json->merge($plugin_theme_json);
    }

    $styles = $theme_json->get_stylesheet();

    # Apply scoping to prevent style leakage
    return $this->applyScopeWrapper($styles);
  }

  /**
   * Get auto-generated layout styles from WordPress Style Engine.
   * 
   * This method retrieves the dynamically generated CSS for layout support features
   * like block gaps, flex/grid layouts, etc. These styles include the auto-generated
   * wp-container-* classes that WordPress creates when blocks have non-default layout
   * attributes (e.g., custom gaps, orientation, widths).
   * 
   * Note: This requires WordPress 6.1+ where the Style Engine was introduced.
   * 
   * @since 1.0.0
   * @return string The scoped auto-generated layout CSS including wp-container-* classes
   */
  protected function getLayoutStyles(): string
  {
    # Do NOT optimize: the optimizer merges the identical blockGap resets
    # (`.wp-container-* > * { margin-block-start: 0 }`) into a single rule and
    # re-appends it at the end of the stylesheet, after the per-container gap
    # rules (`> * + * { margin-block-start: <gap> }`). Since both have equal
    # specificity, the reset would then win and cancel every block gap. Keeping
    # the insertion order (reset before gap, per container) preserves the gaps.
    $layout_styles = wp_style_engine_get_stylesheet_from_context('block-supports', [
      'optimize' => false,
      'prettify' => false,
    ]);

    # Apply scoping to prevent style leakage
    return $this->applyScopeWrapper($layout_styles);
  }
}
