#ifndef FREEDOM_CSS_SELECT_H
#define FREEDOM_CSS_SELECT_H

#include <stddef.h>
#include <string.h>

#include "css.h"

#ifdef __cplusplus
#error "Freedom is pure C (C11). C++ is not supported."
#endif

/*
 * css_select — the selector engine of the author-CSS module: parsing one complex
 * selector into a css_sel and matching it against a css_element chain. Split out
 * of css.c (anti-monolith clause) so the cascade/value side and the selector side
 * stay independently auditable. Internal contract: consumed only by css.c and its
 * tests/fuzzers; the public browser-facing API remains css.h.
 *
 * Selectors are hostile remote content. The engine is pure (no allocation, no
 * I/O), bounded (CSS_MAX_COMPOUNDS / CSS_MAX_ATTR_SEL / CSS_TOK_MAX) and fails
 * closed: any unsupported or malformed construct drops the whole selector.
 * See spec/css_select.md.
 */

/* Bounds shared with the cascade side (css.c token buffers reuse CSS_TOK_MAX). */
#define CSS_TOK_MAX             64u
#define CSS_MAX_CLASSES_PER_SEL 6

/* Combinator joining a compound to the one on its left. */
enum { COMB_DESCENDANT = 0, COMB_CHILD = 1, COMB_ADJACENT = 2, COMB_GENERAL = 3 };

/* Attribute selector operator. PRESENT is bare `[attr]`; the rest carry a value. */
enum { ATTR_PRESENT = 0, ATTR_EQ, ATTR_TILDE, ATTR_PIPE, ATTR_CARET, ATTR_DOLLAR,
       ATTR_STAR };

/*
 * Dynamic element state, as a bitmask on css_element.state. Zero -- the state
 * of a page that has just loaded, with the pointer nowhere and nothing focused
 * -- means no dynamic pseudo matches, which is what every browser does.
 *
 * These exist so the state can come from the ENGINE rather than from a constant
 * in the matcher: when the GUI knows which element is under the pointer it sets
 * CSEL_STATE_HOVER on that element's chain and re-resolves. See the doctrine
 * note in spec/css_select.md.
 */
enum {
    CSEL_STATE_HOVER          = 1u << 0,
    CSEL_STATE_ACTIVE         = 1u << 1,
    CSEL_STATE_FOCUS          = 1u << 2,
    CSEL_STATE_FOCUS_WITHIN   = 1u << 3,
    CSEL_STATE_FOCUS_VISIBLE  = 1u << 4
};

/* Pseudo-class kind (Hito 23b-9). NEVER covers :visited (Zero Knowledge: no
 * history to match, and no CSS history sniffing). The dynamic pseudos each get
 * their own kind so they can be driven independently from css_element.state --
 * they used to share a single PSEUDO_ALWAYS that matched unconditionally, which
 * painted every page as though the mouse were on every element at once. */
enum { PSEUDO_LINK = 0, PSEUDO_NEVER, PSEUDO_HOVER, PSEUDO_ROOT,
       PSEUDO_FIRST_CHILD, PSEUDO_LAST_CHILD, PSEUDO_ONLY_CHILD,
       PSEUDO_NTH_CHILD, PSEUDO_NTH_LAST_CHILD,
       PSEUDO_CHECKED, PSEUDO_DISABLED, PSEUDO_ENABLED,
       PSEUDO_NOT, PSEUDO_IS, PSEUDO_WHERE,
       /* R2: structural-of-type pseudos */
       PSEUDO_FIRST_OF_TYPE, PSEUDO_LAST_OF_TYPE, PSEUDO_ONLY_OF_TYPE,
       PSEUDO_NTH_OF_TYPE, PSEUDO_NTH_LAST_OF_TYPE,
       /* R2: element-state pseudos */
        PSEUDO_EMPTY, PSEUDO_TARGET, PSEUDO_LANG,
        /* R8: pseudo-elements (::before/::after) */
        PSEUDO_BEFORE, PSEUDO_AFTER,
        /* :has() relational pseudo-class (descendant walk) */
        PSEUDO_HAS,
        /* Remaining dynamic pseudos, each driven by its own CSEL_STATE_* bit. */
        PSEUDO_ACTIVE, PSEUDO_FOCUS, PSEUDO_FOCUS_WITHIN, PSEUDO_FOCUS_VISIBLE,
        /* ::placeholder (CSS Pseudo 4 4.5): styles a text control's placeholder,
         * never the control itself. */
        PSEUDO_PLACEHOLDER };

