var components: []const data.Component = &.{};

var inputvalue: []const u8 = "";
var stateFilter: []const u8 = "";

var selected_component: []const u8 = "0";

var fetched = false;
// Helper function to find a component by ID recursively
fn findComponentById(comps: []const data.Component, id: []const u8) ?data.Component {
    fetch();
    for (comps) |comp| {
        if (std.mem.eql(u8, comp.id, id)) {
            return comp;
        }
        if (comp.has_children) {
            if (findComponentById(comp.children, id)) |found| {
                return found;
            }
        }
    }
    return null;
}

fn fetch() void {
    if (fetched) return;
    if (!data.loadSettings()) return;
    const host = data.host;
    const path = data.current_path;
    const base = if (std.mem.startsWith(u8, host, "http://") or std.mem.startsWith(u8, host, "https://")) host else std.fmt.allocPrint(zx.client_allocator, "http://{s}", .{host}) catch return;
    const include_native = if (data.show_native_elements) "1" else "0";
    const url = std.fmt.allocPrint(zx.client_allocator, "{s}/.well-known/_zx/devtool?path={s}&include_native={s}", .{ base, path, include_native }) catch return;
    fetched = true;
    _ = zx.fetch(.wasm(&onFetchText), zx.client_allocator, url, .{ .method = .GET }) catch {};
}

fn onFetchText(res: ?*zx.Fetch.Response, _: ?zx.Fetch.FetchError) void {
    if (res) |r| {
        defer r.deinit();
        if (r.text()) |p| {
            const parsed = zx.util.zxon.parse(zx.Component.Serializable, zx.client_allocator, p, .{}) catch return;
            if (parsed.children) |sc| {
                const mapped = data.fromSerializableSlice(zx.client_allocator, sc) catch unreachable;
                std.log.info("Fetched {d} components", .{mapped.len});
                components = mapped;
            }
        } else |_| {}
    }
    zx.client.rerender();
}



const ComponentSearchInputProps = struct {  };
const ComponentStateProps = struct {  };
const ComponentTreeProps = struct {  };
const ComponentStateFilterProps = struct {  };
const NativeToggleProps = struct {};
pub fn ComponentSearchInput(
    ctx: *zx.ComponentCtx(ComponentSearchInputProps),
) zx.Component {
    return (
        <div @allocator={ctx.allocator} class="search-input-wrapper">
            <input @allocator={ctx.allocator} oninput={handleComponentSearchInput} type="text" placeholder="Find components..." class="search-input" />
        </div>
    );
}
pub fn ComponentTree(
    ctx: *zx.ComponentCtx(ComponentTreeProps),
) zx.Component {
    _ = data.loadSettings();
    const sel = selected_component;
    const display_components = filterNativeElements(ctx.allocator, components);
    return (
        <div @allocator={ctx.allocator} class="component-tree">
            <div class="component-list">
                {for (display_components) |comp| (<ComponentItem allocator={ctx.allocator} id={comp.id} name={comp.name} has_children={comp.has_children} children={comp.children} depth={0} selected={std.mem.eql(u8, comp.id, sel)} badge={comp.badge} is_native={comp.is_native} />)}
            </div>
        </div>
    );
}

pub fn ComponentState(
    ctx: *zx.ComponentCtx(ComponentStateProps),
) zx.Component {
    const sel = selected_component;
    const maybe_component = findComponentById(components, sel);
    
    if (maybe_component) |comp| {
        if (comp.meta) |metadata| {
            return (
                <div @allocator={ctx.allocator} class="component-state-wrapper">
                    <div @allocator={ctx.allocator} class="state-content">
                        <StateSection allocator={ctx.allocator} title="Props" items={metadata.prop_items} />
                        <StateSection allocator={ctx.allocator} title="States" items={metadata.signal_items} />
                        <StateSection allocator={ctx.allocator} title="Actions" items={metadata.action_items} />
                    </div>
                </div>
            );
        }
    }
    
    return (
        <div @allocator={ctx.allocator} class="component-state-wrapper">
            <div @allocator={ctx.allocator} class="state-content">
                <div class="state-empty">
                    <h2>{if (maybe_component) |c| c.name else sel}</h2>
                    <p>{"No state data available for this component."}</p>
                </div>
            </div>
        </div>
    );
}

pub fn handleComponentSearchInput(ctx: zx.client.Event) void {
    inputvalue = ctx.value() orelse "";
    zx.client.rerender();
}

