import { getEntries, getAggregation } from '../storage/token-ledger-store.js';
import { hashProjectPath } from '../storage/base-store.js';
import { buildTokenWasteReport, detectTokenWasteLoops, formatTokenWasteReport, loadTokenWastePolicy, recommendRelevantTools, } from '../engine/token-optimizer/index.js';
import { join } from 'node:path';
import { formatProjectGraphContext, queryProjectGraphSlice, } from '../engine/project-graph/index.js';
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
const DEFAULT_DATA_DIR = join(process.cwd(), 'data');
const DEFAULT_VIEW = 'summary';
const TOP_N_CONSUMERS = 5;
const PROGRESS_BAR_LENGTH = 10;
// ---------------------------------------------------------------------------
// Formatting helpers
// ---------------------------------------------------------------------------
function formatTokens(n) {
    if (n >= 1_000_000) {
        return `${(n / 1_000_000).toFixed(1)}M`;
    }
    if (n >= 1_000) {
        return `${(n / 1_000).toFixed(1)}K`;
    }
    return String(n);
}
function formatCost(usd) {
    return `$${usd.toFixed(4)}`;
}
function formatPercent(n) {
    return `${n.toFixed(1)}%`;
}
function progressBar(percent, length) {
    const filled = Math.round((Math.min(percent, 100) / 100) * length);
    return '█'.repeat(filled) + '░'.repeat(length - filled);
}
function periodLabel(period) {
    const labels = {
        today: 'Today',
        week: 'Last 7 Days',
        month: 'Current Month',
        all_time: 'All Time',
    };
    return labels[period] ?? period;
}
// ---------------------------------------------------------------------------
// Analytics computed locally (analytics.ts built by parallel agent)
// These are minimal self-contained implementations so the handler compiles
// and ships. When analytics.ts is available it can replace these.
// ---------------------------------------------------------------------------
function computeDailyBreakdown(entries) {
    const map = new Map();
    for (const e of entries) {
        const date = e.timestamp.slice(0, 10);
        const existing = map.get(date) ?? { date, tokens: 0, costUsd: 0, calls: 0 };
        existing.tokens += e.totalTokens;
        existing.costUsd += e.costUsd;
        existing.calls += 1;
        map.set(date, existing);
    }
    return [...map.values()].sort((a, b) => a.date.localeCompare(b.date));
}
function detectTrend(daily) {
    if (daily.length < 4) {
        return { direction: 'stable', changePercent: 0 };
    }
    const half = Math.floor(daily.length / 2);
    const recent = daily.slice(half);
    const earlier = daily.slice(0, half);
    const avgRecent = recent.reduce((s, d) => s + d.tokens, 0) / recent.length;
    const avgEarlier = earlier.reduce((s, d) => s + d.tokens, 0) / earlier.length;
    if (avgEarlier === 0) {
        return { direction: 'stable', changePercent: 0 };
    }
    const changePercent = ((avgRecent - avgEarlier) / avgEarlier) * 100;
    const direction = changePercent > 10 ? 'increasing' : changePercent < -10 ? 'decreasing' : 'stable';
    return { direction, changePercent };
}
function detectAnomalies(daily) {
    if (daily.length < 3) {
        return [];
    }
    const avg = daily.reduce((s, d) => s + d.tokens, 0) / daily.length;
    const anomalies = [];
    for (const day of daily) {
        const ratio = avg > 0 ? day.tokens / avg : 0;
        if (ratio >= 3) {
            anomalies.push({
                date: day.date,
                severity: 'critical',
                description: `Token spike (${ratio.toFixed(1)}x average) — ${formatTokens(day.tokens)} tokens`,
                tokens: day.tokens,
            });
        }
        else if (ratio >= 2) {
            anomalies.push({
                date: day.date,
                severity: 'warning',
                description: `Elevated usage (${ratio.toFixed(1)}x average) — ${formatTokens(day.tokens)} tokens`,
                tokens: day.tokens,
            });
        }
    }
    return anomalies.sort((a, b) => b.tokens - a.tokens).slice(0, 5);
}
function computeBudgetStatus(agg) {
    const now = new Date();
    const daysInMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();
    const daysElapsed = now.getDate();
    const dailyAvgUsd = daysElapsed > 0 ? agg.totalCostUsd / daysElapsed : 0;
    const projectedMonthlyUsd = dailyAvgUsd * daysInMonth;
    return {
        monthlyLimitUsd: null,
        spentUsd: agg.totalCostUsd,
        remainingUsd: null,
        percentUsed: null,
        projectedMonthlyUsd,
        projectedExhaustionDay: null,
        isWarning: false,
        isCritical: false,
        isOverBudget: false,
        daysInMonth,
        daysElapsed,
        dailyAvgUsd,
    };
}
function getTopConsumers(tokensByKey, costByKey, totalTokens, topN) {
    return Object.entries(tokensByKey)
        .map(([name, tokens]) => ({
        name,
        tokens,
        costUsd: costByKey[name] ?? 0,
        percentOfTotal: totalTokens > 0 ? (tokens / totalTokens) * 100 : 0,
    }))
        .sort((a, b) => b.tokens - a.tokens)
        .slice(0, topN)
        .map((item, idx) => ({ rank: idx + 1, ...item }));
}
// ---------------------------------------------------------------------------
// View renderers
// ---------------------------------------------------------------------------
function renderSummary(agg, entries, period, groupBy) {
    const daily = computeDailyBreakdown(entries);
    const trend = detectTrend(daily);
    const anomalies = detectAnomalies(daily);
    const keyField = groupBy === 'model' ? 'model' : groupBy === 'spec' ? 'spec' : 'tool';
    const tokensByKey = keyField === 'model' ? agg.tokensByModel : agg.tokensByTool;
    const costByKey = keyField === 'model' ? agg.costByModel : agg.costByTool;
    const topConsumers = getTopConsumers(tokensByKey, costByKey, agg.totalTokens, TOP_N_CONSUMERS);
    const trendIcon = trend.direction === 'increasing' ? '↗️' : trend.direction === 'decreasing' ? '↘️' : '→';
    const trendText = trend.direction === 'stable'
        ? 'Usage is **stable**'
        : `Usage is **${trend.direction} ${Math.abs(trend.changePercent).toFixed(0)}%** week-over-week`;
    const lines = [
        '# Token Intelligence Dashboard',
        '',
        `## Summary (${periodLabel(period)})`,
        '| Metric | Value |',
        '|--------|-------|',
        `| Total Tokens | ${formatTokens(agg.totalTokens)} |`,
        `| Total Cost | ${formatCost(agg.totalCostUsd)} |`,
        `| Calls | ${String(agg.entryCount)} |`,
        `| Avg Cost/Call | ${formatCost(agg.avgCostPerCall)} |`,
        `| Avg Tokens/Call | ${formatTokens(Math.round(agg.avgTokensPerCall))} |`,
        `| Unique Tools | ${String(agg.uniqueTools)} |`,
        `| Unique Models | ${String(agg.uniqueModels)} |`,
        `| Estimation Accuracy | ${formatPercent(agg.estimationAccuracy)} |`,
        '',
    ];
    if (topConsumers.length > 0) {
        lines.push(`## Top ${String(Math.min(TOP_N_CONSUMERS, topConsumers.length))} Consumers (by ${keyField})`, `| # | ${keyField.charAt(0).toUpperCase() + keyField.slice(1)} | Tokens | Cost | % Total |`, `|---|${'-'.repeat(keyField.length + 2)}|--------|------|---------|`, ...topConsumers.map((c) => `| ${String(c.rank)} | ${c.name} | ${formatTokens(c.tokens)} | ${formatCost(c.costUsd)} | ${formatPercent(c.percentOfTotal)} |`), '');
    }
    lines.push('## Trend', `${trendIcon} ${trendText}`, '');
    if (anomalies.length > 0) {
        lines.push('## Anomalies');
        for (const a of anomalies) {
            const icon = a.severity === 'critical' ? '🔴' : '🟡';
            lines.push(`- ${icon} **${a.date}**: ${a.description}`);
        }
        lines.push('');
    }
    return lines.join('\n');
}
function renderDetailed(agg, period, groupBy) {
    const lines = [
        '# Token Intelligence Dashboard — Detailed View',
        '',
        `**Period:** ${periodLabel(period)} | **Group By:** ${groupBy}`,
        '',
        '## By Tool',
        '| Tool | Tokens | Cost | % Total |',
        '|------|--------|------|---------|',
        ...Object.entries(agg.tokensByTool)
            .sort(([, a], [, b]) => b - a)
            .map(([tool, tokens]) => {
            const cost = agg.costByTool[tool] ?? 0;
            const pct = agg.totalTokens > 0 ? (tokens / agg.totalTokens) * 100 : 0;
            return `| ${tool} | ${formatTokens(tokens)} | ${formatCost(cost)} | ${formatPercent(pct)} |`;
        }),
        '',
        '## By Model',
        '| Model | Tokens | Cost | % Total |',
        '|-------|--------|------|---------|',
        ...Object.entries(agg.tokensByModel)
            .sort(([, a], [, b]) => b - a)
            .map(([model, tokens]) => {
            const cost = agg.costByModel[model] ?? 0;
            const pct = agg.totalTokens > 0 ? (tokens / agg.totalTokens) * 100 : 0;
            return `| ${model} | ${formatTokens(tokens)} | ${formatCost(cost)} | ${formatPercent(pct)} |`;
        }),
        '',
        '## Aggregates',
        `- Total Input Tokens: ${formatTokens(agg.totalInputTokens)}`,
        `- Total Output Tokens: ${formatTokens(agg.totalOutputTokens)}`,
        `- Cache Efficiency: tokens in/out ratio ${agg.totalInputTokens > 0 ? (agg.totalOutputTokens / agg.totalInputTokens).toFixed(2) : '—'}`,
        `- Sessions: ${String(agg.uniqueSessions)}`,
        '',
    ];
    return lines.join('\n');
}
function renderBudget(agg, period) {
    const budget = computeBudgetStatus(agg);
    const bar = budget.monthlyLimitUsd !== null && budget.percentUsed !== null
        ? progressBar(budget.percentUsed, PROGRESS_BAR_LENGTH)
        : progressBar(0, PROGRESS_BAR_LENGTH);
    const statusIcon = budget.isOverBudget
        ? '🔴 Over budget'
        : budget.isCritical
            ? '🔴 Critical'
            : budget.isWarning
                ? '🟡 Warning'
                : '✅ On track';
    const lines = [
        '# Token Intelligence Dashboard — Budget View',
        '',
        `**Period:** ${periodLabel(period)}`,
        '',
        '## Budget Status',
        '| | |',
        '|---|---|',
        `| Monthly Limit | ${budget.monthlyLimitUsd !== null ? formatCost(budget.monthlyLimitUsd) : 'Not configured'} |`,
        `| Spent (this period) | ${formatCost(budget.spentUsd)} |`,
        `| Remaining | ${budget.remainingUsd !== null ? formatCost(budget.remainingUsd) : '—'} |`,
        `| Daily Average | ${formatCost(budget.dailyAvgUsd)} |`,
        `| Projected Monthly | ${formatCost(budget.projectedMonthlyUsd)} |`,
        `| Days Elapsed | ${String(budget.daysElapsed)} / ${String(budget.daysInMonth)} |`,
        `| Progress | ${bar} |`,
        `| Status | ${statusIcon} |`,
        '',
        '## Cost Breakdown',
        '| Tool | Cost | % of Spend |',
        '|------|------|------------|',
        ...Object.entries(agg.costByTool)
            .sort(([, a], [, b]) => b - a)
            .map(([tool, cost]) => {
            const pct = agg.totalCostUsd > 0 ? (cost / agg.totalCostUsd) * 100 : 0;
            return `| ${tool} | ${formatCost(cost)} | ${formatPercent(pct)} |`;
        }),
        '',
        '> Configure a budget limit via `PLANU_MONTHLY_BUDGET_USD` environment variable.',
        '',
    ];
    // Apply budget limit from env if configured
    const envBudget = process.env.PLANU_MONTHLY_BUDGET_USD;
    if (envBudget !== undefined && envBudget !== '') {
        const limit = Number(envBudget);
        if (Number.isFinite(limit) && limit > 0) {
            const pct = (budget.spentUsd / limit) * 100;
            const barWithLimit = progressBar(pct, PROGRESS_BAR_LENGTH);
            lines.splice(lines.indexOf('| Monthly Limit | Not configured |'), 1, `| Monthly Limit | ${formatCost(limit)} |`);
            lines.splice(lines.indexOf(`| Progress | ${bar} |`), 1, `| Progress | ${barWithLimit} ${formatPercent(pct)} |`);
        }
    }
    return lines.join('\n');
}
function renderReconciliation(entries, specId) {
    const withEstimates = entries.filter((e) => e.estimatedTokens !== undefined && e.estimatedCostUsd !== undefined);
    if (withEstimates.length === 0) {
        const filter = specId ? ` for spec ${specId}` : '';
        return [
            '# Token Intelligence Dashboard — Reconciliation View',
            '',
            `No estimation data found${filter}.`,
            '',
            'Reconciliation compares pre-implementation estimates (from `estimate_ai_cost`) with actual usage recorded in the ledger.',
            'Run `estimate_ai_cost` before implementing specs to enable this view.',
            '',
        ].join('\n');
    }
    // Group by spec
    const bySpec = new Map();
    for (const e of withEstimates) {
        const key = e.specId ?? 'unknown';
        const existing = bySpec.get(key) ?? [];
        existing.push(e);
        bySpec.set(key, existing);
    }
    const lines = [
        '# Token Intelligence Dashboard — Reconciliation View',
        '',
        '## Estimation Accuracy',
        '| Spec | Est. Tokens | Act. Tokens | Accuracy | Grade |',
        '|------|-------------|-------------|----------|-------|',
    ];
    for (const [spec, specEntries] of bySpec) {
        const actualTokens = specEntries.reduce((s, e) => s + e.totalTokens, 0);
        const estimatedTokens = specEntries.reduce((s, e) => s + (e.estimatedTokens ?? 0), 0);
        const accuracy = actualTokens > 0
            ? Math.max(0, 100 - (Math.abs(estimatedTokens - actualTokens) / actualTokens) * 100)
            : 100;
        const grade = accuracy >= 90
            ? '✅ Excellent'
            : accuracy >= 75
                ? '✅ Good'
                : accuracy >= 50
                    ? '⚠️ Fair'
                    : '❌ Poor';
        lines.push(`| ${spec} | ${formatTokens(estimatedTokens)} | ${formatTokens(actualTokens)} | ${formatPercent(accuracy)} | ${grade} |`);
    }
    lines.push('');
    return lines.join('\n');
}
function renderTrends(entries, period) {
    const daily = computeDailyBreakdown(entries);
    const trend = detectTrend(daily);
    const anomalies = detectAnomalies(daily);
    const trendIcon = trend.direction === 'increasing' ? '↗️' : trend.direction === 'decreasing' ? '↘️' : '→';
    const lines = [
        '# Token Intelligence Dashboard — Trends View',
        '',
        `**Period:** ${periodLabel(period)}`,
        '',
        '## Usage Trend',
        `${trendIcon} ${trend.direction === 'stable'
            ? 'Usage is **stable** over this period.'
            : `Usage is **${trend.direction} ${Math.abs(trend.changePercent).toFixed(0)}%** compared to earlier in the period.`}`,
        '',
    ];
    if (daily.length > 0) {
        lines.push('## Daily Breakdown', '| Date | Tokens | Cost | Calls |', '|------|--------|------|-------|', ...daily.map((d) => `| ${d.date} | ${formatTokens(d.tokens)} | ${formatCost(d.costUsd)} | ${String(d.calls)} |`), '');
    }
    if (anomalies.length > 0) {
        lines.push('## Anomalies');
        for (const a of anomalies) {
            const icon = a.severity === 'critical' ? '🔴' : '🟡';
            lines.push(`- ${icon} **${a.date}**: ${a.description}`);
        }
        lines.push('');
    }
    else {
        lines.push('## Anomalies', 'No anomalies detected in this period.', '');
    }
    return lines.join('\n');
}
async function renderAutopilot(entries, agg, period, projectPath) {
    const { policy } = await loadTokenWastePolicy(projectPath);
    if (!policy.enabled) {
        return [
            '# Token Intelligence Dashboard — Autopilot View',
            '',
            `**Period:** ${periodLabel(period)}`,
            '',
            'Token Waste Autopilot is disabled by policy.',
            '',
        ].join('\n');
    }
    const loops = detectTokenWasteLoops(entries.map((entry) => ({
        type: 'tool',
        key: entry.toolName,
        timestamp: entry.timestamp,
        changedSincePrevious: false,
    })), policy, 'token_intelligence');
    const tools = recommendRelevantTools({
        action: 'token_intelligence',
        intentSignals: ['token', 'status', 'budget'],
        tools: Object.keys(agg.tokensByTool).map((name) => ({ name })),
        policy,
    });
    const report = buildTokenWasteReport({
        action: 'token_intelligence',
        policy,
        loops,
        tools,
    });
    return [
        '# Token Intelligence Dashboard — Autopilot View',
        '',
        `**Period:** ${periodLabel(period)}`,
        '',
        `Analyzed ${String(entries.length)} ledger entries across ${String(agg.uniqueTools)} tools.`,
        '',
        formatTokenWasteReport(report),
        '',
    ].join('\n');
}
async function renderGraphView(projectPath, specId) {
    const slice = await queryProjectGraphSlice({ projectPath, specId }).catch(() => null);
    if (slice === null) {
        return [
            '# Token Intelligence Dashboard — Graph View',
            '',
            'Project graph is unavailable; existing token intelligence views still work.',
            '',
        ].join('\n');
    }
    return [
        '# Token Intelligence Dashboard — Graph View',
        '',
        `Graph freshness: ${slice.freshness.stale ? slice.freshness.reason : 'fresh'}`,
        `Compact slice: ${String(slice.nodes.length)} nodes / ${String(slice.edges.length)} edges`,
        `Estimated raw context avoided: ${String(slice.tokenSavings.estimatedRawContextAvoided)} graph items`,
        '',
        await formatProjectGraphContext(slice),
    ].join('\n');
}
// ---------------------------------------------------------------------------
// Empty state
// ---------------------------------------------------------------------------
function renderEmpty(view, period) {
    return [
        '# Token Intelligence Dashboard',
        '',
        `**View:** ${view} | **Period:** ${periodLabel(period)}`,
        '',
        'No token usage data found for this period.',
        '',
        'Token data is recorded automatically when AI tools execute within a Planu session.',
        'Switch to `all_time` period to see all historical data.',
        '',
    ].join('\n');
}
// ---------------------------------------------------------------------------
// Main handler
// ---------------------------------------------------------------------------
/**
 * Handle the token_intelligence tool call.
 * Loads ledger data from storage and renders a rich markdown dashboard.
 */
