<?php
namespace PrestaShop\Module\PolarisPrestaConnector;

/**
 * Objet de progression de synchronisation
 */
class SyncProgress
{
    /**
     * Objet de synchronisation
     */
    private Syncer|null $syncer = null;

    private $cfg = [
    ];

    private $data = [
        "steps" => [],
        "startTime" => null,
        "endTime" => null,
        "running" => false,
        "lastErrorMsg" => null,
        "aborted" => false,
    ];

    /**
     * Étape courante
     * 
     */
    private string|null $currentStep = null;

    /**
     * Si la configuration est définie, autosauvegarde
     */
    private SyncConfiguration|null $syncCfg = null;

    /**
     * Date de la dernière sauvegarde
     */
    private int $lastSave = 0;

    /**
     * Constructeur
     * 
     * @param Syncer $syncer Objet de synchronisation
     * @param SyncConfiguration|null $syncCfg Configuration de synchronisation ou null si pas de sauvegarde de l'avancement     
     */
    public function __construct(Syncer|null $syncer, SyncConfiguration|null $syncCfg = null)
    {
        $this->syncer = $syncer;
        $this->syncCfg = $syncCfg;
        if (isset($syncCfg))
        {
            $txt = $syncCfg->get(SyncConfiguration::CFG_SYNC_STATE);
            if (!empty($txt))
                $this->data["endTime"] = $txt["endTime"];
        }
    }    

    /**
     * Sérialisation de l'objet
     */
    public function toArray() : array
    {
        return $this->data;
    }

    /**
     * Désérialisation de l'objet
     */
    public static function fromArray(array|null $array)
    {
        $progress = new SyncProgress(null);
        if ($array)
        {
            foreach ($array as $key => $value)
                $progress->data[$key] = $value;
        }
        
        return $progress;
    }

    /**
     * Positionne le marqueur d'activité
     */
    public function setRunning(bool $running)
    {
        $this->data["running"] = $running;
    }

    /**
     * Renvoi vrai si l'état persisté indique une synchronisation en cours.
     *
     * C'est l'intention enregistrée : init() la positionne à true, end() la remet à false.
     */
    public function isRunning() : bool
    {
        return (bool)($this->data["running"] ?? false);
    }

    /**
     * Positionne le marqueur d'arrêt anormal (synchronisation interrompue avant end())
     */
    public function setAborted(bool $aborted)
    {
        $this->data["aborted"] = $aborted;
    }

    /**
     * Décide quand sauver
     * 
     * @param bool $force Force la sauvegarde
     */
    public function autosave($force = false)
    {
        if (!isset($this->syncCfg))
            return;

        // On sauve toutes les 5 secondes uniquement !
        if ($force || (time() - $this->lastSave) > 5)
        {
            $this->lastSave = time();
            $this->syncCfg->set(SyncConfiguration::CFG_SYNC_STATE, $this->toArray());
        }
    }

    /**
     * Démarre la progression
     */
    public function init()
    {
        $this->data["steps"] = [];        
        $this->data["startTime"] = time();        
        $this->data["running"] = true;
        $this->data["lastErrorMsg"] = "";

        $this->startStep("init", "Initialisation...", "Initialisé");        
    }

    /**
     * Commencer une étape
     * 
     * @param string $step Nom de l'étape
     * @param string|null $startMsg Message étape, si existant
     * @param string|null $pendingMsg Message étape, si existant
     * @param string|null $endMsg Message de fin, si existant
     */
    public function startStep(string $step, string|null $startMsg = null, string|null $endMsg = null, string|null $pendingMsg = null)
    {
        if ($this->syncer && $startMsg && $startMsg !== "Initialisation...")
            $this->syncer->audit("[INF] ".$startMsg);

        $this->cfg[$step] = [
            "start" => $startMsg ?? $step,
            "pending" => $pendingMsg ?? $startMsg ?? $endMsg ?? $step,
            "end" => $endMsg ?? $pendingMsg ?? $startMsg ?? $step,
        ];

        $this->currentStep = $step;
        $this->setStep($step, 0, 1, true);
    }

