#include "hb-fuzzer.hh"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <time.h>

#include "hb-subset.h"
#include "hb-ot-cmap-table.hh"

#include <random>
#include <vector>

#ifndef HB_NO_SUBSET_DEPEND
#include <hb.h>

/*
 * Depend Parity Checker — validates depend API correctness by comparing closures.
 *
 * For each font and starting glyph set, this checker:
 * 1. Builds a closure by walking the hb_subset_depend_t dependency graph.
 * 2. Builds the same closure using HarfBuzz's subset plan (the ground truth).
 * 3. Asserts that both closures are identical (or that any extra glyphs in the
 *    depend closure can be explained by a known over-approximation).
 *
 * The depend closure mirrors subset's table order: cmap UVS → GSUB → MATH → COLR → glyf/CFF.
 */


/* ============================================================
 * Configuration globals (set by main() in standalone mode)
 * ============================================================ */

const char *_hb_depend_fuzzer_current_font_path = NULL;
unsigned _hb_depend_fuzzer_seed = 0;
uint64_t _hb_depend_fuzzer_single_test = 0;  /* Specific test to run, or 0 for all */
unsigned _hb_depend_fuzzer_num_tests = 1024; /* Number of tests to run */
bool _hb_depend_fuzzer_verbose = false;
bool _hb_depend_fuzzer_report_over_approximation = false;
bool _hb_depend_fuzzer_report_under_approximation = false;
float _hb_depend_fuzzer_inject_over_approx = 0.0f;
float _hb_depend_fuzzer_inject_under_approx = 0.0f;

/* Explicit test mode */
bool _hb_depend_fuzzer_explicit_test = false;
const char *_hb_depend_fuzzer_explicit_unicodes = NULL;
const char *_hb_depend_fuzzer_explicit_gids = NULL;
bool _hb_depend_fuzzer_explicit_no_layout_closure = false;
const char *_hb_depend_fuzzer_explicit_layout_features = NULL;

/* Debug control (only used when HB_DEBUG_DEPEND > 0) */
static hb_codepoint_t debug_gid = HB_CODEPOINT_INVALID;
static bool trace_closure = false;

static bool config_initialized = false;

/* Debug messaging - follows HarfBuzz DEBUG_MSG pattern but simplified for parity checker */
#ifndef HB_DEBUG_DEPEND
#define HB_DEBUG_DEPEND 1
#endif

#if HB_DEBUG_DEPEND
#define DEBUG_MSG_DEPEND(msg, ...) fprintf (stderr, "%-10s" msg "\n", "DEPEND", ##__VA_ARGS__)
#else
#define DEBUG_MSG_DEPEND(msg, ...) do {} while (0)
#endif

/* Read env-var overrides for debug_gid and trace_closure. Called once on first run. */
static void
init_parity_config ()
{
  if (config_initialized)
    return;

  config_initialized = true;

  const char *debug_gid_str = getenv ("HB_DEPEND_PARITY_DEBUG_GID");
  if (debug_gid_str)
    debug_gid = (hb_codepoint_t) strtoul (debug_gid_str, NULL, 0);

  const char *trace_closure_str = getenv ("HB_DEPEND_PARITY_TRACE_CLOSURE");
  if (trace_closure_str && strcmp (trace_closure_str, "1") == 0)
    trace_closure = true;
}


/* ============================================================
 * Closure algorithm — depend API side
 * ============================================================ */

/**
 * deferred_ligature_t:
 *
 * Records a ligature edge whose dependent glyph cannot be added immediately
 * because not all component glyphs are yet in the closure.  Stored at deferral
 * time so we do not need to re-scan edges when revisiting it.
 */
struct deferred_ligature_t
{
  deferred_ligature_t () = default;
  deferred_ligature_t (hb_codepoint_t dependent_,
		       hb_codepoint_t ligature_set_index_,
		       hb_subset_depend_edge_flags_t flags_)
    : dependent (dependent_),
      ligature_set_index (ligature_set_index_),
      flags (flags_) {}

  hb_codepoint_t dependent;
  hb_codepoint_t ligature_set_index;
  hb_subset_depend_edge_flags_t flags;
};

/* Returns true with probability |prob| using |rng| (always false if rng is null or prob <= 0) */
static bool
rng_prob_hit (std::mt19937 *rng, float prob)
{
  if (!rng || prob <= 0.0f)
    return false;
  return std::uniform_real_distribution<float> (0.0f, 1.0f) (*rng) < prob;
}

/* Context shared across the table-stage calls inside compute_depend_closure */
struct depend_closure_ctx_t
{
  hb_subset_depend_t *depend;
  hb_set_t           *glyphs;          /* Accumulating closure set (modified in place) */
  hb_set_t           *active_features; /* NULL = no feature filter */
  bool                skip_gsub;
  bool               *hit_flagged_edge;
  std::mt19937       *injection_rng;
  std::vector<deferred_ligature_t> deferred_ligatures;
};

/* Check whether all context requirements encoded in |context_set_idx| are met.
 *
 * Elements in a context set can be:
 * - Direct GID (value < 0x80000000): that specific glyph must be in the closure.
 * - Indirect set reference (value >= 0x80000000): at least one glyph from the
 *   referenced set must be in the closure.
 *
 * Returns true if ALL requirements are satisfied (or if context_set_idx is invalid). */
static bool
check_context_satisfied (hb_subset_depend_t *depend,
                         hb_codepoint_t context_set_idx,
                         hb_set_t *current_closure)
{
  if (context_set_idx == HB_CODEPOINT_INVALID)
    return true;

  hb_set_t *context_elements = hb_set_create ();
  if (!hb_subset_depend_lookup_set (depend, context_set_idx, context_elements))
  {
    hb_set_destroy (context_elements);
    return true;  /* Invalid context set, don't filter */
  }

  bool satisfied = true;
  hb_codepoint_t elem = HB_SET_VALUE_INVALID;
  while (hb_set_next (context_elements, &elem))
  {
    if (elem < 0x80000000)
    {
      /* Direct GID reference */
      if (!hb_set_has (current_closure, elem))
      {
        satisfied = false;
        break;
      }
    }
    else
    {
      /* Indirect set reference — at least one glyph from the set must be present */
      hb_codepoint_t set_idx = elem & 0x7FFFFFFF;
      hb_set_t *required_glyphs = hb_set_create ();

      if (!hb_subset_depend_lookup_set (depend, set_idx, required_glyphs))
      {
        hb_set_destroy (required_glyphs);
        continue;  /* Invalid set reference, skip this requirement */
      }

      bool any_present = false;
      hb_codepoint_t gid = HB_SET_VALUE_INVALID;
      while (hb_set_next (required_glyphs, &gid))
      {
        if (hb_set_has (current_closure, gid))
        {
          any_present = true;
          break;
        }
      }

      hb_set_destroy (required_glyphs);

      if (!any_present)
      {
        satisfied = false;
        break;
      }
    }
  }

  hb_set_destroy (context_elements);
  return satisfied;
}

/* Process edges for glyphs in |to_process| that belong to tables in |table_filter|.
 * Appends newly reachable glyphs to ctx.glyphs and back to |to_process| for further expansion.
 * Returns true if any new glyphs were added. */
