<script>
    import { tick } from 'svelte';
    import { apiRequest } from '../lib/api';

    export let state;

    const PAGE_SIZE = 50;
    const BOTTOM_TOLERANCE = 40; // px of slack before we consider the player "scrolled away"
    const TOP_LOAD_THRESHOLD = 60; // px from the top that triggers loading older events

    const KIND_ICONS = {
        join: '👋',
        phase: '🧭',
        scene: '🎬',
        captain: '🏴‍☠️',
        challenge: '⚔️',
        card: '🃏',
        tax: '💰',
        objective: '🎯',
        vote: '🗳️',
        obstacle: '🌊',
        joker: '🤡',
        victory: '🎉',
        warning: '⚠️',
        rank: '🎖️',
        info: '📜',
    };

    let open = false;
    let logEl;
    let events = []; // ascending by timestamp; accumulated across polls + history loads
    let seenIds = new Set();
    let haveMore = true;
    let loadingOlder = false;
    let atBottom = true;
    let error = '';
    let timelineVersion = null;

    // Rollback buttons appear during a scene, for Admins and Deep players (server-enforced too).
    $: canRollback = state.game?.phase === 'scene'
        && (state.player?.is_admin || state.player?.role === 'deep');
    // When set, the game is at a rolled-back state; events past this checkpoint are the
    // greyed, still-undoable future. Rolling forward to one of them redoes the rollback.
    $: head = state.game?.rollback_head_checkpoint_id ?? null;

    $: mergePolled(state.events);

    // The poll only carries the newest PAGE_SIZE events, so accumulate them here —
    // otherwise events the player paged back to would vanish as the window slides.
    async function mergePolled(polled) {
        if (!polled) return;
        // Plain rollback/redo keeps every event (only `head` moves); but taking a new
        // action from a rolled-back state truncates the abandoned future and bumps the
        // version, which tells us to drop what we'd accumulated and reseed.
        const version = state.game?.rollback_timeline_version ?? 0;
        if (timelineVersion !== null && version !== timelineVersion) {
            events = [];
            seenIds = new Set();
        }
        timelineVersion = version;
        if (events.length === 0) {
            haveMore = state.events_have_more ?? polled.length >= PAGE_SIZE;
        }
        let added = false;
        for (const e of polled) {
            if (!seenIds.has(e.id)) {
                seenIds.add(e.id);
                events.push(e);
                added = true;
            }
        }
        if (added) {
            events = events;
            if (open && atBottom) {
                await tick();
                scrollToBottom();
            }
        }
    }

    function scrollToBottom() {
        if (logEl) logEl.scrollTop = logEl.scrollHeight;
        atBottom = true;
    }

    function onScroll() {
        if (!logEl) return;
        atBottom = logEl.scrollHeight - logEl.scrollTop - logEl.clientHeight <= BOTTOM_TOLERANCE;
        if (logEl.scrollTop <= TOP_LOAD_THRESHOLD) loadOlder();
    }

    async function loadOlder() {
        if (loadingOlder || !haveMore || events.length === 0) return;
        loadingOlder = true;
        try {
            const oldest = events[0].timestamp;
            const data = await apiRequest(`/game/${state.game.id}/events?before=${oldest}&limit=${PAGE_SIZE}`);
            haveMore = data.have_more;
            const fresh = data.events.filter(e => !seenIds.has(e.id));
            for (const e of fresh) seenIds.add(e.id);
            if (fresh.length) {
                // Prepending grows the content above the viewport; restore the
                // player's position so the log doesn't visually jump.
                const prevHeight = logEl.scrollHeight;
                const prevTop = logEl.scrollTop;
                events = [...fresh, ...events];
                await tick();
                logEl.scrollTop = prevTop + (logEl.scrollHeight - prevHeight);
            }
        } catch (e) {
            // Transient fetch failure; scrolling again retries.
        } finally {
            loadingOlder = false;
        }
    }

    async function toggleOpen() {
        open = !open;
        if (open) {
            await tick();
            scrollToBottom();
        }
    }

    function formatTime(ts) {
        return new Date(ts * 1000).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
    }

    // Both rollback (to a past event) and redo (to a greyed future event) are the same
    // call — it just moves the head. It's reversible until someone takes a new action,
    // so no confirm; the greyed future makes the state obvious.
    async function rollback(event) {
        error = '';
        try {
            await apiRequest(
                `/game/${state.game.id}/player/${state.player.id}/rollback`,
                'POST',
                { checkpoint_id: event.checkpoint_id },
            );
            // The state-changed ping drives Dashboard to refetch the restored state.
        } catch (e) {
            error = e.message;
        }
    }
