CTask* browser_task = Fs;

JsonObject* cyberia = Json.CreateObject(Fs);
Context2D* APP_ICON = @image_file_to_context2d("M:/Applications/Internet/Cyberia.app/Resources/icon.png");

Window* win = NULL;
TabPanelWidget* tabpanel1 = NULL;
RectWidget* controlsbackdrop1 = NULL;
TextLabelWidget* status1 = NULL;
TextLabelWidget* status2 = NULL;
RectWidget* statusbackdrop1 = NULL;
VerticalScrollBarWidget* vscroll1 = NULL;

U8* previous_hovered_href = NULL;

ButtonWidget* backbtn1 = NULL;
ButtonWidget* fwdbtn1 = NULL;
ButtonWidget* refreshbtn1 = NULL;
ButtonWidget* hanbagabtn1 = NULL;
RectWidget* background1 = NULL;
TextInputWidget* addressbar1 = NULL;

@window_widgets_list* widgets_base = NULL;
@html_dom_node* node_list = NULL;

I64 prev_win_width = -1;
I64 prev_win_height = -1;
I64 prev_mouse_z = Mouse.z;

Window* hanbaga_menu = Menu.New("Cyberia");
Menu.AddItem(hanbaga_menu, "New Tab", NULL, &Reboot, NULL, NULL);
Menu.AddItem(hanbaga_menu, "New Window", NULL, NULL, NULL, NULL);
Menu.AddItem(hanbaga_menu, "New Private Window", NULL, NULL, NULL, NULL);

class @browser
{
    HtmlRenderer* renderer;
    JsonArray* bookmarks;
    JsonArray* history;
    I64 history_index;
    JsonObject* javascript_link_handlers;
    CTask* task;
    TabPanelTab* tab;
    U8* fetch_buffer;
    U8* lazyload_buffer;
    U8* lazyload_timeout_buffer;
    U8* go_to_url_string;
    U8* search_query;
};

@browser* browser = NULL;

I64 @cyberia_is_alphanum(I64 c)
{
    return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9');
}

U8* @cyberia_urlencode_str(U8* str, I64* output_len)
{
    if (str == NULL) {
        return NULL;
    }

    I64 len = StrLen(str);
    U8* encoded_str = NULL;
    U8* dest;
    I64 encoded_len = 0;
    U8* p;
    I64 c;
    Bool contains_plus = FALSE;

    // First pass: calculate the exact required length
    p = str;
    while (*p) {
        c = *p;
        // Check if the character should NOT be encoded
        if (@cyberia_is_alphanum(c) || c == '-' || c == '_' || c == '.') {
            encoded_len++;
        } else if (c == ' ') {
            encoded_len++; // Will be replaced by '+'
            contains_plus = TRUE;
        } else {
            encoded_len += 3; // Will be replaced by %XX
        }
        ++p;
    }

    *output_len = encoded_len;

    if (encoded_len == StrLen(str) && !contains_plus)
        return str;

    // Allocate memory for the encoded string + null terminator
    encoded_str = CAlloc(encoded_len + 1, browser_task);
    if (!encoded_str)
        return NULL;

    // Second pass: build the encoded string
    dest = encoded_str;
    p = str;
    while (*p) {
        c = *p;
        if (@cyberia_is_alphanum(c) || c == '-' || c == '_' || c == '.') {
            *dest++ = c;
        } else if (c == ' ') {
            *dest++ = '+';
        } else {
            StrPrint(dest, "%%%02X", c);
            dest += 3;
        }
        ++p;
    }
    *dest = '\0';
    return encoded_str;
}

U0 @cyberia_win_really_close(Window* win)
{
    // Free everything
    if (win == Compositor.active_win) {
        Gui.Window.SetFocus(Compositor.GetWindowByTitle("Wallpaper"));
    }
    Compositor.UnregisterForGlobalInputEvents(win);
    Compositor.DestroyWindow(win);
    Kill(app_event_loop);
}

U0 @cyberia_win_close(Window* win)
{
    if (KeyDown(SC_CTRL) && KeyDown(Char2ScanCode('w')))
        return;
    @cyberia_win_really_close(win);
}

U8* @browser_tls_connection_state(@http_response* resp)
{
    if (!resp || !resp->s || !resp->s->ctx)
        return NULL;

    I64 connect_state = @tls_connection_status(resp->s->ctx);
    switch (connect_state) {
    case 0:
        return "TLS: Sent Client Hello";
    case 1:
        return "TLS: Parsed Server Hello";
    case 2:
        return "TLS: Key Share";
    case 0xff:
        return "TLS: Finished";
    default:
        return "";
    }
}
extern U0 @cyberia_navigate(Bool refresh = FALSE);

