"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.exportWorkflow = exportWorkflow;
exports.getWorkflowStatus = getWorkflowStatus;
exports.exportWorkflowExecution = exportWorkflowExecution;
exports.getWorkflowState = getWorkflowState;
exports.listJobs = listJobs;
const db_1 = require("../../lib/db");
const client_1 = require("./client");
const post_process_1 = require("./post-process");
/** Error thrown when a workflow job is not found (expired or never existed). */
class WorkflowNotFoundError extends Error {
    constructor(workflowId) {
        super(`${workflowId} Not Found`);
        this.status = 404;
        this.name = 'WorkflowNotFoundError';
    }
}
/**
 * Export the full workflow state for a given workflow (raw HotMesh format).
 *
 * Delegates to the HotMesh Durable `handle.export()` method and
 * normalises the result into an `LTWorkflowExport`.
 */
async function exportWorkflow(workflowId, taskQueue, workflowName, options) {
    try {
        const handle = await (0, client_1.getHandle)(taskQueue, workflowName, workflowId);
        const raw = await handle.export({ ...options, enrich_inputs: true });
        return {
            workflow_id: workflowId,
            data: raw.data,
            state: raw.state,
            status: raw.status,
            timeline: raw.timeline,
            transitions: raw.transitions,
        };
    }
    catch (err) {
        if (err instanceof WorkflowNotFoundError)
            throw err;
        throw new WorkflowNotFoundError(workflowId);
    }
}
/**
 * Return only the status semaphore for a workflow.
 * 0 = complete, negative = interrupted.
 */
async function getWorkflowStatus(workflowId, taskQueue, workflowName) {
    try {
        const handle = await (0, client_1.getHandle)(taskQueue, workflowName, workflowId);
        const status = await handle.status();
        return { workflow_id: workflowId, status };
    }
    catch {
        throw new WorkflowNotFoundError(workflowId);
    }
}
/**
 * Export workflow state as a structured execution event history.
 *
 * Delegates to HotMesh's native `handle.exportExecution()` which produces
 * typed events with ISO timestamps, durations, event cross-references,
 * system/user classification, and a summary.
 */
async function exportWorkflowExecution(workflowId, taskQueue, workflowName, options) {
    try {
        const handle = await (0, client_1.getHandle)(taskQueue, workflowName, workflowId);
        const [execution, raw] = await Promise.all([
            handle.exportExecution({ ...options, enrich_inputs: true }),
            handle.export({ allow: ['state'], values: true }),
        ]);
        const processed = (0, post_process_1.postProcessExecution)(execution);
        // Extract parent_workflow_id from the raw state metadata (HotMesh's `pj` field).
        // The SDK's WorkflowExecution doesn't surface this yet; when it does,
        // the SDK value takes precedence (defensive: only set if absent).
        if (!processed.parent_workflow_id) {
            const pj = extractParentJobId(raw);
            if (pj && pj !== workflowId) {
                processed.parent_workflow_id = pj;
            }
        }
        return processed;
    }
    catch (err) {
        if (err instanceof WorkflowNotFoundError)
            throw err;
        throw new WorkflowNotFoundError(workflowId);
    }
}
/**
 * Walk the raw export state looking for the `pj` (parent job ID) field.
 * HotMesh stores it at `state.output.metadata.pj` after symbol inflation.
 * Returns undefined if not found (workflow has no parent).
 */
function extractParentJobId(raw) {
    const state = raw?.state;
    if (!state || typeof state !== 'object')
        return undefined;
    // Typical path: state.output.metadata.pj
    const output = state.output ?? state;
    const metadata = output?.metadata;
    if (metadata?.pj && typeof metadata.pj === 'string') {
        return metadata.pj;
    }
    // Fallback: walk top-level state keys (older export shapes)
    if (state.pj && typeof state.pj === 'string') {
        return state.pj;
    }
    return undefined;
}
/**
 * Return the current job state of a workflow.
 * If the workflow is complete this is also the output.
 */
