//! Visual showcase for shared list semantics.
//!
//! Generates several HWPX documents under `temp/list_shared_semantics_visual/`
//! so they can be opened in Hancom Office for manual inspection.
//!
//! Usage:
//!   cargo run -p hwpforge-smithy-hwpx --example list_shared_semantics_visual

#[path = "_support/list_visual.rs"]
mod list_visual;

use std::fs;
use std::path::Path;

use hwpforge_blueprint::builtins::builtin_default;
use hwpforge_blueprint::registry::StyleRegistry;
use hwpforge_core::document::Document;
use hwpforge_core::image::ImageStore;
use hwpforge_core::metadata::Metadata;
use hwpforge_core::numbering::{NumberingDef, ParagraphListRef};
use hwpforge_core::paragraph::Paragraph;
use hwpforge_core::run::Run;
use hwpforge_core::section::Section;
use hwpforge_core::table::{Table, TableCell, TablePageBreak, TableRow};
use hwpforge_core::PageSettings;
use hwpforge_foundation::{CharShapeIndex, Color, HwpUnit, NumberFormatType, ParaShapeIndex};
use hwpforge_smithy_hwpx::{HwpxEncoder, HwpxRegistryBridge};
use list_visual::{
    build_base_visual_style_ids, build_list_shape_array, bullet_def, para_head, push_bullet,
    push_numbering, BaseVisualStyleIds,
};

const OUT_DIR: &str = "temp/list_shared_semantics_visual";
const DEBUG_DIR: &str = "temp/list_shared_semantics_visual/debug_isolation";

#[derive(Clone, Copy)]
struct VisualStyles {
    body_cs: CharShapeIndex,
    title_cs: CharShapeIndex,
    note_cs: CharShapeIndex,
    body_ps: ParaShapeIndex,
    title_ps: ParaShapeIndex,
    note_ps: ParaShapeIndex,
    bullet_primary: [ParaShapeIndex; 5],
    bullet_secondary: [ParaShapeIndex; 5],
    bullet_edge: [ParaShapeIndex; 5],
    numbered_primary: [ParaShapeIndex; 10],
    numbered_offset: [ParaShapeIndex; 4],
    numbered_nested: [ParaShapeIndex; 4],
    outline: [ParaShapeIndex; 10],
}

#[derive(Clone)]
struct VisualRegistry {
    registry: StyleRegistry,
    styles: VisualStyles,
}

fn main() {
    let visual = build_visual_registry();
    let out_dir = Path::new(OUT_DIR);
    fs::create_dir_all(out_dir).expect("create output directory");
    fs::create_dir_all(DEBUG_DIR).expect("create debug output directory");

    let cases = vec![
        (
            "00_all_in_one.hwpx",
            "Shared List Semantics - All In One",
            build_all_in_one_case(&visual.styles),
        ),
        (
            "01_bullet_matrix.hwpx",
            "Shared List Semantics - Bullet Matrix",
            build_bullet_matrix_case(&visual.styles),
        ),
        (
            "02_numbering_custom_formats.hwpx",
            "Shared List Semantics - Numbering Formats",
            build_numbering_case(&visual.styles),
        ),
        (
            "03_outline_depth.hwpx",
            "Shared List Semantics - Outline Depth",
            build_outline_case(&visual.styles),
        ),
        (
            "04_mixed_edge_cases.hwpx",
            "Shared List Semantics - Mixed Edge Cases",
            build_mixed_case(&visual.styles),
        ),
        (
            "05_table_ordered_lists.hwpx",
            "Shared List Semantics - Table Ordered Lists",
            build_table_case(&visual.styles),
        ),
    ];

    let image_store = ImageStore::new();
    for (file_name, title, paragraphs) in cases {
        let path = out_dir.join(file_name);
        write_case(&path, title, paragraphs, &visual.registry, &image_store);
        println!("generated {}", path.display());
    }

    write_manifest(out_dir);
    println!("manifest {}", out_dir.join("README.md").display());

    for (file_name, title, paragraphs) in build_debug_cases(&visual.styles) {
        let path = Path::new(DEBUG_DIR).join(file_name);
        write_case(&path, title, paragraphs, &visual.registry, &image_store);
        println!("generated {}", path.display());
    }

    write_debug_manifest(Path::new(DEBUG_DIR));
    println!("debug manifest {}", Path::new(DEBUG_DIR).join("README.md").display());
}

