use regenerator2000::disassembler::formatter_acme::AcmeFormatter;
use regenerator2000::disassembler::{Disassembler, DisassemblyLine};
use regenerator2000::state::{Assembler, BlockType, DocumentSettings};
use std::collections::{BTreeMap, BTreeSet};

#[test]
fn test_tass_formatting_force_w() {
    let settings = DocumentSettings {
        assembler: Assembler::Tass64,
        preserve_long_bytes: true,
        ..Default::default()
    };

    let disassembler = Disassembler::new();
    let labels = BTreeMap::new();
    let origin = 0x1000;

    // LDA $0012 (Absolute) -> should be LDA @w $0012
    let code = vec![0xAD, 0x12, 0x00]; // AD = LDA Abs
    let block_types = vec![BlockType::Code, BlockType::Code, BlockType::Code];

    let lines = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    assert_eq!(lines.len(), 1);
    let line = &lines[0];
    assert_eq!(line.mnemonic, "lda");
    assert_eq!(line.operand, "@w $0012");
}

#[test]
fn test_tass_formatting_no_force_if_disabled() {
    let settings = DocumentSettings {
        assembler: Assembler::Tass64,
        preserve_long_bytes: false,
        ..Default::default()
    };

    let disassembler = Disassembler::new();
    let labels = BTreeMap::new();
    let origin = 0x1000;

    let code = vec![0xAD, 0x12, 0x00];
    let block_types = vec![BlockType::Code, BlockType::Code, BlockType::Code];

    let lines = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    assert_eq!(lines.len(), 1);
    assert_eq!(lines[0].operand, "$0012");
}

#[test]
fn test_acme_formatting_basic() {
    let settings = DocumentSettings {
        assembler: Assembler::Acme,
        ..Default::default()
    };

    let disassembler = Disassembler::new();
    let labels = BTreeMap::new();
    let origin = 0x1000;

    let code = vec![0xAD, 0x12, 0x34]; // LDA $3412
    let block_types = vec![BlockType::Code, BlockType::Code, BlockType::Code];

    let lines = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    assert_eq!(lines.len(), 1);
    assert_eq!(lines[0].mnemonic, "lda");
    assert_eq!(lines[0].operand, "$3412");
}

#[test]
fn test_text_char_limit_configurable() {
    let settings = DocumentSettings {
        text_char_limit: 10,
        assembler: Assembler::Acme, // Use Acme for simpler output (!text)
        ..Default::default()
    };

    // "Hello World This Is Long" is 24 chars
    let data = b"Hello World This Is Long".to_vec();
    let block_types = vec![BlockType::PetsciiText; data.len()];

    let disassembler = Disassembler::new();
    let labels = BTreeMap::new();
    let origin = 0x1000;

    let lines = disassembler.disassemble(
        &data,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    // With limit 10, it should split:
    // "Hello Worl" (10 chars)
    // "d This Is " (10 chars)
    // "Long" (4 chars)

    assert_eq!(lines.len(), 3);
    assert_eq!(lines[0].operand, "\"Hello Worl\"");
    assert_eq!(lines[1].operand, "\"d This Is \"");
    assert_eq!(lines[2].operand, "\"Long\"");
}

#[test]
fn test_screencode_limit_configurable() {
    let settings = DocumentSettings {
        text_char_limit: 10,
        assembler: Assembler::Tass64,
        ..Default::default()
    };

    // "ABC...J" (10 chars) + "KLM...T" (10 chars)
    // 0x01..0x14 (A..T)
    let mut data = Vec::new();
    for i in 1..=20 {
        data.push(i as u8);
    }
    let block_types = vec![BlockType::ScreencodeText; data.len()];

    let disassembler = Disassembler::new();
    let labels = BTreeMap::new();
    let origin = 0x1000;

    let lines = disassembler.disassemble(
        &data,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    // Tass wrapping: .encode, .enc "...", .text (10), .text (10), .endencode
    // Lines:
    // 0: .encode
    // 1: .enc "screen"
    // 2: .text "ABCDEFGHIJ"
    // 3: .text "KLMNOPQRST"
    // 4: .endencode

    // Filter for .text lines
    let text_lines: Vec<&DisassemblyLine> =
        lines.iter().filter(|l| l.mnemonic == ".text").collect();

    assert_eq!(text_lines.len(), 2);
    // Tass formatter typically outputs quoted strings
    assert_eq!(text_lines[0].operand, "\"ABCDEFGHIJ\"");
    assert_eq!(text_lines[1].operand, "\"KLMNOPQRST\"");
}

#[test]
fn test_acme_directives() {
    let settings = DocumentSettings {
        assembler: Assembler::Acme,
        ..Default::default()
    };

    let disassembler = Disassembler::new();
    let labels = BTreeMap::new();
    let origin = 0x1000;

    // .BYTE equivalent
    let code = vec![0xFF];
    let block_types = vec![BlockType::DataByte];

    let lines = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    assert_eq!(lines.len(), 1);
    assert_eq!(lines[0].mnemonic, "!byte");
    assert_eq!(lines[0].operand, "$ff");
}

#[test]
fn test_acme_lowercase_output() {
    let settings = DocumentSettings {
        assembler: Assembler::Acme,
        ..Default::default()
    };

    let disassembler = Disassembler::new();
    let mut labels = BTreeMap::new();
    let origin = 0x1000;

    // Add a label with MixedCase name
    labels.insert(
        0x1005,
        vec![regenerator2000::state::Label {
            name: "MixedCaseLabel".to_string(),
            kind: regenerator2000::state::LabelKind::User,
            label_type: regenerator2000::state::LabelType::AbsoluteAddress,
        }],
    );

    // Code:
    // LDA #$FF  -> lda #$ff
    // JMP $1005 -> jmp mixedcaselabel
    let code = vec![
        0xA9, 0xFF, // LDA #$FF
        0x4C, 0x05, 0x10, // JMP $1005
    ];
    let block_types = vec![
        BlockType::Code,
        BlockType::Code,
        BlockType::Code,
        BlockType::Code,
        BlockType::Code,
    ];

    let lines = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    assert_eq!(lines.len(), 2);

    // Line 1: lda #$ff
    assert_eq!(lines[0].mnemonic, "lda");
    assert_eq!(lines[0].operand, "#$ff");

    // Line 2: jmp MixedCaseLabel
    assert_eq!(lines[1].mnemonic, "jmp");
    assert_eq!(lines[1].operand, "MixedCaseLabel");
}

#[test]
fn test_acme_plus2_formatting() {
    let settings = DocumentSettings {
        assembler: Assembler::Acme,
        preserve_long_bytes: true,
        ..Default::default()
    };

    let disassembler = Disassembler::new();
    let labels = BTreeMap::new();
    let origin = 0x1000;

    // LDA $0012 (Absolute) -> should be lda+2 $0012
    let code = vec![0xAD, 0x12, 0x00]; // AD = LDA Abs
    let block_types = vec![BlockType::Code, BlockType::Code, BlockType::Code];

    let lines = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    assert_eq!(lines.len(), 1);
    let line = &lines[0];
    assert_eq!(line.mnemonic, "lda+2");
    // ACME formatter uses 4 digits for absolute addresses
    assert_eq!(line.operand, "$0012");
}

#[test]
fn test_xref_formatting_with_dollar() {
    let settings = DocumentSettings {
        assembler: Assembler::Tass64,
        ..Default::default()
    };

    let disassembler = Disassembler::new();
    let mut labels = BTreeMap::new();
    let origin = 0x1000;

    // Create a label with references
    labels.insert(
        0x1000,
        vec![regenerator2000::state::Label {
            name: "TestLabel".to_string(),
            kind: regenerator2000::state::LabelKind::User,
            label_type: regenerator2000::state::LabelType::AbsoluteAddress,
        }],
    );

    // Code: NOP
    let code = vec![0xEA];
    let block_types = vec![BlockType::Code];

    let mut cross_refs = BTreeMap::new();
    cross_refs.insert(0x1000, vec![0x2000, 0x3000]);

    let lines = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &cross_refs,
        &BTreeMap::new(),
        &[],
        &std::collections::BTreeSet::new(),
    );

    assert_eq!(lines.len(), 1);
    // Check that the comment contains "x-ref: $2000, $3000"
    // Note: refs are sorted and deduped.
    assert!(lines[0].comment.contains("x-ref: $2000, $3000"));
}

#[test]
fn test_xref_count_configurable() {
    let mut settings = DocumentSettings {
        assembler: Assembler::Tass64,
        ..Default::default()
    };

    let disassembler = Disassembler::new();
    let mut labels = BTreeMap::new();
    let origin = 0x1000;

    // Create a label with many references
    labels.insert(
        0x1000,
        vec![regenerator2000::state::Label {
            name: "ManyRefs".to_string(),
            kind: regenerator2000::state::LabelKind::User,
            label_type: regenerator2000::state::LabelType::AbsoluteAddress,
        }],
    );

    let code = vec![0xEA];
    let block_types = vec![BlockType::Code];

    let mut cross_refs = BTreeMap::new();
    cross_refs.insert(0x1000, vec![0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005]);

    // Case 1: Default (5)
    settings.max_xref_count = 5;
    let lines = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &cross_refs,
        &BTreeMap::new(),
        &[],
        &std::collections::BTreeSet::new(),
    );
    assert_eq!(lines.len(), 1);
    // Should show 5 items
    let comment = &lines[0].comment;
    assert!(comment.contains("$2004"));
    assert!(!comment.contains("$2005"));

    // Case 2: Limit to 2
    settings.max_xref_count = 2;
    let lines = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &cross_refs,
        &BTreeMap::new(),
        &[],
        &std::collections::BTreeSet::new(),
    );
    let comment = &lines[0].comment;
    assert!(comment.contains("$2000, $2001"));
    assert!(!comment.contains("$2002"));

    // Case 3: Zero (Off)
    settings.max_xref_count = 0;
    let lines = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &cross_refs,
        &BTreeMap::new(),
        &[],
        &std::collections::BTreeSet::new(),
    );
    assert!(lines[0].comment.is_empty());
}

