<?php

namespace CMW\Manager\Views\Implementations;

use CMW\Controller\Users\UsersController;
use CMW\Manager\Api\APIManager;
use CMW\Manager\Components\ComponentsManager;
use CMW\Manager\Env\EnvManager;
use CMW\Manager\Flash\Flash;
use CMW\Manager\Router\RouterException;
use CMW\Manager\Theme\Editor\ThemeEditorProcessor;
use CMW\Manager\Theme\Loader\ThemeLoader;
use CMW\Manager\Views\IView;
use CMW\Utils\Utils;
use JetBrains\PhpStorm\ArrayShape;
use JetBrains\PhpStorm\ExpectedValues;
use function extract;
use function in_array;
use function is_file;
use function is_null;
use function ob_get_clean;
use function ob_start;
use function print_r;

class BaseViewImplementation implements IView
{
    private static self $_instance;

    private ?string $package;
    private ?string $viewFile;
    private ?string $customPath = null;
    private ?string $customTemplate = null;
    private array $includes;
    private array $variables;
    private bool $needAdminControl;
    private bool $isAdminFile;
    private bool $isPublicView;
    private string $themeName;
    private bool $overrideBackendMode;

    /**
     * @param string|null $package
     * @param string|null $viewFile
     * @param bool|null $isAdminFile
     */
    public function __construct(?string $package = null, ?string $viewFile = null, ?bool $isAdminFile = false)
    {
        $this->package = $package;
        $this->viewFile = $viewFile;
        $this->includes = $this->generateInclude();
        $this->variables = [];
        $this->needAdminControl = false;
        $this->isAdminFile = $isAdminFile;
        $this->isPublicView = false;
        $this->themeName = ThemeLoader::getInstance()->getCurrentTheme()->name();
        $this->overrideBackendMode = false;
    }

    public function weight(): int
    {
        return 1;
    }

    public static function getInstance(): self
    {
        if (!isset(self::$_instance)) {
            self::$_instance = new self();
        }

        return self::$_instance;
    }


    public function setPackage(string $package): static
    {
        $this->package = $package;
        return $this;
    }

    public function setViewFile(string $viewFile): static
    {
        $this->viewFile = $viewFile;
        return $this;
    }

    public function needAdminControl(bool $needAdminControl = true): static
    {
        $this->needAdminControl = $needAdminControl;
        return $this;
    }

    public function setAdminView(bool $isAdminFile = true): static
    {
        $this->isAdminFile = $isAdminFile;
        return $this;
    }

    public function addVariable(string $variableName, mixed $variable): static
    {
        $this->variables[$variableName] = $variable;
        return $this;
    }

    public function addVariableList(array $variableList): static
    {
        foreach ($variableList as $key => $value) {
            $this->addVariable($key, $value);
        }
        return $this;
    }

    public function addScriptBefore(string ...$script): static
    {
        foreach ($script as $s) {
            $this->includes['scripts']['before'][] = $s;
        }
        return $this;
    }

    public function addScriptAfter(string ...$script): static
    {
        foreach ($script as $s) {
            $this->includes['scripts']['after'][] = $s;
        }
        return $this;
    }

    public function addPhpBefore(string ...$php): static
    {
        foreach ($php as $p) {
            $this->includes['php']['before'][] = $p;
        }
        return $this;
    }

    public function addPhpAfter(string ...$php): static
    {
        foreach ($php as $p) {
            $this->includes['php']['after'][] = $p;
        }
        return $this;
    }

    public function addStyle(string ...$style): static
    {
        foreach ($style as $s) {
            $this->includes['styles'][] = $s;
        }
        return $this;
    }

    public function setCustomPath(string $path): static
    {
        $this->customPath = $path;
        return $this;
    }

    public function setCustomTemplate(string $path): static
    {
        $this->customTemplate = $path;
        return $this;
    }

    public function setOverrideBackendMode(bool $overrideBackendMode): static
    {
        $this->overrideBackendMode = $overrideBackendMode;
        return $this;
    }

