<?php // phpcs:ignore SlevomatCodingStandard.TypeHints.DeclareStrictTypes.DeclareStrictTypesMissing

namespace MailPoet\WooCommerce\TransactionalEmails;

if (!defined('ABSPATH')) exit;


use MailPoet\Entities\NewsletterEntity;
use MailPoet\Newsletter\Renderer\Renderer as NewsletterRenderer;
use MailPoet\Newsletter\Shortcodes\Shortcodes;
use MailPoetVendor\csstidy;
use MailPoetVendor\csstidy_print;

class Renderer {
  const CONTENT_CONTAINER_ID = 'mailpoet_woocommerce_container';

  /** @var csstidy */
  private $cssParser;

  /** @var NewsletterRenderer */
  private $renderer;

  /** @var string */
  private $htmlBeforeContent;

  /** @var string */
  private $htmlAfterContent;

  /** @var Shortcodes */
  private $shortcodes;

  /** @var FontFamilyValidator */
  private $fontFamilyValidator;

  public function __construct(
    csstidy $cssParser,
    NewsletterRenderer $renderer,
    Shortcodes $shortcodes,
    FontFamilyValidator $fontFamilyValidator
  ) {
    $this->cssParser = $cssParser;
    $this->htmlBeforeContent = '';
    $this->htmlAfterContent = '';
    $this->renderer = $renderer;
    $this->shortcodes = $shortcodes;
    $this->fontFamilyValidator = $fontFamilyValidator;
  }

  public function render(NewsletterEntity $newsletter, ?string $subject = null) {
    $preparedNewsletter = $this->prepareNewsletterForRendering($newsletter);
    $renderedNewsletter = $this->renderer->renderAsPreview($preparedNewsletter, 'html', $subject);
    $headingText = $subject ?? '';

    $renderedHtml = $this->processShortcodes($preparedNewsletter, $renderedNewsletter);

    $renderedHtml = str_replace(ContentPreprocessor::WC_HEADING_PLACEHOLDER, $headingText, $renderedHtml);
    $html = explode(ContentPreprocessor::WC_CONTENT_PLACEHOLDER, $renderedHtml);
    $this->htmlBeforeContent = $html[0];
    $this->htmlAfterContent = $html[1];
  }

  public function getHTMLBeforeContent() {
    if (empty($this->htmlBeforeContent)) {
      throw new \Exception("You should call 'render' before 'getHTMLBeforeContent'");
    }
    return $this->htmlBeforeContent . '<!--WooContent--><div id="' . self::CONTENT_CONTAINER_ID . '"><div id="body_content"><div id="body_content_inner"><table style="width: 100%"><tr><td style="padding: 10px 20px;">';
  }

  public function getHTMLAfterContent() {
    if (empty($this->htmlAfterContent)) {
      throw new \Exception("You should call 'render' before 'getHTMLAfterContent'");
    }
    return '<!--WooContent--></td></tr></table></div></div></div>' . $this->htmlAfterContent;
  }

  /**
   * In this method we alter the rendered content that is output when processing the WooCommerce email template.
   * - We update inlined font-family rules in the content block generated by Woo
   */
  public function updateRenderedContent(NewsletterEntity $newsletter, string $content): string {
    $isSavedWithStyledWooBlock = $newsletter->getGlobalStyle('woocommerce', 'isSavedWithUpdatedStyles');
    // For Backward compatibility do not apply styles for content unless the template was edited with the editor
    // and user visually checked and is aware of updated styles feature.
    if (!$isSavedWithStyledWooBlock) {
      return $content;
    }
    $contentParts = explode('<!--WooContent-->', $content);
    if (count($contentParts) !== 3) {
      return $content;
    }
    [$beforeWooContent, $wooContent, $afterWooContent] = $contentParts;
    $fontFamily = $newsletter->getGlobalStyle('text', 'fontFamily');

    // Only replace font family if user provided a custom font
    if (!empty($fontFamily)) {
      // Validate and sanitize font family
      $safeFontFamily = $this->fontFamilyValidator->validateFontFamily($fontFamily);
      $wooContent = $this->replaceFontFamily($wooContent, $safeFontFamily);
    }
    
    return implode('', [$beforeWooContent, $wooContent, $afterWooContent]);
  }

  private function replaceFontFamily(string $content, string $fontFamily): string {
    $processor = new \WP_HTML_Tag_Processor($content);

    // Process all tags that might have style attributes
    while ($processor->next_tag()) {
      $styleAttr = $processor->get_attribute('style');
      if (empty($styleAttr)) {
        continue;
      }

      // Fast path: avoid parsing when no font-family present
      if (stripos($styleAttr, 'font-family') === false) {
        continue;
      }

      // Use CSS parser for CSS manipulation
      // Wrap inline style in a selector so csstidy can parse it properly
      $cssParser = new csstidy();
      $cssParser->settings['compress_colors'] = false;
      $cssParser->settings['remove_last_;'] = false;
      $cssParser->parse('x { ' . $styleAttr . ' }');

      // Update font-family in parsed CSS
      $updated = false;
      foreach ($cssParser->css as $index => $rules) {
        foreach ($rules as $selector => $properties) {
          if (isset($properties['font-family'])) {
            $properties['font-family'] = $fontFamily;
            $cssParser->css[$index][$selector] = $properties;
            $updated = true;
          }
        }
      }

      // Only update the attribute if we made changes
      if ($updated) {
        /** @var csstidy_print $print */
        $print = $cssParser->print;
        $fullCss = $print->plain();
        // Extract the properties from "x { ... }" format
        if (preg_match('/x\s*{\s*(.*)\s*}/s', $fullCss, $matches)) {
          $updatedStyle = trim($matches[1]);
          $processor->set_attribute('style', $updatedStyle);
        }
      }
    }

    return $processor->get_updated_html();
  }

