use nu_ansi_term::{Color, Style};
use std::borrow::BorrowMut;

use crate::{completion, refs};

pub(crate) struct ReedlineCompleter<SE: brush_core::ShellExtensions> {
    pub shell: refs::ShellRef<SE>,
}

impl<SE: brush_core::ShellExtensions> reedline::Completer for ReedlineCompleter<SE> {
    fn complete(&mut self, line: &str, pos: usize) -> Vec<reedline::Suggestion> {
        tokio::task::block_in_place(|| {
            tokio::runtime::Handle::current().block_on(self.complete_async(line, pos))
        })
    }
}

impl<SE: brush_core::ShellExtensions> ReedlineCompleter<SE> {
    async fn complete_async(&self, line: &str, pos: usize) -> Vec<reedline::Suggestion> {
        let mut shell_guard = self.shell.lock().await;
        let shell = shell_guard.borrow_mut().as_mut();
        let completions = completion::complete_async(shell, line, pos).await;

        // We're done with the shell, so drop it eagerly.
        drop(shell_guard);

        let insertion_index = completions.insertion_index;
        let delete_count = completions.delete_count;
        let options = completions.options;

        completions
            .candidates
            .into_iter()
            .map(|candidate| {
                Self::to_suggestion(line, candidate, insertion_index, delete_count, &options)
            })
            .collect()
    }

    #[allow(
        clippy::string_slice,
        reason = "all indices + counts are expected to be at char boundaries"
    )]
    fn to_suggestion(
        line: &str,
        mut candidate: String,
        mut insertion_index: usize,
        mut delete_count: usize,
        options: &brush_core::completion::ProcessingOptions,
    ) -> reedline::Suggestion {
        let mut style = Style::new();

        // Special handling for filename completions.
        if options.treat_as_filenames {
            if brush_core::sys::fs::ends_with_path_separator(&candidate) {
                style = style.fg(Color::Green);
            }

            if insertion_index + delete_count <= line.len() {
                let removed = &line[insertion_index..insertion_index + delete_count];
                if let Some(last_sep_index) = brush_core::sys::fs::rfind_path_separator(removed) {
                    if candidate.starts_with(removed) {
                        candidate = candidate.split_off(last_sep_index + 1);
                        insertion_index += last_sep_index + 1;
                        delete_count -= last_sep_index + 1;
                    }
                }
            }
        }

        // See if there's whitespace at the end.
        let append_whitespace = candidate.ends_with(' ');
        if append_whitespace {
            candidate.pop();
        }

        reedline::Suggestion {
            value: candidate,
            description: None,
            style: Some(style),
            extra: None,
            span: reedline::Span {
                start: insertion_index,
                end: insertion_index + delete_count,
            },
            match_indices: None,
            display_override: None,
            append_whitespace,
        }
    }
}
