#ifndef OPENXLSX_XLCELL_HPP
#define OPENXLSX_XLCELL_HPP

#ifdef _MSC_VER    // conditionally enable MSVC specific pragmas to avoid other compilers warning about unknown pragmas
#    pragma warning(push)
#    pragma warning(disable : 4251)
#    pragma warning(disable : 4275)
#endif    // _MSC_VER

#include <iostream>    // std::ostream
#include <memory>
#include <ostream>    // std::basic_ostream

// ===== OpenXLSX Includes ===== //
#include "OpenXLSX-Exports.hpp"
#include "XLCellReference.hpp"
#include "XLCellValue.hpp"
#include "XLFormula.hpp"
#include "XLSharedStrings.hpp"
#include "XLStyles.hpp"    // XLStyleIndex
#include "XLStyle.hpp"

namespace OpenXLSX
{
    // ===== Flags that can be passed to XLCell::clear as parameter keep, flags can be combined with bitwise OR
    //                                  // Do not clear the cell's:
    constexpr const uint32_t XLKeepCellStyle   = 1;    // style (attribute s)
    constexpr const uint32_t XLKeepCellType    = 2;    // type (attribute t)
    constexpr const uint32_t XLKeepCellValue   = 4;    // value (child node v)
    constexpr const uint32_t XLKeepCellFormula = 8;    // formula (child node f)

    class XLCellRange;

    /**
     * @brief An implementation class encapsulating the properties and behaviours of a spreadsheet cell.
     */
    class OPENXLSX_EXPORT XLCell
    {
        friend class XLCellIterator;
        friend class XLCellValueProxy;
        friend class XLFormulaProxy;
        friend class XLRowDataIterator;
        friend bool operator==(const XLCell& lhs, const XLCell& rhs);
        friend bool operator!=(const XLCell& lhs, const XLCell& rhs);

    public:
/**
         * @brief Default constructor. Constructs a null object.
         */
        XLCell();

        /**
         * @brief Constructs an XLCell mapped to an XML node.
         * @param cellNode The underlying XML node representing the cell.
         * @param sharedStrings Reference to the workbook's shared strings table.
         */
        XLCell(const XMLNode& cellNode, const XLSharedStrings& sharedStrings);

        /**
         * @brief Copy constructor
         * @param other The XLCell object to be copied.
         * @note The copy constructor has been deleted, as it makes no sense to copy a cell. If the objective is to
         * copy the getValue, create the the target object and then use the copy assignment operator.
         */
        XLCell(const XLCell& other);

        /**
         * @brief Move constructor
         * @param other The XLCell object to be moved
         * @note The move constructor has been deleted, as it makes no sense to move a cell.
         */
        XLCell(XLCell&& other) noexcept;

        /**
         * @brief Destructor
         * @note Using the default destructor
         */
        virtual ~XLCell();

        /**
         * @brief Copy assignment operator
         * @param other The XLCell object to be copy assigned
         * @return A reference to the new object
         * @note Copies only the cell contents, not the pointer to parent worksheet etc.
         */
        virtual XLCell& operator=(const XLCell& other);

        /**
         * @brief Move assignment operator
         * @param other The XLCell object to be move assigned
         * @return A reference to the new object
         * @note The move assignment constructor has been deleted, as it makes no sense to move a cell.
         */
        virtual XLCell& operator=(XLCell&& other) noexcept;

        /**
         * @brief Copy contents of a cell, value & formula
         * @param other The XLCell object from which to copy
         */
        void copyFrom(XLCell const& other);

        /**
         * @brief test if cell object has no (valid) content
         * @return
         */
        bool empty() const;

        /**
         * @brief opposite of empty()
         * @return
         */
        explicit operator bool() const;

        /**
         * @brief clear all cell content and attributes except for the cell reference (attribute r)
         * @param keep do not clear cell properties whose flags are set in keep (XLKeepCellStyle, XLKeepCellType,
         *              XLKeepCellValue, XLKeepCellFormula), flags can be combined with bitwise OR
         * @note due to the way OOXML separates comments from the cells, this function will *not* clear a cell comment - refer to
         * XLComments& XLSheet::comments() for that
         */
        void clear(uint32_t keep);

        /**
         * @brief Retrieves the mutable value proxy for the cell.
         * @return Reference to the XLCellValueProxy.
         */
        XLCellValueProxy& value();

        /**
         * @brief Retrieves the read-only value proxy for the cell.
         * @return Const reference to the XLCellValueProxy.
         */
        const XLCellValueProxy& value() const;

        /**
         * @brief get the XLCellReference object for the cell.
         * @return A reference to the cells' XLCellReference object.
         */
        XLCellReference cellReference() const;

        /**
         * @brief get the XLCell object from the current cell offset
         * @return A reference to the XLCell object.
         */
        XLCell offset(uint16_t rowOffset, uint16_t colOffset) const;

        /**
         * @brief test if cell has a formula (XML) node, even if it is an empty string
         * @return true if XML has a formula node, empty or not - otherwise false
         */
        bool hasFormula() const;

        /**
         * @brief Retrieves the mutable formula proxy for the cell.
         * @return Reference to the XLFormulaProxy.
         */
        XLFormulaProxy& formula();

        /**
         * @brief Retrieves the read-only formula proxy for the cell.
         * @return Const reference to the XLFormulaProxy.
         */
        const XLFormulaProxy& formula() const;

        /**
         */
        std::string getString() const { return value().getString(); }

        /**
         * @brief Get the array index of xl/styles.xml:<styleSheet>:<cellXfs> for the style used in this cell.
         *        This value is stored in the s attribute of a cell like so: s="2"
         * @returns The index of the applicable format style
         */
        XLStyleIndex cellFormat() const;

