#include <nanobind/ndarray.h>

#include <variant>
#include <vector>

#include "internal_access.hpp"

void add_image_to_worksheet(XLWorksheet& ws, py::bytes imageData, const std::string& extension,
                            uint32_t row, uint16_t col, double width, double height) {
    // Use public APIs instead of Rob hack / reinterpret_cast
    XLDocument& doc = get_parent_doc(ws);

    // 1. Add image to document package
    auto& archive = get_archive(doc);
    int imgNum = 1;
    while (archive.hasEntry("xl/media/image" + std::to_string(imgNum) + "." + extension)) {
        ++imgNum;
    }
    std::string imgName = "image" + std::to_string(imgNum) + "." + extension;

    // FIX: Copy py::bytes data BEFORE releasing GIL (accessing Python buffer requires GIL)
    std::string imgDataStr(static_cast<const char*>(imageData.data()), imageData.size());
    std::string imgPath;
    {
        py::gil_scoped_release release;
        imgPath = doc.addImage(imgName, std::move(imgDataStr));
    }

    // 2. Get worksheet drawing
    XLDrawing& drawing = ws.drawing();

    // 3. Add relationship from drawing to image
    std::string relPath = "../media/" + imgPath.substr(imgPath.find_last_of('/') + 1);

    std::string relId;
    {
        py::gil_scoped_release release;
        auto rel = drawing.relationships().addRelationship(XLRelationshipType::Image, relPath);
        relId = rel.id();

        // 4. Add image to drawing
        drawing.addImage(relId, imgName, "Image", row - 1, col - 1, (uint32_t)width,
                         (uint32_t)height);
    }
}

// Helper function to convert XLCellValue to py::object efficiently
// Note: GIL must be held when calling this function
inline py::object cell_value_to_pyobject(const XLCellValue& val) {
    XLValueType type = val.type();
    switch (type) {
        case XLValueType::Boolean:
            return py::cast(val.get<bool>());
        case XLValueType::Integer:
            return py::cast(val.get<int64_t>());
        case XLValueType::Float:
            return py::cast(val.get<double>());
        case XLValueType::String:
            return py::cast(val.get<std::string>());
        default:
            return py::none();
    }
}

// Get a single cell's value directly without creating a Cell object
py::object get_cell_value(XLWorksheet& ws, uint32_t row, uint16_t col) {
    Expects(row >= 1 && row <= kExcelMaxRows);
    Expects(col >= 1 && col <= kExcelMaxCols);

    CellData data;
    {
        py::gil_scoped_release release;
        XLCell cell = ws.cell(row, col);
        data = CellData::from(cell.value());
    }
    return data.to_python();
}

// Bulk read a specific range of cells - returns list[list[Any]]
py::list get_range_data(XLWorksheet& ws, uint32_t startRow, uint16_t startCol, uint32_t endRow,
                        uint16_t endCol) {
    Expects(startRow >= 1 && startRow <= kExcelMaxRows);
    Expects(endRow >= startRow && endRow <= kExcelMaxRows);
    Expects(startCol >= 1 && startCol <= kExcelMaxCols);
    Expects(endCol >= startCol && endCol <= kExcelMaxCols);

    auto numRows = gsl::narrow<uint32_t>(endRow - startRow + 1);
    auto numCols = gsl::narrow<uint16_t>(endCol - startCol + 1);

    // First, read all data without GIL
    std::vector<CellData> data;

    {
        py::gil_scoped_release release;

        data.resize(static_cast<size_t>(numRows) * numCols);

        for (uint32_t r = startRow; r <= endRow; ++r) {
            size_t baseIdx = static_cast<size_t>(r - startRow) * numCols;
            XLRow row = ws.row(r);
            if (!row.empty()) {
                std::vector<XLCellValue> values = row.values();

                for (uint16_t c = startCol; c <= endCol; ++c) {
                    auto colIdx = gsl::narrow<size_t>(c - 1);  // values is 0-indexed
                    if (colIdx < values.size()) {
                        data[baseIdx + (c - startCol)] = CellData::from(values[colIdx]);
                    }
                }
            }
        }
    }

    // Now convert to Python with GIL held
    py::list result;
    for (uint32_t r = 0; r < numRows; ++r) {
        py::list pyRow;
        size_t baseIdx = static_cast<size_t>(r) * numCols;
        for (uint16_t c = 0; c < numCols; ++c) {
            pyRow.append(data[baseIdx + c].to_python());
        }
        result.append(pyRow);
    }

    return result;
}

