<?php

namespace Webkul\Theme;

use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Vite;
use Illuminate\Support\Str;
use Webkul\Theme\Exceptions\ViterNotFound;

class Themes
{
    /**
     * Contains current activated theme code.
     *
     * @var string
     */
    protected $activeTheme = null;

    /**
     * Contains all themes.
     *
     * @var array
     */
    protected $themes = [];

    /**
     * Contains laravel default view paths.
     *
     * @var array
     */
    protected $laravelViewsPath;

    /**
     * Create a new themes instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->laravelViewsPath = Config::get('view.paths');

        $this->loadThemes();
    }

    /**
     * Return list of all registered themes.
     *
     * @return array
     */
    public function all()
    {
        return $this->themes;
    }

    /**
     * Return list of registered themes.
     *
     * @return array
     */
    public function getChannelThemes()
    {
        $themes = config('themes.shop', []);

        $channelThemes = [];

        foreach ($themes as $code => $data) {
            $channelThemes[] = new Theme(
                $code,
                $data['name'] ?? '',
                $data['assets_path'] ?? '',
                $data['views_path'] ?? '',
                isset($data['vite']) ? $data['vite'] : [],
            );

            if (! empty($data['parent'])) {
                $parentThemes[$code] = $data['parent'];
            }
        }

        return $channelThemes;
    }

    /**
     * Check if specified exists.
     *
     * @return bool
     */
    public function exists(string $themeName)
    {
        foreach ($this->themes as $theme) {
            if ($theme->code == $themeName) {
                return true;
            }
        }

        return false;
    }

    /**
     * Prepare all themes.
     *
     * @return void
     */
    public function loadThemes()
    {
        $parentThemes = [];

        /**
         * Octane safety: Handle request context more safely.
         */
        $isAdmin = false;

        try {
            $currentRequest = request();

            if ($currentRequest && $currentRequest->url()) {
                $isAdmin = Str::contains($currentRequest->url(), config('app.admin_url').'/');
            }
        } catch (\Exception $e) {
            /**
             * Fallback if request context is not available.
             */
            $isAdmin = false;
        }

        if ($isAdmin) {
            $themes = config('themes.admin', []);
        } else {
            $themes = config('themes.shop', []);
        }

        foreach ($themes as $code => $data) {
            $this->themes[] = new Theme(
                $code,
                $data['name'] ?? '',
                $data['assets_path'] ?? '',
                $data['views_path'] ?? '',
                $data['views_namespace'] ?? null,
                $data['vite'] ?? [],
            );

            if (! empty($data['parent'])) {
                $parentThemes[$code] = $data['parent'];
            }
        }

        foreach ($parentThemes as $childCode => $parentCode) {
            $child = $this->find($childCode);

            if ($this->exists($parentCode)) {
                $parent = $this->find($parentCode);
            } else {
                $parent = new Theme($parentCode);
            }

            $child->setParent($parent);
        }
    }

    /**
     * Enable theme.
     *
     * @return \Webkul\Theme\Theme
     */
    public function set(string $themeName)
    {
        if ($this->exists($themeName)) {
            $theme = $this->find($themeName);
        } else {
            $theme = new Theme($themeName);
        }

        $this->activeTheme = $theme;

        $paths = $theme->getViewPaths();

        foreach ($this->laravelViewsPath as $path) {
            if (! in_array($path, $paths)) {
                $paths[] = $path;
            }
        }

        Config::set('view.paths', $paths);

        $themeViewFinder = app('view.finder');

        $themeViewFinder->setPaths($paths);

        return $theme;
    }

    /**
     * Get current theme.
     *
     * @return \Webkul\Theme\Theme
     */
    public function current()
    {
        return $this->activeTheme ?? null;
    }

    /**
     * Get current theme's name.
     *
     * @return string
     */
    public function getName()
    {
        return $this->current()?->name ?? '';
    }

    /**
     * Find a theme by it's name.
     *
     * @return \Webkul\Theme\Theme
     */
    public function find(string $themeName)
    {
        foreach ($this->themes as $theme) {
            if ($theme->code == $themeName) {
                return $theme;
            }
        }

        throw new Exceptions\ThemeNotFound($themeName);
    }

    /**
     * Original view paths defined in `config.view.php`.
     *
     * @return array
     */
    public function getLaravelViewPaths()
    {
        return $this->laravelViewsPath;
    }

    /**
     * Return the asset URL of the current theme if a theme is found; otherwise, check from the namespace.
     *
     * @return string
     */
    public function url(string $filename, ?string $namespace = null)
    {
        $url = trim($filename, '/');

        /**
         * If the namespace is null, it means the theming system is activated. We use the request URI to
         * detect the theme and provide Vite assets based on the current theme.
         */
        if (empty($namespace)) {
            $currentTheme = $this->current();

            /**
             * Octane safety: Initialize theme if not set.
             */
            if (! $currentTheme) {
                $this->ensureThemeInitialized();

                $currentTheme = $this->current();
            }

            return $currentTheme->url($url);
        }

        /**
         * If a namespace is provided, it means the developer knows what they are doing and must create the
         * registry in the provided configuration. We will analyze based on that.
         */
        $viters = config('bagisto-vite.viters');

        if (empty($viters[$namespace])) {
            throw new ViterNotFound($namespace);
        }

        $viteUrl = trim($viters[$namespace]['package_assets_directory'], '/').'/'.$url;

        return Vite::useHotFile($viters[$namespace]['hot_file'])
            ->useBuildDirectory($viters[$namespace]['build_directory'])
            ->asset($viteUrl);
    }

    /**
     * Set bagisto vite in current theme.
     *
     * @param  mixed  $entryPoints
     * @return mixed
     */
    public function setBagistoVite($entryPoints, ?string $namespace = null)
    {
        /**
         * If the namespace is null, it means the theming system is activated. We use the request URI to
         * detect the theme and provide Vite assets based on the current theme.
         */
        if (empty($namespace)) {
            $currentTheme = $this->current();

            /**
             * Octane safety: Initialize theme if not set.
             */
            if (! $currentTheme) {
                $this->ensureThemeInitialized();

                $currentTheme = $this->current();
            }

            return $currentTheme->setBagistoVite($entryPoints);
        }

        /**
         * If a namespace is provided, it means the developer knows what they are doing and must create the
         * registry in the provided configuration. We will analyze based on that.
         */
        $viters = config('bagisto-vite.viters');

        if (empty($viters[$namespace])) {
            throw new ViterNotFound($namespace);
        }

        return Vite::useHotFile($viters[$namespace]['hot_file'])
            ->useBuildDirectory($viters[$namespace]['build_directory'])
            ->withEntryPoints($entryPoints);
    }

    /**
     * Ensure theme is properly initialized (Octane Safety).
     *
     * @return void
     */
    protected function ensureThemeInitialized()
    {
        if (! $this->activeTheme) {
            /**
             * Reload themes based on current request context.
             */
            $this->loadThemes();

            /**
             * Set default theme if no theme is set.
             */
            if (! $this->activeTheme && ! empty($this->themes)) {
                $defaultTheme = $this->themes[0];

                $this->set($defaultTheme->code);
            }
        }
    }
}
