<?php

namespace Statamic\Providers;

use Carbon\Carbon;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\AboutCommand;
use Illuminate\Foundation\Http\Middleware\TrimStrings;
use Illuminate\Http\Request;
use Illuminate\Routing\Router;
use Illuminate\Support\ServiceProvider;
use Statamic\CP\CarbonAsVueComponent;
use Statamic\Facades;
use Statamic\Facades\Addon;
use Statamic\Facades\Site;
use Statamic\Facades\Stache;
use Statamic\Facades\Token;
use Statamic\Fields\FieldsetRecursionStack;
use Statamic\Jobs\HandleEntrySchedule;
use Statamic\Sites\Sites;
use Statamic\Stache\Query\RevisionQueryBuilder;
use Statamic\Statamic;
use Statamic\Tokens\Handlers\LivePreview;
use Statamic\View\Scaffolding\TemplateGenerator;

class AppServiceProvider extends ServiceProvider
{
    protected $root = __DIR__.'/../..';

    protected $configFiles = [
        'antlers', 'api', 'assets', 'autosave', 'cp', 'editions', 'forms', 'git', 'graphql', 'live_preview', 'markdown', 'oauth', 'protect', 'revisions',
        'routes', 'search', 'static_caching', 'stache', 'system', 'templates', 'users', 'webauthn',
    ];

    public function boot()
    {
        $this->app->booted(function () {
            $this->registerMiddlewareGroup();
            Statamic::runBootedCallbacks();
            $this->loadRoutesFrom("{$this->root}/routes/routes.php");
        });

        $this->app[\Illuminate\Contracts\Http\Kernel::class]
            ->pushMiddleware(\Statamic\Http\Middleware\PoweredByHeader::class)
            ->pushMiddleware(\Statamic\Http\Middleware\CheckComposerJsonScripts::class)
            ->pushMiddleware(\Statamic\Http\Middleware\CheckMultisite::class)
            ->pushMiddleware(\Statamic\Http\Middleware\StopImpersonating::class);

        $this->loadViewsFrom("{$this->root}/resources/views", 'statamic');

        collect($this->configFiles)->each(function ($config) {
            $this->publishes(["{$this->root}/config/$config.php" => config_path("statamic/$config.php")], 'statamic');
        });

        $this->publishes([
            "{$this->root}/resources/users" => resource_path('users'),
        ], 'statamic');

        $this->publishes([
            "{$this->root}/resources/dist" => public_path('vendor/statamic/cp'),
        ], 'statamic-cp');

        $this->publishes([
            "{$this->root}/resources/dist-dev" => public_path('vendor/statamic/cp-dev'),
        ], 'statamic-cp-dev');

        $this->publishes([
            "{$this->root}/resources/dist-frontend" => public_path('vendor/statamic/frontend'),
        ], 'statamic-frontend');

        $this->loadTranslationsFrom("{$this->root}/lang", 'statamic');
        $this->loadJsonTranslationsFrom("{$this->root}/lang");

        $this->publishes([
            "{$this->root}/lang" => app()->langPath().'/vendor/statamic',
        ], 'statamic-translations');

        $this->loadViewsFrom("{$this->root}/resources/views/extend", 'statamic');

        $this->publishes([
            "{$this->root}/resources/views/extend/forms" => resource_path('views/vendor/statamic/forms'),
        ], 'statamic-forms');

        $this->publishes([
            "{$this->root}/resources/views/extend/scaffolding" => resource_path('views/vendor/statamic/scaffolding'),
        ], 'statamic-scaffolding');

        $this->app['redirect']->macro('cpRoute', function ($route, $parameters = []) {
            return $this->to(cp_route($route, $parameters));
        });

        Carbon::macro('asVueComponent', static function (?array $options = null) {
            return (new CarbonAsVueComponent)(self::this(), $options);
        });

        Request::macro('statamicToken', function () {
            if ($token = $this->token ?? $this->header('X-Statamic-Token')) {
                return Token::find($token);
            }
        });

        Request::macro('isLivePreview', function () {
            return optional($this->statamicToken())->handler() === LivePreview::class;
        });

        TrimStrings::skipWhen(function (Request $request) {
            $route = config('statamic.cp.route');

            return ! $route || $request->is($route.'/*');
        });

        $this->addAboutCommandInfo();

        $this->app->make(Schedule::class)->job(HandleEntrySchedule::class)->everyMinute();
    }