#[test]
fn test_text_and_screencode_disassembly() {
    // 1. Test Tass Text
    let mut settings = DocumentSettings {
        assembler: Assembler::Tass64,
        ..Default::default()
    };
    let disassembler = Disassembler::new();
    let labels = BTreeMap::new();
    let origin = 0x1000;

    // "ABC"
    let code = vec![0x41, 0x42, 0x43];
    let block_types = vec![
        BlockType::PetsciiText,
        BlockType::PetsciiText,
        BlockType::PetsciiText,
    ];
    let lines = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    // Tass formatting produces 4 lines: .encode, .enc "ascii", .text "ABC", .endencode
    assert_eq!(lines.len(), 4);
    assert_eq!(lines[0].mnemonic, ".encode");
    assert_eq!(lines[2].mnemonic, ".text");
    assert_eq!(lines[2].operand, "\"ABC\"");

    // 2. Test Acme Text
    settings.assembler = Assembler::Acme;
    let lines = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );
    assert_eq!(lines.len(), 1);
    assert_eq!(lines[0].mnemonic, "!text");
    assert_eq!(lines[0].operand, "\"ABC\"");

    // 3. Test Screencode (using "ABC" screen codes 1, 2, 3)
    let code_scr = vec![0x01, 0x02, 0x03]; // A, B, C in Screen Code (0x01=A, 0x02=B, 0x03=C)
    let block_types_scr = vec![
        BlockType::ScreencodeText,
        BlockType::ScreencodeText,
        BlockType::ScreencodeText,
    ];

    // Acme Screencode
    settings.assembler = Assembler::Acme;
    let lines = disassembler.disassemble(
        &code_scr,
        &block_types_scr,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );
    assert_eq!(lines.len(), 1);
    assert_eq!(lines[0].mnemonic, "!scr");
    // ACME !scr is inverted: "a" -> 0x01 ("A")
    assert_eq!(lines[0].operand, "\"abc\"");

    // 4. Test fallback for invalid text
    let code_bad = vec![0xFF];
    let block_types_bad = vec![BlockType::PetsciiText];
    let lines = disassembler.disassemble(
        &code_bad,
        &block_types_bad,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );
    // For Tass, this will produce 4 lines: ENCODE, ENC, BYTE, ENDENCODE
    // But we need to use Acme setting from previous step?
    // Wait, let's reset to Tass if we want to confirm fallback logic for Tass, or Acme for Acme.
    // The previous test logic assumed 1 line.
    // If settings is still Acme:
    // Acme text fallback -> !byte
    // Acme Formatter default fallback is check handle_text implementation.
    // handle_text calls format_text if valid, else handle_partial_data.
    // 0xFF (255) is not in 0x20..0x7E range. So it goes to handle_partial_data -> 1 line.
    assert_eq!(lines.len(), 1);
    assert_eq!(lines[0].mnemonic, "!text");
    assert_eq!(lines[0].operand, "$ff");
}

