#include "lumacs/buffer.hpp"
#include <fstream>
#include <sstream>
#include <algorithm>

namespace lumacs {

Buffer::Buffer() : name_("*scratch*"), lines_{""}, modified_(false), language_("text") {
    line_styles_.resize(1); // Match lines_ size
    emit_event(BufferEvent::Created);
}

Buffer::Buffer(std::string name)
    : name_(std::move(name)), lines_{""}, modified_(false), language_("text") {
    line_styles_.resize(1); // Match lines_ size
    emit_event(BufferEvent::Created);
}

std::optional<Buffer> Buffer::from_file(const std::filesystem::path& path) {
    std::ifstream file(path);
    if (!file.is_open()) {
        return std::nullopt;
    }

    Buffer buffer;
    buffer.file_path_ = path;
    buffer.name_ = path.filename().string();
    buffer.lines_.clear();
    buffer.line_styles_.clear();
    buffer.modified_ = false;

    std::string line;
    while (std::getline(file, line)) {
        buffer.lines_.push_back(std::move(line));
    }

    // Ensure at least one line
    if (buffer.lines_.empty()) {
        buffer.lines_.push_back("");
    }

    // Initialize styles to match lines
    buffer.line_styles_.resize(buffer.lines_.size());

    // Auto-detect language
    buffer.language_ = detect_language(path);

    // Emit loaded event
    buffer.emit_event(BufferEvent::Loaded);

    return buffer;
}

// === Content Access ===

size_t Buffer::line_count() const noexcept {
    return lines_.size();
}

const std::string& Buffer::line(size_t index) const {
    return lines_.at(index);
}

const std::vector<std::string>& Buffer::lines() const noexcept {
    return lines_;
}

std::string Buffer::content() const {
    if (lines_.empty()) {
        return "";
    }

    std::ostringstream oss;
    for (size_t i = 0; i < lines_.size(); ++i) {
        if (i > 0) {
            oss << '\n';
        }
        oss << lines_[i];
    }
    return oss.str();
}

// === Modification ===

void Buffer::insert(Position pos, std::string_view text) {
    if (text.empty()) {
        return;
    }

    emit_event(BufferEvent::BeforeChange, pos.line);
    pos = clamp_position(pos);

    // Handle multiline text insertion
    std::vector<std::string> new_lines;
    std::string text_copy{text};
    std::istringstream stream{text_copy};
    std::string line_text;
    while (std::getline(stream, line_text)) {
        new_lines.push_back(std::move(line_text));
    }

    if (new_lines.empty()) {
        return;
    }

    auto& current_line = lines_[pos.line];

    if (new_lines.size() == 1) {
        // Single line insertion
        current_line.insert(pos.column, new_lines[0]);
    } else {
        // Multiline insertion
        std::string after = current_line.substr(pos.column);
        current_line = current_line.substr(0, pos.column) + new_lines[0];

        ensure_styles_size();

        // Insert middle lines
        for (size_t i = 1; i < new_lines.size() - 1; ++i) {
            lines_.insert(lines_.begin() + pos.line + i, new_lines[i]);
            line_styles_.insert(line_styles_.begin() + pos.line + i, {});
        }

        // Insert last line with remaining text
        lines_.insert(lines_.begin() + pos.line + new_lines.size() - 1,
                     new_lines.back() + after);
        line_styles_.insert(line_styles_.begin() + pos.line + new_lines.size() - 1, {});
    }

    mark_modified();
    emit_event(BufferEvent::AfterChange, pos.line);
}

void Buffer::insert_char(Position pos, char c) {
    emit_event(BufferEvent::BeforeChange, pos.line);
    pos = clamp_position(pos);
    lines_[pos.line].insert(pos.column, 1, c);
    mark_modified();
    emit_event(BufferEvent::AfterChange, pos.line);
}

void Buffer::insert_newline(Position pos) {
    emit_event(BufferEvent::BeforeChange, pos.line);
    pos = clamp_position(pos);

    auto& current_line = lines_[pos.line];
    std::string after = current_line.substr(pos.column);
    current_line = current_line.substr(0, pos.column);

    ensure_styles_size();
    lines_.insert(lines_.begin() + pos.line + 1, std::move(after));
    line_styles_.insert(line_styles_.begin() + pos.line + 1, {});
    mark_modified();
    emit_event(BufferEvent::AfterChange, pos.line);
}

void Buffer::erase(Range range) {
    emit_event(BufferEvent::BeforeChange, range.start.line);
    if (range.start >= range.end) {
        return;
    }

    range.start = clamp_position(range.start);
    range.end = clamp_position(range.end);

    if (range.start.line == range.end.line) {
        // Single line deletion
        auto& line = lines_[range.start.line];
        line.erase(range.start.column, range.end.column - range.start.column);

        // Clear styles for this line since positions have changed
        if (range.start.line < line_styles_.size()) {
            line_styles_[range.start.line].clear();
        }
    } else {
        // Multiline deletion
        std::string prefix = lines_[range.start.line].substr(0, range.start.column);
        std::string suffix = lines_[range.end.line].substr(range.end.column);

        ensure_styles_size();

        // Remove lines in between (and their styles)
        lines_.erase(lines_.begin() + range.start.line,
                    lines_.begin() + range.end.line + 1);
        line_styles_.erase(line_styles_.begin() + range.start.line,
                          line_styles_.begin() + range.end.line + 1);

        // Insert merged line
        lines_.insert(lines_.begin() + range.start.line, prefix + suffix);
        line_styles_.insert(line_styles_.begin() + range.start.line, {});
    }

    ensure_min_lines();
    mark_modified();
    emit_event(BufferEvent::AfterChange, range.start.line);
}

void Buffer::erase_char(Position pos) {
    emit_event(BufferEvent::BeforeChange, pos.line);
    if (pos.column == 0 && pos.line == 0) {
        return; // Nothing to delete
    }

    pos = clamp_position(pos); // Clamp the target position for deletion

    if (pos.column > 0) {
        // Delete character before cursor - clear styles for this line
        lines_[pos.line].erase(pos.column - 1, 1);
        if (pos.line < line_styles_.size()) {
            line_styles_[pos.line].clear();
        }
    } else if (pos.line > 0) {
        // Join with previous line
        std::string current = std::move(lines_[pos.line]);

        ensure_styles_size();
        lines_.erase(lines_.begin() + pos.line);
        line_styles_.erase(line_styles_.begin() + pos.line);

        lines_[pos.line - 1] += current;
        // Clear styles for merged line
        if (pos.line - 1 < line_styles_.size()) {
            line_styles_[pos.line - 1].clear();
        }
    }

    mark_modified();
    emit_event(BufferEvent::AfterChange, pos.line);
}

void Buffer::replace(Range range, std::string_view text) {
    erase(range);
    insert(range.start, text);
}

std::optional<Range> Buffer::find(const std::string& query, Position start_pos) const {
    if (query.empty()) {
        return std::nullopt;
    }

    // Simple linear search for now
    // Start from the start_pos line and column
    
    start_pos = clamp_position(start_pos);
    
    for (size_t i = start_pos.line; i < lines_.size(); ++i) {
        const auto& line_text = lines_[i];
        size_t start_col = (i == start_pos.line) ? start_pos.column : 0;
        
        size_t found = line_text.find(query, start_col);
        if (found != std::string::npos) {
            Position start = {i, found};
            Position end = {i, found + query.length()};
            return Range{start, end};
        }
    }
    
    // Note: This only searches within lines (no multi-line search yet)
    return std::nullopt;
}

std::optional<Range> Buffer::find_backward(const std::string& query, Position start_pos) const {
    if (query.empty()) {
        return std::nullopt;
    }

    start_pos = clamp_position(start_pos);
    
    // Iterate backwards from start_pos.line to 0
    for (int i = static_cast<int>(start_pos.line); i >= 0; --i) {
        const auto& line_text = lines_[i];
        
        // For backward search, search up to start_pos.column
        size_t search_limit = (i == static_cast<int>(start_pos.line)) ? start_pos.column : std::string::npos;
        
        // rfind searches for last occurrence starting at or before pos
        // Note: if search_limit is npos, it searches whole string
        
        size_t found = line_text.rfind(query, search_limit);
        
        if (found != std::string::npos) {
            Position start = {static_cast<size_t>(i), found};
            Position end = {static_cast<size_t>(i), found + query.length()};
            return Range{start, end};
        }
    }
    return std::nullopt;
}

void Buffer::clear() {
    lines_.clear();
    lines_.push_back("");
    line_styles_.clear();
    line_styles_.resize(1);
    mark_modified();
}

// === File Operations ===

bool Buffer::save() {
    if (!file_path_) {
        return false;
    }
    return save_as(*file_path_);
}

bool Buffer::save_as(const std::filesystem::path& path) {
    emit_event(BufferEvent::BeforeSave);

    std::ofstream file(path);
    if (!file.is_open()) {
        return false;
    }

    for (size_t i = 0; i < lines_.size(); ++i) {
        if (i > 0) {
            file << '\n';
        }
        file << lines_[i];
    }

    if (!file.good()) {
        return false;
    }

    file_path_ = path;
    name_ = path.filename().string();
    modified_ = false;

    emit_event(BufferEvent::AfterSave);
    return true;
}

bool Buffer::is_modified() const noexcept {
    return modified_;
}

std::optional<std::filesystem::path> Buffer::file_path() const noexcept {
    return file_path_;
}

// === Buffer Properties ===

const std::string& Buffer::name() const noexcept {
    return name_;
}

void Buffer::set_name(std::string name) {
    name_ = std::move(name);
}

bool Buffer::is_valid_position(Position pos) const noexcept {
    if (pos.line >= lines_.size()) {
        return false;
    }
    return pos.column <= lines_[pos.line].size();
}

Position Buffer::clamp_position(Position pos) const noexcept {
    if (lines_.empty()) {
        return {0, 0};
    }

    pos.line = std::min(pos.line, lines_.size() - 1);
    pos.column = std::min(pos.column, lines_[pos.line].size());
    return pos;
}

// === Private Methods ===

void Buffer::ensure_min_lines() {
    if (lines_.empty()) {
        lines_.push_back("");
        line_styles_.resize(1);
    }
}

void Buffer::mark_modified() {
    modified_ = true;
}

void Buffer::ensure_styles_size() {
    if (line_styles_.size() != lines_.size()) {
        line_styles_.resize(lines_.size());
    }
}

// === Syntax Highlighting / Styling ===

void Buffer::set_style(Range range, TextAttribute attr) {
    ensure_styles_size();

    // Clamp the range to valid positions
    range.start = clamp_position(range.start);
    range.end = clamp_position(range.end);

    if (range.start >= range.end) {
        return; // Invalid range
    }

    // For simplicity, only handle single-line styles for now
    // Multi-line styles can be added later if needed
    if (range.start.line != range.end.line) {
        // Could split into multiple single-line ranges
        // For now, just handle the first line
        Range single_line = range;
        single_line.end.line = range.start.line;
        single_line.end.column = lines_[range.start.line].size();
        line_styles_[range.start.line].push_back({single_line, attr});
        return;
    }

    // Add the styled range to the appropriate line
    line_styles_[range.start.line].push_back({range, attr});
}

const std::vector<StyledRange>& Buffer::get_line_styles(size_t line) const {
    static const std::vector<StyledRange> empty;

    if (line >= line_styles_.size()) {
        return empty;
    }

    return line_styles_[line];
}

void Buffer::clear_styles() {
    line_styles_.clear();
    line_styles_.resize(lines_.size());
}

void Buffer::clear_line_styles(size_t line) {
    ensure_styles_size();

    if (line < line_styles_.size()) {
        line_styles_[line].clear();
    }
}

// === Events & Hooks ===

Buffer::BufferEventCallbackHandle Buffer::on_buffer_event(BufferEventCallback callback) {
    BufferEventCallbackHandle handle = next_callback_handle_++;
    event_callbacks_[handle] = std::move(callback);
    return handle;
}

void Buffer::disconnect_event_callback(BufferEventCallbackHandle handle) {
    event_callbacks_.erase(handle);
}

void Buffer::emit_event(BufferEvent event, size_t line) {
    BufferEventData data;
    data.event = event;
    data.line = line;
    data.language = language_;

    for (const auto& pair : event_callbacks_) {
        pair.second(data); // Call the std::function in the pair
    }
}

void Buffer::set_language(std::string lang) {
    if (language_ != lang) {
        language_ = std::move(lang);
        emit_event(BufferEvent::LanguageChanged);
    }
}

std::string Buffer::detect_language(const std::filesystem::path& path) {
    auto ext = path.extension().string();

    // Convert to lowercase
    std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);

