// SPDX-License-Identifier: Apache-2.0
#pragma once

#include <vtbackend/CellFlags.hpp>
#include <vtbackend/Color.hpp>
#include <vtbackend/GraphicsAttributes.hpp>
#include <vtbackend/Hyperlink.hpp>
#include <vtbackend/Image.hpp>
#include <vtbackend/LineFlags.hpp>
#include <vtbackend/Primitives.hpp>
#include <vtbackend/TextScale.hpp>

#include <crispy/AlignedAllocator.hpp>

#include <cstdint>
#include <optional>
#include <ranges>

namespace vtbackend
{

/// Convenience alias: 64-byte aligned vector for SIMD-friendly SoA arrays.
template <typename T>
using AlignedVector = crispy::AlignedVector<T>;

/// Maximum number of codepoints in a grapheme cluster (matches CompactCell::MaxCodepoints).
///
/// Sized by the longest grapheme cluster real text produces, not by a round number. The RGI emoji
/// set reaches ten codepoints -- 👨🏻‍❤️‍💋‍👨🏻 (kiss: man, man, with skin tones) is
/// `U+1F468 U+1F3FB ZWJ U+2764 U+FE0F ZWJ U+1F48B ZWJ U+1F468 U+1F3FB` -- and a tag-sequence flag
/// reaches seven. A cap of 7 truncated the ten-codepoint family, which then split into two wide
/// cells and advanced the cursor four columns instead of two; jquast's ucs-detect counted 43 such
/// sequences.
///
/// 16 leaves headroom above the longest currently-defined sequence. Raising it costs nothing in
/// layout: only @c clusterPool grows in the worst case, and no element type changes.
inline constexpr uint8_t MaxGraphemeClusterSize = 16;
static_assert(MaxGraphemeClusterSize <= 255, "clusterSize is a uint8_t");

/// Structure-of-Arrays storage for one terminal line.
///
/// Each array has exactly `columns` elements OR is empty (blank state).
/// In the blank state, all six AlignedVectors have size 0 — the line is logically
/// `cols` wide and uniformly filled with @c fillAttrs. Reads are short-circuited by
/// the owning @c Line; writes call @c materialize() first to inflate the arrays
/// via @c initializeLineSoA. Blank lines are O(1) to construct and reset.
///
/// Arrays are grouped into tiers by access frequency:
/// - Tier 1 (Hot): codepoints, widths — touched on every character write
/// - Tier 2 (Warm): colors, flags — touched when SGR is non-default
/// - Tier 3 (Cold): hyperlinks — rarely non-default
///
/// Grapheme clusters with multiple codepoints store the primary codepoint in @c codepoints[]
/// and extra codepoints in a flat @c clusterPool, indexed by @c clusterPoolIndex[].
struct LineSoA
{
    // --- Tier 1: Hot — touched on every character write and render ---

    /// Primary Unicode codepoint per cell (first codepoint of the grapheme cluster).
    AlignedVector<char32_t> codepoints;

    /// East Asian Width display width per cell (1 or 2 columns).
    AlignedVector<uint8_t> widths;

    /// Per-cell vertical scale in cells, from the kitty text sizing protocol (`OSC 66` `s=`).
    ///
    /// 1 for ordinary text, which is why an all-zero line is still materialized to 1. Kept beside
    /// `widths` rather than folded into CellFlags because it is a 1..7 magnitude, not a flag.
    AlignedVector<uint8_t> scales;

    // --- Tier 2: Warm — SGR "pen" attributes, always read/written together ---

    /// Graphics attributes per cell (foreground, background, underline color + flags).
    /// Packed as a single struct for cache locality — one write per cell instead of four.
    AlignedVector<GraphicsAttributes> sgr;

    // --- Tier 3: Cold — rarely non-default ---

    /// Hyperlink ID per cell.
    AlignedVector<HyperlinkId> hyperlinks;

    /// Fractional scale and alignment per cell, packed as `n:4 | d:4 | v:2 | h:2`.
    /// @see vtbackend::packTextScaleExtras
    ///
    /// Cold on purpose: `scale` above is read on every erase, selection and render, while these are
    /// zero for all but a vanishing fraction of cells. 0 means "ordinary", so a zeroed line is right.
    AlignedVector<uint16_t> textScaleExtras;

    // --- Grapheme cluster overflow ---