pub fn handleStateFilterInput(ctx: zx.client.Event) void {
    stateFilter = ctx.value() orelse "";
    zx.client.rerender();
}

pub fn ComponentStateFilter(
    ctx: *zx.ComponentCtx(ComponentStateFilterProps),
) zx.Component {
    return (
        <div @allocator={ctx.allocator} class="filter-wrapper">
            <input @allocator={ctx.allocator} oninput={handleStateFilterInput} type="text" placeholder="Search..." class="filter-input" />
        </div>
    );
}

pub fn handleComponentClick(ctx: zx.client.Event) void {
    const id = ctx.value() orelse "0";
    selected_component = id;
    zx.client.rerender();
}

const icons = @import("./components/icons.zx");

pub fn NativeToggle(
    ctx: *zx.ComponentCtx(NativeToggleProps),
) zx.Component {
    const active = data.show_native_elements;
    const btn_class = if (active) "native-toggle-btn native-toggle-btn-on" else "native-toggle-btn native-toggle-btn-off";
    return (
        <button @allocator={ctx.allocator} onclick={handleToggleNative} class={btn_class} title="Toggle native elements">
            <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 18l6-6-6-6"></path><path d="M8 6l-6 6 6 6"></path></svg>
        </button>
    );
}

pub fn handleToggleNative(_: zx.client.Event) void {
    data.show_native_elements = !data.show_native_elements;
    data.saveSettings();
    // Refetch so payload matches the selected native-elements mode.
    fetched = false;
    fetch();
    zx.client.rerender();
}

pub fn Page(ctx: zx.PageContext) zx.Component {
    return (
        <div @allocator={ctx.arena} class="devtools-container">
            <div class="sidebar">
                <div class="search-box">
                    <div class="search-wrapper">
                        <ComponentSearchInput @rendering={.client} />
                        {icons.Search(ctx.arena)}
                        <NativeToggle @rendering={.client} />
                    </div>
                </div>

                <ComponentTree @rendering={.client} />
            </div>

            <div class="devtools-resize-handle"></div>

            <div class="state-panel">
                <div class="state-header">
                    // <span class="state-title" id="state-title">{&selected_component}</span>
                    <ComponentStateFilter @rendering={.client} />
                    // <div class="state-actions">
                    //     {icons.StateEye(ctx.arena)}
                    //     {icons.StateArrows(ctx.arena)}
                    //     {icons.StateList(ctx.arena)}
                    //     {icons.StateExport(ctx.arena)}
                    // </div>
                </div>

                <ComponentState @rendering={.client} />

            </div>
        </div>
    );
}

const Component = data.Component;

fn hasNativeChild(children: []const Component) bool {
    for (children) |child| {
        if (child.is_native) return true;
    }
    return false;
}

const ComponentItemProps = struct { id: []const u8, name: []const u8, has_children: bool, children: []const Component, depth: usize = 0, selected: bool = false, badge: []const u8 = "", is_native: bool = false };

