import { createConversation, getOrCreateConversation, getRecentMessages, insertMessage, } from './conversation.js';
import { logActivityStart, logActivityComplete, logActivityFail, getDailySummary, formatDailySummaryForPrompt, } from '../services/activity-service.js';
import { createPipelineTasksFromYaml, markAllTasksDone, markAllTasksFailed, markTaskExecutingByPosition, } from '../services/pipeline-task-service.js';
import { parse as parseYaml } from 'yaml';
import fs from 'node:fs';
import path from 'node:path';
function getSquadModel(squadsDir, squadName) {
    if (squadName === '_jarvis' || squadName === '_expxagents')
        return undefined;
    const yamlPath = path.join(squadsDir, squadName, 'squad.yaml');
    if (!fs.existsSync(yamlPath))
        return undefined;
    try {
        const raw = fs.readFileSync(yamlPath, 'utf-8');
        const parsed = parseYaml(raw);
        return parsed?.squad?.model;
    }
    catch {
        return undefined;
    }
}
export class ChatHandler {
    db;
    bridge;
    broadcast;
    broadcastToAll;
    squadsDir;
    constructor(config) {
        this.db = config.db;
        this.bridge = config.bridge;
        this.broadcast = config.broadcast;
        this.broadcastToAll = config.broadcastToAll;
        this.squadsDir = config.squadsDir;
    }
    handleChatSend(squadName, message, files) {
        const { conversation, created } = getOrCreateConversation(this.db, squadName);
        if (created) {
            this.broadcast(squadName, {
                type: 'chat:conversation_created',
                squadName,
                conversationId: conversation.id,
            });
        }
        insertMessage(this.db, {
            conversationId: conversation.id,
            role: 'user',
            content: message,
        });
        const prompt = this.buildPrompt(squadName, conversation.id, message, files);
        console.log(`[chat] squad="${squadName}" prompt=${prompt.length} chars cwd will be projectRoot or squadsDir`);
        // Log activity start
        const activity = logActivityStart(this.db, {
            squadCode: squadName,
            conversationId: conversation.id,
            userMessage: message,
        });
        // Create pipeline tasks from squad.yaml — mark first step as executing
        const pipelineTasks = createPipelineTasksFromYaml(this.db, this.squadsDir, squadName, activity.id, 'chat');
        if (pipelineTasks.length > 0) {
            markTaskExecutingByPosition(this.db, { activityId: activity.id, position: 0 });
            this.broadcastToAll({ type: 'kanban:task_updated', activityId: activity.id, squadName });
        }
        const projectRoot = path.resolve(this.squadsDir, '..');
        let cwd;
        if (squadName === '_jarvis' || squadName === '_expxagents') {
            cwd = projectRoot;
        }
        else {
            cwd = path.join(this.squadsDir, squadName);
        }
        const model = getSquadModel(this.squadsDir, squadName);
        this.bridge.send({
            prompt,
            cwd,
            squadName,
            model,
            onActivity: (tool, summary) => {
                this.broadcast(squadName, {
                    type: 'chat:activity',
                    squadName,
                    conversationId: conversation.id,
                    tool,
                    summary,
                });
            },
            onOutputChunk: (agentId, agentName, delta) => {
                this.broadcast(squadName, {
                    type: 'agent:output_chunk',
                    squadName,
                    agentId,
                    agentName,
                    delta,
                });
            },
            onChunk: (text) => {
                this.broadcast(squadName, {
                    type: 'chat:chunk',
                    squadName,
                    conversationId: conversation.id,
                    delta: text,
                });
            },
            onDone: (fullText) => {
                // If Claude returned nothing, surface as error instead of blank message
                if (!fullText.trim()) {
                    console.log(`[chat] WARNING: empty response from Claude for squad="${squadName}"`);
                    logActivityFail(this.db, activity.id, 'Empty response from Claude');
                    if (pipelineTasks.length > 0) {
                        markAllTasksFailed(this.db, { activityId: activity.id, error: 'Empty response' });
                        this.broadcastToAll({ type: 'kanban:task_updated', activityId: activity.id, status: 'failed' });
                    }
                    this.broadcast(squadName, {
                        type: 'chat:error',
                        squadName,
                        error: 'Claude returned an empty response. Check server logs for details.',
                    });
                    return;
                }
                const assistantMsg = insertMessage(this.db, {
                    conversationId: conversation.id,
                    role: 'assistant',
                    content: fullText,
                });
                // Log activity complete with first 500 chars as summary
                logActivityComplete(this.db, activity.id, fullText.slice(0, 500));
                // Mark pipeline tasks done with output summary
                if (pipelineTasks.length > 0) {
                    markAllTasksDone(this.db, { activityId: activity.id, result: fullText.slice(0, 2000) });
                    this.broadcastToAll({ type: 'kanban:task_updated', activityId: activity.id, status: 'done' });
                }
                this.broadcast(squadName, {
                    type: 'chat:done',
                    squadName,
                    conversationId: conversation.id,
                    messageId: assistantMsg.id,
                });
            },
            onError: (error) => {
                // Log activity failure
                logActivityFail(this.db, activity.id, error.message);
                // Mark pipeline tasks failed
                if (pipelineTasks.length > 0) {
                    markAllTasksFailed(this.db, { activityId: activity.id, error: error.message });
                    this.broadcastToAll({ type: 'kanban:task_updated', activityId: activity.id, status: 'failed' });
                }
                this.broadcast(squadName, {
                    type: 'chat:error',
                    squadName,
                    error: error.message,
                });
            },
        });
    }
    handleNewConversation(squadName) {
        const conversation = createConversation(this.db, squadName);
        this.broadcast(squadName, {
            type: 'chat:conversation_created',
            squadName,
            conversationId: conversation.id,
        });
    }
    stop(squadName) {
        this.bridge.kill(squadName);
    }
    /** Read available squads from squadsDir for context injection */
    getSquadsContext() {
        try {
            if (!fs.existsSync(this.squadsDir))
                return '';
            const entries = fs.readdirSync(this.squadsDir, { withFileTypes: true });
            const lines = [];
            for (const entry of entries) {
                if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name.startsWith('_'))
                    continue;
                const yamlPath = path.join(this.squadsDir, entry.name, 'squad.yaml');
                if (!fs.existsSync(yamlPath))
                    continue;
                try {
                    const raw = fs.readFileSync(yamlPath, 'utf-8');
                    const parsed = parseYaml(raw);
                    const s = parsed?.squad;
                    if (s) {
                        const agents = Array.isArray(s.agents)
                            ? s.agents.map((a) => a.name || a.id || a).join(', ')
                            : '';
                        lines.push(`- **${s.name || entry.name}** (${s.code || entry.name}): ${s.description || 'Sem descrição'}${agents ? ` | Agentes: ${agents}` : ''}`);
                    }
                }
                catch { /* skip malformed yaml */ }
            }
            return lines.length > 0
                ? '\n\n# Squads disponíveis:\n' + lines.join('\n')
                : '';
        }
        catch {
            return '';
        }
    }
    /** Read ExpxAgents skill content from .claude/skills/expxagents/SKILL.md */
    getExpxAgentsSkillContent() {
        try {
            const projectRoot = path.resolve(this.squadsDir, '..');
            const skillPath = path.join(projectRoot, '.claude', 'skills', 'expxagents', 'SKILL.md');
            if (!fs.existsSync(skillPath))
                return '';
            let content = fs.readFileSync(skillPath, 'utf-8');
            // Strip YAML frontmatter
            const fmMatch = content.match(/^---\n[\s\S]*?\n---\n/);
            if (fmMatch) {
                content = content.slice(fmMatch[0].length);
            }
            return content.trim();
        }
        catch {
            return '';
        }
    }
    /** Read company context from _expxagents/_memory/company.md if configured */
    getCompanyContext() {
        try {
            const projectRoot = path.resolve(this.squadsDir, '..');
            const companyPath = path.join(projectRoot, '_expxagents', '_memory', 'company.md');
            if (!fs.existsSync(companyPath))
                return '';
            const content = fs.readFileSync(companyPath, 'utf-8');
            if (content.includes('NOT CONFIGURED'))
                return '';
            return '\n\n# Contexto da empresa:\n' + content;
        }
        catch {
            return '';
        }
    }
    buildPrompt(squadName, conversationId, userMessage, files) {
        // _expxagents channel: always include full context + conversation history
        // Each claude -p invocation is independent, so we must provide all context every time
        // IMPORTANT: Do NOT use raw SKILL.md here — it instructs Claude to read files and use
        // interactive tools (AskUserQuestion) which don't work in -p mode. Instead, provide
        // a chat-optimized system prompt with pre-injected context.
        if (squadName === '_expxagents') {
            const parts = [];
            parts.push('You are ExpxAgents, an AI assistant for managing AI agent squads. ' +
                'You help users create, manage, and run squads. Respond concisely in Portuguese.\n' +
                'IMPORTANT: Do NOT use tools to read files — all relevant context is provided below. ' +
                'Just respond directly with text to the user\'s message.');
            parts.push(this.getCompanyContext());
            parts.push(this.getSquadsContext());
            // Include conversation history so Claude has continuity across -p invocations
            const history = getRecentMessages(this.db, conversationId, 10);
            // Exclude: current user message (just inserted) and empty assistant responses
            const pastMessages = history.filter((msg) => {
                // Skip empty/whitespace-only messages
                if (!msg.content.trim())
                    return false;
                // Skip the current user message (last in array)
                if (msg.role === 'user' && msg.content === userMessage && msg === history[history.length - 1])
                    return false;
                return true;
            });
            if (pastMessages.length > 0) {
                const historyText = pastMessages
                    .map((msg) => {
                    if (msg.role === 'user')
                        return `User: ${msg.content}`;
                    if (msg.role === 'assistant')
                        return `Assistant: ${msg.content}`;
                    return '';
                })
                    .filter(Boolean)
                    .join('\n');
                parts.push(`\n\nConversation so far:\n${historyText}`);
            }
            if (files && files.length > 0) {
                parts.push('\n\n# Attached files:\n');
                for (const f of files) {
                    parts.push(`\n## File: ${f.name}\n\`\`\`\n${f.content}\n\`\`\`\n`);
                }
            }
            parts.push(`\n\nUser: ${userMessage}`);
            return parts.join('');
        }
        // Other channels: include history since they don't use --continue
        const history = getRecentMessages(this.db, conversationId, 10);
        const historyText = history
            .map((msg) => {
            if (msg.role === 'user')
                return `User: ${msg.content}`;
            if (msg.role === 'assistant')
                return `Assistant: ${msg.content}`;
            if (msg.role === 'agent_dm')
                return `[Agent ${msg.agent_name}]: ${msg.content}`;
            return '';
        })
            .filter(Boolean)
            .join('\n');
        const parts = [];
        if (squadName === '_jarvis') {
            parts.push('You are Jarvis, the AI assistant for ExpxAgents. You help the user manage their AI squads, ' +
                'answer questions about the platform, and execute commands. Respond concisely in Portuguese.\n\n' +
                'When the user asks a question that relates to a specific squad\'s domain (e.g., financial data, marketing, etc.), ' +
                'inform them which squad can help and offer to route the question. ' +
                'If the user asks to run or manage a squad, acknowledge the request and explain what will happen.');
            // Inject company context
            parts.push(this.getCompanyContext());
            // Inject available squads
            parts.push(this.getSquadsContext());
            // Include today's activity summary so Jarvis knows what squads have been working on
            const today = new Date().toISOString().slice(0, 10);
            const summaries = getDailySummary(this.db, today);
            if (summaries.length > 0) {
                parts.push('\n\n# Atividades de hoje (' + today + '):\n' +
                    formatDailySummaryForPrompt(summaries));
            }
        }
        else {
            parts.push(`You are assisting with squad '${squadName}'.`);
        }
        if (historyText) {
            parts.push(`\nConversation:\n${historyText}`);
        }
        if (files && files.length > 0) {
            parts.push('\n\n# Attached files:\n');
            for (const f of files) {
                parts.push(`\n## File: ${f.name}\n\`\`\`\n${f.content}\n\`\`\`\n`);
            }
        }
        parts.push(`\nUser: ${userMessage}`);
        return parts.join('');
    }
}
//# sourceMappingURL=chat-handler.js.map