/**
 * Per-US Azure DevOps Sync (v0.34.0+)
 *
 * Syncs each User Story to its explicitly declared project's ADO project/area.
 * Uses the **Project**: and **Board**: fields in spec.md (NOT keyword-based classification).
 *
 * Key difference from multi-project-sync:
 * - Multi-project sync uses keyword/heuristic classification
 * - Per-US sync uses EXPLICIT **Project**: and **Board**: fields from spec.md
 *
 * @module per-us-sync
 * @since v0.34.0
 */
import { consoleLogger } from '../../../../../src/utils/logger.js';
/**
 * Default work item type by process template
 */
const WORK_ITEM_TYPE_BY_TEMPLATE = {
    agile: 'User Story',
    scrum: 'Product Backlog Item',
    cmmi: 'Requirement',
    basic: 'Issue',
};
/**
 * Per-US ADO Sync
 *
 * Syncs each US to its declared project's ADO project/area path.
 * For 2-level structures, uses **Board**: to determine area path.
 */
export class PerUSAdoSync {
    constructor(adoClient, projectMappings, options = {}) {
        this.adoClient = adoClient;
        this.projectMappings = projectMappings;
        this.logger = options.logger ?? consoleLogger;
        // Explicit workItemType takes priority, then process template lookup, then default
        this.workItemType = options.workItemType
            ?? WORK_ITEM_TYPE_BY_TEMPLATE[options.processTemplate?.toLowerCase() ?? '']
            ?? 'User Story';
    }
    /**
     * Sync all user stories to their respective ADO projects
     *
     * @param userStories - User stories with explicit project/board fields
     * @param featureId - Feature ID (e.g., "FS-137")
     * @param options - Sync options
     */
    async syncUserStories(userStories, featureId, options = {}) {
        const synced = [];
        const failed = [];
        const externalRefs = {};
        // Group USs by their declared project
        const groups = this.groupByProject(userStories, options.defaultProject);
        this.logger.log(`📡 Per-US ADO Sync: ${userStories.length} USs across ${groups.size} projects`);
        for (const [projectId, stories] of groups) {
            // Get ADO mapping for this project
            const mapping = this.projectMappings[projectId]?.ado;
            if (!mapping) {
                // No ADO mapping for this project
                this.logger.warn(`   ⚠️  No ADO mapping for project "${projectId}" - skipping ${stories.length} USs`);
                for (const story of stories) {
                    failed.push({
                        usId: story.id,
                        projectId,
                        adoProject: 'N/A',
                        areaPath: '',
                        workItemId: 0,
                        url: '',
                        action: 'skipped',
                        error: `No ADO mapping for project "${projectId}"`
                    });
                }
                continue;
            }
            // Sync each US to this project's ADO project
            for (const story of stories) {
                try {
                    const result = await this.syncUserStory(story, mapping, featureId, options);
                    synced.push({
                        ...result,
                        projectId
                    });
                    // Build external ref
                    if (!options.dryRun && result.action !== 'skipped') {
                        externalRefs[story.id] = {
                            ado: {
                                provider: 'ado',
                                issueNumber: result.workItemId,
                                url: result.url,
                                targetProject: projectId,
                                lastSynced: new Date().toISOString()
                            }
                        };
                    }
                }
                catch (error) {
                    failed.push({
                        usId: story.id,
                        projectId,
                        adoProject: mapping.project,
                        areaPath: mapping.areaPath || '',
                        workItemId: 0,
                        url: '',
                        action: 'skipped',
                        error: error instanceof Error ? error.message : String(error)
                    });
                }
            }
        }
        // Calculate summary
        const created = synced.filter(r => r.action === 'created').length;
        const updated = synced.filter(r => r.action === 'updated').length;
        const skipped = synced.filter(r => r.action === 'skipped').length;
        return {
            success: failed.length === 0,
            synced,
            failed,
            externalRefs,
            summary: {
                total: userStories.length,
                created,
                updated,
                skipped,
                failed: failed.length
            }
        };
    }
    /**
     * Sync a single user story to ADO
     *
     * For 2-level structures:
     * - **Project**: maps to ADO project
     * - **Board**: maps to area path under the project
     */
    async syncUserStory(story, mapping, featureId, options) {
        const title = `[${featureId}][${story.id}] ${story.title}`;
        const description = this.buildWorkItemDescription(story, featureId);
        // Determine area path:
        // - Use story.board if available (2-level structure)
        // - Fall back to mapping.areaPath
        // - Fall back to project root
        let areaPath = mapping.areaPath || mapping.project;
        if (story.board) {
            // 2-level structure: append board as area path segment
            areaPath = `${mapping.project}\\${story.board}`;
        }
        if (options.dryRun) {
            this.logger.log(`   🔍 [DRY-RUN] Would sync ${story.id} to ${mapping.project} (area: ${areaPath})`);
            return {
                usId: story.id,
                projectId: story.project || 'unknown',
                adoProject: mapping.project,
                areaPath,
                workItemId: 0,
                url: '',
                action: 'skipped'
            };
        }
        // Check for existing work item (scoped to current feature to prevent cross-increment matches)
        const existingItem = await this.findExistingWorkItem(mapping.project, featureId, story.id);
        if (existingItem) {
            // Update existing work item
            await this.adoClient.updateWorkItem(mapping.project, existingItem.id, title, description, areaPath);
            this.logger.log(`   🔄 Updated ${story.id} → ${mapping.project}/${existingItem.id}`);
            return {
                usId: story.id,
                projectId: story.project || 'unknown',
                adoProject: mapping.project,
                areaPath,
                workItemId: existingItem.id,
                url: this.adoClient.getWorkItemUrl(mapping.project, existingItem.id),
                action: 'updated'
            };
        }
        else {
            // Create new work item
            const newItem = await this.adoClient.createWorkItem(mapping.project, this.workItemType, title, description, areaPath);
            this.logger.log(`   ✅ Created ${story.id} → ${mapping.project}/${newItem.id}`);
            return {
                usId: story.id,
                projectId: story.project || 'unknown',
                adoProject: mapping.project,
                areaPath,
                workItemId: newItem.id,
                url: newItem.url,
                action: 'created'
            };
        }
    }
    /**
     * Find existing work item by Feature ID + US ID in title.
     * Scoped to current feature to prevent cross-increment matches (FS-604).
     */
    async findExistingWorkItem(project, featureId, usId) {
        try {
            const query = `[System.Title] Contains '[${featureId}][${usId}]'`;
            const results = await this.adoClient.searchWorkItems(project, query);
            return results.length > 0 ? { id: results[0].id } : null;
        }
        catch {
            return null;
        }
    }
    /**
     * Build work item description from user story
     *
     * Includes full User Story format with description and Acceptance Criteria.
     * Uses `acceptanceCriteriaFull` if available (v1.0.59+) for AC descriptions,
     * otherwise falls back to AC IDs only.
     */
    buildWorkItemDescription(story, featureId) {
        const lines = [];
        lines.push(`<h1>${this.escapeHtml(story.title)}</h1>`);
        lines.push('');
        // User Story description (As a... I want... So that...)
        if (story.description) {
            // Convert markdown bold to HTML
            const htmlDesc = story.description
                .replace(/\*\*As a\*\*/g, '<strong>As a</strong>')
                .replace(/\*\*I want\*\*/g, '<strong>I want</strong>')
                .replace(/\*\*So that\*\*/g, '<strong>So that</strong>')
                .replace(/\n/g, '<br/>');
            lines.push(`<p>${htmlDesc}</p>`);
            lines.push('');
        }
        // Acceptance Criteria - prefer full descriptions if available
        if (story.acceptanceCriteriaFull && story.acceptanceCriteriaFull.length > 0) {
            // Use full AC data with descriptions (v1.0.59+)
            lines.push('<h2>Acceptance Criteria</h2>');
            lines.push('<ul>');
            for (const ac of story.acceptanceCriteriaFull) {
                const checkbox = ac.completed ? '☑' : '☐';
                lines.push(`  <li>${checkbox} <strong>${this.escapeHtml(ac.id)}</strong>: ${this.escapeHtml(ac.description)}</li>`);
            }
            lines.push('</ul>');
            lines.push('');
        }
        else if (story.acceptanceCriteria && story.acceptanceCriteria.length > 0) {
            // Fallback: AC IDs only (backward compatible)
            lines.push('<h2>Acceptance Criteria</h2>');
            lines.push('<ul>');
            for (const ac of story.acceptanceCriteria) {
                lines.push(`  <li>☐ ${this.escapeHtml(ac)}</li>`);
            }
            lines.push('</ul>');
            lines.push('');
        }
        lines.push('<hr/>');
        lines.push('');
        lines.push(`<p><strong>Feature</strong>: ${this.escapeHtml(featureId)}</p>`);
        lines.push(`<p><strong>User Story</strong>: ${this.escapeHtml(story.id)}</p>`);
        if (story.project) {
            lines.push(`<p><strong>Project</strong>: ${this.escapeHtml(story.project)}</p>`);
        }
        if (story.board) {
            lines.push(`<p><strong>Board</strong>: ${this.escapeHtml(story.board)}</p>`);
        }
        return lines.join('\n');
    }
    /**
     * Escape HTML special characters
     */
    escapeHtml(str) {
        return str
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;')
            .replace(/'/g, '&#39;');
    }
    /**
     * Group user stories by their explicit project field
     */
    groupByProject(userStories, defaultProject) {
        const groups = new Map();
        for (const story of userStories) {
            const project = story.project || defaultProject || 'default';
            if (!groups.has(project)) {
                groups.set(project, []);
            }
            groups.get(project).push(story);
        }
        return groups;
    }
}
/**
 * Format per-US sync results for display
 */
export function formatPerUSSyncResults(result) {
    const lines = [];
    lines.push('');
    lines.push('📊 Per-US ADO Sync Results');
    lines.push('');
    // Group by project
    const byProject = new Map();
    for (const r of [...result.synced, ...result.failed]) {
        const existing = byProject.get(r.projectId) || [];
        existing.push(r);
        byProject.set(r.projectId, existing);
    }
    for (const [projectId, results] of byProject) {
        const adoProject = results[0]?.adoProject || 'N/A';
        const areaPath = results[0]?.areaPath || '';
        lines.push(`**${projectId}** (→ ${adoProject}${areaPath ? ` [${areaPath}]` : ''}):`);
        for (const r of results) {
            const icon = r.action === 'created' ? '✅' :
                r.action === 'updated' ? '🔄' :
                    r.error ? '❌' : '⏭️';
            if (r.workItemId > 0) {
                lines.push(`  ${icon} ${r.usId} → ${r.adoProject}/${r.workItemId}`);
            }
            else if (r.error) {
                lines.push(`  ${icon} ${r.usId}: ${r.error}`);
            }
            else {
                lines.push(`  ${icon} ${r.usId} (${r.action})`);
            }
        }
        lines.push('');
    }
    lines.push(`📈 Summary: ${result.summary.created} created, ${result.summary.updated} updated, ${result.summary.failed} failed`);
    return lines.join('\n');
}
//# sourceMappingURL=per-us-sync.js.map