<?php
declare(strict_types=1);

namespace WP_AUX\Entities\AuxFramework\Partials;

use WHOOP\Entities\Elements\Img;
use WP_AUX\Services\AuxFramework\ElementClassesService;
use WP_AUX\Services\HtmlService;
use WHOOP\Entities\Elements\Div;

/**
 * AUX Card partial for use everywhere cards are implemented.
 * @since 1.0.0
 * @link https://aggieux.tamu.edu/?path=/docs/components-cards-card-groups-single-cards-about-cards--docs
 */
class CardEntity
{
  protected ?string $id = null;
  protected ?string $type = null;
  protected array $styles = [];
  protected array $classes = [];
  protected ?string $heading = null;
  protected ?string $superheader = null;
  protected ?string $subheader = null;
  protected ?string $headerPrepend = null;
  protected array $headerStyles = [];
  protected ?string $content = null;
  protected string $ctaType = 'none';
  protected string|array|null $cta = null;
  protected array $ctas = [];
  protected ?string $icon = null;
  protected ?ImageEntity $image = null;
  protected bool $hasIntro = false;
  protected bool $isFeature = false;
  protected ?int $headingLevel = null;
  /**
   * @var callable[]
   */
  protected array $imageFilters = [];

  public function __construct(?string $heading, ?string $content = null) {
    $this->heading = $heading;
    $this->content = $content;
  }

  public function setType(?string $type): self
  {
    $this->type = $type;
    return $this;
  }

  /**
   * Set the ID of the card
   *
   * This ID will also be the ID of the card's wrapping container
   *
   * @since 1.0.0
   */
  public function setId(?string $id): self
  {
    $this->id = $id;
    return $this;
  }

  /**
   * Set if the card is part of a group or collection that has an intro
   * @since 1.0.0
   */
  public function setHasIntro(bool $hasIntro): self
  {
    $this->hasIntro = $hasIntro;
    return $this;
  }

  /**
   * Set the superheader for the heading group for the card
   * @since 1.0.0
   */
  public function setSuperheader(?string $superheader): self
  {
    $this->superheader = $superheader;
    return $this;
  }

  /**
   * Set the subheader for the heading group for the card
   * @since 1.0.0
   */
  public function setSubheader(?string $subheader): self
  {
    $this->subheader = $subheader;
    return $this;
  }

  /**
   * Set the prepend content for the heading group for the card
   * @since 1.0.0
   */
  public function setHeaderPrepend(?string $headerPrepend): self
  {
    $this->headerPrepend = $headerPrepend;
    return $this;
  }

  /**
   * Set the type of CTA(s) for the card
   * @since 1.0.0
   */
  public function setCtaType(?string $ctaType): self
  {
    if (empty($ctaType)) {
      $ctaType = 'none';
    }

    $this->ctaType = $ctaType;

    return $this;
  }

  /**
   * Set the single CTA for the card
   * @since 1.0.0
   */
  public function setCta(array|string|null $cta): self
  {
    if (is_string($cta) && !empty($cta)) {
      $cta = [
        'url' => $cta,
      ];
    }

    if (is_array($cta)) {
      $this->cta = $cta;
    }

    return $this;
  }

  /**
   * Add a CTA to the list of CTAs for the card
   * @since 1.0.0
   */
  public function addCta(array|null|string $cta): self
  {
    if (is_string($cta) && !empty($cta)) {
      $cta = [
        'url' => $cta,
      ];
    }

    if (is_array($cta)) {
      $this->ctas[] = $cta;
    }

    return $this;
  }

  /**
   * Set the icon for the card
   * @since 1.0.0
   */
  public function setIcon(?string $icon): self
  {
    $this->icon = $icon;
    return $this;
  }