U0 @cyberia_hanbaga_show(Widget* widget)
{
    hanbaga_menu->x = win->x + win->width - hanbaga_menu->width - 5;
    hanbaga_menu->y = win->y + hanbagabtn1->y + hanbagabtn1->height + 24;
    Gui.Window.Show(hanbaga_menu);
    Gui.Window.SetFocus(hanbaga_menu);
    Gui.Window.Refresh(hanbaga_menu);
}

U0 @cyberia_link_clicked(Widget* widget)
{
    @html_dom_node* node = @self_or_ancestor_matches_tag_name(widget->data, "a");
    if (!node)
        return;
    U8* unresolved_href = node->attributes->@("href");
    if (!unresolved_href)
        return;
    U8* resolved_href = @resolve_href(browser->renderer, unresolved_href);
    if (!resolved_href)
        return;
    StrCpy(&addressbar1->text, resolved_href);
    Free(resolved_href);
    Spawn(&@cyberia_navigate);
}

U0 @cyberia_collect_form_nodes(JsonArray* array, @html_dom_node* node)
{
    if (!array || !node)
        return;

    if (!StrICmp(node->tagName, "input") || !StrICmp(node->tagName, "textarea")) {
        if (node->attributes->@("name")) {
            array->append(node, JSON_NUMBER);
        }
    }

    I64 i;
    if (node->children->length) {
        for (i = 0; i < node->children->length; i++)
            @cyberia_collect_form_nodes(array, node->children->@(i));
    }
}

I64 @cyberia_count_text_input_elements(@html_dom_node* node)
{
    I64 count = 0;
    if (!node)
        return count;

    if (!StrICmp(node->tagName, "input") && (!node->attributes->@("type") || (!StrICmp(node->attributes->@("type"), "text") || !StrICmp(node->attributes->@("type"), "password"))))
        ++count;

    I64 i;
    if (node->children->length) {
        for (i = 0; i < node->children->length; i++)
            count += @cyberia_count_text_input_elements(node->children->@(i));
    }
    return count;
}

Bool @cyberia_form_has_one_text_input_element(@html_dom_node* form_node)
{
    return @cyberia_count_text_input_elements(form_node);
}

U0 @cyberia_form_submit(@html_dom_node* form_node)
{
    if (!form_node->attributes->@("method"))
        form_node->attributes->set("method", "get", JSON_STRING);

    if (!form_node->attributes->@("action"))
        form_node->attributes->set("action", "#", JSON_STRING);

    I64 i;
    U8* method = form_node->attributes->@("method");
    U8* action = @resolve_href(browser->renderer, form_node->attributes->@("action"));
    @html_dom_node* element_node;

    JsonArray* form_elements = Json.CreateArray(browser->renderer->task);
    @cyberia_collect_form_nodes(form_elements, form_node);

    U8 get_request_str[1024];

    if (!StrICmp(method, "get")) {
        StrPrint(get_request_str, "%s?", action);
        for (i = 0; i < form_elements->length; i++) {
            element_node = form_elements->@(i);
            U8* name = element_node->attributes->@("name");
            U8* raw_value = "";
            U8* encoded_value = NULL;
            I64 encoded_value_length = 0;
            Widget* element_gui_widget = element_node->attributes->@("cyberiaGuiWidget");
            switch (element_gui_widget->type) {
            case WIDGET_TYPE_CHECKBOX:
                if (element_gui_widget(CheckBoxWidget*)->checked) {
                    raw_value = @t(element_node->attributes->@("value"), element_node->attributes->@("value"), "on");
                } else {
                    raw_value = NULL;
                }
                break;
            case WIDGET_TYPE_INPUT:
                raw_value = &element_gui_widget(TextInputWidget*)->text;
                break;
            default:
                break;
            }

            encoded_value = @cyberia_urlencode_str(raw_value, &encoded_value_length);

            if (encoded_value) {
                String.Append(get_request_str, "%s=%s", name, encoded_value);
                if (i < form_elements->length - 1)
                    String.Append(get_request_str, "&");
                if (encoded_value_length != StrLen(raw_value)) {
                    Free(encoded_value);
                }
            }
        }
        StrCpy(&addressbar1->text, &get_request_str);
    }

    Free(action);
    Spawn(&@cyberia_navigate);
}