// Bulk read all rows data - returns list[list[Any]]
py::list get_rows_data(XLWorksheet& ws) {
    // First, read all data without GIL
    std::vector<CellData> data;
    uint32_t rowCount = 0;
    uint16_t colCount = 0;

    {
        py::gil_scoped_release release;

        rowCount = ws.rowCount();
        colCount = ws.columnCount();

        data.resize(static_cast<size_t>(rowCount) * colCount);

        for (uint32_t r = 1; r <= rowCount; ++r) {
            size_t baseIdx = static_cast<size_t>(r - 1) * colCount;
            XLRow row = ws.row(r);
            if (!row.empty()) {
                std::vector<XLCellValue> values = row.values();
                auto valCount =
                    std::min(static_cast<uint32_t>(values.size()), static_cast<uint32_t>(colCount));
                for (uint32_t i = 0; i < valCount; ++i) {
                    data[baseIdx + i] = CellData::from(values[i]);
                }
            }
        }
    }

    // Now convert to Python with GIL held
    // Note: nanobind py::list has no size-based constructor, so we use append
    py::list result;
    for (uint32_t r = 0; r < rowCount; ++r) {
        py::list pyRow;
        size_t baseIdx = static_cast<size_t>(r) * colCount;
        for (uint16_t c = 0; c < colCount; ++c) {
            pyRow.append(data[baseIdx + c].to_python());
        }
        result.append(std::move(pyRow));
    }

    return result;
}

// Get a single row's data as list[Any] - more efficient for row iteration
py::list get_row_values(XLWorksheet& ws, uint32_t rowNumber) {
    Expects(rowNumber >= 1 && rowNumber <= kExcelMaxRows);

    // First, read data without GIL
    std::vector<CellData> rowData;
    uint16_t colCount;

    {
        py::gil_scoped_release release;

        colCount = ws.columnCount();
        rowData.reserve(colCount);

        XLRow row = ws.row(rowNumber);
        if (!row.empty()) {
            std::vector<XLCellValue> values = row.values();
            for (const auto& val : values) {
                rowData.push_back(CellData::from(val));
            }
        }

        // Pad with empty values if needed
        while (rowData.size() < colCount) {
            rowData.emplace_back();
        }
    }

    // Convert to Python with GIL held
    py::list result;
    for (const auto& cellData : rowData) {
        result.append(cellData.to_python());
    }

    return result;
}

// Write a numpy array to a worksheet range cleanly using nanobind's ndarray
template <typename T>
void write_range_typed(XLWorksheet& ws, uint32_t startRow, uint16_t startCol,
                       py::ndarray<T, py::c_contig, py::device::cpu> b) {
    if (b.ndim() != 2) {
        throw std::runtime_error("Incompatible buffer dimension! Expected 2D array.");
    }

    auto numRows = gsl::narrow<uint32_t>(b.shape(0));
    auto numCols = gsl::narrow<uint16_t>(b.shape(1));

    Expects(startRow >= 1 && startRow + numRows - 1 <= kExcelMaxRows);
    Expects(startCol >= 1 && startCol + numCols - 1 <= kExcelMaxCols);

    const T* ptr = static_cast<const T*>(b.data());
    std::vector<T> data(ptr, ptr + static_cast<size_t>(numRows) * numCols);

    // Now release GIL and write to worksheet using our copied data
    {
        py::gil_scoped_release release;
        for (uint32_t r = 0; r < numRows; ++r) {
            for (uint16_t c = 0; c < numCols; ++c) {
                T val = data[static_cast<size_t>(r) * numCols + c];
                ws.cell(startRow + r, startCol + c).value() = val;
            }
        }
    }
}

