#!/usr/bin/env php
<?php

declare(strict_types=1);

/**
 * Lightweight local WordPress emulation for executing plugin code in code/.
 */

final class WP_Error
{
    public function __construct(
        public string $code,
        public string $message,
        public array $data = []
    ) {
    }
}

final class WP_REST_Request
{
    private array $params = [];
    private array $headers = [];

    public function __construct(
        private readonly string $method,
        private readonly string $route,
        private readonly array $jsonParams = [],
        array $headers = []
    ) {
        $this->params = $jsonParams;
        foreach ($headers as $k => $v) {
            $this->headers[strtolower((string) $k)] = (string) $v;
        }
    }

    public function get_method(): string
    {
        return $this->method;
    }

    public function get_route(): string
    {
        return $this->route;
    }

    public function get_json_params(): array
    {
        return $this->jsonParams;
    }

    public function set_param(string $key, mixed $value): void
    {
        $this->params[$key] = $value;
    }

    public function get_param(string $key): mixed
    {
        return $this->params[$key] ?? null;
    }

    public function get_header(string $name): string
    {
        return $this->headers[strtolower($name)] ?? '';
    }
}

final class WPDB_Compat
{
    public string $prefix = 'wp_';
    public int $insert_id = 0;

    /** @var array<string, array<int, array<string, mixed>>> */
    private array $tables = [];

    /** @var array<string, int> */
    private array $autoIds = [];

    public function __construct(string $dbPath)
    {
        // In-memory emulation; path retained for compatibility.
    }

    public function get_charset_collate(): string
    {
        return '';
    }

    public function prepare(string $query, mixed ...$args): string
    {
        $out = $query;
        foreach ($args as $arg) {
            $replacement = is_int($arg) ? (string) $arg : ("'" . str_replace("'", "''", (string) $arg) . "'");
            $out = preg_replace('/%[dsf]/', $replacement, $out, 1) ?? $out;
        }
        return $out;
    }

    public function query(string $query): int|false
    {
        $trimmed = trim($query);
        if (preg_match('/^CREATE TABLE IF NOT EXISTS\s+([a-zA-Z0-9_]+)/i', $trimmed, $m)) {
            $table = $m[1];
            $this->tables[$table] = $this->tables[$table] ?? [];
            $this->autoIds[$table] = $this->autoIds[$table] ?? 0;
            return 0;
        }
        return 0;
    }

    public function get_results(string $query): array
    {
        $q = trim($query);

        if (preg_match('/^SELECT \* FROM\s+([a-zA-Z0-9_]+)\s+ORDER BY\s+id\s+ASC$/i', $q, $m)) {
            $rows = $this->tables[$m[1]] ?? [];
            usort($rows, static fn(array $a, array $b): int => ((int) ($a['id'] ?? 0)) <=> ((int) ($b['id'] ?? 0)));
            return array_map(static fn(array $r): object => (object) $r, $rows);
        }

        if (preg_match('/^SELECT\s+\*\s+FROM\s+([a-zA-Z0-9_]+)\s+WHERE\s+id\s*=\s*(\d+)$/i', $q, $m)) {
            $row = $this->findById($m[1], (int) $m[2]);
            return $row ? [(object) $row] : [];
        }

        if (preg_match('/^SELECT\s+\*\s+FROM\s+([a-zA-Z0-9_]+)\s+WHERE\s+caldav_resource\s*=\s*\'([^\']*)\'$/i', $q, $m)) {
            $table = $m[1];
            $needle = str_replace("''", "'", $m[2]);
            $rows = $this->tables[$table] ?? [];
            foreach ($rows as $row) {
                if ((string) ($row['caldav_resource'] ?? '') === $needle) {
                    return [(object) $row];
                }
            }
            return [];
        }

        if (preg_match('/^SELECT\s+occurrence_key\s+FROM\s+([a-zA-Z0-9_]+)\s+WHERE\s+event_id\s*=\s*(\d+)\s+AND\s+exception_type\s*=\s*\'([^\']+)\'$/i', $q, $m)) {
            $table = $m[1];
            $eventId = (int) $m[2];
            $type = $m[3];
            $rows = $this->tables[$table] ?? [];
            $filtered = array_values(array_filter(
                $rows,
                static fn(array $r): bool => ((int) ($r['event_id'] ?? 0) === $eventId) && ((string) ($r['exception_type'] ?? '') === $type)
            ));
            return array_map(
                static fn(array $r): object => (object) ['occurrence_key' => (string) ($r['occurrence_key'] ?? '')],
                $filtered
            );
        }

        return [];
    }

    public function get_row(string $query): ?object
    {
        $rows = $this->get_results($query);
        return $rows[0] ?? null;
    }

