use ParseOpt as PO;

use super::*;

#[test]
fn test_parse_simple() {
    let buf = "**abcd** rest";
    let (t, r) = parse_simple_pat(buf.as_bytes(), b"**", b"**", PO::None, MdTree::Strong).unwrap();
    assert_eq!(t, MdTree::Strong("abcd"));
    assert_eq!(r, b" rest");

    // Escaping should fail
    let buf = r"**abcd\** rest";
    let res = parse_simple_pat(buf.as_bytes(), b"**", b"**", PO::None, MdTree::Strong);
    assert!(res.is_none());
}

#[test]
fn test_parse_comment() {
    let opt = PO::TrimNoEsc;
    let buf = "<!-- foobar! -->rest";
    let (t, r) = parse_simple_pat(buf.as_bytes(), CMT_S, CMT_E, opt, MdTree::Comment).unwrap();
    assert_eq!(t, MdTree::Comment("foobar!"));
    assert_eq!(r, b"rest");

    let buf = r"<!-- foobar! \-->rest";
    let (t, r) = parse_simple_pat(buf.as_bytes(), CMT_S, CMT_E, opt, MdTree::Comment).unwrap();
    assert_eq!(t, MdTree::Comment(r"foobar! \"));
    assert_eq!(r, b"rest");
}

#[test]
fn test_parse_heading() {
    let buf1 = "# Top level\nrest";
    let (t, r) = parse_heading(buf1.as_bytes()).unwrap();
    assert_eq!(t, MdTree::Heading(1, vec![MdTree::PlainText("Top level")].into()));
    assert_eq!(r, b"\nrest");

    let buf1 = "# Empty";
    let (t, r) = parse_heading(buf1.as_bytes()).unwrap();
    assert_eq!(t, MdTree::Heading(1, vec![MdTree::PlainText("Empty")].into()));
    assert_eq!(r, b"");

    // Combo
    let buf2 = "### Top `level` _woo_\nrest";
    let (t, r) = parse_heading(buf2.as_bytes()).unwrap();
    assert_eq!(
        t,
        MdTree::Heading(
            3,
            vec![
                MdTree::PlainText("Top "),
                MdTree::CodeInline("level"),
                MdTree::PlainText(" "),
                MdTree::Emphasis("woo"),
            ]
            .into()
        )
    );
    assert_eq!(r, b"\nrest");
}

#[test]
fn test_parse_code_inline() {
    let buf1 = "`abcd` rest";
    let (t, r) = parse_codeinline(buf1.as_bytes()).unwrap();
    assert_eq!(t, MdTree::CodeInline("abcd"));
    assert_eq!(r, b" rest");

    // extra backticks, newline
    let buf2 = "```ab\ncd``` rest";
    let (t, r) = parse_codeinline(buf2.as_bytes()).unwrap();
    assert_eq!(t, MdTree::CodeInline("ab\ncd"));
    assert_eq!(r, b" rest");

    // test no escaping
    let buf3 = r"`abcd\` rest";
    let (t, r) = parse_codeinline(buf3.as_bytes()).unwrap();
    assert_eq!(t, MdTree::CodeInline(r"abcd\"));
    assert_eq!(r, b" rest");
}

#[test]
fn test_parse_code_block() {
    let buf1 = "```rust\ncode\ncode\n```\nleftovers";
    let (t, r) = parse_codeblock(buf1.as_bytes());
    assert_eq!(t, MdTree::CodeBlock { txt: "code\ncode", lang: Some("rust") });
    assert_eq!(r, b"\nleftovers");

    let buf2 = "`````\ncode\ncode````\n`````\nleftovers";
    let (t, r) = parse_codeblock(buf2.as_bytes());
    assert_eq!(t, MdTree::CodeBlock { txt: "code\ncode````", lang: None });
    assert_eq!(r, b"\nleftovers");
}

#[test]
fn test_parse_link() {
    let simple = "[see here](docs.rs) other";
    let (t, r) = parse_any_link(simple.as_bytes(), false).unwrap();
    assert_eq!(t, MdTree::Link { disp: "see here", link: "docs.rs" });
    assert_eq!(r, b" other");

    let simple_toplevel = "[see here](docs.rs) other";
    let (t, r) = parse_any_link(simple_toplevel.as_bytes(), true).unwrap();
    assert_eq!(t, MdTree::Link { disp: "see here", link: "docs.rs" });
    assert_eq!(r, b" other");

    let reference = "[see here] other";
    let (t, r) = parse_any_link(reference.as_bytes(), true).unwrap();
    assert_eq!(t, MdTree::RefLink { disp: "see here", id: None });
    assert_eq!(r, b" other");

    let reference_full = "[see here][docs-rs] other";
    let (t, r) = parse_any_link(reference_full.as_bytes(), false).unwrap();
    assert_eq!(t, MdTree::RefLink { disp: "see here", id: Some("docs-rs") });
    assert_eq!(r, b" other");

    let reference_def = "[see here]: docs.rs\nother";
    let (t, r) = parse_any_link(reference_def.as_bytes(), true).unwrap();
    assert_eq!(t, MdTree::LinkDef { id: "see here", link: "docs.rs" });
    assert_eq!(r, b"\nother");
}

const IND1: &str = r"test standard
    ind
    ind2
not ind";
const IND2: &str = r"test end of stream
  1
  2
";
const IND3: &str = r"test empty lines
  1
  2

not ind";

#[test]
fn test_indented_section() {
    let (t, r) = get_indented_section(IND1.as_bytes());
    assert_eq!(str::from_utf8(t).unwrap(), "test standard\n    ind\n    ind2");
    assert_eq!(str::from_utf8(r).unwrap(), "\nnot ind");

    let (txt, rest) = get_indented_section(IND2.as_bytes());
    assert_eq!(str::from_utf8(txt).unwrap(), "test end of stream\n  1\n  2\n");
    assert_eq!(str::from_utf8(rest).unwrap(), "");

    let (txt, rest) = get_indented_section(IND3.as_bytes());
    assert_eq!(str::from_utf8(txt).unwrap(), "test empty lines\n  1\n  2\n");
    assert_eq!(str::from_utf8(rest).unwrap(), "\nnot ind");
}

const HBT: &str = r"# Heading

content";

#[test]
fn test_heading_breaks() {
    let expected = vec![
        MdTree::Heading(1, vec![MdTree::PlainText("Heading")].into()),
        MdTree::PlainText("content"),
    ]
    .into();
    let res = entrypoint(HBT);
    assert_eq!(res, expected);
}

const NL1: &str = r"start

end";
const NL2: &str = r"start


end";
const NL3: &str = r"start



end";

#[test]
fn test_newline_breaks() {
    let expected =
        vec![MdTree::PlainText("start"), MdTree::ParagraphBreak, MdTree::PlainText("end")].into();
    for (idx, check) in [NL1, NL2, NL3].iter().enumerate() {
        let res = entrypoint(check);
        assert_eq!(res, expected, "failed {idx}");
    }
}

const WRAP: &str = "plain _italics
italics_";

#[test]
fn test_wrap_pattern() {
    let expected = vec![
        MdTree::PlainText("plain "),
        MdTree::Emphasis("italics"),
        MdTree::Emphasis(" "),
        MdTree::Emphasis("italics"),
    ]
    .into();
    let res = entrypoint(WRAP);
    assert_eq!(res, expected);
}

const WRAP_NOTXT: &str = r"_italics_
**bold**";

#[test]
fn test_wrap_notxt() {
    let expected =
        vec![MdTree::Emphasis("italics"), MdTree::PlainText(" "), MdTree::Strong("bold")].into();
    let res = entrypoint(WRAP_NOTXT);
    assert_eq!(res, expected);
}

const MIXED_LIST: &str = r"start
- _italics item_
<!-- comment -->
- **bold item**
  second line [link1](foobar1)
  third line [link2][link-foo]
-   :crab:
    extra indent
end
[link-foo]: foobar2
";

#[test]
fn test_list() {
    let expected = vec![
        MdTree::PlainText("start"),
        MdTree::ParagraphBreak,
        MdTree::UnorderedListItem(vec![MdTree::Emphasis("italics item")].into()),
        MdTree::LineBreak,
        MdTree::UnorderedListItem(
            vec![
                MdTree::Strong("bold item"),
                MdTree::PlainText(" second line "),
                MdTree::Link { disp: "link1", link: "foobar1" },
                MdTree::PlainText(" third line "),
                MdTree::Link { disp: "link2", link: "foobar2" },
            ]
            .into(),
        ),
        MdTree::LineBreak,
        MdTree::UnorderedListItem(
            vec![MdTree::PlainText("🦀"), MdTree::PlainText(" extra indent")].into(),
        ),
        MdTree::ParagraphBreak,
        MdTree::PlainText("end"),
    ]
    .into();
    let res = entrypoint(MIXED_LIST);
    assert_eq!(res, expected);
}

const SMOOSHED: &str = r#"
start
### heading
1. ordered item
```rust
println!("Hello, world!");
```
`inline`
``end``
"#;

#[test]
fn test_without_breaks() {
    let expected = vec![
        MdTree::PlainText("start"),
        MdTree::ParagraphBreak,
        MdTree::Heading(3, vec![MdTree::PlainText("heading")].into()),
        MdTree::OrderedListItem(1, vec![MdTree::PlainText("ordered item")].into()),
        MdTree::ParagraphBreak,
        MdTree::CodeBlock { txt: r#"println!("Hello, world!");"#, lang: Some("rust") },
        MdTree::ParagraphBreak,
        MdTree::CodeInline("inline"),
        MdTree::PlainText(" "),
        MdTree::CodeInline("end"),
    ]
    .into();
    let res = entrypoint(SMOOSHED);
    assert_eq!(res, expected);
}

const CODE_STARTLINE: &str = r#"
start
`code`
middle
`more code`
end
"#;

#[test]
fn test_code_at_start() {
    let expected = vec![
        MdTree::PlainText("start"),
        MdTree::PlainText(" "),
        MdTree::CodeInline("code"),
        MdTree::PlainText(" "),
        MdTree::PlainText("middle"),
        MdTree::PlainText(" "),
        MdTree::CodeInline("more code"),
        MdTree::PlainText(" "),
        MdTree::PlainText("end"),
    ]
    .into();
    let res = entrypoint(CODE_STARTLINE);
    assert_eq!(res, expected);
}

#[test]
fn test_code_in_parens() {
    let expected =
        vec![MdTree::PlainText("("), MdTree::CodeInline("Foo"), MdTree::PlainText(")")].into();
    let res = entrypoint("(`Foo`)");
    assert_eq!(res, expected);
}

const LIST_WITH_SPACE: &str = "
para
 * l1
 * l2
";

#[test]
fn test_list_with_space() {
    let expected = vec![
        MdTree::PlainText("para"),
        MdTree::ParagraphBreak,
        MdTree::UnorderedListItem(vec![MdTree::PlainText("l1")].into()),
        MdTree::LineBreak,
        MdTree::UnorderedListItem(vec![MdTree::PlainText("l2")].into()),
    ]
    .into();
    let res = entrypoint(LIST_WITH_SPACE);
    assert_eq!(res, expected);
}

const SNAKE_CASE: &str = "
foo*bar*
foo**bar**
foo_bar_
foo__bar__
";

#[test]
fn test_snake_case() {
    let expected = vec![
        MdTree::PlainText("foo"),
        MdTree::Emphasis("bar"),
        MdTree::PlainText(" "),
        MdTree::PlainText("foo"),
        MdTree::Strong("bar"),
        MdTree::PlainText(" "),
        MdTree::PlainText("foo_bar_"),
        MdTree::PlainText(" "),
        MdTree::PlainText("foo__bar__"),
    ]
    .into();
    let res = entrypoint(SNAKE_CASE);
    assert_eq!(res, expected);
}

#[test]
fn test_codeblock_trailing_whitespace() {
    let buf = "```rust\ncode\n```    \nrest";
    let (t, r) = parse_codeblock(buf.as_bytes());
    assert_eq!(t, MdTree::CodeBlock { txt: "code", lang: Some("rust") });
    assert_eq!(r, b"\nrest");

    let buf = "```rust\ncode\n```abc\nrest";
    let (t, r) = parse_codeblock(buf.as_bytes());
    assert_eq!(t, MdTree::CodeBlock { txt: "code\n```abc\nrest", lang: Some("rust") });
    assert_eq!(r, b"");
}

#[test]
fn test_list_item_leading_whitespace() {
    // extra spaces after marker
    let buf = "-   hello";
    let (t, r) = parse_unordered_li(buf.as_bytes());
    assert_eq!(t, MdTree::UnorderedListItem(vec![MdTree::PlainText("hello")].into()));
    assert_eq!(r, b"");

    // tab after the marker space
    let buf = "- \thello";
    let (t, r) = parse_unordered_li(buf.as_bytes());
    assert_eq!(t, MdTree::UnorderedListItem(vec![MdTree::PlainText("hello")].into()));
    assert_eq!(r, b"");

    // ordered list
    let buf = "1.   hello";
    let (t, r) = parse_ordered_li(buf.as_bytes());
    assert_eq!(t, MdTree::OrderedListItem(1, vec![MdTree::PlainText("hello")].into()));
    assert_eq!(r, b"");
}