    public function register()
    {
        collect($this->configFiles)->each(function ($config) {
            $this->mergeConfigFrom("{$this->root}/config/$config.php", "statamic.$config");
        });

        $this->app->singleton(Sites::class);

        collect([
            \Statamic\Contracts\Entries\EntryRepository::class => \Statamic\Stache\Repositories\EntryRepository::class,
            \Statamic\Contracts\Taxonomies\TermRepository::class => \Statamic\Stache\Repositories\TermRepository::class,
            \Statamic\Contracts\Taxonomies\TaxonomyRepository::class => \Statamic\Stache\Repositories\TaxonomyRepository::class,
            \Statamic\Contracts\Entries\CollectionRepository::class => \Statamic\Stache\Repositories\CollectionRepository::class,
            \Statamic\Contracts\Globals\GlobalRepository::class => \Statamic\Stache\Repositories\GlobalRepository::class,
            \Statamic\Contracts\Globals\GlobalVariablesRepository::class => \Statamic\Stache\Repositories\GlobalVariablesRepository::class,
            \Statamic\Contracts\Assets\AssetContainerRepository::class => \Statamic\Stache\Repositories\AssetContainerRepository::class,
            \Statamic\Contracts\Structures\StructureRepository::class => \Statamic\Structures\StructureRepository::class,
            \Statamic\Contracts\Structures\CollectionTreeRepository::class => \Statamic\Stache\Repositories\CollectionTreeRepository::class,
            \Statamic\Contracts\Structures\NavTreeRepository::class => \Statamic\Stache\Repositories\NavTreeRepository::class,
            \Statamic\Contracts\Structures\NavigationRepository::class => \Statamic\Stache\Repositories\NavigationRepository::class,
            \Statamic\Contracts\Assets\AssetRepository::class => \Statamic\Assets\AssetRepository::class,
            \Statamic\Contracts\Forms\FormRepository::class => \Statamic\Forms\FormRepository::class,
            \Statamic\Contracts\Forms\SubmissionRepository::class => \Statamic\Stache\Repositories\SubmissionRepository::class,
            \Statamic\Contracts\Tokens\TokenRepository::class => \Statamic\Tokens\FileTokenRepository::class,
            \Statamic\Contracts\Addons\SettingsRepository::class => \Statamic\Addons\FileSettingsRepository::class,
            \Statamic\Contracts\Revisions\RevisionRepository::class => \Statamic\Revisions\RevisionRepository::class,
        ])->each(function ($concrete, $abstract) {
            if (! $this->app->bound($abstract)) {
                Statamic::repository($abstract, $concrete);
            }
        });

        $this->app->singleton(\Statamic\Contracts\Data\DataRepository::class, function ($app) {
            return (new \Statamic\Data\DataRepository)
                ->setRepository('entry', \Statamic\Contracts\Entries\EntryRepository::class)
                ->setRepository('term', \Statamic\Contracts\Taxonomies\TermRepository::class)
                ->setRepository('collection', \Statamic\Contracts\Entries\CollectionRepository::class)
                ->setRepository('taxonomy', \Statamic\Contracts\Taxonomies\TaxonomyRepository::class)
                ->setRepository('global', \Statamic\Contracts\Globals\GlobalRepository::class)
                ->setRepository('asset', \Statamic\Contracts\Assets\AssetRepository::class)
                ->setRepository('user', \Statamic\Contracts\Auth\UserRepository::class);
        });

        $this->app->singleton(\Statamic\Fields\BlueprintRepository::class, function () {
            return (new \Statamic\Fields\BlueprintRepository)
                ->setDirectories(config('statamic.system.blueprints_path'))
                ->setFallback('default', function () {
                    return \Statamic\Facades\Blueprint::make()->setContents([
                        'tabs' => [
                            'main' => [
                                'sections' => [
                                    [
                                        'display' => __('Content'),
                                        'fields' => [
                                            [
                                                'handle' => 'content',
                                                'field' => ['type' => 'markdown', 'localizable' => true],
                                            ],
                                        ],
                                    ],
                                ],
                            ],
                        ],
                    ]);
                });
        });

        $this->app->singleton(\Statamic\Fields\FieldsetRepository::class, function () {
            return (new \Statamic\Fields\FieldsetRepository)
                ->setDirectory(config('statamic.system.fieldsets_path'));
        });

        $this->app->singleton(FieldsetRecursionStack::class);

        $this->app->bind(RevisionQueryBuilder::class, function ($app) {
            return new RevisionQueryBuilder($app['stache']->store('revisions'));
        });

        collect([
            'entries' => fn () => Facades\Entry::query(),
            'form-submissions' => fn () => Facades\FormSubmission::query(),
            'terms' => fn () => Facades\Term::query(),
            'assets' => fn () => Facades\Asset::query(),
            'users' => fn () => Facades\User::query(),
        ])->each(function ($binding, $alias) {
            app()->bind('statamic.queries.'.$alias, $binding);
        });

        $this->app->instance('statamic.query-scopes', collect());

        $this->app->bind('statamic.imaging.guzzle', function () {
            return new \GuzzleHttp\Client;
        });

        $this->app->bind(TemplateGenerator::class, function () {
            return (new TemplateGenerator)
                ->withCoreGenerators()
                ->templateLanguage(config('statamic.templates.language', 'antlers'))
                ->indentType(config('statamic.templates.style.indent_type', 'space'))
                ->indentSize(config('statamic.templates.style.indent_size', 4))
                ->finalNewline(config('statamic.templates.style.final_newline', false))
                ->preferComponentSyntax(config('statamic.templates.antlers.use_components', false));
        });
    }