    /**
     * Finir une étape
     * 
     * @param string $step Nom de l'étape
     * @param string|null $msg Message de fin override
     */
    public function endStep(string $step, string|null $msg = null)
    {
        if ($msg)
            $this->cfg[$step]["end"] = $msg;

        $nb = $this->data['steps'][$step]['d'];

        $this->setStep($step, $nb, $nb);
    }

    /**
     * Finir une étape
     * 
     * @param string $step Nom de l'étape
     * @param string $errorMsg Message d'erreur
     */
    public function error(string $step, string $errorMsg)
    {
        $this->data['steps'][$step]['errMsg'] = $errorMsg;        
    }

    /**
     * Ajoute un élément de fait à l'étape spécifiée
     * 
     * @param string $step Nom de l'étape
     * @param int $nb Nombre d'éléments à ajouter (par défaut 1)
     */
    public function progress(string $step, int $nb = 1)
    {
        $currentStep = $this->data['steps'][$step]['d'] ?? 0;
        $currentMax = $this->data['steps'][$step]['n'] ?? 0;

        $nextStep = $currentStep + $nb;
        if ($nextStep >= $currentMax)
            $currentMax = $nextStep + 1;    // On ajuste le max si besoin car si le max <= fait, c'est le signe que le traitement est fini !

        $this->setStep($step, $nextStep, $currentMax);
    }

    /**
     * Finir une étape
     * 
     * @param string $step Nom de l'étape
     * @param int $max Nombre d'éléments max
     */
    public function setMax(string $step, int|null $max = null)
    {
        $this->setStep($step, null, $max ?? $step+1);
    }    

    /**
     * Ajout d'une étape
     * 
     * @param string $step Nom de l'étape
     * @param int|null $done Nombre d'éléments traités
     * @param int|null $nb Nombre total d'éléments
     * @param bool $forceSave Force la sauvegarde
     */
    private function setStep(string $step, int|null $done = null, int|null $nb = null, bool $forceSave = false)
    {
        // Puis, localisation de l'étape
        if (!isset($this->data['steps'][$step]))
        {
            // Si l'étape n'existe pas, on l'ajoute
            $this->data['steps'][$step] = [ 'm' => $step, 'd' => $done ?? 0, 'n' => $nb ?? 0 ];
        }

        // Remplacement
        if ($done !== null)
            $this->data['steps'][$step]['d'] = $done;
        if ($nb !== null)
            $this->data['steps'][$step]['n'] = $nb;

        // calcul de l'avancement
        $calc = $this->data['steps'][$step]['n'] ? 
                    ($this->data['steps'][$step]['d'] . '&nbsp;/&nbsp;' . $this->data['steps'][$step]['n']) :
                    $this->data['steps'][$step]['d'];
                            
        // Localisation du message ...
        $ended = $done === $nb;
        $this->data['steps'][$step]['e'] = $ended;
        $this->data['steps'][$step]['m'] = str_replace('{nb}', $calc, sprintf($this->cfg[$step][$ended ? "end" : "pending"] ?? $step, 
            $this->data['steps'][$step]['d'], 
            $this->data['steps'][$step]['n']));
    
        // sauvegarde ?
        $this->autosave($forceSave);
    }

    /**
     * Termine une synchronisation
     */
    public function end($errorMsg = null)
    {        
        if (!$this->currentStep)
        $this->currentStep = "Fin";

        $this->data["endTime"] = time();
        $this->data["running"] = false;

        if (isset($errorMsg) && !empty($errorMsg))
            $this->data["lastErrorMsg"] = '['.$this->currentStep . '] '.$errorMsg;
        else
            $this->data["lastErrorMsg"] = null;

        // Force !
        $this->autosave(true);
    }
}