U0 @cyberia_form_submit_clicked(Widget* widget)
{
    if (!widget || !widget->data)
        return;

    @html_dom_node* form_node = @self_or_ancestor_matches_tag_name(widget->data, "form");

    if (!form_node)
        return;

    @cyberia_form_submit(form_node);
}

U0 @cyberia_refresh_clicked()
{
    if (refreshbtn1->disabled)
        return;
    Spawn(&@cyberia_navigate, TRUE);
}

U0 @cyberia_update_history_buttons()
{
    backbtn1->disabled = TRUE;
    fwdbtn1->disabled = TRUE;
    if (browser->history_index > 0)
        backbtn1->disabled = FALSE;
    if (browser->history->length - 1 > browser->history_index)
        fwdbtn1->disabled = FALSE;
}

U0 @cyberia_set_tab_title_and_icon(TabPanelTab* tab, U8* title, Context2D* icon)
{
    if (!tab)
        return;
    if (icon) {
        tab->icon = icon;
    }
    if (!title || !StrLen(title)) {
        StrCpy(&tab->text, "Untitled page");
    }
    if (StrLen(title) > 32) {
        MemCpy(&tab->text, title, 29);
        StrCpy(&tab->text + 28, "...");
    } else {
        StrCpy(&tab->text, title);
    }
}

U0 @cyberia_set_tab_title(U8* title)
{
    @cyberia_set_tab_title_and_icon(browser->tab, title, NULL);
}

U0 @cyberia_history()
{
    @cyberia_update_history_buttons;
    win->focused_widget = NULL;

    HtmlRenderer* renderer = browser->renderer = browser->history->@(browser->history_index);

    HttpUrl* url = renderer->current_url;
    StrCpy(&addressbar1->text, "");

    if (!StrICmp(url->host, "127.0.0.255")) {
        StrCpy(&addressbar1->text, "about:newtab");
    } else {
        Bool is_alternate_port = FALSE;
        if (!StrICmp(url->scheme, "http://") && url->port != 80)
            is_alternate_port = TRUE;
        if (!StrICmp(url->scheme, "https://") && url->port != 443)
            is_alternate_port = TRUE;
        if (is_alternate_port)
            String.Append(&addressbar1->text, "%s%s:%d%s%s", url->scheme, url->host, url->port, url->path, url->query);
        else
            String.Append(&addressbar1->text, "%s%s%s%s", url->scheme, url->host, url->path, url->query);
    }

    background1->color = renderer->background_color;
    @cyberia_set_tab_title_and_icon(browser->tab, renderer->current_title, @favicon_for_page(renderer));

    @reflow_node_list(renderer);
    widgets_base->next = renderer->widgets_base;

    status1->SetText("Done");
    refreshbtn1->disabled = FALSE;
}

U0 @cyberia_back_clicked()
{
    if (backbtn1->disabled)
        return;
    if (browser->history_index > 0) {
        --browser->history_index;
        @cyberia_history;
    }
}

U0 @cyberia_fwd_clicked()
{
    if (fwdbtn1->disabled)
        return;
    if (browser->history_index < browser->history->length - 1) {
        ++browser->history_index;
        @cyberia_history;
    }
}

@window_widgets_list* @cyberia_append_widget_to_list(@window_widgets_list* list, Widget* widget, CTask* task)
{
    while (list->next) {
        list = list->next;
    }
    list->next = CAlloc(sizeof(@window_widgets_list), task);
    list->next->prev = list;
    list->next->widget = widget;
    return list->next;
}

U8* NEW_TAB_HTML = "<html><head><title>New tab</title><body></body></html>";

U0 @cyberia_init_renderer(HtmlRenderer* renderer, U8* url_string)
{
    MemSet(renderer, 0, sizeof(HtmlRenderer));

    renderer->images = NULL;
    renderer->link_pointer = Compositor.theme.pointer.link;

    renderer->link_callback = &@cyberia_link_clicked;
    renderer->form_submit_callback = &@cyberia_form_submit_clicked;
    renderer->image_load_callback = &@reflow_node_list;
    renderer->title_callback = &@cyberia_set_tab_title;

    renderer->status_widget = status1;
    renderer->background_color = Color(255, 255, 255);
    renderer->background_widget = background1;

    renderer->vertical_scroll_widget = vscroll1;

    renderer->win = CAlloc(sizeof(Window), browser_task);
    renderer->win->widget = CAlloc(sizeof(@window_widgets_list), browser_task);
    renderer->widgets_base = renderer->win->widget;
    renderer->reflow_win = win;

    renderer->indent = -1;
    // renderer->debug = 1;

    renderer->current_url_string = StrNew(url_string, browser_task);
    renderer->current_url = @http_parse_url(url_string);
    renderer->cache_directory = HTTP_CACHE_DIRECTORY;
    renderer->task = browser_task;
}

