pub fn Page(ctx: zx.PageContext) zx.Component {
    return (
        <div @allocator={ctx.arena} class="todo-container">
            <TodoApp @rendering={.client} />
        </div>
    );
}

const Todo = struct { id: u32, text: []const u8, completed: bool };
const TodoList = std.ArrayListUnmanaged(Todo);

pub fn TodoApp(ctx: *zx.ComponentCtx(void)) !zx.Component {
    const todos_state = ctx.state(TodoList, blk: {
        var list = TodoList{};
        try list.appendSlice(ctx.allocator, &initial_todos);
        break :blk list;
    });
    _ = ctx.state(u32, 4);

    const todos_list = todos_state.get();

    return (
        <div @allocator={ctx.allocator} class="todo-app">
            <div class="todo-header">
                <h1 class="todo-title">ZX + WASM</h1>
                <Counter />
                // <CounterCmp initial={0} />
                <p class="todo-subtitle">A demo todo app built with ZX Client Side Rendering</p>
            </div>

            <form class="todo-input-section" onsubmit={ctx.bind(&addTodo)}>
                <input type="text" class="todo-input" id="todo-input" placeholder="Add a new todo..." required />
                <button class="todo-add-btn" type="submit">Add</button>
            </form>

            <div class="todo-stats">
                <span class="todo-stat"><strong>{todos_list.items.len}</strong> total</span>
                <span class="todo-stat-divider">•</span>
                <span class="todo-stat"><strong>{countCompleted(todos_list)}</strong> done</span>
                <span class="todo-stat-divider">•</span>
                <span class="todo-stat"><strong>{todos_list.items.len - countCompleted(todos_list)}</strong> left</span>
                <button class="todo-clear-btn" onclick={ctx.bind(&clearTodos)}>Clear All</button>
            </div>

            <ul class="todo-list">
                {for (todos_list.items) |todo| (
                    <TodoItem 
                        id={todo.id} 
                        text={todo.text} 
                        completed={todo.completed} 
                        onToggle={ctx.bind(&toggleTodo)}
                        onDelete={ctx.bind(&deleteTodo)}
                    />
                )}
            </ul>
        </div>
    );
}

fn TodoItem(ctx: *zx.ComponentCtx(struct { 
    id: u32, 
    text: []const u8, 
    completed: bool,
    onToggle: zx.EventHandler,
    onDelete: zx.EventHandler
})) zx.Component {
    const class = if (ctx.props.completed) "todo-item todo-item-completed" else "todo-item";
    const key = ctx.props.id;
    const value = ctx.props.id;

    return (
        <li @allocator={ctx.allocator} {class} {key}>
            <input type="button" class="todo-checkbox" {value} onclick={ctx.props.onToggle} />
            <span> {ctx.props.text} </span>
            <button class="todo-delete-btn" {value} onclick={ctx.props.onDelete}>×</button>
        </li>
    );
}

const initial_todos = [_]Todo{
    .{ .id = 1, .text = "Build a fast web app with ZX", .completed = true },
    .{ .id = 2, .text = "Implement server-side rendering", .completed = true },
    .{ .id = 3, .text = "Implement client-side rendering", .completed = false },
};

fn addTodo(e: *zx.client.Event.Stateful) void {
    e.preventDefault();
    if (!zx.platform.isClient()) return;

    const todos_state = e.state(TodoList);
    const id_state = e.state(u32);

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

    const id = id_state.get();
    id_state.set(id + 1);

    var list = todos_state.get();
    list.append(zx.client_allocator, .{ .id = id, .text = text, .completed = false }) catch return;
    todos_state.set(list);

    input.ref.set("value", js.string("")) catch return;
}

fn toggleTodo(e: *zx.client.Event.Stateful) void {
    if (!zx.platform.isClient()) return;
    const todos_state = e.state(TodoList);
    _ = e.state(u32); // skip next_id

    const event = e.getEvent();
    defer event.deinit();
    var id: ?u32 = null;
    if (event.getTarget()) |target| {
        defer target.deinit();
        if (target.getAlloc(js.String, zx.client_allocator, "value") catch null) |v| {
            id = std.fmt.parseInt(u32, v, 10) catch null;
        }
    }

    const list = todos_state.get();
    for (list.items) |*todo| {
        if (todo.id == id) {
            todo.completed = !todo.completed;
            break;
        }
    }
    todos_state.set(list);
}

fn clearTodos(e: *zx.client.Event.Stateful) void {
    const todos_state = e.state(TodoList);
    var list = todos_state.get();
    list.items.len = 0;
    todos_state.set(list);
}

fn deleteTodo(e: *zx.client.Event.Stateful) void {
    if (!zx.platform.isClient()) return;
    const todos_state = e.state(TodoList);
    _ = e.state(u32); // skip next_id

    const event = e.getEvent();
    defer event.deinit();
    var id: ?u32 = null;
    if (event.getTarget()) |target| {
        defer target.deinit();
        if (target.getAlloc(js.String, zx.client_allocator, "value") catch null) |v| {
            id = std.fmt.parseInt(u32, v, 10) catch null;
        }
    }

    var list = todos_state.get();
    for (list.items, 0..) |todo, i| {
        if (todo.id == id) {
            _ = list.orderedRemove(i);
            break;
        }
    }
    todos_state.set(list);
}

fn countCompleted(list: TodoList) u32 {
    var count: u32 = 0;
    for (list.items) |todo| {
        if (todo.completed) count += 1;
    }
    return count;
}

// This is temporarily here to validated pagecontext is buildable for wasm target
fn Counter(allocator: zx.Allocator) zx.Component {
    const ctx = zx.PageContext{
        .request = .{
            .url = "",
            .method = .GET,
            .pathname = "",
            .headers = .{},
            .arena = allocator,
        },
        .response = .{ .arena = allocator },
        .allocator = allocator,
        .arena = allocator,
    };
    const counter_page = CounterPage(ctx);
    return (
        <div @allocator={allocator} style="display: none;">
            {counter_page}
        </div>
    );
}

pub const CounterPage = @import("./simple/page.zx").Page;
pub const CounterCmp = @import("./simple/page.zx").Counter;

const zx = @import("zx");
const std = @import("std");
const builtin = @import("builtin");
const client = zx.client;
const js = zx.Client.js;
