<?php

namespace Concrete\Block\CoreStackDisplay;

use Concrete\Core\Block\Block;
use Concrete\Core\Block\BlockController;
use Concrete\Core\Block\Traits\HasSubBlocksTrait;
use Concrete\Core\Feature\UsesFeatureInterface;
use Concrete\Core\Multilingual\Page\Section\Section;
use Concrete\Core\Page\Page;
use Concrete\Core\Page\Stack\Stack;
use Concrete\Core\Permission\Checker;
use Concrete\Core\Statistics\UsageTracker\TrackableInterface;
use Concrete\Core\Utility\Service\Xml;

/**
 * The controller for the stack display block. This is an internal proxy block that is inserted when a stack's contents are displayed in a page.
 *
 * @package Blocks
 * @subpackage Core Stack Display
 *
 * @author Andrew Embler <andrew@concretecms.org>
 * @copyright  Copyright (c) 2003-2022 concretecms. (http://www.concretecms.org)
 * @license    http://www.concretecms.org/license/     MIT License
 */
class Controller extends BlockController implements TrackableInterface, UsesFeatureInterface
{
    use HasSubBlocksTrait;

    /**
     * @var int|null
     */
    public $stID;

    /**
     * @var bool
     */
    protected $btCacheBlockRecord = true;

    /**
     * @var string
     */
    protected $btTable = 'btCoreStackDisplay';

    /**
     * @var bool
     */
    protected $btIsInternal = true;

    /**
     * @var int|null
     */
    protected $stIDNeutral;

    /**
     * @var int|null
     */
    protected $bOriginalID;

    /**
     * @return string
     */
    public function getBlockTypeDescription()
    {
        return t('Proxy block for stacks added through the UI.');
    }

    /**
     * @return string
     */
    public function getBlockTypeName()
    {
        return t('Stack Display');
    }

    /**
     * @return int
     */
    public function getOriginalBlockID()
    {
        return $this->bOriginalID;
    }

    /**
     * @return string
     */
    public function getSearchableContent()
    {
        $searchableContent = '';
        $stack = Stack::getByID($this->stID);
        if (is_object($stack)) {
            $blocks = $stack->getBlocks();
            if (!empty($blocks)) {
                foreach ($blocks as $block) {
                    if (method_exists($block->instance, 'getSearchableContent')) {
                        $searchableContent .= $block->instance->getSearchableContent();
                    }
                }
            }
        }

        return $searchableContent;
    }

    /**
     * @param \SimpleXMLElement $blockNode
     * @param Page $page
     *
     * @return array<string, mixed>
     */
    public function getImportData($blockNode, $page)
    {
        $args = ['stID' => 0];
        if (isset($blockNode->stack)) {
            $stack = null;
            $path = isset($blockNode->stack['path']) ? (string) $blockNode->stack['path'] : '';
            if ($path !== '') {
                $stack = Stack::getByPath($path);
            }
            if (!$stack) {
                $name = trim((string) $blockNode->stack);
                if ($name !== '') {
                    $stack = Stack::getByName($name);
                }
            }
            if ($stack) {
                $args['stID'] = (int) $stack->getCollectionID();
            }
        }

        return $args;
    }

    /**
     * @param string $method
     * @param mixed[] $parameters
     *
     * @return bool
     */
    public function isValidControllerTask($method, $parameters = [])
    {
        $b = $this->findBlockForAction($method, $parameters);

        return !empty($b);
    }

    /**
     * @param string $action
     * @param mixed[] $parameters
     *
     * @return mixed|void
     */
    public function runAction($action, $parameters = [])
    {
        parent::runAction($action, $parameters); // handles on_page_view

        $b = $this->findBlockForAction($action, $parameters);
        if (empty($b)) {
            return;
        }

        $controller = $b->getController();

        return $controller->runAction($action, $parameters);
    }

    /**
     * @param string $outputContent
     *
     * @return void
     */
    public function registerViewAssets($outputContent = '')
    {
        $stack = $this->getStack(true);
        if ($stack === null) {
            return;
        }

        $blocks = $stack->getBlocks();
        foreach ($blocks as $b) {
            /** @var BlockController|null $controller */
            $controller = $b->getController();
            if ($controller) {
                /** this always returns void */
                $controller->registerViewAssets($outputContent);
            }
        }
    }

    /**
     * @param string $method
     * @param array<string, mixed> $parameters
     *
     * @return \Concrete\Core\Block\Block|null
     */
    public function findBlockForAction($method, $parameters)
    {
        $stack = $this->getStack(true);
        if ($stack === null) {
            return null;
        }
        $blocks = $stack->getBlocks();
        foreach ($blocks as $b) {
            $controller = $b->getController();
            if ($controller->isValidControllerTask($method, $parameters)) {
                return $b;
            }
        }

        return null;
    }

