"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LOAD_SYMBOL_MAP = void 0;
exports.hmshTimestampToISO = hmshTimestampToISO;
exports.computeDuration = computeDuration;
exports.fromString = fromString;
exports.sanitizeAppId = sanitizeAppId;
exports.quoteSchema = quoteSchema;
exports.loadSymbolMap = loadSymbolMap;
exports.inflateAttributes = inflateAttributes;
exports.restoreHierarchy = restoreHierarchy;
exports.extractActivities = extractActivities;
const db_1 = require("../lib/db");
// ── HotMesh timestamp helpers ───────────────────────────────────────────────
/**
 * Convert HotMesh's compact timestamp (YYYYMMDDHHmmss.SSS) to ISO 8601.
 */
function hmshTimestampToISO(ts) {
    if (!ts || ts.length < 14)
        return ts;
    const y = ts.slice(0, 4);
    const mo = ts.slice(4, 6);
    const d = ts.slice(6, 8);
    const h = ts.slice(8, 10);
    const mi = ts.slice(10, 12);
    const rest = ts.slice(12); // ss.SSS
    return `${y}-${mo}-${d}T${h}:${mi}:${rest}Z`;
}
/**
 * Compute duration in milliseconds between two HotMesh compact timestamps.
 */
function computeDuration(ac, au) {
    if (!ac || !au)
        return null;
    const s = new Date(hmshTimestampToISO(ac)).getTime();
    const e = new Date(hmshTimestampToISO(au)).getTime();
    return e >= s ? e - s : null;
}
// ── HotMesh serialization ───────────────────────────────────────────────────
/**
 * Deserialize a HotMesh serialized value.
 * Prefix conventions: /s = JSON, /d = number, /t = true, /f = false, /n = null.
 */
function fromString(value) {
    if (typeof value !== 'string')
        return undefined;
    const prefix = value.slice(0, 2);
    const rest = value.slice(2);
    switch (prefix) {
        case '/t': return true;
        case '/f': return false;
        case '/d': return Number(rest);
        case '/n': return null;
        case '/s': return JSON.parse(rest);
        default: return value;
    }
}
// ── Schema helpers ──────────────────────────────────────────────────────────
/**
 * Validate and sanitize an app_id for use as a Postgres schema name.
 */
function sanitizeAppId(appId) {
    if (!/^[a-zA-Z0-9_.-]+$/.test(appId)) {
        throw new Error('Invalid app_id: must contain only alphanumeric characters, underscores, hyphens, and dots');
    }
    return appId;
}
/**
 * Quote a schema name for safe use in SQL queries (Postgres identifier quoting).
 */
function quoteSchema(schema) {
    return `"${schema.replace(/"/g, '""')}"`;
}
// ── Symbol inflation ────────────────────────────────────────────────────────
/** Schema-qualified symbol lookup — schema is injected as an identifier, not a parameter. */
const LOAD_SYMBOL_MAP = (schema) => `SELECT key, field, value FROM ${schema}.symbols WHERE key LIKE $1`;
exports.LOAD_SYMBOL_MAP = LOAD_SYMBOL_MAP;
/**
 * Load all symbol key mappings from {schema}.symbols for the given appId.
 * Returns a reverse map: abbreviated 3-char key → human-readable path.
 */
async function loadSymbolMap(schema, appId) {
    const pool = (0, db_1.getPool)();
    try {
        const result = await pool.query((0, exports.LOAD_SYMBOL_MAP)(schema), ['keys:%']);
        const reverseMap = {};
        for (const row of result.rows) {
            if (row.field && row.value) {
                reverseMap[row.value] = row.field;
            }
        }
        return reverseMap;
    }
    catch (err) {
        // Schema or symbols table may not exist yet — return empty map
        // so attribute inflation falls back to raw abbreviated keys.
        if (err.code === '42P01')
            return {}; // undefined_table
        throw err;
    }
}
/**
 * Inflate raw HotMesh job attributes using the symbol map.
 * Converts abbreviated 3-char keys + dimensional indices into
 * human-readable "dimensions/path" entries with deserialized values.
 *
 * Key patterns:
 *   "abc"          → 3-char job-level key (metadata/jc, data/summary, etc.)
 *   "abc,N,M,..."  → activity/transition key with dimensional index
 *   ":"            → status semaphore
 */
function inflateAttributes(attrs, symbolMap) {
    const flat = {};
    const symKeyRegex = /^([a-zA-Z]{2,3})(,\d+(?:,\d+)*)?$/;
    for (const [key, rawValue] of Object.entries(attrs)) {
        if (key === ':') {
            flat[':'] = rawValue;
            continue;
        }
        const match = key.match(symKeyRegex);
        if (match) {
            const letters = match[1];
            const dimSuffix = match[2] || '';
            const inflatedPath = symbolMap[letters] || letters;
            const dimensions = dimSuffix ? dimSuffix.slice(1).replace(/,/g, '/') + '/' : '';
            flat[`${dimensions}${inflatedPath}`] = fromString(rawValue);
        }
        else {
            flat[key] = fromString(rawValue);
        }
    }
    return flat;
}
/**
 * Rebuild a nested object from a flat "dim1/dim2/.../path" → value map.
 */
function restoreHierarchy(flat) {
    const result = {};
    for (const key in flat) {
        if (flat[key] === undefined)
            continue;
        const keys = key.split('/');
        let current = result;
        for (let i = 0; i < keys.length; i++) {
            if (i === keys.length - 1) {
                current[keys[i]] = flat[key];
            }
            else {
                current[keys[i]] = current[keys[i]] || {};
                current = current[keys[i]];
            }
        }
    }
    return result;
}
/**
 * Walk an inflated HotMesh hierarchy to extract all activities/transitions.
 * Recurses through numeric dimension keys, collecting activity nodes that
 * have an `output.metadata` subtree.
 */
function extractActivities(hierarchy) {
    const activities = [];
    function walk(node, dims) {
        for (const key of Object.keys(node)) {
            if (/^\d+$/.test(key)) {
                walk(node[key], [...dims, key]);
                continue;
            }
            const actNode = node[key];
            if (!actNode || typeof actNode !== 'object')
                continue;
            const output = actNode['output'];
            if (!output)
                continue;
            const meta = output['metadata'];
            const data = output['data'];
            if (!meta)
                continue;
            activities.push({
                name: key,
                type: meta['atp'] || 'worker',
                step: meta['stp'] || key,
                ac: meta['ac'] || null,
                au: meta['au'] || null,
                traceId: meta['l1s'] || null,
                spanId: meta['l2s'] || null,
                error: meta['err'] || null,
                data: data || null,
                dimensions: dims.join('/'),
            });
        }
    }
    walk(hierarchy, []);
    activities.sort((a, b) => {
        if (!a.ac)
            return 1;
        if (!b.ac)
            return -1;
        return a.ac.localeCompare(b.ac);
    });
    return activities;
}
