/**
 * Provider-Agnostic AC Progress Sync
 *
 * Routes AC completion events to all enabled providers (GitHub, JIRA, ADO)
 * using a simple function-map pattern. No adapter classes, no orchestrator.
 *
 * GitHub: delegates to existing 0193 modules (postACProgressComments + autoCloseCompletedUserStories)
 * JIRA: calls JiraStatusSync for comments and transitions
 * ADO: calls AdoClient for comments, AdoStatusSync for state transitions
 *
 * @module ac-progress-sync
 */
import { readFile } from 'fs/promises';
import { formatACCheckboxes } from './ac-checkbox-formatter.js';
import { SyncCircuitBreaker } from './increment/sync-circuit-breaker.js';
// ============================================================================
// Shared Helpers
// ============================================================================
function emptyResult() {
    return { posted: [], closed: [], skipped: [], errors: [] };
}
function serializeError(err) {
    return err instanceof Error ? err.message : String(err);
}
function formatProgressText(acStates, format) {
    const commentBody = formatACCheckboxes(acStates, format);
    const total = acStates.length;
    const completed = acStates.filter(ac => ac.completed).length;
    return `AC Progress: ${completed}/${total}\n\n${commentBody}`;
}
function isAllComplete(acStates) {
    return acStates.length > 0 && acStates.every(ac => ac.completed);
}
/**
 * GitHub provider: delegates to existing 0193 modules directly.
 */
async function syncGitHubACProgress(incrementId, affectedUSIds, ctx, config) {
    const result = emptyResult();
    const { postACProgressComments } = await import('../../plugins/specweave/lib/integrations/github/github-ac-comment-poster.js');
    const { autoCloseCompletedUserStories } = await import('../../plugins/specweave/lib/integrations/github/github-us-auto-closer.js');
    const options = {
        owner: config.github?.owner || '',
        repo: config.github?.repo || '',
        token: config.github?.token,
    };
    // Post progress comments
    const commentResult = await postACProgressComments(incrementId, affectedUSIds, ctx.specPath, options);
    for (const posted of commentResult.posted) {
        result.posted.push({ usId: posted.usId, ref: String(posted.issueNumber) });
    }
    for (const err of commentResult.errors) {
        result.errors.push({ usId: err.usId, error: err.error });
    }
    // Auto-close completed user stories
    const closeResult = await autoCloseCompletedUserStories(incrementId, affectedUSIds, ctx.specPath, options);
    for (const closed of closeResult.closed) {
        result.closed.push({ usId: closed.usId, ref: String(closed.issueNumber) });
    }
    for (const skipped of closeResult.skipped) {
        result.skipped.push({ usId: skipped.usId, reason: skipped.reason });
    }
    for (const err of closeResult.errors) {
        result.errors.push({ usId: err.usId, error: err.error });
    }
    return result;
}
/**
 * JIRA provider: posts comments and transitions issues.
 */
async function syncJiraACProgress(incrementId, affectedUSIds, ctx, config) {
    const result = emptyResult();
    const { JiraStatusSync } = await import('../../plugins/specweave/lib/integrations/jira/jira-status-sync.js');
    const jiraSync = new JiraStatusSync(config.jira?.domain || '', config.jira?.email || '', config.jira?.apiToken || '', config.jira?.projectKey || '');
    await jiraSync.init();
    const jiraLinks = config.externalLinks?.jira?.userStories || {};
    for (const usId of affectedUSIds) {
        const link = jiraLinks[usId];
        if (!link) {
            result.skipped.push({ usId, reason: 'no-issue-link' });
            continue;
        }
        const us = ctx.userStories.get(usId);
        const acStates = us?.acStates || [];
        try {
            // Post progress comment with proper ADF formatting and dedup
            const posted = await jiraSync.postProgressComment(link.issueKey, acStates);
            if (posted) {
                result.posted.push({ usId, ref: link.issueKey });
            }
            else {
                result.skipped.push({ usId, reason: 'duplicate-progress' });
            }
            // Auto-transition to "In Progress" when work has started (partial completion)
            const completed = acStates.filter(ac => ac.completed).length;
            if (completed > 0 && !isAllComplete(acStates)) {
                await jiraSync.updateStatus(link.issueKey, { state: 'In Progress' });
            }
            // Auto-transition to Done if all ACs complete
            if (isAllComplete(acStates)) {
                const transitioned = await jiraSync.updateStatus(link.issueKey, { state: 'Done' });
                if (transitioned) {
                    result.closed.push({ usId, ref: link.issueKey });
                }
                else {
                    result.skipped.push({ usId, reason: 'already-closed' });
                }
            }
        }
        catch (err) {
            result.errors.push({ usId, error: serializeError(err) });
        }
    }
    return result;
}
/**
 * ADO provider: posts comments and transitions work items.
 */
