#pragma once

#include <cstddef>
#include <cstdint>
#include <string>
#include <unordered_set>

enum class tb_gdb_run_result {
	ok = 0,
	error,
	exited,
	timed_out
};

struct tb_gdb_target {
	virtual ~tb_gdb_target() = default;

	// Register numbering follows the provided target.xml:
	// x0..x31 = 0..31, pc = 32.
	virtual uint32_t read_reg(uint32_t regno) = 0;
	virtual void write_reg(uint32_t regno, uint32_t value) = 0;

	virtual bool read_mem(uint32_t addr, uint8_t *dst, size_t len) = 0;
	virtual bool write_mem(uint32_t addr, const uint8_t *src, size_t len) = 0;

	// Advance execution until one instruction retires.
	virtual tb_gdb_run_result step_instruction() = 0;

	virtual uint32_t exit_code() const = 0;

	// Optional: handle GDB "monitor" commands (qRcmd). Return true if handled.
	// If handled, out_console is printed on the GDB console (can be empty).
	virtual bool monitor_cmd(const std::string &cmd, std::string &out_console) {
		(void)cmd;
		out_console.clear();
		return false;
	}
};

class tb_gdb_server {
public:
	tb_gdb_server(uint16_t port, tb_gdb_target &target);
	~tb_gdb_server();

	// Blocks until the session ends (disconnect/kill) or the target exits.
	tb_gdb_run_result serve();

private:
	uint16_t port_;
	tb_gdb_target &target_;

	int server_fd_ = -1;
	int client_fd_ = -1;
	bool no_ack_mode_ = false;

	std::string target_xml_;
	std::string memory_map_xml_;

	// Execute breakpoints (RSP Z0/Z1): stop before executing instruction at PC.
	// When resuming from a breakpoint, ignore a match at the current PC once.
	std::unordered_set<uint32_t> breakpoints_;
	uint32_t ignore_breakpoint_pc_ = 0xffffffffu;

	// Socket helpers
	bool open_listen_socket_();
	bool accept_client_();
	void close_client_();

	bool recv_packet_(std::string &out_payload, bool &got_interrupt);
	bool send_packet_(const std::string &payload);
	bool maybe_send_ack_(bool ok);
	bool check_interrupt_();

	// RSP helpers
	static uint8_t checksum_(const std::string &s);
	static int hex_val_(char c);
	static std::string to_hex_bytes_(const uint8_t *data, size_t len);
	static bool from_hex_bytes_(const std::string &hex, std::string &out_bytes);
	static void append_u32_le_hex_(std::string &out, uint32_t v);
	static bool parse_u32_hex_(const std::string &s, uint32_t &out);

	std::string handle_command_(const std::string &cmd, bool &run_command_consumed);
	std::string stop_reply_(int signo) const;
	std::string stop_reply_exit_() const;
};