U0 @cyberia_navigate(Bool refresh = FALSE)
{
    win->focused_widget = NULL;

    if (!StrLen(&addressbar1->text)) {
        return;
    }

    if (MemCmp(&addressbar1->text, "about:", 6) && MemCmp(&addressbar1->text, "http://", 7) && MemCmp(&addressbar1->text, "https://", 8)) {
        U8 prepend_buf[512];
        StrPrint(prepend_buf, "https://%s", &addressbar1->text);
        StrCpy(&addressbar1->text, prepend_buf);
    }

    U8* url_string = StrNew(&addressbar1->text);
    if (!url_string || !browser || !browser_task)
        return;

    if (!StrCmp(&addressbar1->text, "about:newtab")) {
        url_string = StrNew("http://127.0.0.255");
    }

    if (!refresh) {
        browser->renderer = CAlloc(sizeof(HtmlRenderer), browser_task);
        ++browser->history_index;
        while (browser->history->@(browser->history_index)) {
            browser->history->remove(browser->history_index);
        }
        browser->history->append(browser->renderer, JSON_NUMBER);
        @cyberia_update_history_buttons;
    }

    HtmlRenderer* renderer = browser->renderer;
    @cyberia_init_renderer(renderer, url_string);

    U8 err_msg_buffer[128];
    U8 status_text_buffer[1024];

    if (!renderer->current_url) {
        StrCpy(err_msg_buffer, "ERROR: Could not parse URL");
        MessageBox.Error(err_msg_buffer);
        Free(url_string);
        return;
    }

    if (!@is_supported_url_scheme(renderer->current_url)) {
        StrPrint(err_msg_buffer, "ERROR: Unsupported URL scheme: %s", renderer->current_url->scheme);
        MessageBox.Error(err_msg_buffer);
        Free(url_string);
        return;
    }

    HttpUrl* url = renderer->current_url;

    Bool is_alternate_port = FALSE;
    if (!StrICmp(url->scheme, "http://") && url->port != 80)
        is_alternate_port = TRUE;
    if (!StrICmp(url->scheme, "https://") && url->port != 443)
        is_alternate_port = TRUE;
    StrCpy(status_text_buffer, "Fetching ");
    if (is_alternate_port)
        String.Append(status_text_buffer, "%s%s:%d%s%s", url->scheme, url->host, url->port, url->path, url->query);
    else
        String.Append(status_text_buffer, "%s%s%s%s", url->scheme, url->host, url->path, url->query);
    String.Append(status_text_buffer, "...");
    status1->SetText(status_text_buffer);

    U8* buffer = browser->fetch_buffer;
    MemSet(buffer, 0, HTTP_FETCH_BUFFER_SIZE);

    @http_response* resp = NULL;

    if (!StrCmp(&addressbar1->text, "about:newtab")) {
        resp = CAlloc(sizeof(@http_response));
        resp->body.data = NEW_TAB_HTML;
        resp->body.length = StrLen(NEW_TAB_HTML);
        resp->state = HTTP_STATE_DONE;
        resp->status.code = 200;
        StrCpy(&addressbar1->text, "");
        win->focused_widget = addressbar1;
    } else {
        resp = Http.Get(renderer->current_url, buffer);
    }

    while (resp->state != HTTP_STATE_DONE) {
        if (resp->state >= HTTP_STATE_HEADERS_RECEIVED) {
            StrPrint(status_text_buffer, "Received %d bytes", resp->body.length);
            status1->SetText(status_text_buffer);
        } else {
            if (@http_scheme_is_https(renderer->current_url)) {
                if (@browser_tls_connection_state(resp)) {
                    StrPrint(status_text_buffer, "%s", @browser_tls_connection_state(resp));
                    status1->SetText(status_text_buffer);
                }
            }
        }
        Sleep(1);
    }

    if (resp->status.code == 301 || resp->status.code == 302) {
        U8* unresolved_location = resp->headers->@("Location");
        if (!unresolved_location)
            return;
        U8* resolved_location = @resolve_href(browser->renderer, unresolved_location);
        if (!resolved_location)
            return;
        StrCpy(&addressbar1->text, resolved_location);
        Free(resolved_location);
        @cyberia_navigate(1);
        return;
    }

    // Create node tree
    I64 images_count = 0;
    node_list = @html_tokenize_and_create_node_list(resp->body.data, resp->body.length, renderer->task, &images_count);

    // Create empty CSS rules array, traverse node tree and populate CSS rules array
    renderer->css_rules = Json.Clone(CSS_DEFAULT_RULES, renderer->task);
    renderer->forms = Json.CreateArray(renderer->task);
    @process_css_rules_from_node_list(node_list, renderer);
    if (renderer->debug) {
        "=== Begin dump of CSS rules ===\n";
        "%s\n", Json.Stringify(renderer->css_rules, renderer->task);
        "=== End dump of CSS rules ===\n";
    }

    status1->SetText("Rendering page...");
    Sleep(100);
    @render_node_list(node_list, renderer);
    @cyberia_set_tab_title_and_icon(browser->tab, renderer->current_title, @favicon_for_page(renderer));

    @window_widgets_list* append = renderer->widgets_base;
    append = @cyberia_append_widget_to_list(append, controlsbackdrop1, browser_task);
    append = @cyberia_append_widget_to_list(append, backbtn1, browser_task);
    append = @cyberia_append_widget_to_list(append, fwdbtn1, browser_task);
    append = @cyberia_append_widget_to_list(append, refreshbtn1, browser_task);
    append = @cyberia_append_widget_to_list(append, hanbagabtn1, browser_task);
    append = @cyberia_append_widget_to_list(append, addressbar1, browser_task);
    append = @cyberia_append_widget_to_list(append, statusbackdrop1, browser_task);
    append = @cyberia_append_widget_to_list(append, status1, browser_task);
    append = @cyberia_append_widget_to_list(append, status2, browser_task);
    append = @cyberia_append_widget_to_list(append, vscroll1, browser_task);
    append = @cyberia_append_widget_to_list(append, tabpanel1, browser_task);

    vscroll1->scroll = 0;

    @cyberia_history;
    @fetch_images_for_page(renderer);
}

