<?php

namespace App\Support;

use App\Contracts\AgentContract;
use App\Contracts\PricingModelContract;
use App\Support\AgentRegistry;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;

/**
 * AbstractModuleServiceProvider
 *
 * Base class for all ChargePanda module service providers.
 *
 * Each module's ModuleServiceProvider must extend this class and implement
 * modulePath() and moduleNamespace(). The helpers provided here cover the
 * most common registration tasks so that module authors write minimal boilerplate.
 *
 * Minimal example:
 *
 *   class ModuleServiceProvider extends AbstractModuleServiceProvider
 *   {
 *       protected function modulePath(): string      { return dirname(__DIR__); }
 *       protected function moduleNamespace(): string { return 'Modules\\Saas'; }
 *
 *       public function boot(): void
 *       {
 *           $this->loadMigrations();
 *           $this->loadWebRoutes();
 *           $this->loadViews();
 *       }
 *   }
 */
abstract class AbstractModuleServiceProvider extends ServiceProvider
{
    /**
     * Absolute path to the module's root directory (where module.json lives).
     *
     * Typically: dirname(__DIR__) when ModuleServiceProvider is inside src/,
     * or just __DIR__ when it sits at the module root.
     */
    abstract protected function modulePath(): string;

    /**
     * The PSR-4 namespace root for this module (e.g. "Modules\\Saas").
     */
    abstract protected function moduleNamespace(): string;

    /**
     * Additional Artisan command classes provided by this module.
     * Override in the concrete provider to register commands.
     *
     * @var class-string[]
     */
    protected array $commands = [];

    // -------------------------------------------------------------------------
    // Registration helpers
    // -------------------------------------------------------------------------

    /**
     * Load the module's database migrations.
     * Only runs when Artisan is active so that they appear in migrate:status etc.
     */
    protected function loadMigrations(): void
    {
        $path = $this->modulePath() . '/database/migrations';

        if (is_dir($path)) {
            $this->loadMigrationsFrom($path);
        }
    }

    /**
     * Load the module's web routes, wrapped in the web middleware group.
     *
     * @param array $extra Additional route-group attributes (e.g. ['prefix' => 'app']).
     */
    protected function loadWebRoutes(array $extra = []): void
    {
        $this->loadRouteFile('routes/web.php', array_merge([
            'middleware' => ['web'],
            'namespace'  => $this->moduleNamespace() . '\\Http\\Controllers',
        ], $extra));
    }

    /**
     * Load the module's API routes, wrapped in the api middleware group.
     *
     * @param array $extra Additional route-group attributes (e.g. ['prefix' => 'api/saas']).
     */
    protected function loadApiRoutes(array $extra = []): void
    {
        $this->loadRouteFile('routes/api.php', array_merge([
            'middleware' => ['api'],
            'namespace'  => $this->moduleNamespace() . '\\Http\\Controllers',
        ], $extra));
    }

    /**
     * Load the module's Blade views under a given namespace.
     * Defaults to the module slug (e.g. "saas" → {{ saas::layout }}).
     */
    protected function loadViews(?string $namespace = null): void
    {
        $path = $this->modulePath() . '/resources/views';

        if (is_dir($path)) {
            $this->loadViewsFrom($path, $namespace ?? $this->moduleSlug());
        }
    }

    /**
     * Merge the module's config file into the application config.
     * The config key defaults to the module slug.
     */
    protected function loadModuleConfig(?string $key = null): void
    {
        $key  = $key ?? $this->moduleSlug();
        $path = $this->modulePath() . '/config/' . $key . '.php';

        if (file_exists($path)) {
            $this->mergeConfigFrom($path, $key);
        }
    }

    /**
     * Register module Artisan commands listed in $this->commands.
     * Only runs in console context.
     */
    protected function loadCommands(): void
    {
        if ($this->app->runningInConsole() && !empty($this->commands)) {
            $this->commands($this->commands);
        }
    }

