pub fn Page(ctx: zx.PageContext) zx.Component {
    var chat_username = ctx.request.cookies.get("username") orelse "";
    const set_username = ctx.request.searchParams.get("username") orelse "";
    var leave_username = ctx.request.searchParams.get("leave") orelse "";
    if (set_username.len > 0) {
        ctx.response.cookies.set("username", set_username, .{});
        chat_username = set_username;
        ctx.response.redirect("/examples/realtime", null);
    }
    if (leave_username.len > 0) {
        ctx.response.cookies.delete("username", .{});
        leave_username = leave_username;
        chat_username = "";
    }

    return (
        <div @allocator={ctx.arena} class="todo-container">
            {if (chat_username.len > 0) (<ChatRoom username={chat_username} @rendering={.client} />) else (<JoinScreen />)}
            <style @escaping={.none}>{@embedFile("../wasm/style.css")}</style>
            <style @escaping={.none}>{@embedFile("style.css")}</style>
        </div>
    );
}

fn JoinScreen(allocator: zx.Allocator) zx.Component {
    return (
        <div @allocator={allocator} class="timer-demo chat-join">
            <h3 class="timer-title">ZX Chat</h3>
            <p class="timer-subtitle">Real-time chat powered by WebSockets</p>

            <form class="todo-input-section">
                <input type="text" class="todo-input" name="username" placeholder="Enter your name..." />
                <button type="submit" class="todo-add-btn">Join Chat</button>
            </form>
        </div>
    );
}

pub fn ChatRoom(ctx: *zx.ComponentCtx(struct { username: []const u8 })) zx.Component {
    const msgs_state = ctx.state(MessageList, .{});
    messages = msgs_state;

    const connected = ws != null;
    const status_class = if (connected) "status-online" else "status-offline";
    const msgs = msgs_state.get();

    if (ws == null) {
        joinChat(ctx.props.username);
    }

    return (
        <div @allocator={ctx.allocator} class="timer-demo chat-room">
            <div class="chat-header">
                <h3 class="timer-title">Chat Room</h3>
                <div class="chat-user-info">
                    <span class={status_class}></span>
                    <span class="chat-username">{ctx.props.username}</span>
                    <form class="chat-leave-form">
                        <button name="leave" class="timer-btn chat-leave-btn" value={ctx.props.username}>Leave</button>
                    </form>
                </div>
            </div>

            <div class="chat-messages">
                {for (msgs.items) |msg| (<MessageItem key={msg.text} username={msg.username} text={msg.text} is_system={msg.is_system} />)}
            </div>

            <form class="todo-input-section chat-input" onsubmit={sendMessage}>
                <input type="text" class="todo-input" id="message-input" placeholder="Type a message..." />
                <button class="todo-add-btn" onclick={sendMessage}>Send</button>
            </form>
        </div>
    );
}

const MessageItemProps = struct { username: []const u8, text: []const u8, is_system: bool };
fn MessageItem(allocator: zx.Allocator, props: MessageItemProps) zx.Component {
    if (props.is_system) {
        return (<div @allocator={allocator} class="chat-msg chat-msg-system">{props.text}</div>);
    }

    const is_self = std.mem.eql(u8, props.username, current_username);
    const msg_class = if (is_self) "chat-msg chat-msg-self" else "chat-msg chat-msg-other";

    return (
        <div @allocator={allocator} class={msg_class}>
            <span class="chat-msg-name">{props.username}</span>
            <p class="chat-msg-text">{props.text}</p>
        </div>
    );
}

const Message = struct { username: []const u8, text: []const u8, is_system: bool };
const MessageList = std.ArrayListUnmanaged(Message);

var messages: ?*zx.State(MessageList) = null;
var ws: ?zx.WebSocket = null;
var current_username: []const u8 = "";

fn joinChat(username: []const u8) void {
    if (zx.platform != .browser) return;

    std.log.info("joinChat: {s}", .{username});
    current_username = zx.client_allocator.dupe(u8, username) catch return;
    ws = zx.WebSocket.init(zx.client_allocator, "/examples/realtime/ws", .{}) catch return;

    if (ws) |*w| {
        w.onopen = &onWsOpen;
        w.onmessage = &onWsMessage;
        w.onclose = &onWsClose;
        w.onerror = &onWsError;
        w.connect() catch {};
    }

    // Trigger initial re-render to update connected status
    if (messages) |msgs_state| {
        msgs_state.set(msgs_state.get());
    }
}

fn leaveChat(_: zx.client.Event) void {
    if (zx.platform != .browser) return;

    if (ws) |*w| {
        w.close(.{ .code = 1000, .reason = "User left" });
        w.deinit();
    }
    ws = null;

    if (messages) |msgs_state| {
        var msgs = msgs_state.get();
        msgs.deinit(zx.client_allocator);
        msgs_state.set(.{});
    }

    // Clear input via DOM
    const document = zx.client.Document.init(zx.client_allocator);
    if (document.getElementById("message-input")) |input| {
        input.ref.set("value", zx.Client.js.string("")) catch {};
    } else |_| {}
}

fn sendMessage(e: zx.client.Event) void {
    if (zx.platform != .browser) return;
    e.preventDefault();

    const document = zx.client.Document.init(zx.client_allocator);
    const input = document.getElementById("message-input") catch return;
    const text = input.ref.getAlloc(zx.Client.js.String, zx.client_allocator, "value") catch return;

    std.log.info("sendMessage: {s}", .{text});
    if (text.len == 0) return;

    if (ws) |*w| {
        if (w.isConnected()) {
            w.send(text) catch {};
            input.ref.set("value", zx.Client.js.string("")) catch {};
        }
    }
}

fn onWsOpen(_: *zx.WebSocket) void {}

fn onWsMessage(_: *zx.WebSocket, event: zx.WebSocket.MessageEvent) void {
    if (zx.platform != .browser) return;

    const data = event.text() orelse return;
    const is_system = std.mem.startsWith(u8, data, "system:");

    var username: []const u8 = "";
    var text: []const u8 = data;

    if (std.mem.indexOf(u8, data, ": ")) |colon_idx| {
        username = zx.client_allocator.dupe(u8, data[0..colon_idx]) catch return;
        text = zx.client_allocator.dupe(u8, data[colon_idx + 2 ..]) catch return;
    } else {
        text = zx.client_allocator.dupe(u8, data) catch return;
    }

    if (messages) |msgs_state| {
        var msgs = msgs_state.get();
        msgs.insert(zx.client_allocator, 0, .{
            .username = username,
            .text = text,
            .is_system = is_system,
        }) catch return;
        msgs_state.set(msgs);
    }
}

fn onWsClose(_: *zx.WebSocket, _: zx.WebSocket.CloseEvent) void {
    if (zx.platform != .browser) return;

    if (messages) |msgs_state| {
        var msgs = msgs_state.get();
        msgs.insert(zx.client_allocator, 0, .{
            .username = "",
            .text = "Disconnected from server",
            .is_system = true,
        }) catch {};
        msgs_state.set(msgs);
    }
}

fn onWsError(_: *zx.WebSocket, _: zx.WebSocket.ErrorEvent) void {
    if (zx.platform != .browser) return;

    if (messages) |msgs_state| {
        var msgs = msgs_state.get();
        msgs.insert(zx.client_allocator, 0, .{
            .username = "",
            .text = "Connection error",
            .is_system = true,
        }) catch {};
        msgs_state.set(msgs);
    }
}

const zx = @import("zx");
const std = @import("std");