#[test]
fn test_text_mixed_content() {
    let settings = DocumentSettings {
        assembler: Assembler::Tass64,
        ..Default::default()
    };

    let disassembler = Disassembler::new();
    let labels = BTreeMap::new();
    let origin = 0x1000;

    // $00, $01, "A", "B", $00
    let code = vec![0x00, 0x01, 0x41, 0x42, 0x00];
    let block_types = vec![
        BlockType::PetsciiText,
        BlockType::PetsciiText,
        BlockType::PetsciiText,
        BlockType::PetsciiText,
        BlockType::PetsciiText,
    ];

    let lines = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    // Filter relevant lines (Tass wraps in ENCODE)
    // We expect .encode, .enc, .text ..., .endencode
    // The .text line should be merged: .text $00, $01, "AB", $00

    let text_lines: Vec<&DisassemblyLine> =
        lines.iter().filter(|l| l.mnemonic == ".text").collect();

    assert_eq!(text_lines.len(), 1);
    assert_eq!(text_lines[0].operand, "$00, $01, \"AB\", $00");
}

#[test]
fn test_text_escaping() {
    let mut settings = DocumentSettings::default();
    let disassembler = Disassembler::new();
    let labels = BTreeMap::new();
    let origin = 0x1000;

    // String: Quote " Backslash \
    // ASCII: 51 75 6f 74 65 20 22 20 42 61 63 6b 73 6c 61 73 68 20 5c
    let code = vec![
        0x51, 0x75, 0x6F, 0x74, 0x65, 0x20, 0x22, 0x20, 0x42, 0x61, 0x63, 0x6B, 0x73, 0x6C, 0x61,
        0x73, 0x68, 0x20, 0x5C,
    ];
    let block_types = vec![BlockType::PetsciiText; code.len()];

    // 1. Test ACME: "Quote \" Backslash \\"
    settings.assembler = Assembler::Acme;
    let lines_acme = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );
    assert_eq!(lines_acme.len(), 1);
    assert_eq!(lines_acme[0].operand, "\"Quote \\\" Backslash \\\\\"");

    // 2. Test Tass64: "Quote "" Backslash \"
    settings.assembler = Assembler::Tass64;
    let lines_tass = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    // Tass output structure: .encode, .enc, .text ..., .endencode
    // Filter for .text
    let text_lines: Vec<&DisassemblyLine> = lines_tass
        .iter()
        .filter(|l| l.mnemonic == ".text")
        .collect();

    assert_eq!(text_lines.len(), 1);
    // Tass escapes " as "" and leaves \ alone
    assert_eq!(text_lines[0].operand, "\"Quote \"\" Backslash \\\"");
}

#[test]
fn test_screencode_mixed() {
    let mut settings = DocumentSettings::default();
    let disassembler = Disassembler::new();
    let labels = BTreeMap::new();
    let origin = 0x1000;

    // Screencodes:
    // 0x01 ('A'), 0x00 ('@'), 0x80 (Invalid/Reverse), 0x01 ('A')
    // Expected: "A@", $80, "A"

    // Tass SC map: 0->@, 1->A
    // 0x00 -> '@' (ASCII 64)
    // 0x01 -> 'A' (ASCII 65)

    // Escaping check:
    // Quote (0x22): mapped?
    // 0x22 (34) -> 34 (ASCII 34 = ")
    // So 0x22 is a quote in screencode too?
    // Let's check handle_screencode map:
    // b < 32 -> b+64
    // b < 64 -> b
    // 34 is in 32..64 range -> 34. Correct.

    // So let's test: 0x22 ("), 0xFF (invalid), 0x22 (")
    let code = vec![0x22, 0xFF, 0x22];
    let block_types = vec![BlockType::ScreencodeText; code.len()];

    // 1. ACME
    settings.assembler = Assembler::Acme;
    let lines_acme = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );
    assert_eq!(lines_acme.len(), 1);
    // !scr "\"" (escaped quote), $ff, "\""
    // Expected: !scr "\"", $ff, "\""
    assert_eq!(lines_acme[0].operand, "\"\\\"\", $ff, \"\\\"\"");

    // 2. Tass
    settings.assembler = Assembler::Tass64;
    let lines_tass = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );
    // .text """""", $ff, """"""
    let text_lines: Vec<&DisassemblyLine> = lines_tass
        .iter()
        .filter(|l| l.mnemonic == ".text")
        .collect();

    assert_eq!(text_lines.len(), 1);
    // Tass escapes " as ""
    // "" (escaped quote), $ff, ""
    // Expected string in operand: """" (quote), $ff, """" (quote)
    // Wait. " -> ""
    // So one quote is "".
    // Quoted string: """"""
    assert_eq!(text_lines[0].operand, "\"\"\"\", $ff, \"\"\"\"");
}

#[test]
fn test_tass_screencode_enc_wrapping() {
    let settings = DocumentSettings {
        assembler: Assembler::Tass64,
        ..Default::default()
    };

    let disassembler = Disassembler::new();
    let labels = BTreeMap::new();
    let origin = 0x1000;

    // "ABC" in screencode (0x01, 0x02, 0x03)
    let code = vec![0x01, 0x02, 0x03];
    let block_types = vec![
        BlockType::ScreencodeText,
        BlockType::ScreencodeText,
        BlockType::ScreencodeText,
    ];

    let lines = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    assert_eq!(lines.len(), 4);

    // 1. Start Block
    assert_eq!(lines[0].mnemonic, ".encode");
    assert_eq!(lines[1].mnemonic, ".enc");
    assert_eq!(lines[1].operand, "\"screen\"");

    // 2. Content
    assert_eq!(lines[2].mnemonic, ".text");
    assert!(lines[2].operand.contains("\"ABC\""));

    // 3. End Block
    assert_eq!(lines[3].mnemonic, ".endencode");
}