// Read numeric data into a numpy array
// FIX: Use unique_ptr for exception-safe memory management (was: raw new with delayed capsule)
py::ndarray<py::numpy, double, py::shape<-1, -1>> get_range_values(
    XLWorksheet& ws, uint32_t startRow, uint16_t startCol, uint32_t endRow, uint16_t endCol) {
    Expects(startRow >= 1 && startRow <= kExcelMaxRows);
    Expects(endRow >= startRow && endRow <= kExcelMaxRows);
    Expects(startCol >= 1 && startCol <= kExcelMaxCols);
    Expects(endCol >= startCol && endCol <= kExcelMaxCols);

    auto numRows = gsl::narrow<size_t>(endRow - startRow + 1);
    auto numCols = gsl::narrow<size_t>(endCol - startCol + 1);

    // FIX: Use unique_ptr so memory is freed on exception before capsule takes ownership
    auto uptr = std::make_unique<double[]>(numRows * numCols);
    gsl::span<double> buf(uptr.get(), numRows * numCols);

    {
        py::gil_scoped_release release;
        for (size_t r = 0; r < numRows; ++r) {
            auto rowSpan = buf.subspan(r * numCols, numCols);
            XLRow row = ws.row(gsl::narrow<uint32_t>(startRow + r));
            if (row.empty()) {
                std::fill(rowSpan.begin(), rowSpan.end(), 0.0);
                continue;
            }

            std::vector<XLCellValue> values = row.values();
            for (size_t c = 0; c < numCols; ++c) {
                auto colIdx = gsl::narrow<size_t>(startCol + c - 1);
                if (colIdx < values.size()) {
                    const auto& val = values[colIdx];
                    if (val.type() == XLValueType::Float) {
                        rowSpan[c] = val.get<double>();
                    } else if (val.type() == XLValueType::Integer) {
                        rowSpan[c] = static_cast<double>(val.get<int64_t>());
                    } else {
                        rowSpan[c] = 0.0;
                    }
                } else {
                    rowSpan[c] = 0.0;
                }
            }
        }
    }

    // Transfer ownership from unique_ptr to capsule
    double* ptr = uptr.release();
    py::capsule owner(ptr, [](void* p) noexcept { delete[] (double*)p; });
    size_t shape[2] = {numRows, numCols};
    return py::ndarray<py::numpy, double, py::shape<-1, -1>>(ptr, 2, shape, owner);
}

// Direct cell value setter - bypasses Python Cell object creation
void set_cell_value(XLWorksheet& ws, uint32_t row, uint16_t col, py::object value) {
    Expects(row >= 1 && row <= kExcelMaxRows);
    Expects(col >= 1 && col <= kExcelMaxCols);

    if (value.is_none()) {
        py::gil_scoped_release release;
        ws.cell(row, col).value().clear();
    } else if (py::isinstance<py::bool_>(value)) {
        bool val = py::cast<bool>(value);
        py::gil_scoped_release release;
        ws.cell(row, col).value() = val;
    } else if (py::isinstance<py::int_>(value)) {
        int64_t val = py::cast<int64_t>(value);
        py::gil_scoped_release release;
        ws.cell(row, col).value() = val;
    } else if (py::isinstance<py::float_>(value)) {
        double val = py::cast<double>(value);
        py::gil_scoped_release release;
        ws.cell(row, col).value() = val;
    } else if (py::isinstance<py::str>(value)) {
        std::string val = py::cast<std::string>(value);
        py::gil_scoped_release release;
        ws.cell(row, col).value() = val;
    } else {
        throw py::type_error("Unsupported type for cell value");
    }
}

// Write a 2D Python list to a worksheet range
// Uses OpenXLSX's row batch assignment for better performance
void write_rows_data(XLWorksheet& ws, uint32_t startRow, uint16_t startCol, py::list rows) {
    Expects(startRow >= 1 && startRow <= kExcelMaxRows);
    Expects(startCol >= 1 && startCol <= kExcelMaxCols);

    // First pass: extract all data while holding GIL, convert to XLCellValue directly
    std::vector<std::vector<XLCellValue>> data;
    data.reserve(py::len(rows));

    for (auto row : rows) {
        std::vector<XLCellValue> rowData;
        py::list rowList = py::cast<py::list>(row);
        rowData.reserve(py::len(rowList));

        for (auto cell : rowList) {
            CellData cd = CellData::from_python(cell);
            rowData.push_back(cd.to_xlcellvalue());
        }
        data.push_back(std::move(rowData));
    }

    // Second pass: write to worksheet without GIL using row-level batch assignment
    {
        py::gil_scoped_release release;

        for (size_t r = 0; r < data.size(); ++r) {
            XLRow xlRow = ws.row(gsl::narrow<uint32_t>(startRow + r));
            xlRow.values() = data[r];
        }
    }
}