    /**
     * Register a custom pricing model with the core PricingModelRegistry.
     *
     * Call this inside your module's boot() method:
     *
     *   $this->registerPricingModel(new MyCustomPricingModel());
     *
     * The model's key() string is stored in pricing_tiers.pricing_model and must
     * be unique across all registered modules.
     */
    protected function registerPricingModel(PricingModelContract $handler): void
    {
        $this->app->make(PricingModelRegistry::class)->register($handler);
    }

    /**
     * Add a link to the admin sidebar for this module.
     *
     * Call this inside your module's boot() method:
     *
     *   $this->registerAdminMenuItem(
     *       label: 'AI CV Builder',
     *       route: 'ch-admin.ai-cv-builder.index',
     *       icon:  'ti-id-badge',
     *       order: 10,
     *   );
     *
     * The item will appear in the sidebar between the core nav and Settings,
     * sorted by $order (lower numbers appear first).
     */
    protected function registerAdminMenuItem(string $label, string $route, string $icon = 'ti-package', int $order = 50): void
    {
        $this->app->make('module.admin.menu')->push(compact('label', 'route', 'icon', 'order'));
    }

    /**
     * Add a link to the customer account sidebar for this module.
     *
     * Call this inside your module's boot() method:
     *
     *   $this->registerAccountMenuItem(
     *       label:  'AI CV Builder',
     *       route:  'my-module.account.index',
     *       icon:   'description',          // Material Symbols icon name
     *       order:  10,
     *       routes: ['my-module.account.*'], // route patterns for active-state highlight
     *   );
     *
     * The item appears in the customer account sidebar (next to Downloads, Orders, etc.),
     * sorted by $order (lower numbers appear first).
     *
     * $routes accepts the same patterns as request()->routeIs() and is used to
     * highlight the nav item as active when any of those routes are current.
     * Defaults to [$route] when omitted.
     */
    protected function registerAccountMenuItem(
        string    $label,
        string    $route,
        string    $icon      = 'extension',
        int       $order     = 50,
        array     $routes    = [],
        ?callable $condition = null,
    ): void {
        $this->app->make('module.account.menu')->push([
            'label'     => $label,
            'route'     => $route,
            'icon'      => $icon,
            'order'     => $order,
            'routes'    => $routes ?: [$route],
            'condition' => $condition, // optional: callable returning bool, evaluated at render time
        ]);
    }

    /**
     * Add a link to the admin Settings dropdown for this module.
     *
     * The $section string must match the filename of a blade partial that the
     * module publishes to resources/views/admin/settings/partials/{section}.blade.php,
     * OR that the module registers via loadViews() under a custom path.
     *
     *   $this->registerAdminSettingsLink(label: 'SaaS', section: 'saas', order: 10);
     */
    protected function registerAdminSettingsLink(string $label, string $section, int $order = 50): void
    {
        $this->app->make('module.admin.settings')->push(compact('label', 'section', 'order'));
    }

    /**
     * Inject a tab into the plan edit modal in the Product Manager.
     *
     * Call this inside your module's boot() method:
     *
     *   $this->registerPlanTab(
     *       key:        'key-pool',
     *       label:      'Key Pool',
     *       contentUrl: route('license-keys.admin.plan-tab'),
     *   );
     *
     * The tab's contentUrl is called via GET with ?plan_id=X and must return an
     * HTML fragment (no <html>/<body>). The fragment may contain <form> elements
     * whose action attributes point to the module's own save endpoints — the
     * Product Manager will POST them via axios and refresh the tab on success.
     *
     * For new (unsaved) plans the modal renders a "Save first" placeholder
     * automatically; the contentUrl is not called in that case.
     */
    protected function registerPlanTab(string $key, string $label, string|\Closure $contentUrl): void
    {
        $this->app->make('plan.tabs')->push(compact('key', 'label', 'contentUrl'));
    }