async function getWorkflowState(workflowId, taskQueue, workflowName) {
    try {
        const handle = await (0, client_1.getHandle)(taskQueue, workflowName, workflowId);
        const state = await handle.state(true);
        return { workflow_id: workflowId, state };
    }
    catch {
        throw new WorkflowNotFoundError(workflowId);
    }
}
const JOB_SORTABLE_COLUMNS = new Set(['created_at', 'updated_at', 'entity', 'status']);
function buildJobOrderBy(sortBy, order) {
    if (!sortBy || !JOB_SORTABLE_COLUMNS.has(sortBy)) {
        return '(CASE WHEN j.status > 0 THEN 0 ELSE 1 END), j.created_at DESC';
    }
    const dir = order === 'asc' ? 'ASC' : 'DESC';
    return `j.${sortBy} ${dir}`;
}
/**
 * List workflow jobs from durable.jobs where entity IS NOT NULL.
 * Returns paginated results sorted by active first, then created_at DESC.
 */
async function listJobs(params) {
    const limit = Math.min(params.limit ?? 20, 100);
    const offset = params.offset ?? 0;
    const pool = (0, db_1.getPool)();
    const conditions = ['j.entity IS NOT NULL'];
    const values = [];
    let idx = 1;
    if (params.registered === 'true') {
        conditions.push(`EXISTS (SELECT 1 FROM lt_config_workflows c WHERE c.workflow_type = j.entity)`);
    }
    else if (params.registered === 'false') {
        conditions.push(`NOT EXISTS (SELECT 1 FROM lt_config_workflows c WHERE c.workflow_type = j.entity)`);
    }
    if (params.entity) {
        const entities = params.entity.split(',').map((e) => e.trim()).filter(Boolean);
        if (entities.length === 1) {
            conditions.push(`j.entity = $${idx++}`);
            values.push(entities[0]);
        }
        else if (entities.length > 1) {
            conditions.push(`j.entity = ANY($${idx++})`);
            values.push(entities);
        }
    }
    if (params.search) {
        conditions.push(`j.key ILIKE $${idx++}`);
        values.push(`%${params.search}%`);
    }
    if (params.status === 'running') {
        conditions.push('j.status > 0');
    }
    else if (params.status === 'completed') {
        conditions.push('j.status = 0');
    }
    else if (params.status === 'failed') {
        conditions.push('j.status < 0');
    }
    const where = conditions.join(' AND ');
    const [countResult, dataResult] = await Promise.all([
        pool.query(`SELECT COUNT(*) FROM durable.jobs j WHERE ${where}`, values),
        pool.query(`WITH ju_symbols AS (
         SELECT value FROM durable.symbols
         WHERE key LIKE 'hmsh:durable:sym:keys:%' AND field = 'metadata/ju'
       )
       SELECT j.key, j.entity, j.status, j.is_live, j.created_at,
         CASE WHEN j.updated_at != j.created_at THEN j.updated_at
              WHEN ju.value IS NOT NULL THEN to_timestamp(ju.value, 'YYYYMMDDHH24MISS.MS')
              ELSE j.updated_at
         END as updated_at,
         ws.id as set_id
       FROM durable.jobs j
       LEFT JOIN durable.jobs_attributes ju
         ON ju.job_id = j.id
         AND ju.symbol IN (SELECT value FROM ju_symbols)
         AND (ju.dimension IS NULL OR ju.dimension = '')
       LEFT JOIN lt_workflow_sets ws
         ON ws.source_workflow_id = REPLACE(j.key, 'hmsh:durable:j:', '')
         AND j.entity = 'mcpWorkflowPlanner'
       WHERE ${where}
       ORDER BY ${buildJobOrderBy(params.sort_by, params.order)}
       LIMIT $${idx++} OFFSET $${idx++}`, [...values, limit, offset]),
    ]);
    const jobs = dataResult.rows.map((row) => ({
        workflow_id: row.key.replace('hmsh:durable:j:', ''),
        entity: row.entity,
        status: (row.status > 0 ? 'running' : row.status === 0 ? 'completed' : 'failed'),
        is_live: row.is_live,
        created_at: row.created_at,
        updated_at: row.updated_at,
        ...(row.set_id ? { set_id: row.set_id } : {}),
    }));
    return { jobs, total: parseInt(countResult.rows[0].count, 10) };
}
