/**
 * `react-views` adapter — surfaces React components from `.tsx` / `.jsx`
 * files as candidate views.
 *
 * Why this exists: codebases with React/React Native UI (idle-meta's
 * editor, mythology-mobile, idle-clicker-mobile) declare their views as
 * top-level component functions or classes. None of the prior adapters
 * (typescript-decorators, typescript-interfaces, express-routes,
 * typescript-prisma) read JSX — so all view declarations were silently
 * dropped from analyse output.
 *
 * Each detected component becomes one entry in `prepassFacts.views[]`,
 * keyed by component name. Skeleton-emitter then emits a `views:` block
 * per detected SpecVerse component, populated with the views that fall
 * under that source-tree subtree.
 *
 * Detection criteria (any one matches):
 *   - `export function Name(...)` returning JSX (heuristic: contains `<...>`)
 *   - `export const Name = (...) => <...>` or `export const Name: FC<...> = ...`
 *   - `export default function Name(...)` or `export default class Name`
 *   - `class Name extends React.Component` / `extends Component`
 *
 * Naming gates:
 *   - PascalCase identifier required (lowercase-first names = helpers, skip)
 *   - Length ≥ 2 (single-letter names = generic placeholders, skip)
 *
 * Out of scope for v1:
 *   - JSX render-tree analysis (which other components does this view render?)
 *   - Hook detection (`useFoo`, `useBar`)
 *   - Prop type extraction
 *   - State/store consumer detection
 *   These can be added incrementally without breaking the v1 contract.
 */
