/*
 * Copyright 2026, Jaime Terrats.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { ensureDir, readJsonlFile } from './file-writer.js';
function workflowRunPath(cwd) {
    return join(cwd, '.setup-agents', 'state', 'workflow-runs.jsonl');
}
function estimatesPath(cwd) {
    return join(cwd, '.setup-agents', 'state', 'estimates.jsonl');
}
export function aggregateDashboardData(cwd) {
    const runs = readJsonlFile(workflowRunPath(cwd));
    const estimates = readJsonlFile(estimatesPath(cwd));
    const burndown = computeBurndown(runs);
    const velocity = computeVelocity(runs, estimates);
    const effortMix = computeEffortMix(estimates);
    const phaseDuration = computePhaseDuration(runs);
    const latestRunByStory = new Map();
    for (const run of runs)
        latestRunByStory.set(run.storyId, run);
    const taskCount = latestRunByStory.size;
    return {
        burndown,
        velocity,
        effortMix,
        phaseDuration,
        generatedAt: new Date().toISOString(),
        taskCount,
    };
}
export function renderDashboardHtml(data, options) {
    const title = options?.title ?? 'Delivery Dashboard';
    return `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>${escapeHtml(title)}</title>
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f7fa; padding: 1.5rem; }
    header { text-align: center; margin-bottom: 2rem; }
    header h1 { font-size: 1.75rem; color: #1a1a2e; }
    header p { color: #6b7280; font-size: 0.875rem; margin-top: 0.25rem; }
    .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; max-width: 1200px; margin: 0 auto; }
    .card { background: #fff; border-radius: 0.75rem; padding: 1.25rem; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
    .card h2 { font-size: 1rem; color: #374151; margin-bottom: 0.75rem; }
    canvas { width: 100% !important; height: 280px !important; }
    @media (max-width: 768px) { .grid { grid-template-columns: 1fr; } }
  </style>
</head>
<body>
  <header>
    <h1>${escapeHtml(title)}</h1>
    <p>Generated at ${escapeHtml(data.generatedAt)} &mdash; ${data.taskCount} tasks</p>
  </header>
  <div class="grid">
    <div class="card"><h2>Burndown</h2><canvas id="burndownChart"></canvas></div>
    <div class="card"><h2>Velocity</h2><canvas id="velocityChart"></canvas></div>
    <div class="card"><h2>Effort Mix</h2><canvas id="effortMixChart"></canvas></div>
    <div class="card"><h2>Phase Duration</h2><canvas id="phaseDurationChart"></canvas></div>
  </div>
  <script>const DASHBOARD_DATA = ${JSON.stringify(data)};</script>
  <script>
    function renderCharts() {
      const d = DASHBOARD_DATA;
      new Chart(document.getElementById('burndownChart'), {
        type: 'line',
        data: { labels: d.burndown.map(p => p.label), datasets: [{ label: 'Remaining', data: d.burndown.map(p => p.value), borderColor: '#3b82f6', tension: 0.3, fill: false }] },
        options: { responsive: true, maintainAspectRatio: false }
      });
      new Chart(document.getElementById('velocityChart'), {
        type: 'bar',
        data: { labels: d.velocity.map(p => p.label), datasets: [{ label: 'Points', data: d.velocity.map(p => p.value), backgroundColor: '#10b981' }] },
        options: { responsive: true, maintainAspectRatio: false }
      });
      new Chart(document.getElementById('effortMixChart'), {
        type: 'doughnut',
        data: { labels: d.effortMix.map(p => p.label), datasets: [{ data: d.effortMix.map(p => p.value), backgroundColor: ['#6366f1','#f59e0b','#10b981','#ef4444','#8b5cf6'] }] },
        options: { responsive: true, maintainAspectRatio: false }
      });
      new Chart(document.getElementById('phaseDurationChart'), {
        type: 'bar',
        data: { labels: d.phaseDuration.map(p => p.label), datasets: [{ label: 'Avg minutes', data: d.phaseDuration.map(p => p.value), backgroundColor: '#f97316' }] },
        options: { responsive: true, maintainAspectRatio: false, indexAxis: 'y' }
      });
    }
    renderCharts();
  </script>
</body>
</html>`;
}
export function generateDashboard(cwd, options) {
    const data = aggregateDashboardData(cwd);
    const html = renderDashboardHtml(data, options);
    const outputPath = options?.outputPath ?? join(cwd, '.setup-agents', 'dashboard.html');
    ensureDir(join(outputPath, '..'));
    writeFileSync(outputPath, html, 'utf8');
    return outputPath;
}
function escapeHtml(text) {
    return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
function computeBurndown(runs) {
    const latestByStory = new Map();
    for (const run of runs)
        latestByStory.set(run.storyId, run);
    const uniqueRuns = [...latestByStory.values()];
    const total = uniqueRuns.length;
    if (total === 0)
        return [];
    const completionDates = [];
    for (const run of uniqueRuns) {
        if (run.status === 'done') {
            const date = run.updatedAt.slice(0, 10);
            completionDates.push(date);
        }
    }
    completionDates.sort();
    const points = [];
    let remaining = total;
    const countByDate = new Map();
    for (const d of completionDates)
        countByDate.set(d, (countByDate.get(d) ?? 0) + 1);
    for (const [date, count] of [...countByDate.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
        remaining -= count;
        points.push({ label: date, value: remaining });
    }
    if (points.length > 0) {
        points.unshift({ label: 'start', value: total });
    }
    return points;
}
function computeVelocity(runs, estimates) {
    const latestByStory = new Map();
    for (const run of runs)
        latestByStory.set(run.storyId, run);
    const estimateByStory = new Map();
    for (const est of estimates)
        estimateByStory.set(est.storyId, est);
    const pointsByWeek = new Map();
    for (const [storyId, run] of latestByStory) {
        if (run.status !== 'done')
            continue;
        const est = estimateByStory.get(storyId);
        const points = est?.soloEstimateDays ?? 1;
        const week = isoWeek(run.updatedAt);
        pointsByWeek.set(week, (pointsByWeek.get(week) ?? 0) + points);
    }
    return [...pointsByWeek.entries()]
        .sort((a, b) => a[0].localeCompare(b[0]))
        .map(([week, value]) => ({ label: week, value }));
}
function computeEffortMix(estimates) {
    const latestByStory = new Map();
    for (const est of estimates)
        latestByStory.set(est.storyId, est);
    const counts = { xs: 0, s: 0, m: 0, l: 0, xl: 0 };
    for (const est of latestByStory.values()) {
        if (est.sizingLabel in counts) {
            counts[est.sizingLabel]++;
        }
    }
    return Object.entries(counts)
        .filter(([, v]) => v > 0)
        .map(([label, value]) => ({ label, value }));
}
function computePhaseDuration(runs) {
    const durationsByPhase = new Map();
    for (const run of runs) {
        for (const phase of run.phases) {
            if (phase.startedAt && phase.completedAt) {
                const durationMs = new Date(phase.completedAt).getTime() - new Date(phase.startedAt).getTime();
                const durationMin = durationMs / (1000 * 60);
                if (durationMin >= 0) {
                    const existing = durationsByPhase.get(phase.phase) ?? [];
                    existing.push(durationMin);
                    durationsByPhase.set(phase.phase, existing);
                }
            }
        }
    }
    return [...durationsByPhase.entries()]
        .map(([phase, durations]) => ({
        label: phase,
        value: Math.round(durations.reduce((a, b) => a + b, 0) / durations.length),
    }))
        .sort((a, b) => b.value - a.value);
}
function isoWeek(isoDate) {
    const date = new Date(isoDate);
    const dayOfYear = Math.floor((date.getTime() - new Date(date.getFullYear(), 0, 1).getTime()) / 86_400_000) + 1;
    const dayOfWeek = date.getDay() || 7;
    const weekNumber = Math.ceil((dayOfYear - dayOfWeek + 10) / 7);
    return `${date.getFullYear()}-W${String(weekNumber).padStart(2, '0')}`;
}
//# sourceMappingURL=dashboard-generator.js.map