use std::io::{IsTerminal, Write};

use crate::{
    InputBackend, ShellError,
    input_backend::{InteractivePrompt, ReadResult, parse_result_is_complete},
};

/// Represents a minimal shell input backend, capable of taking commands from standard input.
#[derive(Default)]
pub struct MinimalInputBackend {
    parser_options: brush_parser::ParserOptions,
}

impl MinimalInputBackend {
    /// Creates a minimal backend using the shell's active parser options.
    pub const fn new(parser_options: brush_parser::ParserOptions) -> Self {
        Self { parser_options }
    }
}

impl InputBackend for MinimalInputBackend {
    fn read_line(
        &mut self,
        _shell_ref: &crate::ShellRef<impl brush_core::ShellExtensions>,
        prompt: InteractivePrompt,
    ) -> Result<ReadResult, ShellError> {
        self.display_prompt(&prompt)?;

        let mut result = String::new();
        loop {
            match Self::read_input_line()? {
                ReadResult::Input(s) | ReadResult::BoundCommand(s) => {
                    result.push_str(&s);
                    if self.is_complete_input(&result) {
                        break;
                    }
                }
                ReadResult::Eof => {
                    if result.is_empty() {
                        return Ok(ReadResult::Eof);
                    }
                    // Return the partial command so the normal parser path can
                    // report the incomplete construct instead of dropping it.
                    break;
                }
                ReadResult::Interrupted => return Ok(ReadResult::Interrupted),
            }
        }

        if result.is_empty() {
            Ok(ReadResult::Eof)
        } else {
            Ok(ReadResult::Input(result))
        }
    }
}

impl MinimalInputBackend {
    fn is_complete_input(&self, input: &str) -> bool {
        let mut parser = brush_parser::Parser::new(input.as_bytes(), &self.parser_options);
        parse_result_is_complete(&parser.parse_program())
    }

    #[expect(clippy::unused_self)]
    fn should_display_prompt(&self) -> bool {
        std::io::stdin().is_terminal()
    }

    fn display_prompt(&self, prompt: &InteractivePrompt) -> Result<(), ShellError> {
        if self.should_display_prompt() {
            eprint!("{}", prompt.prompt);
            std::io::stderr().flush()?;
        }

        Ok(())
    }

    fn read_input_line() -> Result<ReadResult, ShellError> {
        let mut input = String::new();
        let bytes_read = std::io::stdin()
            .read_line(&mut input)
            .map_err(ShellError::InputError)?;

        if bytes_read == 0 {
            Ok(ReadResult::Eof)
        } else {
            Ok(ReadResult::Input(input))
        }
    }
}