    // Map extensions to language names
    if (ext == ".lua") return "lua";
    if (ext == ".cpp" || ext == ".cc" || ext == ".cxx") return "cpp";
    if (ext == ".c") return "c";
    if (ext == ".h" || ext == ".hpp" || ext == ".hxx") return "cpp";
    if (ext == ".py") return "python";
    if (ext == ".js") return "javascript";
    if (ext == ".ts") return "typescript";
    if (ext == ".rs") return "rust";
    if (ext == ".go") return "go";
    if (ext == ".java") return "java";
    if (ext == ".rb") return "ruby";
    if (ext == ".sh") return "bash";
    if (ext == ".md") return "markdown";
    if (ext == ".json") return "json";
    if (ext == ".xml") return "xml";
    if (ext == ".html" || ext == ".htm") return "html";
    if (ext == ".css") return "css";

    return "text"; // Default fallback
}

// === Undo/Redo ===

void Buffer::save_undo_state(Position cursor) {
    // Don't save state during undo/redo operations
    if (in_undo_redo_) {
        return;
    }

    // Save current state
    undo_stack_.emplace_back(lines_, cursor);

    // Limit undo stack size
    if (undo_stack_.size() > MAX_UNDO_LEVELS) {
        undo_stack_.erase(undo_stack_.begin());
    }
}

