<?php

namespace onespace\tools\widgets\grapeJS;

use Exception;
use onespace\tools\helpers\Minifier;
use Yii;
use yii\base\Widget;
use yii\helpers\Html;

/**
 * Load up the GrapesJS editor.
 * 
 * {@inheritdoc}
 * 
 * @see https://grapesjs.com/docs
 * @see https://grapesjs.com/docs/getting-started.html
 * 
 * @author  Gareth Palmer <gareth@one-space.co.za>
 */

final class GrapesJS extends Widget {

    use Minifier;

    public const DONT_STORE = 0;
    public const STORE_LOCAL = 1;
    public const STORE_REMOTE = 2;
    public const STORE_LOCAL_INLINE = 3;

    /**
     * OPTIONAL. Whether or not to use the newsletter plugin.
     * 
     * @var bool    $isNewsletter   Default: false
     * 
     * @access  public
     */

    public bool $isNewsletter = false;

    /**
     * OPTIONAL. The height of editor.
     * 
     * @var string  $height Default: 1000px
     * 
     * @access  public
     */

    public string $height;

    /**
     * OPTIONAL. The height of editor.
     * 
     * @var string  $width
     * 
     * @access  public
     */

    public string $width;

    /**
     * The content of the div to preload.
     * 
     * @var string  $content
     * 
     * @access  public
     */

    public string $content = '';

    /**
     * The json encoded content which should be loaded into div.
     * 
     * @var string|array|false    $jsonContent    JSON encoded string
     * 
     * @access  public
     */

    public string|array|false $jsonContent = false;

    /**
     * OPTIONAL. Whether or not to load the contents directly from the contents of the div.
     * 
     * @var bool    $fromElement    Default: true
     * 
     * @access  public
     */

    public bool $fromElement = true;

    /**
     * OPTIONAL. A completely custom loading script. If set, this will overwrite all other settings.
     * 
     * @var string  $customLoadScript
     * 
     * @access  public
     */

    public string $customLoadScript;

    /**
     * OPTIONAL. Any extra scripting options to add to the page when loading.
     * 
     * @var string  $extraScripts
     * 
     * @access  public
     */

    public string $extraScripts;

    /**
     * OPTIONAL. A completely custom styles sheet. If set, this will overwrite all other styles.
     * 
     * @var string  $customLoadScript
     * 
     * @access  public
     */

    public string $customStyles;

    /**
     * OPTIONAL. The type of storage used by the system.
     * 
     * @var int $storageType    Default: `self::DONT_STORE`
     * 
     * @access  public
     */

    public int $storageType;

    /**
     * OPTIONAL. The storage key needed for local storage.
     * 
     * @var string  $storageKey
     * 
     * @access  public
     */

    public string $storageKey;

    /**
     * OPTIONAL. Any HTML elements to add to the main DIV.
     * 
     * @var array   $options    Default: []
     * 
     * @access  public
     */

    public array $options = [];

    /**
     * OPTIONAL. The ID used throughout the widget to identify this specific instance.
     * This allows for multiple instances of the widget on the same page.
     * 
     * @var string  $id
     * 
     * @access  public
     */

    public string $id;

    /**
     * Whether to load the widget in debug mode or not.
     * 
     * @var bool    $debug
     * 
     * @access  public
     */

    public bool $debug = false;


    /**
     * {@inheritdoc}
     * 
     * @throws  Exception   When not storage key is defined and storing locally.
     * 
     * @access  public
     */

    public function init(): void {
        $this->id ??= str_replace('-', '_', 'gjs' . Yii::$app->security->generateRandomString(12));

        $this->options['id'] = $this->id;
        $this->options['class'] ??= 'grapes-js';

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

        $this->extraScripts ??= '';

        $this->storageType ??= self::DONT_STORE;
        if ($this->storageType == self::STORE_REMOTE) {
            throw new Exception("This storage medium has not yet been built. Please do not use");
        }
        if ($this->storageType == self::STORE_LOCAL) {
            if (!isset($this->storageKey)) {
                throw new Exception("You must define a storage key when storing locally");
            }
        }

        $view = $this->getView();
        $view->registerJsFile('https://unpkg.com/grapesjs@0.22.6/dist/grapes.min.js');
        if ($this->isNewsletter) {
            $view->registerJsFile('https://unpkg.com/grapesjs-preset-newsletter@1.0.2/dist/index.js');
        }
        $view->registerCssFile('https://unpkg.com/grapesjs@0.22.6/dist/css/grapes.min.css');

        $view->registerCss(file_get_contents(Yii::getAlias('@app/web/css/webfonts.css')));

        if ($this->debug) {
            $view->registerJs($this->JS());
            $view->registerCss($this->CSS());
        } else {
            $view->registerJs($this->minifyJs($this->JS()));
            $view->registerCss($this->minifyCss($this->CSS()));
        }
    }