static bool
process_edges_for_tables (depend_closure_ctx_t &ctx,
                          hb_set_t *to_process,
                          hb_set_t *table_filter)
{
  bool added_any = false;

  while (!hb_set_is_empty (to_process))
  {
    hb_codepoint_t gid = hb_set_get_min (to_process);
    hb_set_del (to_process, gid);

    if (gid == debug_gid)
      DEBUG_MSG_DEPEND ("Processing glyph %u", gid);

    unsigned int total = hb_subset_depend_lookup_glyph (ctx.depend, gid, 0, nullptr, nullptr);
    for (unsigned int idx = 0; idx < total; idx++)
    {
      unsigned int count = 1;
      hb_subset_depend_entry_t entry;
      hb_subset_depend_lookup_glyph (ctx.depend, gid, idx, &count, &entry);

      hb_tag_t      table_tag   = entry.table_tag;
      hb_codepoint_t dependent  = entry.dependent;
      hb_tag_t      layout_tag  = entry.layout_tag;
      hb_codepoint_t ligature_set = entry.ligature_set_index;
      hb_codepoint_t context_set  = entry.context_set_index;
      hb_subset_depend_edge_flags_t flags = entry.flags;

      /* Debug: trace all edges touching the watched glyph */
      if (debug_gid != HB_CODEPOINT_INVALID &&
          (gid == debug_gid || dependent == debug_gid))
        DEBUG_MSG_DEPEND ("Considering edge: %u -> %u (feature=%c%c%c%c, ctx=%u)",
                    gid, dependent, HB_UNTAG (layout_tag), context_set);

      /* Filter by table if requested */
      if (table_filter && !hb_set_has (table_filter, table_tag))
        continue;

      if (dependent == debug_gid || gid == debug_gid)
        DEBUG_MSG_DEPEND ("Edge: %u -> %u (table=%c%c%c%c, ligset=%u)",
                    gid, dependent, HB_UNTAG (table_tag), ligature_set);

      /* UVS closure is handled via the cmap accelerator, not depend edges */
      if (table_tag == HB_TAG ('c','m','a','p'))
        continue;

      /* Skip GSUB edges when requested */
      if (ctx.skip_gsub && table_tag == HB_OT_TAG_GSUB)
        continue;

      /* Filter GSUB edges by active features; over-approx injection can bypass the filter */
      if (ctx.active_features && table_tag == HB_OT_TAG_GSUB &&
          !hb_set_has (ctx.active_features, layout_tag))
      {
        if (!rng_prob_hit (ctx.injection_rng, _hb_depend_fuzzer_inject_over_approx))
        {
          if (dependent == debug_gid)
            DEBUG_MSG_DEPEND ("Skipping edge %u -> %u: feature %c%c%c%c not active",
                        gid, dependent, HB_UNTAG (layout_tag));
          continue;
        }
      }

      /* Skip edges whose context requirements are unsatisfied; over-approx injection can bypass */
      if (context_set != HB_CODEPOINT_INVALID &&
          !check_context_satisfied (ctx.depend, context_set, ctx.glyphs))
      {
        if (!rng_prob_hit (ctx.injection_rng, _hb_depend_fuzzer_inject_over_approx))
        {
          if (dependent == debug_gid)
            DEBUG_MSG_DEPEND ("Skipping edge %u -> %u: context not satisfied (ctx_set=%u)",
                        gid, dependent, context_set);
          continue;
        }
      }

      if (ligature_set != HB_CODEPOINT_INVALID)
      {
        /* Defer: revisit once we know whether all components are in the closure.
         * Feature filtering has already been applied above. */
        ctx.deferred_ligatures.push_back (deferred_ligature_t (dependent, ligature_set, flags));
      }
      else
      {
        /* Under-approx injection: randomly drop this edge */
        if (rng_prob_hit (ctx.injection_rng, _hb_depend_fuzzer_inject_under_approx))
          continue;

        if (!hb_set_has (ctx.glyphs, dependent))
        {
          if (ctx.hit_flagged_edge && (flags & 0x03))
            *ctx.hit_flagged_edge = true;

          if (dependent == debug_gid)
            DEBUG_MSG_DEPEND ("Adding glyph %u to closure (from %u, feature=%c%c%c%c, ctx=%u)",
                        dependent, gid, HB_UNTAG (layout_tag), context_set);
          if (trace_closure)
            DEBUG_MSG_DEPEND ("Depend: %u -> %u (table=%c%c%c%c, feature=%c%c%c%c)",
                        gid, dependent, HB_UNTAG (table_tag), HB_UNTAG (layout_tag));
          hb_set_add (ctx.glyphs, dependent);
          hb_set_add (to_process, dependent);
          added_any = true;
        }
      }
    }
  }

  return added_any;
}

/* Compute closure using the depend API by following the dependency graph.
 *
 * On entry ctx.glyphs is the initial glyph set; on exit it holds the full closure.
 * input_unicodes (may be NULL) is used for UVS (cmap format 14) handling only.
 * Closure is built in table order: cmap UVS → GSUB → MATH → COLR → glyf/CFF.
 * There is no loop back to GSUB after glyf/CFF: component glyphs are rendering
 * details, not shaping inputs, and subset doesn't loop back either. */
