#pragma once

#include "render/core/color.h"
#include "render/core/mat3.h"
#include "render/core/renderer.h"

#include <GLES2/gl2.h>
#include <cstdint>
#include <list>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>

// Forward declarations to avoid dragging Pango headers into every TU.
typedef struct _PangoContext PangoContext;
typedef struct _PangoFontMap PangoFontMap;
typedef struct _PangoLayout PangoLayout;

class GlyphProgram;

// Pango/Cairo-backed text renderer.
//
// Rasterizes a shaped PangoLayout into an ARGB32 Cairo surface, uploads it as
// a premultiplied RGBA texture, and draws it through GlyphProgram.
// Handles Latin, CJK, Arabic, BiDi, and COLR v1 emoji via fontconfig fallback.
//
// measure() and truncate() do not require a current EGL context.
// draw() needs EGL current (creates/binds GL textures).
class CairoTextRenderer {
public:
  struct TextMetrics {
    float width = 0.0f;
    float left = 0.0f;
    float right = 0.0f;
    float top = 0.0f;       // negative — above baseline
    float bottom = 0.0f;    // positive — below baseline
    float inkTop = 0.0f;    // negative — visible ink above baseline
    float inkBottom = 0.0f; // positive — visible ink below baseline
  };

  CairoTextRenderer();
  ~CairoTextRenderer();

  CairoTextRenderer(const CairoTextRenderer&) = delete;
  CairoTextRenderer& operator=(const CairoTextRenderer&) = delete;

  void initialize(GlyphProgram* program);
  void cleanup();

  // HiDPI: raster at `scale × fontSize` pixels and shrink the quad by 1/scale.
  void setContentScale(float scale);
  void setFontFamily(std::string family);

  [[nodiscard]] TextMetrics measure(std::string_view text, float fontSize, bool bold = false, float maxWidth = 0.0f,
                                    int maxLines = 0, TextAlign align = TextAlign::Start);

  void draw(float surfaceWidth, float surfaceHeight, float x, float baselineY, std::string_view text, float fontSize,
            const Color& color, const Mat3& transform, bool bold = false, float maxWidth = 0.0f, int maxLines = 0,
            TextAlign align = TextAlign::Start);

private:
  struct CacheKey {
    std::string text;
    std::uint32_t sizeQ = 0;     // fontSize * 64 + 0.5
    std::uint32_t colorRgba = 0; // packed r<<24|g<<16|b<<8|a
    std::uint32_t maxWidthQ = 0; // maxWidth * 64 + 0.5, 0 = no limit
    std::uint16_t scaleQ = 0;    // contentScale * 64 + 0.5
    std::uint16_t maxLines = 0;  // 0 = no explicit limit (use '\n'-count fallback)
    TextAlign align = TextAlign::Start;
    bool bold = false;

    bool operator==(const CacheKey& other) const noexcept;
  };
  struct CacheKeyHash {
    std::size_t operator()(const CacheKey& k) const noexcept;
  };

  // Color-independent key used to cache logical TextMetrics. measure() is
  // called from layout paths that run every frame on dirty surfaces; rebuilding
  // a PangoLayout for each call was the top allocation hot-spot in heaptrack.
  struct MetricsKey {
    std::string text;
    std::uint32_t sizeQ = 0;
    std::uint32_t maxWidthQ = 0;
    std::uint16_t scaleQ = 0;
    std::uint16_t maxLines = 0;
    TextAlign align = TextAlign::Start;
    bool bold = false;

    bool operator==(const MetricsKey& other) const noexcept;
  };
  struct MetricsKeyHash {
    std::size_t operator()(const MetricsKey& k) const noexcept;
  };

  // LruList is a list of pointers into map keys — we break the otherwise
  // circular type dependency (CacheEntry ↔ CacheMap) this way. CacheKey* is
  // stable because unordered_map never moves key-value nodes under insert
  // (we also reserve bucket capacity upfront to avoid rehashing).
  using LruList = std::list<const CacheKey*>;

  // A very tall text block can exceed GL_MAX_TEXTURE_SIZE (typically 4096 or
  // 8192). We slice the Pango layout into N vertically-stacked tiles, each its
  // own GL texture sized ≤ GL_MAX_TEXTURE_SIZE. draw() emits one quad per tile;
  // tiles abut on exact buffer-pixel boundaries so there is no visible seam.
  struct Tile {
    GLuint texture = 0;
    int pixelHeight = 0;  // raster pixels
    int pixelYOffset = 0; // from top of full layout, in raster pixels
  };

  struct CacheEntry {
    std::vector<Tile> tiles;
    int pixelWidth = 0;   // total raster surface pixel width
    int pixelHeight = 0;  // total raster surface pixel height (sum of tiles)
    float baselinePx = 0; // baseline from top of full layout, in raster pixels
    TextMetrics metrics;  // logical metrics in logical (unscaled) pixels
    std::size_t bytes = 0;
    bool tinted = false; // true: GL_ALPHA coverage, tint in shader; false: premul RGBA
    LruList::iterator lruIt;
  };

  using CacheMap = std::unordered_map<CacheKey, CacheEntry, CacheKeyHash>;
  using MetricsMap = std::unordered_map<MetricsKey, TextMetrics, MetricsKeyHash>;

  // Build a PangoLayout at the given scaled size. Caller owns the layout (g_object_unref).
  PangoLayout* buildLayout(std::string_view text, float fontSize, bool bold, float maxWidthPxScaled, int maxLines,
                           TextAlign align) const;
  // Render a layout into a new GL texture; fills out fields of `entry`.
  // When `tinted` is true, rasterizes as CAIRO_FORMAT_A8 / GL_ALPHA and the
  // color is applied via u_tint at draw time. When false, rasterizes as
  // CAIRO_FORMAT_ARGB32 with `color` baked in (for COLR emoji content).
  void rasterizeLayout(PangoLayout* layout, const Color& color, bool tinted, CacheEntry& entry);
  // Extract logical metrics from a laid-out PangoLayout, dividing by PANGO_SCALE and by scale.
  TextMetrics metricsFromLayout(PangoLayout* layout) const;

  CacheEntry* lookupOrRasterize(std::string_view text, float fontSize, bool bold, float maxWidth, int maxLines,
                                TextAlign align, const Color& color);
  void touch(CacheMap::iterator it);
  void evict(CacheMap::iterator it);
  void evictIfNeeded();
  void clearCaches();

  float m_contentScale = 1.0f;
  bool m_fontConfigInitialized = false;
  std::string m_fontFamily = "sans-serif";

  PangoFontMap* m_fontMap = nullptr;      // owned
  PangoContext* m_pangoContext = nullptr; // owned
  GlyphProgram* m_program = nullptr;

  CacheMap m_cache;
  LruList m_lru;
  std::size_t m_cacheBytes = 0;
  int m_glMaxTextureSize = 0; // lazy-queried on first rasterize

  MetricsMap m_metricsCache;

  static constexpr std::size_t kMaxCacheEntries = 512;
  static constexpr std::size_t kMaxCacheBytes = 32 * 1024 * 1024;
  static constexpr std::size_t kMaxMetricsEntries = 1024;
};
