/*
* Ricochet - A minimalist CLI web browser for fast, distraction-free navigation.
* Copyright (C) 2026 Richard Qin
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "ricochet/core/form_handler.hh"
#include "ricochet/core/ui_input.hh"
#include "ricochet/core/utils.hh"
namespace ricochet::core {
InputAction submit_form( const render::InputField& target_input,
const std::vector& inputs,
const std::string& current_url,
std::vector& history,
std::size_t& history_idx )
{
std::string body;
for ( const auto& in : inputs ) {
if ( in.action == target_input.action && !in.name.empty() && in.type != "submit" && in.type != "button" ) {
if ( ( in.type == "checkbox" || in.type == "radio" ) && !in.checked ) {
continue;
}
std::string encoded = in.value;
for ( char& ch : encoded ) {
if ( ch == ' ' ) {
ch = '+';
}
}
if ( !body.empty() ) {
body += "&";
}
body += in.name + "=" + encoded;
}
}
const std::string action
= target_input.action.empty() ? current_url : normalize_url( target_input.action, current_url );
HttpRequest next_req { .url = action, .method = "GET", .body = "" };
if ( target_input.method == "post" || target_input.method == "POST" ) {
next_req.method = "POST";
next_req.body = body;
} else {
const char sep = action.contains( '?' ) ? '&' : '?';
next_req.url += sep + body;
}
history.resize( history_idx + 1 );
history.push_back( next_req );
history_idx++;
return InputAction::Navigate;
}
void update_input_value( PageData& page_data, std::size_t index, const std::string& query )
{
page_data.inputs[index - 1].value = query;
const std::string marker = "[I" + std::to_string( index ) + ":";
const std::string replacement = "[I" + std::to_string( index ) + ":" + query + "]";
auto update_lines = [&]( std::vector& lines_arr ) {
for ( auto& line : lines_arr ) {
const std::size_t pos = line.find( marker );
if ( pos != std::string::npos ) {
const std::size_t end = line.find( ']', pos );
if ( end != std::string::npos ) {
line.replace( pos, end - pos + 1, replacement );
}
}
}
};
update_lines( page_data.lines );
update_lines( page_data.original_lines );
}
void update_single_line_ui( std::string& line, const std::string& marker, const render::InputField& in )
{
const std::string checked_str = marker + ( in.type == "checkbox" ? "[X] " : "(X) " ) + in.value + "]";
const std::string unchecked_str = marker + ( in.type == "checkbox" ? "[ ] " : "( ) " ) + in.value + "]";
const std::string& target_str = in.checked ? checked_str : unchecked_str;
const std::string& old_str = in.checked ? unchecked_str : checked_str;
const std::size_t pos = line.find( old_str );
if ( pos != std::string::npos ) {
line.replace( pos, old_str.length(), target_str );
}
}
void update_toggle_ui( PageData& page_data )
{
for ( std::size_t i = 0; i < page_data.inputs.size(); ++i ) {
const auto& in = page_data.inputs[i];
if ( in.type != "checkbox" && in.type != "radio" ) {
continue;
}
const std::string marker = "[I" + std::to_string( i + 1 ) + ":";
for ( auto& line : page_data.lines ) {
update_single_line_ui( line, marker, in );
}
for ( auto& line : page_data.original_lines ) {
update_single_line_ui( line, marker, in );
}
}
}
InputAction handle_form_input( const tui::Terminal& terminal,
PageData& page_data,
const std::string& current_url,
std::vector& history,
std::size_t& history_idx )
{
const std::string num_input = get_terminal_input( terminal, "Select Input [#]:" );
if ( num_input.empty() ) {
return InputAction::None;
}
std::size_t index = 0;
try {
index = std::stoul( num_input );
} catch ( ... ) {
return InputAction::None;
}
if ( index == 0 || index > page_data.inputs.size() ) {
return InputAction::None;
}
auto& target_input = page_data.inputs[index - 1];
if ( target_input.type == "submit" || target_input.type == "button" ) {
return submit_form( target_input, page_data.inputs, current_url, history, history_idx );
}
if ( target_input.type == "checkbox" || target_input.type == "radio" ) {
if ( target_input.type == "radio" ) {
for ( auto& in : page_data.inputs ) {
if ( in.name == target_input.name && in.action == target_input.action ) {
in.checked = false;
}
}
target_input.checked = true;
} else {
target_input.checked = !target_input.checked;
}
update_toggle_ui( page_data );
return InputAction::None;
}
const std::string query = get_terminal_input( terminal, "Enter Text [I" + std::to_string( index ) + "]:" );
if ( query.empty() ) {
return InputAction::None;
}
update_input_value( page_data, index, query );
return InputAction::None;
}
} // namespace ricochet::core