#include "lumacs/tui_editor.hpp"
#include "lumacs/editor_core.hpp"
#include "lumacs/lua_api.hpp"
#include "lumacs/mode_activator.hpp"
#include "lumacs/keybinding.hpp"
#include "lumacs/cursor_blink.hpp"
#include "lumacs/message_formatter.hpp"
#include <ncurses.h>
#include <memory>
#include <chrono>
#include <string>
#include <sstream>
#include <algorithm>
#include <spdlog/spdlog.h>

using namespace lumacs;

/// ncurses-based TUI frontend for Lumacs
class TuiEditor : public IEditorView {
public:
    TuiEditor() : core_(nullptr) {}

    ~TuiEditor() override {
        endwin(); // Cleanup ncurses
    }

    // IEditorView methods
    void init() override;
    void run() override;
    void handle_editor_event(EditorEvent event) override;
    void set_core(EditorCore* core) override;

private:
    EditorCore* core_ = nullptr; // Raw pointer to EditorCore, not owned
    std::unique_ptr<ModeActivator> mode_activator_;
    bool should_quit_ = false;
    std::string message_line_;
    int height_ = 0, width_ = 0;

    // Cursor blinking using shared controller
    CursorBlinkController cursor_blink_;
    
    // Viewport bounds calculation (avoids code duplication)
    struct ViewportBounds {
        int content_width;
        int content_height;
        int line_number_width;
        int minibuffer_lines;
    };

    ViewportBounds calculate_viewport_bounds() const {
        ViewportBounds bounds;
        bounds.minibuffer_lines = 1;  // Always reserve 1 line for minibuffer/message
        bounds.content_height = height_ - bounds.minibuffer_lines;
        bool show_line_numbers = core_->config().get<bool>("show_line_numbers", true);
        bounds.line_number_width = show_line_numbers ? core_->config().get<int>("line_number_width", 6) : 0;
        bounds.content_width = width_ - bounds.line_number_width;
        return bounds;
    }

    // Private helper method declarations
    std::string resolve_key(int ch);
    bool handle_input(int ch);
    void render();
    void render_layout_node(std::shared_ptr<LayoutNode> node, int x, int y, int width, int height);
    void render_window(std::shared_ptr<Window> window, int x, int y, int width, int height);
    void render_window_modeline(std::shared_ptr<Window> window, int x, int y, int width, bool is_active);
    void render_message_line();

    int get_attributes_for_face(const std::string& face_name);

    // Hardware cursor position tracking
    int hw_cursor_x_ = -1;
    int hw_cursor_y_ = -1;
};

// --- TuiEditor Public Method Definitions ---

void TuiEditor::init() {
    // Initialize ncurses
    initscr();
    cbreak();           // Disable line buffering
    noecho();           // Don't echo pressed keys
    keypad(stdscr, TRUE); // Enable special keys
    raw();              // Enable all control characters
    timeout(50);        // Set 50ms timeout for getch() to avoid blocking forever
    
    // Color support
    if (has_colors()) {
        start_color();
        use_default_colors();
    }
    
    // Get screen dimensions
    getmaxyx(stdscr, height_, width_);
    
    // Initialize theme colors for ncurses
    if (has_colors() && core_->theme_manager().active_theme()) {
        core_->theme_manager().active_theme()->initialize_ncurses_colors();
    }
    
    // Set initial viewport size
    auto bounds = calculate_viewport_bounds();
    core_->set_viewport_size(bounds.content_width, bounds.content_height);
    
    spdlog::debug("ncurses editor initialized: {}x{}", width_, height_);
}

void TuiEditor::run() {
    should_quit_ = false;
    
    // Initial render
    render();

    while (!should_quit_) {
        // Handle screen resize
        int new_height, new_width;
        getmaxyx(stdscr, new_height, new_width);
        if (new_height != height_ || new_width != width_) {
            height_ = new_height;
            width_ = new_width;
            auto bounds = calculate_viewport_bounds();
            core_->set_viewport_size(bounds.content_width, bounds.content_height);
            spdlog::debug("Screen resized to: {}x{}", width_, height_);
            spdlog::debug("Content area: {}x{}", bounds.content_width, bounds.content_height);
            cursor_blink_.notify_cursor_moved();  // Resize implies movement
            render();
        }

        // Get input (with timeout)
        int ch = getch();

        // Only process input and render if we got actual input (not timeout)
        if (ch != ERR) {
            cursor_blink_.notify_cursor_moved();
            handle_input(ch);
            render();
        } else {
            // No input (timeout) - update blink state
            bool needs_redraw = cursor_blink_.update();

            // Check and clear transient messages
            core_->check_and_clear_message();

            // Call Lua idle hooks (e.g., which-key)
            if (core_->lua_api()) {
                core_->lua_api()->call_idle_hooks();
            }

            // Check if echo area changed
            if (!core_->echo_area_lines().empty()) {
                needs_redraw = true;
            }

            if (needs_redraw) {
                render();
            }
        }
    }
}