async function syncAdoACProgress(incrementId, affectedUSIds, ctx, config) {
    const result = emptyResult();
    const { AdoStatusSync } = await import('../../plugins/specweave/lib/integrations/ado/ado-status-sync.js');
    const { AdoClient } = await import('../../plugins/specweave/lib/integrations/ado/ado-client.js');
    const adoSync = new AdoStatusSync(config.ado?.organization || '', config.ado?.project || '', config.ado?.pat || '');
    const adoClient = new AdoClient({
        organization: config.ado?.organization || '',
        project: config.ado?.project || '',
        personalAccessToken: config.ado?.pat || '',
    });
    const adoLinks = config.externalLinks?.ado?.userStories || {};
    for (const usId of affectedUSIds) {
        const link = adoLinks[usId];
        if (!link) {
            result.skipped.push({ usId, reason: 'no-issue-link' });
            continue;
        }
        const us = ctx.userStories.get(usId);
        const acStates = us?.acStates || [];
        try {
            // Post progress comment
            const commentText = formatProgressText(acStates, 'ado');
            await adoClient.addComment(link.workItemId, commentText);
            result.posted.push({ usId, ref: String(link.workItemId) });
            // Auto-transition to Done/Closed if all ACs complete
            // Basic process uses "Done", Agile/CMMI use "Closed"
            if (isAllComplete(acStates)) {
                const currentStatus = await adoSync.getStatus(link.workItemId);
                const doneStates = ['Closed', 'Done', 'Completed', 'Resolved'];
                if (doneStates.includes(currentStatus.state)) {
                    result.skipped.push({ usId, reason: 'already-closed' });
                }
                else {
                    // Try "Done" first (Basic process), fall back to "Closed" (Agile/CMMI)
                    try {
                        await adoSync.updateStatus(link.workItemId, { state: 'Done' });
                        result.closed.push({ usId, ref: String(link.workItemId) });
                    }
                    catch {
                        await adoSync.updateStatus(link.workItemId, { state: 'Closed' });
                        result.closed.push({ usId, ref: String(link.workItemId) });
                    }
                }
            }
        }
        catch (err) {
            result.errors.push({ usId, error: serializeError(err) });
        }
    }
    return result;
}
// ============================================================================
// Provider Map
// ============================================================================
const PROVIDER_MAP = {
    github: syncGitHubACProgress,
    jira: syncJiraACProgress,
    ado: syncAdoACProgress,
};
// Per-provider circuit breakers (module-level singletons)
const circuitBreakers = {
    github: new SyncCircuitBreaker(),
    jira: new SyncCircuitBreaker(),
    ado: new SyncCircuitBreaker(),
};
// ============================================================================
// Main Entry Point
// ============================================================================
/**
 * Sync AC progress to all enabled providers.
 *
 * Reads config to determine which providers are enabled, then calls each
 * provider's sync function. Errors are isolated per provider.
 *
 * @param incrementId - The increment ID
 * @param affectedUSIds - User story IDs that have AC changes
 * @param specPath - Path to spec.md
 * @param config - Sync configuration with provider settings and external links
 * @returns Per-provider results with posted, closed, skipped, and errors
 */
export async function syncACProgressToProviders(incrementId, affectedUSIds, specPath, config) {
    const result = {};
    if (affectedUSIds.length === 0) {
        return result;
    }
    // Build context from spec.md
    const ctx = await buildACProgressContext(specPath, affectedUSIds);
    // Execute each enabled provider
    const providers = ['github', 'jira', 'ado'];
    for (const provider of providers) {
        const providerConfig = config.sync?.[provider];
        if (!providerConfig?.enabled)
            continue;
        // Check circuit breaker before calling provider
        const breaker = circuitBreakers[provider];
        if (!breaker.canSync()) {
            const skippedResult = emptyResult();
            for (const usId of affectedUSIds) {
                skippedResult.skipped.push({ usId, reason: 'circuit-open' });
            }
            result[provider] = skippedResult;
            continue;
        }
        const syncFn = PROVIDER_MAP[provider];
        try {
            const providerResult = await syncFn(incrementId, affectedUSIds, ctx, config);
            result[provider] = providerResult;
            // Record circuit breaker outcome based on provider errors
            // A successful sync can have posted (comments), closed (issues), or both.
            // Only record failure when there were errors AND nothing succeeded.
            const anySuccess = providerResult.posted.length > 0 || providerResult.closed.length > 0;
            if (providerResult.errors.length > 0 && !anySuccess) {
                breaker.recordFailure();
            }
            else {
                breaker.recordSuccess();
            }
        }
        catch (err) {
            breaker.recordFailure();
            // Top-level error isolation: if provider function throws unexpectedly
            const providerResult = emptyResult();
            providerResult.errors.push({
                usId: affectedUSIds[0] || 'unknown',
                error: serializeError(err),
            });
            result[provider] = providerResult;
        }
    }
    return result;
}
/**
 * Close all external tool issues for a completed increment.
 *
 * Unlike syncACProgressToProviders (which operates on recently-changed US IDs),
 * this function parses ALL user story IDs from spec.md and attempts to close
 * their corresponding external issues. Used when increment status -> "completed".
 */
