<?php
declare(strict_types=1);

// Refuse to run when requested directly via the web server instead of through
// the app router (defence in depth on top of the deny rule for /plugins/).
defined('BASE_PATH') || exit;

require_once __DIR__ . '/MeetingPollController.php';
require_once __DIR__ . '/PublicPollController.php';

/**
 * Terminfindung — a scheduling-poll plugin with multiple date/time options.
 *
 * Flow:
 *   1. A logged-in user creates a poll: title, optional description/location,
 *      and a list of date/time options. They get a public share link and can
 *      invite participants by e-mail (Plenvo users via the app-wide user-search
 *      filter, plus arbitrary external addresses).
 *   2. Anyone with the link votes Yes / (If-need-be) / No per option after
 *      entering their name — no Plenvo account required. Logged-in users vote
 *      with their account; their name is taken from their profile.
 *   3. The owner watches a results grid on the manage page, sees which invitees
 *      have responded, picks the final slot ("finalize"), and can optionally
 *      e-mail every participant the confirmed time.
 *
 * Storage: five self-contained tables (mp_polls / mp_options / mp_responses /
 * mp_votes / mp_invites) created idempotently on boot. Public link tokens are
 * 32 hex chars.
 *
 * The plugin ships enabled by default (it is not in PluginManager::DEFAULT_DISABLED),
 * so it activates automatically on every instance after an update; admins can switch
 * it off via /admin/plugins. Tables are created idempotently on boot.
 */
class MeetingPollPlugin
{
    public function register(): void
    {
        // Defence in depth: a thrown exception here would propagate out of
        // PluginManager::load() and 500 the whole app. Migrations are already
        // self-contained, but wrap the rest too so a bad upgrade can never take
        // an instance down — at worst the poll routes are simply not registered.
        try {
            self::migrate();

            // ── Authenticated routes (owner / creator area) ─────────────────
            PluginManager::addRoute('/polls',             fn() => MeetingPollController::index());
            PluginManager::addRoute('/polls/create',      fn() => MeetingPollController::create());
            PluginManager::addRoute('/polls/user-search', fn() => MeetingPollController::userSearch());

            PluginManager::addDynamicRoute('#^/polls/(\d+)$#',         fn($m) => MeetingPollController::show((int)$m[1]));
            PluginManager::addDynamicRoute('#^/polls/(\d+)/edit$#',    fn($m) => MeetingPollController::edit((int)$m[1]));
            PluginManager::addDynamicRoute('#^/polls/(\d+)/finalize$#',fn($m) => MeetingPollController::finalize((int)$m[1]));
            PluginManager::addDynamicRoute('#^/polls/(\d+)/reopen$#',  fn($m) => MeetingPollController::reopen((int)$m[1]));
            PluginManager::addDynamicRoute('#^/polls/(\d+)/delete$#',  fn($m) => MeetingPollController::delete((int)$m[1]));
            PluginManager::addDynamicRoute('#^/polls/(\d+)/responses/(\d+)/delete$#',
                fn($m) => MeetingPollController::deleteResponse((int)$m[1], (int)$m[2]));
            PluginManager::addDynamicRoute('#^/polls/(\d+)/invite$#',
                fn($m) => MeetingPollController::invite((int)$m[1]));
            PluginManager::addDynamicRoute('#^/polls/(\d+)/invites/(\d+)/resend$#',
                fn($m) => MeetingPollController::resendInvite((int)$m[1], (int)$m[2]));
            PluginManager::addDynamicRoute('#^/polls/(\d+)/invites/(\d+)/delete$#',
                fn($m) => MeetingPollController::deleteInvite((int)$m[1], (int)$m[2]));

            // ── Public routes (no login needed — share link) ────────────────
            PluginManager::addPublicDynamicRoute('#^/poll/([a-f0-9]{32})$#',
                fn($m) => PublicPollController::show($m[1]));
            PluginManager::addPublicDynamicRoute('#^/poll/([a-f0-9]{32})/vote$#',
                fn($m) => PublicPollController::vote($m[1]));

            // ── Sidebar entry (both the admin and the regular-user nav blocks) ──
            PluginManager::on('user_nav_groups',  [self::class, 'renderNav']);
            PluginManager::on('admin_nav_groups', [self::class, 'renderNav']);
        } catch (\Throwable $e) {
            error_log('[meetingpoll] register failed: ' . $e->getMessage());
        }
    }

    /** Renders the "Terminfindung" sidebar link. */
    public static function renderNav(mixed $ctx): void
    {
        $uri    = is_array($ctx) ? (string)($ctx['uri'] ?? '') : '';
        $active = ($uri === '/polls' || str_starts_with($uri, '/polls/'));
        ?>
        <a href="/polls" class="nav-item <?= $active ? 'active' : '' ?>">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="18" height="18"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/><path d="M9 16l2 2 4-4"/></svg>
            <?= t('mp_nav') ?>
        </a>
        <?php
    }

