/**
 * Part 2 (#GUI-uplift) — view-extraction precision + renderable timeline.
 *
 * The react-views adapter extracts EVERY React component as a view, so an
 * analysed app's `views:` fills with presentational primitives (Card,
 * CardHeader), layout/infra (RootLayout), and sub-components (BookingDialog,
 * NewBookingForm) that are pieces OF a screen, not screens. The end-user GUI
 * should be the source's REAL screens.
 *
 * This pass keeps only top-level screens and — crucially — turns the source's
 * bespoke availability UI into a RENDERABLE view: when the source renders a
 * Timeline/Calendar component AND a model has a date-range + a belongsTo to a
 * resource, it emits a `<Resource>Timeline` view bound to that model. The
 * runtime's timeline resolver derives the rest of the binding from the model,
 * so the analysed app GAINS a working availability grid with no hand-authoring.
 *
 * Gates (drop a view if):
 *   1. it's a UI primitive — declared under a `ui/` directory (shadcn-style
 *      Card/Button/…);
 *   2. its viewType is `layout` or `provider` (app infra);
 *   3. it's a sub-component — imported by another component that is NOT a route
 *      page (so the page's top client survives; deeper pieces drop).
 * Conservative: runs only when a real component import graph is available; a
 * view with no detectable importer is kept (favour keeping a screen).
 */