    /// Number of codepoints in the grapheme cluster at each cell.
    /// 0 = empty cell, 1 = single codepoint (primary only), 2+ = has extras in clusterPool.
    AlignedVector<uint8_t> clusterSize;

    /// Index into @c clusterPool where this cell's extra codepoints start.
    /// Only meaningful when @c clusterSize[col] >= 2.
    AlignedVector<uint16_t> clusterPoolIndex;

    /// Flat pool of extra codepoints for all multi-codepoint grapheme clusters on this line.
    /// Cleared on line reset (lazy compaction).
    std::vector<char32_t> clusterPool;

    // --- Image fragments (extremely rare, null 99.99% of lines) ---

    /// Column → image fragment mapping. Disengaged when no images on this line.
    /// Engaged on first setImageFragment(), reset on resetLine().
    using ImageFragmentMap = std::unordered_map<uint16_t, std::shared_ptr<ImageFragment>>;
    std::optional<ImageFragmentMap> imageFragments;

    // --- Line-level metadata ---

    /// Line flags (Wrappable, Wrapped, Marked, etc.).
    LineFlags lineFlags {};

    /// Number of columns populated with content (for efficient trim operations).
    ColumnCount usedColumns {};

    /// Cached: true while this line can be rendered as ONE batched RenderLine.
    ///
    /// That representation is a flat string of one codepoint per column with uniform attributes, so
    /// the flag answers "is the line still expressible that way", not merely "is its SGR uniform".
    /// Set to true on line init/reset, and cleared when a cell becomes inexpressible:
    ///
    /// - its SGR or hyperlink differs from the first cell's (@see writeCellToSoA)
    /// - it is a wide cell's continuation (@see fillWideCharContinuation)
    /// - it carries an image fragment (@see BasicCellProxy::setImageFragment)
    /// - it holds a grapheme cluster, i.e. more than one codepoint (@see appendCodepointToCluster)
    ///
    /// Anything that makes a cell fail that test must clear this, or @c Line::trivialBuffer will
    /// silently drop what it cannot represent. Read as O(1) by the render path -- and by
    /// @c Terminal::maxBulkTextSequenceWidth, which gates the parser's bulk-write path on it.
    bool trivial = true;