#[test]
fn test_tass_screencode_multiline_wrapping() {
    let settings = DocumentSettings {
        assembler: Assembler::Tass64,
        text_char_limit: 32,
        ..Default::default()
    };

    let disassembler = Disassembler::new();
    let labels = BTreeMap::new();
    let origin = 0x1000;

    // 40 bytes of screencode (exceeds 32 byte limit per line)
    // 0x01 * 40
    let code = vec![0x01; 40];
    let block_types = vec![BlockType::ScreencodeText; 40];

    let lines = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    // Expected:
    // 1. .ENCODE
    // 2. .ENC "SCREEN"
    // 3. .TEXT "..." (32 bytes)
    // 4. .TEXT "..." (8 bytes)
    // 5. .ENDENCODE

    assert_eq!(lines.len(), 5);

    // Line 1-2: Header
    assert_eq!(lines[0].mnemonic, ".encode");
    assert_eq!(lines[1].mnemonic, ".enc");
    assert_eq!(lines[1].operand, "\"screen\"");

    // Line 3: First chunk
    assert_eq!(lines[2].mnemonic, ".text");
    // Verify bytes presence?
    assert_eq!(lines[2].bytes.len(), 32);

    // Line 4: Second chunk
    assert_eq!(lines[3].mnemonic, ".text");
    assert_eq!(lines[3].bytes.len(), 8);

    // Line 5: Footer
    assert_eq!(lines[4].mnemonic, ".endencode");
}

#[test]
fn test_text_show_bytes_is_false() {
    let settings = DocumentSettings {
        assembler: Assembler::Tass64,
        ..Default::default()
    };

    let disassembler = Disassembler::new();
    let labels = BTreeMap::new();
    let origin = 0x1000;

    let code = vec![0x41, 0x42, 0x43]; // "ABC"
    let block_types = vec![
        BlockType::PetsciiText,
        BlockType::PetsciiText,
        BlockType::PetsciiText,
    ];

    let lines = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    // Filter for the .text line
    let text_line = lines.iter().find(|l| l.mnemonic == ".text").unwrap();
    assert!(!text_line.show_bytes, "Text blocks should not show bytes");
}

#[test]
fn test_screencode_show_bytes_is_false() {
    let settings = DocumentSettings {
        assembler: Assembler::Tass64,
        ..Default::default()
    };

    let disassembler = Disassembler::new();
    let labels = BTreeMap::new();
    let origin = 0x1000;

    let code = vec![0x01, 0x02, 0x03]; // "ABC" in screencode
    let block_types = vec![
        BlockType::ScreencodeText,
        BlockType::ScreencodeText,
        BlockType::ScreencodeText,
    ];

    let lines = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    // Filter for the .text line (inside .encode block)
    let text_line = lines.iter().find(|l| l.mnemonic == ".text").unwrap();
    assert!(
        !text_line.show_bytes,
        "Screencode blocks should not show bytes"
    );
}

#[test]
fn test_databyte_show_bytes_is_false() {
    let settings = DocumentSettings {
        assembler: Assembler::Tass64,
        ..Default::default()
    };

    let disassembler = Disassembler::new();
    let labels = BTreeMap::new();
    let origin = 0x1000;

    let code = vec![0x10, 0x20];
    let block_types = vec![BlockType::DataByte, BlockType::DataByte];

    let lines = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    assert_eq!(lines.len(), 1);
    assert!(
        !lines[0].show_bytes,
        "DataByte blocks should not show bytes"
    );
}

#[test]
fn test_dataword_show_bytes_is_false() {
    let settings = DocumentSettings {
        assembler: Assembler::Tass64,
        ..Default::default()
    };

    let disassembler = Disassembler::new();
    let labels = BTreeMap::new();
    let origin = 0x1000;

    let code = vec![0x10, 0x20]; // $2010
    let block_types = vec![BlockType::DataWord, BlockType::DataWord];

    let lines = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    assert_eq!(lines.len(), 1);
    assert!(
        !lines[0].show_bytes,
        "DataWord blocks should not show bytes"
    );
}

#[test]
fn test_address_show_bytes_is_false() {
    let settings = DocumentSettings {
        assembler: Assembler::Tass64,
        ..Default::default()
    };

    let disassembler = Disassembler::new();
    let labels = BTreeMap::new();
    let origin = 0x1000;

    let code = vec![0x10, 0x20]; // $2010
    let block_types = vec![BlockType::Address, BlockType::Address];

    let lines = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    assert_eq!(lines.len(), 1);
    assert!(!lines[0].show_bytes, "Address blocks should not show bytes");
}

#[test]
fn test_tass_block_separation() {
    let settings = DocumentSettings {
        assembler: Assembler::Tass64,
        ..Default::default()
    };
    let disassembler = Disassembler::new();
    let labels = BTreeMap::new();
    let origin = 0x1000;

    // SC (1 byte), Code (1 byte), SC (1 byte)
    let code = vec![0x01, 0xEA, 0x02];
    let block_types = vec![
        BlockType::ScreencodeText,
        BlockType::Code,
        BlockType::ScreencodeText,
    ];

    let lines = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    // Block 1 (SC) -> 4 lines (Start, Enc, Text, End)
    // Code -> 1 line
    // Block 2 (SC) -> 4 lines (Start, Enc, Text, End)
    // Total 9 lines
    assert_eq!(lines.len(), 9);

    assert_eq!(lines[0].mnemonic, ".encode");
    assert_eq!(lines[3].mnemonic, ".endencode");

    // Code
    assert_eq!(lines[4].mnemonic, "nop");

    // Block 2
    assert_eq!(lines[5].mnemonic, ".encode");
    assert_eq!(lines[8].mnemonic, ".endencode");
}

