<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */

namespace Magento\Framework\Translate;

use Magento\Framework\App\Area;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\App\ScopeInterface;
use Magento\Framework\App\ScopeResolverInterface;
use Magento\Framework\App\State;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\ObjectManager\ResetAfterRequestInterface;
use Magento\Framework\Translate\Inline\ConfigInterface;
use Magento\Framework\Translate\Inline\ParserInterface;
use Magento\Framework\Translate\Inline\StateInterface;
use Magento\Framework\UrlInterface;
use Magento\Framework\View\Element\Template;
use Magento\Framework\View\LayoutInterface;

/**
 * Translate Inline Class
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
 */
class Inline implements InlineInterface, ResetAfterRequestInterface
{
    /**
     * Indicator to hold state of whether inline translation is allowed
     *
     * @var bool
     */
    protected $isAllowed;

    /**
     * @var ParserInterface
     */
    protected $parser;

    /**
     * Flag about inserted styles and scripts for inline translates
     *
     * @var bool
     */
    protected $isScriptInserted = false;

    /**
     * @var UrlInterface
     */
    protected $url;

    /**
     * @var LayoutInterface
     */
    protected $layout;

    /**
     * @var ConfigInterface
     */
    protected $config;

    /**
     * @var ScopeResolverInterface
     */
    protected $scopeResolver;

    /**
     * @var string
     */
    protected $templateFileName;

    /**
     * @var string
     */
    protected $translatorRoute;

    /**
     * @var null|string
     */
    protected $scope;

    /**
     * @var Inline\StateInterface
     */
    protected $state;

    /**
     * @var array
     */
    private $allowedAreas = [Area::AREA_FRONTEND, Area::AREA_ADMINHTML];

    /**
     * @var State
     */
    private $appState;

    /**
     * @param ScopeResolverInterface $scopeResolver
     * @param UrlInterface $url
     * @param LayoutInterface $layout
     * @param Inline\ConfigInterface $config
     * @param Inline\ParserInterface $parser
     * @param Inline\StateInterface $state
     * @param string $templateFileName
     * @param string $translatorRoute
     * @param string|null $scope
     * @param State|null $appState
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
     */
    public function __construct(
        ScopeResolverInterface $scopeResolver,
        UrlInterface $url,
        LayoutInterface $layout,
        ConfigInterface $config,
        ParserInterface $parser,
        StateInterface $state,
        $templateFileName = '',
        $translatorRoute = '',
        $scope = null,
        ?State $appState = null
    ) {
        $this->scopeResolver = $scopeResolver;
        $this->url = $url;
        $this->layout = $layout;
        $this->config = $config;
        $this->parser = $parser;
        $this->state = $state;
        $this->templateFileName = $templateFileName;
        $this->translatorRoute = $translatorRoute;
        $this->scope = $scope;
        $this->appState = $appState ?: ObjectManager::getInstance()->get(State::class);
    }

    /**
     * Check if Inline Translates is allowed
     *
     * @return bool
     */
    public function isAllowed()
    {
        if ($this->isAllowed === null) {
            $scope = $this->scope instanceof ScopeInterface ? null : $this->scopeResolver->getScope($this->scope);

            $this->isAllowed = $this->config->isActive($scope)
                && $this->config->isDevAllowed($scope)
                && $this->isAreaAllowed();
        }

        return $this->state->isEnabled() && $this->isAllowed;
    }

    /**
     * Retrieve Inline Parser instance
     *
     * @return Inline\ParserInterface
     */
    public function getParser()
    {
        return $this->parser;
    }

    /**
     * Replace translation templates with HTML fragments
     *
     * @param array|string $body
     * @param bool $isJson
     * @return $this
     */
    public function processResponseBody(&$body, $isJson = false)
    {
        if (!$this->isAllowed()) {
            $this->stripInlineTranslations($body);
            return $this;
        }

        $this->getParser()->setIsJson($isJson);

        if (is_array($body)) {
            foreach ($body as &$part) {
                $this->processResponseBody($part, $isJson);
            }
        } elseif (is_string($body)) {
            $this->getParser()->processResponseBodyString($body);
            $this->addInlineScript();
            $body = $this->getParser()->getContent();
        }

        $this->getParser()->setIsJson(false);

        return $this;
    }

    /**
     * Additional translation mode html attribute is not needed for base inline translation.
     *
     * @param mixed|string|null $tagName
     * @return null
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    public function getAdditionalHtmlAttribute($tagName = null)
    {
        return null;
    }

    /**
     * Add inline script code
     *
     * Insert script and html with
     * added inline translation content.
     *
     * @return void
     */
    protected function addInlineScript()
    {
        $content = $this->getParser()->getContent();
        if (stripos($content, '</body>') === false) {
            return;
        }
        if (!$this->isScriptInserted) {
            $this->getParser()->setContent(
                str_ireplace('</body>', $this->getInlineScript() . '</body>', $content)
            );
            $this->isScriptInserted = true;
        }
    }

    /**
     * Retrieve inline script code
     *
     * Create block to render script and html with
     * added inline translation content.
     *
     * @return string
     */
    protected function getInlineScript()
    {
        /** @var $block Template */
        $block = $this->layout->createBlock(Template::class);

        $block->setAjaxUrl($this->getAjaxUrl());
        $block->setTemplate($this->templateFileName);

        return $block->toHtml();
    }

    /**
     * Return URL for ajax requests
     *
     * @return string
     */
    protected function getAjaxUrl()
    {
        return $this->url->getUrl(
            $this->translatorRoute,
            ['_secure' => $this->scopeResolver->getScope()->isCurrentlySecure()]
        );
    }

    /**
     * Strip inline translations from text
     *
     * @param array|string &$body
     * @return $this
     */
    protected function stripInlineTranslations(&$body)
    {
        if (is_array($body)) {
            foreach ($body as &$part) {
                $this->stripInlineTranslations($part);
            }
        } elseif (is_string($body)) {
            $body = preg_replace('#' . ParserInterface::REGEXP_TOKEN . '#', '$1', $body);
        }

        return $this;
    }

    /**
     * Indicates whether the current area is valid for inline translation
     *
     * @return bool
     */
    private function isAreaAllowed(): bool
    {
        try {
            return in_array($this->appState->getAreaCode(), $this->allowedAreas, true);
        } catch (LocalizedException $e) {
            return false;
        }
    }

    /**
     * @inheritDoc
     */
    public function _resetState(): void
    {
        $this->isAllowed = null;
        $this->isScriptInserted = false;
    }
}