fn build_visual_registry() -> VisualRegistry {
    let template = builtin_default().expect("builtin default template");
    let mut registry = StyleRegistry::from_template(&template).expect("default style registry");

    let BaseVisualStyleIds { body_cs, title_cs, note_cs, body_ps, title_ps, note_ps } =
        build_base_visual_style_ids(&mut registry);

    let _outline_builtin = push_numbering(&mut registry, NumberingDef::default_outline());
    let numbering_primary = push_numbering(&mut registry, custom_formats_numbering(10));
    let numbering_offset = push_numbering(&mut registry, offset_numbering(11));
    let numbering_nested = push_numbering(&mut registry, nested_numbering(12));

    // Hancom is picky here. Reuse the fixture-proven glyph/id pattern instead
    // of experimenting with multiple arbitrary bullet definitions.
    let bullet_primary = push_bullet(&mut registry, bullet_def(1, ""));
    let bullet_secondary = bullet_primary;
    let bullet_edge = bullet_primary;

    let bullet_primary_shapes = build_list_shape_array(&mut registry, body_ps, |level| {
        ParagraphListRef::Bullet { bullet_id: bullet_primary, level }
    });
    let bullet_secondary_shapes = build_list_shape_array(&mut registry, body_ps, |level| {
        ParagraphListRef::Bullet { bullet_id: bullet_secondary, level }
    });
    let bullet_edge_shapes = build_list_shape_array(&mut registry, body_ps, |level| {
        ParagraphListRef::Bullet { bullet_id: bullet_edge, level }
    });
    let numbered_primary_shapes = build_list_shape_array_10(&mut registry, body_ps, |level| {
        ParagraphListRef::Number { numbering_id: numbering_primary, level }
    });
    let numbered_offset_shapes = build_list_shape_array(&mut registry, body_ps, |level| {
        ParagraphListRef::Number { numbering_id: numbering_offset, level }
    });
    let numbered_nested_shapes = build_list_shape_array(&mut registry, body_ps, |level| {
        ParagraphListRef::Number { numbering_id: numbering_nested, level }
    });
    let outline_shapes = build_list_shape_array_10(&mut registry, body_ps, |level| {
        ParagraphListRef::Outline { level }
    });

    VisualRegistry {
        registry,
        styles: VisualStyles {
            body_cs,
            title_cs,
            note_cs,
            body_ps,
            title_ps,
            note_ps,
            bullet_primary: bullet_primary_shapes,
            bullet_secondary: bullet_secondary_shapes,
            bullet_edge: bullet_edge_shapes,
            numbered_primary: numbered_primary_shapes,
            numbered_offset: numbered_offset_shapes,
            numbered_nested: numbered_nested_shapes,
            outline: outline_shapes,
        },
    }
}

fn build_list_shape_array_10(
    registry: &mut StyleRegistry,
    base_para: ParaShapeIndex,
    make_ref: impl Fn(u8) -> ParagraphListRef,
) -> [ParaShapeIndex; 10] {
    build_list_shape_array::<10>(registry, base_para, make_ref)
}

fn custom_formats_numbering(id: u32) -> NumberingDef {
    NumberingDef {
        id,
        start: 1,
        levels: vec![
            para_head(1, 1, NumberFormatType::Digit, "^1."),
            para_head(1, 2, NumberFormatType::CircledDigit, "^2"),
            para_head(1, 3, NumberFormatType::RomanSmall, "^3)"),
            para_head(1, 4, NumberFormatType::LatinCapital, "Section ^4."),
            para_head(1, 5, NumberFormatType::LatinSmall, "step ^5)"),
            para_head(1, 6, NumberFormatType::CircledLatinSmall, "^6"),
            para_head(1, 7, NumberFormatType::HangulSyllable, "제 ^7 항"),
            para_head(1, 8, NumberFormatType::CircledHangulSyllable, "^8"),
            para_head(1, 9, NumberFormatType::HangulJamo, "^9)"),
            para_head(1, 10, NumberFormatType::RomanCapital, "APPENDIX ^10"),
        ],
    }
}

fn offset_numbering(id: u32) -> NumberingDef {
    NumberingDef {
        id,
        start: 5,
        levels: vec![
            para_head(5, 1, NumberFormatType::Digit, "[^1]"),
            para_head(3, 2, NumberFormatType::LatinCapital, "CASE-^2"),
            para_head(7, 3, NumberFormatType::RomanCapital, "(^3)"),
            para_head(2, 4, NumberFormatType::HangulSyllable, "제 ^4 조"),
        ],
    }
}