U0 @cyberia_new_tab()
{
    TabPanelTab* new_tab = CAlloc(sizeof(TabPanelTab));
    new_tab->icon = DEFAULT_FAVICON;
    StrCpy(&new_tab->text, "New Tab");

    @browser* new_browser = CAlloc(sizeof(@browser), browser_task);
    new_browser->history = Json.CreateArray(browser_task);
    new_browser->history_index = -1;
    new_browser->tab = new_tab;
    new_browser->renderer = NULL;
    new_browser->task = browser_task;
    new_browser->fetch_buffer = CAlloc(HTTP_FETCH_BUFFER_SIZE, browser_task);
    new_browser->go_to_url_string = NULL;
    new_browser->javascript_link_handlers = Json.CreateObject(browser_task);
    new_browser->lazyload_buffer = CAlloc(HTTP_FETCH_BUFFER_SIZE, browser_task);
    new_browser->lazyload_timeout_buffer = CAlloc(HTTP_FETCH_BUFFER_SIZE, browser_task);

    browser = new_tab->data = new_browser;

    I64 i = 0;
    TabPanelTab* tabs = tabpanel1->tabs;
    if (!tabs) {
        tabpanel1->tabs = new_tab;
    } else {
        i = 1;
        while (tabs->next) {
            tabs = tabs->next;
            ++i;
        }
        tabs->next = new_tab;
        new_tab->prev = tabs;
    }
    tabpanel1->index = i;
    ++tabpanel1->count;

    StrCpy(&addressbar1->text, "about:newtab");
    Spawn(&@cyberia_navigate);
    win->focused_widget = addressbar1;
}

TabPanelTab* @cyberia_tab_index(TabPanelWidget* widget, I64 index)
{
    TabPanelTab* tab = widget->tabs;
    if (!tab) {
        return NULL;
    }
    I64 i;
    for (i = 0; i < index; i++) {
        tab = tab->next;
        if (!tab)
            return NULL;
    }
    return tab;
}

U0 @cyberia_select_tab(I64 index)
{
    tabpanel1->index = index;
    browser = @cyberia_tab_index(tabpanel1, index)->data;
    @cyberia_history;
}