/* Sub-selector for :not()/:is()/:where(): a simple compound with only
 * tag/.class/#id/[attr] (no combinators, no pseudo-classes inside
 * sub-selectors). Bounded: CSS_MAX_SUB_SELS per selector, one level deep. */
#define CSS_MAX_SUB_SELS 4
#define CSS_SUB_MAX_ATTRS 1
#define CSS_SUB_MAX_PSEUDOS 2

/* One attribute selector inside a compound: name OP value, with a case flag. */
typedef struct css_attr_match {
    char name[CSS_TOK_MAX];
    char value[CSS_TOK_MAX];
    int  op;   /* ATTR_* */
    int  ci;   /* 1 = match the value case-insensitively (the trailing `i` flag) */
} css_attr_match;

typedef struct css_sub_sel {
    char tag[CSS_TOK_MAX];
    int  has_tag;
    char id[CSS_TOK_MAX];
    int  has_id;
    char cls[CSS_TOK_MAX];
    int  has_cls;
    css_attr_match attrs[CSS_SUB_MAX_ATTRS];
    int  nattrs;
    /* Simple (argument-less, non-element) pseudo-classes: PSEUDO_* kinds. */
    int  pseudos[CSS_SUB_MAX_PSEUDOS];
    int  npseudos;
} css_sub_sel;

/* One pseudo-class inside a compound. a/b are the An+B coefficients of the
 * nth-child family (unused otherwise). sub_first/sub_count index into the
 * parent css_sel.subs[] for :not/:is/:where, or -1/0 for no sub-selectors. */
typedef struct css_pseudo_match {
    int kind;  /* PSEUDO_* */
    int a, b;
    int sub_first; /* first sub-selector in parent sel->subs[], -1 = none */
    int sub_count; /* number of consecutive sub-selectors */
    char lang[CSS_TOK_MAX]; /* :lang() value (lowercased) */
} css_pseudo_match;

/* One compound selector: optional type, optional id, zero+ classes, zero+ [attr],
 * zero+ pseudo-classes. */
typedef struct css_compound {
    char tag[CSS_TOK_MAX];
    int  has_tag;
    char id[CSS_TOK_MAX];
    int  has_id;
    char cls[CSS_MAX_CLASSES_PER_SEL][CSS_TOK_MAX];
    int  ncls;
    css_attr_match attrs[CSS_MAX_ATTR_SEL];
    int  nattrs;
    css_pseudo_match pseudos[CSS_MAX_PSEUDO_SEL];
    int  npseudo;
} css_compound;

/* A complex selector: a chain of compounds, parts[nparts-1] being the subject (the
 * element a rule styles). comb[k] (k>=1) is the combinator to the LEFT of parts[k];
 * comb[0] is unused. A single compound is nparts == 1. subs[] holds sub-selectors
 * for :not()/:is()/:where() pseudo-classes, indexed by css_pseudo_match.sub_first. */