fn nested_numbering(id: u32) -> NumberingDef {
    NumberingDef {
        id,
        start: 1,
        levels: vec![
            para_head(1, 1, NumberFormatType::Digit, "^1."),
            para_head(1, 2, NumberFormatType::LatinCapital, "^2."),
            para_head(1, 3, NumberFormatType::RomanSmall, "^3)"),
            para_head(1, 4, NumberFormatType::CircledDigit, "^4"),
        ],
    }
}

fn title(text: &str, styles: &VisualStyles) -> Paragraph {
    para(text, styles.title_cs, styles.title_ps)
}

fn note(text: &str, styles: &VisualStyles) -> Paragraph {
    para(text, styles.note_cs, styles.note_ps)
}

fn body(text: &str, styles: &VisualStyles) -> Paragraph {
    para(text, styles.body_cs, styles.body_ps)
}

fn list_item(text: &str, styles: &VisualStyles, para_shape: ParaShapeIndex) -> Paragraph {
    para(text, styles.body_cs, para_shape)
}

fn para(text: &str, char_shape: CharShapeIndex, para_shape: ParaShapeIndex) -> Paragraph {
    Paragraph::with_runs(vec![Run::text(text, char_shape)], para_shape)
}

fn blank(styles: &VisualStyles) -> Paragraph {
    body("", styles)
}

fn cover(title_text: &str, description: &str, styles: &VisualStyles) -> Vec<Paragraph> {
    vec![title(title_text, styles), note(description, styles), blank(styles)]
}

fn build_all_in_one_case(styles: &VisualStyles) -> Vec<Paragraph> {
    let mut paras = cover(
        "공유 리스트 시각 검증 종합본",
        "bullet / ordered / outline shared semantics를 한 파일에 모았다. list indent, custom numbering glyph, mixed transition, outline depth를 한글 화면에서 직접 확인하면 된다.",
        styles,
    );
    paras.extend(build_bullet_block(styles));
    paras.push(title("숫자 목록 커스텀 포맷", styles).with_page_break());
    paras.extend(build_numbering_block(styles));
    paras.push(title("개요(outline) 레벨 1-10", styles).with_page_break());
    paras.extend(build_outline_block(styles));
    paras.push(title("혼합 전환 및 edge case", styles).with_page_break());
    paras.extend(build_mixed_block(styles));
    paras.push(title("table 안의 ordered / numbered list", styles).with_page_break());
    paras.extend(build_table_block(styles));
    paras
}

fn build_bullet_matrix_case(styles: &VisualStyles) -> Vec<Paragraph> {
    let mut paras = cover(
        "Bullet Matrix",
        "primary(•), secondary(◦), edge(※) bullet definition과 5단계 nested level을 확인한다.",
        styles,
    );
    paras.extend(build_bullet_block(styles));
    paras
}

fn build_numbering_case(styles: &VisualStyles) -> Vec<Paragraph> {
    let mut paras = cover(
        "Numbering Formats",
        "custom numFormat, custom text template, non-1 start 값을 눈으로 확인하는 문서다.",
        styles,
    );
    paras.extend(build_numbering_block(styles));
    paras
}

fn build_outline_case(styles: &VisualStyles) -> Vec<Paragraph> {
    let mut paras = cover(
        "Outline Depth",
        "outline level 1..10을 순서대로 배치했다. level 9/10은 기본 정의상 label text가 비어 있어 표시가 다르게 보일 수 있다.",
        styles,
    );
    paras.extend(build_outline_block(styles));
    paras
}

fn build_mixed_case(styles: &VisualStyles) -> Vec<Paragraph> {
    let mut paras = cover(
        "Mixed Edge Cases",
        "list가 normal paragraph로 끊겼다가 다시 이어지는 경우, bullet -> number -> outline 전환, nested list depth를 한 화면에서 본다.",
        styles,
    );
    paras.extend(build_mixed_block(styles));
    paras
}

fn build_table_case(styles: &VisualStyles) -> Vec<Paragraph> {
    let mut paras = cover(
        "Table Ordered Lists",
        "table cell 안에 ordered / numbered list paragraph를 직접 넣은 문서다. 셀 폭이 좁을 때의 줄바꿈, nested level, start offset, plain paragraph interruption까지 한 번에 본다.",
        styles,
    );
    paras.extend(build_table_block(styles));
    paras
}