void TuiEditor::handle_editor_event(EditorEvent event) {
    // Handle UI-specific events first
    if (event == EditorEvent::Quit) {
        should_quit_ = true;
        return;
    } else if (event == EditorEvent::Message) {
        message_line_ = core_->last_message();
        return;
    } else if (event == EditorEvent::CursorMoved) {
        cursor_blink_.notify_cursor_moved();
        render();
        return;
    } else if (event == EditorEvent::TransientMessageCleared) {
        render();
        return;
    }

    // Delegate mode activation events to ModeActivator
    if (mode_activator_) {
        auto quit_callback = [this]() { should_quit_ = true; };
        mode_activator_->handle_mode_event(event, quit_callback);
    }
}

void TuiEditor::set_core(EditorCore* core) {
    core_ = core;
    if (core_) {
        mode_activator_ = std::make_unique<ModeActivator>(*core_);
    }
}

// --- TuiEditor Private Helper Method Definitions ---

int TuiEditor::get_attributes_for_face(const std::string& face_name) {
    auto theme = core_->theme_manager().active_theme();
    if (theme) {
        return theme->get_face_attributes_ncurses(face_name);
    }
    return 0; // A_NORMAL
}

/// Convert ncurses key code to our key name format
std::string TuiEditor::resolve_key(int ch) {
    spdlog::trace("NCURSES INPUT: Raw key code: {} (0x{:x})", ch, ch);
    
    if (ch == 27) return "Escape";
    if (ch == '\n' || ch == '\r' || ch == KEY_ENTER) return "Return";
    if (ch == '\t') return "Tab";
    if (ch == KEY_BACKSPACE || ch == 127 || ch == '\b') return "Backspace";
    if (ch == KEY_UP) return "ArrowUp";
    if (ch == KEY_DOWN) return "ArrowDown";
    if (ch == KEY_LEFT) return "ArrowLeft";
    if (ch == KEY_RIGHT) return "ArrowRight";
    if (ch == KEY_HOME) return "Home";
    if (ch == KEY_END) return "End";
    if (ch == KEY_PPAGE) return "PageUp";
    if (ch == KEY_NPAGE) return "PageDown";
    if (ch == ' ') return "Space";
    
    // Control keys
    if (ch > 0 && ch < 32) {
        return "C-" + std::string(1, (char)('a' + ch - 1));
    }

    // Regular characters
    if (ch >= 32 && ch < 127) {
        return std::string(1, (char)ch);
    }

    return "";
}