fn ComponentItem(allocator: zx.Allocator, props: ComponentItemProps) zx.Component {
    const sel = selected_component;
    const dom_id = std.fmt.allocPrint(allocator, "comp-{s}", .{props.id}) catch "";
    const padding = props.depth * 24 + 16;
    const style_str = std.fmt.allocPrint(allocator, "padding-left: {d}px", .{padding}) catch "";
    const is_selected = std.mem.eql(u8, props.id, sel);
    const item_class = if (is_selected) "component-root" else "component-item";
    const name_class = if (is_selected) "component-name-root" else "component-name";
    const group_class = getComponentGroupClass(props.name, props.has_children, props.children);

    // Any leaf (native or not): flat item, no expand toggle, no arrow
    if (!props.has_children) {
        return (
            <div @allocator={allocator} class={group_class}>
                <div class={item_class} style={style_str}>
                    <button value={props.id} onclick={handleComponentClick} class="component-select-btn-leaf">
                        <span class="tree-arrow-spacer"></span>
                        <span class="bracket">{"<"}</span>
                        <span class={name_class}>{props.name}</span>
                        <span class="bracket">{">"}</span>
                        {if (props.badge.len > 0) (<span class="component-badge">{props.badge}</span>) else ""}
                    </button>
                </div>
            </div>
        );
    }

    // Native element with children: starts collapsed to avoid noisy trees
    if (props.is_native) {
        return (
            <div @allocator={allocator} class={group_class}>
                <input type="checkbox" id={dom_id} class="component-toggle" />
                <div class={item_class} style={style_str}>
                    <label for={dom_id} class="component-toggle-label">
                        <span class="tree-arrow">{icons.ChevronRight(allocator)}</span>
                    </label>
                    <button value={props.id} onclick={handleComponentClick} class="component-select-btn">
                        <span class="bracket">{"<"}</span>
                        <span class={name_class}>{props.name}</span>
                        <span class="bracket">{">"}</span>
                        {if (props.badge.len > 0) (<span class="component-badge">{props.badge}</span>) else ""}
                    </button>
                </div>
                <div class="component-children">
                    {for (props.children) |child| (
                        <ComponentItem allocator={allocator} id={child.id} name={child.name} has_children={child.has_children} children={child.children} depth={props.depth + 1} selected={std.mem.eql(u8, child.id, sel)} badge={child.badge} is_native={child.is_native} />
                    )}
                </div>
            </div>
        );
    }

    // Non-native component whose direct children are all user components: starts EXPANDED
    if (!hasNativeChild(props.children)) {
        return (
            <div @allocator={allocator} class={group_class}>
                <input type="checkbox" id={dom_id} class="component-toggle" checked />
                <div class={item_class} style={style_str}>
                    <label for={dom_id} class="component-toggle-label">
                        <span class="tree-arrow">{icons.ChevronRight(allocator)}</span>
                    </label>
                    <button value={props.id} onclick={handleComponentClick} class="component-select-btn">
                        <span class="bracket">{"<"}</span>
                        <span class={name_class}>{props.name}</span>
                        <span class="bracket">{">"}</span>
                        {if (props.badge.len > 0) (<span class="component-badge">{props.badge}</span>) else ""}
                    </button>
                </div>
                <div class="component-children">
                    {for (props.children) |child| (
                        <ComponentItem allocator={allocator} id={child.id} name={child.name} has_children={child.has_children} children={child.children} depth={props.depth + 1} selected={std.mem.eql(u8, child.id, sel)} badge={child.badge} is_native={child.is_native} />
                    )}
                </div>
            </div>
        );
    }

    // Non-native component that directly contains native children: starts collapsed
    return (
        <div @allocator={allocator} class={group_class}>
            <input type="checkbox" id={dom_id} class="component-toggle" />
            <div class={item_class} style={style_str}>
                <label for={dom_id} class="component-toggle-label">
                    <span class="tree-arrow">{icons.ChevronRight(allocator)}</span>
                </label>
                <button value={props.id} onclick={handleComponentClick} class="component-select-btn">
                    <span class="bracket">{"<"}</span>
                    <span class={name_class}>{props.name}</span>
                    <span class="bracket">{">"}</span>
                    {if (props.badge.len > 0) (<span class="component-badge">{props.badge}</span>) else ""}
                </button>
            </div>
            <div class="component-children">
                {for (props.children) |child| (
                    <ComponentItem allocator={allocator} id={child.id} name={child.name} has_children={child.has_children} children={child.children} depth={props.depth + 1} selected={std.mem.eql(u8, child.id, sel)} badge={child.badge} is_native={child.is_native} />
                )}
            </div>
        </div>
    );
}

const StateItem = data.StateItem;
fn StateSection(allocator: zx.Allocator, props: struct { title: []const u8, items: []const StateItem }) ?zx.Component {
    if (props.items.len == 0) return null;
    const id = std.fmt.allocPrint(allocator, "state-section-{s}", .{props.title}) catch "";
    return (
        <div @allocator={allocator} class="state-section">
            <input type="checkbox" id={id} class="state-toggle" checked />
            <label for={id} class="section-header">
                <span class="section-arrow">{icons.ChevronRight(allocator)}</span>
                <span class="section-title">{props.title}</span>
            </label>
            <div class="section-items">
                {for (props.items, 0..) |item, i| (
                    <StateItemRow allocator={allocator} item={item} path={std.fmt.allocPrint(allocator, "ss-{s}-{d}", .{props.title, i}) catch ""} />
                )}
            </div>
        </div>
    );
}