fn build_debug_cases(styles: &VisualStyles) -> Vec<(&'static str, &'static str, Vec<Paragraph>)> {
    vec![
        (
            "00_step0_cover_only.hwpx",
            "Debug 00 step0 cover only",
            cover(
                "공유 리스트 시각 검증 종합본",
                "title + note + blank만 있는 최소 문서",
                styles,
            ),
        ),
        (
            "00_step1_cover_plus_bullet.hwpx",
            "Debug 00 step1 cover plus bullet",
            concat_blocks(vec![
                cover(
                    "공유 리스트 시각 검증 종합본",
                    "cover 다음에 bullet block만 붙인 문서",
                    styles,
                ),
                build_bullet_block(styles),
            ]),
        ),
        (
            "00_step2_add_numbering.hwpx",
            "Debug 00 step2 add numbering",
            concat_blocks(vec![
                cover(
                    "공유 리스트 시각 검증 종합본",
                    "bullet 다음에 numbering block을 추가한 문서",
                    styles,
                ),
                build_bullet_block(styles),
                vec![title("숫자 목록 커스텀 포맷", styles).with_page_break()],
                build_numbering_block(styles),
            ]),
        ),
        (
            "00_step3_add_outline.hwpx",
            "Debug 00 step3 add outline",
            concat_blocks(vec![
                cover(
                    "공유 리스트 시각 검증 종합본",
                    "outline block까지 추가한 문서",
                    styles,
                ),
                build_bullet_block(styles),
                vec![title("숫자 목록 커스텀 포맷", styles).with_page_break()],
                build_numbering_block(styles),
                vec![title("개요(outline) 레벨 1-10", styles).with_page_break()],
                build_outline_block(styles),
            ]),
        ),
        (
            "00_step4_add_mixed.hwpx",
            "Debug 00 step4 add mixed",
            concat_blocks(vec![
                cover(
                    "공유 리스트 시각 검증 종합본",
                    "mixed block까지 추가한 문서",
                    styles,
                ),
                build_bullet_block(styles),
                vec![title("숫자 목록 커스텀 포맷", styles).with_page_break()],
                build_numbering_block(styles),
                vec![title("개요(outline) 레벨 1-10", styles).with_page_break()],
                build_outline_block(styles),
                vec![title("혼합 전환 및 edge case", styles).with_page_break()],
                build_mixed_block(styles),
            ]),
        ),
        (
            "00_step5_add_table.hwpx",
            "Debug 00 step5 add table",
            build_all_in_one_case(styles),
        ),
        (
            "01_bullet_min_single.hwpx",
            "Debug bullet single",
            concat_blocks(vec![
                cover("Bullet Debug", "bullet item 한 줄만 있는 최소 문서", styles),
                vec![list_item("single bullet item", styles, styles.bullet_primary[0])],
            ]),
        ),
        (
            "02_bullet_nested_2.hwpx",
            "Debug bullet nested 2",
            concat_blocks(vec![
                cover("Bullet Debug", "level 1 + level 2 nested bullet", styles),
                vec![
                    list_item("level 1", styles, styles.bullet_primary[0]),
                    list_item("level 2", styles, styles.bullet_primary[1]),
                ],
            ]),
        ),
        (
            "03_bullet_nested_5.hwpx",
            "Debug bullet nested 5",
            concat_blocks(vec![
                cover("Bullet Debug", "5단계 nested bullet만 있는 문서", styles),
                styles
                    .bullet_primary
                    .iter()
                    .enumerate()
                    .map(|(idx, shape)| {
                        list_item(&format!("bullet level {}", idx + 1), styles, *shape)
                    })
                    .collect(),
            ]),
        ),
        (
            "04_bullet_wrapping.hwpx",
            "Debug bullet wrapping",
            concat_blocks(vec![
                cover("Bullet Debug", "긴 bullet text 줄바꿈만 보는 문서", styles),
                vec![
                    list_item(
                        "폭이 좁지 않아도 긴 문장이 여러 줄로 보일 수 있도록 bullet 본문을 충분히 길게 만들어 hanging indent 정렬을 눈으로 확인한다.",
                        styles,
                        styles.bullet_primary[0],
                    ),
                    list_item(
                        "두 번째 긴 bullet 문장도 동일하게 번호 대신 bullet glyph만 고정되고 본문은 다음 줄로 자연스럽게 접혀야 한다.",
                        styles,
                        styles.bullet_primary[1],
                    ),
                ],
            ]),
        ),
        (
            "05_bullet_interruption.hwpx",
            "Debug bullet interruption",
            concat_blocks(vec![
                cover("Bullet Debug", "bullet 사이에 plain paragraph가 끼는 문서", styles),
                vec![
                    list_item("before interruption", styles, styles.bullet_primary[0]),
                    body("이 문단은 list가 아닌 일반 문단이다.", styles),
                    list_item("after interruption", styles, styles.bullet_primary[0]),
                ],
            ]),
        ),
        (
            "06_bullet_plain_vs_semantic.hwpx",
            "Debug bullet plain vs semantic",
            concat_blocks(vec![
                cover("Bullet Debug", "plain text bullet과 real bullet semantics 비교", styles),
                vec![
                    body("• 이 줄은 문자 그대로 bullet 기호를 쓴 plain text다.", styles),
                    list_item("이 줄은 실제 bullet semantics다.", styles, styles.bullet_primary[0]),
                ],
            ]),
        ),
    ]
}

