use std::{io, path::Path};

type OtherError = Box<dyn std::error::Error>;
type OtherResult<T> = Result<T, OtherError>;

pub enum ReadlineResult {
    Line(String),
    Eof,
    Interrupt,
    Io(std::io::Error),
    Other(OtherError),
}

#[allow(unused)]
mod basic_readline {
    use super::*;

    pub trait Helper {}
    impl<T> Helper for T {}

    pub struct Readline<H: Helper> {
        helper: H,
    }

    impl<H: Helper> Readline<H> {
        pub fn new(helper: H) -> Self {
            Readline { helper }
        }

        pub fn load_history(&mut self, _path: &Path) -> OtherResult<()> {
            Ok(())
        }

        pub fn save_history(&mut self, _path: &Path) -> OtherResult<()> {
            Ok(())
        }

        pub fn add_history_entry(&mut self, _entry: &str) -> OtherResult<()> {
            Ok(())
        }

        pub fn readline(&mut self, prompt: &str) -> ReadlineResult {
            use std::io::prelude::*;
            print!("{prompt}");
            if let Err(e) = io::stdout().flush() {
                return ReadlineResult::Io(e);
            }

            let next_line = io::stdin().lock().lines().next();
            match next_line {
                Some(Ok(line)) => ReadlineResult::Line(line),
                None => ReadlineResult::Eof,
                Some(Err(e)) if e.kind() == io::ErrorKind::Interrupted => ReadlineResult::Interrupt,
                Some(Err(e)) => ReadlineResult::Io(e),
            }
        }
    }
}

#[cfg(not(target_arch = "wasm32"))]
mod rustyline_readline {
    use super::*;

    pub trait Helper: rustyline::Helper {}
    impl<T: rustyline::Helper> Helper for T {}

    /// Readline: the REPL
    pub struct Readline<H: Helper> {
        repl: rustyline::Editor<H>,
    }

    impl<H: Helper> Readline<H> {
        pub fn new(helper: H) -> Self {
            use rustyline::*;
            let mut repl = Editor::with_config(
                Config::builder()
                    .completion_type(CompletionType::List)
                    .tab_stop(8)
                    .bracketed_paste(false) // multi-line paste
                    .build(),
            )
            .expect("failed to initialize line editor");
            repl.set_helper(Some(helper));
            Readline { repl }
        }

        pub fn load_history(&mut self, path: &Path) -> OtherResult<()> {
            self.repl.load_history(path)?;
            Ok(())
        }

        pub fn save_history(&mut self, path: &Path) -> OtherResult<()> {
            if !path.exists() {
                if let Some(parent) = path.parent() {
                    std::fs::create_dir_all(parent)?;
                }
            }
            self.repl.save_history(path)?;
            Ok(())
        }

        pub fn add_history_entry(&mut self, entry: &str) -> OtherResult<()> {
            self.repl.add_history_entry(entry);
            Ok(())
        }

        pub fn readline(&mut self, prompt: &str) -> ReadlineResult {
            use rustyline::error::ReadlineError;
            loop {
                break match self.repl.readline(prompt) {
                    Ok(line) => ReadlineResult::Line(line),
                    Err(ReadlineError::Interrupted) => ReadlineResult::Interrupt,
                    Err(ReadlineError::Eof) => ReadlineResult::Eof,
                    Err(ReadlineError::Io(e)) => ReadlineResult::Io(e),
                    Err(ReadlineError::WindowResized) => continue,
                    Err(e) => ReadlineResult::Other(e.into()),
                };
            }
        }
    }
}

#[cfg(target_arch = "wasm32")]
use basic_readline as readline_inner;
#[cfg(not(target_arch = "wasm32"))]
use rustyline_readline as readline_inner;

pub use readline_inner::Helper;

pub struct Readline<H: Helper>(readline_inner::Readline<H>);

impl<H: Helper> Readline<H> {
    pub fn new(helper: H) -> Self {
        Readline(readline_inner::Readline::new(helper))
    }
    pub fn load_history(&mut self, path: &Path) -> OtherResult<()> {
        self.0.load_history(path)
    }
    pub fn save_history(&mut self, path: &Path) -> OtherResult<()> {
        self.0.save_history(path)
    }
    pub fn add_history_entry(&mut self, entry: &str) -> OtherResult<()> {
        self.0.add_history_entry(entry)
    }
    pub fn readline(&mut self, prompt: &str) -> ReadlineResult {
        self.0.readline(prompt)
    }
}