void Buffer::clear_redo_stack() {
    if (!in_undo_redo_) {
        redo_stack_.clear();
    }
}

bool Buffer::undo(Position& out_cursor) {
    if (undo_stack_.empty()) {
        return false;
    }

    in_undo_redo_ = true;

    // Save current state to redo stack
    redo_stack_.emplace_back(lines_, out_cursor);

    // Restore previous state
    auto& state = undo_stack_.back();
    lines_ = state.lines;
    out_cursor = state.cursor;

    // Remove from undo stack
    undo_stack_.pop_back();

    // Update styles size
    line_styles_.clear();
    line_styles_.resize(lines_.size());

    mark_modified();
    in_undo_redo_ = false;

    emit_event(BufferEvent::AfterChange);
    return true;
}

bool Buffer::redo(Position& out_cursor) {
    if (redo_stack_.empty()) {
        return false;
    }

    in_undo_redo_ = true;

    // Save current state to undo stack
    undo_stack_.emplace_back(lines_, out_cursor);

    // Restore next state
    auto& state = redo_stack_.back();
    lines_ = state.lines;
    out_cursor = state.cursor;

    // Remove from redo stack
    redo_stack_.pop_back();

    // Update styles size
    line_styles_.clear();
    line_styles_.resize(lines_.size());

    mark_modified();
    in_undo_redo_ = false;

    emit_event(BufferEvent::AfterChange);
    return true;
}