static void
compute_depend_closure (hb_face_t *face, depend_closure_ctx_t &ctx,
                        const hb_set_t *input_unicodes)
{
  if (debug_gid != HB_CODEPOINT_INVALID)
    DEBUG_MSG_DEPEND ("Starting closure, glyph %u %s in initial set",
                debug_gid,
                hb_set_has (ctx.glyphs, debug_gid) ? "IS" : "is NOT");

  if (trace_closure)
  {
    hb_codepoint_t gid = HB_SET_VALUE_INVALID;
    fprintf (stderr, "DEPEND: Initial set:");
    while (hb_set_next (ctx.glyphs, &gid))
      fprintf (stderr, " %u", gid);
    fprintf (stderr, "\n");
  }

  hb_set_t *to_process = hb_set_create ();

  /* Stage 1: UVS closure (cmap format 14), processed before GSUB to match subset's order.
   * For each (base unicode, variation selector) pair present in input_unicodes,
   * look up the variant glyph and add it to the closure. */
  if (input_unicodes)
  {
    hb_set_t *all_vs = hb_set_create ();
    hb_face_collect_variation_selectors (face, all_vs);
    hb_set_intersect (all_vs, input_unicodes);

    if (!hb_set_is_empty (all_vs))
    {
      hb_font_t *font = hb_font_create (face);
      hb_codepoint_t vs = HB_SET_VALUE_INVALID;
      while (hb_set_next (all_vs, &vs))
      {
        hb_codepoint_t base = HB_SET_VALUE_INVALID;
        while (hb_set_next (input_unicodes, &base))
        {
          if (hb_set_has (all_vs, base))
            continue;  /* base is itself a VS, skip */
          hb_codepoint_t variant_glyph;
          if (hb_font_get_variation_glyph (font, base, vs, &variant_glyph))
          {
            hb_set_add (ctx.glyphs, variant_glyph);
            if (trace_closure && variant_glyph == debug_gid)
              DEBUG_MSG_DEPEND ("UVS: Added glyph %u via U+%04X+U+%04X",
                          variant_glyph, base, vs);
          }
        }
      }
      hb_font_destroy (font);
    }

    hb_set_destroy (all_vs);
    if (trace_closure)
      DEBUG_MSG_DEPEND ("After UVS (cmap format 14): closure has %u glyphs",
                  hb_set_get_population (ctx.glyphs));
  }

  /* Stage 2: GSUB closure — iterate until stable, matching subset's internal behavior.
   * Ligatures are resolved here (not after glyf/CFF): add a ligature only once all of
   * its required components are in the closure. */
  if (!ctx.skip_gsub)
  {
    hb_set_t *gsub_filter = hb_set_create ();
    hb_set_add (gsub_filter, HB_OT_TAG_GSUB);
    hb_set_t *ligature_set_glyphs = hb_set_create ();

    unsigned prev_gsub_count;
    unsigned iteration = 0;
    do {
      prev_gsub_count = hb_set_get_population (ctx.glyphs);

      hb_set_union (to_process, ctx.glyphs);
      process_edges_for_tables (ctx, to_process, gsub_filter);

      /* Drain deferred ligatures: add each ligature whose component set is now fully satisfied */
      bool lig_made_progress = true;
      while (lig_made_progress && !ctx.deferred_ligatures.empty ())
      {
        lig_made_progress = false;

        for (size_t i = 0; i < ctx.deferred_ligatures.size (); )
        {
          const deferred_ligature_t &def = ctx.deferred_ligatures[i];

          hb_set_clear (ligature_set_glyphs);
          hb_subset_depend_lookup_set (ctx.depend, def.ligature_set_index, ligature_set_glyphs);
          bool set_satisfied = hb_set_is_subset (ligature_set_glyphs, ctx.glyphs);

          /* Over-approx injection: pretend the set is satisfied even when it isn't */
          if (!set_satisfied)
            set_satisfied = rng_prob_hit (ctx.injection_rng, _hb_depend_fuzzer_inject_over_approx);

          if (set_satisfied)
          {
            /* Under-approx injection: skip adding this ligature */
            if (!rng_prob_hit (ctx.injection_rng, _hb_depend_fuzzer_inject_under_approx) &&
                !hb_set_has (ctx.glyphs, def.dependent))
            {
              if (ctx.hit_flagged_edge && (def.flags & 0x03))
                *ctx.hit_flagged_edge = true;
              if (trace_closure)
                DEBUG_MSG_DEPEND ("Adding GSUB ligature %u (components satisfied, flags=0x%02x)",
                            def.dependent, def.flags);
              hb_set_add (ctx.glyphs, def.dependent);
              lig_made_progress = true;
            }
            /* Erase by swapping with the last element */
            ctx.deferred_ligatures[i] = ctx.deferred_ligatures.back ();
            ctx.deferred_ligatures.pop_back ();
          }
          else
            i++;
        }
      }

      iteration++;
    } while (hb_set_get_population (ctx.glyphs) > prev_gsub_count && iteration < 64);

    hb_set_destroy (ligature_set_glyphs);
    hb_set_destroy (gsub_filter);
    if (trace_closure)
      DEBUG_MSG_DEPEND ("After GSUB (%u iterations): closure has %u glyphs",
                  iteration, hb_set_get_population (ctx.glyphs));
  }

  /* Stage 3: MATH closure */
  {
    hb_set_t *math_filter = hb_set_create ();
    hb_set_add (math_filter, HB_OT_TAG_MATH);
    hb_set_union (to_process, ctx.glyphs);
    process_edges_for_tables (ctx, to_process, math_filter);
    hb_set_destroy (math_filter);
    if (trace_closure)
      DEBUG_MSG_DEPEND ("After MATH: closure has %u glyphs", hb_set_get_population (ctx.glyphs));
  }

  /* Stage 4: COLR closure */
  {
    hb_set_t *colr_filter = hb_set_create ();
    hb_set_add (colr_filter, HB_TAG ('C','O','L','R'));
    hb_set_union (to_process, ctx.glyphs);
    process_edges_for_tables (ctx, to_process, colr_filter);
    hb_set_destroy (colr_filter);
    if (trace_closure)
      DEBUG_MSG_DEPEND ("After COLR: closure has %u glyphs", hb_set_get_population (ctx.glyphs));
  }

  /* Stage 5: glyf/CFF closure */
  {
    hb_set_t *glyf_filter = hb_set_create ();
    hb_set_add (glyf_filter, HB_TAG ('g','l','y','f'));
    hb_set_add (glyf_filter, HB_TAG ('C','F','F',' '));
    hb_set_union (to_process, ctx.glyphs);
    process_edges_for_tables (ctx, to_process, glyf_filter);
    hb_set_destroy (glyf_filter);
    if (trace_closure)
      DEBUG_MSG_DEPEND ("After glyf/CFF: closure has %u glyphs", hb_set_get_population (ctx.glyphs));
  }

  hb_set_destroy (to_process);
}


/* ============================================================
 * Closure algorithm — subset side (ground truth)
 * ============================================================ */

/* Compute closure using the HarfBuzz subset mechanism.
 * If active_features is non-NULL and empty on entry, it is populated with the
 * subset's default features so the depend side can use the same set. */
static void
compute_subset_closure (hb_face_t *face, hb_set_t *glyphs, bool skip_gsub,
                        hb_set_t *codepoints, hb_set_t *active_features)
{
  hb_subset_input_t *input = hb_subset_input_create_or_fail ();
  if (!input) return;

  unsigned flags = HB_SUBSET_FLAGS_NO_BIDI_CLOSURE;
  if (skip_gsub)
    flags |= HB_SUBSET_FLAGS_NO_LAYOUT_CLOSURE;
  hb_subset_input_set_flags (input, (hb_subset_flags_t) flags);

  /* Populate input — either from codepoints or glyph IDs */
  if (codepoints && !hb_set_is_empty (codepoints))
  {
    hb_set_t *input_codepoints = hb_subset_input_set (input, HB_SUBSET_SETS_UNICODE);
    hb_set_union (input_codepoints, codepoints);
  }
  else
  {
    hb_set_t *input_glyphs = hb_subset_input_glyph_set (input);
    hb_set_union (input_glyphs, glyphs);
  }

  /* Handle features:
   * - active_features NULL:      use subset defaults, don't capture them
   * - active_features non-empty: clear defaults and use the specified set
   * - active_features empty:     capture defaults into it (output parameter) */
  if (active_features && !skip_gsub)
  {
    hb_set_t *input_features = hb_subset_input_set (input, HB_SUBSET_SETS_LAYOUT_FEATURE_TAG);
    if (hb_set_is_empty (active_features))
      hb_set_union (active_features, input_features);
    else
    {
      hb_set_clear (input_features);
      hb_set_union (input_features, active_features);
    }
  }

  hb_subset_plan_t *plan = hb_subset_plan_create_or_fail (face, input);
  if (!plan)
  {
    hb_subset_input_destroy (input);
    return;
  }

  /* Extract the retained glyph set from the plan's glyph mapping */
  hb_map_t *glyph_map = hb_subset_plan_old_to_new_glyph_mapping (plan);
  hb_set_clear (glyphs);
  int idx = -1;
  hb_codepoint_t key, value;
  while (hb_map_next (glyph_map, &idx, &key, &value))
    hb_set_add (glyphs, key);

  if (debug_gid != HB_CODEPOINT_INVALID)
    DEBUG_MSG_DEPEND ("Subset closure %s glyph %u",
               hb_set_has (glyphs, debug_gid) ? "INCLUDES" : "does not include",
               debug_gid);

  hb_subset_plan_destroy (plan);
  hb_subset_input_destroy (input);
}


/* ============================================================
 * Comparison and reporting
 * ============================================================ */