fn concat_blocks(blocks: Vec<Vec<Paragraph>>) -> Vec<Paragraph> {
    blocks.into_iter().flatten().collect()
}

fn build_bullet_block(styles: &VisualStyles) -> Vec<Paragraph> {
    let mut paras = vec![
        title("Bullet depth / wrapping / interruption", styles),
        note(
            "fixture에서 실제로 열린 bullet glyph를 그대로 써서 Hancom 안정성을 우선했다. 여기서는 여러 bullet definition보다 depth, wrapping, interruption을 보는 게 핵심이다.",
            styles,
        ),
    ];

    for (idx, shape) in styles.bullet_primary.iter().enumerate() {
        paras.push(list_item(
            &format!("bullet level {} - nested depth와 hanging indent 확인", idx + 1),
            styles,
            *shape,
        ));
    }
    paras.push(blank(styles));

    for (idx, shape) in styles.bullet_secondary.iter().enumerate() {
        paras.push(list_item(
            &format!(
                "wrapped bullet level {} - 폭이 좁아져도 bullet 정렬이 깨지지 않는 긴 문장 예제",
                idx + 1
            ),
            styles,
            *shape,
        ));
    }
    paras.push(blank(styles));

    paras.push(body("이 문단은 bullet 사이에 낀 일반 문단이다.", styles));

    for (idx, shape) in styles.bullet_edge.iter().enumerate() {
        paras.push(list_item(
            &format!("resume bullet level {} - plain paragraph 이후 재개", idx + 1),
            styles,
            *shape,
        ));
    }
    paras
}

fn build_numbering_block(styles: &VisualStyles) -> Vec<Paragraph> {
    let mut paras = vec![
        title("커스텀 numbering format 10종", styles),
        note("Digit, CircledDigit, RomanSmall, LatinCapital, LatinSmall, CircledLatinSmall, HangulSyllable, CircledHangulSyllable, HangulJamo, RomanCapital을 순서대로 배치했다.", styles),
    ];

    for (idx, shape) in styles.numbered_primary.iter().enumerate() {
        paras.push(list_item(
            &format!("custom numbering level {} - 포맷과 label template를 눈으로 확인", idx + 1),
            styles,
            *shape,
        ));
    }
    paras.push(blank(styles));
    paras.push(title("start offset / prefix-suffix edge", styles));
    paras.push(note(
        "첫 번째 항목이 1이 아니라 5부터 시작해야 하고, [^1], CASE-^2, 제 ^4 조 같은 template가 보이면 정상이다.",
        styles,
    ));
    for (idx, shape) in styles.numbered_offset.iter().enumerate() {
        paras.push(list_item(
            &format!("offset numbering level {} - 시작 번호와 접두/접미 확인", idx + 1),
            styles,
            *shape,
        ));
    }
    paras.push(blank(styles));
    paras.push(title("nested numbering 4단계", styles));
    for (idx, shape) in styles.numbered_nested.iter().enumerate() {
        paras.push(list_item(
            &format!("nested numbering level {} - depth 유지 확인", idx + 1),
            styles,
            *shape,
        ));
    }
    paras
}

fn build_outline_block(styles: &VisualStyles) -> Vec<Paragraph> {
    let mut paras = vec![
        title("Outline level 1..10", styles),
        note("shared IR과 HWPX paraPr/heading(level)은 모두 zero-based다. 화면에서 개요 번호가 정상적으로 보이면 bridge가 level을 올바르게 내린 것이다.", styles),
    ];
    for (idx, shape) in styles.outline.iter().enumerate() {
        paras.push(list_item(
            &format!("outline level {} - 개요 번호와 들여쓰기 확인", idx + 1),
            styles,
            *shape,
        ));
    }
    paras.push(blank(styles));
    paras.push(body(
        "이 문단은 list semantics가 없는 일반 문단이다. outline block 뒤에 plain paragraph가 들어가도 numbering state가 망가지지 않는지 같이 본다.",
        styles,
    ));
    paras
}

