/*
 * 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 { execFile as execFileCb } from 'node:child_process';
import { existsSync, readFileSync, readdirSync } from 'node:fs';
import { join } from 'node:path';
import { promisify } from 'node:util';
const execFile = promisify(execFileCb);
const VALID_TASK_ID = /^[A-Za-z][\w-]*$/;
const MAX_COMMITS = 50;
function isValidTaskId(id) {
    return VALID_TASK_ID.test(id) && id.length <= 64;
}
function loadOrchestraTasks(cwd) {
    const tasksPath = join(cwd, '.agent-workflow', 'tasks.json');
    if (!existsSync(tasksPath))
        return [];
    try {
        const raw = readFileSync(tasksPath, 'utf-8');
        return JSON.parse(raw);
    }
    catch {
        return [];
    }
}
function findTask(cwd, taskId) {
    return loadOrchestraTasks(cwd).find((t) => t.id === taskId);
}
async function getCommitsForTask(cwd, taskId) {
    try {
        const { stdout } = await execFile('git', ['log', '--all', `--max-count=${MAX_COMMITS}`, '--format=%H|%aI|%s', '--grep', taskId, '--'], { cwd, maxBuffer: 5 * 1024 * 1024 });
        if (!stdout.trim())
            return [];
        const lines = stdout.trim().split('\n');
        const parsed = lines
            .map((line) => {
            const parts = line.split('|');
            if (parts.length < 3)
                return null;
            return { hash: parts[0], date: parts[1], subject: parts.slice(2).join('|') };
        })
            .filter((p) => p !== null);
        const fileResults = await Promise.all(parsed.map((p) => getCommitFiles(cwd, p.hash)));
        return parsed.map((p, i) => ({
            hash: p.hash,
            date: p.date,
            subject: p.subject,
            files: fileResults[i],
        }));
    }
    catch {
        return [];
    }
}
async function getCommitFiles(cwd, hash) {
    try {
        const { stdout } = await execFile('git', ['diff-tree', '--no-commit-id', '-r', '--name-status', hash], {
            cwd,
            maxBuffer: 5 * 1024 * 1024,
        });
        if (!stdout.trim())
            return [];
        return stdout
            .trim()
            .split('\n')
            .map((line) => {
            const [statusChar, ...pathParts] = line.split('\t');
            const filePath = pathParts.join('\t');
            let status = 'modified';
            if (statusChar === 'A')
                status = 'added';
            else if (statusChar === 'D')
                status = 'deleted';
            else if (statusChar?.startsWith('R'))
                status = 'modified';
            return { path: filePath, status };
        })
            .filter((f) => f.path);
    }
    catch {
        return [];
    }
}
function loadEvidence(cwd) {
    const evidencePath = join(cwd, '.agent-workflow', 'evidence.jsonl');
    if (!existsSync(evidencePath))
        return [];
    try {
        const raw = readFileSync(evidencePath, 'utf-8');
        return raw
            .trim()
            .split('\n')
            .filter(Boolean)
            .map((line) => JSON.parse(line));
    }
    catch {
        return [];
    }
}
function getEvidenceForTask(cwd, taskId) {
    return loadEvidence(cwd).filter((e) => e.taskIds?.includes(taskId) ?? e.summary.includes(taskId));
}
function extractFilesFromEvidence(records) {
    const results = [];
    for (const rec of records) {
        const phase = rec.owner?.replace('setup-agents:', '') ?? null;
        if (rec.path) {
            results.push({ path: rec.path, phase, timestamp: rec.createdAt });
        }
        // Extract file paths from command evidence
        if (rec.evidenceKind === 'file' && rec.summary) {
            const pathMatch = rec.summary.match(/(?:^|\s)([\w./-]+\.\w+)/);
            if (pathMatch) {
                results.push({ path: pathMatch[1], phase, timestamp: rec.createdAt });
            }
        }
    }
    return results;
}
// ─── Handoff correlation ────────────────────────────────────────────────────
function getHandoffFiles(cwd, taskId) {
    const handoffsDir = join(cwd, '.agent-workflow', 'handoffs');
    if (!existsSync(handoffsDir))
        return [];
    const results = [];
    try {
        const files = readdirSync(handoffsDir).filter((f) => f.startsWith(taskId) && f.endsWith('.md'));
        for (const filename of files) {
            const content = readFileSync(join(handoffsDir, filename), 'utf-8');
            // Extract phase from filename pattern: TASK-role-to-role.md or runtime-handoff.md
            const phaseMatch = filename.match(/-(developer|architect|qa|product_manager|product_owner|release_manager)-/);
            const phase = phaseMatch ? phaseMatch[1] : null;
            // Extract file paths from markdown content (lines starting with - or ` delimited)
            const lines = content.split('\n');
            for (const line of lines) {
                const trimmed = line.trim();
                // Match bullet points like "- src/foo/bar.ts" or backtick-wrapped paths
                const bulletMatch = trimmed.match(/^-\s+([\w.@/-]+\.\w+)/);
                const backtickMatch = trimmed.match(/`([\w.@/-]+\.\w+)`/);
                const candidate = bulletMatch?.[1] ?? backtickMatch?.[1];
                if (candidate && candidate.includes('/') && !results.some((r) => r.path === candidate)) {
                    results.push({ path: candidate, phase });
                }
            }
        }
    }
    catch {
        // Ignore read errors
    }
    return results;
}
// ─── Public API ─────────────────────────────────────────────────────────────
export async function getTaskFiles(cwd, taskId) {
    if (!isValidTaskId(taskId)) {
        throw new Error(`Invalid task ID: ${taskId}`);
    }
    const fileMap = new Map();
    // 1. Declared paths from task definition
    const task = findTask(cwd, taskId);
    if (task?.paths) {
        for (const declaredPath of task.paths) {
            fileMap.set(declaredPath, {
                path: declaredPath,
                status: 'expected',
                source: 'declared-path',
                phase: null,
                lastModified: null,
            });
        }
    }
    // 2. Git commits mentioning the task ID
    const commits = await getCommitsForTask(cwd, taskId);
    for (const commit of commits) {
        for (const file of commit.files) {
            const existing = fileMap.get(file.path);
            if (existing) {
                // Upgrade from expected to actual status
                existing.status = file.status;
                existing.source = 'git-commit';
                existing.lastModified = commit.date;
            }
            else {
                fileMap.set(file.path, {
                    path: file.path,
                    status: file.status,
                    source: 'git-commit',
                    phase: null,
                    lastModified: commit.date,
                });
            }
        }
    }
    // 3. Evidence records
    const evidence = getEvidenceForTask(cwd, taskId);
    const evidenceFiles = extractFilesFromEvidence(evidence);
    for (const ef of evidenceFiles) {
        if (!fileMap.has(ef.path)) {
            fileMap.set(ef.path, {
                path: ef.path,
                status: 'modified',
                source: 'evidence',
                phase: ef.phase,
                lastModified: ef.timestamp,
            });
        }
        else {
            const existing = fileMap.get(ef.path);
            if (!existing.phase && ef.phase) {
                existing.phase = ef.phase;
            }
        }
    }
    // 4. Handoff documents
    const handoffFiles = getHandoffFiles(cwd, taskId);
    for (const hf of handoffFiles) {
        if (!fileMap.has(hf.path)) {
            fileMap.set(hf.path, {
                path: hf.path,
                status: 'modified',
                source: 'handoff',
                phase: hf.phase,
                lastModified: null,
            });
        }
        else {
            const existing = fileMap.get(hf.path);
            if (!existing.phase && hf.phase) {
                existing.phase = hf.phase;
            }
        }
    }
    return Array.from(fileMap.values()).sort((a, b) => a.path.localeCompare(b.path));
}
export async function getTaskTimeline(cwd, taskId) {
    if (!isValidTaskId(taskId)) {
        throw new Error(`Invalid task ID: ${taskId}`);
    }
    const events = [];
    // Git commits
    const commits = await getCommitsForTask(cwd, taskId);
    for (const commit of commits) {
        events.push({
            type: 'commit',
            timestamp: commit.date,
            description: commit.subject,
            files: commit.files.map((f) => f.path),
        });
    }
    // Evidence records
    const evidence = getEvidenceForTask(cwd, taskId);
    for (const rec of evidence) {
        const files = [];
        if (rec.path)
            files.push(rec.path);
        events.push({
            type: 'evidence',
            timestamp: rec.createdAt,
            description: `[${rec.evidenceKind ?? 'evidence'}] ${rec.summary}`,
            files,
        });
    }
    // Phase transitions from handoff filenames
    const handoffsDir = join(cwd, '.agent-workflow', 'handoffs');
    if (existsSync(handoffsDir)) {
        try {
            const handoffFiles = readdirSync(handoffsDir).filter((f) => f.startsWith(taskId) && f.endsWith('.md'));
            for (const filename of handoffFiles) {
                const filePath = join(handoffsDir, filename);
                const content = readFileSync(filePath, 'utf-8');
                // Try to get date from content
                const dateMatch = content.match(/\*\*Date:\*\*\s*(\d{4}-\d{2}-\d{2})/);
                const timestamp = dateMatch ? new Date(dateMatch[1]).toISOString() : new Date().toISOString();
                // Extract role transition from filename
                const transitionMatch = filename.match(/(\w+)-to-(\w+)\.md$/);
                const runtimeMatch = filename.match(/-(\w+)-(\w+)-runtime-handoff\.md$/);
                let description;
                if (transitionMatch) {
                    description = `Phase transition: ${transitionMatch[1]} -> ${transitionMatch[2]}`;
                }
                else if (runtimeMatch) {
                    description = `Runtime handoff: ${runtimeMatch[1]} (${runtimeMatch[2]})`;
                }
                else {
                    description = `Handoff: ${filename}`;
                }
                // Extract modified files from handoff content
                const handoffFileSet = new Set();
                for (const line of content.split('\n')) {
                    const t = line.trim();
                    const bm = t.match(/^-\s+([\w.@/-]+\.\w+)/);
                    const btm = t.match(/`([\w.@/-]+\.\w+)`/);
                    const p = bm?.[1] ?? btm?.[1];
                    if (p && p.includes('/'))
                        handoffFileSet.add(p);
                }
                const files = [...handoffFileSet];
                events.push({
                    type: 'phase-transition',
                    timestamp,
                    description,
                    files,
                });
            }
        }
        catch {
            // Ignore read errors
        }
    }
    // Sort chronologically (newest first)
    return events.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
}
//# sourceMappingURL=task-files.js.map