#pragma once

#include "fl/gfx/canvas.h"  // IWYU pragma: keep
#include "fl/gfx/draw_mode.h"
#include "fl/math/distance_lut.h"  // IWYU pragma: keep
#include "fl/math/integer_math.h"  // IWYU pragma: keep
#include "fl/math/fixed_point/s16x16.h"
#include "fl/math/math.h"
#include "fl/math/scale8.h"

namespace fl {
namespace gfx {

/// sqrt is provided by fl::sqrt overloads in math.h for:
/// - All floating-point types (float, double)
/// - All fixed-point types (s8x8, s12x4, s16x16, s24x8, u8x8, u12x4, u16x16, u24x8)
/// - Integer types (via cast to float)
/// Using: using fl::sqrt; (in drawStrokeLine template to find overloads via ADL)

/// Convert a normalized [0,1] Coord value to u8 (0-255) for nscale8.
/// Uses integer arithmetic for fixed-point types; float for float/double.
/// Generic: for fixed-point types (those with .to_int())
template<typename T>
inline fl::u8 coordToU8(T alpha) {
    return (fl::u8)fl::clamp((alpha * T(255.0f) + T(0.5f)).to_int(), 0, 255);
}

// Specialization for float
template<>
inline fl::u8 coordToU8<float>(float alpha) {
    return (fl::u8)fl::round(alpha * 255.0f);
}

// Specialization for double
template<>
inline fl::u8 coordToU8<double>(double alpha) {
    return (fl::u8)fl::round(alpha * 255.0);
}

// Specialization for int
template<>
inline fl::u8 coordToU8<int>(int alpha) {
    return (fl::u8)fl::clamp(alpha, 0, 255);
}

// Specialization for s16x16: uses from_raw, no float at all
template<>
inline fl::u8 coordToU8<fl::s16x16>(fl::s16x16 alpha) {
    constexpr fl::s16x16 c255 = fl::s16x16::from_raw(255 * fl::s16x16::SCALE);
    constexpr fl::s16x16 half = fl::s16x16::from_raw(1 << 15);
    return (fl::u8)fl::clamp((alpha * c255 + half).to_int(), (fl::i32)0, (fl::i32)255);
}

/// Convert an integer to Coord without float intermediate.
/// For s16x16: uses from_raw (pure integer shift). For float: implicit cast.
template<typename T>
inline T fromInt(int n) { return T(static_cast<float>(n)); }

template<>
inline fl::s16x16 fromInt<fl::s16x16>(int n) {
    return fl::s16x16::from_raw(n * fl::s16x16::SCALE);
}

/// Convert rational p/q to Coord without float.
/// For s16x16: integer multiply+divide. For float: float divide.
template<typename T>
inline T fromFrac(int p, int q) { return T(static_cast<float>(p) / static_cast<float>(q)); }

template<>
inline fl::s16x16 fromFrac<fl::s16x16>(int p, int q) {
    return fl::s16x16::from_raw((p * fl::s16x16::SCALE) / q);
}

/// Divide Coord by 2 using shift (avoids expensive division on embedded).
/// s16x16: raw right-shift (1 instruction). float: multiply by 0.5.
template<typename T>
inline T halfOf(T val) { return val / fromInt<T>(2); }

template<>
inline fl::s16x16 halfOf<fl::s16x16>(fl::s16x16 val) {
    return fl::s16x16::from_raw(val.raw() >> 1);
}

template<>
inline float halfOf<float>(float val) { return val * 0.5f; }

template<>
inline double halfOf<double>(double val) { return val * 0.5; }

template<>
inline int halfOf<int>(int val) { return val >> 1; }

/// Helper to convert any coordinate type to int
/// Supports: s16x16 (via to_int()), float, int, and other arithmetic types
template<typename T>
inline int toInt(const T& val) {
    return val.to_int();  // For s16x16 and similar
}

// Specialization for float
template<>
inline int toInt<float>(const float& val) {
    return static_cast<int>(val);
}

// Specialization for int (passthrough)
template<>
inline int toInt<int>(const int& val) {
    return val;
}

// Specialization for double
template<>
inline int toInt<double>(const double& val) {
    return static_cast<int>(val);
}

/// Internal helper: add or set pixel to rectangular buffer with bounds checking
/// Direct row-major indexing: pixels[y * width + x]
/// Templated on Overwrite for compile-time dispatch (no per-pixel branch).
template<typename PixelT, bool Overwrite>
inline void addPixelToBuffer(PixelT* pixels, int width, int height,
                              int x, int y, const PixelT& color) {
    if (x >= 0 && x < width && y >= 0 && y < height) {
        if (Overwrite)
            pixels[y * width + x] = color;
        else
            pixels[y * width + x] += color;
    }
}

namespace detail {

// void_t for C++11 SFINAE (detects member existence)
template<typename...> struct voider { typedef void type; };

// Trait: does this Coord type need division instead of reciprocal multiply?
// Default (float, double, int — no FRAC_BITS member): use multiply
template<typename Coord, typename = void>
struct needs_division : fl::false_type {};

// Fixed-point types with FRAC_BITS < 16: reciprocal too imprecise, use division
template<typename Coord>
struct needs_division<Coord,
    typename voider<fl::bool_constant<(Coord::FRAC_BITS < 16)>>::type>
    : fl::bool_constant<(Coord::FRAC_BITS < 16)> {};

// Tag-dispatch: high precision → multiply by precomputed reciprocal
template<typename Coord>
inline Coord aaRatioDispatch(Coord num, Coord, Coord inv_denom, fl::false_type) {
    return num * inv_denom;
}

// Tag-dispatch: low precision → direct division (more accurate)
template<typename Coord>
inline Coord aaRatioDispatch(Coord num, Coord denom, Coord, fl::true_type) {
    return num / denom;
}

}  // namespace detail

/// Compute num/denom using the best strategy for the Coord type:
/// - float, s16x16, s8x24 (FRAC_BITS >= 16): multiply by precomputed reciprocal
/// - s8x8, s12x4, s24x8 (FRAC_BITS < 16): direct division
template<typename Coord>
inline Coord aaRatio(Coord num, Coord denom, Coord inv_denom) {
    return detail::aaRatioDispatch(num, denom, inv_denom,
        typename detail::needs_division<Coord>::type());
}

// ============================================================================
// Integer-optimized helpers for disc/ring/line drawing
// ============================================================================

namespace detail {

/// Convert any Coord type to 8.8 fixed-point (fl::i32).
/// Generic fallback: use to_float() conversion.
template<typename Coord>
inline fl::i32 toFixed8(Coord val) {
    return static_cast<fl::i32>(val.to_float() * 256.0f);
}

template<>
inline fl::i32 toFixed8<float>(float val) {
    return static_cast<fl::i32>(val * 256.0f);
}

template<>
inline fl::i32 toFixed8<double>(double val) {
    return static_cast<fl::i32>(val * 256.0);
}

template<>
inline fl::i32 toFixed8<int>(int val) {
    return static_cast<fl::i32>(static_cast<fl::u32>(val) << 8);
}

template<>
inline fl::i32 toFixed8<fl::s16x16>(fl::s16x16 val) {
    // Q16.16 → Q8.8: shift right by 8
    return static_cast<fl::i32>(val.raw() >> 8);
}

/// Precompute right-shift and reciprocal multiplier for AA division.
/// Converts 32-bit AA division: (diff * 255u) / band  (~60 cycles on AVR)
/// into a multiply-by-reciprocal: ((diff >> shift) * inv) >> 8  (~14 cycles on AVR)
/// where inv = round(255 * 256 / scaled).
inline void computeBandShift(fl::i32 band, fl::u8 &shift_out, fl::u16 &inv_out) {
    fl::u8 sh = 0;
    fl::u32 tmp = static_cast<fl::u32>(band);
    while (tmp > 255u) { tmp >>= 1; ++sh; }
    shift_out = sh;
    // Reciprocal: (255 * 256 + scaled/2) / scaled — rounds to nearest
    fl::u8 scaled = static_cast<fl::u8>(tmp);
    inv_out = static_cast<fl::u16>((65280u + (scaled >> 1)) / scaled);
}

/// Convert any Coord type to Q16.16 raw i32 for integer inner loops.
/// s16x16: direct raw access (already Q16.16, zero overhead).
/// float/double: multiply by 65536. int: shift left by 16.
template<typename Coord>
inline fl::i32 toQ16(Coord val) {
    return static_cast<fl::i32>(val.to_float() * 65536.0f);
}

template<>
inline fl::i32 toQ16<float>(float val) {
    return static_cast<fl::i32>(val * 65536.0f);
}

template<>
inline fl::i32 toQ16<double>(double val) {
    return static_cast<fl::i32>(val * 65536.0);
}

template<>
inline fl::i32 toQ16<int>(int val) {
    return static_cast<fl::i32>(val) << 16;
}

template<>
inline fl::i32 toQ16<fl::s16x16>(fl::s16x16 val) {
    return val.raw();
}


/// Disc context: bundles per-circle constants into a struct passed by reference.
/// This reduces function-call overhead on register-poor architectures (AVR: 12 params
/// → 1 pointer), matching the struct-based approach used in hand-optimized code.
template<typename PixelT>
struct DiscCtx {
    fl::i32 xdelta0;
    int xmin, xmax;
    fl::i32 rin2, rout2;
    fl::u8 band_shift;
    fl::u16 band_inv;      // reciprocal: (255 * 256 + scaled/2) / scaled
    PixelT color;
};

/// Ring context: bundles all per-circle constants into a struct passed by reference.
/// This reduces function-call overhead on register-poor architectures (AVR: 16 params
/// → 1 pointer), matching the struct-based approach used in hand-optimized code.
template<typename PixelT>
struct RingCtx {
    fl::i32 xdelta0;
    int xmin, xmax;
    fl::i32 ii2, io2, oi2, oo2;
    fl::u8 inner_shift, outer_shift;
    fl::u16 inner_inv, outer_inv;  // reciprocals: (255 * 256 + scaled/2) / scaled
    PixelT color;
};

/// Render one scanline of a disc using incremental d².
/// Templated on Overwrite for compile-time dispatch (no per-pixel branch).
/// Uses (n+1)² = n² + 2n + 1 identity — zero multiplies in the inner loop.
template<typename PixelT, bool Overwrite>
inline void renderDiscRow(PixelT* buf, int w, int py,
                          fl::i32 d2_row,
                          const DiscCtx<PixelT>& f) {
    PixelT* ptr = &buf[py * w + f.xmin];
    fl::i32 d2 = d2_row;
    fl::i32 xd = f.xdelta0;
    int px = f.xmin;
    // Phase 1: Skip outside pixels (left, d2 decreasing)
    while (px <= f.xmax && d2 >= f.rout2) {
        d2 += xd; xd += 131072; ++ptr; ++px;
    }
    // Phase 2: Outer AA fringe (left)
    while (px <= f.xmax && d2 >= f.rin2 && d2 < f.rout2) {
        fl::u16 diff = static_cast<fl::u16>(static_cast<fl::u32>(f.rout2 - d2) >> f.band_shift);
        fl::u8 br = static_cast<fl::u8>((diff * f.band_inv) >> 8);
        PixelT c = f.color; c.nscale8(br);
        if (Overwrite) *ptr = c; else *ptr += c;
        d2 += xd; xd += 131072; ++ptr; ++px;
    }
    // Phase 3: Full brightness interior (d2 hits minimum, then increases)
    while (px <= f.xmax && d2 < f.rin2) {
        if (Overwrite) *ptr = f.color; else *ptr += f.color;
        d2 += xd; xd += 131072; ++ptr; ++px;
    }
    // Phase 4: Outer AA fringe (right, d2 increasing)
    while (px <= f.xmax && d2 < f.rout2) {
        fl::u16 diff = static_cast<fl::u16>(static_cast<fl::u32>(f.rout2 - d2) >> f.band_shift);
        fl::u8 br = static_cast<fl::u8>((diff * f.band_inv) >> 8);
        PixelT c = f.color; c.nscale8(br);
        if (Overwrite) *ptr = c; else *ptr += c;
        d2 += xd; xd += 131072; ++ptr; ++px;
    }
}

/// Render one scanline of a ring using incremental d² with phase-based scanning.
/// Templated on Overwrite for compile-time dispatch (no per-pixel branch).
/// Zones (left to right): outside → outer-AA → solid → inner-AA → hole →
///                         inner-AA → solid → outer-AA → outside.
template<typename PixelT, bool Overwrite>
inline void renderRingRow(PixelT* buf, int w, int py,
                          fl::i32 d2_row,
                          const RingCtx<PixelT>& g) {
    PixelT* ptr = &buf[py * w + g.xmin];
    fl::i32 d2 = d2_row;
    fl::i32 xd = g.xdelta0;
    int px = g.xmin;
    // Preamble: left exterior (d2 >= oo2)
    while (px <= g.xmax && d2 >= g.oo2) {
        d2 += xd; xd += 131072; ++ptr; ++px;
    }
    // Phase 1: outer-left AA fringe (oi2 <= d2 < oo2)
    while (px <= g.xmax && d2 >= g.oi2 && d2 < g.oo2) {
        fl::u16 diff = static_cast<fl::u16>(static_cast<fl::u32>(g.oo2 - d2) >> g.outer_shift);
        fl::u8 br = static_cast<fl::u8>((diff * g.outer_inv) >> 8);
        PixelT c = g.color; c.nscale8(br);
        if (Overwrite) *ptr = c; else *ptr += c;
        d2 += xd; xd += 131072; ++ptr; ++px;
    }
    // Phase 2: full-brightness band left (io2 <= d2 < oi2)
    while (px <= g.xmax && d2 >= g.io2 && d2 < g.oi2) {
        if (Overwrite) *ptr = g.color; else *ptr += g.color;
        d2 += xd; xd += 131072; ++ptr; ++px;
    }
    // Phase 3: inner-left AA fringe (ii2 <= d2 < io2)
    while (px <= g.xmax && d2 >= g.ii2 && d2 < g.io2) {
        fl::u16 diff = static_cast<fl::u16>(static_cast<fl::u32>(d2 - g.ii2) >> g.inner_shift);
        fl::u8 br = static_cast<fl::u8>((diff * g.inner_inv) >> 8);
        PixelT c = g.color; c.nscale8(br);
        if (Overwrite) *ptr = c; else *ptr += c;
        d2 += xd; xd += 131072; ++ptr; ++px;
    }
    // Phase 4: transparent hole (d2 < ii2)
    while (px <= g.xmax && d2 < g.ii2) {
        d2 += xd; xd += 131072; ++ptr; ++px;
    }
    // Phase 5: inner-right AA fringe (ii2 <= d2 < io2)
    while (px <= g.xmax && d2 >= g.ii2 && d2 < g.io2) {
        fl::u16 diff = static_cast<fl::u16>(static_cast<fl::u32>(d2 - g.ii2) >> g.inner_shift);
        fl::u8 br = static_cast<fl::u8>((diff * g.inner_inv) >> 8);
        PixelT c = g.color; c.nscale8(br);
        if (Overwrite) *ptr = c; else *ptr += c;
        d2 += xd; xd += 131072; ++ptr; ++px;
    }
    // Phase 6: full-brightness band right (io2 <= d2 < oi2)
    while (px <= g.xmax && d2 >= g.io2 && d2 < g.oi2) {
        if (Overwrite) *ptr = g.color; else *ptr += g.color;
        d2 += xd; xd += 131072; ++ptr; ++px;
    }
    // Phase 7: outer-right AA fringe (oi2 <= d2 < oo2)
    while (px <= g.xmax && d2 >= g.oi2 && d2 < g.oo2) {
        fl::u16 diff = static_cast<fl::u16>(static_cast<fl::u32>(g.oo2 - d2) >> g.outer_shift);
        fl::u8 br = static_cast<fl::u8>((diff * g.outer_inv) >> 8);
        PixelT c = g.color; c.nscale8(br);
        if (Overwrite) *ptr = c; else *ptr += c;
        d2 += xd; xd += 131072; ++ptr; ++px;
    }
}

/// Stroke line context: bundles per-line constants into a struct passed by
/// reference. This reduces function-call overhead on register-poor architectures
/// (AVR: many i32 params → 1 pointer), matching the struct-based approach used
/// in drawDisc/drawRing.
template<typename PixelT>
struct StrokeCtx {
    fl::i32 threshold_q;
    fl::i32 len2_q;
    fl::i32 dx_q, dy_q;
    fl::u8 aa_shift;
    fl::u16 aa_inv;
    fl::i32 x0_8, y0_8, x1_8, y1_8;
    fl::i32 r_max2_8;
    fl::u8 cap_shift;
    fl::u16 cap_inv;
    fl::i32 dot_ext_q;
    LineCap cap;
    PixelT color;
    int xmin, xmax;
};

/// Render one scanline of a stroke line using phase-based scanning.
/// Templated on Overwrite for compile-time dispatch (no per-pixel branch).
/// AA uses precomputed shift+reciprocal — zero Coord multiplies.
template<typename PixelT, bool Overwrite>
inline void renderStrokeRow(PixelT* buf, int w, int py,
                            fl::i32 cross_start, fl::i32 dot_start,
                            const StrokeCtx<PixelT>& sc) {
    PixelT* ptr = &buf[py * w + sc.xmin];
    fl::i32 cross = cross_start;
    fl::i32 dot = dot_start;
    int px = sc.xmin;
    const fl::i32 thr = sc.threshold_q;
    const fl::i32 neg_thr = -thr;

    // Phase 1: Skip pixels outside the band
    while (px <= sc.xmax && (cross >= thr || cross < neg_thr)) {
        cross += sc.dy_q;
        dot += sc.dx_q;
        ++ptr; ++px;
    }

    // Hoist per-row endcap ey values (py is constant within a row)
    fl::i32 py8 = static_cast<fl::i32>(py) << 8;
    fl::i32 ey8_y0 = py8 - sc.y0_8;
    fl::i32 ey8_y1 = py8 - sc.y1_8;

    // Phase 2: Process visible pixels (|cross| < threshold)
    while (px <= sc.xmax && cross < thr && cross >= neg_thr) {
        fl::i32 abs_cross = (cross < 0) ? -cross : cross;
        bool on_segment = (dot >= 0 && dot <= sc.len2_q);

        if (on_segment) {
            // Integer AA: shift+multiply (no Coord multiply)
            fl::u16 shifted = static_cast<fl::u16>(
                static_cast<fl::u32>(abs_cross) >> sc.aa_shift);
            fl::u8 linear_idx = static_cast<fl::u8>((shifted * sc.aa_inv) >> 8);
            // Fast approximate x/255 ≈ (x + 1 + (x >> 8)) >> 8
            fl::u16 sq = static_cast<fl::u16>(linear_idx) * linear_idx;
            fl::u8 sq_idx = static_cast<fl::u8>((sq + 1 + (sq >> 8)) >> 8);
            PixelT c = sc.color;
            c.nscale8(FL_PGM_READ_BYTE_NEAR(&distanceAA_LUT[sq_idx]));
            if (Overwrite) *ptr = c; else *ptr += c;
        } else if (sc.cap == LineCap::ROUND) {
            // Endpoint distance in 8.8 fixed-point
            fl::i32 ex8;
            fl::i16 ey_s;
            if (dot < 0) {
                ex8 = (static_cast<fl::i32>(px) << 8) - sc.x0_8;
                ey_s = static_cast<fl::i16>(ey8_y0);
            } else {
                ex8 = (static_cast<fl::i32>(px) << 8) - sc.x1_8;
                ey_s = static_cast<fl::i16>(ey8_y1);
            }
            fl::i16 ex_s = static_cast<fl::i16>(ex8);
            // 16×16→32 multiply (4 MUL on AVR vs ~70 for 32×32)
            fl::i32 ed2 = static_cast<fl::i32>(ex_s) * ex_s +
                          static_cast<fl::i32>(ey_s) * ey_s;
            if (ed2 < sc.r_max2_8) {
                fl::u16 shifted = static_cast<fl::u16>(
                    static_cast<fl::u32>(ed2) >> sc.cap_shift);
                fl::u8 idx = static_cast<fl::u8>((shifted * sc.cap_inv) >> 8);
                PixelT c = sc.color;
                c.nscale8(FL_PGM_READ_BYTE_NEAR(&distanceAA_LUT[idx]));
                if (Overwrite) *ptr = c; else *ptr += c;
            }
        } else if (sc.cap == LineCap::SQUARE) {
            if (dot >= -sc.dot_ext_q && dot <= sc.len2_q + sc.dot_ext_q) {
                fl::u16 shifted = static_cast<fl::u16>(
                    static_cast<fl::u32>(abs_cross) >> sc.aa_shift);
                fl::u8 linear_idx = static_cast<fl::u8>((shifted * sc.aa_inv) >> 8);
                fl::u16 sq = static_cast<fl::u16>(linear_idx) * linear_idx;
                fl::u8 sq_idx = static_cast<fl::u8>((sq + 1 + (sq >> 8)) >> 8);
                PixelT c = sc.color;
                c.nscale8(FL_PGM_READ_BYTE_NEAR(&distanceAA_LUT[sq_idx]));
                if (Overwrite) *ptr = c; else *ptr += c;
            }
        }
        // FLAT: pixels outside [0, len2] are silently dropped

        cross += sc.dy_q;
        dot += sc.dx_q;
        ++ptr; ++px;
    }
    // Phase 3: remaining pixels are outside the band — nothing to do
}

}  // namespace detail

/// ============================================================================
/// CANVAS API (Primary - Cache Optimal)
/// ============================================================================
/// Graphics operations work on rectangular Canvas objects with direct row-major indexing.
/// All drawing functions use row-major indexing for cache-optimal performance.

/// @brief Wu antialiased line drawing
/// @param canvas Canvas with pixel buffer and dimensions
/// @param x0, y0, x1, y1 Line start and end coordinates (any arithmetic type)
/// @param color Pixel color
template<typename PixelT, typename Coord>
void drawLine(Canvas<PixelT>& canvas, const PixelT& color,
              Coord x0, Coord y0, Coord x1, Coord y1,
              fl::DrawMode mode);

template<typename PixelT, typename Coord>
void drawDisc(Canvas<PixelT>& canvas, const PixelT& color,
              Coord cx, Coord cy, Coord r,
              fl::DrawMode mode);

template<typename PixelT, typename Coord>
void drawRing(Canvas<PixelT>& canvas, const PixelT& color,
              Coord cx, Coord cy, Coord r, Coord thickness,
              fl::DrawMode mode);

template<typename PixelT, typename Coord>
void drawStrokeLine(Canvas<PixelT>& canvas, const PixelT& color,
                    Coord x0, Coord y0, Coord x1, Coord y1, Coord thickness,
                    LineCap cap, fl::DrawMode mode);

/// ============================================================================
/// LEGACY RASTERTARGET API (Deprecated - for backward compatibility)
/// ============================================================================

/// ============================================================================
/// CANVAS IMPLEMENTATIONS
/// ============================================================================

// ---------------------------------------------------------------------------
// drawLine: Integer Wu antialiased line (8.8 fixed-point internally)
// Uses int loop counter and bit-shift for AA — no Coord ops in inner loop.
// ---------------------------------------------------------------------------
namespace detail {

template<typename PixelT, typename Coord, bool Overwrite>
inline void drawLineCore(Canvas<PixelT>& canvas, const PixelT& color,
                         Coord x0, Coord y0, Coord x1, Coord y1) {
    PixelT* pixels = canvas.pixels;
    int width = canvas.width;
    int height = canvas.height;

    // Convert to 8.8 fixed-point
    fl::i32 fx0 = detail::toFixed8(x0);
    fl::i32 fy0 = detail::toFixed8(y0);
    fl::i32 fx1 = detail::toFixed8(x1);
    fl::i32 fy1 = detail::toFixed8(y1);

    bool steep = fl::abs(fy1 - fy0) > fl::abs(fx1 - fx0);
    if (steep) { fl::swap(fx0, fy0); fl::swap(fx1, fy1); }
    if (fx0 > fx1) { fl::swap(fx0, fx1); fl::swap(fy0, fy1); }

    fl::i32 dx8 = fx1 - fx0;
    fl::i32 dy8 = fy1 - fy0;

    // Gradient per pixel step in 8.8: (dy * 256) / dx
    fl::i32 gradient = (dx8 == 0) ? 0 : ((dy8 << 8) / dx8);

    // First endpoint: round x to nearest pixel boundary
    fl::i32 xend = (fx0 + 128) & ~0xFF;
    fl::i32 yend = fy0 + ((gradient * (xend - fx0)) >> 8);
    fl::i32 xgap = 256 - ((fx0 + 128) & 0xFF);  // rfpart(x0 + 0.5)
    int xpxl1 = xend >> 8;
    int ypxl1 = yend >> 8;
    fl::u8 yfrac1 = static_cast<fl::u8>(yend & 0xFF);

    // Second endpoint
    fl::i32 xend2 = (fx1 + 128) & ~0xFF;
    fl::i32 yend2 = fy1 + ((gradient * (xend2 - fx1)) >> 8);
    fl::i32 xgap2 = (fx1 + 128) & 0xFF;  // fpart(x1 + 0.5)
    int xpxl2 = xend2 >> 8;
    int ypxl2 = yend2 >> 8;
    fl::u8 yfrac2 = static_cast<fl::u8>(yend2 & 0xFF);

    // Draw first endpoint
    {
        fl::u8 b1 = static_cast<fl::u8>(((255 - yfrac1) * xgap) >> 8);
        fl::u8 b2 = static_cast<fl::u8>((yfrac1 * xgap) >> 8);
        if (steep) {
            PixelT c = color; c.nscale8(b1);
            addPixelToBuffer<PixelT, Overwrite>(pixels, width, height, ypxl1, xpxl1, c);
            c = color; c.nscale8(b2);
            addPixelToBuffer<PixelT, Overwrite>(pixels, width, height, ypxl1 + 1, xpxl1, c);
        } else {
            PixelT c = color; c.nscale8(b1);
            addPixelToBuffer<PixelT, Overwrite>(pixels, width, height, xpxl1, ypxl1, c);
            c = color; c.nscale8(b2);
            addPixelToBuffer<PixelT, Overwrite>(pixels, width, height, xpxl1, ypxl1 + 1, c);
        }
    }

    // Draw second endpoint
    {
        fl::u8 b1 = static_cast<fl::u8>(((255 - yfrac2) * xgap2) >> 8);
        fl::u8 b2 = static_cast<fl::u8>((yfrac2 * xgap2) >> 8);
        if (steep) {
            PixelT c = color; c.nscale8(b1);
            addPixelToBuffer<PixelT, Overwrite>(pixels, width, height, ypxl2, xpxl2, c);
            c = color; c.nscale8(b2);
            addPixelToBuffer<PixelT, Overwrite>(pixels, width, height, ypxl2 + 1, xpxl2, c);
        } else {
            PixelT c = color; c.nscale8(b1);
            addPixelToBuffer<PixelT, Overwrite>(pixels, width, height, xpxl2, ypxl2, c);
            c = color; c.nscale8(b2);
            addPixelToBuffer<PixelT, Overwrite>(pixels, width, height, xpxl2, ypxl2 + 1, c);
        }
    }

    // Main loop: integer counter, 8.8 intery accumulation
    fl::i32 intery = yend + gradient;
    if (steep) {
        for (int x = xpxl1 + 1; x < xpxl2; ++x) {
            int y = intery >> 8;
            fl::u8 frac = static_cast<fl::u8>(intery & 0xFF);
            PixelT c = color; c.nscale8(255 - frac);
            addPixelToBuffer<PixelT, Overwrite>(pixels, width, height, y, x, c);
            c = color; c.nscale8(frac);
            addPixelToBuffer<PixelT, Overwrite>(pixels, width, height, y + 1, x, c);
            intery += gradient;
        }
    } else {
        for (int x = xpxl1 + 1; x < xpxl2; ++x) {
            int y = intery >> 8;
            fl::u8 frac = static_cast<fl::u8>(intery & 0xFF);
            PixelT c = color; c.nscale8(255 - frac);
            addPixelToBuffer<PixelT, Overwrite>(pixels, width, height, x, y, c);
            c = color; c.nscale8(frac);
            addPixelToBuffer<PixelT, Overwrite>(pixels, width, height, x, y + 1, c);
            intery += gradient;
        }
    }
}

template<typename PixelT, typename Coord, bool Overwrite>
inline void drawDiscCore(Canvas<PixelT>& canvas, const PixelT& color,
                         Coord cx, Coord cy, Coord r);

template<typename PixelT, typename Coord, bool Overwrite>
inline void drawRingCore(Canvas<PixelT>& canvas, const PixelT& color,
                         Coord cx, Coord cy, Coord r, Coord thickness);

template<typename PixelT, typename Coord, bool Overwrite>
inline void drawStrokeLineCore(Canvas<PixelT>& canvas, const PixelT& color,
                               Coord x0, Coord y0, Coord x1, Coord y1,
                               Coord thickness, LineCap cap);

}  // namespace detail

template<typename PixelT, typename Coord>
inline void drawLine(Canvas<PixelT>& canvas, const PixelT& color,
                     Coord x0, Coord y0, Coord x1, Coord y1,
                     fl::DrawMode mode) {
    if (mode == fl::DrawMode::DRAW_MODE_OVERWRITE)
        detail::drawLineCore<PixelT, Coord, true>(canvas, color, x0, y0, x1, y1);
    else
        detail::drawLineCore<PixelT, Coord, false>(canvas, color, x0, y0, x1, y1);
}

// ---------------------------------------------------------------------------
// drawDiscCore: Templated on Overwrite for compile-time dispatch.
// ---------------------------------------------------------------------------
template<typename PixelT, typename Coord, bool Overwrite>
inline void detail::drawDiscCore(Canvas<PixelT>& canvas, const PixelT& color,
                                 Coord cx, Coord cy, Coord r) {
    PixelT* pixels = canvas.pixels;
    int width = canvas.width;
    int height = canvas.height;

    fl::i32 cx8 = detail::toFixed8(cx);
    fl::i32 cy8 = detail::toFixed8(cy);
    fl::i32 r8 = detail::toFixed8(r);

    fl::i32 rin8 = r8 - 128;   // r - 0.5 in 8.8
    fl::i32 rout8 = r8 + 128;  // r + 0.5 in 8.8

    fl::i32 rin2 = rin8 * rin8;
    fl::i32 rout2 = rout8 * rout8;
    fl::i32 band = rout2 - rin2;
    if (band <= 0) return;

    int ri = (rout8 >> 8) + 1;
    int cxi = cx8 >> 8;
    int cyi = cy8 >> 8;

    int xmin = cxi - ri; if (xmin < 0) xmin = 0;
    int xmax = cxi + ri; if (xmax >= width) xmax = width - 1;
    if (xmin > xmax) return;
    if (cyi + ri < 0 || cyi - ri >= height) return;

    fl::i32 dx8 = (static_cast<fl::i32>(xmin) << 8) - cx8;
    fl::i32 dx2 = dx8 * dx8;

    detail::DiscCtx<PixelT> fc;
    fc.xdelta0 = 512 * dx8 + 65536;
    fc.xmin = xmin; fc.xmax = xmax;
    fc.rin2 = rin2; fc.rout2 = rout2;
    detail::computeBandShift(band, fc.band_shift, fc.band_inv);
    fc.color = color;

    fl::i32 cyfrac = (static_cast<fl::i32>(cyi) << 8) - cy8;
    fl::i32 d2c = dx2 + cyfrac * cyfrac;

    if (cyi >= 0 && cyi < height)
        detail::renderDiscRow<PixelT, Overwrite>(pixels, width, cyi, d2c, fc);

    fl::i32 botd2 = d2c, botdelta = 512 * cyfrac + 65536;
    fl::i32 topd2 = d2c, topdelta = -512 * cyfrac + 65536;
    botd2 += botdelta; botdelta += 131072;
    topd2 += topdelta; topdelta += 131072;

    for (int dy = 1; dy <= ri; ++dy) {
        bool cbot = (botd2 - dx2 <= rout2);
        bool ctop = (topd2 - dx2 <= rout2);
        if (!cbot && !ctop) break;
        int pyb = cyi + dy, pyt = cyi - dy;
        if (cbot && pyb >= 0 && pyb < height)
            detail::renderDiscRow<PixelT, Overwrite>(pixels, width, pyb, botd2, fc);
        if (ctop && pyt >= 0 && pyt < height)
            detail::renderDiscRow<PixelT, Overwrite>(pixels, width, pyt, topd2, fc);
        botd2 += botdelta; botdelta += 131072;
        topd2 += topdelta; topdelta += 131072;
    }
}

template<typename PixelT, typename Coord>
inline void drawDisc(Canvas<PixelT>& canvas, const PixelT& color,
                     Coord cx, Coord cy, Coord r,
                     fl::DrawMode mode) {
    if (mode == fl::DrawMode::DRAW_MODE_OVERWRITE)
        detail::drawDiscCore<PixelT, Coord, true>(canvas, color, cx, cy, r);
    else
        detail::drawDiscCore<PixelT, Coord, false>(canvas, color, cx, cy, r);
}

// ---------------------------------------------------------------------------
// drawRingCore: Templated on Overwrite for compile-time dispatch.
// ---------------------------------------------------------------------------
template<typename PixelT, typename Coord, bool Overwrite>
inline void detail::drawRingCore(Canvas<PixelT>& canvas, const PixelT& color,
                                 Coord cx, Coord cy, Coord r, Coord thickness) {
    PixelT* pixels = canvas.pixels;
    int width = canvas.width;
    int height = canvas.height;

    fl::i32 cx8 = detail::toFixed8(cx);
    fl::i32 cy8 = detail::toFixed8(cy);
    fl::i32 r8 = detail::toFixed8(r);
    fl::i32 t8 = detail::toFixed8(thickness);

    fl::i32 r_ii8 = r8 - 128;
    fl::i32 r_io8 = r8 + 128;
    fl::i32 r_oi8 = r8 + t8 - 128;
    fl::i32 r_oo8 = r8 + t8 + 128;

    if (r_ii8 < 0) r_ii8 = 0;
    if (r_io8 < 0) r_io8 = 0;

    fl::i32 ii2 = r_ii8 * r_ii8;
    fl::i32 io2 = r_io8 * r_io8;
    fl::i32 oi2 = r_oi8 * r_oi8;
    fl::i32 oo2 = r_oo8 * r_oo8;

    fl::i32 inner_band = io2 - ii2;
    fl::i32 outer_band = oo2 - oi2;

    int ri = (r_oo8 >> 8) + 1;
    int cxi = cx8 >> 8;
    int cyi = cy8 >> 8;

    int xmin = cxi - ri; if (xmin < 0) xmin = 0;
    int xmax = cxi + ri; if (xmax >= width) xmax = width - 1;
    if (xmin > xmax) return;
    if (cyi + ri < 0 || cyi - ri >= height) return;

    fl::i32 dx8 = (static_cast<fl::i32>(xmin) << 8) - cx8;
    fl::i32 dx2 = dx8 * dx8;

    detail::RingCtx<PixelT> gc;
    gc.xdelta0 = 512 * dx8 + 65536;
    gc.xmin = xmin; gc.xmax = xmax;
    gc.ii2 = ii2; gc.io2 = io2; gc.oi2 = oi2; gc.oo2 = oo2;
    if (inner_band > 0) detail::computeBandShift(inner_band, gc.inner_shift, gc.inner_inv);
    else { gc.inner_shift = 0; gc.inner_inv = 65280u; }
    if (outer_band > 0) detail::computeBandShift(outer_band, gc.outer_shift, gc.outer_inv);
    else { gc.outer_shift = 0; gc.outer_inv = 65280u; }
    gc.color = color;

    fl::i32 cyfrac = (static_cast<fl::i32>(cyi) << 8) - cy8;
    fl::i32 d2c = dx2 + cyfrac * cyfrac;

    if (cyi >= 0 && cyi < height)
        detail::renderRingRow<PixelT, Overwrite>(pixels, width, cyi, d2c, gc);

    fl::i32 botd2 = d2c, botdelta = 512 * cyfrac + 65536;
    fl::i32 topd2 = d2c, topdelta = -512 * cyfrac + 65536;
    botd2 += botdelta; botdelta += 131072;
    topd2 += topdelta; topdelta += 131072;

    for (int dy = 1; dy <= ri; ++dy) {
        bool cbot = (botd2 - dx2 <= oo2);
        bool ctop = (topd2 - dx2 <= oo2);
        if (!cbot && !ctop) break;
        int pyb = cyi + dy, pyt = cyi - dy;
        if (cbot && pyb >= 0 && pyb < height)
            detail::renderRingRow<PixelT, Overwrite>(pixels, width, pyb, botd2, gc);
        if (ctop && pyt >= 0 && pyt < height)
            detail::renderRingRow<PixelT, Overwrite>(pixels, width, pyt, topd2, gc);
        botd2 += botdelta; botdelta += 131072;
        topd2 += topdelta; topdelta += 131072;
    }
}

template<typename PixelT, typename Coord>
inline void drawRing(Canvas<PixelT>& canvas, const PixelT& color,
                     Coord cx, Coord cy, Coord r, Coord thickness,
                     fl::DrawMode mode) {
    if (mode == fl::DrawMode::DRAW_MODE_OVERWRITE)
        detail::drawRingCore<PixelT, Coord, true>(canvas, color, cx, cy, r, thickness);
    else
        detail::drawRingCore<PixelT, Coord, false>(canvas, color, cx, cy, r, thickness);
}

// ---------------------------------------------------------------------------
// drawStrokeLineCore: Templated on Overwrite for compile-time dispatch.
// ---------------------------------------------------------------------------
template<typename PixelT, typename Coord, bool Overwrite>
inline void detail::drawStrokeLineCore(Canvas<PixelT>& canvas, const PixelT& color,
                                       Coord x0, Coord y0, Coord x1, Coord y1,
                                       Coord thickness, LineCap cap) {
    PixelT* pixels = canvas.pixels;
    int width = canvas.width;
    int height = canvas.height;

    fl::i32 x0_8 = detail::toFixed8(x0), y0_8 = detail::toFixed8(y0);
    fl::i32 x1_8 = detail::toFixed8(x1), y1_8 = detail::toFixed8(y1);
    fl::i32 dx_8 = x1_8 - x0_8;
    fl::i32 dy_8 = y1_8 - y0_8;
    if (dx_8 == 0 && dy_8 == 0) return;

    fl::i32 len2_16 = dx_8 * dx_8 + dy_8 * dy_8;
    fl::i32 len_8 = static_cast<fl::i32>(
        fl::isqrt32(static_cast<fl::u32>(len2_16)));
    fl::i32 thickness_8 = detail::toFixed8(thickness);
    fl::i32 r_max_8 = thickness_8 >> 1;
    fl::i32 threshold_q = r_max_8 * len_8;

    if (threshold_q <= 0) return;

    int x0i = x0_8 >> 8, y0i = y0_8 >> 8;
    int x1i = x1_8 >> 8, y1i = y1_8 >> 8;
    int margin = ((r_max_8 + 255) >> 8) + 2;
    int xmin = (x0i < x1i ? x0i : x1i) - margin;
    int xmax = (x0i > x1i ? x0i : x1i) + margin;
    int ymin = (y0i < y1i ? y0i : y1i) - margin;
    int ymax = (y0i > y1i ? y0i : y1i) + margin;

    if (xmin < 0) xmin = 0;
    if (xmax >= width) xmax = width - 1;
    if (ymin < 0) ymin = 0;
    if (ymax >= height) ymax = height - 1;
    if (xmin > xmax || ymin > ymax) return;

    fl::i32 dx_q = dx_8 << 8;
    fl::i32 dy_q = dy_8 << 8;

    fl::i32 rx_base_8 = (static_cast<fl::i32>(xmin) << 8) - x0_8;
    fl::i32 ry_row_8 = (static_cast<fl::i32>(ymin) << 8) - y0_8;
    fl::i32 cross_row_q = rx_base_8 * dy_8 - ry_row_8 * dx_8;
    fl::i32 dot_row_q = rx_base_8 * dx_8 + ry_row_8 * dy_8;

    fl::i32 len2_q = len_8 * len_8;
    fl::i32 dot_ext_q = (cap == LineCap::SQUARE) ? threshold_q : 0;

    detail::StrokeCtx<PixelT> sc;
    sc.threshold_q = threshold_q;
    sc.len2_q = len2_q;
    sc.dx_q = dx_q;
    sc.dy_q = dy_q;
    sc.dot_ext_q = dot_ext_q;
    detail::computeBandShift(threshold_q, sc.aa_shift, sc.aa_inv);
    sc.x0_8 = x0_8; sc.y0_8 = y0_8;
    sc.x1_8 = x1_8; sc.y1_8 = y1_8;
    sc.r_max2_8 = r_max_8 * r_max_8;
    sc.cap_shift = 0; sc.cap_inv = 0;
    if (cap == LineCap::ROUND && sc.r_max2_8 > 0) {
        detail::computeBandShift(sc.r_max2_8, sc.cap_shift, sc.cap_inv);
    }
    sc.cap = cap;
    sc.color = color;
    sc.xmin = xmin; sc.xmax = xmax;

    for (int py = ymin; py <= ymax; ++py) {
        if (py >= 0 && py < height) {
            detail::renderStrokeRow<PixelT, Overwrite>(pixels, width, py,
                                                       cross_row_q, dot_row_q, sc);
        }
        cross_row_q -= sc.dx_q;
        dot_row_q += sc.dy_q;
    }
}

template<typename PixelT, typename Coord>
inline void drawStrokeLine(Canvas<PixelT>& canvas, const PixelT& color,
                           Coord x0, Coord y0, Coord x1, Coord y1, Coord thickness,
                           LineCap cap, fl::DrawMode mode) {
    if (mode == fl::DrawMode::DRAW_MODE_OVERWRITE)
        detail::drawStrokeLineCore<PixelT, Coord, true>(canvas, color, x0, y0, x1, y1, thickness, cap);
    else
        detail::drawStrokeLineCore<PixelT, Coord, false>(canvas, color, x0, y0, x1, y1, thickness, cap);
}

}  // namespace gfx
}  // namespace fl