bool TuiEditor::handle_input(int ch) {
    std::string key_name;

    // Check for Meta key sequence (Escape + Key)
    if (ch == 27) {
        // Set non-blocking read to check for immediate next key
        timeout(0);
        int next_ch = getch();
        timeout(50); // Restore timeout

        if (next_ch != ERR) {
            // Ensure next_ch is a valid printable char or special key we can map
            // For now, handle simple M-char sequences
            std::string next_key = resolve_key(next_ch);
            if (!next_key.empty() && next_key.length() == 1) { // Simple char
                 key_name = "M-" + next_key;
            } else if (!next_key.empty()) {
                 // Special key with Meta, e.g. M-ArrowUp? 
                 // resolve_key returns "ArrowUp", so we get "M-ArrowUp". This is valid.
                 key_name = "M-" + next_key;
            } else {
                // Couldn't resolve next key, just treat as Escape then ignore next?
                // Or treat as Escape sequence.
                key_name = "Escape";
                // We effectively consumed next_ch and ignored it. 
                // Better might be to ungetch, but ncurses ungetch is tricky.
                // Let's assume if resolve_key fails it was garbage.
            }
        } else {
            key_name = "Escape";
        }
    } else {
        key_name = resolve_key(ch);
    }
    
    if (key_name.empty()) {
        spdlog::trace("Empty key name, ignoring input");
        return false;
    }

    spdlog::trace("Resolved key: {}", key_name);

    // Handle Minibuffer Input Logic first
    if (core_->minibuffer_manager().is_active()) {
        return core_->minibuffer_manager().handle_key_event(key_name);
    }

    // Normal mode processing (pass to keybinding system)
    KeyProcessingResult result = core_->keybinding_manager().process_key(Key::parse(key_name));

    if (result.command_result.has_value()) {
        core_->set_message(result.command_result->message);
    }
    
    if (result.type == KeyResult::Unbound) {
        // Fallback: Self-insert for printable characters
        // Check if key is a single character and not a control sequence
        // The resolve_key function returns "C-x", "M-x", "Esc", "Return", etc.
        // Printable characters are returned as single chars "a", "1", etc.
        bool has_ctrl = key_name.find("C-") != std::string::npos;
        bool has_meta = key_name.find("M-") != std::string::npos;

        // Special case: "Space" should be treated as a printable character
        bool is_space = (key_name == "Space");
        std::string char_to_insert = is_space ? " " : key_name;

        if (!has_ctrl && !has_meta && (key_name.length() == 1 || is_space)) {
            // We can assume it's printable if length is 1 and it's not a special key (which resolve_key handles)
            core_->command_system().execute("self-insert-command", {char_to_insert});

            // --- Macro Recording Logic for Self-Insert ---
            if (core_->is_recording_macro()) {
                core_->record_key_sequence(char_to_insert);
            }
            // --------------------------------------------

            return true;
        }
    }

    return result.type != KeyResult::Unbound;
}

// process_key is removed as keybinding_manager handles it



void TuiEditor::render() {
    // Reset hardware cursor position
    hw_cursor_x_ = -1;
    hw_cursor_y_ = -1;

    // Clear and update screen info
    getmaxyx(stdscr, height_, width_);
    
    // Set background color from theme
    auto theme = core_->theme_manager().active_theme();
    if (theme) {
        int bg_color_pair = theme->get_color_pair(ThemeElement::Background);
        bkgd(bg_color_pair);
    }
    clear();
    
    // Calculate content area
    auto bounds = calculate_viewport_bounds();

    // Render the layout tree recursively (now includes per-window modelines)
    render_layout_node(core_->root_layout(), 0, 0, width_, bounds.content_height);
    
    // Global message/command line (last line)
    render_message_line();
    
    // Set hardware cursor
    if (core_->minibuffer_manager().is_active()) {
        // Minibuffer active: render_message_line already moved cursor to input pos
        curs_set(2); // Ensure visible (high visibility)
    } else {
        // Minibuffer inactive: place at buffer cursor
        if (hw_cursor_x_ != -1 && hw_cursor_y_ != -1 && cursor_blink_.is_visible()) {
            move(hw_cursor_y_, hw_cursor_x_);
            curs_set(2); // High visibility
        } else {
            curs_set(0); // Hide if off-screen or blinking off
        }
    }

    // Refresh screen
    refresh();
}

void TuiEditor::render_layout_node(std::shared_ptr<LayoutNode> node, int x, int y, int width, int height) {
    if (!node) return;
    
    if (node->type == LayoutNode::Type::Leaf) {
        // Render a single window
        render_window(node->window, x, y, width, height);
    } else if (node->type == LayoutNode::Type::HorizontalSplit) {
        // Split horizontally: top and bottom windows
        int top_height = height / 2;
        int bottom_height = height - top_height;
        
        render_layout_node(node->child1, x, y, width, top_height);
        render_layout_node(node->child2, x, y + top_height, width, bottom_height);
    } else if (node->type == LayoutNode::Type::VerticalSplit) {
        // Split vertically: left and right windows  
        int separator_width = (width > 2) ? 1 : 0;
        int available_width = width - separator_width;

        int left_width = available_width / 2;
        int right_width = available_width - left_width;
        
        render_layout_node(node->child1, x, y, left_width, height);

        // Draw separator if enabled
        if (separator_width > 0) {
            int attrs = get_attributes_for_face("window-divider");
            attron(attrs);
            
            int sep_x = x + left_width;
            for (int i = 0; i < height; ++i) {
                mvaddch(y + i, sep_x, ACS_VLINE);
            }
            
            attroff(attrs);
        }

        render_layout_node(node->child2, x + left_width + separator_width, y, right_width, height);
    }
}