/* Print the parameters of a single test to stderr */
static void
print_test_params (uint64_t seed, bool is_gsub,
                   hb_set_t *start_glyphs, hb_set_t *codepoints, hb_set_t *features)
{
  fprintf (stderr, "Test: seed=0x%016llx\n", (unsigned long long) seed);
  fprintf (stderr, "Test type: %s\n", is_gsub ? "GSUB" : "non-GSUB");

  if (start_glyphs && !hb_set_is_empty (start_glyphs))
  {
    fprintf (stderr, "Starting glyphs (%u): ", hb_set_get_population (start_glyphs));
    hb_codepoint_t gid = HB_SET_VALUE_INVALID;
    unsigned count = 0;
    while (hb_set_next (start_glyphs, &gid) && count < 20)
    {
      fprintf (stderr, "%u ", gid);
      count++;
    }
    if (count == 20 && hb_set_get_population (start_glyphs) > 20)
      fprintf (stderr, "...");
    fprintf (stderr, "\n");
  }

  if (codepoints && !hb_set_is_empty (codepoints))
  {
    fprintf (stderr, "Starting codepoints (%u): ", hb_set_get_population (codepoints));
    hb_codepoint_t cp = HB_SET_VALUE_INVALID;
    unsigned count = 0;
    while (hb_set_next (codepoints, &cp) && count < 20)
    {
      fprintf (stderr, "U+%04X ", cp);
      count++;
    }
    if (count == 20 && hb_set_get_population (codepoints) > 20)
      fprintf (stderr, "...");
    fprintf (stderr, "\n");
  }

  if (features && !hb_set_is_empty (features))
  {
    fprintf (stderr, "Active features (%u): ", hb_set_get_population (features));
    hb_codepoint_t feature = HB_SET_VALUE_INVALID;
    unsigned count = 0;
    while (hb_set_next (features, &feature) && count < 10)
    {
      fprintf (stderr, "%c%c%c%c ", HB_UNTAG ((hb_tag_t) feature));
      count++;
    }
    if (count == 10 && hb_set_get_population (features) > 10)
      fprintf (stderr, "...");
    fprintf (stderr, "\n");
  }
}

/* Compare the depend-API closure against the subset-plan closure for one test case.
 *
 * Under-approximation (depend missed glyphs subset found) is always a bug: assert.
 * Over-approximation (depend found extra glyphs) is expected when a flagged edge
 * (FROM_CONTEXT_POSITION or FROM_NESTED_CONTEXT) was traversed, since depend cannot
 * know whether the intermediate glyph consumer will actually be present; otherwise
 * it is also a bug.
 *
 * Returns true if the two closures are identical. */
