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

namespace BplanBase\Globals;


use BplanBase\Globals\Enums\LocationAccessPointSubType;
use BplanBase\Globals\Enums\LocationRoomSubType;
use BplanBase\Globals\Enums\LocationSiteSubType;
use BplanBase\Globals\Http\Middleware\InitApiEnv;
use BplanBase\Globals\Http\Middleware\InitWebEnv;
use BplanBase\Globals\Http\Middleware\LoadTranslations;
use BplanBase\Globals\Http\Middleware\LogApiRequest;
use BplanBase\Globals\Models\PrinterType;
use BplanBase\Globals\Registries\DynamicFormRegistry;
use BplanBase\Globals\Registries\LocationTypeRegistry;
use BplanBase\Globals\Registries\SeederRegistry;
use BplanBase\Globals\Support\LivewireComponentAutoDiscovery;
use Illuminate\Database\Events\QueryExecuted;
use Illuminate\Routing\Router;
use Livewire\Livewire;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\ServiceProvider;


/**
 * Package Service Provider Class
 *
 * @version     9.0.0 / 2026-06-08
 * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
 */
class PackageServiceProvider extends ServiceProvider
{


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


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


    /**
     * @var         string
     */
    const TENANT_QUERY_PARAMETER = 'init-tenant';


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


    /**
     * @var         string
     */
    protected string $_basePath;


    /**
     * @var         string
     */
    protected string $_baseNamespace = 'BplanBase\\Globals';


    /**
     * Indicates if loading of the provider is deferred.
     *
     * @var         bool
     */
    protected bool $defer = false;


    /**
     * @var         int
     */
    protected int $_packageKey = 1000;


    /**
     * @var         string
     */
    protected string $_packageName = 'globals';


    /**
     * @var         string
     */
    protected string $_vendorPackageName = 'bplan-base/globals';


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


    private static array $_translationCallbacks = [];


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


