<?php
/**
 * Main Application Class
 *
 * @package ZenoChatbot\Framework\Foundation
 */

namespace ZenoChatbot\Framework\Foundation;

if (!defined('ABSPATH')) {
    exit;
}

/**
 * Application container and lifecycle manager
 */
class Application extends Container
{

    /**
     * Service providers
     *
     * @var array
     */
    protected $serviceProviders = [];

    /**
     * Loaded service providers
     *
     * @var array
     */
    protected $loadedProviders = [];

    /**
     * Booted service providers
     *
     * @var array
     */
    protected $bootedProviders = [];

    /**
     * Application version
     *
     * @var string
     */
    public $version = ZENOCHATBOT_VERSION;

    /**
     * Application path
     *
     * @var string
     */
    public $basePath;

    /**
     * Route configurations
     *
     * @var array
     */
    private $routeConfig = [];

    /**
     * Current request type
     *
     * @var string|null
     */
    private $requestType = null;

    /**
     * Indicates if the application has been booted
     *
     * @var bool
     */
    protected $booted = false;

    /**
     * Stack of currently resolving abstractions to detect circular dependencies
     * @var array
     */
    protected $resolvingStack = [];

    /**
     * Constructor
     */
    public function __construct($basePath = null)
    {
        $this->basePath = $basePath ?: ZENOCHATBOT_PLUGIN_DIR;
        $this->registerBaseBindings();
    }

    /**
     * Create a new application builder instance
     *
     * @param string $basePath
     * @return ApplicationBuilder
     */
    public static function configure($basePath)
    {
        return new ApplicationBuilder($basePath);
    }

    /**
     * Create a new application instance
     *
     * @param string $basePath
     * @return static
     */
    public static function create($basePath = null)
    {
        return new static($basePath);
    }

    /**
     * Configure routes for the application
     *
     * @param array $routes
     * @return $this
     */
    public function routes($routes = [])
    {
        $this->routeConfig = $routes;
        return $this;
    }

    /**
     * Get route configuration
     *
     * @return array
     */
    public function getRouteConfig()
    {
        return $this->routeConfig;
    }

    /**
     * Get configuration value using dot notation
     *
     * @param string $key
     * @param mixed $default
     * @return mixed
     * @throws \Exception
     */
    public function config($key = null, $default = null)
    {
        $config = $this->make('config');

        if ($key === null) {
            return $config;
        }

        // Use Repository's get method for dot notation
        if (method_exists($config, 'get')) {
            return $config->get($key, $default);
        }

        // Fallback to array access for backward compatibility
        return $this->getConfigValue($config, $key, $default);
    }

    /**
     * Get configuration value using dot notation
     *
     * @param array $config
     * @param string $key
     * @param mixed $default
     * @return mixed
     */
    private function getConfigValue($config, $key, $default = null)
    {

        if (strpos($key, '.') === false) {
            return $config[$key] ?? $default;
        }

        $keys = explode('.', $key);
        $value = $config;

        foreach ($keys as $segment) {
            if (!is_array($value) || !array_key_exists($segment, $value)) {
                return $default;
            }

            $value = $value[$segment];
        }

        return $value;
    }

    /**
     * Set configuration value using dot notation
     *
     * @param string $key
     * @param mixed $value
     * @return void
     * @throws \Exception
     */
    public function setConfig($key, $value)
    {

        $config = $this->make('config');

        if (strpos($key, '.') === false) {
            $config[$key] = $value;
            $this->singleton('config', $config);
            return;
        }

        $keys = explode('.', $key);

        $current = &$config;

        foreach ($keys as $i => $segment) {
            if ($i === count($keys) - 1) {
                $current[$segment] = $value;
            } else {
                if (!isset($current[$segment]) || !is_array($current[$segment])) {
                    $current[$segment] = [];
                }
                $current = &$current[$segment];
            }
        }

        $this->singleton('config', $config);
    }