        /**
         * @brief Set the cell style (attribute s) with a reference to the array index of xl/styles.xml:<styleSheet>:<cellXfs>
         * @param cellFormatIndex The style to set, corresponding to the nidex of XLStyles::cellStyles()
         * @returns True on success, false on failure
         */
        XLCell& setCellFormat(XLStyleIndex cellFormatIndex);

        /**
         * @brief Apply a high-level XLStyle to this cell. Automatically creates or reuses
         * the required underlying XML structures in xl/styles.xml.
         * @param style The requested high-level style
         */
        XLCell& setStyle(const XLStyle& style);

        /**
         * @brief Print the XML contents of the XLCell using the underlying XMLNode print function
         */
        void print(std::basic_ostream<char>& ostr) const;

    private:
        /**
         * @param lhs
         * @param rhs
         * @return
         */
        static bool isEqual(const XLCell& lhs, const XLCell& rhs);
std::unique_ptr<XMLNode> m_cellNode;      /**< A pointer to the root XMLNode for the cell. */
        XLSharedStringsRef       m_sharedStrings; /**< */
        XLCellValueProxy         m_valueProxy;    /**< */
        XLFormulaProxy           m_formulaProxy;  /**< */
    };

    class OPENXLSX_EXPORT XLCellAssignable : public XLCell
    {
    public:
        /**
         * @brief Default constructor. Constructs a null object.
         */
        XLCellAssignable() : XLCell() {}

        /**
         * @brief Copy constructor. Constructs an assignable XLCell from an existing cell
         * @param other the cell to construct from
         */
        XLCellAssignable(XLCell const& other);
        XLCellAssignable(XLCellAssignable const& other);

        /**
         * @brief Move constructor. Constructs an assignable XLCell from a temporary (r)value
         * @param other the cell to construct from
         */
        XLCellAssignable(XLCell&& other);
        XLCellAssignable(XLCellAssignable&& other) noexcept;

        /**
         * @brief Copy assignment operator
         * @param other The XLCell object to be copy assigned
         * @return A reference to the new object
         * @note Copies only the cell contents, not the pointer to parent worksheet etc.
         */
        XLCellAssignable& operator=(const XLCell& other) override;
        XLCellAssignable& operator=(const XLCellAssignable& other);

        /**
         * @brief Move assignment operator -> overrides XLCell copy operator, becomes a copy operator
         * @param other The XLCell object to be copy assigned
         * @return A reference to the new object
         * @note Copies only the cell contents, not the pointer to parent worksheet etc.
         */
        XLCellAssignable& operator=(XLCell&& other) noexcept override;
        XLCellAssignable& operator=(XLCellAssignable&& other) noexcept;

        /**
         * @brief Templated assignment operator.
         * @tparam T The type of the value argument.
         * @param value The value.
         * @return A reference to the assigned-to object.
         */
        template<typename T,
                 typename = std::enable_if_t<
                     std::is_integral_v<T> or std::is_floating_point_v<T> or std::is_same_v<std::decay_t<T>, std::string> ||
                     std::is_same_v<std::decay_t<T>, std::string_view> or std::is_same_v<std::decay_t<T>, const char*> ||
                     std::is_same_v<std::decay_t<T>, char*> or std::is_same_v<T, XLDateTime>>>
        XLCellAssignable& operator=(T value)
        {
            XLCell::value() = value;    // forward implementation to templated XLCellValue& XLCellValue::operator=(T value)
            return *this;
        }
    };
}    // namespace OpenXLSX

namespace OpenXLSX
{
    /**
         * @param lhs
     * @param rhs
     * @return
     */
    inline bool operator==(const XLCell& lhs, const XLCell& rhs) { return XLCell::isEqual(lhs, rhs); }

    /**
         * @param lhs
     * @param rhs
     * @return
     */
    inline bool operator!=(const XLCell& lhs, const XLCell& rhs) { return !XLCell::isEqual(lhs, rhs); }

    /**
     * @brief      ostream output of XLCell content
     * @param os   the ostream destination
     * @param c    the cell to output to the stream
     * @return
     */
    inline std::ostream& operator<<(std::ostream& os, const XLCell& c)
    {
        XLCellValue val = c.value();
        switch (val.type()) {
            case XLValueType::Integer: os << val.get<int64_t>(); break;
            case XLValueType::Float:   os << val.get<double>(); break;
            case XLValueType::Boolean: os << (val.get<bool>() ? "true" : "false"); break;
            case XLValueType::Empty:   break;
            default:                   os << c.getString(); break;
        }
        return os;
    }

    /**
     * @brief      ostream output of XLCellAssignable content
     * @param os   the ostream destination
     * @param c    the cell to output to the stream
     * @return
     */
    inline std::ostream& operator<<(std::ostream& os, const XLCellAssignable& c)
    {
        XLCellValue val = c.value();
        switch (val.type()) {
            case XLValueType::Integer: os << val.get<int64_t>(); break;
            case XLValueType::Float:   os << val.get<double>(); break;
            case XLValueType::Boolean: os << (val.get<bool>() ? "true" : "false"); break;
            case XLValueType::Empty:   break;
            default:                   os << c.getString(); break;
        }
        return os;
    }
}    // namespace OpenXLSX

#ifdef _MSC_VER    // conditionally enable MSVC specific pragmas to avoid other compilers warning about unknown pragmas
#    pragma warning(pop)
#endif    // _MSC_VER

#endif    // OPENXLSX_XLCELL_HPP