typedef struct css_sel {
    int  comb[CSS_MAX_COMPOUNDS];
    int  nparts;
    int  spec;
    int  order;     /* document order (tie-break) */
    int  rule;      /* index into rules[] */
    int  layer;     /* @layer rank (css_atrule), 0 = unlayered */
    css_sub_sel subs[CSS_MAX_SUB_SELS];
    int  nsubs;
    /* Feature bits every ancestor chain must contain for this selector to match (the tags, ids and
     * classes of the compounds linked to the subject by descendant/child combinators). 0 = no
     * requirement. Computed by csel_parse; see csel_anc_reject. */
    unsigned long long anc_req;
    /* Feature bits the SUBJECT element itself must have (its compound's tag, id and every class):
     * the index keys a selector by only one of them, this checks the rest without evaluating. */
    unsigned long long subj_req;
    /* Feature bits compound i alone requires of whatever element it is tested against. Lets the
     * ancestor walk of a descendant combinator skip, without comparing a single string, every
     * ancestor whose own feature bits lack one (see complex_matches). */
    unsigned long long part_req[CSS_MAX_COMPOUNDS];
    /* MUST stay the last member: a selector stored in a sheet keeps only the nparts compounds it
     * uses (csel_compact_size), so everything after this offset is not allocated for it. */
    css_compound parts[CSS_MAX_COMPOUNDS];
} css_sel;

/* Bytes a selector with `nparts` compounds needs: the header plus the compounds it uses. A full
 * css_sel is ~12.8 KiB; a typical rule selector (1-3 compounds) needs a third of that, and a
 * stylesheet holds tens of thousands of them. */
size_t csel_compact_size(int nparts);

/* Heap copy of *sel holding only the compounds in use (free()d by the caller); NULL on OOM or a
 * selector with nparts outside [1, CSS_MAX_COMPOUNDS]. The copy is read-only for matching:
 * parts[k] for k >= nparts is not backed by memory. */
css_sel *csel_compact_dup(const css_sel *sel);

/* Parses the complex selector s[a,b) into *sel (spec computed; order/rule left to
 * the caller). Returns 1 if supported, 0 to drop the selector (fail closed). */
int csel_parse(const char *s, size_t a, size_t b, css_sel *sel);

/* True if *sel matches element *el against its ancestor chain. target_id
 * (optional) is the URL fragment for :target matching; NULL -> never matches.
 * allow_pseudo_el: 1 = CSS cascade mode (::before/::after match), 0 = DOM
 * query mode (pseudo-elements never match real elements).
 * pseudo_kind (optional, may be NULL): when non-NULL and the subject compound
 * matched via PSEUDO_BEFORE or PSEUDO_AFTER, *pseudo_kind is set to that
 * pseudo-element kind; otherwise set to -1. */
int csel_matches(const css_sel *sel, const css_element *el, const char *target_id,
                 int allow_pseudo_el, int *pseudo_kind);

/* Ancestor filter. csel_element_feature_bits: the bits of one element's tag, id and classes (two
 * bits per feature, hashed the way csel_ident_eq compares). csel_anc_reject: 1 when the selector
 * REQUIRES a feature that none of the element's ancestors has (or that the element itself lacks:
 * a class or tag of its own compound), so it cannot match and need not be evaluated; 0 when it might. Conservative by construction: no bloom (anc_bloom_valid == 0) or no
 * requirement answers 0, and only compounds reachable through descendant/child combinators count. */
unsigned long long csel_element_feature_bits(const css_element *e);
static inline int csel_anc_reject(const css_sel *sel, const css_element *el) {
    return el->anc_bloom_valid && ((sel->anc_req & ~el->anc_bloom) != 0ull ||
                                   (sel->subj_req & ~el->own_bloom) != 0ull);
}

/* True when matching *sel can read sibling/child STRUCTURE (nth positions, of-type counts,
 * :empty, :has(), `+`/`~`): an element's position among its siblings and its children count.
 * Pure and conservative (anything unrecognised answers 1). A selector that answers 0 matches
 * from the element's own tag/id/classes/attributes and its ancestors alone, so the caller can
 * skip scanning sibling lists -- on a flat list of N nodes that scan made every match O(N). */
int csel_needs_structure(const css_sel *sel);