static bool
compare_closures (hb_face_t *face, hb_subset_depend_t *depend,
                  uint64_t seed, const char *font_path,
                  bool is_gsub, hb_set_t *start_glyphs, hb_set_t *codepoints,
                  hb_set_t *test_features, hb_map_t *unicode_to_glyph_cache)
{
  bool skip_gsub = !is_gsub;

  /* --- Subset closure (ground truth) --- */
  hb_set_t *subset_closure = hb_set_create ();
  hb_set_union (subset_closure, start_glyphs);

  /* active_features: pre-populate if caller supplied features, else let subset fill defaults */
  hb_set_t *active_features = hb_set_create ();
  if (test_features)
    hb_set_union (active_features, test_features);

  compute_subset_closure (face, subset_closure, skip_gsub, codepoints, active_features);

  if (trace_closure)
  {
    DEBUG_MSG_DEPEND ("Subset: Final closure has %u glyphs, checking for 82 and 124...",
               hb_set_get_population (subset_closure));
    DEBUG_MSG_DEPEND ("Subset: glyph 82 ('o') %s in closure",
               hb_set_has (subset_closure, 82) ? "IS" : "is NOT");
    DEBUG_MSG_DEPEND ("Subset: glyph 124 (ordmasculine) %s in closure",
               hb_set_has (subset_closure, 124) ? "IS" : "is NOT");
  }

  /* --- Depend closure --- */

  /* For GSUB tests starting from codepoints, map codepoints → glyphs via cmap only
   * (not glyf/COLR/MATH), matching subset's own ordering. */
  hb_set_t *actual_start_glyphs = hb_set_create ();
  if (is_gsub && codepoints && !hb_set_is_empty (codepoints))
  {
    hb_set_add (actual_start_glyphs, 0);  /* .notdef */
    unsigned num_glyphs = hb_face_get_glyph_count (face);
    hb_font_t *font = hb_font_create (face);
    hb_codepoint_t cp = HB_SET_VALUE_INVALID;
    while (hb_set_next (codepoints, &cp))
    {
      hb_codepoint_t gid;
      if (hb_font_get_nominal_glyph (font, cp, &gid) && gid < num_glyphs)
        hb_set_add (actual_start_glyphs, gid);
    }
    hb_font_destroy (font);
  }
  else
    hb_set_union (actual_start_glyphs, start_glyphs);

  hb_set_t *depend_closure = hb_set_create ();
  hb_set_union (depend_closure, actual_start_glyphs);
  hb_set_add (depend_closure, 0);  /* subset always adds .notdef; see hb-subset-plan.cc:451 */

  if (debug_gid != HB_CODEPOINT_INVALID && active_features)
  {
    hb_codepoint_t feat = HB_SET_VALUE_INVALID;
    fprintf (stderr, "DEPEND: Active features (%u):", hb_set_get_population (active_features));
    while (hb_set_next (active_features, &feat))
      fprintf (stderr, " %c%c%c%c", HB_UNTAG (feat));
    fprintf (stderr, "\n");
  }

  /* Seed RNG for reproducible error injection: same seed → same edges skipped/added */
  std::mt19937 injection_rng_storage;
  std::mt19937 *injection_rng = nullptr;
  if (_hb_depend_fuzzer_inject_over_approx > 0.0f ||
      _hb_depend_fuzzer_inject_under_approx > 0.0f)
  {
    injection_rng_storage.seed ((unsigned) seed);
    injection_rng = &injection_rng_storage;
  }

  /* Build the unicode set for UVS (cmap format 14) closure.
   * GSUB tests: use input codepoints directly.
   * Non-GSUB tests: reverse-map starting glyphs to their unicodes
   * (matches subset's behavior at hb-subset-plan.cc:310-319). */
  hb_set_t *uvs_unicodes = hb_set_create ();
  if (is_gsub && codepoints && !hb_set_is_empty (codepoints))
  {
    hb_set_union (uvs_unicodes, codepoints);
  }
  else
  {
    hb_map_t *unicode_to_glyph = unicode_to_glyph_cache;
    if (!unicode_to_glyph)
    {
      unicode_to_glyph = hb_map_create ();
      hb_face_collect_nominal_glyph_mapping (face, unicode_to_glyph, NULL);
    }
    int idx = -1;
    hb_codepoint_t unicode, glyph;
    while (hb_map_next (unicode_to_glyph, &idx, &unicode, &glyph))
      if (hb_set_has (actual_start_glyphs, glyph))
        hb_set_add (uvs_unicodes, unicode);
    if (!unicode_to_glyph_cache)
      hb_map_destroy (unicode_to_glyph);
  }

  bool hit_flagged_edge = false;
  depend_closure_ctx_t ctx;
  ctx.depend           = depend;
  ctx.glyphs           = depend_closure;
  ctx.active_features  = skip_gsub ? nullptr : active_features;
  ctx.skip_gsub        = skip_gsub;
  ctx.hit_flagged_edge = &hit_flagged_edge;
  ctx.injection_rng    = injection_rng;

  compute_depend_closure (face, ctx, uvs_unicodes);
  hb_set_destroy (uvs_unicodes);
  hb_set_destroy (actual_start_glyphs);

  /* --- Compare --- */
  bool match = hb_set_is_equal (depend_closure, subset_closure);

  hb_set_t *in_depend_not_subset = hb_set_create ();
  hb_set_t *in_subset_not_depend = hb_set_create ();
  hb_set_set (in_depend_not_subset, depend_closure);
  hb_set_subtract (in_depend_not_subset, subset_closure);
  hb_set_set (in_subset_not_depend, subset_closure);
  hb_set_subtract (in_subset_not_depend, depend_closure);

  /* Under-approximation: depend missed glyphs that subset found */
  if (!hb_set_is_empty (in_subset_not_depend))
  {
    if (_hb_depend_fuzzer_report_under_approximation ||
        _hb_depend_fuzzer_report_over_approximation ||
        _hb_depend_fuzzer_verbose)
    {
      fprintf (stderr, "Test 0x%016llx: UNDER-APPROX - Depend missed %u glyphs:",
               (unsigned long long) seed, hb_set_get_population (in_subset_not_depend));
      hb_codepoint_t gid = HB_SET_VALUE_INVALID;
      unsigned count = 0;
      while (hb_set_next (in_subset_not_depend, &gid) && count < 20)
      {
        fprintf (stderr, " %u", gid);
        count++;
      }
      if (count == 20)
        fprintf (stderr, " ...");
      fprintf (stderr, "\n");
    }
    if (!_hb_depend_fuzzer_report_under_approximation)
      assert (0 && "Depend API missed glyphs that subset found");
  }

  /* Over-approximation: depend found extra glyphs not in subset closure.
   * Expected when a flagged (contextual-position) edge was traversed; a bug otherwise. */
  if (!hb_set_is_empty (in_depend_not_subset))
  {
    if (hit_flagged_edge)
    {
      if (_hb_depend_fuzzer_verbose || _hb_depend_fuzzer_report_over_approximation)
      {
        if (_hb_depend_fuzzer_report_over_approximation && !_hb_depend_fuzzer_verbose)
        {
          fprintf (stderr, "Test 0x%016llx: Depend found %u extra glyphs (expected, hit flagged edge):",
                   (unsigned long long) seed, hb_set_get_population (in_depend_not_subset));
          hb_codepoint_t gid = HB_SET_VALUE_INVALID;
          while (hb_set_next (in_depend_not_subset, &gid))
            fprintf (stderr, " %u", gid);
          fprintf (stderr, "\n");
        }
        else
        {
          fprintf (stderr, "\n=== DEPEND API OVER-APPROXIMATION (EXPECTED) ===\n");
          fprintf (stderr, "Font: %s\n", font_path ? font_path : "(unknown)");
          fprintf (stderr, "Test seed: 0x%016llx\n", (unsigned long long) seed);
          fprintf (stderr, "Depend found %u extra glyphs not in subset closure:\n",
                   hb_set_get_population (in_depend_not_subset));
          hb_codepoint_t gid = HB_SET_VALUE_INVALID;
          unsigned count = 0;
          while (hb_set_next (in_depend_not_subset, &gid) && count < 50)
          {
            fprintf (stderr, " %u", gid);
            count++;
          }
          if (count == 50)
            fprintf (stderr, " ...");
          fprintf (stderr, "\n");
          fprintf (stderr, "Note: Hit flagged edge from contextual position (intermediate glyph consumer).\n");
          fprintf (stderr, "This is expected over-approximation, not a bug.\n");
          fprintf (stderr, "===============================================\n\n");
        }
      }
    }
    else
    {
      /* Unexpected: no flagged edge, but depend still has extra glyphs.
       *
       * Before asserting, check whether this is a known self-feeding chain:
       * a GSUB lookup whose output coverage overlaps its input coverage,
       * causing the transitive depend closure to follow the chain further
       * than the subsetter's iteration-capped closure.  This is harmless
       * over-approximation — no shaper re-applies a lookup to its own
       * output within a single feature application. */
      bool explained_by_self_feeding = false;
      if (!skip_gsub && active_features)
      {
        /* Check each active feature for self-feeding: a feature where any
         * dependent glyph also appears as a source glyph. */
        hb_set_t *self_feeding_features = hb_set_create ();
        unsigned num_glyphs = hb_face_get_glyph_count (face);
        hb_codepoint_t feat_tag = HB_SET_VALUE_INVALID;
        while (hb_set_next (active_features, &feat_tag))
        {
          hb_set_t *sources = hb_set_create ();
          hb_set_t *dependents = hb_set_create ();
          for (hb_codepoint_t gid = 0; gid < num_glyphs; gid++)
          {
            unsigned total = hb_subset_depend_lookup_glyph (depend, gid, 0, nullptr, nullptr);
            for (unsigned j = 0; j < total; j++)
            {
              unsigned cnt = 1;
              hb_subset_depend_entry_t entry;
              hb_subset_depend_lookup_glyph (depend, gid, j, &cnt, &entry);
              if (entry.table_tag != HB_OT_TAG_GSUB) continue;
              if (entry.layout_tag != feat_tag) continue;
              hb_set_add (sources, gid);
              hb_set_add (dependents, entry.dependent);
            }
          }
          hb_set_intersect (sources, dependents);
          if (!hb_set_is_empty (sources))
            hb_set_add (self_feeding_features, feat_tag);
          hb_set_destroy (sources);
          hb_set_destroy (dependents);
        }

        if (!hb_set_is_empty (self_feeding_features))
        {
          /* Check that every extra glyph is reachable from the subset closure
           * through a chain of edges within a self-feeding feature. */
          hb_set_t *reachable = hb_set_create ();
          hb_set_union (reachable, subset_closure);
          bool changed;
          do
          {
            changed = false;
            hb_codepoint_t gid = HB_SET_VALUE_INVALID;
            while (hb_set_next (reachable, &gid))
            {
              unsigned total = hb_subset_depend_lookup_glyph (depend, gid, 0, nullptr, nullptr);
              for (unsigned j = 0; j < total; j++)
              {
                unsigned cnt = 1;
                hb_subset_depend_entry_t e;
                hb_subset_depend_lookup_glyph (depend, gid, j, &cnt, &e);
                if (e.table_tag != HB_OT_TAG_GSUB) continue;
                if (!hb_set_has (self_feeding_features, e.layout_tag)) continue;
                if (!hb_set_has (reachable, e.dependent))
                {
                  hb_set_add (reachable, e.dependent);
                  changed = true;
                }
              }
            }
          } while (changed);

          /* If every extra glyph is explained by self-feeding chains,
           * this is known over-approximation. */
          hb_set_t *unexplained = hb_set_create ();
          hb_set_union (unexplained, in_depend_not_subset);
          hb_set_subtract (unexplained, reachable);
          if (hb_set_is_empty (unexplained))
            explained_by_self_feeding = true;
          hb_set_destroy (unexplained);
          hb_set_destroy (reachable);
        }
        hb_set_destroy (self_feeding_features);
      }

      if (explained_by_self_feeding)
      {
        if (_hb_depend_fuzzer_verbose || _hb_depend_fuzzer_report_over_approximation)
          fprintf (stderr, "Test 0x%016llx: Depend found %u extra glyphs (explained by self-feeding GSUB chain)\n",
                   (unsigned long long) seed, hb_set_get_population (in_depend_not_subset));
      }
      else
      {
        if (_hb_depend_fuzzer_report_under_approximation ||
            _hb_depend_fuzzer_report_over_approximation ||
            _hb_depend_fuzzer_verbose)
        {
          fprintf (stderr, "Test 0x%016llx: UNEXPECTED OVER-APPROX - Depend found %u extra glyphs WITHOUT hitting flagged edge:",
                   (unsigned long long) seed, hb_set_get_population (in_depend_not_subset));
          hb_codepoint_t gid = HB_SET_VALUE_INVALID;
          unsigned count = 0;
          while (hb_set_next (in_depend_not_subset, &gid) && count < 20)
          {
            fprintf (stderr, " %u", gid);
            count++;
          }
          if (count == 20)
            fprintf (stderr, " ...");
          fprintf (stderr, "\n");
        }
        if (!_hb_depend_fuzzer_report_under_approximation)
          assert (0 && "Depend API has unexpected over-approximation without flagged edges");
      }
    }
  }

  hb_set_destroy (in_subset_not_depend);
  hb_set_destroy (in_depend_not_subset);
  hb_set_destroy (active_features);
  hb_set_destroy (subset_closure);
  hb_set_destroy (depend_closure);

  return match;
}