// Write a single row of Python data
void write_row_data(XLWorksheet& ws, uint32_t row, uint16_t startCol, py::list values) {
    Expects(row >= 1 && row <= kExcelMaxRows);
    Expects(startCol >= 1 && startCol <= kExcelMaxCols);

    // Extract data while holding GIL
    std::vector<XLCellValue> data;
    data.reserve(py::len(values));

    for (auto val : values) {
        CellData cd = CellData::from_python(val);
        data.push_back(cd.to_xlcellvalue());
    }

    // Write without GIL using row-level batch assignment
    {
        py::gil_scoped_release release;

        XLRow xlRow = ws.row(row);
        xlRow.values() = data;
    }
}

// Batch set multiple cell values: [(row, col, value), ...]
void set_cells_batch(XLWorksheet& ws, py::list cells) {
    // Structure to hold row, col, value
    struct CellWrite {
        uint32_t row;
        uint16_t col;
        CellData value;
    };

    // Extract all data while holding GIL
    std::vector<CellWrite> writes;
    writes.reserve(py::len(cells));

    for (auto item : cells) {
        py::tuple t = py::cast<py::tuple>(item);
        if (py::len(t) != 3) {
            throw py::value_error("Each item must be a tuple of (row, col, value)");
        }
        CellWrite cw;
        cw.row = py::cast<uint32_t>(t[0]);
        cw.col = py::cast<uint16_t>(t[1]);
        Expects(cw.row >= 1 && cw.row <= kExcelMaxRows);
        Expects(cw.col >= 1 && cw.col <= kExcelMaxCols);
        cw.value = CellData::from_python(t[2]);
        writes.push_back(std::move(cw));
    }

    // Write without GIL
    {
        py::gil_scoped_release release;

        for (const auto& cw : writes) {
            XLCell cell = ws.cell(cw.row, cw.col);
            cw.value.apply_to(cell);
        }
    }
}

