use std::{
    fs, io,
    path::{Path, PathBuf},
    process::ExitCode,
};

use clap::Parser;

fn list<P: AsRef<Path>>(path: P, depth: usize, last_mask: u64) -> io::Result<()> {
    fn indent(depth: usize, mask: u64) {
        for i in 0..depth + 1 {
            if i < depth {
                if (1 << i) & mask == 0 {
                    print!("│ ");
                } else {
                    print!("  ");
                }
            } else if (1 << i) & mask == 0 {
                print!("├─");
            } else {
                print!("└─");
            }
        }
    }

    let mut entries = vec![];
    let dir = fs::read_dir(path)?;
    for entry in dir {
        let Ok((ty, entry)) = entry.and_then(|e| Ok((e.file_type()?, e))) else {
            entries.push(None);
            continue;
        };

        let name = entry.file_name().into_string().unwrap();

        if name == "." || name == ".." {
            continue;
        }

        entries.push(Some((name, entry.path(), ty.is_dir())));
    }

    entries.sort_by(|a, b| {
        let (Some((a, _, _)), Some((b, _, _))) = (a.as_ref(), b.as_ref()) else {
            return Ord::cmp(&a.is_none(), &b.is_none());
        };

        Ord::cmp(a, b)
    });

    let len = entries.len();
    for (i, entry) in entries.into_iter().enumerate() {
        let last_bit = ((i == len - 1) as u64) << depth;
        indent(depth, last_mask | last_bit);

        let Some((name, path, dir)) = entry else {
            println!("<error>");
            continue;
        };

        println!("{name}");

        if dir {
            list(path, depth + 1, last_mask | last_bit).ok();
        }
    }

    Ok(())
}

#[derive(Debug, Parser)]
struct Args {
    path: Option<PathBuf>,
}

fn run<P: AsRef<Path>>(path: P) -> io::Result<()> {
    list(path, 0, 0)
}

fn main() -> ExitCode {
    let args = Args::parse();
    let path = args.path.unwrap_or_else(|| PathBuf::from("."));
    match run(&path) {
        Ok(()) => ExitCode::SUCCESS,
        Err(error) => {
            eprintln!("{}: {}", path.display(), error);
            ExitCode::FAILURE
        }
    }
}
