#[must_use]
pub fn build(src: &str) -> String {
    parse(src).join("\n")
}

#[must_use]
#[expect(clippy::too_many_lines)]
pub fn parse(src: &str) -> Vec<String> {
    let stack = "0x10000";
    let acc = "acc";
    let rga = "rga";

    let bpr = "bpr";
    let spr = "spr";
    let mut instrs = Vec::<String>::new();

    // Define symbols
    let print_start = "print";

    let tokens = lex(src);

    let mut idstack = Vec::<u32>::new();

    // set up a stack
    instrs.push(format!("\tlwi {}, {}", stack, bpr));
    instrs.push(format!("\tmov {}, {}", bpr, spr));
    // set up the data pointer
    instrs.push(format!("{}: \t lwi 0x30000, {}", "main", rga));

    for (id, tok) in tokens.iter().enumerate() {
        match tok {
            BfToken::Inc => {
                instrs.push(format!("\tinc {}", acc));
            }
            BfToken::Dec => {
                instrs.push(format!("\tdec {}", acc));
            }
            BfToken::IncPtr => {
                instrs.push(format!("\tstw {}, {}, 0", acc, rga));
                instrs.push(format!("\taddi {}, 4, {}", rga, rga));
                instrs.push(format!("\tlwd {}, {}, 0", rga, acc));
            }
            BfToken::DecPtr => {
                instrs.push(format!("\tstw {}, {}, 0", acc, rga));
                instrs.push(format!("\tsubi {}, 4, {}", rga, rga));
                instrs.push(format!("\tlwd {}, {}, 0", rga, acc));
            }
            BfToken::Out => {
                instrs.push(format!("\tpush {}", acc));
                instrs.push(format!("\tcall {}", print_start));
                instrs.push(format!("\tpop zero"));
            }
            BfToken::In => {
                instrs.push(format!("\tlwd 0x40000, {}, 0", acc));
            }
            BfToken::Forward => {
                let loop_start = format!("loop_start_{}", id);
                let loop_end = format!("loop_end_{}", id);
                idstack.push(id as u32);
                instrs.push(format!("\tcmp {}, zero", acc));
                instrs.push(format!("\tjeq {}, zero", loop_end));
                instrs.push(format!("{}: \tnop", loop_start));
            }
            BfToken::Back => {
                if let Some(start_id) = idstack.pop() {
                    let loop_start = format!("loop_start_{}", start_id);
                    let loop_end = format!("loop_end_{}", start_id);
                    instrs.push(format!("\tcmp {}, zero", acc));
                    instrs.push(format!("\tjne {}, zero", loop_start));
                    instrs.push(format!("{}: \tnop", loop_end));
                } else {
                    eprintln!("Warning: Unmatched ']' at position {}", id);
                }
            }
        }
    }

    instrs.push("\thlt".to_string());

    insert_lib(&mut instrs);

    instrs
}

fn insert_lib(instrs: &mut Vec<String>) {
    let bpr = "bpr";
    let spr = "spr";
    let rg0 = "rg0";
    let rg1 = "rg1";

    let print_start = "print";
    let current = "current";
    instrs.push(format!("\tdw {}, 0x20000", current));
    instrs.push(format!("{}: \tpush {}", print_start, bpr));
    instrs.push(format!("\tmov {}, {}", spr, bpr));
    instrs.push(format!("\tlwd {}, {}, 8", bpr, rg0));
    instrs.push(format!("\tlwd {}, {}, 0", current, rg1));
    instrs.push(format!("\tstb {}, {}, 0", rg0, rg1));
    instrs.push(format!("\taddi {}, 1, {}", rg1, rg1));
    instrs.push(format!("\tstw {}, {}, 0", rg1, current));
    instrs.push(format!("\tmov {}, {}", bpr, spr));
    instrs.push(format!("\tpop {}", bpr));
    instrs.push("\treturn".to_string());
}

enum BfToken {
    Inc,
    Dec,
    IncPtr,
    DecPtr,
    Out,
    In,
    Forward,
    Back,
}

fn lex(src: &str) -> Vec<BfToken> {
    src.chars()
        .filter_map(|c| match c {
            '+' => Some(BfToken::Inc),
            '-' => Some(BfToken::Dec),
            '>' => Some(BfToken::IncPtr),
            '<' => Some(BfToken::DecPtr),
            '.' => Some(BfToken::Out),
            ',' => Some(BfToken::In),
            '[' => Some(BfToken::Forward),
            ']' => Some(BfToken::Back),
            _ => None,
        })
        .collect()
}

fn _create_symbol(id: u32) -> String {
    format!("label_{}", id)
}