    /** Renders a plugin template, optionally wrapped in a core layout. */
    public static function render(string $template, array $data = [], ?string $layout = 'base'): void
    {
        extract($data, EXTR_SKIP);
        ob_start();
        include __DIR__ . '/views/' . $template . '.php';
        $content = ob_get_clean();
        $layoutFile = $layout ? BASE_PATH . '/views/layout/' . $layout . '.php' : '';
        if ($layoutFile !== '' && file_exists($layoutFile)) {
            include $layoutFile;
        } else {
            echo $content;
        }
    }

    /** Idempotent schema creation — safe on every boot, fresh install or upgrade. */
    private static function migrate(): void
    {
        if (!defined('DB_PATH') || !file_exists(DB_PATH)) return;
        try {
            $db = Database::getInstance();
            $db->execute('CREATE TABLE IF NOT EXISTS mp_polls (
                id              INTEGER PRIMARY KEY AUTOINCREMENT,
                owner_id        INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
                token           TEXT NOT NULL UNIQUE,
                title           TEXT NOT NULL,
                description     TEXT NOT NULL DEFAULT \'\',
                location        TEXT NOT NULL DEFAULT \'\',
                allow_maybe     INTEGER NOT NULL DEFAULT 1,
                hide_results    INTEGER NOT NULL DEFAULT 0,
                deadline        INTEGER,
                notify_owner    INTEGER NOT NULL DEFAULT 1,
                final_option_id INTEGER,
                created_at      DATETIME DEFAULT CURRENT_TIMESTAMP
            )');
            $db->execute('CREATE INDEX IF NOT EXISTS idx_mp_polls_owner ON mp_polls(owner_id)');

            $db->execute('CREATE TABLE IF NOT EXISTS mp_options (
                id         INTEGER PRIMARY KEY AUTOINCREMENT,
                poll_id    INTEGER NOT NULL REFERENCES mp_polls(id) ON DELETE CASCADE,
                starts_at  INTEGER NOT NULL,
                ends_at    INTEGER,
                all_day    INTEGER NOT NULL DEFAULT 0,
                sort_order INTEGER NOT NULL DEFAULT 0
            )');
            $db->execute('CREATE INDEX IF NOT EXISTS idx_mp_options_poll ON mp_options(poll_id)');

            $db->execute('CREATE TABLE IF NOT EXISTS mp_responses (
                id         INTEGER PRIMARY KEY AUTOINCREMENT,
                poll_id    INTEGER NOT NULL REFERENCES mp_polls(id) ON DELETE CASCADE,
                user_id    INTEGER REFERENCES users(id) ON DELETE SET NULL,
                name       TEXT NOT NULL,
                email      TEXT NOT NULL DEFAULT \'\',
                comment    TEXT NOT NULL DEFAULT \'\',
                edit_token TEXT NOT NULL,
                created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
                updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
            )');
            $db->execute('CREATE INDEX IF NOT EXISTS idx_mp_responses_poll ON mp_responses(poll_id)');
            $db->execute('CREATE UNIQUE INDEX IF NOT EXISTS idx_mp_responses_user ON mp_responses(poll_id, user_id) WHERE user_id IS NOT NULL');

            $db->execute('CREATE TABLE IF NOT EXISTS mp_votes (
                response_id INTEGER NOT NULL REFERENCES mp_responses(id) ON DELETE CASCADE,
                option_id   INTEGER NOT NULL REFERENCES mp_options(id) ON DELETE CASCADE,
                value       TEXT NOT NULL DEFAULT \'no\',
                PRIMARY KEY (response_id, option_id)
            )');

            $db->execute('CREATE TABLE IF NOT EXISTS mp_invites (
                id           INTEGER PRIMARY KEY AUTOINCREMENT,
                poll_id      INTEGER NOT NULL REFERENCES mp_polls(id) ON DELETE CASCADE,
                user_id      INTEGER REFERENCES users(id) ON DELETE SET NULL,
                email        TEXT NOT NULL,
                name         TEXT NOT NULL DEFAULT \'\',
                invite_token TEXT,
                created_at   DATETIME DEFAULT CURRENT_TIMESTAMP,
                last_sent_at INTEGER,
                UNIQUE(poll_id, email)
            )');
            $db->execute('CREATE INDEX IF NOT EXISTS idx_mp_invites_poll ON mp_invites(poll_id)');
            // Per-invitee link token (added after the table first shipped without it).
            try { $db->execute('ALTER TABLE mp_invites ADD COLUMN invite_token TEXT'); } catch (\Throwable) {}
            try { $db->execute('CREATE INDEX IF NOT EXISTS idx_mp_invites_token ON mp_invites(invite_token)'); } catch (\Throwable) {}
        } catch (\Throwable $e) {
            error_log('[meetingpoll] migration failed: ' . $e->getMessage());
        }
    }
}
