/**
 * Token injection library for subagent templates.
 * Ports lib/skills/token-inject.sh.
 *
 * Implements strict token replacement with validation to prevent hallucination.
 * All tokens use {{TOKEN_NAME}} format.
 *
 * @epic T4454
 * @task T4521
 */
import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import { getProjectRoot } from '../../paths.js';
// ============================================================================
// Required Tokens
// ============================================================================
const REQUIRED_TOKENS = ['TASK_ID', 'DATE', 'TOPIC_SLUG'];
/** Validation patterns for required tokens. */
const TOKEN_PATTERNS = {
    TASK_ID: /^T\d+$/,
    DATE: /^\d{4}-\d{2}-\d{2}$/,
    TOPIC_SLUG: /^[a-zA-Z0-9_-]+$/,
    EPIC_ID: /^T\d+$/,
    PARENT_ID: /^T\d+$/,
    SESSION_ID: /^session[-_]\d{8}[-_]\d{6}[-_][a-f0-9]+$/,
};
// ============================================================================
// Default Values
// ============================================================================
/** CLEO command defaults. */
const CLEO_DEFAULTS = {
    TASK_SHOW_CMD: 'cleo show',
    TASK_START_CMD: 'cleo start',
    TASK_CURRENT_CMD: 'cleo current',
    TASK_COMPLETE_CMD: 'cleo complete',
    TASK_LINK_CMD: 'cleo research link',
    TASK_LIST_CMD: 'cleo list',
    TASK_FIND_CMD: 'cleo find',
    TASK_ADD_CMD: 'cleo add',
    TASK_EXISTS_CMD: 'cleo exists',
    TASK_PHASE_CMD: 'cleo phase show',
    TASK_TREE_CMD: 'cleo list --tree',
    SESSION_LIST_CMD: 'cleo session list',
    SESSION_START_CMD: 'cleo session start',
    SESSION_END_CMD: 'cleo session end',
    SESSION_GC_CMD: 'cleo session gc',
    RESEARCH_LIST_CMD: 'cleo research list',
    RESEARCH_SHOW_CMD: 'cleo research show',
    RESEARCH_PENDING_CMD: 'cleo research pending',
    RESEARCH_INJECT_CMD: 'cleo research inject',
    DASH_CMD: 'cleo dash',
    OUTPUT_DIR: '.cleo/agent-outputs',
    MANIFEST_PATH: '.cleo/agent-outputs/MANIFEST.jsonl',
};
// ============================================================================
// Token Loading
// ============================================================================
/**
 * Load token definitions from placeholders.json.
 * @task T4521
 */
export function loadPlaceholders(cwd) {
    const projectRoot = getProjectRoot(cwd);
    const path = join(projectRoot, 'skills', '_shared', 'placeholders.json');
    if (!existsSync(path))
        return null;
    try {
        return JSON.parse(readFileSync(path, 'utf-8'));
    }
    catch {
        return null;
    }
}
/**
 * Build the full default values map (merging placeholders.json with hardcoded defaults).
 * @task T4521
 */
export function buildDefaults(cwd) {
    const defaults = { ...CLEO_DEFAULTS };
    const config = loadPlaceholders(cwd);
    if (config) {
        // Merge defaults from all sections
        const sections = [
            config.context,
            config.taskCommands?.tokens,
            config.taskContext?.tokens,
            config.skillSpecific?.tokens,
        ];
        for (const section of sections) {
            if (!Array.isArray(section))
                continue;
            for (const def of section) {
                if (def.default !== undefined && def.default !== '') {
                    defaults[def.token] = def.default;
                }
            }
        }
    }
    return defaults;
}
// ============================================================================
// Token Validation
// ============================================================================
/**
 * Validate a single token value against its pattern.
 * @task T4521
 */
export function validateTokenValue(token, value) {
    const pattern = TOKEN_PATTERNS[token];
    if (!pattern)
        return { valid: true }; // No pattern = always valid
    if (!value) {
        return { valid: false, error: `Token ${token} is empty` };
    }
    if (!pattern.test(value)) {
        return {
            valid: false,
            error: `Token ${token} value "${value}" does not match pattern ${pattern}`,
        };
    }
    return { valid: true };
}
/**
 * Validate all required tokens are present and valid.
 * @task T4521
 */
