var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { BaseEntity } from "@memberjunction/core";
import { RegisterClass, UUIDsEqual } from "@memberjunction/global";
import { MJQueryEntity } from "../generated/entity_subclasses.js";
import { QueryEngine } from "../engines/QueryEngine.js";
/**
 * Extended query entity with child-relationship getters and business logic.
 * All child data reads through `QueryEngine`'s event-driven cache — always
 * fresh, no `provider.Refresh()` needed.
 *
 * Available on both client and server. Server-specific logic (embedding,
 * extraction pipeline, dialect conversion) lives in `MJQueryEntityServer`.
 */
let MJQueryEntityExtended = class MJQueryEntityExtended extends MJQueryEntity {
    constructor() {
        // ─── Child Relationship Getters (read-through from QueryEngine) ─────────────
        super(...arguments);
        this._categoryPath = null;
        // ─── Cache Configuration ────────────────────────────────────────────────────
        this._cacheConfig = null;
    }
    /** All field definitions for this query */
    get QueryFields() {
        return QueryEngine.Instance.GetQueryFields(this.ID);
    }
    /** All parameter definitions for this query */
    get QueryParameters() {
        return QueryEngine.Instance.GetQueryParameters(this.ID);
    }
    /** All entity associations for this query */
    get QueryEntities() {
        return QueryEngine.Instance.QueryEntities.filter(e => UUIDsEqual(e.QueryID, this.ID));
    }
    /** All permission records for this query */
    get QueryPermissions() {
        return QueryEngine.Instance.GetQueryPermissions(this.ID);
    }
    /** All composition dependency records (queries this query references via {{query:"..."}} syntax) */
    get QueryDependencies() {
        return QueryEngine.Instance.Dependencies.filter(d => UUIDsEqual(d.QueryID, this.ID));
    }
    /** Queries that depend on (reference) this query via {{query:"..."}} syntax */
    get QueryDependents() {
        return QueryEngine.Instance.Dependencies.filter(d => UUIDsEqual(d.DependsOnQueryID, this.ID));
    }
    // ─── Category Navigation ────────────────────────────────────────────────────
    /** Gets the category entity for this query, or undefined if uncategorized */
    get CategoryEntity() {
        if (!this.CategoryID)
            return undefined;
        return QueryEngine.Instance.Categories.find(c => UUIDsEqual(c.ID, this.CategoryID));
    }
    /**
     * Gets the full hierarchical path of this query's category (e.g., "Ground-Truth-Queries/Sales").
     * Returns empty string if the query is not categorized.
     */
    get CategoryPath() {
        if (this._categoryPath === null) {
            this._categoryPath = this.BuildCategoryPath(this.CategoryID);
        }
        return this._categoryPath;
    }
    /**
     * Builds a category path string from a CategoryID by walking up the category hierarchy.
     */
    BuildCategoryPath(categoryID) {
        if (!categoryID)
            return '';
        const categories = QueryEngine.Instance.Categories;
        const segments = [];
        let currentID = categoryID;
        while (currentID) {
            const cat = categories.find(c => UUIDsEqual(c.ID, currentID));
            if (!cat)
                break;
            segments.unshift(cat.Name);
            currentID = cat.ParentID;
        }
        return segments.join('/');
    }
    // ─── Permission Checks ──────────────────────────────────────────────────────
    /**
     * Checks if a user has permission to run this query based on their roles.
     * A user can run a query if:
     * 1. The query has no permissions defined (open to all)
     * 2. The user has at least one role that is granted permission
     */
    UserHasRunPermissions(user) {
        const permissions = this.QueryPermissions;
        if (!permissions || permissions.length === 0) {
            return true;
        }
        if (user && user.UserRoles) {
            for (const userRole of user.UserRoles) {
                if (permissions.some(p => p.Role.trim().toLowerCase() === userRole.Role.trim().toLowerCase())) {
                    return true;
                }
            }
        }
        return false;
    }
    /**
     * Checks whether a user can run this query, considering query-level permissions,
     * entity-level read permissions, and composition dependencies (recursive).
     *
     * Permission logic:
     * 1. Check query-level run permissions (role-based)
     * 2. If the query has explicit Query Permissions, those take precedence for this
     *    query AND its composition dependencies (stored-procedure semantics)
     * 3. If no explicit Query Permissions, check entity read permissions for all
     *    referenced entities via the Query Entities bridge
     * 4. Recursively check all composition dependencies ({{query:"..."}})
     *
     * @param user The user to check permissions for
     * @param _visited Internal — tracks visited query IDs to prevent infinite loops in cyclic dependencies
     * @returns Object with canRun boolean and list of denied entity names
     */
    UserCanRun(user, _visited) {
        // Cycle guard
        const visited = _visited ?? new Set();
        if (visited.has(this.ID)) {
            return { canRun: true, deniedEntities: [] };
        }
        visited.add(this.ID);
        // Step 1: Check query-level run permissions
        if (!this.UserHasRunPermissions(user)) {
            return { canRun: false, deniedEntities: [] };
        }
        // Step 2: Explicit Query Permissions override entity checks for this query
        // and all its dependencies (stored-procedure semantics)
        const permissions = this.QueryPermissions;
        if (permissions && permissions.length > 0) {
            return { canRun: true, deniedEntities: [] };
        }
        // Step 3: No explicit permissions — check entity read access
        const deniedEntities = [];
        const queryEntities = this.QueryEntities;
        if (queryEntities && queryEntities.length > 0) {
            const md = this.ProviderToUse;
            for (const qe of queryEntities) {
                const entityInfo = md.EntityByID(qe.EntityID);
                if (!entityInfo)
                    continue; // Stale reference — skip
                const perms = entityInfo.GetUserPermisions(user);
                if (!perms || !perms.CanRead) {
                    deniedEntities.push(entityInfo.Name);
                }
            }
        }
        // Step 4: Recursively check composition dependencies
        const dependencies = this.QueryDependencies;
        if (dependencies && dependencies.length > 0) {
            const allQueries = QueryEngine.Instance.Queries;
            for (const dep of dependencies) {
                const depQuery = allQueries.find(q => UUIDsEqual(q.ID, dep.DependsOnQueryID));
                if (!depQuery)
                    continue; // Dependency not found — skip
                const depResult = depQuery.UserCanRun(user, visited);
                if (!depResult.canRun) {
                    return { canRun: false, deniedEntities: [...deniedEntities, ...depResult.deniedEntities] };
                }
                deniedEntities.push(...depResult.deniedEntities);
            }
        }
        return { canRun: deniedEntities.length === 0, deniedEntities };
    }
    // ─── Status Checks ──────────────────────────────────────────────────────────
    /** Whether this query has been formally approved for production use */
    get IsApproved() {
        return this.Status === 'Approved';
    }
    /**
     * Whether this query can be referenced by other queries via composition syntax.
     * A query is composable only when both Reusable=true and Status='Approved'.
     */
    get IsComposable() {
        return this.Reusable && this.IsApproved;
    }
    // ─── Platform SQL Resolution ────────────────────────────────────────────────
    /**
     * Resolves the SQL for a given database platform.
     * Resolution order:
     *   1. QuerySQL child table entry matching this query + platform's dialect
     *   2. Base SQL property (the query's primary SQL)
     */
    GetPlatformSQL(platform) {
        const qe = QueryEngine.Instance;
        const dialect = qe.SQLDialects.find(d => d.PlatformKey === platform);
        if (dialect) {
            const entry = qe.QuerySQLs.find(qs => UUIDsEqual(qs.QueryID, this.ID) && UUIDsEqual(qs.SQLDialectID, dialect.ID));
            if (entry?.SQL)
                return entry.SQL;
        }
        return this.SQL;
    }
    /**
     * Gets the cache configuration for this query.
     * If the query has no explicit cache config but category inheritance is enabled,
     * walks up the category tree to find inherited cache settings.
     */
    get CacheConfig() {
        if (this._cacheConfig !== null) {
            return this._cacheConfig;
        }
        if (this.CacheEnabled) {
            this._cacheConfig = {
                enabled: true,
                ttlMinutes: this.CacheTTLMinutes || 60,
                maxCacheSize: this.CacheMaxSize || undefined,
                cacheKey: 'exact'
            };
            return this._cacheConfig;
        }
        const inherited = this.getInheritedCacheConfig();
        if (inherited) {
            this._cacheConfig = inherited;
            return this._cacheConfig;
        }
        this._cacheConfig = { enabled: false, ttlMinutes: 0 };
        return this._cacheConfig;
    }
    getInheritedCacheConfig() {
        const categories = QueryEngine.Instance.Categories;
        let catID = this.CategoryID;
        while (catID) {
            const cat = categories.find(c => UUIDsEqual(c.ID, catID));
            if (!cat)
                break;
            if (cat.DefaultCacheEnabled && cat.CacheInheritanceEnabled) {
                return {
                    enabled: true,
                    ttlMinutes: cat.DefaultCacheTTLMinutes || 60,
                    maxCacheSize: cat.DefaultCacheMaxSize || undefined,
                    cacheKey: 'exact',
                    inheritFromCategory: true
                };
            }
            catID = cat.ParentID;
        }
        return null;
    }
};
MJQueryEntityExtended = __decorate([
    RegisterClass(BaseEntity, 'MJ: Queries')
], MJQueryEntityExtended);
export { MJQueryEntityExtended };
//# sourceMappingURL=MJQueryEntityExtended.js.map