    /**
     * Create a response instance
     *
     * @param mixed $data
     * @return \ZenoChatbot\Framework\Http\ResponseBuilder
     */
    public function response($data = [])
    {
        return new \ZenoChatbot\Framework\Http\ResponseBuilder($this, $data);
    }

    /**
     * Detect and set the current request type
     *
     * @return string
     */
    public function detectRequestType()
    {
        if ($this->requestType === null) {
            DetectRequestType::detect($this);
        }

        return $this->requestType;
    }

    /**
     * Get the current request type
     *
     * @return string
     */
    public function getRequestType()
    {
        return $this->detectRequestType();
    }

    /**
     * Set the request type manually (for testing)
     *
     * @param string $type
     * @return $this
     */
    public function setRequestType($type)
    {
        $this->requestType = $type;
        return $this;
    }

    /**
     * Register a service provider with the application
     *
     * @param ServiceProvider|string $provider
     * @param bool $force
     * @return ServiceProvider
     */
    public function register($provider, $force = false)
    {
        if (($registered = $this->getProvider($provider)) && !$force) {
            return $registered;
        }

        // If the given "provider" is a string, we will resolve it, passing in the
        // application instance automatically for the developer. This is simply
        // a more convenient way of specifying your service provider classes.
        if (is_string($provider)) {
            $provider = $this->resolveProvider($provider);
        }

        $provider->register();

        // If there are bindings / singletons set as properties on the provider we
        // will spin through them and register them with the application, which
        // serves as a convenience layer while registering a lot of bindings.
        if (property_exists($provider, 'bindings')) {
            foreach ($provider->bindings as $key => $value) {
                $this->bind($key, $value);
            }
        }

        if (property_exists($provider, 'singletons')) {
            foreach ($provider->singletons as $key => $value) {
                $this->singleton($key, $value);
            }
        }

        $this->markAsRegistered($provider);

        // If the application has already booted, we will call this boot method on
        // the provider class so it has an opportunity to do its boot logic and
        // will be ready for any usage by this developer's application logic.
        if ($this->isBooted()) {
            $this->bootProvider($provider);
        }

        return $provider;
    }

    /**
     * Get the registered service provider instance if it exists
     *
     * @param ServiceProvider|string $provider
     * @return ServiceProvider|null
     */
    public function getProvider($provider)
    {
        return array_values($this->getProviders($provider))[0] ?? null;
    }

    /**
     * Get the registered service provider instances if any exist
     *
     * @param ServiceProvider|string $provider
     * @return array
     */
    public function getProviders($provider)
    {
        $name = is_string($provider) ? $provider : get_class($provider);

        return array_filter($this->serviceProviders, function ($value) use ($name) {
            return $value instanceof $name;
        });
    }

    /**
     * Resolve a service provider instance from the class name
     *
     * @param string $provider
     * @return ServiceProvider
     */
    public function resolveProvider($provider)
    {
        return new $provider($this);
    }

    /**
     * Mark the given provider as registered
     *
     * @param ServiceProvider $provider
     * @return void
     */
    protected function markAsRegistered($provider)
    {
        $this->serviceProviders[] = $provider;
        $this->loadedProviders[get_class($provider)] = true;
    }

    /**
     * Boot the application's service providers
     *
     * @return void
     */
    public function boot()
    {
        if ($this->isBooted()) {
            return;
        }

        // Boot all registered service providers
        array_walk($this->serviceProviders, function ($provider) {
            $this->bootProvider($provider);
        });

        $this->booted = true;
    }

    /**
     * Boot the given service provider
     *
     * @param ServiceProvider $provider
     * @return void
     */
    protected function bootProvider($provider)
    {
        if (method_exists($provider, 'boot')) {
            $provider->boot();
        }

        $this->bootedProviders[] = $provider;
    }

    /**
     * Determine if the application has booted
     *
     * @return bool
     */
    public function isBooted()
    {
        return $this->booted ?? false;
    }


