use unicode_width::UnicodeWidthStr;

pub fn render_markdown_plain(markdown: &str) -> String {
    let mut out = Vec::new();
    let mut in_fence = false;
    for line in markdown.lines() {
        if line.trim_start().starts_with("```") {
            in_fence = !in_fence;
            continue;
        }
        if in_fence {
            out.push(line.to_string());
            continue;
        }
        let trimmed = line.trim_start();
        if trimmed.starts_with('#') {
            out.push(trimmed.trim_start_matches('#').trim_start().to_string());
        } else {
            out.push(line.replace('`', ""));
        }
    }
    out.join("\n").trim_end().to_string()
}

pub fn byte_index(value: &str, char_index: usize) -> usize {
    value
        .char_indices()
        .nth(char_index)
        .map(|(index, _)| index)
        .unwrap_or(value.len())
}

pub fn char_len(value: &str) -> usize {
    value.chars().count()
}

pub fn prefix(value: &str, char_index: usize) -> String {
    value.chars().take(char_index).collect()
}

pub fn display_width(value: &str) -> usize {
    UnicodeWidthStr::width(value)
}

const CHO: &[char] = &[
    'ㄱ', 'ㄲ', 'ㄴ', 'ㄷ', 'ㄸ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅃ', 'ㅅ', 'ㅆ', 'ㅇ', 'ㅈ', 'ㅉ', 'ㅊ', 'ㅋ',
    'ㅌ', 'ㅍ', 'ㅎ',
];
const JUNG: &[char] = &[
    'ㅏ', 'ㅐ', 'ㅑ', 'ㅒ', 'ㅓ', 'ㅔ', 'ㅕ', 'ㅖ', 'ㅗ', 'ㅘ', 'ㅙ', 'ㅚ', 'ㅛ', 'ㅜ', 'ㅝ', 'ㅞ',
    'ㅟ', 'ㅠ', 'ㅡ', 'ㅢ', 'ㅣ',
];
const JONG: &[char] = &[
    '\0', 'ㄱ', 'ㄲ', 'ㄳ', 'ㄴ', 'ㄵ', 'ㄶ', 'ㄷ', 'ㄹ', 'ㄺ', 'ㄻ', 'ㄼ', 'ㄽ', 'ㄾ', 'ㄿ', 'ㅀ',
    'ㅁ', 'ㅂ', 'ㅄ', 'ㅅ', 'ㅆ', 'ㅇ', 'ㅈ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ',
];

pub fn compose_hangul_jamo(value: &str) -> String {
    let mut out = String::new();
    let mut run = Vec::new();
    for char in decompose_hangul(value).chars() {
        if is_hangul_jamo(char) {
            run.push(char);
        } else {
            if !run.is_empty() {
                out.push_str(&compose_hangul_run(&run));
                run.clear();
            }
            out.push(char);
        }
    }
    if !run.is_empty() {
        out.push_str(&compose_hangul_run(&run));
    }
    out
}

fn decompose_hangul(value: &str) -> String {
    let mut chars = String::new();
    for char in value.chars() {
        let code = char as u32;
        if (0xAC00..=0xD7A3).contains(&code) {
            let offset = code - 0xAC00;
            let lead = (offset / 588) as usize;
            let vowel = ((offset % 588) / 28) as usize;
            let tail = (offset % 28) as usize;
            chars.push(CHO[lead]);
            chars.push(JUNG[vowel]);
            if tail != 0 {
                chars.push(JONG[tail]);
            }
        } else {
            chars.push(char);
        }
    }
    chars
}