    /**
     * {@inheritdoc}
     * 
     * @return  string
     * 
     * @access  public
     */

    public function run(): string {
        $html = '';
        $html .= Html::tag('div', $this->content, options: $this->options);
        if ($this->storageType == self::STORE_LOCAL_INLINE) {
            $html .= Html::input('hidden', 'grapeJS-content-data', options: ['id' => "grapeJS-content-data{$this->id}"]);
            $html .= Html::input('hidden', 'grapeJS-content-html', options: ['id' => "grapeJS-content-html{$this->id}"]);
        }
        return $html;
    }


    /**
     * Returns the JS used for loading the editor.
     * 
     * @return  string
     * 
     * @access  public
     */

    public function JS(): string {
        if (isset($this->customLoadScript)) {
            return $this->customLoadScript;
        }
        $nl = $this->isNewsletter ? '1' : '0';

        $fromElement = <<<JS
        false
        JS;
        if ($this->fromElement) {
            $fromElement = <<<JS
            true
            JS;
        }

        $js = '';

        $storage = match ($this->storageType) {
            self::DONT_STORE => <<<JS
            false
            JS,
            /*************************************/
            self::STORE_LOCAL => <<<JS
            {
                type: 'local',
                autosave: true,
                autoload: true,
                stepsBeforeSave: 1,
                options: {
                    local: {
                    key: '{$this->storageKey}', // The key for the local storage
                    },
                }
            }
            JS,
            /*************************************/
            ## To be built
            self::STORE_REMOTE => <<<JS
            false
            JS,
            /*************************************/
            self::STORE_LOCAL_INLINE => <<<JS
            { type: 'inline' }
            JS,
            /*************************************/
            default => <<<JS
            false
            JS,
        };

        $stL = '0';
        $jcl = $this->jsonContent === false ? '0' : '1';
        if ($this->storageType == self::STORE_LOCAL_INLINE) {
            $stL = '1';
            $js .= <<<JS
            const inlineStorage{$this->id} = editor => {
                const projectDataEl = document.querySelector('#grapeJS-content-data{$this->id}');
                const projectHtmlEl = document.querySelector('#grapeJS-content-html{$this->id}');
                const handleLoadData = () => {
                    const component = editor.Pages.getSelected().getMainComponent();
                    projectHtmlEl.value = '<html>' + '<head>' + '<style>' + editor.getCss({ component }) + '</style>' + '</head>' + editor.getHtml({ component }) + '<html>';
                };
                editor.Storage.add('inline', {
                    load() {
                        projectDataEl.value = JSON.stringify(editor.getProjectData());
                        handleLoadData();
                    },
                    store(data) {
                        projectDataEl.value = JSON.stringify(data);
                        handleLoadData();
                    }
                });
            };
            JS;
        }

        $height = '';
        if (isset($this->height)) {
            $height = <<<JS
            height: $this->height,
            JS;
        }
        $width = '';
        if (isset($this->width)) {
            $width = <<<JS
            width: $this->width,
            JS;
        }

        $js .= <<<JS
        const plugins{$this->id} = [];
        const pluginsOpts{$this->id} = {};
        if ({$nl} == 1) {
            plugins{$this->id}.push('grapesjs-preset-newsletter');
            pluginsOpts{$this->id}['grapesjs-preset-newsletter'] = {};
        }
        if ($stL == 1) {
            plugins{$this->id}.push(inlineStorage{$this->id});
        }

        const data{$this->id} = {
            container: '#{$this->id}',
            fromElement: {$fromElement},
            {$height}
            {$width}
            storageManager: $storage,
            panels: { defaults: [] },
            plugins: plugins{$this->id},
            pluginsOpts: pluginsOpts{$this->id},
            styleManager: {
                sectors: [
                    {
                        name: 'Typography',
                        open: false,
                        buildProps: ['font-family', 'font-size', 'font-weight', 'letter-spacing'],
                        properties: [
                            {
                                property: 'font-family',
                                name: 'Font', 
                                type: 'select',
                                defaults: 'Arial, Helvetica, sans-serif',
                                options: [
                                    { name: 'Arial', value: 'Arial, Helvetica, sans-serif' },
                                    { name: 'Arial Black', value: 'Arial Black, Gadget, sans-serif' },
                                    { name: 'Brush Script MT', value: 'Brush Script MT, sans-serif' },
                                    { name: 'Calibri', value: 'calibri, sans-serif' },
                                    { name: 'Comic Sans MS', value: 'Comic Sans MS, cursive, sans-serif' },
                                    { name: 'Courier New', value: 'Courier New, Courier, monospace' },
                                    { name: 'Georgia', value: 'Georgia, serif' },
                                    { name: 'Helvetica', value: 'Helvetica, sans-serif' },
                                    { name: 'Impact', value: 'Impact, Charcoal, sans-serif' },
                                    { name: 'Lucida Sans Unicode', value: 'Lucida Sans Unicode, Lucida Grande, sans-serif' },
                                    { name: 'Open Sans', value: 'open-sans, sans-serif' },
                                    { name: 'Playfair Display', value: 'playfair-display' },
                                    { name: 'Tahoma', value: 'Tahoma, Geneva, sans-serif' },
                                    { name: 'Times New Roman', value: 'Times New Roman, Times, serif' },
                                    { name: 'Trebuchet MS', value: 'Trebuchet MS, Helvetica, sans-serif' },
                                    { name: 'Verdana', value: 'Verdana, Geneva, sans-serif' },
                                ],
                            },
                        ],
                    },
                ],
            },
        };

        const editor{$this->id} = grapesjs.init(data{$this->id});
        if ({$nl} == 1) {
            const pnm = editor{$this->id}.Panels;
            editor{$this->id}.on('load', function() {
                pnm.getButton('options', 'sw-visibility').set('active', 1);
            });
        }

        if ({$jcl} == 1) {
            editor{$this->id}.loadProjectData({$this->jsonContent});
        }

        {$this->extraScripts}

        // Export variable for easy outside access.
        var selEditor = editor{$this->id};
        JS;

        return $js;
    }