    /**
     * Get application version
     *
     * @return string
     */
    public function version()
    {
        return $this->version;
    }

    /**
     * Get base path
     *
     * @param string $path
     * @return string
     */
    public function basePath($path = '')
    {
        return $this->basePath . ($path ? DIRECTORY_SEPARATOR . $path : '');
    }

    /**
     * Get resource path
     *
     * @param string $path
     * @return string
     */
    public function resourcePath($path = '')
    {
        return $this->basePath('resources' . ($path ? DIRECTORY_SEPARATOR . $path : ''));
    }

    /**
     * Get asset path
     *
     * @param string $path
     * @return string
     */
    public function assetPath($path = '')
    {
        return $this->basePath('assets' . ($path ? DIRECTORY_SEPARATOR . $path : ''));
    }

    /**
     * Get asset URL
     *
     * @param string $path
     * @return string
     */
    public function assetUrl($path = '')
    {
        return ZENOCHATBOT_PLUGIN_URL . 'assets/' . ltrim($path, '/');
    }

    /**
     * Check if application is in debug mode
     *
     * @return bool
     */
    public function isDebug()
    {
        return defined('WP_DEBUG') && WP_DEBUG;
    }


    /**
     * Register base bindings (called by ApplicationBuilder)
     */
    public function registerBaseBindings()
    {
        // Bind the application instance
        $this->instance('app', $this);
        $this->instance(Application::class, $this);
        $this->instance(Container::class, $this);
    }

    /**
     * Register core container aliases (called by ApplicationBuilder)
     */
    public function registerCoreContainerAliases()
    {
        $this->alias(Application::class, 'app');
        $this->alias(Container::class, 'container');
        $this->alias(\ZenoChatbot\Framework\Exceptions\Handler::class, 'exception.handler');
        $this->alias(\ZenoChatbot\Framework\Http\Request::class, 'request');
    }

    /**
     * Load configuration files from config directory (called by ApplicationBuilder)
     *
     * @return array
     */
    public function loadConfigFiles()
    {
        $config = [];
        $configPath = $this->basePath('config');

        if (!is_dir($configPath)) {
            return $config;
        }

        foreach (glob($configPath . '/*.php') ?: [] as $file) {

            // if file name is str.php, skip it
            if (basename($file, '.php') === 'str') {
                continue;
            }


            try {
                $data = require $file;
                if (is_array($data)) {
                    $config[basename($file, '.php')] = $data;
                }
            } catch (\Throwable $e) {
                if (defined('WP_DEBUG') && WP_DEBUG) {
                    error_log('Zeno Chatbot: Config loading error for ' . $file . ': ' . $e->getMessage());
                }
            }
        }

        return $config;
    }


    /**
     * Get config path
     *
     * @param string $path
     * @return string
     */
    public function configPath($path = '')
    {
        return $this->basePath('config' . ($path ? DIRECTORY_SEPARATOR . $path : ''));
    }

    /**
     * Handle a REST request action via the framework RestHandler.
     * Central entry point so routes do not instantiate handlers directly.
     *
     * @param mixed $action Controller string (e.g., "UserController@show"), callable, or [Class, method]
     * @param \WP_REST_Request $request
     * @return \WP_REST_Response|\WP_Error
     */
    public function handleRestRequest($action, $request)
    {
        return \ZenoChatbot\Framework\Foundation\Handler\RestHandler::create($this)
            ->handle($action, $request)
        ;
    }

    /**
     * Handle application shutdown
     */
    public function terminate()
    {
        // Terminate all service providers
        foreach ($this->serviceProviders as $provider) {

            if (method_exists($provider, 'terminate')) {
                $provider->terminate();
            }
        }

        // Clear container instances
        $this->flush();
    }

    public function log($content)
    {
        $this->make(Logger::class)->log($content);
    }
}