void init_worksheet(py::module_& m) {
    // Bind XLDrawingItem
    py::class_<XLDrawingItem>(m, "XLDrawingItem")
        .def("name", &XLDrawingItem::name)
        .def("description", &XLDrawingItem::description)
        .def("row", &XLDrawingItem::row)
        .def("col", &XLDrawingItem::col)
        .def("width", &XLDrawingItem::width)
        .def("height", &XLDrawingItem::height)
        .def("relationship_id", &XLDrawingItem::relationshipId);

    // Bind XLDrawing
    py::class_<XLDrawing>(m, "XLDrawing")
        .def("image_count", &XLDrawing::imageCount)
        .def("image", &XLDrawing::image, py::arg("index"))
        .def("add_image", &XLDrawing::addImage, py::arg("r_id"), py::arg("name"),
             py::arg("description"), py::arg("row"), py::arg("col"), py::arg("width"),
             py::arg("height"), py::arg("options") = XLImageOptions())
        .def("add_scaled_image", &XLDrawing::addScaledImage, py::arg("r_id"), py::arg("name"),
             py::arg("description"), py::arg("data"), py::arg("row"), py::arg("col"),
             py::arg("scaling_factor") = 1.0);

    // Bind XLColumn
    py::class_<XLColumn>(m, "XLColumn")
        .def("width", &XLColumn::width)
        .def("set_width", &XLColumn::setWidth, py::arg("width"))
        .def("is_hidden", &XLColumn::isHidden)
        .def("set_hidden", &XLColumn::setHidden, py::arg("state"))
        .def("format", &XLColumn::format)
        .def("set_format", &XLColumn::setFormat, py::arg("cellFormatIndex"));

    // XLPaneState Enum
    py::enum_<XLPaneState>(m, "XLPaneState")
        .value("Split", XLPaneState::Split)
        .value("Frozen", XLPaneState::Frozen)
        .value("FrozenSplit", XLPaneState::FrozenSplit);

    // XLPane Enum
    py::enum_<XLPane>(m, "XLPane")
        .value("BottomRight", XLPane::BottomRight)
        .value("TopRight", XLPane::TopRight)
        .value("BottomLeft", XLPane::BottomLeft)
        .value("TopLeft", XLPane::TopLeft);

    // Bind XLWorksheet
    py::class_<XLWorksheet>(m, "XLWorksheet")
        .def("name", &XLWorksheet::name)
        .def("set_name", &XLWorksheet::setName)
        .def("index", &XLWorksheet::index)
        .def("set_index", &XLWorksheet::setIndex)
        .def("visibility", &XLWorksheet::visibility)
        .def("set_visibility", &XLWorksheet::setVisibility)
        .def("is_active", &XLWorksheet::isActive)
        .def("set_active", &XLWorksheet::setActive)
        .def("row_count", &XLWorksheet::rowCount)
        .def("column_count", &XLWorksheet::columnCount)
        .def("has_drawing", &XLWorksheet::hasDrawing)
        .def("drawing", &XLWorksheet::drawing, py::rv_policy::reference_internal)
        .def("has_panes", &XLWorksheet::hasPanes)
        .def("freeze_panes", py::overload_cast<uint16_t, uint32_t>(&XLWorksheet::freezePanes),
             py::arg("column"), py::arg("row"))
        .def("freeze_panes", py::overload_cast<const std::string&>(&XLWorksheet::freezePanes),
             py::arg("cellRef"))
        .def("split_panes", &XLWorksheet::splitPanes, py::arg("xSplit"), py::arg("ySplit"),
             py::arg("topLeftCell") = "", py::arg("activePane") = XLPane::BottomRight)
        .def("clear_panes", &XLWorksheet::clearPanes)
        .def("has_auto_filter", &XLWorksheet::hasAutoFilter)
        .def("auto_filter", &XLWorksheet::autoFilter)
        .def(
            "set_auto_filter",
            [](XLWorksheet& self, const std::string& range) {
                self.setAutoFilter(self.range(range));
            },
            py::arg("range"))
        .def("autofilter_object", &XLWorksheet::autofilterObject)
        .def("clear_auto_filter", &XLWorksheet::clearAutoFilter)
        .def("set_zoom", &XLWorksheet::setZoom, py::arg("scale"))
        .def("zoom", &XLWorksheet::zoom)
        .def("add_hyperlink", &XLWorksheet::addHyperlink, py::arg("cellRef"), py::arg("url"),
             py::arg("tooltip") = "")
        .def("add_internal_hyperlink", &XLWorksheet::addInternalHyperlink, py::arg("cellRef"),
             py::arg("location"), py::arg("tooltip") = "")
        .def("has_hyperlink", &XLWorksheet::hasHyperlink, py::arg("cellRef"))
        .def("get_hyperlink", &XLWorksheet::getHyperlink, py::arg("cellRef"))
        .def("remove_hyperlink", &XLWorksheet::removeHyperlink, py::arg("cellRef"))
        .def("data_validations", &XLWorksheet::dataValidations, py::rv_policy::reference_internal)
        .def("page_setup", &XLWorksheet::pageSetup)
        .def("page_margins", &XLWorksheet::pageMargins)
        .def("print_options", &XLWorksheet::printOptions)
        .def("tables", &XLWorksheet::tables, py::rv_policy::reference_internal)
        .def("has_tables", &XLWorksheet::hasTables)
        .def(
            "cell",
            [](XLWorksheet& self, const std::string& ref) {
                py::gil_scoped_release release;
                return (XLCell)self.cell(ref);
            },
            py::keep_alive<0, 1>())
        .def(
            "cell",
            [](XLWorksheet& self, int row, int col) {
                py::gil_scoped_release release;
                return (XLCell)self.cell(row, col);
            },
            py::keep_alive<0, 1>())
        .def(
            "range",
            [](XLWorksheet& self, const std::string& address) {
                py::gil_scoped_release release;
                return self.range(address);
            },
            py::keep_alive<0, 1>())
        .def(
            "range",
            [](XLWorksheet& self, const std::string& topLeft, const std::string& bottomRight) {
                py::gil_scoped_release release;
                return self.range(XLCellReference(topLeft), XLCellReference(bottomRight));
            },
            py::keep_alive<0, 1>())
        .def("column", py::overload_cast<uint16_t>(&XLWorksheet::column, py::const_),
             py::keep_alive<0, 1>())
        .def("column", py::overload_cast<const std::string&>(&XLWorksheet::column, py::const_),
             py::keep_alive<0, 1>())
        .def(
            "merge_cells",
            [](XLWorksheet& self, const std::string& rangeReference, bool emptyHiddenCells) {
                py::gil_scoped_release release;
                self.mergeCells(rangeReference, emptyHiddenCells);
            },
            py::arg("rangeReference"), py::arg("emptyHiddenCells") = false)
        .def(
            "unmerge_cells",
            [](XLWorksheet& self, const std::string& rangeReference) {
                py::gil_scoped_release release;
                self.unmergeCells(rangeReference);
            },
            py::arg("rangeReference"))
        .def("column_format",
             py::overload_cast<const std::string&>(&XLWorksheet::getColumnFormat, py::const_))
        .def("merges", &XLWorksheet::merges, py::rv_policy::reference_internal)
        .def("set_column_format",
             py::overload_cast<const std::string&, XLStyleIndex>(&XLWorksheet::setColumnFormat),
             py::arg("column"), py::arg("cellFormatIndex"))
        .def("set_column_format",
             py::overload_cast<uint16_t, XLStyleIndex>(&XLWorksheet::setColumnFormat),
             py::arg("column"), py::arg("cellFormatIndex"))
        .def("row_format", &XLWorksheet::getRowFormat)
        .def("set_row_format", &XLWorksheet::setRowFormat, py::arg("row"),
             py::arg("cellFormatIndex"))
        .def(
            "protect_sheet",
            [](XLWorksheet& self, bool set) {
                py::gil_scoped_release release;
                self.protectSheet(set);
            },
            py::arg("set") = true)
        .def(
            "protect_objects",
            [](XLWorksheet& self, bool set) {
                py::gil_scoped_release release;
                self.protectObjects(set);
            },
            py::arg("set") = true)
        .def(
            "protect_scenarios",
            [](XLWorksheet& self, bool set) {
                py::gil_scoped_release release;
                self.protectScenarios(set);
            },
            py::arg("set") = true)
        .def("sheet_protected", &XLWorksheet::sheetProtected)
        .def("objects_protected", &XLWorksheet::objectsProtected)
        .def("scenarios_protected", &XLWorksheet::scenariosProtected)
        .def(
            "set_password",
            [](XLWorksheet& self, const std::string& password) {
                py::gil_scoped_release release;
                self.setPassword(password);
            },
            py::arg("password"))
        .def("clear_password",
             [](XLWorksheet& self) {
                 py::gil_scoped_release release;
                 self.clearPassword();
             })
        .def("password_hash", &XLWorksheet::passwordHash)
        .def("password_is_set", &XLWorksheet::passwordIsSet)
        .def("insert_columns_allowed", &XLWorksheet::insertColumnsAllowed)
        .def("insert_rows_allowed", &XLWorksheet::insertRowsAllowed)
        .def("delete_columns_allowed", &XLWorksheet::deleteColumnsAllowed)
        .def("delete_rows_allowed", &XLWorksheet::deleteRowsAllowed)
        .def("select_locked_cells_allowed", &XLWorksheet::selectLockedCellsAllowed)
        .def("select_unlocked_cells_allowed", &XLWorksheet::selectUnlockedCellsAllowed)
        .def("insert_hyperlinks_allowed", &XLWorksheet::insertHyperlinksAllowed)
        .def("auto_filter_allowed", &XLWorksheet::autoFilterAllowed)
        .def("sort_allowed", &XLWorksheet::sortAllowed)
        .def("pivot_tables_allowed", &XLWorksheet::pivotTablesAllowed)
        .def("format_cells_allowed", &XLWorksheet::formatCellsAllowed)
        .def("format_columns_allowed", &XLWorksheet::formatColumnsAllowed)
        .def("format_rows_allowed", &XLWorksheet::formatRowsAllowed)
        .def(
            "set_insert_columns_allowed",
            [](XLWorksheet& self, bool set) {
                py::gil_scoped_release release;
                self.allowInsertColumns(set);
            },
            py::arg("set") = true)
        .def(
            "set_insert_rows_allowed",
            [](XLWorksheet& self, bool set) {
                py::gil_scoped_release release;
                self.allowInsertRows(set);
            },
            py::arg("set") = true)
        .def(
            "set_insert_hyperlinks_allowed",
            [](XLWorksheet& self, bool set) {
                py::gil_scoped_release release;
                self.allowInsertHyperlinks(set);
            },
            py::arg("set") = true)
        .def(
            "set_delete_columns_allowed",
            [](XLWorksheet& self, bool set) {
                py::gil_scoped_release release;
                self.allowDeleteColumns(set);
            },
            py::arg("set") = true)
        .def(
            "set_delete_rows_allowed",
            [](XLWorksheet& self, bool set) {
                py::gil_scoped_release release;
                self.allowDeleteRows(set);
            },
            py::arg("set") = true)
        .def(
            "set_select_locked_cells_allowed",
            [](XLWorksheet& self, bool set) {
                py::gil_scoped_release release;
                self.allowSelectLockedCells(set);
            },
            py::arg("set") = true)
        .def(
            "set_select_unlocked_cells_allowed",
            [](XLWorksheet& self, bool set) {
                py::gil_scoped_release release;
                self.allowSelectUnlockedCells(set);
            },
            py::arg("set") = true)
        .def(
            "set_auto_filter_allowed",
            [](XLWorksheet& self, bool set) {
                py::gil_scoped_release release;
                self.allowAutoFilter(set);
            },
            py::arg("set") = true)
        .def(
            "set_sort_allowed",
            [](XLWorksheet& self, bool set) {
                py::gil_scoped_release release;
                self.allowSort(set);
            },
            py::arg("set") = true)
        .def(
            "set_pivot_tables_allowed",
            [](XLWorksheet& self, bool set) {
                py::gil_scoped_release release;
                self.allowPivotTables(set);
            },
            py::arg("set") = true)
        .def(
            "set_format_cells_allowed",
            [](XLWorksheet& self, bool set) {
                py::gil_scoped_release release;
                self.allowFormatCells(set);
            },
            py::arg("set") = true)
        .def(
            "set_format_columns_allowed",
            [](XLWorksheet& self, bool set) {
                py::gil_scoped_release release;
                self.allowFormatColumns(set);
            },
            py::arg("set") = true)
        .def(
            "set_format_rows_allowed",
            [](XLWorksheet& self, bool set) {
                py::gil_scoped_release release;
                self.allowFormatRows(set);
            },
            py::arg("set") = true)
        .def("comments", &XLWorksheet::comments, py::rv_policy::reference_internal)
        .def("add_image", &add_image_to_worksheet, py::arg("image_data"), py::arg("extension"),
             py::arg("row") = 1, py::arg("col") = 1, py::arg("width") = 0, py::arg("height") = 0)
        // Bulk read APIs for performance optimization
        .def("get_rows_data", &get_rows_data,
             "Get all rows data as list[list[Any]] - optimized for bulk read")
        .def("get_row_values", &get_row_values, py::arg("row"),
             "Get a single row's values as list[Any]")
        .def("get_range_data", &get_range_data, py::arg("start_row"), py::arg("start_col"),
             py::arg("end_row"), py::arg("end_col"),
             "Get a range of cells as list[list[Any]] - optimized bulk read for specific range")
        .def("get_cell_value", &get_cell_value, py::arg("row"), py::arg("col"),
             "Get a single cell's value directly without creating a Cell object")
        .def("write_range_data", &write_range_typed<double>, py::arg("start_row"),
             py::arg("start_col"), py::arg("data"),
             "Write a 2D numpy array or buffer to a worksheet range")
        .def("write_range_data", &write_range_typed<int64_t>, py::arg("start_row"),
             py::arg("start_col"), py::arg("data"))
        .def("write_range_data", &write_range_typed<bool>, py::arg("start_row"),
             py::arg("start_col"), py::arg("data"))
        .def("get_range_values", &get_range_values, py::arg("start_row"), py::arg("start_col"),
             py::arg("end_row"), py::arg("end_col"),
             "Read a range of numeric cells into a 2D numpy array of doubles")
        // Performance-optimized write APIs - bypass Python Cell object creation
        .def("set_cell_value", &set_cell_value, py::arg("row"), py::arg("col"), py::arg("value"),
             "Set a cell's value directly without creating a Cell object. "
             "10-20x faster than ws.cell(row, col).value = val for bulk operations")
        .def("write_rows_data", &write_rows_data, py::arg("start_row"), py::arg("start_col"),
             py::arg("rows"),
             "Write a 2D Python list to a worksheet range. "
             "Optimized for any Python data (strings, mixed types). "
             "For pure numeric data, use write_range_data with numpy for best performance")
        .def("write_row_data", &write_row_data, py::arg("row"), py::arg("start_col"),
             py::arg("values"), "Write a single row of Python data")
        .def("set_cells_batch", &set_cells_batch, py::arg("cells"),
             "Batch set multiple cell values: [(row, col, value), ...]. "
             "Efficient for non-contiguous cell updates")
        .def("stream_writer", &XLWorksheet::streamWriter)
        .def("stream_reader", &XLWorksheet::streamReader)
        .def("peek_cell", py::overload_cast<const std::string&>(&XLWorksheet::peekCell, py::const_),
             py::arg("ref"))
        .def("peek_cell", py::overload_cast<uint32_t, uint16_t>(&XLWorksheet::peekCell, py::const_),
             py::arg("row"), py::arg("col"))
        .def("auto_fit_column", &XLWorksheet::autoFitColumn, py::arg("column_number"))
        .def("add_sort_condition", &XLWorksheet::addSortCondition, py::arg("ref"),
             py::arg("col_id"), py::arg("descending") = false)
        .def("apply_auto_filter", &XLWorksheet::applyAutoFilter)
        .def("add_conditional_formatting",
             py::overload_cast<const std::string&, const XLCfRule&>(
                 &XLWorksheet::addConditionalFormatting),
             py::arg("sqref"), py::arg("rule"))
        .def("add_conditional_formatting_dxf",
             py::overload_cast<const std::string&, const XLCfRule&, const XLDxf&>(
                 &XLWorksheet::addConditionalFormatting),
             py::arg("sqref"), py::arg("rule"), py::arg("dxf"))
        .def("remove_conditional_formatting",
             py::overload_cast<const std::string&>(&XLWorksheet::removeConditionalFormatting),
             py::arg("sqref"))
        .def("clear_all_conditional_formatting", &XLWorksheet::clearAllConditionalFormatting)
        .def("header_footer", &XLWorksheet::headerFooter)
        .def("set_print_area", &XLWorksheet::setPrintArea, py::arg("sqref"))
        .def("set_print_title_rows", &XLWorksheet::setPrintTitleRows, py::arg("first_row"),
             py::arg("last_row"))
        .def("set_print_title_cols", &XLWorksheet::setPrintTitleCols, py::arg("first_col"),
             py::arg("last_col"))
        .def("add_sparkline", &XLWorksheet::addSparkline, py::arg("location"),
             py::arg("data_range"), py::arg("type") = XLSparklineType::Line)
        .def("insert_image",
             py::overload_cast<const std::string&, const std::string&>(&XLWorksheet::insertImage),
             py::arg("cell_reference"), py::arg("image_path"))
        .def("insert_image_opt",
             py::overload_cast<const std::string&, const std::string&, const XLImageOptions&>(
                 &XLWorksheet::insertImage),
             py::arg("cell_reference"), py::arg("image_path"), py::arg("options"))
        .def("add_chart",
             py::overload_cast<XLChartType, std::string_view, uint32_t, uint32_t, uint32_t,
                               uint32_t>(&XLWorksheet::addChart),
             py::arg("type"), py::arg("name"), py::arg("row"), py::arg("col"), py::arg("width"),
             py::arg("height"))
        .def("add_chart_anchor",
             py::overload_cast<XLChartType, const XLChartAnchor&>(&XLWorksheet::addChart),
             py::arg("type"), py::arg("anchor"))
        .def("add_pivot_table", &XLWorksheet::addPivotTable, py::arg("options"))
        .def("add_comment", &XLWorksheet::addComment, py::arg("cell_ref"), py::arg("text"),
             py::arg("author") = "");
}