void TuiEditor::render_window(std::shared_ptr<Window> window, int x, int y, int width, int height) {
    if (!window) return;
    
    // Check configuration for line numbers and modeline
    bool show_line_numbers = core_->config().get<bool>("show_line_numbers", true);
    bool show_modeline = core_->config().get<bool>("show_modeline", true);
    int line_number_width = show_line_numbers ? core_->config().get<int>("line_number_width", 6) : 0;
    int modeline_height = show_modeline ? 1 : 0;
    
    // Update window viewport size (reserve space for modeline)
    int content_width = width - line_number_width;
    int content_height = height - modeline_height;
    window->set_viewport_size(content_width, content_height);
    
    // Get window data
    const auto& buffer = window->buffer();
    const auto cursor = window->cursor();
    auto [start_line, end_line] = window->visible_line_range();
    bool is_active = (window == core_->active_window());
    
    spdlog::trace("Render window at {},{} size {}x{} viewport={}-{} cursor=({},{}) active={}",
                  x, y, width, height, start_line, end_line, cursor.line, cursor.column, is_active);
    
    // Render buffer lines
    for (int screen_y = 0; screen_y < content_height && start_line + screen_y < end_line; ++screen_y) {
        size_t buffer_line_idx = start_line + screen_y;
        const auto& line_text = buffer.line(buffer_line_idx);
        
        // Clear this line
        move(y + screen_y, x);
        for (int i = 0; i < width; ++i) addch(' ');
        
        // Line number (if enabled)
        if (show_line_numbers) {
            mvprintw(y + screen_y, x, "%3zu   ", buffer_line_idx + 1);
        }

        // Line content with syntax highlighting
        if (!line_text.empty()) {
            int max_content_width = content_width - 1;
            const auto& styles = buffer.get_line_styles(buffer_line_idx);

            if (styles.empty()) {
                // No styles, render as plain text
                std::string display_text = line_text;
                if ((int)display_text.length() > max_content_width) {
                    display_text = display_text.substr(0, max_content_width - 3) + "...";
                }
                mvprintw(y + screen_y, x + line_number_width, "%s", display_text.c_str());
            } else {
                // Render with syntax highlighting
                size_t pos = 0;
                int screen_x = x + line_number_width;

                for (const auto& styled : styles) {
                    size_t start = styled.range.start.column;
                    size_t end = std::min(styled.range.end.column, line_text.size());

                    // Render unstyled text before this styled range
                    if (pos < start && pos < line_text.size()) {
                        size_t len = std::min(start - pos, line_text.size() - pos);
                        std::string unstyled = line_text.substr(pos, len);
                        mvprintw(y + screen_y, screen_x, "%s", unstyled.c_str());
                        screen_x += unstyled.length();
                        pos = start;
                    }

                    // Render styled text
                    if (pos < end && pos < line_text.size()) {
                        size_t len = std::min(end - pos, line_text.size() - pos);
                        std::string styled_text = line_text.substr(pos, len);

                        // Apply color
                        int attrs = get_attributes_for_face(styled.attr.face_name);
                        attron(attrs);
                        mvprintw(y + screen_y, screen_x, "%s", styled_text.c_str());
                        attroff(attrs);

                        screen_x += styled_text.length();
                        pos = end;
                    }
                }

                // Render remaining unstyled text
                if (pos < line_text.size()) {
                    std::string remaining = line_text.substr(pos);
                    if ((int)(screen_x - x - line_number_width + remaining.length()) > max_content_width) {
                        remaining = remaining.substr(0, max_content_width - (screen_x - x - line_number_width) - 3) + "...";
                    }
                    mvprintw(y + screen_y, screen_x, "%s", remaining.c_str());
                }
            }
        }

        // Highlight ISearch match
        if (core_->minibuffer_manager().is_isearch_active() && core_->minibuffer_manager().get_isearch_match_range().has_value()) {
            auto match = core_->minibuffer_manager().get_isearch_match_range().value();
            
            // Check if current line is part of the match
            if (buffer_line_idx >= match.start.line && buffer_line_idx <= match.end.line) {
                int horizontal_offset = window->viewport().horizontal_offset;
                
                size_t hl_start = (buffer_line_idx == match.start.line) ? match.start.column : 0;
                size_t hl_end = (buffer_line_idx == match.end.line) ? match.end.column : line_text.length();
                
                // Handle case where match extends past end of line (e.g. capturing newline)
                // Ensure we don't go past line text length for string extraction
                size_t text_len = line_text.length();
                size_t safe_hl_end = std::min(hl_end, text_len);

                // Adjust for horizontal scroll
                int draw_start = static_cast<int>(hl_start) - horizontal_offset;
                int draw_end = static_cast<int>(safe_hl_end) - horizontal_offset;
                
                // Clip to viewable area
                draw_start = std::max(0, draw_start);
                // Clip draw_end to window width
                if (draw_end > content_width) draw_end = content_width;

                if (draw_start < draw_end) {
                    // Extract text to highlight
                    std::string text_to_hl = line_text.substr(draw_start + horizontal_offset, draw_end - draw_start);
                    
                    int screen_x = x + line_number_width + draw_start;

                    int attrs = get_attributes_for_face(core_->minibuffer_manager().is_isearch_failed() ? "isearch-fail" : "isearch");
                    if (attrs == 0) attrs = A_REVERSE;
                    
                    attron(attrs);
                    mvprintw(y + screen_y, screen_x, "%s", text_to_hl.c_str());
                    attroff(attrs);
                }
            }
        }
        
        // Show cursor if this is the cursor line and this is the active window
        if (buffer_line_idx == cursor.line && is_active && cursor_blink_.is_visible()) {
            int cursor_screen_x = x + line_number_width + cursor.column;
            if (cursor_screen_x < x + width) {
                char cursor_char = ' ';
                if (cursor.column < line_text.size()) {
                    cursor_char = line_text[cursor.column];
                }
                
                // Force A_REVERSE to ensure visible block cursor
                int attrs = A_REVERSE;
                
                attron(attrs);
                mvaddch(y + screen_y, cursor_screen_x, cursor_char);
                attroff(attrs);
            }
        }

        // Track hardware cursor position for active window
        if (buffer_line_idx == cursor.line && is_active) {
            int cursor_screen_x = x + line_number_width + (int)cursor.column;
            if (cursor_screen_x < x + width) {
                hw_cursor_x_ = cursor_screen_x;
                hw_cursor_y_ = y + screen_y;
            }
        }
    }
    
    // Fill remaining lines (for empty lines below buffer) - no tildes
    size_t displayed_lines = std::min((size_t)content_height, end_line - start_line);
    for (int screen_y = displayed_lines; screen_y < content_height; ++screen_y) {
        move(y + screen_y, x);
        for (int i = 0; i < width; ++i) addch(' ');
    }
    
    // Render modeline for this window
    if (show_modeline) {
        render_window_modeline(window, x, y + content_height, width, is_active);
    }
}