/* ============================================================
 * Random sampling utilities
 * ============================================================ */

/* Use std::mt19937 for deterministic, cross-platform random behaviour */
static unsigned
rng_range (std::mt19937 &rng, unsigned min, unsigned max)
{
  if (max <= min)
    return min;
  std::uniform_int_distribution<unsigned> dist (min, max);
  return dist (rng);
}

/* Select n random distinct values from range [0, max) */
static void
rng_select_distinct (std::mt19937 &rng, hb_set_t *out, unsigned n, unsigned max)
{
  if (n >= max)
  {
    for (unsigned i = 0; i < max; i++)
      hb_set_add (out, i);
    return;
  }
  while (hb_set_get_population (out) < n)
    hb_set_add (out, rng_range (rng, 0, max - 1));
}

/* Select n random distinct values from an existing set */
static void
rng_select_from_set (std::mt19937 &rng, hb_set_t *out, unsigned n, const hb_set_t *source)
{
  unsigned source_size = hb_set_get_population (source);
  if (n == 0 || source_size == 0)
    return;

  if (n >= source_size)
  {
    hb_set_union (out, source);
    return;
  }

  /* Convert source to array for indexed access */
  hb_codepoint_t *array = (hb_codepoint_t *) malloc (source_size * sizeof (hb_codepoint_t));
  unsigned idx = 0;
  hb_codepoint_t val = HB_SET_VALUE_INVALID;
  while (hb_set_next (source, &val))
    array[idx++] = val;

  while (hb_set_get_population (out) < n)
    hb_set_add (out, array[rng_range (rng, 0, source_size - 1)]);

  free (array);
}

/* ============================================================
 * Font and input queries
 * ============================================================ */

/* Return a newly-allocated set containing all GSUB feature tags for |face| */
static hb_set_t *
get_gsub_features (hb_face_t *face)
{
  hb_set_t *features = hb_set_create ();

  if (!hb_ot_layout_has_substitution (face))
    return features;

  unsigned int feature_count = hb_ot_layout_table_get_feature_tags (face,
                                                                     HB_OT_TAG_GSUB,
                                                                     0, nullptr, nullptr);
  hb_tag_t *feature_tags = (hb_tag_t *) malloc (feature_count * sizeof (hb_tag_t));
  if (feature_tags)
  {
    hb_ot_layout_table_get_feature_tags (face,
                                         HB_OT_TAG_GSUB,
                                         0, &feature_count, feature_tags);
    for (unsigned int i = 0; i < feature_count; i++)
      hb_set_add (features, feature_tags[i]);
    free (feature_tags);
  }

  return features;
}

/* Parse comma-separated list of unicodes (U+XXXX, decimal, or ranges) into a set */
static bool
parse_unicodes (const char *str, hb_set_t *unicodes)
{
  if (!str || !*str)
    return false;

  const char *p = str;
  while (*p)
  {
    while (*p == ' ' || *p == ',')
      p++;
    if (!*p)
      break;

    const char *start = p;
    hb_codepoint_t start_cp;
    if (*p == 'U' && *(p + 1) == '+')
      start_cp = (hb_codepoint_t) strtoul (p + 2, (char **) &p, 16);
    else
      start_cp = (hb_codepoint_t) strtoul (p, (char **) &p, 10);

    if (p == start || (p == start + 2 && start[0] == 'U' && start[1] == '+'))
    {
      fprintf (stderr, "Error: Invalid unicode value at: %.20s\n", start);
      return false;
    }

    if (*p == '-')
    {
      p++;
      const char *end_start = p;
      hb_codepoint_t end_cp;
      if (*p == 'U' && *(p + 1) == '+')
        end_cp = (hb_codepoint_t) strtoul (p + 2, (char **) &p, 16);
      else
        end_cp = (hb_codepoint_t) strtoul (p, (char **) &p, 10);

      if (p == end_start || (p == end_start + 2 && end_start[0] == 'U' && end_start[1] == '+'))
      {
        fprintf (stderr, "Error: Invalid unicode value in range at: %.20s\n", end_start);
        return false;
      }

      hb_set_add_range (unicodes, start_cp, end_cp);
    }
    else
      hb_set_add (unicodes, start_cp);
  }

  return !hb_set_is_empty (unicodes);
}

/* Parse comma-separated list of glyph IDs (decimal or ranges) into a set */
static bool
parse_gids (const char *str, hb_set_t *gids)
{
  if (!str || !*str)
    return false;

  const char *p = str;
  while (*p)
  {
    while (*p == ' ' || *p == ',')
      p++;
    if (!*p)
      break;

    const char *start = p;
    hb_codepoint_t start_gid = (hb_codepoint_t) strtoul (p, (char **) &p, 10);

    if (p == start)
    {
      fprintf (stderr, "Error: Invalid glyph ID at: %.20s\n", start);
      return false;
    }

    if (*p == '-')
    {
      p++;
      const char *end_start = p;
      hb_codepoint_t end_gid = (hb_codepoint_t) strtoul (p, (char **) &p, 10);

      if (p == end_start)
      {
        fprintf (stderr, "Error: Invalid glyph ID in range at: %.20s\n", end_start);
        return false;
      }

      hb_set_add_range (gids, start_gid, end_gid);
    }
    else
      hb_set_add (gids, start_gid);
  }

  return !hb_set_is_empty (gids);
}

/* Parse comma-separated list of feature tags into a set.
 * The special value "*" means all features from the face. */
static bool
parse_features (const char *str, hb_set_t *features, hb_face_t *face)
{
  if (!str || !*str)
    return false;

  if (strcmp (str, "*") == 0)
  {
    hb_set_t *all_features = get_gsub_features (face);
    hb_set_union (features, all_features);
    hb_set_destroy (all_features);
    return true;
  }

  const char *p = str;
  while (*p)
  {
    while (*p == ' ' || *p == ',')
      p++;
    if (!*p)
      break;

    char tag_str[5] = {0};
    int i = 0;
    while (*p && *p != ',' && *p != ' ' && i < 4)
      tag_str[i++] = *p++;

    if (i > 0)
    {
      while (i < 4)
        tag_str[i++] = ' ';
      tag_str[4] = '\0';
      hb_set_add (features, HB_TAG (tag_str[0], tag_str[1], tag_str[2], tag_str[3]));
    }
  }

  return !hb_set_is_empty (features);
}

/* ============================================================
 * Test parameter generation
 * ============================================================ */

/* Test parameters generated from a 64-bit seed or explicit command-line args */
struct test_params
{
  bool is_gsub;           /* GSUB test (codepoint-based) vs non-GSUB (glyph-based) */
  unsigned target_size;   /* Target set size (may be clamped by font size) */
  hb_set_t *glyphs;       /* Starting glyphs (NULL on failure) */
  hb_set_t *codepoints;   /* Starting codepoints (NULL if non-GSUB) */
  hb_set_t *features;     /* Active features (NULL for default/all, only used for GSUB) */
};

