<?php

namespace WHOOP\Entities\Elements\Traits;

use WHOOP\Entities\Attribute;
use WHOOP\Entities\Classlist;
use WHOOP\Entities\Dataset;

/**
 * Shared traits for WHOOP HTML element objects
 * @since 1.0.0
 */
trait ElementTraits
{
  protected static array $defaultAttributes = [];

  protected string $id;

  /**
   * @var Attribute[]
   */
  protected array $attributes;
  protected Dataset $dataset;
  protected Classlist $classlist;

  /**
   * Initialize the properties and attributes of an element
   *
   * This method is called in the Element constructor.
   *
   * @since 1.0.0
   */
  protected function initProperties(): void
  {
    $this->attributes = [];
    $this->dataset = new Dataset();
    $this->classlist = new Classlist();

    if (static::$defaultAttributes) {
      foreach (static::$defaultAttributes as $attribute => $attributeValue) {
        $this->setAttribute($attribute, $attributeValue);
      }
    }
  }

  /**
   * Set the ID for the element, including the "id" attribute
   * @since 1.0.0
   */
  public function setId(string $id): static
  {
    $this->id = $id;

    $this->setAttribute('id', $id);

    return $this;
  }

  /**
   * Get the element ID, which will be the same as the "id" attribute
   * @since 1.0.0
   */
  public function getId(): string
  {
    return $this->id;
  }

  /**
   * Get an Attribute object for the element
   *
   * Data attributes ("data-...") are handled in the element's "dataset" and can
   * be accessed using self::getDataValue().
   *
   * The class attribute is handled by the element's "classlist" and can be read
   * using self::getClassString().
   *
   * @since 1.0.0
   */
  public function getAttribute(string $key): Attribute
  {
    return $this->attributes[$key];
  }

  /**
   * Get the value of an Attribute for the element
   *
   * Data attributes ("data-...") are handled in the element's "dataset" and can
   * be accessed using self::getDataValue().
   *
   * The class attribute is handled by the element's "classlist" and can be read
   * using self::getClassString().
   *
   * @since 1.0.0
   */
  public function getAttributeValue(string $key): mixed
  {
    if (!isset($this->attributes[$key])) {
      return null;
    }

    return $this->attributes[$key]->getValue();
  }

  /**
   * Set an attribute for the element
   * @since 1.0.0
   */
  public function setAttribute(string $key, mixed $value, ?string $quote = null): static
  {
    if ('class' === $key) {
      return $this->addClass($value);
    } elseif (strpos($key, 'data-') === 0) {
      return $this->setDataValue(substr($key, 5), $value);
    }

    $this->attributes[$key] = new Attribute($key, $value, $quote);

    return $this;
  }

  /**
   * Set multiple attributes for the element, provided in an array of key/value pairs
   * @since 1.0.0
   */
  public function setAttributes(array $attributes): static
  {
    foreach ($attributes as $key => $value) {
      $this->setAttribute($key, $value);
    }

    return $this;
  }

  /**
   * Remove an attribute for the element
   * @since 1.0.0
   */
  public function unsetAttribute(string $key): static
  {
    unset($this->attributes[$key]);

    return $this;
  }

  /**
   * Get all of the attributes as an HTML string
   * @since 1.0.0
   */
  public function getAttributesString(): string
  {
    $attributesStrings = [];

    if (!empty($this->attributes)) {
      $attributesStrings[] = join(' ', $this->attributes);
    }

    if ($dataset = (string) $this->dataset) {
      $attributesStrings[] = $dataset;
    }

    if ($classlist = (string) $this->classlist) {
      $attributesStrings[] = $classlist;
    }

    return join(" ", $attributesStrings);
  }

  /**
   * Set a "data-" value for the element
   *
   * The "data-" prefix is not required and will be added before rendering.
   *
   * @since 1.0.0
   */
  public function setDataValue(string $key, mixed $value): static
  {
    $this->dataset->setDataValue($key, $value);

    return $this;
  }

  /**
   * Get the value of a "data-" attribute
   * @since 1.0.0
   */
  public function getDataValue(string $key): mixed
  {
    return $this->dataset->getDataValue($key);
  }

  /**
   * Add a class to the element
   * @since 1.0.0
   */
  public function addClass(string $class): static
  {
    $this->classlist->addClass($class);

    return $this;
  }

  /**
   * Remove a class from the element
   * @since 1.0.0
   */
  public function removeClass(string $class): static
  {
    $this->classlist->removeClass($class);

    return $this;
  }

  /**
   * Get the complete list of classes for the element as a string
   * @since 1.0.0
   */
  public function getClassesString(): string
  {
    return $this->classlist->getClassesString();
  }
}