    /**
     *
     * @return 	    void
     *
     * @version     1.2.0 / 2025-08-03
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    protected function _prepareSqlLogging(): void
    {
        if (Config::get('globals.db.sql-logging', false) === true) {
            /*
            **  Register log channel at runtime. */
            $this->_registerSqlLogChannel();
            /*
            **  Log SQL queries. */
            DB::listen(function (QueryExecuted $Query) {
                Log::channel('sql')->info(
                    "\n".'  '.$Query->sql
                        ."\n".'  Bindings: '.((empty($Query->bindings)) ? '-' : print_r($Query->bindings, true))
                        ."\n".'  Runtime : '.$Query->time.'s'
                        ."\n"
                );
            });
        }
    } // _prepareSqlLogging()


    /**
     *
     * @return      void
     *
     * @version     1.0.0 / 2025-08-03
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    protected function _registerAppLogChannel(): void
    {
        $logging = Config::get('logging');
        $logLevel = Config::get('logging.channels.slack.level');

        $logging['channels']['applog'] = [
            'driver' => 'daily',
            'path' => storage_path('logs/app/app.log'),
            'level' => $logLevel,
            'days' => 14,
            'replace_placeholders' => true,
        ];
        $logging['channels']['applog-composite'] = [
            'driver' => 'stack',
            'channels' => ['applog', $logging['default']],
            'ignore_exceptions' => false,
        ];
        Config::set('logging', $logging);

    } // _registerAppLogChannel()


    /**
     *
     * @return      void
     *
     * @version     1.3.0 / 2026-06-06
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    protected function _registerFacadeAlias()
    {
        if (!class_exists('AppLog')) {
            class_alias(Facades\AppLog::class,    'AppLog');
            class_alias(Enums\LogCode::class,     'LogCode');
            class_alias(Facades\Registry::class,  'Registry');
            class_alias(Logging\TraceCode::class, 'TraceCode');
        }
    } // _registerFacadeAlias()


    /**
     *
     * @return      void
     *
     * @version     1.0.0 / 2025-08-03
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    protected function _registerSqlLogChannel(): void
    {
        /*
        **  Check whether the channel already exists to avoid duplicate entries. */
        if (!array_key_exists('sql', Config::get('logging.channels', []))) {
            Config::set('logging.channels.sql', [
                'driver' => 'daily',
                'path' => storage_path('logs/sql/sql.log'),
                'level' => 'info',
                'days' => 14,
                'replace_placeholders' => true,
            ]);
        }
    } // _registerSqlLogChannel()


    /**
     * Bootstrap the application events.
     *
     * @return      void
     *
     * @version     2.21.0 / 2026-06-08
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    public function boot(Router $Router): void
    {
        /*
        **  Grant SystemManager users and SuperAdmin role holders all permissions unconditionally.
        **  Returning null falls through to normal Spatie permission checks. */
        Gate::before(function ($user, string $ability): ?bool {
            if ($user->hasMinAccessLevel('SystemManager') === true) {
                return true;
            }
            if ($user->hasRole('SuperUser') === true) {
                return true;
            }
            return null;
        });
        /*
        **  Inform Laravel about translations. */
        $this->loadTranslationsFrom($this->_basePath.'/lang', $this->_packageName);

        if ($this->app->runningInConsole()) {
            $this->commands([
                Console\Commands\GeneratePostmanCollection::class,
                Console\Commands\GlobalsSetup::class,
                // Console\Commands\HealthCheck::class,
                // Console\Commands\SelfCheck::class,
                Console\Commands\SendTestMail::class,
                Console\Commands\TriggerPrintJob::class,
                Console\Commands\WarmUpApp::class,
            ]);
            require_once $this->_basePath.'/helpers/seeder-helpers.php';

            $this->publishes([
                $this->_basePath.'/etc/setup/src/config/http-service.php' => config_path('http-service.php'),

            ], 'http-service');

            SeederRegistry::register($this->_packageKey,        $this->_vendorPackageName, \BplanBase\Globals\Database\Seeders\DatabaseSeeder::class);
            SeederRegistry::register($this->_packageKey + 2000, $this->_vendorPackageName, \BplanBase\Globals\Database\Seeders\PostDatabaseSeeder::class);
            SeederRegistry::register($this->_packageKey + 3000, $this->_vendorPackageName, \BplanBase\Globals\Database\Seeders\FinishSeeder::class);
        }
        $this->_registerAppLogChannel();
        $this->_prepareSqlLogging();
        /*
        **  Inform Laravel about views, define a namespace for Blade components and register Livewire components. */
        $this->loadViewsFrom($this->_basePath.'/resources/views', $this->_packageName);

        Blade::componentNamespace('BplanBase\\Globals\\View\\Components', $this->_packageName);
        LivewireComponentAutoDiscovery::discover($this->_baseNamespace.'\\Livewire', $this->_basePath.'/src/Livewire', $this->_packageName);
        Livewire::addPersistentMiddleware([InitWebEnv::class]);

        if ($this->app->runningInConsole()) {
            /*
            **  Inform Laravel about migrations. */
            $this->loadMigrationsFrom($this->_basePath.'/database/migrations');
        }
        /*
        **  Load package routes. */
        $this->loadRoutesFrom($this->_basePath.'/routes/api.php');
        $this->loadRoutesFrom($this->_basePath.'/routes/web.php');
        $this->loadRoutesFrom($this->_basePath.'/routes/webhooks.php');
        /*
        **  loadRoutesFrom() prüft intern ob ein Route-Cache existiert — wenn ja, überspringt
        **  es die Datei komplett.
        **
        **  Broadcast-Channels sind aber keine HTTP-Routen — sie werden nicht gecacht.
        **  Broadcast::channel() registriert Authorization-Callbacks, die bei jedem Request
        **  neu aufgebaut werden müssen. Würde loadRoutesFrom() verwendet werden und der
        **  Route-Cache wäre aktiv, würden die Channel-Registrierungen komplett ausbleiben.
        **  Broadcast würde dann alle Channel-Authentifizierungen verweigern.
        **
        **  require stellt sicher, dass die Channels immer registriert werden, unabhängig
        **  vom Route-Cache-Status. */
        require $this->_basePath.'/routes/channels.php';

        DynamicFormRegistry::register(PrinterType::class, 'config_template', 'label');

        LocationTypeRegistry::register('AccessPoint', array_column(LocationAccessPointSubType::cases(), 'name'));
        LocationTypeRegistry::register('Other',       ['Misc']);
        LocationTypeRegistry::register('Room',        array_column(LocationRoomSubType::cases(), 'name'));
        LocationTypeRegistry::register('Site',        array_column(LocationSiteSubType::cases(), 'name'));

    } // boot()


    /**
     * Register the service provider.
     *
     * @return      void
     *
     * @version     1.18.0 / 2026-06-06
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    public function register()
    {
        if (!defined('ENV_START')) {
            define('ENV_START', microtime(true));
            define('ENV_TIME', time());
        }
        $this->_basePath = str_replace('\\', '/', realpath(__DIR__.'/..'));
        /*
        **  Funktionsbibliotheken laden.
        **  Hier werden nur die Helper geladen werden, die frühzeitig im Bootstrapping zur
        **  Verfügung stehen müssen. Alle anderen können regulär in composer.json eingebunden
        **  werden. */
        require_once $this->_basePath.'/helpers/env-helpers.php';
        /*
        **  Load the package configuration. */
        $this->mergeConfigFrom($this->_basePath.'/config/'.$this->_packageName.'.php', $this->_packageName);
        $this->mergeConfigFrom($this->_basePath.'/config/dynamic-data-handling.php', 'dynamic-data-handling');
        $this->mergeConfigFrom($this->_basePath.'/config/setup.php', $this->_packageName.'-setup');

        if ($this->app->runningInConsole() === false) {
            $currentModule = request()->route()?->getPrefix() ?? null;

            $this->app->register(Providers\EventServiceProvider::class);
            $this->app->register(Providers\NavigationServiceProvider::class);
            $this->app->register(Providers\ViewServiceProvider::class);
            $this->app->register(Providers\ExtensionNavigationServiceProvider::class);
            $this->app->register(Providers\CoreNavigationServiceProvider::class);
        }
        $this->_registerFacadeAlias();

    } // register()


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


    /**
     *
     * @param       callable $callback
     *
     * @return      void
     *
     * @version     1.0.0 / 2026-05-03
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    public static function addTranslations(callable $callback): void
    {
        static::$_translationCallbacks[] = $callback;

    } // addTranslations()


    /**
     *
     * @param       string $group
     *              "api", "web"
     *
     * @param       string $getForUsage
     *              "append", "prepend"
     *
     * @param       array|string $append
     *
     * @param       array|string $prepend
     *
     * @return      array
     *
     * @version     1.2.0 / 2026-05-03
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    public static function getMiddlewares(string $group, string $getForUsage, array|string $append = [], array|string $prepend = []): array
    {
        $middlewares = [];

        switch ($group) {
            case 'api':
                if ($getForUsage === 'append') {
                    $middlewares = [
                        LogApiRequest::class,
                        InitApiEnv::class,
                    ];
                } else {
                    //
                }
                break;

            case 'web':
                if ($getForUsage === 'append') {
                    $middlewares = [
                        InitWebEnv::class,
                        LoadTranslations::class,
                    ];
                } else {
                    //
                }
                break;

            default:
                //
        }
        if (!empty($append)) {
            $middlewares = array_merge($middlewares, Arr::wrap($append));
        }
        if (!empty($prepend)) {
            $middlewares = array_merge(Arr::wrap($prepend), $middlewares);
        }
        return $middlewares;

    } // getMiddlewares()


    /**
     *
     * @return      array
     *
     * @version     1.0.0 / 2026-05-03
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    public static function getTranslationCallbacks(): array
    {
        return self::$_translationCallbacks;

    } // getTranslationCallbacks()


} // class PackageServiceProvider extends ServiceProvider {}