void TuiEditor::render_window_modeline(std::shared_ptr<Window> window, int x, int y, int width, bool is_active) {
    const auto& buffer = window->buffer();
    const auto cursor = window->cursor();
    
    // Choose modeline colors
    std::string face_name = is_active ? "mode-line" : "mode-line-inactive";
    int attrs = get_attributes_for_face(face_name);
    if (attrs == 0) attrs = is_active ? A_REVERSE : A_DIM;
    
    attron(attrs);
    
    // Clear the modeline
    move(y, x);
    for (int i = 0; i < width; ++i) addch(' ');
    
    // Create modeline content
    std::string modeline;
    
    // Buffer name and modification status
    modeline += buffer.name();
    if (buffer.is_modified()) modeline += " [+] ";
    
    // Cursor position
    modeline += " | " + std::to_string(cursor.line + 1) + ":" + std::to_string(cursor.column + 1);
    
    // Major mode (if available)
    // TODO: Add major mode support when available
    
    // Right-aligned content (percentage through file)
    std::string right_side;
    if (buffer.line_count() > 0) {
        int percentage = (cursor.line * 100) / (buffer.line_count() - 1);
        right_side = " " + std::to_string(percentage) + "%";
    }
    
    // Truncate modeline if too long
    int available_width = width - right_side.length();
    if ((int)modeline.length() > available_width) {
        modeline = modeline.substr(0, available_width - 3) + "...";
    }
    
    // Render left side
    mvprintw(y, x, "%s", modeline.c_str());
    
    // Render right side
    if (!right_side.empty()) {
        mvprintw(y, x + width - right_side.length(), "%s", right_side.c_str());
    }
    
    // Turn off modeline attributes
    attroff(attrs);
}