    /**
     * The styles of the builder.
     * 
     * @return  string
     * 
     * @access  public
     */

    public function CSS(): string {
        if (isset($this->customStyles)) {
            return $this->customStyles;
        }
        return <<<CSS
        .grapes-js {
            --gjs-arrow: 4px solid rgba(0,0,0,.7);
            border: 3px solid #444;
            border-radius: var(--panel-border-radius);
        }

        /* Primary color for the background */
        .gjs-one-bg {
            background-color: #fff!important;
        }

        /* Secondary color for the text color */
        .gjs-two-color {
            color: var(--main-colour)!important;
        }

        /* Quaternary color for the text color */
        .gjs-four-color,
        .gjs-four-color-h:hover {
            color: var(--main-accent)!important;
        }

        .gjs-sm-field input,
        .gjs-clm-select input,
        .gjs-clm-field input,
        .gjs-sm-field select,
        .gjs-clm-select select,
        .gjs-clm-field select {
            color: inherit;
        }

        .gjs-field-arrow-u {
            border-bottom: var(--gjs-arrow);
        }

        .gjs-field-arrow-d {
            border-top: var(--gjs-arrow);
        }

        .gjs-d-s-arrow {
            border-top: var(--gjs-arrow)!important;
        }

        div.gjs-pn-panel.gjs-pn-views-container.gjs-one-bg.gjs-two-color * {
            font-size: var(--gjs-font-size);
        }

        div.gjs-pn-panel.gjs-pn-devices-c.gjs-one-bg.gjs-two-color {
            left: 0;
        }

        div.gjs-pn-panel.gjs-pn-options.gjs-one-bg.gjs-two-color {
            left: 120px;
            right: unset;
        }

        div.gjs-pn-panel.gjs-pn-views.gjs-one-bg.gjs-two-color {
            border-bottom: unset;
            width: unset;
            min-width: 15%;
        }

        div.gjs-pn-panel.gjs-pn-views-container.gjs-one-bg.gjs-two-color {
            min-width: 15%;
        }
        CSS;
    }
}
