<?php
declare(strict_types=1);

namespace WP_AUX\Entities;

/**
 * HEAD META tags collections
 *
 * Handles the creation and management of meta tags for various types.
 * Supports basic meta tag creation, additions within a group (e.g. Open Graph, Schema.org), and rendering of meta tags as HTML strings.
 * @since 1.0.0
 */
class MetaTagsEntity
{
  protected string $type = '';
  protected bool $addClasses = false;
  protected array $metaTags = [];

  public function __construct(string $type, bool $addClasses = false) {
    $this->type = $type;
    $this->addClasses = $addClasses;
  }

  /**
   * Sets a meta tag with the specified name, content, whether to add classes to the tag, and additional attributes.
   *
   * This method will overwrite any existing meta tags with the same name. To add another tag
   * with the same name, use the addTag() method.
   *
   * @param bool|null $addClass Optional parameter to specify if a class should be added.
   * @param array $attributes Additional attributes for the meta tag in an array of tag/property name => property value pairs.
   * @since 1.0.0
   */
  public function setTag(string $name, string|null $content, ?bool $addClass = null, array $attributes = []): static
  {
    // Do not set or output a meta tag when content is null or empty (after trim)
    if ($content === null || (is_string($content) && trim($content) === '')) {
      return $this;
    }

    $this->metaTags[$name] = [[
      'content' => $content,
      'addClass' => $addClass,
      'attributes' => $attributes,
    ]];

    return $this;
  }

  /**
   * Adds a meta tag with the specified name, content, whether to add classes to the tag, and additional attributes.
   *
   * This method will add to any existing meta tags with the same name. To overwrite any existing
   * meta tags with the same name, use the setTag() method.
   *
   * @param bool|null $addClass Optional parameter to specify if a class should be added.
   * @param array $attributes Additional attributes for the meta tag in an array of tag/property name => property value pairs.
   * @since 1.0.0
   */
  public function addTag(string $name, string $content, ?bool $addClass = null, array $attributes = []): static
  {
    $this->metaTags[$name][] = [
      'content' => $content,
      'addClass' => $addClass,
      'attributes' => $attributes,
    ];

    return $this;
  }

  /**
   * Add a grouped meta tag for "array"-ed meta tags.
   *
   * This method will add to any existing meta tags with the same name. To overwrite any existing
   * meta tags with the same name, use the setTag() method.
   *
   * @param string $name Name of the first and primary meta tag
   * @param string $content Content for the first and primary meta tag
   * @param array $group Array of sub-tags in name => value pairs (e.g. Open Graph, Schema.org)
   * @param bool|null $addClass Optional parameter to specify if a class should be added.
   * @param array $attributes Additional attributes for the meta tag in an array of tag/property name => property value pairs.
   * @since 1.0.0
   * @link https://ogp.me/#array
   */
  public function addGroupTag(string $name, string $content, array $group, ?bool $addClass = null, array $attributes = []): static
  {
    $this->metaTags[$name][] = [
      'content' => $content,
      'group' => $group,
      'addClass' => $addClass,
      'attributes' => $attributes,
    ];

    return $this;
  }

  /**
   * Render each meta tag as a string
   * @since 1.0.0
   */
  public function __toString(): string
  {
    $tags = [];
    $namespace = '';
    $classPrepend = '';
    $tagAttrName = 'property';
    $tagCommentName = 'Standard';

    switch ($this->type) {
      case 'og':
        $namespace = 'og:';
        $classPrepend = 'og-';
        $tagCommentName = 'Open Graph';
        break;
      case 'item':
        $tagAttrName = 'itemprop';
        $tagCommentName = 'Schema.org';
        break;
      case 'dublin-core':
        $namespace = 'DC.';
        $tagAttrName = 'name';
        $tagCommentName = 'Dublin Core';
        break;
    }

    // Loop through the meta tags and add them to the tags array
    foreach ($this->metaTags as $name => $metaTags) {
      foreach ($metaTags as $tag) {
        $attributes = [];

        // Add classes to the tag if the addClasses flag is true and the addClass property is true or not set
        if (($this->addClasses && $tag['addClass'] !== false) || $tag['addClass'] === true) {
          $attributes[] = 'class="tamu-meta tamu-meta-' . $classPrepend . str_replace('_', '-', $name) . '"';
        }

        // Add the attributes to the tag
        if ($tag['attributes']) {
          foreach ($tag['attributes'] as $attrName => $attrValue) {
            $attrValue = wp_strip_all_tags($attrValue);

            if (is_null($attrValue) || '' === $attrValue) {
              continue;
            }

            $attributes[] = sprintf('%s="%s"', $attrName, $attrValue);
          }
        }

        // The HTML description meta tag uses the name attribute instead of the property attribute
        $finalTagAttrName = $tagAttrName;
        if ($this->type === 'standard' && $name === 'description') {
          $finalTagAttrName = 'name';
        }

        // Add the tag to the tags array
        $tags[] = sprintf(
          '<meta %s="%s" content="%s" %s/>',
          $finalTagAttrName,
          $namespace.$name,
          wp_kses_post(wp_strip_all_tags($tag['content'])),
          join(' ', $attributes)
        );

        // If the tag has a group, loop through the group and add the tags to the tags array
        if (!empty($tag['group'])) {
          foreach ($tag['group'] as $itemName => $itemValue) {
            $tags[] = sprintf(
              '<meta %s="%s" content="%s"/>',
              $finalTagAttrName,
              $namespace.$name . ':' . $itemName,
              wp_kses_post(wp_strip_all_tags($itemValue))
            );
          }
        }
      }
    }

    return "\n\n<!-- TAMU {$tagCommentName} Metatags -->\n" . join("\n", $tags);
  }

  /**
   * Create a new meta tags entity with the specified type and the flag to add or not add classes to each tag by default
   * @since 1.0.0
   */
  public static function create(string $type, bool $addClasses = false): static
  {
    return new static($type, $addClasses);
  }
}