  /**
   * Set the image for the card
   *
   * If the image is a prepared ImageEntity, just use that. Otherwise, create
   * an ImageEntity and wrap it in "card__image"
   *
   * @since 1.0.0
   */
  public function setImage(ImageEntity|Img|string|array|null $image): self
  {
    if (empty($image)) {
      return $this;
    }

    if ($image instanceof ImageEntity) {
      $this->image = $image;
    } else {
      $this->image = ImageEntity::create($image)->wrap('card__image');
    }

    return $this;
  }

  /**
   * Get the ImageEntity for the card
   * @since 1.0.0
   */
  public function getImage(): ImageEntity
  {
    return $this->image;
  }

  /**
   * Flag the card as being featured
   * @since 1.0.0
   */
  public function setIsFeature(bool $isFeature): self
  {
    $this->isFeature = $isFeature;
    return $this;
  }

  /**
   * Set the heading level for the card's heading group
   *
   * If set, this will override the default heading level behavior.
   *
   * @param int $level The heading level (should be 2 or 3)
   * @since 1.0.2
   */
  public function setHeadingLevel(int $level): self
  {
    $this->headingLevel = $level;
    return $this;
  }

  /**
   * Add a style to the card
   *
   * Add multiple styles at once as separate arguments.
   *
   * @since 1.0.0
   */
  public function addStyle(string ...$style): self
  {
    foreach ($style as $styleName) {
      $this->styles[] = $styleName;
    }

    return $this;
  }

  /**
   * Add a class to the card's wrapping container
   *
   * Add multiple classes at once as separate arguments.
   *
   * @since 1.0.0
   */
  public function addClass(string ...$class): self
  {
    foreach ($class as $className) {
      $this->classes[] = $className;
    }

    return $this;
  }

  /**
   * Add a style to the card's header
   * @since 1.0.0
   */
  public function addHeaderStyle(string $style): self
  {
    $this->headerStyles[] = $style;
    return $this;
  }

  /**
   * Add a callable filter to be applied to card's ImageEntity before rendering
   * @since 1.0.0
   */
  public function addImageFilter(string|callable $filter, ?callable $filterFunction = null): self
  {
    if (!is_null($filterFunction)) {
      $this->imageFilters[$filter] = $filterFunction;
    } else {
      $this->imageFilters[] = $filter;
    }

    return $this;
  }

  /**
   * Remove a callable filter to be applied to card's ImageEntity before rendering
   * @since 1.0.0
   */
  public function removeImageFilter(string $filter): self
  {
    if (isset($this->imageFilters[$filter])) {
      unset($this->imageFilters[$filter]);
    }

    return $this;
  }

