use clap::Parser;
use regex::Regex;
use std::io::Write;
use std::{fs::File, path::Path};

const LIST_LINE_PATTERN: &str = r"^\s*-(?: |$)";
const IGNORE_LINE_PATTERN: &str = r"^\s*(?:collapsed|id|title)::";

fn replace_video(line: &str) -> String {
    let re = Regex::new(r"\{\{video (.+?)\}\}").unwrap();

    re.replace_all(line, "![]($1)").to_string()
}

fn remove_image_size(line: &str) -> String {
    let re = Regex::new(r"!\[(.*?)\][(](.+?)[)]\{:height\s*\d+,\s*:width\s*\d+\}").unwrap();

    re.replace_all(line, "![$1]($2)").to_string()
}

fn remove_bullets(line: &str) -> String {
    let re = Regex::new(LIST_LINE_PATTERN).unwrap();

    re.replace(line, "").to_string()
}

fn dedent(line: &str, indent: usize) -> String {
    let re = Regex::new(&format!(r"^\s{{0,{}}}", indent * 2)).unwrap();

    re.replace(line, "").to_string()
}

fn process_line(
    line: &str,
    is_list: bool,
    indent: usize,
    prev_indent: usize,
    is_code_block: &mut bool,
) -> String {
    let mut line = replace_video(line);
    line = remove_image_size(&line);
    line = remove_bullets(&line);

    if line.starts_with("```") {
        *is_code_block = !*is_code_block;
    }

    if *is_code_block {
        return dedent(&line, indent);
    }

    line = dedent(&line, 1);

    let is_quoted = line.starts_with(">");

    if indent == 0 {
        if prev_indent == 0 && is_quoted {
            line = format!("{}{}", "  ".repeat(indent), line);
        } else if prev_indent != 0 {
            line = format!("\n{}", line);
        }
    } else if is_list {
        line = format!("{}- {}", "  ".repeat(indent), line);
    } else {
        line = format!("{}{}", "  ".repeat(indent), line);
    }

    line
}

fn process_input(input: &str, writer: &mut std::io::BufWriter<File>) {
    let ignore_regex = Regex::new(IGNORE_LINE_PATTERN).unwrap();
    let list_regex = Regex::new(LIST_LINE_PATTERN).unwrap();
    let mut prev_indent = 0;
    let mut is_code_block = false;

    for line in input.lines() {
        if ignore_regex.is_match(line) {
            continue;
        }

        let mut indent = line
            .replace('\t', "  ")
            .chars()
            .take_while(|&c| c == ' ')
            .count()
            / 2;

        let is_list = list_regex.is_match(line);

        if !is_list && indent > 0 {
            indent -= 1;
        }

        let processed_line = process_line(line, is_list, indent, prev_indent, &mut is_code_block);

        writer
            .write_fmt(format_args!("{}\n", processed_line))
            .unwrap();

        prev_indent = indent;
    }
}

#[derive(Parser, Debug)]
#[command(version)]
struct Args {
    #[arg(short, long)]
    input: String,

    #[arg(short, long)]
    output: String,
}

fn main() {
    let args = Args::parse();

    if !Path::new(&args.input).join("logseq").exists() {
        eprintln!("The logseq directory does not exist.");
        std::process::exit(1);
    }

    let input_assets = Path::new(&args.input).join("assets");
    let output_path = Path::new(&args.output);
    let output_assets = output_path.join("assets");

    std::fs::create_dir_all(&output_assets).unwrap();

    for entry in input_assets.read_dir().unwrap() {
        let entry = entry.unwrap();
        let path = entry.path();

        if path.is_file() {
            let output_path = output_assets.join(path.file_name().unwrap());
            std::fs::copy(&path, &output_path).unwrap();
        }
    }

    let input_journals = Path::new(&args.input).join("journals");
    let input_pages = Path::new(&args.input).join("pages");
    let input_journal_files = input_journals.read_dir().unwrap();
    let input_pages_files = input_pages.read_dir().unwrap();

    for entry in input_journal_files {
        let entry = entry.unwrap();
        let path = entry.path();

        if !path.is_file() {
            continue;
        }

        let name = path
            .file_name()
            .unwrap()
            .to_str()
            .unwrap()
            .replace("_", "-");

        let output_file = File::create(output_path.join(name)).unwrap();
        let mut writer = std::io::BufWriter::new(output_file);
        let input = std::fs::read_to_string(&path).unwrap();
        process_input(&input, &mut writer);
    }

    for entry in input_pages_files {
        let entry = entry.unwrap();
        let path = entry.path();

        if path.is_file() {
            let input = std::fs::read_to_string(&path).unwrap();
            let name = path.file_name().unwrap();
            let output_file = File::create(output_path.join(name)).unwrap();
            let mut writer = std::io::BufWriter::new(output_file);
            process_input(&input, &mut writer);
        }
    }
}