<?php

declare(strict_types=1);

namespace App\Core;

use PDO;

/**
 * Abstract Base Controller
 *
 * Provides common functionality for all controllers in the application,
 * including view rendering, model loading, JSON responses, and CSRF validation.
 */
abstract class Controller
{
    /**
     * Database connection instance
     */
    protected PDO $db;

    /**
     * Base path for view files
     */
    private const VIEW_PATH = APP_ROOT . '/app/Views/';

    /**
     * Base namespace for model classes
     */
    private const MODEL_NAMESPACE = 'App\\Models\\';

    /**
     * Constructor — obtains a PDO connection via the getDb() function.
     */
    public function __construct()
    {
        $this->db = getDb();
    }

    /**
     * Render a view file with extracted data.
     *
     * Extracts the $data array into individual variables, includes the
     * view file, and terminates execution. Flash messages are automatically
     * available as $flash_error, $flash_success, etc.
     *
     * @param string $view The view path relative to /app/Views/ (e.g. 'home/index')
     * @param array<string, mixed> $data Associative array of data to pass to the view
     * @return never
     */
    protected function view(string $view, array $data = []): never
    {
        // Extract data into local variables for the view
        extract($data, EXTR_SKIP);

        // Make flash messages available in views
        $flash_error   = flash('error');
        $flash_success = flash('success');
        $flash_warning = flash('warning');
        $flash_info    = flash('info');

        // Build the full path to the view file
        $viewFile = self::VIEW_PATH . $view . '.php';

        if (!file_exists($viewFile)) {
            http_response_code(500);
            error_log("View not found: {$viewFile}");
            echo "Error: View '{$view}' could not be found.";
            exit;
        }

        require $viewFile;
        exit;
    }

    /**
     * Instantiate and return a model object.
     *
     * Creates a new instance of the specified model class, passing
     * the PDO connection to its constructor.
     *
     * @param string $model The model class name (without namespace)
     * @return object The instantiated model
     * @throws \RuntimeException If the model class does not exist
     */
    protected function model(string $model): object
    {
        $class = self::MODEL_NAMESPACE . $model;

        if (!class_exists($class)) {
            throw new \RuntimeException("Model class '{$class}' not found.");
        }

        return new $class($this->db);
    }

    /**
     * Send a JSON response with appropriate headers.
     *
     * Sets the HTTP status code and Content-Type header, encodes
     * the data array as JSON, and terminates execution.
     *
     * @param array<string, mixed> $data The data to encode as JSON
     * @param int $code The HTTP status code (default: 200)
     * @return never
     */
    protected function json(array $data, int $code = 200): never
    {
        http_response_code($code);
        header('Content-Type: application/json; charset=utf-8');
        echo json_encode($data, JSON_THROW_ON_ERROR);
        exit;
    }

    /**
     * Validate CSRF token on POST requests.
     *
     * Checks for a valid CSRF token in the POST data (csrf_token field).
     * If validation fails, sets a flash error message and redirects back.
     * Should be called at the start of any POST handler.
     *
     * @return void
     */
    protected function validateCsrf(): void
    {
        $token = $_POST['csrf_token'] ?? '';

        if (!verifyCsrfToken($token)) {
            flash('error', 'Invalid security token. Please try again.');
            redirect($_SERVER['HTTP_REFERER'] ?? '/');
        }
    }
}
