use crate::error::{CoreError, CoreResult};
use crate::model::{BlockType, DocumentBlock, DocumentModel, ReferenceKind};

pub fn import_markdown(input: &str) -> CoreResult<DocumentModel> {
    let mut blocks = Vec::new();
    let mut ancestry: Vec<String> = Vec::new();
    let mut next_id = 1usize;
    let mut lines = input.lines().peekable();

    while let Some(line) = lines.next() {
        if line.trim().is_empty() {
            continue;
        }

        if line.trim_start().starts_with("```") {
            let language = line
                .trim_start()
                .trim_start_matches("```")
                .trim()
                .to_string();
            let mut code_lines = Vec::new();
            let mut closed = false;
            for next in lines.by_ref() {
                if next.trim_start().starts_with("```") {
                    closed = true;
                    break;
                }
                code_lines.push(next.to_string());
            }
            if !closed {
                return Err(CoreError::MarkdownParse("代码块缺少结束围栏".into()));
            }
            ancestry.clear();
            let mut block = DocumentBlock::new(next_block_id(&mut next_id), BlockType::CodeBlock)
                .with_text(code_lines.join("\n"));
            if !language.is_empty() {
                block = block.with_language(language);
            }
            blocks.push(block);
            continue;
        }

        let indent = count_indent(line);
        while ancestry.len() > indent as usize {
            ancestry.pop();
        }

        let trimmed = line.trim_start();
        let mut block = if let Some((level, title)) = parse_heading(trimmed) {
            ancestry.clear();
            DocumentBlock::new(next_block_id(&mut next_id), BlockType::Heading)
                .with_heading_level(level)
                .with_text(title)
        } else if let Some((checked, text)) = parse_todo(trimmed) {
            DocumentBlock::new(next_block_id(&mut next_id), BlockType::Todo)
                .with_checked(checked)
                .with_text(text)
        } else if let Some(text) = parse_bullet(trimmed) {
            DocumentBlock::new(next_block_id(&mut next_id), BlockType::BulletListItem)
                .with_text(text)
        } else if let Some(text) = parse_numbered(trimmed) {
            DocumentBlock::new(next_block_id(&mut next_id), BlockType::NumberedListItem)
                .with_text(text)
        } else if let Some(text) = trimmed.strip_prefix("> ") {
            DocumentBlock::new(next_block_id(&mut next_id), BlockType::Quote).with_text(text)
        } else if trimmed == "---" {
            ancestry.clear();
            DocumentBlock::new(next_block_id(&mut next_id), BlockType::Divider)
        } else if let Some((target_id, label)) = parse_reference_token(trimmed, "[[", "]]") {
            ancestry.clear();
            DocumentBlock::new(next_block_id(&mut next_id), BlockType::PageReference)
                .with_reference(ReferenceKind::Page, target_id, label)
        } else if let Some((target_id, label)) = parse_reference_token(trimmed, "((", "))") {
            ancestry.clear();
            DocumentBlock::new(next_block_id(&mut next_id), BlockType::BlockReference)
                .with_reference(ReferenceKind::Block, target_id, label)
        } else {
            DocumentBlock::new(next_block_id(&mut next_id), BlockType::Paragraph).with_text(trimmed)
        };

        if indent > 0 {
            let parent_id = ancestry
                .last()
                .cloned()
                .ok_or(CoreError::MarkdownParse("缩进层级缺少父块".into()))?;
            block = block.with_parent(parent_id, indent);
        }
        ancestry.push(block.id.clone());
        blocks.push(block);
    }

    Ok(DocumentModel::new(blocks))
}

pub fn import_plain_text(input: &str) -> CoreResult<DocumentModel> {
    let mut blocks = Vec::new();
    let normalized = input.replace("\r\n", "\n");
    let mut next_id = 1usize;
    for paragraph in normalized
        .split("\n\n")
        .map(str::trim)
        .filter(|value| !value.is_empty())
    {
        let block = DocumentBlock::new(next_block_id(&mut next_id), BlockType::Paragraph)
            .with_text(paragraph.replace('\n', " "));
        blocks.push(block);
    }
    Ok(DocumentModel::new(blocks))
}

