//! Word text object commands.
//!
//! Implements text objects: `iw`, `aw`, `iW`, `aW`.
//!
//! These commands calculate the range of a word under/around the cursor
//! for use with operators like delete, yank, and change.
//!
//! # Mode-Aware Behavior (Epic #465)
//!
//! Text objects behave differently based on the current mode:
//! - **Operator-pending mode** (d, y, c): Store range for operator consumption
//! - **Visual mode** (v, V, Ctrl-V): Update selection to cover the text object

use {
    reovim_driver_command::{
        ArgKind, ArgSpec, Command, CommandContext, CommandHandler, CommandResult,
    },
    reovim_driver_session::{
        OperatorPendingState, SessionRuntime, TextObjRange,
        api::{ExtensionApi, ModeApi, Selection, SelectionMode},
    },
    reovim_kernel::api::v1::{CommandId, Position, TextObject, TextObjectEngine, WordBoundary},
};

use crate::ids;

// =============================================================================
// Helper functions
// =============================================================================

/// Check if the current mode is a visual mode.
fn is_visual_mode(runtime: &SessionRuntime<'_>) -> bool {
    let mode = runtime.current_mode();
    let mode_name = mode.name();
    mode_name == "visual" || mode_name == "visual-line" || mode_name == "visual-block"
}

/// Get the selection mode for the current visual mode.
fn visual_selection_mode(runtime: &SessionRuntime<'_>) -> SelectionMode {
    let mode = runtime.current_mode();
    match mode.name() {
        "visual-line" => SelectionMode::Line,
        "visual-block" => SelectionMode::Block,
        _ => SelectionMode::Character,
    }
}

/// Execute a word text object and store the range for operator consumption,
/// or update the selection if in visual mode.
#[cfg_attr(coverage_nightly, coverage(off))]
fn execute_word_textobj(
    runtime: &mut SessionRuntime<'_>,
    args: &CommandContext,
    text_object: TextObject,
) -> CommandResult {
    let Some(buffer_id) = args.buffer_id() else {
        return CommandResult::error("No active buffer");
    };

    let count = args.count().unwrap_or(1);

    // Get cursor from per-client Window (#471)
    let Some(window) = runtime.windows().active() else {
        return CommandResult::error("No active window");
    };
    let pos = Position::new(window.cursor.line, window.cursor.column);

    // Calculate text object range using with_buffer_read callback
    let range_result = runtime.with_buffer_read(buffer_id, |buffer| {
        TextObjectEngine::range(buffer, pos, text_object, count)
    });

    let Some(range_result) = range_result else {
        return CommandResult::error("Buffer not found");
    };

    let Some((start, end)) = range_result else {
        return CommandResult::Success; // No-op if no range found
    };

    // Kernel returns inclusive end position, convert to exclusive for Range
    // end.column points to the last character, we need end.column + 1
    let end_exclusive = Position::new(end.line, end.column + 1);

    // Check if we're in visual mode
    if is_visual_mode(runtime) {
        // Visual mode: update the selection to cover the text object
        let sel_mode = visual_selection_mode(runtime);
        if let Some(window) = runtime.windows_mut().active_mut() {
            window.selection = Some(Selection::new(start, end_exclusive, sel_mode));
            // Move cursor to end of selection
            window.cursor = end.into();
        }
    } else {
        // Operator-pending mode: store range for operator consumption (Epic #465)
        // The operator resolver's on_command_complete will take() this range
        let state = runtime.ext_mut::<OperatorPendingState>();
        state.set_textobj_range(TextObjRange::characterwise(start, end_exclusive));
    }

    CommandResult::Success
}

// =============================================================================
// Inner Word (iw)
// =============================================================================

/// Inner word text object.
///
/// Selects the word under the cursor without surrounding whitespace.
#[derive(Debug, Clone, Copy, Default)]
pub struct InnerWord;

impl Command for InnerWord {
    fn id(&self) -> CommandId {
        ids::INNER_WORD
    }

    fn description(&self) -> &'static str {
        "Inner word text object"
    }

    fn args(&self) -> Vec<ArgSpec> {
        vec![ArgSpec::optional(
            "count",
            ArgKind::Count,
            "Number of words",
        )]
    }
}

impl CommandHandler for InnerWord {
    fn execute(&self, runtime: &mut SessionRuntime<'_>, args: &CommandContext) -> CommandResult {
        execute_word_textobj(runtime, args, TextObject::InnerWord(WordBoundary::Word))
    }
}

// =============================================================================
// A Word (aw)
// =============================================================================

/// A word text object.
///
/// Selects the word under the cursor including trailing (or leading) whitespace.
#[derive(Debug, Clone, Copy, Default)]
pub struct AWord;

impl Command for AWord {
    fn id(&self) -> CommandId {
        ids::AROUND_WORD
    }

    fn description(&self) -> &'static str {
        "A word text object (including whitespace)"
    }

    fn args(&self) -> Vec<ArgSpec> {
        vec![ArgSpec::optional(
            "count",
            ArgKind::Count,
            "Number of words",
        )]
    }
}

impl CommandHandler for AWord {
    fn execute(&self, runtime: &mut SessionRuntime<'_>, args: &CommandContext) -> CommandResult {
        execute_word_textobj(runtime, args, TextObject::AWord(WordBoundary::Word))
    }
}

// =============================================================================
// Inner WORD (iW)
// =============================================================================

/// Inner WORD text object.
///
/// Selects the WORD (whitespace-delimited) under the cursor.
#[derive(Debug, Clone, Copy, Default)]
pub struct InnerWordBig;

impl Command for InnerWordBig {
    fn id(&self) -> CommandId {
        ids::INNER_WORD_BIG
    }

    fn description(&self) -> &'static str {
        "Inner WORD text object"
    }

    fn args(&self) -> Vec<ArgSpec> {
        vec![ArgSpec::optional(
            "count",
            ArgKind::Count,
            "Number of WORDs",
        )]
    }
}

impl CommandHandler for InnerWordBig {
    fn execute(&self, runtime: &mut SessionRuntime<'_>, args: &CommandContext) -> CommandResult {
        execute_word_textobj(runtime, args, TextObject::InnerWord(WordBoundary::BigWord))
    }
}

// =============================================================================
// A WORD (aW)
// =============================================================================

/// A WORD text object.
///
/// Selects the WORD (whitespace-delimited) including surrounding whitespace.
#[derive(Debug, Clone, Copy, Default)]
pub struct AWordBig;

impl Command for AWordBig {
    fn id(&self) -> CommandId {
        ids::AROUND_WORD_BIG
    }

    fn description(&self) -> &'static str {
        "A WORD text object (including whitespace)"
    }

    fn args(&self) -> Vec<ArgSpec> {
        vec![ArgSpec::optional(
            "count",
            ArgKind::Count,
            "Number of WORDs",
        )]
    }
}

impl CommandHandler for AWordBig {
    fn execute(&self, runtime: &mut SessionRuntime<'_>, args: &CommandContext) -> CommandResult {
        execute_word_textobj(runtime, args, TextObject::AWord(WordBoundary::BigWord))
    }
}

// =============================================================================
// Command Registration
// =============================================================================

/// Get all word text object commands as boxed trait objects.
#[must_use]
pub fn all_commands() -> Vec<Box<dyn CommandHandler>> {
    vec![
        Box::new(InnerWord),
        Box::new(AWord),
        Box::new(InnerWordBig),
        Box::new(AWordBig),
    ]
}

// =============================================================================
// Tests
// =============================================================================