export async function handleTokenIntelligence(input) {
    const period = (input.period ?? 'month');
    const groupBy = input.groupBy ?? 'tool';
    const view = input.view ?? DEFAULT_VIEW;
    const specId = input.specId;
    const projectPath = input.projectPath ?? process.cwd();
    const dataDir = DEFAULT_DATA_DIR;
    const projectHash = hashProjectPath(projectPath);
    const filter = {
        period,
        ...(specId !== undefined ? { specId } : {}),
    };
    const [entries, agg] = await Promise.all([
        getEntries(projectHash, dataDir, filter),
        getAggregation(projectHash, dataDir, filter),
    ]);
    if (entries.length === 0 &&
        view !== 'reconciliation' &&
        view !== 'autopilot' &&
        view !== 'graph') {
        return { content: [{ type: 'text', text: renderEmpty(view, period) }] };
    }
    let text;
    switch (view) {
        case 'detailed':
            text = renderDetailed(agg, period, groupBy);
            break;
        case 'budget':
            text = renderBudget(agg, period);
            break;
        case 'reconciliation':
            text = renderReconciliation(entries, specId);
            break;
        case 'trends':
            text = renderTrends(entries, period);
            break;
        case 'autopilot':
            text = await renderAutopilot(entries, agg, period, projectPath);
            break;
        case 'graph':
            text = await renderGraphView(projectPath, specId);
            break;
        default:
            text = renderSummary(agg, entries, period, groupBy);
            break;
    }
    return { content: [{ type: 'text', text }] };
}
//# sourceMappingURL=token-intelligence-handler.js.map