    protected function registerMiddlewareGroup()
    {
        $router = $this->app->make(Router::class);

        collect([
            \Statamic\Http\Middleware\StacheLock::class,
            \Statamic\Http\Middleware\HandleToken::class,
            \Statamic\Http\Middleware\Localize::class,
            \Statamic\Http\Middleware\AddViewPaths::class,
            \Statamic\Http\Middleware\AuthGuard::class,
            \Statamic\Http\Middleware\RedirectIfTwoFactorSetupIncomplete::class,
            \Statamic\StaticCaching\Middleware\Cache::class,
        ])->each(fn ($middleware) => $router->pushMiddlewareToGroup('statamic.web', $middleware));
    }

    protected function addAboutCommandInfo()
    {
        if (! class_exists(AboutCommand::class)) {
            return;
        }

        $addons = Addon::all();

        AboutCommand::add('Statamic', [
            'Version' => fn () => Statamic::version().' '.(Statamic::pro() ? '<fg=yellow;options=bold>PRO</>' : 'Solo'),
            'Addons' => $addons->count(),
            'Stache Watcher' => fn () => $this->stacheWatcher(),
            'Static Caching' => config('statamic.static_caching.strategy') ?: 'Disabled',
            'Sites' => fn () => $this->sitesAboutCommandInfo(),
        ]);

        foreach ($addons as $addon) {
            AboutCommand::add('Statamic Addons', $addon->package(), $addon->version());
        }
    }

    private function stacheWatcher()
    {
        $status = Stache::isWatcherEnabled() ? 'Enabled' : 'Disabled';

        if (config('statamic.stache.watcher') === 'auto') {
            $status .= ' (auto)';
        }

        return $status;
    }

    private function sitesAboutCommandInfo()
    {
        if (($sites = Site::all())->count() === 1) {
            return 1;
        }

        // If there are 5 or fewer sites, list all their names.
        // If there are more than 5, list the first 3 and append "and n more".
        $summary = $sites->count() <= 5
            ? $sites->map->name()->join(', ')
            : $sites->take(3)->map->name()->join(', ').', and '.($sites->count() - 3).' more';

        return $sites->count().' ('.$summary.')';
    }
}