    public function get_var(string $query): mixed
    {
        $q = trim($query);
        if (preg_match('/^SHOW TABLES LIKE\s*\'([^\']+)\'$/i', $q, $m)) {
            $table = str_replace("''", "'", $m[1]);
            return array_key_exists($table, $this->tables) ? $table : null;
        }
        $row = $this->get_row($query);
        if (!$row) {
            return null;
        }
        $vars = get_object_vars($row);
        foreach ($vars as $value) {
            return $value;
        }
        return null;
    }

    public function insert(string $table, array $data, array $formats = []): int|false
    {
        $this->tables[$table] = $this->tables[$table] ?? [];
        $this->autoIds[$table] = $this->autoIds[$table] ?? 0;

        $id = ++$this->autoIds[$table];
        $row = ['id' => $id] + $data;
        $this->tables[$table][] = $row;
        $this->insert_id = $id;

        return 1;
    }

    public function update(string $table, array $data, array $where, array $formats = [], array $whereFormats = []): int|false
    {
        $rows = $this->tables[$table] ?? [];
        $updated = 0;
        foreach ($rows as $i => $row) {
            if (!$this->matchesWhere($row, $where)) {
                continue;
            }
            $rows[$i] = array_merge($row, $data);
            $updated++;
        }
        $this->tables[$table] = $rows;
        return $updated;
    }

    public function delete(string $table, array $where, array $whereFormats = []): int|false
    {
        $rows = $this->tables[$table] ?? [];
        $kept = [];
        $deleted = 0;
        foreach ($rows as $row) {
            if ($this->matchesWhere($row, $where)) {
                $deleted++;
                continue;
            }
            $kept[] = $row;
        }
        $this->tables[$table] = $kept;
        return $deleted;
    }

    private function findById(string $table, int $id): ?array
    {
        $rows = $this->tables[$table] ?? [];
        foreach ($rows as $row) {
            if ((int) ($row['id'] ?? 0) === $id) {
                return $row;
            }
        }
        return null;
    }

    private function matchesWhere(array $row, array $where): bool
    {
        foreach ($where as $k => $v) {
            if (!array_key_exists($k, $row)) {
                return false;
            }
            if ((string) $row[$k] !== (string) $v) {
                return false;
            }
        }
        return true;
    }
}

$GLOBALS['wp_actions'] = [];
$GLOBALS['wp_shortcodes'] = [];
$GLOBALS['wp_routes'] = [];
$GLOBALS['wp_options'] = [];
$GLOBALS['wp_mail_outbox'] = [];
$GLOBALS['wp_user'] = [
    'id' => 1,
    'email' => 'admin@example.test',
    'caps' => ['manage_options', 'edit_posts'],
];
$GLOBALS['wp_activation_hooks'] = [];
$GLOBALS['wp_admin_menu'] = [];
$GLOBALS['wp_options']['admin_email'] = 'admin@example.test';

function add_action(string $hook, callable $callback, int $priority = 10, int $acceptedArgs = 1): void
{
    $GLOBALS['wp_actions'][$hook][] = $callback;
}

function do_action(string $hook, mixed ...$args): void
{
    foreach (($GLOBALS['wp_actions'][$hook] ?? []) as $cb) {
        $cb(...$args);
    }
}

function add_shortcode(string $tag, callable $callback): void
{
    $GLOBALS['wp_shortcodes'][$tag] = $callback;
}

function do_shortcode(string $content): string
{
    if (preg_match('/\[([a-z0-9_\-]+)\]/i', $content, $m)) {
        $tag = $m[1];
        if (isset($GLOBALS['wp_shortcodes'][$tag])) {
            return (string) call_user_func($GLOBALS['wp_shortcodes'][$tag]);
        }
    }
    return $content;
}

function register_rest_route(string $namespace, string $route, array $args): void
{
    $GLOBALS['wp_routes'][] = [
        'namespace' => '/' . trim($namespace, '/'),
        'route' => $route,
        'args' => $args,
    ];
}

function rest_do_request(WP_REST_Request $request): array|WP_Error
{
    $method = strtoupper($request->get_method());
    $path = '/' . trim($request->get_route(), '/');

    foreach ($GLOBALS['wp_routes'] as $entry) {
        $namespace = $entry['namespace'];
        $routePattern = $entry['route'];
        $args = $entry['args'];

        $registeredMethods = strtoupper((string) ($args['methods'] ?? 'GET'));
        if ($registeredMethods !== $method) {
            continue;
        }

        $regex = '#^' . preg_quote($namespace, '#') . preg_replace('#/#', '\\/', $routePattern) . '$#';
        if (!preg_match($regex, $path, $matches)) {
            continue;
        }

        foreach ($matches as $k => $v) {
            if (is_string($k)) {
                $request->set_param($k, $v);
            }
        }

        $perm = $args['permission_callback'] ?? '__return_true';
        $allowed = is_callable($perm) ? (bool) call_user_func($perm, $request) : false;
        if (!$allowed) {
            return new WP_Error('forbidden', 'Forbidden', ['status' => 403]);
        }

        $cb = $args['callback'] ?? null;
        if (!is_callable($cb)) {
            return new WP_Error('server_error', 'Route callback missing', ['status' => 500]);
        }

        return call_user_func($cb, $request);
    }

    return new WP_Error('not_found', 'Route not found', ['status' => 404]);
}