  /**
   * In this method we alter CSS that is later inlined into the WooCommerce email template. WooCommerce use Emogrifier to inline CSS.
   * The inlining is called after the rendering and after the modifications we apply to the rendered content in self::updateRenderedContent
   * - We prefix the original selectors to avoid inlining those rules into content added int the MailPoet's editor.
   * - We update the font-family in the original CSS if it's set in the editor.
   * - We update the font-size for the inner content if it's set in the editor.
   */
  public function enhanceCss(string $css, NewsletterEntity $newsletter): string {
    $this->cssParser->settings['compress_colors'] = false;
    $this->cssParser->parse($css);
    foreach ($this->cssParser->css as $index => $rules) {
      $this->cssParser->css[$index] = [];
      foreach ($rules as $selectors => $properties) {
        $properties = $this->updateStyleDefinition($selectors, $newsletter, $properties);
        $selectors = explode(',', $selectors);
        $selectors = array_map(function($selector) {
          return '#' . self::CONTENT_CONTAINER_ID . ' ' . $selector;
        }, $selectors);
        $selectors = implode(',', $selectors);

        $this->cssParser->css[$index][$selectors] = $properties;
      }
    }

    /** @var csstidy_print */
    $print = $this->cssParser->print;
    $css = $print->plain();

    // Enforce the special heading color for the WooCommerce email header
    $wooHeadingColor = $newsletter->getGlobalStyle('woocommerce', 'headingFontColor');
    if ($wooHeadingColor) {
      $css .= "#mailpoet-woo-email-header { color: $wooHeadingColor !important; }";
    }
    return $css;
  }

  private function processShortcodes(NewsletterEntity $newsletter, $content) {
    $this->shortcodes->setQueue(null);
    $this->shortcodes->setSubscriber(null);
    $this->shortcodes->setNewsletter($newsletter);
    return $this->shortcodes->replace($content);
  }

  /**
   * This method prepares the newsletter for rendering
   * - We ensure that the font-family and branding color are used as default for all headings
   */
  private function prepareNewsletterForRendering(NewsletterEntity $newsletter): NewsletterEntity {
    $newsletterClone = clone($newsletter);
    $headingFontFamily = $this->fontFamilyValidator->validateFontFamily($newsletter->getGlobalStyle('woocommerce', 'headingFontFamily'));
    if ($headingFontFamily !== FontFamilyValidator::DEFAULT_FONT_FAMILY) {
      $newsletterClone->setGlobalStyle('h1', 'fontFamily', $headingFontFamily);
      $newsletterClone->setGlobalStyle('h2', 'fontFamily', $headingFontFamily);
      $newsletterClone->setGlobalStyle('h3', 'fontFamily', $headingFontFamily);
    }
    $brandingColor = $newsletter->getGlobalStyle('woocommerce', 'brandingColor');
    $contentHeadingColor = $newsletter->getGlobalStyle('woocommerce', 'contentHeadingFontColor') ?? $brandingColor;
    if ($contentHeadingColor) {
      $newsletterClone->setGlobalStyle('h1', 'color', $contentHeadingColor);
      $newsletterClone->setGlobalStyle('h2', 'color', $contentHeadingColor);
      $newsletterClone->setGlobalStyle('h3', 'color', $contentHeadingColor);
    }
    return $newsletterClone;
  }

  private function updateStyleDefinition(string $selectors, NewsletterEntity $newsletter, $properties) {
    // For Backward compatibility do not apply styles for content unless the template was edited with the editor
    // and user visually checked and is aware of updated styles feature.
    $isSavedWithStyledWooBlock = $newsletter->getGlobalStyle('woocommerce', 'isSavedWithUpdatedStyles');
    if (!$isSavedWithStyledWooBlock) {
      return $properties;
    }

    if (!is_array($properties)) {
      $properties = [];
    }
    $fontFamilyRaw = $newsletter->getGlobalStyle('text', 'fontFamily');
    $headingFontFamilyRaw = $newsletter->getGlobalStyle('woocommerce', 'headingFontFamily');
    $fontFamily = $fontFamilyRaw !== null ? $this->fontFamilyValidator->validateFontFamily($fontFamilyRaw) : null;
    $headingFontFamily = $headingFontFamilyRaw !== null ? $this->fontFamilyValidator->validateFontFamily($headingFontFamilyRaw) : null;
    $fontSize = $newsletter->getGlobalStyle('text', 'fontSize');
    $brandingColor = $newsletter->getGlobalStyle('woocommerce', 'brandingColor');
    $contentHeadingColor = $newsletter->getGlobalStyle('woocommerce', 'contentHeadingFontColor') ?? $brandingColor;

    // Update font family if it's set in the editor
    if (!empty($fontFamilyRaw) && !empty($properties['font-family'])) {
      $properties['font-family'] = $fontFamily;
    }
    // Update font size for inner content
    if ($fontSize && ($selectors === '#body_content_inner')) {
      $properties['font-size'] = $fontSize;
    }

    // Update heading font sizes and font family
    $supportedHeadings = ['h1', 'h2', 'h3'];
    foreach ($supportedHeadings as $heading) {
      $headingFontSize = $newsletter->getGlobalStyle($heading, 'fontSize');
      if ($headingFontSize && ($selectors === $heading)) {
        $properties['font-size'] = $headingFontSize;
      }
      if (!empty($headingFontFamilyRaw) && ($selectors === $heading)) {
        $properties['font-family'] = $headingFontFamily;
      }
      if ($contentHeadingColor && ($selectors === $heading)) {
        $properties['color'] = $contentHeadingColor;
      }
    }
    return $properties;
  }
}