#[test]
fn test_tass_label_interruption() {
    use regenerator2000::state::{Label, LabelKind, LabelType};

    let settings = DocumentSettings {
        assembler: Assembler::Tass64,
        ..Default::default()
    };
    let disassembler = Disassembler::new();
    let mut labels = BTreeMap::new();

    // Label at index 1 (0x1001)
    labels.insert(
        0x1001,
        vec![Label {
            name: "MID".to_string(),
            kind: LabelKind::Auto,
            label_type: LabelType::Field,
        }],
    );

    let origin = 0x1000;

    // SC (2 bytes)
    let code = vec![0x01, 0x02];
    let block_types = vec![BlockType::ScreencodeText, BlockType::ScreencodeText];

    let lines = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    // Expectation:
    // Label breaks the chunk processing loop, but types are contiguous.
    // Chunk 1: byte 0x01. is_start=True. Next byte is SC, but Label present -> is_end=False?
    // Wait, handle_screencode logic:
    // Loop breaks at count=1 because label at next addr.
    // is_end check: next_pc=1. address_types[1] IS Screencode. So is_end=False.
    // Output: .ENCODE, .ENC, .TEXT (No END).

    // Chunk 2: byte 0x02. Label attached here.
    // is_start check: prev (0x00) was Screencode. is_start=False.
    // is_end check: next (EOF) or non-SC. is_end=True.
    // Output: .TEXT, .ENDENCODE.

    // Total lines:
    // Chunk 1: 3 lines (.ENCODE, .ENC, .TEXT)
    // Chunk 2: 2 lines (.TEXT, .ENDENCODE)
    // Total 5 lines.

    assert_eq!(lines.len(), 5);

    assert_eq!(lines[0].mnemonic, ".encode");
    assert_eq!(lines[2].mnemonic, ".text");
    assert_eq!(lines[2].operand, "\"A\"");

    // Label should be on the first line of the second chunk
    assert_eq!(lines[3].label, Some("MID".to_string()));
    assert_eq!(lines[3].mnemonic, ".text");
    assert_eq!(lines[3].operand, "\"B\"");

    assert_eq!(lines[4].mnemonic, ".endencode");
}

#[test]
fn test_tass_screencode_single_byte_special() {
    let settings = DocumentSettings {
        assembler: Assembler::Tass64,
        ..Default::default()
    };

    let disassembler = Disassembler::new();
    let labels = BTreeMap::new();
    let origin = 0x1000;

    // Single byte $4F
    let code = vec![0x4F];
    let block_types = vec![BlockType::ScreencodeText];

    let lines = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    // Expected:
    // .ENCODE
    // .ENC SCREEN
    // .BYTE $4F
    // .ENDENCODE

    assert_eq!(lines.len(), 4);
    assert_eq!(lines[0].mnemonic, ".encode");
    assert_eq!(lines[1].mnemonic, ".enc");
    assert_eq!(lines[1].operand, "\"screen\"");
    assert_eq!(lines[2].mnemonic, ".text");
    assert_eq!(lines[2].operand, "\"o\"");
    assert_eq!(lines[3].mnemonic, ".endencode");
}