// === Mark and Region ===

void Buffer::set_mark(Position pos) {
    mark_ = clamp_position(pos);
    mark_active_ = true;
}

void Buffer::deactivate_mark() {
    mark_active_ = false;
}

std::optional<Range> Buffer::get_region(Position point) const {
    if (!mark_active_ || !mark_.has_value()) {
        return std::nullopt;
    }

    Position mark_pos = mark_.value();
    Position clamped_point = clamp_position(point);

    // Return range with start < end
    if (mark_pos <= clamped_point) {
        return Range{mark_pos, clamped_point};
    } else {
        return Range{clamped_point, mark_pos};
    }
}

std::string Buffer::get_text_in_range(Range range) const {
    std::string result;

    Position start = clamp_position(range.start);
    Position end = clamp_position(range.end);

    // Ensure start <= end
    if (end < start) {
        std::swap(start, end);
    }

    // Single line case
    if (start.line == end.line) {
        const auto& line = lines_[start.line];
        if (start.column < line.size() && end.column <= line.size()) {
            return line.substr(start.column, end.column - start.column);
        }
        return "";
    }

    // Multi-line case
    // First line
    const auto& first_line = lines_[start.line];
    if (start.column < first_line.size()) {
        result += first_line.substr(start.column);
    }
    result += '\n';

    // Middle lines
    for (size_t line = start.line + 1; line < end.line; ++line) {
        result += lines_[line];
        result += '\n';
    }

    // Last line
    const auto& last_line = lines_[end.line];
    if (end.column > 0 && end.column <= last_line.size()) {
        result += last_line.substr(0, end.column);
    }

    return result;
}

} // namespace lumacs