//! KylinBot Nucleo-F401RE firmware — JSON-over-serial peripheral.
//!
//! Listens for newline-delimited JSON on USART2 (PA2=TX, PA3=RX).
//! USART2 is connected to ST-Link VCP — host sees /dev/ttyACM0 (Linux) or /dev/cu.usbmodem* (macOS).
//!
//! Protocol: same as Arduino/ESP32 — see docs/hardware-peripherals-design.md

#![no_std]
#![no_main]

use core::str;
use defmt::info;
use embassy_executor::Spawner;
use embassy_stm32::gpio::{Level, Output, Speed};
use embassy_stm32::usart::{Config, Uart};
use heapless::String;
use kylinbot_fw_protocol::{copy_id, write_err, write_ok, Command};
use {defmt_rtt as _, panic_probe as _};

/// Arduino-style pin 13 = PA5 (User LED LD2 on Nucleo-F401RE)
const LED_PIN: i32 = 13;

#[embassy_executor::main]
async fn main(_spawner: Spawner) {
    let p = embassy_stm32::init(Default::default());

    let mut config = Config::default();
    config.baudrate = 115_200;

    let mut usart = Uart::new_blocking(p.USART2, p.PA3, p.PA2, config).unwrap();
    let mut led = Output::new(p.PA5, Level::Low, Speed::Low);

    info!("KylinBot Nucleo firmware ready on USART2 (115200)");

    let mut line_buf: heapless::Vec<u8, 256> = heapless::Vec::new();
    let mut id_buf = [0u8; 16];
    let mut resp_buf: String<128> = String::new();

    loop {
        let mut byte = [0u8; 1];
        if usart.blocking_read(&mut byte).is_ok() {
            let b = byte[0];
            if b == b'\n' || b == b'\r' {
                if !line_buf.is_empty() {
                    let id_len = copy_id(&line_buf, &mut id_buf);
                    let id_str = str::from_utf8(&id_buf[..id_len]).unwrap_or("0");

                    match Command::from_line(&line_buf) {
                        Some(Command::Ping) => {
                            write_ok(&mut resp_buf, id_str, "pong");
                        }
                        Some(Command::Capabilities) => {
                            resp_buf.clear();
                            let _ = core::fmt::Write::write_str(
                                &mut resp_buf,
                                concat!(
                                    r#"{"id":""#,
                                ),
                            );
                            let _ = core::fmt::Write::write_str(&mut resp_buf, id_str);
                            let _ = core::fmt::Write::write_str(
                                &mut resp_buf,
                                r#"","ok":true,"result":"{\"gpio\":[0,1,2,3,4,5,6,7,8,9,10,11,12,13],\"led_pin\":13}"}"#,
                            );
                        }
                        Some(Command::GpioRead { pin }) => {
                            if pin >= 0 && pin <= 13 {
                                write_ok(&mut resp_buf, id_str, "0");
                            } else {
                                write_err(&mut resp_buf, id_str, "Invalid pin");
                            }
                        }
                        Some(Command::GpioWrite { pin, value }) => {
                            if pin == LED_PIN {
                                led.set_level(if value != 0 { Level::High } else { Level::Low });
                                write_ok(&mut resp_buf, id_str, "done");
                            } else if pin >= 0 && pin <= 13 {
                                write_ok(&mut resp_buf, id_str, "done");
                            } else {
                                write_err(&mut resp_buf, id_str, "Invalid pin");
                            }
                        }
                        None => {
                            write_err(&mut resp_buf, id_str, "Unknown command");
                        }
                    }

                    let _ = usart.blocking_write(resp_buf.as_bytes());
                    let _ = usart.blocking_write(b"\n");
                    line_buf.clear();
                }
            } else if line_buf.push(b).is_err() {
                line_buf.clear();
            }
        }
    }
}