U0 @cyberia_close_current_tab()
{
    TabPanelTab* tab = @cyberia_tab_index(tabpanel1, tabpanel1->index);
    --tabpanel1->count;

    // FIXME: Free tab

    // Close first tab
    if (!tabpanel1->index) {
        if (tab->next) {
            tabpanel1->tabs = tab->next;
        }
        @cyberia_select_tab(tabpanel1->index);
        return;
    }

    // Close last tab
    if (tabpanel1->count == tabpanel1->index) {
        if (tab->prev) {
            tab->prev->next = NULL;
        }
        --tabpanel1->index;
        @cyberia_select_tab(tabpanel1->index);
        return;
    }

    // Close other tab
    if (tab->prev) {
        tab->prev->next = tab->next;
    }
    if (tab->next) {
        tab->next->prev = tab->prev;
    }
    @cyberia_select_tab(tabpanel1->index);
}

U0 @cyberia_handle_numbered_tab_navigation()
{
    if (KeyDown(SC_ALT)) {
        if (KeyDown(Char2ScanCode('1')) && tabpanel1->count > 0) {
            @cyberia_select_tab(0);
            return;
        }
        if (KeyDown(Char2ScanCode('2')) && tabpanel1->count > 1) {
            @cyberia_select_tab(1);
            return;
        }
        if (KeyDown(Char2ScanCode('3')) && tabpanel1->count > 2) {
            @cyberia_select_tab(2);
            return;
        }
        if (KeyDown(Char2ScanCode('4')) && tabpanel1->count > 3) {
            @cyberia_select_tab(3);
            return;
        }
        if (KeyDown(Char2ScanCode('5')) && tabpanel1->count > 4) {
            @cyberia_select_tab(4);
            return;
        }
        if (KeyDown(Char2ScanCode('6')) && tabpanel1->count > 5) {
            @cyberia_select_tab(5);
            return;
        }
        if (KeyDown(Char2ScanCode('7')) && tabpanel1->count > 6) {
            @cyberia_select_tab(6);
            return;
        }
        if (KeyDown(Char2ScanCode('8')) && tabpanel1->count > 7) {
            @cyberia_select_tab(7);
            return;
        }
    }
}

U0 @cyberia_win_keypress(Window* w, I64 event)
{
    if (!event || Compositor.active_win != win)
        return;

    @cyberia_handle_numbered_tab_navigation;

    if (KeyDown(SC_ALT)) {
        if (KeyDown(Char2ScanCode('9'))) {
            @cyberia_select_tab(tabpanel1->count - 1);
            return;
        }
        if (KeyDown(SC_CURSOR_LEFT)) {
            @cyberia_back_clicked;
            return;
        }
        if (KeyDown(SC_CURSOR_RIGHT)) {
            @cyberia_fwd_clicked;
            return;
        }
    }

    if (KeyDown(SC_CTRL) && KeyDown(Char2ScanCode('t'))) {
        @cyberia_new_tab;
        return;
    }
    if (KeyDown(SC_CTRL) && KeyDown(Char2ScanCode('w'))) {
        if (tabpanel1->count > 1) {
            @cyberia_close_current_tab;
        } else {
            @cyberia_win_really_close(win);
        }
        return;
    }

    if (!w || !w->focused_widget || !KeyDown(SC_ENTER))
        return;

    if (w->focused_widget == addressbar1) {
        Spawn(&@cyberia_navigate);
        return;
    }

    @html_dom_node* node = w->focused_widget->data;

    if (!node)
        return;
    if (StrICmp(node->tagName, "input"))
        return;
    if (node->attributes->@("type") && (StrICmp(node->attributes->@("type"), "text") && StrICmp(node->attributes->@("type"), "password")))
        return;

    @html_dom_node* form_node = @self_or_ancestor_matches_tag_name(node, "form");
    if (!form_node)
        return;

    // If we are the only text input element, submit the form
    if (@cyberia_form_has_one_text_input_element(form_node)) {
        @cyberia_form_submit(form_node);
    }
}

U0 @cyberia_vscroll_change(Widget*)
{
    if (!browser || !browser->renderer)
        return;

    @reflow_node_list(browser->renderer);
}