export async function closeIncrementIssues(incrementId, specPath, config) {
    const content = await readFile(specPath, 'utf-8');
    const allUSIds = parseAllUserStoryIds(content);
    if (allUSIds.length === 0) {
        return {};
    }
    return syncACProgressToProviders(incrementId, allUSIds, specPath, config);
}
/**
 * Parse all user story IDs from spec.md content.
 * Matches patterns like "### US-001:", "### US-002:" etc.
 */
export function parseAllUserStoryIds(content) {
    const ids = [];
    const pattern = /###\s+(US-(?:[A-Z]+-)*\d+):/g;
    let match;
    while ((match = pattern.exec(content)) !== null) {
        ids.push(match[1]);
    }
    return ids;
}
/**
 * Reset all circuit breakers (for testing only).
 */
export function resetCircuitBreakers() {
    for (const provider of Object.keys(circuitBreakers)) {
        circuitBreakers[provider].reset();
    }
}
/**
 * Build AC progress context by parsing spec.md.
 *
 * Extracts AC states (id, description, completed) for each affected user story.
 */
export async function buildACProgressContext(specPath, affectedUSIds) {
    const content = await readFile(specPath, 'utf-8');
    const userStories = new Map();
    for (const usId of affectedUSIds) {
        const acStates = parseACStatesForUS(content, usId);
        userStories.set(usId, { acStates });
    }
    return {
        specPath,
        specContent: content,
        userStories,
    };
}
/**
 * Parse a US ID into its parts for AC matching.
 * Handles both simple (US-001) and compound (US-SPE-001) formats.
 *
 * US-001 → { prefix: undefined, num: 1 }
 * US-SPE-001 → { prefix: 'SPE', num: 1 }
 */
export function parseUSIdParts(usId) {
    // Compound: US-SPE-001, US-VSK-001, US-VPL-001
    const compoundMatch = usId.match(/^US-([A-Z]+)-(\d+)$/);
    if (compoundMatch) {
        return { prefix: compoundMatch[1], num: parseInt(compoundMatch[2], 10) };
    }
    // Simple: US-001
    const simpleMatch = usId.match(/^US-(\d+)$/);
    if (simpleMatch) {
        return { num: parseInt(simpleMatch[1], 10) };
    }
    // Fallback: try to extract trailing number
    const fallback = usId.match(/(\d+)$/);
    return { num: fallback ? parseInt(fallback[1], 10) : 0 };
}
/**
 * Build the AC ID prefix for a given US ID.
 * US-001 → "AC-US1-"
 * US-SPE-001 → "AC-SPE-US1-"
 */
export function buildACPrefix(usId) {
    const { prefix, num } = parseUSIdParts(usId);
    if (prefix) {
        return `AC-${prefix}-US${num}-`;
    }
    return `AC-US${num}-`;
}
/**
 * Extract AC states for a specific user story from spec.md content.
 * Supports both simple (AC-US1-01) and compound (AC-SPE-US1-01) AC ID formats.
 */
function parseACStatesForUS(content, usId) {
    const states = [];
    const acPrefix = buildACPrefix(usId);
    // Escape the prefix for regex (hyphens are safe, but be defensive)
    const escapedPrefix = acPrefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    // Support both bold (**AC-...**:) and plain (AC-...:) formats
    const acPattern = new RegExp(`- \\[([ x])\\] (?:\\*\\*)?${escapedPrefix}(\\d+)(?:\\*\\*)?:\\s*(.+)`, 'g');
    let match;
    while ((match = acPattern.exec(content)) !== null) {
        states.push({
            id: `${acPrefix}${match[2]}`,
            description: match[3].trim(),
            completed: match[1] === 'x',
        });
    }
    return states;
}
//# sourceMappingURL=ac-progress-sync.js.map