    /**
     * Register an AI agent with the core AgentRegistry.
     *
     * Call this inside your module's boot() method:
     *
     *   $this->registerAgent(KeywordResearchAgent::class);
     *
     * The agent's slug() must be globally unique across all registered modules.
     * Throws if the slug is already taken or the class does not implement AgentContract.
     *
     * @param  class-string<AgentContract>  $agentClass
     */
    protected function registerAgent(string $agentClass): void
    {
        $this->app->make(AgentRegistry::class)->register($agentClass);
    }

    /**
     * Replace the entire account sidebar nav with a custom partial.
     *
     * Call this inside your module's boot() method:
     *
     *   $this->registerAccountNavOverride(
     *       'seo-agents::partials.account.nav',
     *       fn() => setting('seo_agents.saas_mode'),
     *   );
     *
     * The first registered override whose condition evaluates true is used.
     * When $condition is null the override is always applied.
     */
    protected function registerAccountNavOverride(string $view, ?callable $condition = null): void
    {
        $this->app->make('module.account.nav')->push([
            'view'      => $view,
            'condition' => $condition,
        ]);
    }

    /**
     * Hide core account sidebar items when the given condition is true.
     *
     * Call this inside your module's boot() method:
     *
     *   $this->registerHiddenNavItems(
     *       ['downloads', 'orders', 'licenses', 'reviews'],
     *       fn() => setting('seo_agents.saas_mode'),
     *   );
     *
     * Supported slugs: 'downloads', 'orders', 'subscriptions', 'licenses',
     *                   'reviews', 'support', 'account'
     *
     * When $condition is null the items are always hidden.
     */
    protected function registerHiddenNavItems(array $slugs, ?callable $condition = null): void
    {
        foreach ($slugs as $slug) {
            $this->app->make('module.nav.hide')->push([
                'slug'      => $slug,
                'condition' => $condition,
            ]);
        }
    }

    /**
     * Register a view path to include when the translation scanner scans for
     * missing translation keys.
     *
     * Call this inside your module's boot() method:
     *
     *   $this->registerTranslationScanPath($this->modulePath() . '/resources/views');
     */
    protected function registerTranslationScanPath(string $path): void
    {
        if (is_dir($path)) {
            $this->app->make('module.translation.scan_paths')->push($path);
        }
    }

    /**
     * Register a public JS URL to be injected into the product page after the
     * core ChargePanda SDK. Use this alongside registerPricingModel() so that
     * your JS handler is available to CP.registerPricingModel().
     *
     * The URL is typically built with asset():
     *
     *   $this->registerFrontendScript(asset('modules/saas/js/pricing-models.js'));
     *
     * Scripts are output in registration order inside the ch_footer stack on
     * the product page (resources/themes/{theme}/views/pages/shop/show.blade.php).
     */
    protected function registerFrontendScript(string $url): void
    {
        $this->app->make('cp.module.scripts')->push($url);
    }

    // -------------------------------------------------------------------------
    // Metadata helpers
    // -------------------------------------------------------------------------

    /**
     * Return the parsed contents of this module's module.json.
     */
    public function meta(): array
    {
        $path = $this->modulePath() . '/module.json';

        if (!file_exists($path)) {
            return [];
        }

        return json_decode(file_get_contents($path), true) ?? [];
    }

    /**
     * Derive the module slug from module.json, falling back to the directory name.
     */
    protected function moduleSlug(): string
    {
        return $this->meta()['slug'] ?? strtolower(basename($this->modulePath()));
    }

    // -------------------------------------------------------------------------
    // Internal
    // -------------------------------------------------------------------------

    private function loadRouteFile(string $relative, array $attributes): void
    {
        $path = $this->modulePath() . '/' . $relative;

        if (!file_exists($path)) {
            return;
        }

        Route::group($attributes, function () use ($path) {
            require $path;
        });
    }
}
