<?php
// ============================================================
//  MJPOS – Router
// ============================================================

class Router
{
    private array $routes = [];

    public function get(string $uri, string $controller, string $method): void
    {
        $this->routes[] = ['GET', $uri, $controller, $method];
    }

    public function post(string $uri, string $controller, string $method): void
    {
        $this->routes[] = ['POST', $uri, $controller, $method];
    }

    public function dispatch(): void
    {
        $method = $_SERVER['REQUEST_METHOD'];
        $uri    = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);

        // Strip base path prefix
        $base = parse_url(APP_URL, PHP_URL_PATH) ?? '';
        if ($base && str_starts_with($uri, $base)) {
            $uri = substr($uri, strlen($base));
        }
        $uri = '/' . trim($uri, '/');
        if ($uri === '/') $uri = '/dashboard';

        // Normalize to lowercase (routes are always lowercase)
        $uri = strtolower($uri);

        foreach ($this->routes as [$routeMethod, $routeUri, $controller, $action]) {
            if ($routeMethod !== $method) continue;

            // Convert :param to named regex
            $pattern = preg_replace('/\/:([a-zA-Z_]+)/', '/(?P<$1>[^/]+)', $routeUri);
            $pattern = '@^' . $pattern . '$@';

            if (preg_match($pattern, $uri, $matches)) {
                $params = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY);

                $ctrl = new $controller();
                $ctrl->$action(...array_values($params));
                return;
            }
        }

        // 404
        http_response_code(404);
        require APP_PATH . '/views/errors/404.php';
    }
}


// ============================================================
//  MJPOS – Base Controller
// ============================================================

class Controller
{
    protected function loadView(string $view, array $data = []): void
    {
        extract($data, EXTR_SKIP);
        $file = APP_PATH . '/views/' . str_replace('.', '/', $view) . '.php';
        if (!file_exists($file)) {
            throw new RuntimeException("View not found: {$view}");
        }
        require $file;
    }

    protected function json(array $data, int $status = 200): void
    {
        http_response_code($status);
        header('Content-Type: application/json; charset=utf-8');
        echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        exit;
    }

    protected function redirect(string $url): void
    {
        header('Location: ' . $url);
        exit;
    }

    protected function back(): void
    {
        $this->redirect($_SERVER['HTTP_REFERER'] ?? APP_URL . '/dashboard');
    }

    protected function input(string $key, mixed $default = null): mixed
    {
        return $_POST[$key] ?? $_GET[$key] ?? $default;
    }

    protected function post(string $key, mixed $default = null): mixed
    {
        return $_POST[$key] ?? $default;
    }

    protected function get(string $key, mixed $default = null): mixed
    {
        return $_GET[$key] ?? $default;
    }

    protected function flash(string $type, string $message): void
    {
        $_SESSION['flash'][$type][] = $message;
    }

    protected function old(string $key): string
    {
        return htmlspecialchars($_SESSION['old'][$key] ?? '', ENT_QUOTES, 'UTF-8');
    }

    protected function saveOld(array $data): void
    {
        $_SESSION['old'] = $data;
    }

    protected function clearOld(): void
    {
        unset($_SESSION['old']);
    }

    // Generate unique invoice number, e.g. INV-20250523-0001
    protected function generateInvoiceNo(string $prefix = 'INV', string $table = 'sales'): string
    {
        $date    = date('Ymd');
        $pattern = $prefix . '-' . $date . '-%';
        $field   = ($table === 'sales') ? 'invoice_no' : 'invoice_no';

        // Use MAX to find last sequence — safe even if rows were deleted
        $last = Database::fetchColumn(
            "SELECT MAX(CAST(SUBSTRING_INDEX({$field}, '-', -1) AS UNSIGNED))
             FROM {$table}
             WHERE {$field} LIKE ?",
            [$pattern]
        );

        $next = ((int)$last) + 1;
        return $prefix . '-' . $date . '-' . str_pad($next, 4, '0', STR_PAD_LEFT);
    }

    // Pagination helper for views
    protected function paginationLinks(array $pagination): string
    {
        if ($pagination['pages'] <= 1) return '';

        $current = $pagination['current'];
        $pages   = $pagination['pages'];
        $html    = '<nav><ul class="pagination pagination-sm mb-0">';

        for ($i = 1; $i <= $pages; $i++) {
            $active = $i === $current ? ' active' : '';
            $url    = '?page=' . $i . '&' . http_build_query(
                array_diff_key($_GET, ['page' => ''])
            );
            $html  .= "<li class=\"page-item{$active}\">
                         <a class=\"page-link\" href=\"{$url}\">{$i}</a>
                       </li>";
        }
        return $html . '</ul></nav>';
    }
}


// ============================================================
//  MJPOS – Global Helper Functions
// ============================================================

/** Sanitise output for HTML */
if (!function_exists('e')) {
function e(mixed $value): string
{
    return htmlspecialchars((string)$value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
}

/** Format currency with symbol from settings */
function currency(mixed $amount): string
{
    $symbol = $_SESSION['settings']['currency_symbol'] ?? 'Rs.';
    return $symbol . ' ' . number_format((float)$amount, 2);
}

/** Format a date */
function fDate(string $date, string $format = 'd M Y'): string
{
    return $date ? date($format, strtotime($date)) : '-';
}

/** Redirect with a flash message */
function redirectWith(string $url, string $type, string $message): void
{
    $_SESSION['flash'][$type][] = $message;
    header('Location: ' . $url);
    exit;
}

/** Get current setting value */
function setting(string $key, mixed $default = ''): mixed
{
    static $cache = null;
    if ($cache === null) {
        $rows  = Database::fetchAll('SELECT setting_key, setting_value FROM settings');
        $cache = array_column($rows, 'setting_value', 'setting_key');
    }
    return $cache[$key] ?? $default;
}

/** Generate a random alphanumeric code */
function randomCode(int $length = 8): string
{
    return strtoupper(substr(bin2hex(random_bytes($length)), 0, $length));
}

/** Render flash messages HTML */
function flashMessages(): string
{
    if (empty($_SESSION['flash'])) return '';
    $html = '';
    foreach ($_SESSION['flash'] as $type => $messages) {
        $alertClass = match ($type) {
            'success' => 'alert-success',
            'error'   => 'alert-danger',
            'warning' => 'alert-warning',
            default   => 'alert-info',
        };
        foreach ($messages as $msg) {
            $html .= "<div class=\"alert {$alertClass} alert-dismissible fade show\" role=\"alert\">"
                   . e($msg)
                   . '<button type="button" class="btn-close" data-bs-dismiss="alert"></button>'
                   . '</div>';
        }
    }
    unset($_SESSION['flash']);
    return $html;
}

/** Highlight active sidebar link */
function active(string $path): string
{
    $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
    $base = parse_url(APP_URL, PHP_URL_PATH) ?? '';
    if ($base) $uri = str_replace($base, '', $uri);
    $uri = trim($uri, '/');

    // Exact match OR uri starts with path followed by / or ?
    // Prevents 'cheques' matching 'cheques/pending'
    if ($uri === $path) return 'active';
    if (str_starts_with($uri, $path . '/')) return 'active';
    if (str_starts_with($uri, $path . '?')) return 'active';
    return '';
}