import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
import { join } from 'path';
const IGNORE_DIRS = new Set([
    'node_modules', 'dist', 'build', 'out', '.git', '.next', '.turbo',
    '.cache', 'coverage', '__pycache__', '.pytest_cache', 'target', '.idea', '.vscode',
]);
const UI_DIR = /[\\/]ui[\\/]/i;
const DROP_TYPES = new Set(['layout', 'provider']);
const TIMELINE_COMPONENT = /(?:Timeline|Calendar|Schedule|Gantt|Agenda|Availability)/i;
const DATE_NAME = /(?:_at$|At$|_date$|Date$|check_?in$|check_?out$|start_?date$|end_?date$|arrival$|departure$)/i;
function isRoutePage(fp) {
    return /[\\/]app[\\/].*page\.(?:tsx|jsx)$/i.test(fp) || /[\\/]pages[\\/].*\.(?:tsx|jsx)$/i.test(fp);
}
/** Build componentName → set of importer file paths (who imports it). */
function buildImporters(sourceDir) {
    const out = new Map();
    const add = (name, file) => {
        if (!out.has(name))
            out.set(name, new Set());
        out.get(name).add(file);
    };
    const walk = (dir, depth) => {
        if (depth > 8)
            return;
        let entries;
        try {
            entries = readdirSync(dir);
        }
        catch {
            return;
        }
        for (const n of entries) {
            if (IGNORE_DIRS.has(n))
                continue;
            const full = join(dir, n);
            let st;
            try {
                st = statSync(full);
            }
            catch {
                continue;
            }
            if (st.isDirectory()) {
                walk(full, depth + 1);
                continue;
            }
            if (!/\.(?:tsx|jsx)$/.test(n))
                continue;
            let src;
            try {
                src = readFileSync(full, 'utf-8');
            }
            catch {
                continue;
            }
            // `import { A, B } from '...'` and `import C from '...'`.
            for (const m of src.matchAll(/import\s+(?:([A-Za-z0-9_]+)\s*,?\s*)?(?:\{([^}]*)\})?\s*from\s*['"][^'"]+['"]/g)) {
                if (m[1] && /^[A-Z]/.test(m[1]))
                    add(m[1], full);
                if (m[2]) {
                    for (const part of m[2].split(',')) {
                        const id = part.trim().split(/\s+as\s+/)[0].trim();
                        if (/^[A-Z][A-Za-z0-9_]*$/.test(id))
                            add(id, full);
                    }
                }
            }
        }
    };
    walk(sourceDir, 0);
    return out;
}
function attrEntries(model) {
    const a = model?.attributes;
    if (!a)
        return [];
    const list = Array.isArray(a) ? a : Object.entries(a).map(([name, v]) => ({ name, ...(v || {}) }));
    return list.map((x) => ({ name: String(x.name), type: String(x.declaredType ?? x.type ?? '') }));
}
function relEntries(model) {
    const r = model?.relationships;
    if (!r)
        return [];
    const list = Array.isArray(r) ? r : Object.entries(r).map(([name, v]) => ({ name, ...(v || {}) }));
    return list.map((x) => ({ name: String(x.name), type: String(x.type ?? ''), target: String(x.target ?? '') }));
}
// Name → renderable view type. A data-bound domain screen that creates / lists
// / edits a model (NewBookingForm, BookingDialog, BookingList, RoomManagement,
// HouseSelector) maps to `model-view` — the runtime's full per-model CRUD
// manager (create + list + edit + delete + relationship tabs), so the main GUI
// is FUNCTIONAL (you can add houses/rooms/bookings), not a read-only list.
// Dashboards/details keep their own type. Containers/controls that match
// nothing renderable (BookingManagerClient, TimelineControls, Home) → dropped.
const CRUD_SUFFIX = /(?:List|Table|Management|Manager|Selector|Picker|Browser|Grid|Overview)$/i;
const FORM_SUBSTR = /(?:Form|Dialog|Editor|Wizard|Create|Add|Edit|New[A-Z])/;
const DETAIL_SUFFIX = /(?:Detail|Profile)$/i;
const DASHBOARD_SUFFIX = /(?:Dashboard|Stats|Metrics|Summary)$/i;
function inferRenderableType(name, viewType) {
    // Name signals win over the JSX classifier's guess: a `*Management` /
    // `*Selector` / `*Form` screen IS a CRUD manager even if the classifier saw
    // a grid (dashboard) or a modal.
    if (CRUD_SUFFIX.test(name) || FORM_SUBSTR.test(name))
        return 'model-view';
    if (DASHBOARD_SUFFIX.test(name) || viewType === 'dashboard')
        return 'dashboard';
    if (DETAIL_SUFFIX.test(name) || viewType === 'detail')
        return 'detail';
    if (viewType === 'form' || viewType === 'list' || viewType === 'modal')
        return 'model-view';
    return null;
}
function pluralize(s) {
    if (/[^aeiou]y$/i.test(s))
        return s.slice(0, -1) + 'ies';
    if (/(s|x|z|ch|sh)$/i.test(s))
        return s + 'es';
    return s + 's';
}
/** The entity whose name is contained in the component name (longest match). */
function inferModel(name, entities) {
    const lname = String(name).toLowerCase();
    let best = null;
    for (const e of entities ?? []) {
        const en = String(e?.name ?? '');
        if (en && lname.includes(en.toLowerCase()) && (!best || en.length > best.length))
            best = en;
    }
    return best;
}
export function refineViews(opts) {
    const { views, entities, relationships = [], sourceDir } = opts;
    const dropped = [];
    if (!sourceDir || !existsSync(sourceDir) || !Array.isArray(views)) {
        return { views: views ?? [], dropped };
    }
    const importers = buildImporters(sourceDir);
    // Keep the source's REAL screens by mapping each to a renderable
    // {type, model} the runtime can draw; drop primitives, layout/infra, and
    // containers/controls that map to nothing. Import-reachability is NOT used
    // to drop here — a "sub-component" (NewBookingForm, RoomManagement) is often
    // a genuine user screen; mappability is the right filter.
    const kept = [];
    const mapped = [];
    const seenKey = new Set();
    for (const v of views) {
        const fp = String(v?.filePath ?? '');
        if (UI_DIR.test(fp)) {
            dropped.push({ name: v.name, reason: 'ui-primitive' });
            continue;
        }
        if (DROP_TYPES.has(String(v?.viewType ?? ''))) {
            dropped.push({ name: v.name, reason: 'layout-infra' });
            continue;
        }
        const model = inferModel(v.name, entities);
        const type = inferRenderableType(String(v.name), String(v?.viewType ?? ''));
        if (!model || !type) {
            dropped.push({ name: v.name, reason: 'unmappable' });
            continue;
        }
        const key = `${type}:${model}`;
        if (seenKey.has(key)) {
            dropped.push({ name: v.name, reason: 'duplicate' });
            continue;
        }
        seenKey.add(key);
        // A per-model CRUD manager reads cleanest named after the model (plural).
        const name = type === 'model-view' ? pluralize(model) : v.name;
        kept.push({ ...v, name, viewType: type, model });
        mapped.push({ name, type, model });
    }
    void isRoutePage; // retained for future route-aware refinements
    // ── Renderable timeline from the model shape + a source Timeline signal ──
    // The signal is source-wide: a Timeline/Calendar component declared or
    // imported ANYWHERE (the importer graph's keys are every imported component
    // name). This catches the common case where the component that renders the
    // Timeline (e.g. RoomBookingManager) isn't itself an extracted top-level view.
    let addedTimeline;
    const hasTimelineComponent = views.some((v) => TIMELINE_COMPONENT.test(String(v?.name ?? ''))) ||
        views.some((v) => (v?.usesComponents ?? []).some((u) => TIMELINE_COMPONENT.test(u))) ||
        [...importers.keys()].some((name) => TIMELINE_COMPONENT.test(name));
    if (hasTimelineComponent && Array.isArray(entities)) {
        for (const ent of entities) {
            const dateAttrs = attrEntries(ent).filter((a) => /date|time/i.test(a.type) || DATE_NAME.test(a.name));
            // belongsTo from the top-level relationships array first, falling back to
            // an inline entity.relationships shape.
            const topRel = relationships.find((r) => r.from === ent.name && r.type === 'belongsTo');
            const inlineRel = relEntries(ent).find((r) => r.type === 'belongsTo');
            const resource = topRel?.to || inlineRel?.target;
            if (dateAttrs.length >= 2 && resource) {
                const name = `${resource}Timeline`;
                if (!kept.some((v) => v.name === name)) {
                    kept.push({
                        name,
                        filePath: views.find((v) => TIMELINE_COMPONENT.test(String(v?.name ?? '')))?.filePath ?? sourceDir,
                        viewType: 'timeline',
                        model: ent.name,
                        docstring: `${resource} availability timeline`,
                    });
                    addedTimeline = { name, model: ent.name, lane: resource };
                }
                break; // one timeline is enough for the first pass
            }
        }
    }
    return { views: kept, dropped, addedTimeline, mapped };
}
//# sourceMappingURL=view-precision.js.map