    /// The GraphicsAttributes used in the most recent full-line reset/clear.
    /// Used by writeAsciiToSoA and resetLine to skip redundant fill operations
    /// when the line is being rewritten with the same default attributes.
    GraphicsAttributes fillAttrs {};
};

// ---------------------------------------------------------------------------
// Image fragment helpers
// ---------------------------------------------------------------------------

/// Removes a Replace-layer image fragment at the given column.
/// Below and Above layer images coexist with text; only Replace layer images are destroyed.
inline void clearReplacedImageFragment(std::optional<LineSoA::ImageFragmentMap>& fragments, uint16_t col)
{
    if (!fragments)
        return;
    if (auto const it = fragments->find(col); it != fragments->end())
    {
        if (!shouldPreserveImageOnTextWrite(it->second))
            fragments->erase(it);
    }
}

/// Range overload: clears Replace-layer image fragments across [startCol, startCol+count).
inline void clearReplacedImageFragments(std::optional<LineSoA::ImageFragmentMap>& fragments,
                                        uint16_t startCol,
                                        size_t count)
{
    if (!fragments)
        return;
    for (auto const col: std::views::iota(startCol, static_cast<uint16_t>(startCol + count)))
        clearReplacedImageFragment(fragments, col);
}

// ---------------------------------------------------------------------------
// LineSoA helper functions
// ---------------------------------------------------------------------------

/// Initialize all arrays to the given column count with default values.
/// @param line      The LineSoA to initialize.
/// @param cols      Number of columns.
/// @param fillAttrs Default graphics attributes for empty cells.
void initializeLineSoA(LineSoA& line, ColumnCount cols, GraphicsAttributes const& fillAttrs = {});

/// Initialize a line in the blank state: all arrays empty, but @c fillAttrs and @c trivial set.
/// O(1) — no per-column allocation. The owning @c Line tracks the logical column count
/// separately. Reads short-circuit; writes must call @c materialize() first.
/// @param line      The LineSoA to put into the blank state.
/// @param fillAttrs Default graphics attributes that empty cells appear to have.
void initializeBlankLineSoA(LineSoA& line, GraphicsAttributes const& fillAttrs = {}) noexcept;

/// Test whether a LineSoA is in the blank (un-materialized) state.
/// @return true iff @c codepoints is empty (all six arrays are empty by invariant).
[[nodiscard]] inline bool isBlankLineSoA(LineSoA const& line) noexcept
{
    return line.codepoints.empty();
}

/// Resize all arrays to a new column count, preserving existing data.
/// New columns (if growing) are initialized with @p fillAttrs.
void resizeLineSoA(LineSoA& line, ColumnCount newCols, GraphicsAttributes const& fillAttrs = {});

/// Clear a column range to empty cells with the given attributes.
/// Each array is filled independently via std::fill_n (SIMD auto-vectorizable).
void clearRange(LineSoA& line, size_t from, size_t count, GraphicsAttributes const& attrs);

/// Reset an entire line to empty state.
void resetLine(LineSoA& line, ColumnCount cols, GraphicsAttributes const& fillAttrs = {});

/// Copy a column range from one line to another (bulk memcpy per array).
/// Handles cluster pool transfer and image fragments.
void copyColumns(LineSoA const& src, size_t srcCol, LineSoA& dst, size_t dstCol, size_t count);

/// Move columns within a line (for insert/delete character operations).
/// Uses memmove per array to handle overlapping ranges.
void moveColumns(LineSoA& line, size_t srcCol, size_t dstCol, size_t count);

/// Find the last non-empty column (scan codepoints[] for last non-zero).
/// @return Number of used columns (0 if line is empty).
[[nodiscard]] size_t trimBlankRight(LineSoA const& line, size_t cols);

/// Iterate over all codepoints of a grapheme cluster at the given column.
/// @param line The LineSoA to read from.
/// @param col  Column index.
/// @param f    Callback invoked with each char32_t codepoint.
template <typename F>
void forEachCodepoint(LineSoA const& line, size_t col, F f)
{
    if (line.clusterSize[col] == 0)
        return;

    f(line.codepoints[col]);

    if (line.clusterSize[col] > 1)
    {
        auto const start = line.clusterPoolIndex[col];
        auto const extraCount = static_cast<size_t>(line.clusterSize[col] - 1);
        for (size_t i = 0; i < extraCount; ++i)
            f(line.clusterPool[start + i]);
    }
}

/// Append a continuation codepoint to an existing grapheme cluster.
/// @return Width change (0 in current implementation unless AllowWidthChange is enabled).
/// Whether a grapheme cluster's width may be revised after its first codepoint.
///
/// DEC mode 2027 (`DECMode::Unicode`) is what an application uses to say it expects the terminal to
/// measure whole clusters. With the mode reset, a terminal is expected to behave the older way: the
/// first codepoint decides, and a variation selector arriving later changes nothing.
enum class ClusterWidthPolicy : uint8_t
{
    /// Mode 2027 set: the whole cluster decides its width, so a late VS16 can widen it.
    ///
    /// The measurement is symmetric -- it reports a narrowing as readily as a widening -- but the
    /// callers only ever act on growth: terminal-unicode-core has VS15 change the presentation
    /// without touching the width, because a cluster already on screen cannot give a column back.
    /// @see Screen::applyClusterWidthChange.
    ClusterAware,

    /// Mode 2027 reset: the first codepoint decides and nothing later changes it.
    FirstCodepoint,
};

/// Appends @p codepoint to the grapheme cluster at @p col.
///
/// Also clears @c LineSoA::trivial, because a cell holding more than one codepoint cannot be
/// represented on the batched render path (@see Line::trivialBuffer, which keeps one codepoint per
/// column). Any future path that grows a cluster without going through here owes the same clear;
/// without it the extra codepoints are dropped from the rendered text with no diagnostic.
///
/// @return by how many columns the cluster's width changed, which is always 0 under
///         ClusterWidthPolicy::FirstCodepoint.
int appendCodepointToCluster(LineSoA& line,
                             size_t col,
                             char32_t codepoint,
                             ClusterWidthPolicy policy = ClusterWidthPolicy::ClusterAware);

/// Clear cluster overflow data for a specific column.
/// Note: this does not compact the pool; garbage entries remain until line reset.
void clearClusterExtras(LineSoA& line, size_t col);

} // namespace vtbackend