fn compose_hangul_run(chars: &[char]) -> String {
    let mut out = String::new();
    let mut lead = None;
    let mut vowel = None;
    let mut tail = None;

    fn emit(
        out: &mut String,
        lead: &mut Option<char>,
        vowel: &mut Option<char>,
        tail: &mut Option<char>,
    ) {
        match (*lead, *vowel) {
            (Some(l), Some(v)) => {
                if let (Some(l_index), Some(v_index)) = (cho_index(l), jung_index(v)) {
                    let code = 0xAC00
                        + ((l_index * 21 + v_index) * 28 + tail.and_then(jong_index).unwrap_or(0))
                            as u32;
                    if let Some(char) = char::from_u32(code) {
                        out.push(char);
                    }
                } else {
                    for part in [*lead, *vowel, *tail].into_iter().flatten() {
                        out.push(part);
                    }
                }
            }
            _ => {
                for part in [*lead, *vowel, *tail].into_iter().flatten() {
                    out.push(part);
                }
            }
        }
        *lead = None;
        *vowel = None;
        *tail = None;
    }

    for &char in chars {
        if jung_index(char).is_some() {
            if lead.is_none() {
                out.push(char);
            } else if vowel.is_none() {
                vowel = Some(char);
            } else if tail.is_none() {
                if let Some(combined) = combine_jung(vowel.unwrap(), char) {
                    vowel = Some(combined);
                } else {
                    emit(&mut out, &mut lead, &mut vowel, &mut tail);
                    out.push(char);
                }
            } else if let Some((first, second)) = split_jong(tail.unwrap()) {
                tail = Some(first);
                emit(&mut out, &mut lead, &mut vowel, &mut tail);
                lead = Some(second);
                vowel = Some(char);
            } else {
                let next_lead = tail;
                tail = None;
                emit(&mut out, &mut lead, &mut vowel, &mut tail);
                lead = next_lead;
                vowel = Some(char);
            }
        } else if lead.is_none() {
            lead = Some(char);
        } else if vowel.is_none() {
            if let Some(combined) = combine_cho(lead.unwrap(), char) {
                lead = Some(combined);
            } else {
                emit(&mut out, &mut lead, &mut vowel, &mut tail);
                lead = Some(char);
            }
        } else if tail.is_none() && jong_index(char).is_some() {
            tail = Some(char);
        } else if tail.is_some() {
            if let Some(combined) = combine_jong(tail.unwrap(), char) {
                tail = Some(combined);
            } else {
                emit(&mut out, &mut lead, &mut vowel, &mut tail);
                lead = Some(char);
            }
        } else {
            emit(&mut out, &mut lead, &mut vowel, &mut tail);
            lead = Some(char);
        }
    }
    emit(&mut out, &mut lead, &mut vowel, &mut tail);
    out
}

fn is_hangul_jamo(char: char) -> bool {
    cho_index(char).is_some() || jung_index(char).is_some() || jong_index(char).is_some()
}

fn cho_index(char: char) -> Option<usize> {
    CHO.iter().position(|value| *value == char)
}

fn jung_index(char: char) -> Option<usize> {
    JUNG.iter().position(|value| *value == char)
}

fn jong_index(char: char) -> Option<usize> {
    JONG.iter()
        .position(|value| *value == char)
        .filter(|index| *index != 0)
}

fn combine_cho(a: char, b: char) -> Option<char> {
    match (a, b) {
        ('ㄱ', 'ㄱ') => Some('ㄲ'),
        ('ㄷ', 'ㄷ') => Some('ㄸ'),
        ('ㅂ', 'ㅂ') => Some('ㅃ'),
        ('ㅅ', 'ㅅ') => Some('ㅆ'),
        ('ㅈ', 'ㅈ') => Some('ㅉ'),
        _ => None,
    }
}

fn combine_jung(a: char, b: char) -> Option<char> {
    match (a, b) {
        ('ㅗ', 'ㅏ') => Some('ㅘ'),
        ('ㅗ', 'ㅐ') => Some('ㅙ'),
        ('ㅗ', 'ㅣ') => Some('ㅚ'),
        ('ㅜ', 'ㅓ') => Some('ㅝ'),
        ('ㅜ', 'ㅔ') => Some('ㅞ'),
        ('ㅜ', 'ㅣ') => Some('ㅟ'),
        ('ㅡ', 'ㅣ') => Some('ㅢ'),
        _ => None,
    }
}

fn combine_jong(a: char, b: char) -> Option<char> {
    match (a, b) {
        ('ㄱ', 'ㅅ') => Some('ㄳ'),
        ('ㄴ', 'ㅈ') => Some('ㄵ'),
        ('ㄴ', 'ㅎ') => Some('ㄶ'),
        ('ㄹ', 'ㄱ') => Some('ㄺ'),
        ('ㄹ', 'ㅁ') => Some('ㄻ'),
        ('ㄹ', 'ㅂ') => Some('ㄼ'),
        ('ㄹ', 'ㅅ') => Some('ㄽ'),
        ('ㄹ', 'ㅌ') => Some('ㄾ'),
        ('ㄹ', 'ㅍ') => Some('ㄿ'),
        ('ㄹ', 'ㅎ') => Some('ㅀ'),
        ('ㅂ', 'ㅅ') => Some('ㅄ'),
        _ => None,
    }
}

fn split_jong(char: char) -> Option<(char, char)> {
    match char {
        'ㄳ' => Some(('ㄱ', 'ㅅ')),
        'ㄵ' => Some(('ㄴ', 'ㅈ')),
        'ㄶ' => Some(('ㄴ', 'ㅎ')),
        'ㄺ' => Some(('ㄹ', 'ㄱ')),
        'ㄻ' => Some(('ㄹ', 'ㅁ')),
        'ㄼ' => Some(('ㄹ', 'ㅂ')),
        'ㄽ' => Some(('ㄹ', 'ㅅ')),
        'ㄾ' => Some(('ㄹ', 'ㅌ')),
        'ㄿ' => Some(('ㄹ', 'ㅍ')),
        'ㅀ' => Some(('ㄹ', 'ㅎ')),
        'ㅄ' => Some(('ㅂ', 'ㅅ')),
        _ => None,
    }
}