#[test]
fn test_tass_screencode_case_mapping() {
    let settings = DocumentSettings {
        assembler: Assembler::Tass64,
        ..Default::default()
    };

    let disassembler = Disassembler::new();
    let labels = BTreeMap::new();
    let origin = 0x1000;

    // Case A: 30 2d 39 2c 20 08 0f 0c 01 20 03 0f 0d 0f (0-9, HOLA COMO)
    let bytes_a = vec![
        0x30, 0x2d, 0x39, 0x2c, 0x20, 0x08, 0x0F, 0x0C, 0x01, 0x20, 0x03, 0x0F, 0x0D, 0x0F,
    ];
    let block_types_a = vec![BlockType::ScreencodeText; bytes_a.len()];

    let lines_a = disassembler.disassemble(
        &bytes_a,
        &block_types_a,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    assert_eq!(lines_a.len(), 4);
    assert_eq!(lines_a[0].mnemonic, ".encode");
    assert_eq!(lines_a[1].operand, "\"screen\"");
    assert_eq!(lines_a[2].mnemonic, ".text");
    assert_eq!(lines_a[2].operand, "\"0-9, HOLA COMO\"");
    assert_eq!(lines_a[3].mnemonic, ".endencode");

    // Case B: 30 2d 39 2c 20 48 4f 4c 41 20 43 4f 4d 4f (0-9, hola como)
    let bytes_b = vec![
        0x30, 0x2d, 0x39, 0x2c, 0x20, 0x48, 0x4F, 0x4C, 0x41, 0x20, 0x43, 0x4F, 0x4D, 0x4F,
    ];
    let block_types_b = vec![BlockType::ScreencodeText; bytes_b.len()];

    let lines_b = disassembler.disassemble(
        &bytes_b,
        &block_types_b,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    assert_eq!(lines_b.len(), 4);
    assert_eq!(lines_b[1].operand, "\"screen\"");
    assert_eq!(lines_b[2].mnemonic, ".text");
    assert_eq!(lines_b[2].operand, "\"0-9, hola como\"");
}
#[test]
fn test_screencode_limit_0x5f() {
    let settings = DocumentSettings {
        assembler: Assembler::Tass64,
        ..Default::default()
    };

    let disassembler = Disassembler::new();
    let labels = BTreeMap::new();
    let origin = 0x1000;

    // 0x5E (94) -> < 0x5f. Maps to '~' (126). Text.
    // 0x5F (95) -> >= 0x5f. Byte.
    // 0x60 (96) -> >= 0x5f. Byte.
    let code = vec![0x5E, 0x5F, 0x60];
    let block_types = vec![BlockType::ScreencodeText; 3];

    let lines = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    // Expected: .text "~", $5f, $60
    // Tass wraps in .encode ... .endencode
    let text_lines: Vec<&regenerator2000::disassembler::DisassemblyLine> =
        lines.iter().filter(|l| l.mnemonic == ".text").collect();

    assert_eq!(text_lines.len(), 1);
    assert_eq!(text_lines[0].operand, "\"~\", $5f, $60");
}

#[test]
fn test_acme_screencode_case_inversion() {
    let settings = DocumentSettings {
        assembler: Assembler::Acme,
        ..Default::default()
    };
    let disassembler = Disassembler::new();
    let labels = BTreeMap::new();
    let origin = 0x1000;

    // Screencodes:
    // 0x01 -> 'A' (handle_screencode) -> "a" (format_screencode inverted)
    // 0x41 -> 'a' (handle_screencode) -> "A" (format_screencode inverted)
    // 0x1B -> '[' (handle_screencode 27+64=91) -> "['" (format_screencode not special)
    // 0x1E -> '^' (handle_screencode 30+64=94) -> "^" (format_screencode not special)
    // 0x5B -> '{' (handle_screencode 91+32=123) -> $5b (format_screencode hex)
    // 0x5E -> '~' (handle_screencode 94+32=126) -> $5e (format_screencode hex)

    let code = vec![
        0x01, 0x41, // aA
        0x1B, 0x1E, // [^
        0x5B, 0x5E, // {~ -> $5b $5e
    ];
    let block_types = vec![BlockType::ScreencodeText; code.len()];

    let lines = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    assert_eq!(lines.len(), 1);
    assert_eq!(lines[0].mnemonic, "!scr");
    assert_eq!(lines[0].operand, "\"aA[^\", $5b, $5e");
}

#[test]
fn test_target_address_population() {
    let settings = DocumentSettings::default();
    let disassembler = Disassembler::new();
    let labels = BTreeMap::new();
    let origin = 0x1000;

    // 1. JMP $1234 (4C 34 12)
    // 2. BNE +4 (D0 04) -> 1003 + 2 + 4 = 1009
    // 3. NOP (EA)

    let code = vec![
        0x4C, 0x34, 0x12, // JMP $1234
        0xD0, 0x04, // BNE +4 (to 1003 + 2 + 04 = 1009)
        0xEA, // NOP
    ];
    let block_types = vec![
        BlockType::Code,
        BlockType::Code,
        BlockType::Code,
        BlockType::Code,
        BlockType::Code,
        BlockType::Code,
    ];

    let lines = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    assert_eq!(lines.len(), 3);

    // JMP $1234
    assert_eq!(lines[0].target_address, Some(0x1234));

    // BNE +4
    // Address of BNE is 1003. Length 2. Next PC = 1005. Offset +4. Target = 1009.
    assert_eq!(lines[1].target_address, Some(0x1009));

    // NOP
    assert_eq!(lines[2].target_address, None);
}

#[test]
fn test_target_address_specific_instructions() {
    let settings = DocumentSettings {
        brk_single_byte: false,
        patch_brk: false,
        ..Default::default()
    };
    let disassembler = Disassembler::new();
    let labels = BTreeMap::new();
    let origin = 0x1000;

    // 1. JSR $2000 (20 00 20) -> Should have target
    // 2. JMP (Indirect) (6C 34 12) -> Should NOT have target
    // 3. RTS (60) -> Should NOT have target
    // 4. BRK (00) -> Should NOT have target
    // 5. RTI (40) -> Should NOT have target

    let code = vec![
        0x20, 0x00, 0x20, // JSR $2000
        0x6C, 0x34, 0x12, // JMP ($1234)
        0x60, // RTS
        0x00, 0x00, // BRK #$00
        0x40, // RTI
    ];
    let block_types = vec![
        BlockType::Code,
        BlockType::Code,
        BlockType::Code,
        BlockType::Code,
        BlockType::Code,
        BlockType::Code,
        BlockType::Code,
        BlockType::Code,
        BlockType::Code,
        BlockType::Code, // Added one
    ];

    let lines = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    assert_eq!(lines.len(), 5);

    // JSR $2000
    assert_eq!(lines[0].mnemonic, "jsr");
    assert_eq!(lines[0].target_address, Some(0x2000));

    // JMP ($1234)
    assert_eq!(lines[1].mnemonic, "jmp");
    assert_eq!(lines[1].target_address, None);

    // RTS
    assert_eq!(lines[2].mnemonic, "rts");
    assert_eq!(lines[2].target_address, None);

    // BRK
    assert_eq!(lines[3].mnemonic, "brk");
    assert_eq!(lines[3].target_address, None);

    // RTI
    assert_eq!(lines[4].mnemonic, "rti");
    assert_eq!(lines[4].target_address, None);
}

#[test]
fn test_side_comment_propagation_suppressed_for_code() {
    let labels = BTreeMap::new();
    let mut user_side_comments = BTreeMap::new();
    user_side_comments.insert(0x1000, "Loop Start".to_string());

    // $1000: BNE $1000 -> D0 FE
    let data = vec![0xD0, 0xFE];
    let block_types = vec![BlockType::Code; 2];
    let address = 0x1000;

    let disassembler = Disassembler::new();
    let formatter = AcmeFormatter;
    let settings = DocumentSettings::default();
    let system_comments = BTreeMap::new();

    // mimic disassemble loop: get comment for current address
    let side_comment = user_side_comments
        .get(&address)
        .cloned()
        .unwrap_or_default();

    let (_, lines) = disassembler.handle_code(
        0, // pc relative to data start
        &data,
        &block_types,
        address,
        &formatter,
        &labels,
        &settings,
        address, // origin added
        None,
        side_comment,
        None,
        &system_comments,
        &user_side_comments,
        &BTreeMap::new(),
    );

    assert_eq!(lines.len(), 1);
    let line = &lines[0];
    // It SHOULD have the comment "Loop Start" once.
    assert_eq!(line.comment, "Loop Start");
    // It should NOT be "Loop Start; Loop Start"

    // Now test another instruction jumping to it
    // $1002: JMP $1000 -> 4C 00 10
    // We need combined data so target is found as Code
    // $1000: BNE $1000 (D0 FE)
    // $1002: JMP $1000 (4C 00 10)
    let full_data = vec![0xD0, 0xFE, 0x4C, 0x00, 0x10];
    let full_block_types = vec![BlockType::Code; 5];

    // Handle JMP at offset 2 ($1002)
    let (_, lines2) = disassembler.handle_code(
        2,
        &full_data,
        &full_block_types,
        0x1002, // address
        &formatter,
        &labels,
        &settings,
        0x1000, // origin
        None,
        String::new(), // No comment on the JMP itself
        None,
        &system_comments,
        &user_side_comments,
        &BTreeMap::new(),
    );

    assert_eq!(lines2.len(), 1);
    // Should NOT have propagated comment from $1000 because target ($1000) is Code.
    assert_eq!(lines2[0].comment, "");
}

#[test]
fn test_side_comment_propagation_allowed_for_data() {
    let labels = BTreeMap::new();
    let mut user_side_comments = BTreeMap::new();
    user_side_comments.insert(0x2000, "My Data".to_string());

    let data = vec![0xAD, 0x00, 0x20]; // LDA $2000
    // Target $2000 is out of bounds of this data block, so is_code_target should be false.

    let block_types = vec![BlockType::Code; 3];
    let address = 0x1000;

    let disassembler = Disassembler::new();
    let formatter = AcmeFormatter;
    let settings = DocumentSettings::default();
    let system_comments = BTreeMap::new();

    let (_, lines) = disassembler.handle_code(
        0,
        &data,
        &block_types,
        address,
        &formatter,
        &labels,
        &settings,
        address, // origin
        None,
        String::new(),
        None,
        &system_comments,
        &user_side_comments,
        &BTreeMap::new(),
    );

    assert_eq!(lines.len(), 1);
    assert_eq!(lines[0].comment, "My Data");
}

#[test]
fn test_lohi_block() {
    let settings = DocumentSettings {
        assembler: Assembler::Acme,
        ..Default::default()
    };

    let disassembler = Disassembler::new();
    let mut labels = BTreeMap::new();
    let origin = 0x1000;

    // Data: 00 01 (Lo part), C0 D0 (Hi part)
    // Addr 0: 00 paired with C0 -> $C000
    // Addr 1: 01 paired with D0 -> $D001
    let code = vec![0x00, 0x01, 0xC0, 0xD0];
    let block_types = vec![BlockType::LoHiAddress; 4];

    // Case 1: No labels
    let lines = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    // Should produce 2 lines:
    // 1. !byte <$C000, <$D001
    // 2. !byte >$C000, >$D001
    assert_eq!(lines.len(), 2);
    assert_eq!(lines[0].mnemonic, "!byte");
    assert_eq!(lines[0].operand, "<$c000, <$d001");
    // LoHi logic sets `show_bytes` to false to avoid clutter?
    // Let's check implementation. Yes `show_bytes: false`.
    assert!(!lines[0].show_bytes);

    assert_eq!(lines[1].mnemonic, "!byte");
    assert_eq!(lines[1].operand, ">$c000, >$d001");

    // Case 2: With Label at $C000
    labels.insert(
        0xC000,
        vec![regenerator2000::state::Label {
            name: "MyLabel".to_string(),
            kind: regenerator2000::state::LabelKind::User,
            label_type: regenerator2000::state::LabelType::AbsoluteAddress,
        }],
    );

    let lines_labelled = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    assert_eq!(lines_labelled.len(), 2);
    assert_eq!(lines_labelled[0].operand, "<MyLabel, <$d001");
    assert_eq!(lines_labelled[1].operand, ">MyLabel, >$d001");
}

#[test]
fn test_lohi_internal_label_regression() {
    let settings = DocumentSettings {
        assembler: Assembler::Acme,
        ..Default::default()
    };

    let disassembler = Disassembler::new();
    let mut labels = BTreeMap::new();
    let origin = 0x1000;

    // 4 bytes total: 00 01 (Lo), C0 D0 (Hi)
    // Addr: 1000, 1001, 1002, 1003.
    // Label at 1002 (Start of Hi part).
    // The previous bug caused the loop to break at 1002, processing only 00 01 (pair count 1).
    // The correct behavior is to ignore the label and process all 4 bytes (pair count 2).

    let code = vec![0x00, 0x01, 0xC0, 0xD0];
    let block_types = vec![BlockType::LoHiAddress; 4];

    labels.insert(
        0x1002, // Midpoint
        vec![regenerator2000::state::Label {
            name: "HiPart".to_string(),
            kind: regenerator2000::state::LabelKind::User,
            label_type: regenerator2000::state::LabelType::AbsoluteAddress,
        }],
    );

    let lines = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    // Should produce 2 lines (Lo and Hi), not broken parts.
    assert_eq!(lines.len(), 2);
    // Line 1: Lo part (00 01)
    assert_eq!(lines[0].operand, "<$c000, <$d001");

    // Line 2: Hi part (C0 D0)
    // The label "HiPart" is at 1002.
    // The disassembly line for Hi part starts at 1002.
    // So line[1] should have label "HiPart".
    assert_eq!(lines[1].label, Some("HiPart".to_string()));
    assert_eq!(lines[1].operand, ">$c000, >$d001");
}

#[test]
fn test_hilo_block() {
    let settings = DocumentSettings {
        assembler: Assembler::Acme,
        ..Default::default()
    };

    let disassembler = Disassembler::new();
    let mut labels = BTreeMap::new();
    let origin = 0x1000;

    // Data: C0 D0 (Hi part), 00 01 (Lo part)
    // Addr 0: C0 paired with 00 -> $C000
    // Addr 1: D0 paired with 01 -> $D001
    let code = vec![0xC0, 0xD0, 0x00, 0x01];
    let block_types = vec![BlockType::HiLoAddress; 4];

    // Case 1: No labels
    let lines = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    // Should produce 2 lines:
    // 1. !byte >$C000, >$D001  (Hi bytes C0, D0)
    // 2. !byte <$C000, <$D001  (Lo bytes 00, 01)
    assert_eq!(lines.len(), 2);
    assert_eq!(lines[0].mnemonic, "!byte");
    assert_eq!(lines[0].operand, ">$c000, >$d001");
    // LoHi and HiLo logic sets `show_bytes` to false to avoid clutter
    assert!(!lines[0].show_bytes);

    assert_eq!(lines[1].mnemonic, "!byte");
    assert_eq!(lines[1].operand, "<$c000, <$d001");

    // Case 2: With Label at $C000
    labels.insert(
        0xC000,
        vec![regenerator2000::state::Label {
            name: "MyLabel".to_string(),
            kind: regenerator2000::state::LabelKind::User,
            label_type: regenerator2000::state::LabelType::AbsoluteAddress,
        }],
    );

    let lines_labelled = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    assert_eq!(lines_labelled.len(), 2);
    assert_eq!(lines_labelled[0].operand, ">MyLabel, >$d001");
    assert_eq!(lines_labelled[1].operand, "<MyLabel, <$d001");
}

#[test]
fn test_inverted_binary_format() {
    // LDA #$00 -> should be #~%11111111 if InvertedBinary
    // $00 in binary is 00000000. Inverted is 11111111.

    // LDA #$FF -> should be #~%00000000
    // $FF is 11111111. Inverted is 00000000.

    let settings = DocumentSettings {
        assembler: Assembler::Tass64,
        ..Default::default()
    };

    let disassembler = Disassembler::new();
    let labels = BTreeMap::new();
    let origin = 0x1000;

    let code = vec![0xA9, 0x00, 0xA9, 0xFF]; // LDA #$00, LDA #$FF
    let block_types = vec![BlockType::Code; 4];

    let mut immediate_value_formats = BTreeMap::new();
    immediate_value_formats.insert(
        0x1000,
        regenerator2000::state::ImmediateFormat::InvertedBinary,
    );
    immediate_value_formats.insert(
        0x1002,
        regenerator2000::state::ImmediateFormat::InvertedBinary,
    );

    let lines = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &immediate_value_formats,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    assert_eq!(lines[0].operand, "#~%11111111");
    assert_eq!(lines[1].operand, "#~%00000000");
}

#[test]
fn test_acme_accumulator_formatting() {
    let settings = DocumentSettings {
        assembler: Assembler::Acme,
        ..Default::default()
    };

    let disassembler = Disassembler::new();
    let labels = BTreeMap::new();
    let origin = 0x1000;

    // ASL A (0x0A), LSR A (0x4A), ROL A (0x2A), ROR A (0x6A)
    let code = vec![0x0A, 0x4A, 0x2A, 0x6A];
    let block_types = vec![
        BlockType::Code,
        BlockType::Code,
        BlockType::Code,
        BlockType::Code,
    ];

    let lines = disassembler.disassemble(
        &code,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    assert_eq!(lines.len(), 4);
    assert_eq!(lines[0].mnemonic, "asl");
    assert_eq!(lines[0].operand, ""); // Expect empty operand for accumulator in ACME

    assert_eq!(lines[1].mnemonic, "lsr");
    assert_eq!(lines[1].operand, "");

    assert_eq!(lines[2].mnemonic, "rol");
    assert_eq!(lines[2].operand, "");

    assert_eq!(lines[3].mnemonic, "ror");
    assert_eq!(lines[3].operand, "");
}

#[test]
fn test_addresses_per_line() {
    // Set addresses per line to 2
    let settings = DocumentSettings {
        addresses_per_line: 2,
        ..Default::default()
    };

    let disassembler = Disassembler::new();
    let labels = BTreeMap::new();
    let origin = 0x1000;

    // 8 bytes of data -> 4 pairs for LoHi
    // Lo: 00, 01, 02, 03
    // Hi: 10, 11, 12, 13
    // Addresses per line = 2
    // Expected output:
    // Line 1: .byte <, < (Lo part 1) - wait, Lo bytes combined with Hi bytes form address?
    // Let's re-read LoHi logic.
    // LoHi: Lo bytes are at [pc], Hi bytes are at [pc + split_offset].
    // Value = (Hi << 8) | Lo.
    // The operand is output as <Label or >Label.
    // If no label, it formats address.
    // Lo line: .byte <, <
    // Hi line: .byte >, >

    // Let's construct data such that resulting addresses are known.
    // Byte 0 (Lo): 00. Byte 4 (Hi): 10. Addr: .
    // Byte 1 (Lo): 01. Byte 5 (Hi): 11. Addr: .
    // Byte 2 (Lo): 02. Byte 6 (Hi): 12. Addr: .
    // Byte 3 (Lo): 03. Byte 7 (Hi): 13. Addr: .

    let data = vec![0x00, 0x01, 0x02, 0x03, 0x10, 0x11, 0x12, 0x13];
    let block_types = vec![BlockType::LoHiAddress; 8];

    // Standard formatter (Tass64) uses < and > for low/high bytes of address.
    // If no label, it formats as < etc.

    let lines = disassembler.disassemble(
        &data,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    // We expect 4 lines because 4 pairs / 2 pairs per line = 2 lines for Lo, 2 lines for Hi.
    assert_eq!(lines.len(), 4);

    // Verify content
    // Line 0 (Lo 1): <, <
    // Line 1 (Lo 2): <, <
    // Line 2 (Hi 1): >, >
    // Line 3 (Hi 2): >, >

    // Tass formatter: format_address returns .
    // So operands will be <, <

    assert_eq!(lines[0].operand, "<$1000, <$1101");
    assert_eq!(lines[1].operand, "<$1202, <$1303");
    assert_eq!(lines[2].operand, ">$1000, >$1101");
    assert_eq!(lines[3].operand, ">$1202, >$1303");
}

#[test]
fn test_words_per_line() {
    // Set words/addresses per line to 3
    let settings = DocumentSettings {
        addresses_per_line: 3,
        ..Default::default()
    };

    let disassembler = Disassembler::new();
    let labels = BTreeMap::new();
    let origin = 0x2000;

    // 8 bytes -> 4 words
    // Word 1: 00 10 ->
    // Word 2: 01 11 ->
    // Word 3: 02 12 ->
    // Word 4: 03 13 ->

    // addresses_per_line = 3
    // Line 1: .word , ,
    // Line 2: .word

    let data = vec![0x00, 0x10, 0x01, 0x11, 0x02, 0x12, 0x03, 0x13];
    let block_types = vec![BlockType::DataWord; 8];

    let lines = disassembler.disassemble(
        &data,
        &block_types,
        &labels,
        origin,
        &settings,
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &BTreeMap::new(),
        &[],
        &BTreeSet::new(),
    );

    assert_eq!(lines.len(), 2);

    // Tass formatter typically uses .word directive.
    // Check operands
    assert_eq!(lines[0].operand, "$1000, $1101, $1202");
    assert_eq!(lines[1].operand, "$1303");
}

#[test]
fn test_bytes_per_line() {
    let settings = DocumentSettings {
        bytes_per_line: 3,
        ..Default::default()
    };

    let data = [0x11, 0x22, 0x33, 0x44, 0x55];
    let block_types = vec![BlockType::DataByte; 5];
    let disassembler = Disassembler::new();
    let formatter = Box::new(regenerator2000::disassembler::formatter_64tass::TassFormatter);

    let (consumed, lines) = disassembler.handle_data_byte(
        0,
        &data,
        &block_types,
        0x1000,
        formatter.as_ref(),
        &BTreeMap::new(),
        0x1000,
        None,
        String::new(),
        None,
        &BTreeSet::new(),
        &settings,
        &BTreeMap::new(),
    );

    assert_eq!(consumed, 3);
    assert_eq!(lines.len(), 1);
    assert_eq!(lines[0].bytes.len(), 3);
    assert_eq!(lines[0].bytes, vec![0x11, 0x22, 0x33]);
}