/* --- Identifiers: escapes and long names (spec/css_select.md, 2026-09-29) ---
 * A selector identifier (tag, .class, #id) is read with CSS escapes decoded
 * (`.md\:flex` is the class "md:flex") and stored in a CSS_TOK_MAX slot. One that
 * does not fit is FOLDED: its first CSEL_FOLD_PREFIX bytes, CSEL_FOLD_MARK (a byte
 * no identifier contains) and 16 hex digits of a 64-bit hash of the whole name.
 * csel_ident_eq folds the element's token the same way, so a CSS Modules name such
 * as `Primer_Brand__LogoSuite-module__...___bUhyS` (72 bytes) matches exactly
 * instead of being truncated and never matching. */
#define CSEL_FOLD_PREFIX   40u
#define CSEL_FOLD_MARK     '\x1f'
#define CSEL_IDENT_SCRATCH 1024u

int    csel_hex_val(char c);
size_t csel_emit_utf8(unsigned int cp, char *out);
/* Decodes CSS escapes in src[0,n) into dst (cap bytes, NUL-terminated, truncates). */
void   csel_unescape(char *dst, size_t cap, const char *src, size_t n);
/* Bytes the escape starting at s[i] occupies (0 when s[i] does not start one). */
size_t csel_escape_len(const char *s, size_t i, size_t b);
/* dst (CSS_TOK_MAX bytes) receives src[0,len) as-is, or folded when too long. */
void   csel_ident_fold(const char *src, size_t len, char *dst);
/* A stored (possibly folded) identifier against an element token of tlen bytes. */
int    csel_ident_eq(const char *stored, const char *tok, size_t tlen);
/* Reads an identifier at s[*ip] (escapes decoded, non-ASCII kept, lowercased when
 * lower != 0) into dst (CSS_TOK_MAX). Returns 0, *ip untouched, when none. */
int    csel_read_ident(const char *s, size_t *ip, size_t b, char *dst, int lower);

/* Index of the ';' ending the declaration that starts at s[i] (or of a '}' when
 * stop_brace), or b: separators inside quotes, inside (...) such as
 * url(data:a;b), or escaped, are not separators (CSS Syntax 5 section 5.4.4). */
size_t csel_decl_end(const char *s, size_t i, size_t b, int stop_brace);

/* --- ASCII helpers shared by the selector and cascade sides (internal) --- */

static inline char csel_lower_ch(char c) {
    return (c >= 'A' && c <= 'Z') ? (char)(c + 32) : c;
}

static inline int csel_ci_eq(const char *a, const char *b) {
    while (*a != '\0' && *b != '\0') {
        if (csel_lower_ch(*a) != csel_lower_ch(*b)) return 0;
        ++a; ++b;
    }
    return *a == '\0' && *b == '\0';
}

/* True if a starts with the (lowercase) prefix p, case-insensitively. */
static inline int csel_ci_starts(const char *a, const char *p) {
    while (*p != '\0') {
        if (*a == '\0' || csel_lower_ch(*a) != *p) return 0;
        ++a; ++p;
    }
    return 1;
}

/* True if a[0,n) equals b[0,n) up to n chars (b NUL-terminated), case-folded when ci. */
static inline int csel_span_eq(const char *a, const char *b, size_t n, int ci) {
    for (size_t i = 0; i < n; ++i) {
        char x = a[i], y = b[i];
        if (ci) { x = csel_lower_ch(x); y = csel_lower_ch(y); }
        if (x != y) return 0;
    }
    return 1;
}

/* Substring test (used both to drop any value carrying url() — always ci — and by
 * the attribute `*=` operator). A non-empty needle only; an empty one never matches. */
static inline int csel_substr(const char *hay, const char *needle, int ci) {
    size_t nl = strlen(needle), hl = strlen(hay);
    if (nl == 0 || nl > hl) return 0;
    for (size_t off = 0; off + nl <= hl; ++off)
        if (csel_span_eq(hay + off, needle, nl, ci)) return 1;
    return 0;
}

static inline int csel_ident_ch(char c) {
    return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
           (c >= '0' && c <= '9') || c == '_' || c == '-';
}

#endif /* FREEDOM_CSS_SELECT_H */
