<?php
/**
 * Base Export
 *
 * @version     1.0.$Revision:$
 * @version     SVN: $Id:$
 * @package     bplan-base/globals
 * @subpackage  Exports
 * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
 * @copyright   Copyright (C) 2025 bplan-solutions GmbH & Co. KG <https://www.bplan-solutions.de/>
 * /Δ\
 */

namespace BplanBase\Globals\Foundation\Maatwebsite\Excel;


use Illuminate\Support\Arr;
use LogicException;
use Maatwebsite\Excel\Concerns\FromGenerator;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;


/**
 * Base Export
 *
 * @version     1.0.0 / 2025-10-23
 * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
 */
class BaseExport implements WithHeadings, WithMapping
{


/* +++ TRAITS +++ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */


/* +++ CLASS CONSTANTS +++ ++++++++++++++++++++++++++++++++++++++++++++++++++ */


    /**
     * @var     BOOL_FALSE
     */
    const BOOL_FALSE = 'false';


    /**
     * @var     BOOL_TRUE
     */
    const BOOL_TRUE  = 'true';


    /**
     * @var     CAST_BOOL_TO_INT
     */
    const CAST_BOOL_TO_INDIVIDUAL = 0;


    /**
     * @var     CAST_BOOL_TO_INT
     */
    const CAST_BOOL_TO_INT        = 1;


    /**
     * @var     CAST_BOOL_TO_STRING
     */
    const CAST_BOOL_TO_STRING     = 2;


/* +++ OBJECT MEMBERS +++ +++++++++++++++++++++++++++++++++++++++++++++++++++ */


    /**
     * Steuert das Format für Boolean-Werte
     *
     * Es kann entweder eine der CAST_BOOL_TO_*-Konstanten oder ein Array zugewiesen
     * werden. Im Falle des Arrays wird ein numerisch indiziertes Array mit zwei
     * Elementen erwartet, in dessen Index 0 der Wert für FALSE erwartet wird und
     * dessen Index 1 den Wert für TRUE enthalten sollte.
     *
     * Beispiel:
     *      protected array|int $_booleanCast = [
     *          'Off',
     *          'On'
     *      ];
     *
     * Alternativ zu der Array-Variante können auch die beiden Konstanten BOOL_FALSE
     * und BOOL_TRUE in der abgeleiteten Klasse überschrieben und mit eigenen Werten
     * versehen werden. In dem Fall muss $_booleanCast auf den Wert CAST_BOOL_TO_INDIVIDUAL
     * gesetzt werden.
     *
     * Beispiel:
     *
     *      const BOOL_FALSE = 'OFF';
     *      const BOOL_TRUE  = 'ON';
     *
     *      protected array|int $_booleanCast = self::CAST_BOOL_TO_INDIVIDUAL;
     *
     */
    protected array|int $_booleanCast = self::CAST_BOOL_TO_INT;


    /**
     * Liste der Spalten, die nicht im Export enthalten sein sollen
     *
     * Beispiel:
     *      protected array $_excludeColumns = [
     *          'uuid',
     *          'created_at',
     *          'deleted_at',
     *          'updated_at',
     *      ];
     *
     * Muss nicht überschrieben/definiert werden, wenn die abgeleitete Klasse das Interface
     * FromGenerator implementiert. In dem Fall sollte die erforderliche generator-Methode
     * sowieso nur die gewünschten Felder ausliefern.
     *
     * @var     array $_excludeColumns
     */
    protected array $_excludeColumns = [];


    /**
     * Muss von der abgeleiteten Klasse überschrieben werden, wenn diese keine eigene
     * headings-Methode implementiert.
     *
     * @var     protected string $_modelClass
     */
    protected string $_modelClass;


    /**
     * @var     bool $_withHeadings
     */
    protected bool $_withHeadings = false;


/* +++ CLASS MEMBERS +++ ++++++++++++++++++++++++++++++++++++++++++++++++++++ */


/* +++ OBJECT METHODS +++ +++++++++++++++++++++++++++++++++++++++++++++++++++ */


    /**
     *
     * @return      array
     *
     * @version     1.0.0 / 2025-10-23
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    public function headings(): array
    {
        if ($this->_withHeadings === false) {
            return [];
        }
        if ($this instanceof FromGenerator) {
            throw new LogicException('Class '.static::class.' has to implement the method headings().');
        }
        $attributes = $this->_modelClass::getAttributeKeys();

        $headings = [];

        if (!empty($attributes)) {
            $attributes = Arr::except(array_flip($attributes), $this->_excludeColumns);

            $headings = array_keys($attributes);
        }
        return $headings;

    } // headings()


    /**
     *
     * @param       array|object $item
     *
     * @return      array
     *
     * @version     1.0.0 / 2025-10-23
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    public function map($item): array
    {
        /*
        **  Alle Attribute als Array holen. */
        if (is_array($item)) {
            $attributes = $item;
        } else {
            $attributes = $item->toArray();
        }
        if (($this instanceof FromGenerator) === false) {
            /*
            **  Unerwünschte Felder entfernen.
            **  Das ist nicht erforderlich, wenn das Interface FromGenerator implementiert
            **  ist. In dem Fall sollte die generator-Methode sowieso nur die erforderlichen
            **  Felder liefern. */
            $attributes = Arr::except($attributes, $this->_excludeColumns);
        }
        return array_map(function ($value) {
            static $initialized = false;

            static $optionNo;
            static $optionYes;

            if ($initialized === false) {
                $optionNo = __('globals::global.option.no');
                $optionYes = __('globals::global.option.yes');

                $initialized = true;
            }
            if (is_array($value) || is_object($value)) {
                /*
                **  Arrays und Objekte werden in JSON umgewandelt. */
                $value = json_encode($value);

            } elseif (is_bool($value)) {
                if (is_array($this->_booleanCast)) {
                    /*
                    **  Individuelle Werte aus der Objektvariablen $_booleanCast verwenden. */
                    $value = ($value === true)
                        ? $this->_booleanCast[0]
                        : $this->_booleanCast[1];

                } else {
                    if ($this->_booleanCast === self::CAST_BOOL_TO_INDIVIDUAL) {
                        /*
                        **  Individuelle Werte aus den BOOL_*-Konstanten verwenden. */
                        $value = ($value === true)
                            ? (static::BOOL_TRUE ?? self::BOOL_TRUE)
                            : (static::BOOL_FALSE ?? self::BOOL_FALSE);

                    } elseif ($this->_booleanCast === self::CAST_BOOL_TO_INT) {
                        /*
                        **  Boolean-Werte in Integer-Werte umwandeln. */
                        $value = ($value === true) ? '1' : '0';

                    } elseif ($this->_booleanCast === self::CAST_BOOL_TO_STRING) {
                        /*
                        **  Die nein-/ja-Auswahlwerte aus den Locale-Dateien verwenden. */
                        $value = ($value === true) ? $optionYes : $optionNo;

                    } else {
                        /*
                        **  "true" und "false" als Strings einsetzen. Excel interpretiert das dann
                        **  so, dass für TRUE "WAHR" und für FALSE ein leerer Wert eingesetzt wird. */
                        $value = ($value === true) ? 'true' : 'false';
                    }
                }
            } else {
                /*
                **  Alles andere wird explizit nach String gecastet. */
                $value = (string) $value;
            }
            return $value;

        }, $attributes);

    } // map()


/* +++ CLASS METHODS +++ ++++++++++++++++++++++++++++++++++++++++++++++++++++ */


} // class BaseExport implements WithHeadings, WithMapping {}
