<?php

namespace App\Providers;

use App\Auth\FlexibleEloquentUserProvider;
use App\Models\Budget;
use App\Models\Debt;
use App\Models\Goal;
use App\Models\SubscriptionItem;
use App\Models\Transaction;
use App\Models\User;
use App\Policies\BudgetPolicy;
use App\Policies\DebtPolicy;
use App\Policies\GoalPolicy;
use App\Policies\SubscriptionItemPolicy;
use App\Policies\TransactionPolicy;
use App\Services\FinanceDemoRepository;
use App\Services\FinanceRepository;
use App\Services\PreferencesStore;
use App\Support\ApplicationName;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->singleton(FinanceRepository::class, fn () => new FinanceRepository);
        $this->app->alias(FinanceRepository::class, FinanceDemoRepository::class);
    }

    public function boot(): void
    {
        Auth::provider('eloquent', function ($app, array $config) {
            return new FlexibleEloquentUserProvider($app['hash'], $config['model']);
        });

        Gate::policy(Transaction::class, TransactionPolicy::class);
        Gate::policy(Budget::class, BudgetPolicy::class);
        Gate::policy(Goal::class, GoalPolicy::class);
        Gate::policy(Debt::class, DebtPolicy::class);
        Gate::policy(SubscriptionItem::class, SubscriptionItemPolicy::class);
        Gate::define('access-admin-panel', fn (User $user): bool => $user->isSuperAdmin());

        View::composer('*', function ($view) {
            $name = $view->getName() ?? '';
            $isLivewireUpdate = request()->is('livewire/update') || request()->headers->has('X-Livewire');

            // Framework error views must not touch the database; Auth::id() and
            // ApplicationName can query MySQL and mask the real failure with nested 500s.
            if (str_starts_with($name, 'errors.')) {
                $view->with('preferences', PreferencesStore::defaults());
                $view->with('finance', app(FinanceRepository::class));
                $view->with('brandName', (string) config('app.name', 'Budgets'));

                return;
            }

            // Livewire update requests render small fragments frequently; keep them
            // off expensive global lookups to reduce per-interaction latency.
            if ($isLivewireUpdate) {
                $view->with('preferences', PreferencesStore::defaults());
                $view->with('finance', app(FinanceRepository::class));
                $view->with('brandName', (string) config('app.name', 'Budgets'));

                return;
            }

            try {
                $view->with('preferences', PreferencesStore::all());
                $view->with('finance', app(FinanceRepository::class));
                $view->with('brandName', ApplicationName::current());
            } catch (\Throwable $e) {
                // Production often fails here when migrations were not run (e.g. user_preferences,
                // app_settings) or when the DB is temporarily unavailable — including on /login
                // Livewire updates, which still run the global view composer.
                report($e);
                ApplicationName::forget();
                PreferencesStore::forgetMemo();
                $view->with('preferences', PreferencesStore::defaults());
                $view->with('finance', app(FinanceRepository::class));
                $view->with('brandName', (string) config('app.name', 'Budgets'));
            }
        });
    }
}
