<?php

namespace onespace\tools\widgets\ApexCharts;

use onespace\tools\assets\ApexChartsAsset;
use onespace\tools\helpers\Position;
use onespace\tools\helpers\traits\GetView;
use Yii;
use yii\base\Widget;
use yii\helpers\Json;

/**
 * This widget is used to generate Apex Charts on the fly in PHP.
 * 
 * A chart should be called as follows:
 * 
 * ```php
 * echo ApexCharts::widget( [
 *  'options' => [
 *      ...
 *  ],
 * ] );
 * ```
 * 
 * To fill in the options, please refer to the Apex Charts documentation, and conform
 * your options to as an assosiative array matching the object parsed to the Javascript
 * renderer.
 * 
 * @see https://apexcharts.com/docs/
 * @see https://apexcharts.com/javascript-chart-demos/
 * 
 * @author  Gareth Palmer <gareth@one-space.co.za>
 */

final class ApexCharts extends Widget {

    use GetView;

    /**
     * The parsed options used by the widget and ultimately rendered
     * into a chart.
     * 
     * @var array   $options
     * 
     * @access  public
     */

    public array $options;

    /**
     * This property allows you to parse the data and render the chart yourself rather than
     * doing it automatically for you. This allows you - in the case of a `$this->renderPartial`
     * to do the rending in Javascript rather than rely on the PHP runtime (which won't work 
     * when fetched in).
     * 
     * Is only relevant when working with `ApexCharts::widget`
     * 
     * Default is false - the chart will load automatically on page load.
     * 
     * @var boolean $deferLoading
     * 
     * @access  public
     */

    public bool $deferLoading = false;

    /**
     * Set the specific id for the chart.
     * 
     * @var string  $id
     * 
     * @access  public
     */

    public string $id;


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

    public function run(): string {
        parent::run();

        ApexChartsAsset::register($this->getView());

        $this->id ??= str_replace('-', '_', Yii::$app->security->generateRandomString(8));
        $options = Json::encode($this->options);
        $script = "<script defer>const chart{$this->id} = new ApexCharts(document.getElementById('{$this->id}'), {$options}); chart{$this->id}.render();</script>";
        if ($this->deferLoading) {
            return "<div data-chart='{$options}' id='{$this->id}'></div>";
        }
        return "<div id='{$this->id}'></div>{$script}";
    }


    /**
     * ## Example 
     * ```php
     * echo ApexCharts::widget(['options' => [...]]);
     * ```
     * 
     * @see https://apexcharts.com/docs
     * @see https://apexcharts.com/javascript-chart-demos/
     * 
     * @notes
     * To get a JS function working, you are basically out of luck. There is a
     * workaround to get things going in php but the way `Yii::widget` parses
     * things, this always gets converted to text. Therefore it is recommended
     * If you need to do that, execute the `chart` method instead.
     */

    public static function widget($config = []) {
        return parent::widget($config);
    }


    /**
     * Returns a Javascript enabled chart, rendering the JS involved in spinning up
     * the script in a more traditional way.
     * 
     * @param   string $options     Javascript snippet including the const assignment, containing the options
     *                              to be parsed to the chart. For example:
     *                              ```php
     *                              echo ApexCharts::chart(<<<JS
     *                                  const options = {
     *                                      series: [44, 55, 13, 33],
     *                                      labels: ['Apple', 'Mango', 'Orange', 'Watermelon']
     *                                  }
     *                              JS);
     *                              ```
     * @param   string|Position $x  The alignment on the x access. Should be parsed as the Const Position::position.
     *                              Will be converted to enum once on 8.1
     *                              ## Options:
     *                              - Position::LEFT
     *                              - Position::CENTER
     *                              - Position::RIGHT
     * @param   string|Position $y  The alignment on the y access. Should be parsed as the Const Position::position.
     *                              **Important** - The container element by default shrinks to the height of the container.
     *                              Therefore if you want Y-axis alignment, you will need to increase the height of this container.
     *                              Will be converted to enum once on 8.1
     *                              ## Options:
     *                              - Position::TOP
     *                              - Position::MIDDLE
     *                              - Position::BOTTOM
     * 
     * @todo    Replace string param with Position once using the enum
     * 
     * @return  string
     * 
     * @see https://apexcharts.com/docs
     * @see https://apexcharts.com/javascript-chart-demos/
     * 
     * @static
     */

    public static function chart(string $options, ?string $id = null, string $x = Position::LEFT, string $y = Position::TOP): string {
        if (is_null($id)) {
            $id = str_replace('-', '_', 'apc' . Yii::$app->security->generateRandomString(12));
        } else {
            $id = str_replace('-', '_', 'apc' . $id);
        }
        $view = self::getPageView();

        ApexChartsAsset::register($view);

        $pos_x = match ($x) {
            Position::LEFT => 'flex-start',
            Position::CENTER => 'center',
            Position::RIGHT => 'flex-end',
        };

        $pos_y = match ($y) {
            Position::TOP => 'flex-start',
            Position::MIDDLE => 'center',
            Position::BOTTOM => 'flex-end',
        };

        $view->registerCss(<<<CSS
        .center-chart{$id} {
            display: flex;
            justify-content: $pos_x;
            align-items: $pos_y;
        }
        CSS);

        $options_var = explode(' ', $options)[1];

        $view->registerJs($options);
        $view->registerJs(<<<JS
        const chart{$id} = new ApexCharts(document.querySelector("#{$id}"), $options_var);
        chart{$id}.render();
        JS);

        return "<div class='center-chart{$id}'><div id='{$id}'></div></div>";
    }


    /**
     * Handle the task of updating the chart from remote JSON data.
     * 
     * @param   string  $path       The full or relative path to the API which returns JSON
     *                              data which corrisponds to the chart.
     * @param   string  $id         The id of the chart to update.
     * @param   array   $keys       The keys of the parsed data to extract the parsed data into the graph.
     *                              ## Example
     * 
     *                              If the fetch returns a JSON:
     * 
     *                              ```
     *                              // JSON
     *                              {"total_zero": "1", "total_greater_than_zero": "0"}
     *                              ```
     *
     *                              You would parse to `$keys`:
     * 
     *                              ```php
     *                              ['total_zero', 'total_greater_than_zero']
     *                              ```
     * 
     *                              In whatever order you prefer.
     * 
     * @param   integer $interval   The interval between refreshes. Default is 60000 (1 minute).
     */

    public static function chart_updater(string $path, string $id, array $keys, int $interval = 60000): void {
        $id = str_replace('-', '_', 'apc' . $id);
        $view = self::getPageView();
        $keys_json = json_encode($keys);
        $view->registerJs(<<<JS
        setInterval(() => {
            fetch('$path')
                .then(response => response.json())
                .then(data => {
                    const series = [];
                    const keys = JSON.parse('{$keys_json}');
                    keys.forEach(key => {
                        series.push(parseInt(data[key]));
                    });
                    chart{$id}.updateSeries(series);
                })
                .catch(error => console.error(error));
        }, $interval);
        JS);
    }
}