fn build_mixed_block(styles: &VisualStyles) -> Vec<Paragraph> {
    vec![
        title("bullet -> number -> outline 전환", styles),
        list_item("bullet 시작", styles, styles.bullet_primary[0]),
        list_item("bullet nested", styles, styles.bullet_primary[1]),
        list_item("numbered로 전환", styles, styles.numbered_nested[0]),
        list_item("numbered nested", styles, styles.numbered_nested[1]),
        list_item("outline로 전환", styles, styles.outline[0]),
        list_item("outline nested", styles, styles.outline[1]),
        blank(styles),
        title("list 중간에 plain paragraph 끼우기", styles),
        list_item("ordered item before interruption", styles, styles.numbered_offset[0]),
        body(
            "이 문단은 list가 아니다. 번호가 끊겼다가 다음 문단에서 어떻게 보이는지 확인한다.",
            styles,
        ),
        list_item("ordered item after interruption", styles, styles.numbered_offset[0]),
        blank(styles),
        title("깊은 nested bullet + ordered 조합", styles),
        list_item("edge bullet level 1", styles, styles.bullet_edge[0]),
        list_item("edge bullet level 2", styles, styles.bullet_edge[1]),
        list_item("edge bullet level 3", styles, styles.bullet_edge[2]),
        list_item("nested ordered level 4", styles, styles.numbered_nested[3]),
        list_item("custom numbering level 6", styles, styles.numbered_primary[5]),
        blank(styles),
        title("plain paragraph와 list text가 비슷해도 semantics는 달라야 한다", styles),
        body("1. 이 문단은 문자 그대로 '1.'을 쓴 plain text다.", styles),
        list_item(
            "이 문단은 실제 numbering semantics를 사용한다.",
            styles,
            styles.numbered_primary[0],
        ),
        body("• 이 문단도 문자 그대로 bullet glyph를 쓴 plain text다.", styles),
        list_item("이 문단은 실제 bullet semantics를 사용한다.", styles, styles.bullet_primary[0]),
    ]
}

fn build_table_block(styles: &VisualStyles) -> Vec<Paragraph> {
    let table_para = Paragraph::with_runs(
        vec![Run::table(build_ordered_list_table(styles), styles.body_cs)],
        styles.body_ps,
    );

    vec![
        title("table cell 내부 ordered list matrix", styles),
        note(
            "왼쪽은 케이스 이름, 가운데는 실제 cell 내부 list paragraph, 오른쪽은 눈으로 볼 포인트다. 특히 row 4와 row 5를 보면 list가 table 안에서도 depth와 줄바꿈을 유지하는지 바로 드러난다.",
            styles,
        ),
        table_para,
        blank(styles),
        note(
            "추가 확인: 셀 내부 plain paragraph가 list 사이에 끼어 있어도 numbering이 깨지지 않는지, 좁은 셀에서 hanging indent가 망가지지 않는지 본다.",
            styles,
        ),
    ]
}