U0 @cyberia_win_repaint(Window*)
{
    if (!win || !tabpanel1 || !addressbar1 || !background1 || !vscroll1 || !status1 || !statusbackdrop1)
        return;

    tabpanel1->width = win->width - 9;
    tabpanel1->height = 28;

    I64 offset_y = tabpanel1->y + tabpanel1->height + 1;

    background1->y = offset_y + 36;
    controlsbackdrop1->y = -8;
    controlsbackdrop1->height = background1->y + 8;

    backbtn1->y = offset_y + 0;
    fwdbtn1->y = offset_y + 0;
    refreshbtn1->y = offset_y + 0;
    hanbagabtn1->y = offset_y + 0;

    addressbar1->y = offset_y + 6;

    hanbagabtn1->x = win->width - hanbagabtn1->width - 9;
    addressbar1->width = hanbagabtn1->x - addressbar1->x - 3;
    background1->width = win->width - 9;
    background1->height = win->height - background1->y - 48;

    vscroll1->x = win->width;
    vscroll1->y = background1->y;
    vscroll1->width = 16;
    vscroll1->height = background1->height;

    I64 delta_z = Mouse.z - prev_mouse_z;
    if (delta_z) {
        vscroll1->scroll += (RENDERER_DEFAULT_MAX_LINE_HEIGHT * delta_z);
    }
    prev_mouse_z = Mouse.z;

    if (StrLen(&status2->text)) {
        status1->y = win->height;
        status2->y = win->height - 40;
    } else {
        status1->y = win->height - 40;
        status2->y = win->height;
    }

    statusbackdrop1->y = background1->y + background1->height;

    if (!browser || !browser->renderer || !widgets_base)
        return;

    if (browser->renderer->calculated_page_height > background1->height) {
        vscroll1->max = browser->renderer->calculated_page_height;
        vscroll1->length = (vscroll1->height - 31) / MaxI64(2, ToI64(browser->renderer->calculated_page_height / background1->height));
        vscroll1->x = win->width - 25;
        background1->width -= vscroll1->width;
    }

    if (widgets_base->next && (prev_win_width != win->width || prev_win_height != win->height) || delta_z) {
        @reflow_node_list(browser->renderer);
        prev_win_width = win->width;
        prev_win_height = win->height;
    }
}

U0 @cyberia_unset_status_text()
{
    StrCpy(&status2->text, "");
    previous_hovered_href = NULL;
}

U0 @cyberia_addressbar_clicked(Widget*)
{
    if (StrLen(&addressbar1->text) && addressbar1->selected_region_start == -1 && addressbar1->selected_region_end == -1) {
        addressbar1->selected_region_start = 0;
        addressbar1->selected_region_end = StrLen(&addressbar1->text) - 1;
    }
}

U0 @cyberia_tabpanel_clicked(TabPanelWidget* widget)
{
    if (browser != @cyberia_tab_index(widget, widget->index)->data) {
        @cyberia_select_tab(widget->index);
    }
}

U0 @cyberia_win_mouseat(Window*)
{
    if (addressbar1 && win->focused_widget != addressbar1) {
        addressbar1->selected_region_start = -1;
        addressbar1->selected_region_end = -1;
    }

    if (!win->hovered_widget || !win->hovered_widget->pointer) {
        @cyberia_unset_status_text;
        return;
    }
    @html_dom_node* node = @self_or_ancestor_matches_tag_name(win->hovered_widget->data, "a");
    if (!node)
        return;
    U8* unresolved_href = node->attributes->@("href");
    if (!unresolved_href || previous_hovered_href == unresolved_href)
        return;
    previous_hovered_href = unresolved_href;
    U8* resolved_href = @resolve_href(browser->renderer, unresolved_href);
    if (!resolved_href)
        return;

    StrCpy(&status2->text, resolved_href);
    Free(resolved_href);
}

U0 @cyberia_init_widgets_base()
{
    widgets_base = win->widget;
    while (widgets_base->next) {
        widgets_base = widgets_base->next;
    }
}

