use ansi_term::Color;
use std::{borrow::Cow, collections::HashMap, fs::create_dir_all};

use directories::ProjectDirs;
use rustyline::{
    completion::Completer, error::ReadlineError, highlight::Highlighter, hint::Hinter,
    validate::Validator, Config, Editor, Helper,
};

use crate::{
    context::{Context, VariableValue},
    parser::parse,
    scope::Scope,
    tokenizer::tokenize,
};

struct OtomeCompleter {
    definitions: HashMap<String, Vec<String>>,
}

impl Helper for OtomeCompleter {}
impl Highlighter for OtomeCompleter {
    fn highlight<'l>(&self, line: &'l str, pos: usize) -> std::borrow::Cow<'l, str> {
        let _ = pos;
        std::borrow::Cow::Borrowed(line)
    }

    fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
        &'s self,
        prompt: &'p str,
        _default: bool,
    ) -> std::borrow::Cow<'b, str> {
        Cow::Owned(Color::Purple.paint(prompt).to_string())
    }

    fn highlight_hint<'h>(&self, hint: &'h str) -> std::borrow::Cow<'h, str> {
        Cow::Owned(Color::Purple.paint(hint).to_string())
    }

    fn highlight_candidate<'c>(
        &self,
        candidate: &'c str,
        _completion: rustyline::CompletionType,
    ) -> std::borrow::Cow<'c, str> {
        Cow::Owned(Color::Purple.paint(candidate).to_string())
    }

    fn highlight_char(&self, line: &str, pos: usize) -> bool {
        let _ = (line, pos);
        false
    }
}
impl Hinter for OtomeCompleter {
    type Hint = String;

    fn hint(&self, line: &str, _pos: usize, _ctx: &rustyline::Context<'_>) -> Option<Self::Hint> {
        self.definitions.get(&line.to_string()).map(|e| {
            format!(
                " {}",
                e.iter()
                    .map(|v| format!("<{v}>"))
                    .collect::<Vec<String>>()
                    .join(" ")
            )
        })
    }
}
impl Validator for OtomeCompleter {
    fn validate(
        &self,
        ctx: &mut rustyline::validate::ValidationContext,
    ) -> rustyline::Result<rustyline::validate::ValidationResult> {
        let source = ctx.input();
        let commands = match tokenize(source) {
            Ok(tokens) => parse(tokens),
            Err(e) => Err(e),
        };
        match commands {
            Ok(_) => Ok(rustyline::validate::ValidationResult::Valid(None)),
            Err(e) => {
                if e.0.ends_with("end") {
                    Ok(rustyline::validate::ValidationResult::Incomplete)
                } else {
                    Ok(rustyline::validate::ValidationResult::Invalid(Some(e.0)))
                }
            }
        }
    }
}
impl Completer for OtomeCompleter {
    type Candidate = String;

    fn complete(
        &self,
        line: &str,
        pos: usize,
        _ctx: &rustyline::Context<'_>,
    ) -> rustyline::Result<(usize, Vec<Self::Candidate>)> {
        if let Some(word) = line[..pos].split(" ").last() {
            Ok((
                pos - word.len(),
                self.definitions
                    .iter()
                    .filter_map(|(n, _)| {
                        if n.starts_with(word) {
                            Some(n.clone())
                        } else {
                            None
                        }
                    })
                    .collect(),
            ))
        } else {
            Ok((pos, Vec::with_capacity(0)))
        }
    }
}

pub fn repl(context: &mut Context, scope: &mut Scope) {
    let config = Config::builder().history_ignore_space(true).build();
    let mut rl = Editor::<OtomeCompleter>::with_config(config).unwrap();
    let proj_dirs = ProjectDirs::from("com", "jummit", "otomescript").unwrap();
    let hist_file = proj_dirs.data_dir().join("history.txt");
    rl.load_history(&hist_file).ok();
    loop {
        rl.set_helper(Some(OtomeCompleter {
            definitions: scope
                .functions
                .clone()
                .into_iter()
                .map(|(a, b)| (a, b.args))
                .chain(
                    context
                        .builtins
                        .iter()
                        .map(|(name, builtin)| (name.clone(), builtin.args.clone())),
                )
                .collect(),
        }));
        let line = match rl.readline("> ") {
            Ok(line) => line,
            Err(ReadlineError::Interrupted) => {
                break;
            }
            Err(ReadlineError::Eof) => {
                break;
            }
            Err(err) => {
                println!("Error: {:?}", err);
                break;
            }
        };
        let commands = tokenize(&line).unwrap();
        scope.commands = parse(commands).unwrap().into();
        let the_res = context.evaluate(scope);
        rl.add_history_entry(line);
        let res = the_res.map_err(|e| (e, context.last_command.as_ref().unwrap().start));
        if context.exit {
            break;
        }
        match res {
            Ok(list) => {
                if !list.is_empty() {
                    println!(
                        "{}",
                        list.iter()
                            .map(|s| s.replace('\n', "\\n"))
                            .collect::<Vec<String>>()
                            .join(", ")
                    );
                }
                for var in context.get_variables(scope).iter() {
                    println!(
                        "{}: {}",
                        var.0,
                        match var.1 {
                            VariableValue::List(l) => {
                                l.join(", ")
                            }
                            VariableValue::Callable(_) => "<Callable>".to_string(),
                        }
                    )
                }
                if !scope.functions.is_empty() {
                    println!(
                        "Functions: {}",
                        scope
                            .functions
                            .iter()
                            .map(|(n, f)| format!("{}({})", n, f.args.join(" ")))
                            .collect::<Vec<String>>()
                            .join(", ")
                    )
                }
            }
            Err(e) => println!("{} at {}:{}", e.0, e.1 .0, e.1 .1,),
        }
    }
    create_dir_all(hist_file.parent().unwrap()).unwrap();
    rl.save_history(&hist_file).unwrap();
}