fn StateItemRow(allocator: zx.Allocator, props: struct { item: StateItem, depth: usize = 0, path: []const u8 }) zx.Component {
    const item = props.item;
    const has_children = item.children.len > 0;
    const value_color = getValueColor(item.value);
    const padding = props.depth * 18;
    const style_str = std.fmt.allocPrint(allocator, "padding-left: {d}px", .{padding}) catch "";
    const id = std.fmt.allocPrint(allocator, "sr-{s}", .{ props.path }) catch "";
    const group_class = getStateItemGroupClass(item);
    return (
        <div @allocator={allocator} class={group_class} style={style_str}>
            {if (has_children) (
                <>
                    <input type="checkbox" id={id} class="state-row-toggle" />
                    <label for={id} class="state-item">
                        <span class="item-arrow">{icons.ChevronRight(allocator)}</span>
                        <span class="key-default">{item.key}</span>
                        <span class="item-colon">:</span>
                        <span class={value_color}>{item.value}</span>
                        {if (item.meta.len > 0) (<span class="item-meta">{item.meta}</span>) else ""}
                    </label>
                    <div class="state-item-children">
                        {for (item.children, 0..) |child, i| (
                            <StateItemRow allocator={allocator} item={child} depth={props.depth + 1} path={std.fmt.allocPrint(allocator, "{s}-{d}", .{props.path, i}) catch ""} />
                        )}
                    </div>
                </>
            ) else (
                <div class="state-item">
                    <span class="item-spacer"></span>
                    <span class="key-default">{item.key}</span>
                    <span class="item-colon">:</span>
                    <span class={value_color}>{item.value}</span>
                    {if (item.meta.len > 0) (<span class="item-meta">{item.meta}</span>) else ""}
                </div>
            )}
        </div>
    );
}


fn getComponentGroupClass(name: []const u8, has_children: bool, children: []const Component) []const u8 {
    if (componentOrDescendantMatches(name, has_children, children)) return "component-group";
    return "component-group component-group-hidden";
}

fn getStateItemGroupClass(item: StateItem) []const u8 {
    if (stateItemOrDescendantMatches(item)) return "state-item-group";
    return "state-item-group state-item-group-hidden";
}

fn stateItemOrDescendantMatches(item: StateItem) bool {
    if (stateFilter.len == 0) return true;
    if (containsIgnoreCase(item.key, stateFilter)) return true;
    if (containsIgnoreCase(item.value, stateFilter)) return true;
    if (item.meta.len > 0 and containsIgnoreCase(item.meta, stateFilter)) return true;
    for (item.children) |child| {
        if (stateItemOrDescendantMatches(child)) return true;
    }
    return false;
}

fn componentOrDescendantMatches(name: []const u8, has_children: bool, children: []const Component) bool {
    if (inputvalue.len == 0) return true;
    if (containsIgnoreCase(name, inputvalue)) return true;
    if (has_children) {
        for (children) |child| {
            if (componentOrDescendantMatches(child.name, child.has_children, child.children)) return true;
        }
    }
    return false;
}

fn containsIgnoreCase(haystack: []const u8, needle: []const u8) bool {
    if (needle.len > haystack.len) return false;
    var i: usize = 0;
    while (i <= haystack.len - needle.len) : (i += 1) {
        var match = true;
        for (0..needle.len) |j| {
            if (std.ascii.toLower(haystack[i + j]) != std.ascii.toLower(needle[j])) {
                match = false;
                break;
            }
        }
        if (match) return true;
    }
    return false;
}

fn getValueColor(value: []const u8) []const u8 {
    if (std.mem.indexOf(u8, value, "fn") != null) return "value-function";
    if (std.mem.eql(u8, value, "true") or std.mem.eql(u8, value, "false")) return "value-boolean";
    if (std.mem.startsWith(u8, value, "\"")) return "value-string";
    return "value-default";
}

const zx = @import("zx");
const std = @import("std");
const data = @import("data.zig");

fn filterNativeElements(allocator: std.mem.Allocator, comps: []const Component) []const Component {
    if (data.show_native_elements) return comps;
    var list = std.ArrayList(Component).empty;
    for (comps) |comp| {
        if (comp.is_native) {
            const promoted = filterNativeElements(allocator, comp.children);
            list.appendSlice(allocator, promoted) catch {};
        } else {
            var filtered = comp;
            filtered.children = filterNativeElements(allocator, comp.children);
            filtered.has_children = filtered.children.len > 0;
            list.append(allocator, filtered) catch {};
        }
    }
    return list.toOwnedSlice(allocator) catch comps;
}