  public function __toString(): string
  {
    $cardClasses = ElementClassesService::buildClasses('card');

    $headingGroup = HeadingGroupEntity::create(
      $this->heading
    )
      ->setSuperheader($this->superheader)
      ->setSubheader($this->subheader)
      ->setPrepend($this->headerPrepend);

    $media = '';
    if ($this->image) {
      $media = clone $this->image;

      if ($this->imageFilters) {
        foreach ($this->imageFilters as $imageFilter) {
          call_user_func_array($imageFilter, [$media]);
        }
      }
    } elseif ($this->icon) {
      $media = IconEntity::create($this->icon);
    }

    switch ($this->type) {
      case 'standard':
        break;
      case 'inline':
        $cardClasses->addModifier($this->type);
        break;
      case 'sidebar':
        $cardClasses->addModifier('light');
        break;
      default:
        break;
    }

    if ($this->isFeature) {
      $headingGroup->setIsFeature(true);
    }

    if (!is_null($this->headingLevel)) {
      $headingGroup->setLevel($this->headingLevel);
    } elseif ($this->hasIntro) {
      // Default to h3 if there is a section intro
      $headingGroup->setLevel(3);
    }

    if (!empty($this->styles)) {
      foreach ($this->styles as $style) {
        $cardClasses->addModifier($style);
      }
    }

    if (!empty($this->headerStyles)) {
      foreach ($this->headerStyles as $style) {
        $headingGroup->addStyle($style);
      }
    }

    $content = $this->content;
    if (!HtmlService::hasHtml($this->content)) {
      $content = HtmlService::formattedValue($this->content, '<p>%s</p>');
    }

    $cardCta = '';
    $cardCtaUrl = '';
    $cardCtaTitle = '';
    switch ($this->ctaType) {
      case 'link':
        $cardCtaUrl = @$this->cta['url'];
        $cardCtaTitle = @$this->cta['title'];
        $cardCta = HtmlService::formattedValue(@$this->cta['url'], '<a href="%s" class="link--cta-leading">%s</a>', @$this->cta['title']);
        break;
      case 'linked-heading':
        $headingGroup->setLink($this->cta);
        break;
      case 'button':
        $cardCta = HtmlService::formattedValue(@$this->cta['url'], '<a href="%s" class="btn btn--primary-alt">%s</a>', @$this->cta['title']);
        break;
      case 'links':
        $cardCta = LinkListEntity::create();
        foreach ($this->ctas as $cta) {
          $cardCta->addLink($cta);
        }
        break;
      default:
        if ($this->cta) {
          $headingGroup->setLink($this->cta);
        }
    }

    if ('linked' === $this->type) {
      $string = <<<HTML
        <div class="linked-card">
          <a href="$cardCtaUrl" aria-label="$cardCtaTitle">
            <div class="link-arrow"><svg><use href="#aux_arrow-up-right"></use></svg></div>
            $media
            <div class="content-wrapper">
              $headingGroup
              $content
            </div>
          </a>
        </div>
      HTML;
    } else {
      if ($this->image && !empty($media) && $this->cta) {
        $media->setLink($this->cta);

        // If both image and heading are linked, add tabindex="-1" to image link. Otherwise the user interacts
        // with the same link twice if they are using a screen reader or keyboard navigation.
        // Heading is linked when ctaType is 'linked-heading' or in default case
        if ($this->ctaType !== 'link' && $this->ctaType !== 'button' && $this->ctaType !== 'links') {
          $media->addLinkFilter(function($link) {
            $link->setAttribute('tabindex', '-1');
          });
        }
      }

      $cardContent = <<<HTML
        $media
        <div class="card__content">
          $headingGroup
          $content
          $cardCta
        </div>
      HTML;

      if ($this->isFeature) {
        $cardClasses->addModifier('featured');
        $cardContent = HtmlService::formattedValue($cardContent, '<div class="featured-container">%s</div>');
      }

      $div = Div::create($cardContent, [
        'class' => $cardClasses
      ]);

      if (!empty($this->id)) {
        $div->setId($this->id);
      }

      if (!empty($this->classes)) {
        foreach ($this->classes as $class) {
          $div->addClass($class);
        }
      }

      $string = (string) $div;
    }

    return $string;
  }

  /**
   * Create a plain Card with main heading and content
   * @since 1.0.0
   */
  public static function create(?string $heading, ?string $content = null): static
  {
    $card = new static($heading, $content);

    return $card;
  }

  /**
   * Create a sidebar Card with main heading and content
   * @since 1.0.0
   */
  public static function createSidebar(?string $heading, ?string $content = null): static
  {
    $card = new static($heading, $content);
    $card->setType('sidebar');

    return $card;
  }

  /**
   * Create an inline Card with main heading and content
   * @since 1.0.0
   */
  public static function createInline(?string $heading, ?string $content = null): static
  {
    $card = new static($heading, $content);
    $card->setType('inline');

    return $card;
  }

  /**
   * Create a standard Card with main heading and content
   * @since 1.0.0
   */
  public static function createStandard(?string $heading, ?string $content = null): static
  {
    $card = new static($heading, $content);
    $card->setType('standard');

    return $card;
  }

  /**
   * PHP magic method that creates a new "cloned" image when the card is cloned
   *
   * Cloning the image when the card is cloned ensures that modifications to the
   * new card's image do not also get applied to the original instance of the card.
   *
   * @since 1.0.0
   */
  public function __clone() {
    if ($this->image) {
      $this->image = clone $this->image;
    }
  }
}
