Toy implementation of the gemini protocol
use std::{
    fmt::Display,
    io::{stdout, Write},
};

use ansi_term::{Colour, Style};
use clap::Parser;
use url::Url;

use crate::{
    core::run,
    danger,
    media::{Gemini, Line, Media, Preformat},
    network::TcpNetwork,
    Ui,
};

#[derive(Parser)]
pub struct App {
    #[arg(short, long)]
    pub test_mode: bool,
    pub url: Option<String>,
}

// TODO proper error handling
pub enum Error {}

impl App {
    pub fn run(self) -> Result<(), Error> {
        let mut cli = Cli {};
        if self.test_mode {
            test_run(&mut cli);
            Ok(())
        } else {
            let url = self.url.unwrap_or_else(|| {
                "gemini://gemini.circumlunar.space/docs/specification.gmi".into()
            });
            let url: Url = url.parse().expect("Not a valid URL");
            let mut net = TcpNetwork::new(danger::Naive);
            run(&mut net, &mut cli, url);
            Ok(())
        }
    }
}

fn test_run(cli: &mut Cli) {
    let text: Gemini = "plain line
# heading1
## heading 2
### heading 3
> quote 1
> quote 2
> quote 3
line
line 2
line 3
* list 1
* list 2
* list 3
=> gemini://url?query description
```rs
some rust code here
match true {
true
=> false,
false
=> true,
}
```
# heading
> quote
line
* list
=> gemini://url description
ordinary line"
        .into();
    let secret = cli.read_secret("tell me your secrets").unwrap();
    cli.warn(format_args!("your secret was: {secret}"));
    let overt = cli
        .read("tell me something that isn't secret, since I can't be trusted with secrets")
        .unwrap();
    cli.warn(format_args!("your response was: {overt}"));
    cli.render_gemini(text);
}

pub struct Cli {}
impl Cli {
    fn render_gemini(&self, g: Gemini) {
        // println!("{:#?}", g)
        let Gemini { lines } = g;
        for line in lines {
            match line {
                Line::Heading { level, title } => {
                    let title = title.as_ref();
                    let char = match level {
                        crate::media::HeadingLevel::L1 => '=',
                        crate::media::HeadingLevel::L2 => '-',
                        crate::media::HeadingLevel::L3 => ' ',
                    };
                    let under = core::iter::repeat(char)
                        .take(title.len())
                        .collect::<String>();
                    let style = ansi_term::Style::new().bold();
                    println!();
                    println!("{}", style.paint(&under));
                    println!("{}", style.paint(title));
                    println!("{}", style.paint(&under));
                }
                Line::Link { url, description } => {
                    let blue = Colour::Cyan;
                    print!("{}: {}", '🔗', blue.paint(url));
                    if let Some(description) = description {
                        let italic = ansi_term::Style::new().italic();
                        println!(" | {}", italic.paint(description));
                    }
                }
                Line::Text(t) => println!("{}", t),
                Line::Preformatted(p) => {
                    let Preformat { alt, lines } = p;
                    let dim = Style::new().dimmed().italic();
                    let bg = Style::new().on(Colour::Black);
                    println!("{}", dim.paint(alt));
                    for line in lines {
                        println!("{}", bg.paint(line));
                    }
                    println!();
                }
                Line::ListItem(li) => {
                    println!("	•  {}", li)
                }
                Line::Quote(q) => {
                    let italic = Style::new().italic();
                    println!("	| {}", italic.paint(q));
                }
            }
        }
    }
}
impl Ui for Cli {
    fn show(&mut self, content: Media) {
        match content {
            Media::Gemini(g) => self.render_gemini(g),
            Media::Text(s) => println!("{s}"),
        }
    }

    fn read(&mut self, prompt: impl Display) -> Option<String> {
        print!("{prompt}: ");
        stdout().flush().unwrap();
        Some(
            std::io::stdin()
                .lines()
                .next()?
                .expect("Failed to read input"),
        )
    }

    fn read_secret(&mut self, prompt: impl Display) -> Option<String> {
        let dim = ansi_term::Style::new().dimmed();
        print!("{prompt}: ");
        let hidden = "(input hidden)";
        let len = hidden.len() as u32;
        let back = ansi_control_codes::control_sequences::CUB(Some(len));
        print!("{}{back}", dim.paint(hidden));
        stdout().flush().unwrap();
        match rpassword::read_password() {
            Ok(v) => Some(v),
            Err(e) => match e.kind() {
                std::io::ErrorKind::UnexpectedEof => None,
                _ => panic!("Failed to read input"),
            },
        }
    }

    fn warn<D: std::fmt::Display>(&mut self, warning: D) {
        let red = Colour::Red;
        println!("{}: {}", red.paint("Warning"), warning)
    }
}