pub fn export_markdown(document: &DocumentModel) -> String {
    let mut lines = Vec::new();
    for block in document.blocks() {
        let indent = "  ".repeat(block.indent as usize);
        match block.block_type {
            BlockType::Heading => {
                let level = block.heading_level.unwrap_or(1).clamp(1, 6);
                lines.push(format!(
                    "{indent}{} {}",
                    "#".repeat(level as usize),
                    block.content.text
                ));
            }
            BlockType::BulletListItem => {
                lines.push(format!("{indent}- {}", block.content.text));
            }
            BlockType::NumberedListItem => {
                lines.push(format!("{indent}1. {}", block.content.text));
            }
            BlockType::Todo => {
                let marker = if block.checked.unwrap_or(false) {
                    "x"
                } else {
                    " "
                };
                lines.push(format!("{indent}- [{marker}] {}", block.content.text));
            }
            BlockType::Quote => {
                lines.push(format!("{indent}> {}", block.content.text));
            }
            BlockType::Divider => {
                lines.push(format!("{indent}---"));
            }
            BlockType::CodeBlock => {
                let fence = match block.content.language.as_deref() {
                    Some(language) if !language.is_empty() => format!("{indent}```{language}"),
                    _ => format!("{indent}```"),
                };
                lines.push(fence);
                for code_line in block.content.text.lines() {
                    lines.push(format!("{indent}{code_line}"));
                }
                lines.push(format!("{indent}```"));
            }
            BlockType::PageReference => {
                lines.push(format!(
                    "{indent}{}",
                    render_reference_token(block.content.reference.as_ref(), "[[", "]]")
                ));
            }
            BlockType::BlockReference => {
                lines.push(format!(
                    "{indent}{}",
                    render_reference_token(block.content.reference.as_ref(), "((", "))")
                ));
            }
            _ => {
                lines.push(format!("{indent}{}", block.content.text));
            }
        }
    }
    lines.join("\n")
}

fn next_block_id(next_id: &mut usize) -> String {
    let current = *next_id;
    *next_id += 1;
    format!("imported_{current}")
}

fn count_indent(line: &str) -> u16 {
    let mut spaces = 0usize;
    for ch in line.chars() {
        match ch {
            ' ' => spaces += 1,
            '\t' => spaces += 2,
            _ => break,
        }
    }
    (spaces / 2) as u16
}

fn parse_heading(line: &str) -> Option<(u8, &str)> {
    let level = line.chars().take_while(|ch| *ch == '#').count();
    if level == 0 || level > 6 {
        return None;
    }
    let title = line.get(level + 1..)?;
    if !line.as_bytes().get(level).is_some_and(|ch| *ch == b' ') {
        return None;
    }
    Some((level as u8, title))
}

fn parse_todo(line: &str) -> Option<(bool, &str)> {
    if let Some(rest) = line.strip_prefix("- [ ] ") {
        return Some((false, rest));
    }
    if let Some(rest) = line.strip_prefix("- [x] ") {
        return Some((true, rest));
    }
    if let Some(rest) = line.strip_prefix("- [X] ") {
        return Some((true, rest));
    }
    None
}

fn parse_bullet(line: &str) -> Option<&str> {
    line.strip_prefix("- ").or_else(|| line.strip_prefix("* "))
}

fn parse_numbered(line: &str) -> Option<&str> {
    let dot_index = line.find(". ")?;
    if line[..dot_index].chars().all(|ch| ch.is_ascii_digit()) {
        line.get(dot_index + 2..)
    } else {
        None
    }
}

fn parse_reference_token(
    line: &str,
    prefix: &str,
    suffix: &str,
) -> Option<(String, Option<String>)> {
    let body = line.strip_prefix(prefix)?.strip_suffix(suffix)?;
    let mut parts = body.splitn(2, '|');
    let target_id = parts.next()?.trim();
    if target_id.is_empty() {
        return None;
    }
    let label = parts.next().map(|value| value.trim().to_string());
    Some((
        target_id.to_string(),
        label.filter(|value| !value.is_empty()),
    ))
}

fn render_reference_token(
    reference: Option<&crate::model::BlockReference>,
    prefix: &str,
    suffix: &str,
) -> String {
    match reference {
        Some(reference) => match reference.label.as_deref() {
            Some(label) if !label.is_empty() => {
                format!("{prefix}{}|{label}{suffix}", reference.target_id)
            }
            _ => format!("{prefix}{}{suffix}", reference.target_id),
        },
        None => format!("{prefix}{suffix}"),
    }
}