U0 @cyberia_init()
{
    win = Compositor.CreateWindow(24, 24, 992, 768, WIN_FLAGS_DEFAULT);
    // win->explicit_repaint = TRUE;
    Gui.Window.SetCallback(win, "close", &@cyberia_win_close);
    Gui.Window.SetCallback(win, "repaint", &@cyberia_win_repaint);
    Gui.Window.SetCallback(win, "mouseat", &@cyberia_win_mouseat);
    Gui.Window.SetIcon(win, APP_ICON);
    Gui.Window.SetTitle(win, "Cyberia Browser");
    Gui.Window.Center(win);
    Gui.Window.SetFocus(win);

    background1 = Gui.CreateWidget(win, WIDGET_TYPE_RECT, 0, 36, 0, 0);
    background1->color = Color(255, 255, 255);

    controlsbackdrop1 = Gui.CreateWidget(win, WIDGET_TYPE_RECT, -14, 0, Display.Width(), 36 + 14);
    controlsbackdrop1->y = -14;
    controlsbackdrop1->color = Color(204, 204, 204);

    statusbackdrop1 = Gui.CreateWidget(win, WIDGET_TYPE_RECT, 0, 0, Display.Width(), 48);
    statusbackdrop1->color = Color(204, 204, 204);

    status1 = Gui.CreateWidget(win, WIDGET_TYPE_LABEL, 0, 0, 320, 16);
    Gui.Widget.SetFont(status1, "Eight Bit Dragon");
    Gui.Widget.SetText(status1, "Idle");

    status2 = Gui.CreateWidget(win, WIDGET_TYPE_LABEL, 0, 0, 320, 16);
    Gui.Widget.SetFont(status2, "Eight Bit Dragon");
    Gui.Widget.SetText(status2, "");

    backbtn1 = Gui.CreateWidget(win, WIDGET_TYPE_BUTTON, 0, 0, 24, 24);
    Gui.Widget.SetText(backbtn1, "");
    Gui.Widget.SetCallback(backbtn1, "clicked", &@cyberia_back_clicked);
    backbtn1->image = @image_file_to_context2d("M:/Media/Themes/Umami/Icon/actions/back.png");
    backbtn1->disabled_image = @image_file_to_context2d("M:/Media/Themes/Umami/Icon/actions/back-disabled.png");
    backbtn1->disabled = TRUE;
    backbtn1->width = backbtn1->image->width + 8;

    fwdbtn1 = Gui.CreateWidget(win, WIDGET_TYPE_BUTTON, 33, 0, 24, 24);
    Gui.Widget.SetText(fwdbtn1, "");
    Gui.Widget.SetCallback(fwdbtn1, "clicked", &@cyberia_fwd_clicked);
    fwdbtn1->image = @image_file_to_context2d("M:/Media/Themes/Umami/Icon/actions/forward.png");
    fwdbtn1->disabled_image = @image_file_to_context2d("M:/Media/Themes/Umami/Icon/actions/forward-disabled.png");
    fwdbtn1->disabled = TRUE;
    fwdbtn1->width = fwdbtn1->image->width + 8;

    refreshbtn1 = Gui.CreateWidget(win, WIDGET_TYPE_BUTTON, 66, 0, 24, 24);
    Gui.Widget.SetText(refreshbtn1, "");
    Gui.Widget.SetCallback(refreshbtn1, "clicked", &@cyberia_refresh_clicked);
    refreshbtn1->image = @image_file_to_context2d("M:/Media/Themes/Umami/Icon/actions/reload.png");
    refreshbtn1->disabled_image = @image_file_to_context2d("M:/Media/Themes/Umami/Icon/actions/reload-disabled.png");
    refreshbtn1->disabled = TRUE;
    refreshbtn1->width = refreshbtn1->image->width + 8;

    hanbagabtn1 = Gui.CreateWidget(win, WIDGET_TYPE_BUTTON, U64_MAX, 0, 24, 24);
    Gui.Widget.SetText(hanbagabtn1, "");
    Gui.Widget.SetCallback(hanbagabtn1, "clicked", &@cyberia_hanbaga_show);
    hanbagabtn1->image = @image_file_to_context2d("M:/Applications/Internet/Cyberia.app/Resources/hanbaga.png");
    hanbagabtn1->width = hanbagabtn1->image->width + 8;

    vscroll1 = Gui.CreateWidget(win, WIDGET_TYPE_VERT_SCROLLBAR, -99999, -99999, 0, 0);
    Gui.Widget.SetCallback(vscroll1, "change", &@cyberia_vscroll_change);

    addressbar1 = Gui.CreateWidget(win, WIDGET_TYPE_INPUT, 100, 6, 320, 16);
    Gui.Widget.SetFont(addressbar1, "Eight Bit Dragon");
    Gui.Widget.SetCallback(addressbar1, "clicked", &@cyberia_addressbar_clicked);
    Gui.Window.SetCallback(win, "keypress", &@cyberia_win_keypress);
    Compositor.RegisterForGlobalInputEvents(win);

    tabpanel1 = Gui.CreateWidget(win, WIDGET_TYPE_TAB_PANEL, 0, 0, 0, 0);
    tabpanel1->size = 234;
    Gui.Widget.SetCallback(tabpanel1, "clicked", &@cyberia_tabpanel_clicked);

    @cyberia_init_widgets_base;

    @cyberia_new_tab;
    @cyberia_win_repaint(win);
}

@cyberia_init;

U0 Main()
{
    while (1) {
        if (Compositor.active_win != hanbaga_menu && Gui.Window.IsVisible(hanbaga_menu))
            Gui.Window.Hide(hanbaga_menu);
        Sleep(1);
    }
}

Main;