/* Generate test parameters from explicit command-line arguments */
static test_params
generate_test_from_explicit (hb_face_t *face, hb_font_t *font)
{
  test_params params;
  params.glyphs = hb_set_create ();
  params.codepoints = NULL;
  params.features = NULL;
  params.target_size = 0;

  if (_hb_depend_fuzzer_explicit_unicodes)
  {
    params.is_gsub = true;
    params.codepoints = hb_set_create ();

    if (!parse_unicodes (_hb_depend_fuzzer_explicit_unicodes, params.codepoints))
    {
      fprintf (stderr, "Error: Failed to parse --unicodes argument\n");
      hb_set_destroy (params.glyphs);
      hb_set_destroy (params.codepoints);
      params.glyphs = NULL;
      params.codepoints = NULL;
      return params;
    }

    hb_codepoint_t cp = HB_SET_VALUE_INVALID;
    while (hb_set_next (params.codepoints, &cp))
    {
      hb_codepoint_t gid;
      if (hb_font_get_nominal_glyph (font, cp, &gid))
        hb_set_add (params.glyphs, gid);
    }
    params.target_size = hb_set_get_population (params.codepoints);
  }
  else if (_hb_depend_fuzzer_explicit_gids)
  {
    params.is_gsub = !_hb_depend_fuzzer_explicit_no_layout_closure;

    if (!parse_gids (_hb_depend_fuzzer_explicit_gids, params.glyphs))
    {
      fprintf (stderr, "Error: Failed to parse --gids argument\n");
      hb_set_destroy (params.glyphs);
      params.glyphs = NULL;
      return params;
    }
    params.target_size = hb_set_get_population (params.glyphs);
  }
  else
  {
    fprintf (stderr, "Error: Explicit mode requires --unicodes or --gids\n");
    hb_set_destroy (params.glyphs);
    params.glyphs = NULL;
    return params;
  }

  if (_hb_depend_fuzzer_explicit_layout_features)
  {
    params.features = hb_set_create ();
    if (!parse_features (_hb_depend_fuzzer_explicit_layout_features, params.features, face))
    {
      fprintf (stderr, "Error: Failed to parse --layout-features argument\n");
      hb_set_destroy (params.features);
      params.features = NULL;
    }
  }

  return params;
}

/* Generate a test from a 64-bit seed.
 *
 * The seed encodes test type, size category, element selection, and feature selection:
 *
 * Test type distribution (seed % 100):
 * - Non-GSUB single (1):            21%
 * - Non-GSUB small (2–5):           17%
 * - Non-GSUB medium (10–20):        10%
 * - Non-GSUB large (50–100):         6%
 * - Non-GSUB very large (N/10–N/4):  4%
 * - GSUB single (1):                21%
 * - GSUB small (2–5):               10%
 * - GSUB medium (10–20):             6%
 * - GSUB large (50–100):             4%
 *
 * Feature selection (GSUB tests, applied after type):
 * - Single feature:    30%
 * - Small (2–5):       35%
 * - Medium (6–15):     20%
 * - Large (16–50):     10%
 * - All features:       5%
 */
static test_params
generate_test_from_seed (uint64_t seed, hb_face_t *face, hb_font_t *font)
{
  test_params params;
  params.glyphs = NULL;
  params.codepoints = NULL;
  params.features = NULL;

  unsigned num_glyphs = hb_face_get_glyph_count (face);

  hb_set_t *all_codepoints = hb_set_create ();
  hb_face_collect_unicodes (face, all_codepoints);
  unsigned num_codepoints = hb_set_get_population (all_codepoints);

  /* Step 1: Determine test type and size range from seed % 100 */
  unsigned type_selector = seed % 100;
  /* Glyph-starting non-GSUB tests force subset-plan to reverse-map retained
   * glyphs through cmap. Fonts like AdobeBlank cover most of Unicode, making
   * that path too expensive to run hundreds of times in one Meson test. Use
   * codepoint-starting tests for huge-cmap fonts; table dependency coverage
   * still comes from the rest of the corpus. */
  if (num_codepoints > 65536)
    type_selector = 58 + (type_selector % 42);
  unsigned min_size, max_size;

  if (type_selector < 21) {
    params.is_gsub = false; min_size = max_size = 1;           /* Non-GSUB single (21%) */
  } else if (type_selector < 38) {
    params.is_gsub = false; min_size = 2;   max_size = 5;      /* Non-GSUB small (17%) */
  } else if (type_selector < 48) {
    params.is_gsub = false; min_size = 10;  max_size = 20;     /* Non-GSUB medium (10%) */
  } else if (type_selector < 54) {
    params.is_gsub = false; min_size = 50;  max_size = 100;    /* Non-GSUB large (6%) */
  } else if (type_selector < 58) {
    params.is_gsub = false;                                     /* Non-GSUB very large (4%) */
    min_size = num_glyphs / 10; max_size = num_glyphs / 4;
  } else if (type_selector < 79) {
    params.is_gsub = true;  min_size = max_size = 1;           /* GSUB single (21%) */
  } else if (type_selector < 89) {
    params.is_gsub = true;  min_size = 2;   max_size = 5;      /* GSUB small (10%) */
  } else if (type_selector < 95) {
    params.is_gsub = true;  min_size = 10;  max_size = 20;     /* GSUB medium (6%) */
  } else {
    params.is_gsub = true;  min_size = 50;  max_size = 100;    /* GSUB large (4%) */
  }

  /* Step 2: Pick target size within range, then clamp to what the font has */
  params.target_size = min_size + ((seed >> 8) % (max_size - min_size + 1));
  if (params.is_gsub)
    params.target_size = params.target_size < num_codepoints ? params.target_size : num_codepoints;
  else
    params.target_size = params.target_size < num_glyphs ? params.target_size : num_glyphs;

  if (params.target_size < min_size)
  {
    hb_set_destroy (all_codepoints);
    params.target_size = 0;
    return params;
  }

  /* Step 3: Select specific elements using a separate RNG seeded from the high bits */
  unsigned selection_seed = (unsigned)((seed >> 32) ^ seed);
  std::mt19937 selection_rng (selection_seed);

  if (params.is_gsub)
  {
    params.codepoints = hb_set_create ();
    params.glyphs = hb_set_create ();

    /* Select random codepoints by index */
    hb_set_t *cp_indices = hb_set_create ();
    rng_select_distinct (selection_rng, cp_indices, params.target_size, num_codepoints);

    hb_codepoint_t next_idx = HB_SET_VALUE_INVALID;
    hb_codepoint_t cp = HB_SET_VALUE_INVALID;
    unsigned current_idx = 0;
    bool have_idx = hb_set_next (cp_indices, &next_idx);
    while (have_idx && hb_set_next (all_codepoints, &cp))
    {
      while (have_idx && current_idx == next_idx)
      {
        hb_set_add (params.codepoints, cp);

        hb_codepoint_t gid;
        if (hb_font_get_nominal_glyph (font, cp, &gid))
          hb_set_add (params.glyphs, gid);

        have_idx = hb_set_next (cp_indices, &next_idx);
      }
      current_idx++;
    }
    hb_set_destroy (cp_indices);

    /* Select features to activate */
    hb_set_t *available_features = get_gsub_features (face);
    unsigned num_features = hb_set_get_population (available_features);

    if (num_features > 0)
    {
      unsigned feature_selector = std::uniform_int_distribution<unsigned> (0, 99) (selection_rng);
      unsigned num_to_select;

      if (feature_selector < 30)
        num_to_select = 1;                                          /* Single (30%) */
      else if (feature_selector < 65)
        num_to_select = rng_range (selection_rng, 2, 5);           /* Small (35%) */
      else if (feature_selector < 85)
        num_to_select = rng_range (selection_rng, 6, 15);          /* Medium (20%) */
      else if (feature_selector < 95)
        num_to_select = rng_range (selection_rng, 16, 50);         /* Large (10%) */
      else
        num_to_select = num_features;                               /* All (5%) */

      if (num_to_select > num_features)
        num_to_select = num_features;

      params.features = hb_set_create ();
      rng_select_from_set (selection_rng, params.features, num_to_select, available_features);
    }

    hb_set_destroy (available_features);
  }
  else
  {
    params.glyphs = hb_set_create ();
    rng_select_distinct (selection_rng, params.glyphs, params.target_size, num_glyphs);
  }

  hb_set_destroy (all_codepoints);
  return params;
}