    /**
     * @param \SimpleXMLElement $blockNode
     *
     * @return void
     */
    public function export(\SimpleXMLElement $blockNode)
    {
        $stack = $this->getStack(false);
        if ($stack !== null) {
            $stackElement = $this->app->make(Xml::class)->createChildElement($blockNode, 'stack', $stack->getCollectionName());
            if ($stack->getStackType() != Stack::ST_TYPE_GLOBAL_AREA) {
                $pathParts = [$stack->getCollectionHandle()];
                $parentID = $stack->getCollectionParentID();
                while ($parentID) {
                    $parentPage = Page::getByID($parentID);
                    if (!$parentPage || $parentPage->isError()) {
                        break;
                    }
                    $pathPart = $parentPage->getCollectionHandle();
                    if (ltrim(STACKS_PAGE_PATH, '/') === $pathPart) {
                        break;
                    }
                    $pathParts[] = $pathPart;
                    $parentID = $parentPage->getCollectionParentID();
                }
                $stackElement['path'] = '/' . implode('/', array_reverse($pathParts));
            }
        }
    }

    /**
     * @param Page $page
     *
     * @return mixed|void
     */
    public function on_page_view($page)
    {
        $stack = $this->getStack(true);
        if ($stack === null) {
            return;
        }
        $p = new Checker($stack);
        /** @phpstan-ignore-next-line */
        if ($p->canViewPage()) {
            $blocks = $stack->getBlocks();
            foreach ($blocks as $b) {
                $bp = new Checker($b);
                /** @phpstan-ignore-next-line */
                if ($bp->canViewBlock()) {
                    $btc = $b->getController();
                    if (get_class($btc) !== 'Controller') {
                        $btc->outputAutoHeaderItems();
                    }
                    $csr = $b->getCustomStyle();
                    if (is_object($csr)) {
                        $css = $csr->getCSS();
                        if ($css !== '') {
                            $styleHeader = $csr->getStyleWrapper($css);
                            $btc->addHeaderItem($styleHeader);
                        }
                    }
                    $btc->runAction('on_page_view', [$page]);
                }
            }
        }
    }

    /**
     * @return bool
     */
    public function cacheBlockOutput()
    {
        $this->setupCacheSettings();

        return $this->btCacheBlockOutput;
    }

    /**
     * @return bool
     */
    public function cacheBlockOutputOnPost()
    {
        $this->setupCacheSettings();

        return $this->btCacheBlockOutputOnPost;
    }

    /**
     * @return int
     */
    public function getBlockTypeCacheOutputLifetime()
    {
        $this->setupCacheSettings();

        return $this->btCacheBlockOutputLifetime;
    }

    /**
     * @return int|null
     */
    public function getStackID()
    {
        return $this->stID;
    }

    /**
     * @param array<string,mixed> $args
     * @return void
     */
    public function save($args)
    {
        parent::save($args);
        $this->stID = $args['stID'];
    }

    /**
     * @throws \Illuminate\Contracts\Container\BindingResolutionException|\Exception
     *
     * @return void
     */
    protected function load()
    {
        parent::load();
        $this->set('stIDNeutral', null);
        /** @var Stack|false $stack */
        $stack = Stack::getByID($this->stID);
        if ($stack && $stack->isNeutralStack()) {
            $detector = app('multilingual/detector');
            // @var \Concrete\Core\Multilingual\Service\Detector $detector
            if ($detector->isEnabled()) {
                /** @var Section|false $section */
                $section = Section::getCurrentSection();
                if ($section) {
                    $localized = $stack->getLocalizedStack($section);
                    if ($localized) {
                        $this->stIDNeutral = $this->stID;
                        $this->stID = $localized->getCollectionID();
                        $this->set('stIDNeutral', $this->stIDNeutral);
                        $this->set('stID', $this->stID);
                    }
                }
            }
        }
    }

    /**
     * Returns the Stack instance (if found).
     *
     * @param bool $localized set to true to look for a localized version of the stack (if not found return the neutral version)
     *
     * @return Stack|null
     */
    protected function getStack($localized)
    {
        if ($this->stIDNeutral === null || $localized) {
            $result = Stack::getByID($this->stID);
        } else {
            $result = Stack::getByID($this->stIDNeutral);
        }
        /** @var Stack|null $result */

        return $result;
    }

    /**
     * @return void
     */
    protected function setupCacheSettings()
    {
        $page = $this->getCollectionObject();
        if ($this->isCacheSettingsInitialized() || $page->isEditMode()) {
            return;
        }

        $stack = $this->getStack(true);
        if ($stack === null) {
            return;
        }

        $p = new Checker($stack);
        /** @phpstan-ignore-next-line */
        if ($p->canViewPage()) {
            $blocks = $stack->getBlocks();
            $this->initializeSubBlockCacheSettings($page, $blocks);
        }
    }

    public function getRequiredFeatures(): array
    {
        $this->setupCacheSettings();

        return $this->requiredFeatures;
    }
}