fn build_ordered_list_table(styles: &VisualStyles) -> Table {
    let case_w = HwpUnit::from_mm(28.0).expect("case width");
    let content_w = HwpUnit::from_mm(92.0).expect("content width");
    let check_w = HwpUnit::from_mm(40.0).expect("check width");

    let header = TableRow::new(vec![
        header_cell("케이스", case_w, styles),
        header_cell("셀 안의 ordered / numbered list", content_w, styles),
        header_cell("확인 포인트", check_w, styles),
    ])
    .with_header(true);

    let row_simple = TableRow::new(vec![
        plain_cell(
            vec![body("top-level ordered", styles)],
            case_w,
            Some(Color::from_rgb(245, 245, 245)),
        ),
        plain_cell(
            vec![
                list_item("간단한 1단계 ordered item A", styles, styles.numbered_primary[0]),
                list_item("간단한 1단계 ordered item B", styles, styles.numbered_primary[0]),
                list_item("간단한 1단계 ordered item C", styles, styles.numbered_primary[0]),
            ],
            content_w,
            None,
        ),
        plain_cell(
            vec![body("기본 numbering glyph와 top-level hanging indent", styles)],
            check_w,
            None,
        ),
    ]);

    let row_nested = TableRow::new(vec![
        plain_cell(
            vec![body("nested depth", styles)],
            case_w,
            Some(Color::from_rgb(245, 245, 245)),
        ),
        plain_cell(
            vec![
                list_item("level 1 parent", styles, styles.numbered_nested[0]),
                list_item("level 2 child", styles, styles.numbered_nested[1]),
                list_item("level 3 grandchild", styles, styles.numbered_nested[2]),
                list_item("level 4 deep child", styles, styles.numbered_nested[3]),
            ],
            content_w,
            None,
        ),
        plain_cell(
            vec![body("셀 내부에서도 level별 들여쓰기 누락이 없어야 한다", styles)],
            check_w,
            None,
        ),
    ]);

    let row_custom = TableRow::new(vec![
        plain_cell(
            vec![body("custom formats", styles)],
            case_w,
            Some(Color::from_rgb(245, 245, 245)),
        ),
        plain_cell(
            vec![
                list_item("digit", styles, styles.numbered_primary[0]),
                list_item("roman small", styles, styles.numbered_primary[2]),
                list_item("latin capital", styles, styles.numbered_primary[3]),
                list_item("circled latin small", styles, styles.numbered_primary[5]),
                list_item("hangul syllable", styles, styles.numbered_primary[6]),
            ],
            content_w,
            None,
        ),
        plain_cell(
            vec![body("포맷별 glyph와 label template가 서로 다르게 보여야 한다", styles)],
            check_w,
            None,
        ),
    ]);

    let row_offset = TableRow::new(vec![
        plain_cell(
            vec![body("offset + interruption", styles)],
            case_w,
            Some(Color::from_rgb(245, 245, 245)),
        ),
        plain_cell(
            vec![
                list_item("offset list before plain paragraph", styles, styles.numbered_offset[0]),
                body("이 문단은 같은 셀 안의 plain paragraph다.", styles),
                list_item("offset list after plain paragraph", styles, styles.numbered_offset[0]),
                list_item("offset nested child", styles, styles.numbered_offset[1]),
            ],
            content_w,
            None,
        ),
        plain_cell(
            vec![body(
                "첫 번호가 5부터 시작하고, plain paragraph가 끼어도 흐름이 무너지지 않는지",
                styles,
            )],
            check_w,
            None,
        ),
    ]);

    let row_narrow = TableRow::new(vec![
        plain_cell(
            vec![body("narrow wrapping", styles)],
            case_w,
            Some(Color::from_rgb(245, 245, 245)),
        ),
        plain_cell(
            vec![
                list_item(
                    "폭이 좁은 셀 안에서 긴 ordered list 문장이 줄바꿈되더라도 번호와 hanging indent 정렬이 깨지지 않아야 한다.",
                    styles,
                    styles.numbered_primary[0],
                ),
                list_item(
                    "두 번째 항목도 동일하게 줄바꿈되어, 본문만 접히고 번호 시작선은 유지되는지 본다.",
                    styles,
                    styles.numbered_primary[1],
                ),
            ],
            HwpUnit::from_mm(70.0).expect("narrow content width"),
            None,
        ),
        plain_cell(
            vec![body("좁은 셀에서 번호와 본문 정렬이 분리되는지", styles)],
            check_w,
            None,
        ),
    ]);

    let row_compare = TableRow::new(vec![
        plain_cell(
            vec![body("same text / different semantics", styles)],
            case_w,
            Some(Color::from_rgb(245, 245, 245)),
        ),
        plain_cell(
            vec![
                body("1. 이 줄은 plain text다.", styles),
                list_item(
                    "이 줄은 실제 numbering semantics다.",
                    styles,
                    styles.numbered_primary[0],
                ),
                body("[5] 이 줄도 plain text다.", styles),
                list_item(
                    "이 줄은 offset numbering semantics다.",
                    styles,
                    styles.numbered_offset[0],
                ),
            ],
            content_w,
            None,
        ),
        plain_cell(
            vec![body("눈에 비슷해도 plain text와 real list가 다르게 작동해야 한다", styles)],
            check_w,
            None,
        ),
    ]);

    Table::new(vec![
        header,
        row_simple,
        row_nested,
        row_custom,
        row_offset,
        row_narrow,
        row_compare,
    ])
    .with_width(HwpUnit::from_mm(160.0).expect("table width"))
    .with_page_break(TablePageBreak::Cell)
    .with_repeat_header(true)
    .with_cell_spacing(HwpUnit::from_mm(1.0).expect("cell spacing"))
}