/** True when the file path looks like a React/JSX source file. */
export function isReactViewFile(filePath) {
    return /\.(tsx|jsx)$/i.test(filePath);
}
/** True when the source contains content that looks like JSX or React Native imports. */
export function looksLikeReactSource(source) {
    // Quick gate: must reference React or look like JSX. Avoids parsing
    // every .tsx file (some are pure type/utility helpers with no UI).
    if (/\bfrom\s+['"]react(?:-native)?['"]/.test(source))
        return true;
    if (/<[A-Z][A-Za-z0-9_]*[\s/>]/.test(source))
        return true; // JSX tag
    return false;
}
/** PascalCase + min-length gate for component names. */
function isComponentNameShape(name) {
    return /^[A-Z][A-Za-z0-9_]+$/.test(name) && name.length >= 2;
}
/** Map a 0-indexed character offset into the source to a 1-indexed line number. */
function offsetToLine(source, offset) {
    // Count newlines before offset; +1 for 1-indexed.
    let line = 1;
    for (let i = 0; i < offset && i < source.length; i++) {
        if (source[i] === '\n')
            line++;
    }
    return line;
}
/** Find the matching close brace for an open brace at `openIdx`. */
function findMatchingBrace(source, openIdx) {
    let depth = 1;
    let inString = null;
    let inLineComment = false;
    let inBlockComment = false;
    for (let i = openIdx + 1; i < source.length; i++) {
        const c = source[i];
        const n = source[i + 1];
        if (inLineComment) {
            if (c === '\n')
                inLineComment = false;
            continue;
        }
        if (inBlockComment) {
            if (c === '*' && n === '/') {
                inBlockComment = false;
                i++;
            }
            continue;
        }
        if (inString) {
            if (c === '\\') {
                i++;
                continue;
            }
            if (c === inString)
                inString = null;
            continue;
        }
        if (c === '/' && n === '/') {
            inLineComment = true;
            i++;
            continue;
        }
        if (c === '/' && n === '*') {
            inBlockComment = true;
            i++;
            continue;
        }
        if (c === '"' || c === "'" || c === '`') {
            inString = c;
            continue;
        }
        if (c === '{')
            depth++;
        else if (c === '}') {
            depth--;
            if (depth === 0)
                return i;
        }
    }
    return -1;
}
/**
 * Strip line and block comments from source so the regex passes don't
 * pick up commented-out function-like text. Crude but sufficient — we
 * only need approximate line ranges, not exact ones.
 */
function stripComments(source) {
    let out = '';
    let inLine = false;
    let inBlock = false;
    let inString = null;
    for (let i = 0; i < source.length; i++) {
        const c = source[i];
        const n = source[i + 1];
        if (inLine) {
            if (c === '\n') {
                inLine = false;
                out += c;
            }
            else
                out += ' ';
            continue;
        }
        if (inBlock) {
            if (c === '*' && n === '/') {
                inBlock = false;
                out += '  ';
                i++;
            }
            else if (c === '\n')
                out += '\n';
            else
                out += ' ';
            continue;
        }
        if (inString) {
            out += c;
            if (c === '\\') {
                i++;
                if (i < source.length)
                    out += source[i];
                continue;
            }
            if (c === inString)
                inString = null;
            continue;
        }
        if (c === '/' && n === '/') {
            inLine = true;
            out += '  ';
            i++;
            continue;
        }
        if (c === '/' && n === '*') {
            inBlock = true;
            out += '  ';
            i++;
            continue;
        }
        if (c === '"' || c === "'" || c === '`') {
            inString = c;
            out += c;
            continue;
        }
        out += c;
    }
    return out;
}
/**
 * Parse a single React/JSX file and extract candidate view declarations.
 * Returns at most one entry per name (per-file dedup).
 */
export function parseReactViews(filePath, source) {
    if (!looksLikeReactSource(source)) {
        return { filePath, views: [] };
    }
    const cleaned = stripComments(source);
    const found = new Map();
    const record = (name, kind, offset, endOffset) => {
        if (!isComponentNameShape(name))
            return;
        if (found.has(name))
            return; // Per-file dedup.
        const startLine = offsetToLine(cleaned, offset);
        const endLine = endOffset !== undefined ? offsetToLine(cleaned, endOffset) : startLine;
        // Engines 6.30.3+: extract a leading JSDoc / line-comment block above
        // the declaration (within 3 source lines). Looks at the ORIGINAL
        // source so comments aren't already stripped.
        const docstring = extractLeadingDocstring(source, startLine);
        // Engines 6.31.0+: extract richer structural facts from the
        // component body. Body is the text between the start and end of
        // the declaration in the cleaned (comment-stripped) source.
        const bodyEnd = endOffset !== undefined ? endOffset : Math.min(cleaned.length, offset + 4000);
        const body = cleaned.slice(offset, bodyEnd);
        const viewType = classifyViewType({ name, kind, filePath, body });
        const usesComponents = extractUsesComponents(body);
        const actions = extractActions(body);
        const emptyState = detectEmptyState(body);
        found.set(name, {
            name,
            filePath,
            sourceLineRange: [startLine, endLine],
            kind,
            isScreen: name.endsWith('Screen'),
            ...(docstring ? { docstring } : {}),
            viewType,
            ...(usesComponents.length > 0 ? { usesComponents } : {}),
            ...(actions.length > 0 ? { actions } : {}),
            ...(emptyState ? { emptyState: true } : {}),
        });
    };
    // 1. Class components: `class Name extends (React.)Component`
    const classRe = /\bclass\s+([A-Z][A-Za-z0-9_]+)\s+extends\s+(?:React\.)?(?:Pure)?Component\b/g;
    let m;
    while ((m = classRe.exec(cleaned)) !== null) {
        record(m[1], 'class-component', m.index);
    }
    // 2. Function declarations that look like components: `function Name(...)` followed by JSX.
    // Heuristic: declared with `export` (or `export default`) and the body contains a JSX-shaped return.
    const funcRe = /\bexport\s+(?:default\s+)?(?:async\s+)?function\s+([A-Z][A-Za-z0-9_]+)\s*\(/g;
    while ((m = funcRe.exec(cleaned)) !== null) {
        const name = m[1];
        const isDefault = /\bexport\s+default\b/.test(cleaned.slice(Math.max(0, m.index - 20), m.index + m[0].length));
        // Locate the opening brace of the function body.
        const openParenIdx = m.index + m[0].length - 1;
        const closeParenIdx = findMatchingParenLite(cleaned, openParenIdx);
        if (closeParenIdx < 0)
            continue;
        const openBraceIdx = cleaned.indexOf('{', closeParenIdx);
        if (openBraceIdx < 0)
            continue;
        const closeBraceIdx = findMatchingBrace(cleaned, openBraceIdx);
        if (closeBraceIdx < 0)
            continue;
        const body = cleaned.slice(openBraceIdx, closeBraceIdx + 1);
        if (!bodyContainsJsxReturn(body))
            continue;
        record(name, isDefault ? 'default-export' : 'function-component', m.index, closeBraceIdx);
    }
    // 3. Arrow / FC declarations: `export const Name = (...) => ...` or `export const Name: FC = ...`
    const arrowRe = /\bexport\s+const\s+([A-Z][A-Za-z0-9_]+)\s*(?::\s*(?:React\.)?(?:FC|FunctionComponent|VFC)[^=]*)?=\s*(?:\(|<|\b(?:React\.)?(?:memo|forwardRef)\s*\()/g;
    while ((m = arrowRe.exec(cleaned)) !== null) {
        const name = m[1];
        // Best-effort body lookup: find the `=>` that follows the equals.
        const arrowAt = cleaned.indexOf('=>', m.index + m[0].length - 1);
        if (arrowAt < 0)
            continue;
        // Body is either a brace-block or an expression that contains JSX.
        const afterArrow = arrowAt + 2;
        let bodyEnd = afterArrow;
        if (cleaned[afterArrow] === ' ' || cleaned[afterArrow] === '\n') {
            // Skip whitespace.
            let j = afterArrow;
            while (j < cleaned.length && /\s/.test(cleaned[j]))
                j++;
            if (cleaned[j] === '{') {
                const close = findMatchingBrace(cleaned, j);
                if (close >= 0)
                    bodyEnd = close;
            }
            else {
                // Expression body — scan to the next semicolon at the outer level.
                bodyEnd = Math.min(cleaned.length, j + 2000); // bounded sniff
            }
        }
        const body = cleaned.slice(arrowAt, bodyEnd + 1);
        if (!body.includes('<') && !/return\s*\(/.test(body))
            continue;
        // JSX detection: uppercase tag, lowercase tag + whitespace OR self-
        // closing slash OR closing `>`, closing tag, or fragment.
        if (!/<[A-Z]|<[a-z]+[\s/>]|<\/|<>/.test(body))
            continue;
        record(name, 'arrow-component', m.index, bodyEnd);
    }
    // 4. Default-export-only forms missed above: `export default function (...)` (anonymous).
    // Skip these — anonymous default exports can't be named meaningfully without filename.
    // Filename-derived naming is the caller's responsibility.
    return { filePath, views: [...found.values()] };
}
/** True when a function body (between { and }) contains a JSX-shaped return. */
function bodyContainsJsxReturn(body) {
    // Quick gate: any return followed by `<` (with optional paren) or any `<X />`-ish tag.
    if (/return\s*\(?\s*<[A-Za-z]/.test(body))
        return true;
    if (/<>[\s\S]*<\//.test(body))
        return true; // fragment
    if (/<[A-Z][A-Za-z0-9_]*[\s/>]/.test(body))
        return true;
    return false;
}
/**
 * Extract a leading docstring from the original source, scanning upward
 * from the declaration line. Handles three comment shapes (engines 6.30.3+):
 *
 *   - JSDoc block: `/** description */ ` (multi-line stripped, joined on space)
 *   - Block comment: `; /* description *\/`
*   - Line comments: `// description` runs (consecutive lines joined)
*
* Returns null when no comment is found within 3 lines of the declaration
* or when the comment looks like a directive (`@ts-ignore`, `eslint-disable`, etc).
*/
function extractLeadingDocstring(source, declStartLine) {
    const lines = source.split('\n');
    // Scan up from the line BEFORE the declaration. Skip blank lines.
    let i = declStartLine - 2; // 0-indexed; one above declStartLine (which is 1-indexed)
    while (i >= 0 && lines[i].trim() === '')
        i--;
    if (i < 0)
        return null;
    // Limit search to 3 source lines back from the declaration.
    const minLine = Math.max(0, declStartLine - 2 - 5);
    // Try block comment ending on this line.
    if (lines[i].trimEnd().endsWith('*/')) {
        // Walk up to find the opening `/*` or `/**`.
        const collected = [];
        while (i >= minLine) {
            const line = lines[i];
            collected.unshift(line);
            if (/^\s*\/\*\*?/.test(line))
                break;
            i--;
        }
        if (i < minLine)
            return null;
        return cleanBlockComment(collected.join('\n'));
    }
    // Try line comments (consecutive `//`).
    if (/^\s*\/\//.test(lines[i])) {
        const collected = [];
        while (i >= minLine && /^\s*\/\//.test(lines[i])) {
            collected.unshift(lines[i]);
            i--;
        }
        return cleanLineComments(collected.join('\n'));
    }
    return null;
}
/**
 * Clean a multi-line JSDoc / block comment to a single readable line.
 * Strips comment markers, removes leading `*`s, ignores @-directives and
 * empty lines, joins on spaces.
 */
function cleanBlockComment(raw) {
    const stripped = raw
        .replace(/^\s*\/\*\*?/, '') // opening `/*` or `/**`
        .replace(/\*\/\s*$/, ''); // closing `*/`
    const lines = stripped
        .split('\n')
        .map((l) => l.replace(/^\s*\*\s?/, '').trim())
        .filter((l) => l && !l.startsWith('@')); // drop @param / @returns / @ts-ignore / etc.
    if (lines.length === 0)
        return null;
    const joined = lines.join(' ').trim();
    if (!joined)
        return null;
    // Skip directives.
    if (/^(eslint-|ts-|@)/i.test(joined))
        return null;
    // Cap length so over-long descriptions don't bloat the spec.
    return joined.length > 240 ? joined.slice(0, 237) + '...' : joined;
}
function cleanLineComments(raw) {
    const lines = raw
        .split('\n')
        .map((l) => l.replace(/^\s*\/\/\s?/, '').trim())
        .filter(Boolean);
    if (lines.length === 0)
        return null;
    const joined = lines.join(' ');
    if (/^(eslint-|ts-|@)/i.test(joined))
        return null;
    return joined.length > 240 ? joined.slice(0, 237) + '...' : joined;
}
/**
 * Engines 6.31.0+. Classify a React component into one of SpecVerse's
 * standard view types from JSX shape + filename hints. Returns the
 * canonical type string. Falls back to `page` (the catch-all
 * top-level type) when no specific pattern matches.
 *
 * Detection table (first match wins, ordered most-specific first):
 *
 *   `provider`  — Renders `Context.Provider` or `<XContext.Provider>` and
 *                 little else. Distinguished from generic wrappers by
 *                 being focused on the Provider alone.
 *   `layout`    — Filename ends `Layout` AND component renders children
 *                 via `{children}` or `<Outlet/>` (Next.js / RR shapes).
 *   `screen`    — Filename ends `Screen` OR uses `SafeAreaView` /
 *                 `<Screen>` wrapper (RN convention).
 *   `modal`     — Renders `<Modal>` / `<Dialog>` / `createPortal` /
 *                 `<dialog>`.
 *   `wizard`    — Multi-step flow detected by `step` state + numeric
 *                 progression (heuristic).
 *   `dashboard` — Multiple grid/panel children at top level.
 *   `form`      — Renders a `<form>` with submit handler, or has
 *                 `onSubmit` / `useForm` patterns.
 *   `list`      — Renders `<table>`, `.map(item =>`, or `<ul>`/`<ol>`
 *                 with iteration.
 *   `widget`    — Tiny render — single root element, no state, ≤2 JSX
 *                 nodes deep. Pills, badges, icons, status chips.
 *   `detail`    — Single-entity readonly render (h1 + dl/dt/dd or
 *                 similar information-display shape).
 *   `page`      — Default catch-all when nothing more specific matches.
 */
export function classifyViewType(input) {
    const { name, filePath, body } = input;
    const filenameStem = filePath.split('/').pop()?.replace(/\.(tsx|jsx)$/, '') ?? '';
    // Provider — most specific, check first.
    if (/<\w+\s*\.\s*Provider\b/.test(body) || /createContext\b[\s\S]{0,200}\.\s*Provider/.test(body)) {
        return 'provider';
    }
    // Layout — RootLayout / TabLayout pattern (Next.js / Expo Router).
    if (/Layout$/.test(name) || /Layout$/.test(filenameStem)) {
        if (/\{\s*children\s*\}/.test(body) || /<Outlet\b/.test(body) || /<Slot\b/.test(body)) {
            return 'layout';
        }
    }
    // Screen — RN convention.
    if (/Screen$/.test(name) || /Screen$/.test(filenameStem))
        return 'screen';
    if (/<SafeAreaView\b/.test(body))
        return 'screen';
    // Modal / Dialog / Portal.
    if (/<Modal\b|<Dialog\b|<Drawer\b|createPortal\b|<dialog\b/.test(body))
        return 'modal';
    // Wizard heuristic — multi-step state.
    if (/useState[\s\S]{0,100}\bstep\b/.test(body) && /(setStep|nextStep|step\s*\+)/.test(body)) {
        return 'wizard';
    }
    // Form — submit handler or useForm hook.
    if (/<form\b/.test(body) || /\buseForm\s*\(/.test(body) || /onSubmit\s*=/.test(body)) {
        return 'form';
    }
    // List — table / array.map() / ul/ol with iteration.
    if (/<table\b/.test(body))
        return 'list';
    if (/\.\s*map\s*\(\s*[\w(]+\s*=>/.test(body) && /<(ul|ol|tbody|tr)\b/.test(body)) {
        return 'list';
    }
    if (/\.\s*map\s*\(\s*[\w(]+\s*=>\s*<\w/.test(body)) {
        // Generic .map → JSX (e.g. .map(g => <Card key={g.id}>...))
        return 'list';
    }
    // Dashboard — multiple top-level panels in a grid.
    if (/(grid-cols-|<Grid\b|<Stack\b)/.test(body) && (body.match(/<Card\b|<Panel\b|<Section\b/g) || []).length >= 2) {
        return 'dashboard';
    }
    // Detail — readonly single-entity display shape.
    if (/<dl\b/.test(body) || (/<h1\b/.test(body) && /<dd\b|<p>\s*\{[\w.]+\}/.test(body))) {
        return 'detail';
    }
    // Widget — small/single-element render, no state machinery.
    // Heuristic: short body (<800 chars after JSX trim) AND no useState AND
    // few JSX nodes (≤3 distinct tag opens).
    const tagOpens = (body.match(/<[A-Za-z]/g) || []).length;
    if (body.length < 1500 && !/useState\s*\(/.test(body) && tagOpens <= 5) {
        return 'widget';
    }
    // Default — page (top-level navigable / catch-all).
    return 'page';
}
/**
 * Engines 6.31.0+. Extract sub-component names rendered in this view.
 * Captures `<UpperCaseTag` patterns; consumer (skeleton-emitter) can
 * filter against detected views to keep only known refs. PascalCase-
 * gated to skip HTML tags (`<div>`, `<span>`, etc.).
 */
export function extractUsesComponents(body) {
    const found = new Set();
    const re = /<([A-Z][A-Za-z0-9_]+)/g;
    let m;
    while ((m = re.exec(body)) !== null) {
        const tag = m[1];
        // Skip Context.Provider / Fragment / known React internals.
        if (tag === 'Fragment')
            continue;
        found.add(tag);
    }
    return [...found];
}
/**
 * Engines 6.31.0+. Extract action declarations from button/link click
 * handlers. Each action gets a name (the handler reference), an
 * optional label (the button's visible text), and an optional variant
 * inferred from context (destructive / primary / secondary).
 *
 * Patterns recognised:
 *   <button onClick={handler}>Label</button>
 *   <Button onClick={handler} variant="destructive">Label</Button>
 *   <Pressable onPress={handler}>Label</Pressable>      (RN)
 *   <Link onClick={handler}>Label</Link>
 */
export function extractActions(body) {
    const out = [];
    const seen = new Set();
    // Match opening tag with onClick OR onPress + handler ref + capture
    // tag content up to closing tag.
    const re = /<(?:button|Button|Pressable|TouchableOpacity|Link|a)\b([^>]*?)on(?:Click|Press)\s*=\s*\{([\s\S]*?)\}([^>]*)>([\s\S]*?)<\/(?:button|Button|Pressable|TouchableOpacity|Link|a)>/g;
    let m;
    while ((m = re.exec(body)) !== null) {
        const beforeAttrs = m[1];
        const rawHandlerExpr = m[2];
        const afterAttrs = m[3];
        const innerText = m[4];
        const handler = parseHandlerName(rawHandlerExpr);
        if (!handler || seen.has(handler))
            continue;
        seen.add(handler);
        // Extract label — first plain-text content inside the tag. Use the
        // balanced-brace stripper so nested template literals + ternaries
        // (`{cond ? `${x}` : ''}`) don't leak partial expression fragments
        // through the label.
        const labelMatch = stripJsxNoise(innerText)
            .replace(/\s+/g, ' ')
            .trim();
        const label = labelMatch.length > 0 && labelMatch.length < 80
            ? labelMatch.slice(0, 80)
            : undefined;
        // Extract variant from class / variant attribute.
        let variant;
        const attrs = beforeAttrs + afterAttrs;
        if (/variant\s*=\s*["']destructive["']|delete|destructive|red-/.test(attrs + handler.toLowerCase())) {
            variant = 'destructive';
        }
        else if (/variant\s*=\s*["']primary["']|primary|cta/.test(attrs)) {
            variant = 'primary';
        }
        else if (/variant\s*=\s*["']secondary["']|secondary/.test(attrs)) {
            variant = 'secondary';
        }
        out.push({
            name: handler,
            ...(label ? { label } : {}),
            ...(variant ? { variant } : {}),
        });
    }
    return out;
}
/**
 * Engines 6.31.2+. Pick the most useful identifier out of a JSX-event
 * handler expression. Walks the expression in order of preference:
 *
 *   1. Bare identifier ref (`save`, `props.deleteItem` → `deleteItem`)
 *   2. First call inside an arrow / async arrow / function body
 *      (`async () => doStuff()` → `doStuff`)
 *
 * Reserved JS keywords (`async`, `function`, `return`, `await`, `void`,
 * `null`, `undefined`, `true`, `false`) are rejected as handler names so
 * we don't end up emitting `actions: { async: {} }` from
 * `onClick={async () => ...}`.
 */
function parseHandlerName(expr) {
    const RESERVED = new Set([
        'async', 'await', 'function', 'return', 'void',
        'null', 'undefined', 'true', 'false', 'new', 'this',
    ]);
    const cleaned = expr.trim();
    // Bare identifier or dotted access (`save`, `props.save`, `this.save`).
    const bare = cleaned.match(/^[\w.$]+$/);
    if (bare) {
        const tail = cleaned.replace(/^.*\./, '');
        if (!RESERVED.has(tail))
            return tail;
    }
    // Find the first call expression `someFn(...)` inside the handler body.
    const callRe = /\b([A-Za-z_$][\w$]*)\s*\(/g;
    let m;
    while ((m = callRe.exec(cleaned)) !== null) {
        const candidate = m[1];
        if (RESERVED.has(candidate))
            continue;
        return candidate;
    }
    return '';
}
/**
 * Engines 6.31.2+. Strip JSX noise (tags + balanced `{...}` expressions
 * including nested template literals) so the visible inner text falls
 * through cleanly. The naive `\{[^}]*\}` regex breaks on
 * `{cond ? `${x}` : ''}` because the first `}` it finds is inside the
 * template literal, not the JSX expression.
 */
function stripJsxNoise(s) {
    let out = '';
    let i = 0;
    while (i < s.length) {
        const c = s[i];
        if (c === '<') {
            // Skip a balanced tag `<...>`.
            const end = s.indexOf('>', i);
            if (end === -1) {
                i = s.length;
                break;
            }
            i = end + 1;
            continue;
        }
        if (c === '{') {
            // Skip a balanced JSX expression, tracking braces + template
            // literals so embedded `${...}` doesn't terminate us early.
            let depth = 1;
            let inBacktick = false;
            i++;
            while (i < s.length && depth > 0) {
                const ch = s[i];
                if (ch === '\\') {
                    i += 2;
                    continue;
                }
                if (ch === '`') {
                    inBacktick = !inBacktick;
                    i++;
                    continue;
                }
                if (!inBacktick) {
                    if (ch === '{')
                        depth++;
                    else if (ch === '}')
                        depth--;
                }
                i++;
            }
            out += ' ';
            continue;
        }
        out += c;
        i++;
    }
    return out;
}
/**
 * Engines 6.31.0+. Detect a "no items" / empty-state branch in the JSX.
 * Returns true when the body has a conditional render path for the
 * zero-data case.
 */
export function detectEmptyState(body) {
    // Common patterns:
    //   if (items.length === 0) return <Empty/>
    //   {items.length === 0 && <NoItems/>}
    //   {!items.length ? <Empty/> : ...}
    //   {isEmpty && <Empty/>}
    if (/\.length\s*===\s*0/.test(body))
        return true;
    if (/!\w+\.length/.test(body))
        return true;
    if (/\bisEmpty\b/.test(body))
        return true;
    if (/<(Empty|NoItems|EmptyState|NoData|NoResults)\b/.test(body))
        return true;
    return false;
}
/** Lightweight paren-matcher (string-aware). Used by parseReactViews's function-decl path. */
function findMatchingParenLite(source, openIdx) {
    let depth = 1;
    let inString = null;
    for (let i = openIdx + 1; i < source.length; i++) {
        const c = source[i];
        if (inString) {
            if (c === '\\') {
                i++;
                continue;
            }
            if (c === inString)
                inString = null;
            continue;
        }
        if (c === '"' || c === "'" || c === '`') {
            inString = c;
            continue;
        }
        if (c === '(')
            depth++;
        else if (c === ')') {
            depth--;
            if (depth === 0)
                return i;
        }
    }
    return -1;
}
//# sourceMappingURL=react-views.js.map