</script>

<div class="floating-event-log {open ? 'open' : ''}">
    <button class="toggle-log-btn" on:click={toggleOpen}>
        {open ? '⬇️ Hide Log' : '📜 Event Log'}
    </button>

    {#if open}
        {#if error}
            <div class="log-error">{error}</div>
        {/if}
        {#if head !== null}
            <div class="log-rolledback">
                ↩ Rolled back — greyed entries are undone. Click one to redo, or take an action to make it permanent.
            </div>
        {/if}
        <div class="log-content" bind:this={logEl} on:scroll={onScroll}>
            {#if events.length}
                <div class="log-top">
                    {#if loadingOlder}
                        <span class="log-top-note">Hauling up older entries…</span>
                    {:else if haveMore}
                        <button class="load-older-btn" on:click={loadOlder}>⬆️ Load earlier events</button>
                    {:else}
                        <span class="log-top-note">⚓ The story begins here</span>
                    {/if}
                </div>
                {#each events as event (event.id)}
                    {@const isFuture = head !== null && event.checkpoint_id > head}
                    <div class="log-entry log-kind-{event.kind || 'info'}" class:greyed={isFuture}>
                        <span class="log-icon">{KIND_ICONS[event.kind] || KIND_ICONS.info}</span>
                        <span class="log-message">{event.message}</span>
                        <span class="log-time">{formatTime(event.timestamp)}</span>
                        {#if canRollback && event.checkpoint_id > 0}
                            <button
                                class="rollback-btn"
                                title={isFuture ? 'Redo forward to just after this event' : 'Roll the game back to just after this event'}
                                on:click={() => rollback(event)}
                            >{isFuture ? '↪' : '↩'}</button>
                        {/if}
                    </div>
                {/each}
            {:else}
                <div class="log-empty">No events yet.</div>
            {/if}
        </div>

        {#if !atBottom}
            <button class="jump-to-bottom" on:click={scrollToBottom}>⬇️ Latest</button>
        {/if}
    {/if}
</div>

<style>
    .floating-event-log {
        position: fixed;
        bottom: 20px;
        right: 20px;
        width: 380px;
        background: color-mix(in srgb, var(--bg-deep) 95%, transparent);
        border: 1px solid var(--edge);
        border-radius: var(--radius-md);
        box-shadow: var(--shadow-deep);
        z-index: 1000;
        display: flex;
        flex-direction: column;
        max-height: 60vh;
        backdrop-filter: blur(10px);
    }
    .floating-event-log:not(.open) {
        width: auto;
    }
    .toggle-log-btn {
        background: transparent;
        color: var(--text);
        border: none;
        padding: 10px;
        text-align: center;
        cursor: pointer;
        font-weight: bold;
        border-radius: var(--radius-md);
        border-bottom: 1px solid var(--edge-soft);
        font-family: var(--font-heading);
    }
    .toggle-log-btn:hover {
        background: color-mix(in srgb, var(--text) 10%, transparent);
    }
    .log-content {
        overflow-y: auto;
        padding: 10px;
        flex: 1;
        display: flex;
        flex-direction: column;
        gap: 6px;
        scrollbar-width: thin;
    }
    .log-top {
        text-align: center;
        padding: 2px 0 6px;
    }
    .log-top-note {
        font-size: 0.75rem;
        color: var(--text-muted);
        font-style: italic;
    }
    .load-older-btn {
        background: color-mix(in srgb, var(--text) 6%, transparent);
        border: 1px solid var(--edge);
        color: var(--text-muted);
        font-size: 0.75rem;
        padding: 4px 12px;
        border-radius: 12px;
        cursor: pointer;
    }
    .load-older-btn:hover {
        background: color-mix(in srgb, var(--text) 12%, transparent);
        color: var(--text);
    }
    .log-entry {
        display: grid;
        grid-template-columns: auto 1fr auto auto;
        gap: 8px;
        align-items: baseline;
        padding: 6px 8px;
        background: color-mix(in srgb, var(--text) 3%, transparent);
        border-left: 3px solid var(--edge);
        border-radius: 0 6px 6px 0;
        font-size: 0.85rem;
        line-height: 1.35;
        color: color-mix(in srgb, var(--text) 85%, var(--text-muted));
    }
    .rollback-btn {
        background: transparent;
        border: 1px solid var(--edge);
        color: var(--text-muted);
        border-radius: 6px;
        padding: 0 6px;
        font-size: 0.8rem;
        line-height: 1.4;
        cursor: pointer;
        align-self: center;
    }
    .rollback-btn:hover {
        background: color-mix(in srgb, var(--danger) 18%, transparent);
        border-color: var(--danger);
        color: var(--text);
    }
    .log-error {
        margin: 6px 8px 0;
        padding: 6px 10px;
        background: color-mix(in srgb, var(--danger) 14%, transparent);
        border: 1px solid var(--danger);
        border-radius: var(--radius-md);
        color: var(--text);
        font-size: 0.8rem;
    }
    .log-rolledback {
        margin: 6px 8px 0;
        padding: 6px 10px;
        background: color-mix(in srgb, var(--accent) 12%, transparent);
        border: 1px solid var(--accent);
        border-radius: var(--radius-md);
        color: var(--text);
        font-size: 0.78rem;
        line-height: 1.3;
    }
    .log-entry.greyed {
        opacity: 0.45;
        font-style: italic;
    }
    .log-icon {
        font-size: 0.9rem;
    }
    .log-message {
        overflow-wrap: anywhere;
    }
    .log-time {
        font-size: 0.7rem;
        color: var(--text-muted);
        white-space: nowrap;
        opacity: 0.8;
    }
    .log-empty {
        padding: 10px;
        color: var(--text-muted);
        font-style: italic;
    }
    .jump-to-bottom {
        position: absolute;
        bottom: 12px;
        left: 50%;
        transform: translateX(-50%);
        background: var(--accent);
        color: var(--text-inverse);
        border: none;
        border-radius: 16px;
        padding: 5px 14px;
        font-size: 0.8rem;
        font-weight: 700;
        cursor: pointer;
        box-shadow: var(--shadow-soft);
    }
    .jump-to-bottom:hover {
        filter: brightness(1.1);
    }

    /* Accent colors by event kind */
    .log-kind-challenge { border-left-color: var(--danger); }
    .log-kind-card { border-left-color: var(--deep); }
    .log-kind-captain,
    .log-kind-tax,
    .log-kind-rank,
    .log-kind-victory { border-left-color: var(--accent); }
    .log-kind-scene,
    .log-kind-phase { border-left-color: var(--pirat); }
    .log-kind-joker,
    .log-kind-warning { border-left-color: var(--warning); }
    .log-kind-obstacle { border-left-color: var(--deep); }
    .log-kind-join,
    .log-kind-vote,
    .log-kind-objective { border-left-color: var(--mystic); }
    .log-kind-victory {
        background: color-mix(in srgb, var(--accent) 9%, transparent);
        color: var(--accent);
    }
</style>