fn header_cell(text: &str, width: HwpUnit, styles: &VisualStyles) -> TableCell {
    plain_cell(vec![title(text, styles)], width, Some(Color::from_rgb(220, 228, 240)))
}

fn plain_cell(paragraphs: Vec<Paragraph>, width: HwpUnit, background: Option<Color>) -> TableCell {
    let cell = TableCell::new(paragraphs, width);
    if let Some(background) = background {
        return cell.with_background(background);
    }
    cell
}

fn write_case(
    path: &Path,
    title: &str,
    paragraphs: Vec<Paragraph>,
    registry: &StyleRegistry,
    image_store: &ImageStore,
) {
    let mut doc =
        Document::with_metadata(Metadata { title: Some(title.to_string()), ..Metadata::default() });
    doc.add_section(Section::with_paragraphs(paragraphs, PageSettings::a4()));
    let bridge = HwpxRegistryBridge::from_registry(registry).expect("build registry bridge");
    let rebound = bridge.rebind_draft_document(doc).expect("rebind generated document");
    let validated = rebound.validate().expect("validate generated document");
    let bytes =
        HwpxEncoder::encode(&validated, bridge.style_store(), image_store).expect("encode hwpx");
    fs::write(path, bytes).expect("write hwpx");
}

fn write_manifest(out_dir: &Path) {
    let mut text = String::new();
    text.push_str("# List Shared Semantics Visual Checklist\n\n");
    text.push_str("Generated files:\n");
    text.push_str("- 00_all_in_one.hwpx: bullet / numbering / outline / mixed edge case 종합본\n");
    text.push_str("- 01_bullet_matrix.hwpx: bullet glyph 3종 + depth 5단계\n");
    text.push_str("- 02_numbering_custom_formats.hwpx: numbering format 10종 + start offset\n");
    text.push_str("- 03_outline_depth.hwpx: outline level 1..10\n");
    text.push_str("- 04_mixed_edge_cases.hwpx: bullet/number/outline transition\n\n");
    text.push_str("- 05_table_ordered_lists.hwpx: table cell 내부 ordered / numbered list\n\n");
    text.push_str("Visual checks:\n");
    text.push_str("- bullet glyph가 • / ◦ / ※ 로 바뀌는지\n");
    text.push_str("- nested level이 깊어질수록 들여쓰기가 유지되는지\n");
    text.push_str(
        "- numbering format이 Digit / Roman / Latin / Hangul / Circled 계열로 각각 보이는지\n",
    );
    text.push_str("- start offset 문단이 5부터 시작하는지\n");
    text.push_str("- outline level 1..10이 순서대로 보이는지\n");
    text.push_str("- plain paragraph와 real list paragraph가 화면에서 구분되는지\n");
    fs::write(out_dir.join("README.md"), text).expect("write manifest");
}

fn write_debug_manifest(out_dir: &Path) {
    let mut text = String::new();
    text.push_str("# Debug Isolation Opening Order\n\n");
    text.push_str("`00_*` files isolate the cumulative steps of `00_all_in_one.hwpx`.\n");
    text.push_str("`01_*` to `06_*` files isolate the bullet block that used to crash `01_bullet_matrix.hwpx`.\n\n");
    text.push_str("Recommended opening order:\n");
    text.push_str("1. 00_step0_cover_only.hwpx\n");
    text.push_str("2. 01_bullet_min_single.hwpx\n");
    text.push_str("3. 02_bullet_nested_2.hwpx\n");
    text.push_str("4. 03_bullet_nested_5.hwpx\n");
    text.push_str("5. 04_bullet_wrapping.hwpx\n");
    text.push_str("6. 05_bullet_interruption.hwpx\n");
    text.push_str("7. 06_bullet_plain_vs_semantic.hwpx\n");
    text.push_str("8. 00_step1_cover_plus_bullet.hwpx\n");
    text.push_str("9. 00_step2_add_numbering.hwpx\n");
    text.push_str("10. 00_step3_add_outline.hwpx\n");
    text.push_str("11. 00_step4_add_mixed.hwpx\n");
    text.push_str("12. 00_step5_add_table.hwpx\n\n");
    text.push_str(
        "Record which file first crashes Hancom. That tells us the smallest bad combination.\n",
    );
    fs::write(out_dir.join("README.md"), text).expect("write debug manifest");
}