void TuiEditor::render_message_line() {
    int msg_y = height_ - 1;
    int attrs = get_attributes_for_face("minibuffer-prompt");

    // Check for extended echo area lines (e.g., from which-key)
    const auto& echo_lines = core_->echo_area_lines();
    if (!echo_lines.empty()) {
        // Render echo area lines above the main message line
        int echo_start_y = msg_y - static_cast<int>(echo_lines.size());
        if (echo_start_y < 0) echo_start_y = 0;

        for (size_t i = 0; i < echo_lines.size() && echo_start_y + static_cast<int>(i) < msg_y; ++i) {
            move(echo_start_y + static_cast<int>(i), 0);
            clrtoeol();
            attron(attrs);
            mvprintw(echo_start_y + static_cast<int>(i), 0, "%s", echo_lines[i].c_str());
            attroff(attrs);
        }
    }

    attron(attrs);
    move(msg_y, 0);
    clrtoeol();

    if (core_->minibuffer_manager().is_active()) {
        std::string prompt_part = core_->minibuffer_manager().get_prompt();
        std::string input_part = core_->minibuffer_manager().get_input_buffer();
        std::string display_text = prompt_part + input_part;
        
        mvprintw(msg_y, 0, "%s", display_text.c_str());

        // Display completion candidates below the input line (conceptually, but physically above if at bottom)
        auto candidates = core_->minibuffer_manager().get_completion_candidates();
        if (!candidates.empty() && msg_y > 0) {
            // Move up one line to display completions above the current minibuffer line
            move(msg_y - 1, 0);
            clrtoeol(); // Clear the line first to remove artifacts from underlying window
            
            std::string completion_display;
            for (size_t i = 0; i < candidates.size() && static_cast<int>(completion_display.length()) < width_ - 5; ++i) {
                if (!completion_display.empty()) completion_display += " ";
                completion_display += candidates[i].display_text; 
            }
            if (static_cast<int>(completion_display.length()) >= width_ - 5) {
                completion_display = completion_display.substr(0, width_ - 8) + "...";
            }
            
            // Render with a different color/attribute if possible
            mvprintw(msg_y - 1, 0, "%s", completion_display.c_str());
        }

        // Explicitly place cursor at the correct position in minibuffer
        // prompt_part is handled by get_prompt(), cursor_position is relative to input_buffer
        move(msg_y, prompt_part.length() + core_->minibuffer_manager().get_cursor_position());

    } else if (core_->keybinding_manager().is_building_sequence()) {
        // Display partial key sequence (like Emacs "C-x-")
        std::string key_seq = core_->keybinding_manager().current_sequence_display() + "-";
        mvprintw(msg_y, 0, "%s", key_seq.c_str());
    } else if (!message_line_.empty()) {
        // Display transient message with formatting (multi-line collapse + truncation)
        int available_width = width_ - 1;
        std::string display_msg = MessageFormatter::format_for_echo_area(
            message_line_, static_cast<size_t>(available_width));

        // Apply color based on severity
        int msg_attrs = attrs;
        switch (core_->last_message_severity()) {
            case MessageSeverity::Warning:
                msg_attrs |= COLOR_PAIR(3) | A_BOLD;  // Yellow
                break;
            case MessageSeverity::Error:
                msg_attrs |= COLOR_PAIR(1) | A_BOLD;  // Red
                break;
            default:
                break;
        }
        attron(msg_attrs);
        mvprintw(msg_y, 0, "%s", display_msg.c_str());
        attroff(msg_attrs);
    }

    attroff(attrs);

    // After rendering, check if it's time to clear the message.
    core_->check_and_clear_message();
}

namespace lumacs {
std::unique_ptr<IEditorView> create_tui_editor() {
    return std::make_unique<TuiEditor>();
}
}