#endif /* !HB_NO_SUBSET_DEPEND */

extern "C" int LLVMFuzzerTestOneInput (const uint8_t *data, size_t size)
{
#ifndef HB_NO_SUBSET_DEPEND
  alloc_state = _fuzzing_alloc_state (data, size);

  init_parity_config ();

  hb_blob_t *blob = hb_blob_create ((const char *) data, size,
                                    HB_MEMORY_MODE_READONLY, nullptr, nullptr);
  hb_face_t *face = hb_face_create (blob, 0);

  hb_subset_depend_t *depend = hb_subset_depend_from_face_or_fail (face);
  if (!depend)
  {
    hb_face_destroy (face);
    hb_blob_destroy (blob);
    return 0;
  }

  /* Scale injection probabilities to achieve a target per-test detection rate
   * regardless of font complexity.
   *
   * The user supplies a TARGET_RATE (e.g. 0.02 = 2%).  We estimate the average
   * number of edges traversed per test as E_total * 0.0025 (0.25% of all edges),
   * then set per_edge_prob = TARGET_RATE / estimated_edges_per_test.
   *
   * The 0.0025 multiplier was tuned empirically across 8 system fonts
   * (measured: 2.12% under, 3.20% over on 4000 tests at target rate 0.02). */
  if (_hb_depend_fuzzer_inject_over_approx > 0.0f ||
      _hb_depend_fuzzer_inject_under_approx > 0.0f)
  {
    unsigned total_edges = 0;
    unsigned num_glyphs = hb_face_get_glyph_count (face);
    for (unsigned gid = 0; gid < num_glyphs; gid++)
      total_edges += hb_subset_depend_lookup_glyph (depend, gid, 0, nullptr, nullptr);

    float estimated_edges_per_test = total_edges * 0.0025f;
    if (estimated_edges_per_test < 5.0f)
      estimated_edges_per_test = 5.0f;

    if (_hb_depend_fuzzer_inject_over_approx > 0.0f)
    {
      float target_rate = _hb_depend_fuzzer_inject_over_approx;
      _hb_depend_fuzzer_inject_over_approx = target_rate / estimated_edges_per_test;
    }
    if (_hb_depend_fuzzer_inject_under_approx > 0.0f)
    {
      float target_rate = _hb_depend_fuzzer_inject_under_approx;
      _hb_depend_fuzzer_inject_under_approx = target_rate / estimated_edges_per_test;
    }

    if (_hb_depend_fuzzer_verbose)
    {
      fprintf (stderr, "# Adaptive error injection: %u total edges, est. %.0f edges/test\n",
               total_edges, (double) estimated_edges_per_test);
      fprintf (stderr, "# Calculated per-edge probabilities: over=%.6f under=%.6f\n",
               (double) _hb_depend_fuzzer_inject_over_approx,
               (double) _hb_depend_fuzzer_inject_under_approx);
    }
  }

  hb_font_t *font = hb_font_create (face);
  hb_map_t *unicode_to_glyph_cache = nullptr;

  if (_hb_depend_fuzzer_explicit_test)
  {
    test_params params = generate_test_from_explicit (face, font);
    if (params.glyphs == NULL)
      fprintf (stderr, "Error: Failed to generate test from explicit parameters\n");
    else
    {
      print_test_params (0, params.is_gsub, params.glyphs, params.codepoints, params.features);
      if (!params.is_gsub && !unicode_to_glyph_cache)
      {
        unicode_to_glyph_cache = hb_map_create ();
        hb_face_collect_nominal_glyph_mapping (face, unicode_to_glyph_cache, NULL);
      }
      compare_closures (face, depend, 0, _hb_depend_fuzzer_current_font_path,
                        params.is_gsub, params.glyphs, params.codepoints, params.features,
                        unicode_to_glyph_cache);
    }
    if (params.glyphs)     hb_set_destroy (params.glyphs);
    if (params.codepoints) hb_set_destroy (params.codepoints);
    if (params.features)   hb_set_destroy (params.features);
  }
  else if (_hb_depend_fuzzer_single_test != 0)
  {
    uint64_t test_seed = _hb_depend_fuzzer_single_test;
    test_params params = generate_test_from_seed (test_seed, face, font);
    if (params.target_size == 0 || params.glyphs == NULL)
      fprintf (stderr, "Warning: Font too small for this test type\n");
    else
    {
      print_test_params (test_seed, params.is_gsub, params.glyphs, params.codepoints, params.features);
      if (!params.is_gsub && !unicode_to_glyph_cache)
      {
        unicode_to_glyph_cache = hb_map_create ();
        hb_face_collect_nominal_glyph_mapping (face, unicode_to_glyph_cache, NULL);
      }
      compare_closures (face, depend, test_seed, _hb_depend_fuzzer_current_font_path,
                        params.is_gsub, params.glyphs, params.codepoints, params.features,
                        unicode_to_glyph_cache);
    }
    if (params.glyphs)     hb_set_destroy (params.glyphs);
    if (params.codepoints) hb_set_destroy (params.codepoints);
    if (params.features)   hb_set_destroy (params.features);
  }
  else
  {
    /* Generate T pseudo-random 64-bit seeds, then run compare_closures for each */
    std::mt19937 test_rng (_hb_depend_fuzzer_seed);
    std::uniform_int_distribution<uint32_t> uint32_dist;

    for (unsigned i = 0; i < _hb_depend_fuzzer_num_tests; i++)
    {
      /* Keep high bit clear for gint64 compatibility with -t parameter */
      uint64_t test_seed = (((uint64_t)uint32_dist (test_rng) << 32) | (uint64_t)uint32_dist (test_rng)) & 0x7FFFFFFFFFFFFFFFULL;

      test_params params = generate_test_from_seed (test_seed, face, font);
      if (params.target_size == 0 || params.glyphs == NULL)
        continue;

      if (_hb_depend_fuzzer_verbose)
        print_test_params (test_seed, params.is_gsub, params.glyphs, params.codepoints, params.features);

      if (!params.is_gsub && !unicode_to_glyph_cache)
      {
        unicode_to_glyph_cache = hb_map_create ();
        hb_face_collect_nominal_glyph_mapping (face, unicode_to_glyph_cache, NULL);
      }

      compare_closures (face, depend, test_seed, _hb_depend_fuzzer_current_font_path,
                        params.is_gsub, params.glyphs, params.codepoints, params.features,
                        unicode_to_glyph_cache);

      if (params.glyphs)     hb_set_destroy (params.glyphs);
      if (params.codepoints) hb_set_destroy (params.codepoints);
      if (params.features)   hb_set_destroy (params.features);
    }
  }

  if (unicode_to_glyph_cache)
    hb_map_destroy (unicode_to_glyph_cache);
  hb_font_destroy (font);
  hb_subset_depend_destroy (depend);
  hb_face_destroy (face);
  hb_blob_destroy (blob);
#endif /* !HB_NO_SUBSET_DEPEND */

  return 0;
}