    /**
     * @param array $includes
     * @param string ...$files
     * @return void
     */
    public static function loadInclude(array $includes, #[ExpectedValues(flags: ['beforeScript', 'afterScript', 'beforePhp', 'afterPhp', 'styles'])] string ...$files): void
    {
        foreach ($files as $file) {
            self::loadIncludeFile($includes, $file);
        }
    }

    /**
     * @param string $package
     * @param string $viewFile
     * @return void
     * @throws RouterException
     */
    public function basicPublicView(string $package, string $viewFile): void
    {
        $view = new self($package, $viewFile);
        $view->view();
    }

    /**
     * @param string $package
     * @param string $viewFile
     * @return self
     */
    public function createPublicView(string $package, string $viewFile): self
    {
        $view = new self($package, $viewFile);

        $view->isPublicView = true;

        return $view;
    }

    /**
     * @param string $package
     * @param string $viewFile
     * @return self
     */
    public static function createAdminView(string $package, string $viewFile): self
    {
        $view = new self($package, $viewFile);

        $view->setAdminView()->needAdminControl();

        return $view;
    }

    /**
     * @throws RouterException
     */
    public function view(): void
    {
        // Check admin permissions
        if ($this->needAdminControl) {
            UsersController::redirectIfNotHavePermissions('core.dashboard');
        }

        extract($this->variables);
        $includes = $this->includes;

        //Backend mode view logic
        $this->backendView();

        if (is_null($this->customPath) && Utils::containsNullValue($this->package, $this->viewFile)) {
            throw new RouterException('Invalid View usage. Please set a correct path.', 404);
        }

        $path = $this->getViewPath();

        if (!is_file($path)) {
            throw new RouterException(null, 404);
        }

        //Load Elements
        ComponentsManager::getInstance()->loadThemeComponents($this->themeName);

        ob_start();
        require_once $path;
        echo $this->callAlerts();
        if ($this->isPublicView) {
            $content = ob_get_clean();
            $editorMode = isset($_GET['editor']) && $_GET['editor'] == '1';
            $content = ThemeEditorProcessor::getInstance()->replaceThemeValues($content, $editorMode);

            // bufferisation de template pour la gestion de replaceThemeValue
            ob_start();
            require_once($this->getTemplateFile());
            $templateContent = ob_get_clean();
            $templateContent = ThemeEditorProcessor::getInstance()->replaceThemeValues($templateContent, $editorMode);
            echo $templateContent;
        } else {
            $content = ob_get_clean();
            require_once($this->getTemplateFile());
        }
    }

    /**
     * @param array $includes
     * @param string $fileType
     * @return void
     */
    private static function loadIncludeFile(array $includes, #[ExpectedValues(['beforeScript', 'afterScript', 'beforePhp', 'afterPhp', 'styles'])] string $fileType): void
    {
        if (!in_array($fileType, ['beforeScript', 'afterScript', 'beforePhp', 'afterPhp', 'styles'])) {
            return;
        }

        // STYLES
        if ($fileType === 'styles') {
            foreach ($includes['styles'] as $style) {
                $styleLink = EnvManager::getInstance()->getValue('PATH_SUBFOLDER') . $style;
                echo <<<HTML
                        <link rel="stylesheet" href="$styleLink">
                    HTML;
            }
        }

        // SCRIPTS
        if (in_array($fileType, ['beforeScript', 'afterScript'])) {
            $arrayAccessJs = $fileType === 'beforeScript' ? 'before' : 'after';
            foreach ($includes['scripts'][$arrayAccessJs] as $script) {
                $scriptLink = EnvManager::getInstance()->getValue('PATH_SUBFOLDER') . $script;
                echo <<<HTML
                        <script src="$scriptLink"></script>
                    HTML;
            }
        }

        // PHP
        if (in_array($fileType, ['beforePhp', 'afterPhp'])) {
            $arrayAccessPhp = $fileType === 'beforePhp' ? 'before' : 'after';
            foreach ($includes['php'][$arrayAccessPhp] as $php) {
                $phpLink = EnvManager::getInstance()->getValue('DIR') . $php;
                include_once $phpLink;
            }
        }
    }

    /**
     * @return array|array[]
     */
    #[ArrayShape(['styles' => 'array', 'scripts' => 'array', 'php' => 'array'])]
    private function generateInclude(): array
    {
        $array = ['styles' => [], 'scripts' => [], 'array' => []];

        $array['scripts']['before'] = [];
        $array['scripts']['after'] = [];

        $array['php']['before'] = [];
        $array['php']['after'] = [];

        return $array;
    }

    /**
     * @return string
     */
    private function getTemplateFile(): string
    {
        if ($this->customTemplate !== null) {
            return $this->customTemplate;
        }

        return ($this->isAdminFile)
            ? EnvManager::getInstance()->getValue('PATH_ADMIN_VIEW') . 'template.php'
            : "Public/Themes/$this->themeName/Views/template.php";
    }

    /**
     * @throws RouterException
     */
    private function callAlerts(): string
    {
        $alerts = Flash::load();
        $alertContent = '';
        foreach ($alerts as $alert) {
            if (!$alert->isAdmin()) {
                $view = new self('Core', 'Alerts/' . $alert->getType());
            } else {
                $view = new self('Core', 'Alerts/' . $alert->getType(), true);
            }
            $view->addVariable('alert', $alert);
            $alertContent .= $view->loadFile();
        }
        Flash::clear();
        return $alertContent;
    }

    /**
     * @throws RouterException
     */
    public function loadFile(): string
    {
        $path = $this->getViewPath();

        if (!is_file($path)) {
            throw new RouterException(null, 404);
        }

        extract($this->variables);
        $includes = $this->includes;

        ob_start();
        require($path);
        return ob_get_clean();
    }

    /**
     * @return string
     */
    private function getViewPath(): string
    {
        if ($this->customPath !== null) {
            return $this->customPath;
        }

        if ($this->isAdminFile) {
            return "App/Package/$this->package/Views/$this->viewFile.admin.view.php";
        }

        $publicPath = "Public/Themes/$this->themeName/Views/$this->package/$this->viewFile.view.php";

        if (is_file($publicPath)) {
            return $publicPath;
        }

        $this->isPublicView = true;
        return "App/Package/$this->package/Public/$this->viewFile.view.php";
    }


    /**
     * @return void
     * @desc Return the view data if the backend mode is enabled
     */
    private function backendView(): void
    {
        $isBackendModeEnabled = EnvManager::getInstance()->getValue('ENABLE_BACKEND_MODE') === 'true';

        if ($this->overrideBackendMode) {
            return;
        }

        if ($isBackendModeEnabled && !$this->needAdminControl) {
            print_r(
                APIManager::createResponse(
                    data: [
                        'package' => $this->package,
                        'viewFile' => $this->viewFile,
                        'viewFilePath' => $this->getViewPath(),
                        'variables' => $this->variables,
                        'includes' => $this->includes,
                    ],
                )
            );
            die();
        }
    }
}