function register_activation_hook(string $pluginFile, callable $callback): void
{
    $GLOBALS['wp_activation_hooks'][$pluginFile] = $callback;
}

function register_deactivation_hook(string $pluginFile, callable $callback): void
{
    $GLOBALS['wp_activation_hooks'][$pluginFile . ':deactivate'] = $callback;
}

function get_option(string $key, mixed $default = false): mixed
{
    return array_key_exists($key, $GLOBALS['wp_options']) ? $GLOBALS['wp_options'][$key] : $default;
}

function update_option(string $key, mixed $value, bool $autoload = true): bool
{
    $GLOBALS['wp_options'][$key] = $value;
    return true;
}

function delete_option(string $key): bool
{
    unset($GLOBALS['wp_options'][$key]);
    return true;
}

function get_current_user_id(): int
{
    return (int) ($GLOBALS['wp_user']['id'] ?? 0);
}

function current_user_can(string $capability): bool
{
    return in_array($capability, (array) ($GLOBALS['wp_user']['caps'] ?? []), true);
}

function wp_verify_nonce(string $nonce, string $action): bool
{
    return $nonce === 'ok:' . $action;
}

function wp_get_current_user(): object
{
    return (object) ['user_email' => (string) ($GLOBALS['wp_user']['email'] ?? '')];
}

function esc_html(string $value): string
{
    return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

function __return_true(): bool
{
    return true;
}

if (!function_exists('dbDelta')) {
    function dbDelta(string $sql): array
    {
        return [];
    }
}

function add_menu_page(string $pageTitle, string $menuTitle, string $capability, string $menuSlug, callable $callback): void
{
    $GLOBALS['wp_admin_menu'][$menuSlug] = ['title' => $menuTitle, 'callback' => $callback];
}

function add_submenu_page(string $parentSlug, string $pageTitle, string $menuTitle, string $capability, string $menuSlug, callable $callback): void
{
    $GLOBALS['wp_admin_menu'][$menuSlug] = ['title' => $menuTitle, 'parent' => $parentSlug, 'callback' => $callback];
}

function admin_url(string $path = ''): string
{
    return '/wp-admin/' . ltrim($path, '/');
}

function home_url(string $path = ''): string
{
    return 'http://localhost' . (str_starts_with($path, '/') ? $path : ('/' . $path));
}

function wp_mail(string|array $to, string $subject, string $message): bool
{
    $GLOBALS['wp_mail_outbox'][] = ['to' => $to, 'subject' => $subject, 'message' => $message];
    return true;
}

function wp_generate_password(int $length = 12, bool $specialChars = true): string
{
    $bytes = random_bytes(max(1, intdiv($length + 1, 2)));
    return substr(bin2hex($bytes), 0, $length);
}

if (!defined('ABSPATH')) {
define('ABSPATH', __DIR__ . '/wp-emu-root/');
define('CALENDAR_PLUGIN_ALLOW_DEBUG_TOKENS', true);
}

$upgradePath = ABSPATH . 'wp-admin/includes/upgrade.php';
@mkdir(dirname($upgradePath), 0775, true);
file_put_contents(
    $upgradePath,
    "<?php\nif (!function_exists('dbDelta')) { function dbDelta(string \$sql): array { return []; } }\n"
);

$wpdb = new WPDB_Compat(__DIR__ . '/wp_emulation.db');
$GLOBALS['wpdb'] = $wpdb;

$wpdb->query('CREATE TABLE IF NOT EXISTS wp_calendar_events (id INTEGER PRIMARY KEY AUTOINCREMENT)');
$wpdb->query('CREATE TABLE IF NOT EXISTS wp_calendar_recurrence_exceptions (id INTEGER PRIMARY KEY AUTOINCREMENT)');

require_once dirname(__DIR__) . '/code/calendar-plugin.php';

do_action('init');
do_action('rest_api_init');

if (PHP_SAPI === 'cli' && basename((string) ($_SERVER['SCRIPT_FILENAME'] ?? '')) === 'wp_emulation.php') {
    echo "wp-emulation booted\n";
    echo 'routes=' . count($GLOBALS['wp_routes']) . "\n";
}