export function validateRequired(values) {
    const missing = [];
    const invalid = [];
    for (const token of REQUIRED_TOKENS) {
        const value = values[token];
        if (!value) {
            missing.push(token);
            continue;
        }
        const result = validateTokenValue(token, value);
        if (!result.valid) {
            invalid.push({ token, error: result.error });
        }
    }
    return {
        valid: missing.length === 0 && invalid.length === 0,
        missing,
        invalid,
    };
}
/**
 * Validate all tokens in a values map (required + optional).
 * @task T4521
 */
export function validateAllTokens(values) {
    const errors = [];
    for (const [token, value] of Object.entries(values)) {
        if (!value)
            continue; // Empty optional tokens are ok
        const result = validateTokenValue(token, value);
        if (!result.valid) {
            errors.push({ token, error: result.error });
        }
    }
    return { valid: errors.length === 0, errors };
}
// ============================================================================
// Token Injection
// ============================================================================
/**
 * Inject token values into a template string.
 * Replaces all {{TOKEN_NAME}} patterns with corresponding values.
 * Unresolved tokens are left as-is (for debugging).
 * @task T4521
 */
export function injectTokens(template, values) {
    // Merge with defaults (explicit values override defaults)
    const merged = { ...CLEO_DEFAULTS, ...values };
    return template.replace(/\{\{(\w+)\}\}/g, (match, token) => {
        const value = merged[token];
        if (value !== undefined && value !== '') {
            return value;
        }
        // Return the token as-is if not resolved (helps debugging)
        return match;
    });
}
/**
 * Check if a template has unresolved tokens after injection.
 * @task T4521
 */
export function hasUnresolvedTokens(content) {
    const matches = content.match(/\{\{(\w+)\}\}/g) ?? [];
    return [...new Set(matches.map((m) => m.replace(/\{\{|\}\}/g, '')))];
}
/**
 * Load a skill template and inject tokens.
 * @task T4521
 */
export function loadAndInject(templatePath, values) {
    if (!existsSync(templatePath)) {
        throw new Error(`Template not found: ${templatePath}`);
    }
    const template = readFileSync(templatePath, 'utf-8');
    const content = injectTokens(template, values);
    const unresolvedTokens = hasUnresolvedTokens(content);
    return { content, unresolvedTokens };
}
// ============================================================================
// Full Context Resolution
// ============================================================================
/**
 * Build a complete TokenValues map from a task, resolving all standard tokens.
 * Ports ti_set_full_context from lib/skills/token-inject.sh.
 *
 * This is the primary entry point for orchestrators to prepare token values
 * before spawning subagents. It populates: TASK_ID, DATE, TOPIC_SLUG, EPIC_ID,
 * TITLE, TASK_TITLE, TASK_DESCRIPTION, TOPICS_JSON, DEPENDS_LIST, RESEARCH_ID,
 * OUTPUT_DIR, MANIFEST_PATH, and all command defaults.
 *
 * @task T4712
 * @epic T4663
 */
export function setFullContext(task, options) {
    const dateToday = options?.date ?? new Date().toISOString().split('T')[0];
    const topicSlug = options?.topicSlug ??
        (task.title
            .replace(/[^a-zA-Z0-9]+/g, '-')
            .toLowerCase()
            .replace(/^-|-$/g, '') ||
            `task-${task.id}`);
    const outputDir = options?.outputDir ?? '.cleo/agent-outputs';
    const manifestPath = options?.manifestPath ?? `${outputDir}/MANIFEST.jsonl`;
    const values = {
        // Required tokens
        TASK_ID: task.id,
        DATE: dateToday,
        TOPIC_SLUG: topicSlug,
        // Context tokens
        EPIC_ID: task.parentId ?? '',
        PARENT_ID: task.parentId ?? '',
        TITLE: task.title,
        TASK_TITLE: task.title,
        TASK_NAME: task.title,
        TASK_DESCRIPTION: task.description ?? '',
        RESEARCH_ID: `${topicSlug}-${dateToday}`,
        // Output tokens
        OUTPUT_DIR: outputDir,
        MANIFEST_PATH: manifestPath,
        MANIFEST_ID: `${topicSlug}-${dateToday}`,
        MANIFEST_FILE: `${dateToday}_${topicSlug}.md`,
        MANIFEST_TITLE: task.title,
    };
    // Task-specific tokens
    if (task.labels?.length) {
        values['TOPICS_JSON'] = JSON.stringify(task.labels);
    }
    if (task.depends?.length) {
        values['DEPENDS_LIST'] = task.depends.join(', ');
    }
    // Merge with CLEO command defaults
    return { ...buildDefaults(), ...values };
}
//# sourceMappingURL=token.js.map