const ENTITY_TABLES = {
    endpoint: 'endpoint',
    dto: 'dto',
    'database-table': 'database_table',
    'ui-view': 'ui_view',
    ac: 'ac',
};
const ALL_ENTITY_TYPES = ['endpoint', 'dto', 'database-table', 'ui-view', 'ac'];
export function isRawEntityType(value) {
    return (value === 'endpoint' ||
        value === 'dto' ||
        value === 'database-table' ||
        value === 'ui-view' ||
        value === 'ac');
}
export class RawEntityReader {
    db;
    constructor(db) {
        this.db = db;
    }
    getEntity(type, slug) {
        const table = ENTITY_TABLES[type];
        const row = this.db.prepare(`SELECT * FROM ${table} WHERE slug = ?`).get(slug);
        if (!row)
            return null;
        return this.hydrate(type, row);
    }
    getEntityById(type, id) {
        const table = ENTITY_TABLES[type];
        const row = this.db.prepare(`SELECT * FROM ${table} WHERE id = ?`).get(id);
        if (!row)
            return null;
        return this.hydrate(type, row);
    }
    getEntities(type, slugs) {
        const items = [];
        const missing = [];
        for (const slug of slugs) {
            const entity = this.getEntity(type, slug);
            if (entity)
                items.push(entity);
            else
                missing.push(slug);
        }
        return { items, missing };
    }
    findByTag(params) {
        const tagSlugs = params.tags.filter(Boolean);
        if (!tagSlugs.length)
            return [];
        const types = params.type ? [params.type] : ALL_ENTITY_TYPES;
        const out = [];
        for (const type of types) {
            out.push(...this.findByTagForType(type, tagSlugs, params.filter));
        }
        return out;
    }
    findByTagForType(type, tagSlugs, filter) {
        const table = ENTITY_TABLES[type];
        const placeholders = tagSlugs.map(() => '?').join(',');
        let sql;
        const params = [];
        if (filter === 'and') {
            sql = `
        SELECT e.* FROM ${table} e
         WHERE e.id IN (
          SELECT et.entity_id
            FROM entity_tag et
            JOIN tag t ON t.id = et.tag_id
           WHERE et.entity_type = ? AND t.slug IN (${placeholders})
        GROUP BY et.entity_id
          HAVING COUNT(DISTINCT t.slug) = ?
         )
         ORDER BY e.slug
      `;
            params.push(type, ...tagSlugs, tagSlugs.length);
        }
        else {
            sql = `
        SELECT e.* FROM ${table} e
         WHERE e.id IN (
          SELECT et.entity_id FROM entity_tag et
            JOIN tag t ON t.id = et.tag_id
           WHERE et.entity_type = ? AND t.slug IN (${placeholders})
         )
         ORDER BY e.slug
      `;
            params.push(type, ...tagSlugs);
        }
        const rows = this.db.prepare(sql).all(...params);
        return rows.map((row) => this.hydrate(type, row));
    }
    getSection(anchor) {
        const row = this.db
            .prepare('SELECT * FROM section_index WHERE anchor = ?')
            .get(anchor);
        if (!row)
            return null;
        return this.hydrateSection(row);
    }
    listTypes() {
        return [...ALL_ENTITY_TYPES];
    }
    listSlugs(type) {
        const table = ENTITY_TABLES[type];
        const rows = this.db
            .prepare(`SELECT slug FROM ${table} ORDER BY slug`)
            .all();
        return rows.map((r) => r.slug);
    }
    listTags() {
        const rows = this.db.prepare(`SELECT * FROM tag ORDER BY name`).all();
        const countRows = this.db
            .prepare(`SELECT t.slug AS slug, et.entity_type AS entity_type, COUNT(*) AS c
           FROM entity_tag et
           JOIN tag t ON t.id = et.tag_id
         GROUP BY t.slug, et.entity_type`)
            .all();
        const countMap = new Map();
        for (const row of countRows) {
            const entry = countMap.get(row.slug) ?? {};
            entry[row.entity_type] = row.c;
            countMap.set(row.slug, entry);
        }
        return rows.map((r) => ({
            slug: r.slug,
            name: r.name,
            color: r.color,
            description: r.description,
            counts: countMap.get(r.slug) ?? { endpoint: 0, dto: 0, 'database-table': 0, 'ui-view': 0, ac: 0 },
        }));
    }
    /** Returns sections that referenced the given entity (populated by section-indexer). */
    findSectionReferences(type, entityId) {
        const rows = this.db
            .prepare(`SELECT sel.anchor AS anchor, sel.relation AS relation,
                si.page_path AS page_path, si.heading_text AS heading_text
           FROM section_entity_link sel
           JOIN section_index si ON si.anchor = sel.anchor
          WHERE sel.entity_type = ? AND sel.entity_id = ?
          ORDER BY si.page_path, si.line_start`)
            .all(type, entityId);
        return rows.map((r) => ({
            anchor: r.anchor,
            pagePath: r.page_path,
            headingText: r.heading_text,
            relation: r.relation,
        }));
    }
    /** Endpoint-DTO links (denormalised). */
    findEndpointDtos(endpointId) {
        const rows = this.db
            .prepare(`SELECT d.slug AS dto_slug, d.name AS dto_name,
                ed.relation AS relation, ed.status_code AS status_code
           FROM endpoint_dto ed
           JOIN dto d ON d.id = ed.dto_id
          WHERE ed.endpoint_id = ?
          ORDER BY ed.relation, ed.status_code, d.name`)
            .all(endpointId);
        return rows.map((r) => ({
            dtoSlug: r.dto_slug,
            dtoName: r.dto_name,
            relation: r.relation,
            statusCode: r.status_code,
        }));
    }
    /** Reverse: endpoints linked to a DTO. */
    findDtoEndpoints(dtoId) {
        const rows = this.db
            .prepare(`SELECT e.slug AS slug, e.method AS method, e.path AS path,
                ed.relation AS relation, ed.status_code AS status_code
           FROM endpoint_dto ed
           JOIN endpoint e ON e.id = ed.endpoint_id
          WHERE ed.dto_id = ?
          ORDER BY ed.relation, ed.status_code, e.path`)
            .all(dtoId);
        return rows.map((r) => ({
            endpointSlug: r.slug,
            method: r.method,
            path: r.path,
            relation: r.relation,
            statusCode: r.status_code,
        }));
    }
    hydrate(type, row) {
        const id = row.id;
        const slug = row.slug;
        const tags = this.getEntityTagSlugs(type, id);
        const data = {};
        for (const [key, value] of Object.entries(row)) {
            if (key === 'id' || key === 'created_at' || key === 'updated_at')
                continue;
            data[key] = value;
        }
        // Generic JSON column hydration: any string column whose value parses as
        // a JSON array/object gets decoded. Replaces per-entity-type branches
        // (dto.fields, dto.examples, database-table.columns/indexes, ui-view.params).
        for (const [key, value] of Object.entries(data)) {
            if (typeof value !== 'string')
                continue;
            const trimmed = value.trimStart();
            if (!trimmed.startsWith('[') && !trimmed.startsWith('{'))
                continue;
            data[key] = safeJsonArray(value);
        }
        return { type, id, slug, data, tags };
    }
    hydrateSection(row) {
        return {
            anchor: row.anchor,
            pagePath: row.page_path,
            headingPath: row.heading_path,
            headingSlug: row.heading_slug,
            headingText: row.heading_text,
            headingLevel: row.heading_level,
            lineStart: row.line_start,
            lineEnd: row.line_end,
        };
    }
    getEntityTagSlugs(type, entityId) {
        const rows = this.db
            .prepare(`SELECT t.slug AS slug
           FROM entity_tag et
           JOIN tag t ON t.id = et.tag_id
          WHERE et.entity_type = ? AND et.entity_id = ?
          ORDER BY t.name`)
            .all(type, entityId);
        return rows.map((r) => r.slug);
    }
}
function safeJsonArray(raw) {
    try {
        const parsed = JSON.parse(raw);
        return Array.isArray(parsed) ? parsed : [];
    }
    catch {
        return [];
    }
}
//# sourceMappingURL=raw-entity-reader.js.map