<?php

namespace Filament\Support;

use Composer\InstalledVersions;
use Filament\Commands\CacheComponentsCommand;
use Filament\Support\Assets\AssetManager;
use Filament\Support\Assets\Css;
use Filament\Support\Assets\Js;
use Filament\Support\Colors\ColorManager;
use Filament\Support\Commands\AboutCommand as FilamentAboutCommand;
use Filament\Support\Commands\Aliases\MakeIssueCommand as MakeIssueCommandAlias;
use Filament\Support\Commands\AssetsCommand;
use Filament\Support\Commands\CheckTranslationsCommand;
use Filament\Support\Commands\InstallCommand;
use Filament\Support\Commands\MakeIssueCommand;
use Filament\Support\Commands\OptimizeClearCommand;
use Filament\Support\Commands\OptimizeCommand;
use Filament\Support\Commands\UpgradeCommand;
use Filament\Support\Components\ComponentManager;
use Filament\Support\Components\Contracts\ScopedComponentManager;
use Filament\Support\Facades\FilamentAsset;
use Filament\Support\Icons\IconManager;
use Filament\Support\View\ViewManager;
use Illuminate\Foundation\Console\AboutCommand;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;
use Illuminate\Support\Stringable;
use Laravel\Octane\Events\RequestReceived;
use Spatie\LaravelPackageTools\Package;
use Spatie\LaravelPackageTools\PackageServiceProvider;
use Symfony\Component\HtmlSanitizer\HtmlSanitizer;
use Symfony\Component\HtmlSanitizer\HtmlSanitizerConfig;
use Symfony\Component\HtmlSanitizer\HtmlSanitizerInterface;

class SupportServiceProvider extends PackageServiceProvider
{
    public function configurePackage(Package $package): void
    {
        $package
            ->name('filament')
            ->hasCommands([
                AssetsCommand::class,
                CheckTranslationsCommand::class,
                FilamentAboutCommand::class,
                InstallCommand::class,
                MakeIssueCommand::class,
                MakeIssueCommandAlias::class,
                OptimizeClearCommand::class,
                OptimizeCommand::class,
                UpgradeCommand::class,
            ])
            ->hasConfigFile()
            ->hasTranslations()
            ->hasViews();
    }

    public function packageBooted(): void
    {
        FilamentAsset::register([
            Js::make('async-alpine', __DIR__ . '/../dist/async-alpine.js'),
            Css::make('support', __DIR__ . '/../dist/index.css'),
            Js::make('support', __DIR__ . '/../dist/index.js'),
        ], 'filament/support');

        Blade::directive('captureSlots', function (string $expression): string {
            return "<?php \$slotContents = get_defined_vars(); \$slots = collect({$expression})->mapWithKeys(fn (string \$slot): array => [\$slot => \$slotContents[\$slot] ?? null])->all(); unset(\$slotContents) ?>";
        });

        Blade::directive('filamentScripts', function (string $expression): string {
            return "<?php echo \Filament\Support\Facades\FilamentAsset::renderScripts({$expression}) ?>";
        });

        Blade::directive('filamentStyles', function (string $expression): string {
            return "<?php echo \Filament\Support\Facades\FilamentAsset::renderStyles({$expression}) ?>";
        });

        Blade::extend(function ($view) {
            return preg_replace('/\s*@trim\s*/m', '', $view);
        });

        Str::macro('sanitizeHtml', function (string $html): string {
            return app(HtmlSanitizerInterface::class)->sanitize($html);
        });

        Stringable::macro('sanitizeHtml', function (): Stringable {
            /** @phpstan-ignore-next-line */
            return new Stringable(Str::sanitizeHtml($this->value));
        });

        Str::macro('ucwords', function (string $value): string {
            return implode(' ', array_map(
                [Str::class, 'ucfirst'],
                explode(' ', $value),
            ));
        });

        Stringable::macro('ucwords', function (): Stringable {
            /** @phpstan-ignore-next-line */
            return new Stringable(Str::ucwords($this->value));
        });

        if (class_exists(InstalledVersions::class)) {
            $packages = [
                'filament',
                'forms',
                'notifications',
                'support',
                'tables',
            ];

            AboutCommand::add('Filament', static fn () => [
                'Version' => InstalledVersions::getPrettyVersion('filament/support'),
                'Packages' => collect($packages)
                    ->filter(fn (string $package): bool => InstalledVersions::isInstalled("filament/{$package}"))
                    ->join(', '),
                'Views' => function () use ($packages): string {
                    $publishedViewPaths = collect($packages)
                        ->filter(fn (string $package): bool => is_dir(resource_path("views/vendor/{$package}")));

                    if (! $publishedViewPaths->count()) {
                        return '<fg=green;options=bold>NOT PUBLISHED</>';
                    }

                    return "<fg=red;options=bold>PUBLISHED:</> {$publishedViewPaths->join(', ')}";
                },
                'Blade Icons' => function (): string {
                    return File::exists(app()->bootstrapPath('cache/blade-icons.php'))
                        ? '<fg=green;options=bold>CACHED</>'
                        : '<fg=yellow;options=bold>NOT CACHED</>';
                },
                'Panel Components' => function (): string {
                    if (! class_exists(CacheComponentsCommand::class)) {
                        return '<options=bold>NOT AVAILABLE</>';
                    }

                    $path = app()->bootstrapPath('cache/filament/panels');

                    return File::isDirectory($path) && ! File::isEmptyDirectory($path)
                        ? '<fg=green;options=bold>CACHED</>'
                        : '<fg=yellow;options=bold>NOT CACHED</>';
                },
            ]);
        }

        if ($this->app->runningInConsole()) {
            $this->publishes([
                $this->package->basePath('/../config/filament.php') => config_path('filament.php'),
            ], 'filament-config');

            if (method_exists($this, 'optimizes')) {
                $this->optimizes(
                    optimize: 'filament:optimize', /** @phpstan-ignore-line */
                    clear: 'filament:optimize-clear', /** @phpstan-ignore-line */
                    key: 'filament', /** @phpstan-ignore-line */
                );
            }
        }
    }

    public function packageRegistered(): void
    {
        $this->app->scoped(
            AssetManager::class,
            fn () => new AssetManager,
        );

        $this->app->scoped(
            ScopedComponentManager::class,
            fn () => $this->app->make(ComponentManager::class)->clone(),
        );
        $this->app->booted(fn () => ComponentManager::resolveScoped());
        class_exists(RequestReceived::class) && Event::listen(RequestReceived::class, fn () => ComponentManager::resolveScoped());

        $this->app->scoped(
            ColorManager::class,
            fn () => new ColorManager,
        );

        $this->app->scoped(
            IconManager::class,
            fn () => new IconManager,
        );

        $this->app->scoped(
            ViewManager::class,
            fn () => new ViewManager,
        );

        $this->app->scoped(
            HtmlSanitizerInterface::class,
            fn (): HtmlSanitizer => new HtmlSanitizer(
                (new HtmlSanitizerConfig)
                    ->allowSafeElements()
                    ->allowRelativeLinks()
                    ->allowRelativeMedias()
                    ->allowAttribute('class', allowedElements: '*')
                    ->allowAttribute('style', allowedElements: '*')
                    ->withMaxInputLength(500000),
            ),
        );
    }
}
