/**
 * External Issue Auto-Creator
 *
 * Automatically creates external issues (GitHub/JIRA/ADO) for increments
 * when they don't have linked issues.
 *
 * TRIGGER POINTS:
 * 1. After increment creation (post-increment-planning hook)
 * 2. During sync-progress if issue is missing
 * 3. On status change (active → completed)
 *
 * SAFETY FEATURES:
 * - 3-layer idempotency (frontmatter → metadata → API)
 * - Duplicate detection before creation
 * - Rate limiting (circuit breaker)
 * - Non-blocking (failures don't crash the workflow)
 *
 * @see ADR-0139 (Unified Post-Increment GitHub Sync)
 */
import { promises as fs, existsSync, readFileSync } from 'fs';
import { execFileSync } from 'child_process';
import path from 'path';
import yaml from 'yaml';
import { consoleLogger } from '../utils/logger.js';
import { deriveFeatureId } from '../utils/feature-id-derivation.js';
import { ConfigManager } from '../core/config/config-manager.js';
import { isTemplateFile } from '../core/increment/template-creator.js';
import { resolveSyncTarget } from './sync-target-resolver.js';
/**
 * Auto-creates external issues for increments
 *
 * Usage:
 * ```typescript
 * const creator = new ExternalIssueAutoCreator({ projectRoot: process.cwd() });
 * const result = await creator.createForIncrement('0001-feature');
 * ```
 */
export class ExternalIssueAutoCreator {
    constructor(options) {
        this._defaultBranch = null;
        this.projectRoot = options.projectRoot;
        this.logger = options.logger ?? consoleLogger;
        this.configManager = new ConfigManager(this.projectRoot);
    }
    /**
     * Resolve increment directory from a short or full ID.
     * Handles "0421" → "0421-umbrella-docs-update" prefix matching.
     */
    resolveIncrementDir(incrementId) {
        const directPath = path.join(this.projectRoot, '.specweave/increments', incrementId);
        if (existsSync(directPath))
            return incrementId;
        // Try prefix match
        const incrementsDir = path.join(this.projectRoot, '.specweave/increments');
        if (existsSync(incrementsDir)) {
            try {
                const entries = require('fs').readdirSync(incrementsDir);
                for (const entry of entries) {
                    if (entry.startsWith(incrementId + '-')) {
                        return entry;
                    }
                }
            }
            catch {
                // Fall through to original ID
            }
        }
        return incrementId;
    }
    detectDefaultBranch() {
        try {
            // Try config first
            const configPath = path.join(this.projectRoot, '.specweave/config.json');
            if (existsSync(configPath)) {
                const config = JSON.parse(readFileSync(configPath, 'utf-8'));
                if (config.repository?.defaultBranch) {
                    return config.repository.defaultBranch;
                }
            }
            // Try git
            const result = execFileSync('git', ['symbolic-ref', 'refs/remotes/origin/HEAD'], {
                cwd: this.projectRoot,
                encoding: 'utf-8',
                timeout: 5000,
            }).trim();
            // Returns "refs/remotes/origin/main" → extract "main"
            return result.split('/').pop() || 'main';
        }
        catch {
            return 'main';
        }
    }
    getDefaultBranch() {
        if (!this._defaultBranch) {
            this._defaultBranch = this.detectDefaultBranch();
        }
        return this._defaultBranch;
    }
    /**
     * Create external issues for an increment (if configured and not already created)
     *
     * This method:
     * 1. Checks if auto-create is enabled in config
     * 2. Detects which provider to use (GitHub/JIRA/ADO)
     * 3. Checks if issues already exist (3-layer idempotency)
     * 4. Creates issues if missing
     * 5. Updates metadata with issue links
     */
    async createForIncrement(incrementId, targetProvider) {
        try {
            // Load config
            const config = await this.configManager.read();
            // Check if auto-create is enabled
            const autoCreateEnabled = this.isAutoCreateEnabled(config);
            if (!autoCreateEnabled) {
                return {
                    success: true,
                    provider: 'none',
                    skipped: true,
                    skipReason: 'Auto-create disabled in config (sync.autoCreateOnIncrement = false)',
                };
            }
            // Use target provider if specified, otherwise auto-detect
            const provider = targetProvider || this.detectProvider(config);
            if (!provider) {
                return {
                    success: true,
                    provider: 'none',
                    skipped: true,
                    skipReason: 'No external provider configured (GitHub/JIRA/ADO)',
                };
            }
            // Resolve full increment directory name (e.g., "0421" → "0421-umbrella-docs-update")
            const resolvedId = this.resolveIncrementDir(incrementId);
            // Guard: skip if spec.md is still a template with placeholders
            const specPath = path.join(this.projectRoot, '.specweave/increments', resolvedId, 'spec.md');
            if (isTemplateFile(specPath)) {
                this.logger.log(`⏭️ Skipping ${incrementId}: spec.md is still a template`);
                return {
                    success: true,
                    provider: 'none',
                    skipped: true,
                    skipReason: 'spec.md is still a template - deferring until spec is complete',
                };
            }
            // Load increment info
            const incrementInfo = await this.loadIncrementInfo(resolvedId);
            if (!incrementInfo) {
                return {
                    success: false,
                    provider,
                    error: `Increment ${incrementId} not found or missing spec.md`,
                };
            }
            // Check if already has external issue (Layer 1 & 2)
            const existingIssue = await this.checkExistingIssue(resolvedId, provider);
            if (existingIssue) {
                this.logger.log(`✅ ${incrementId} already has ${provider} issue: ${existingIssue}`);
                return {
                    success: true,
                    provider,
                    skipped: true,
                    skipReason: `Issue already exists: ${existingIssue}`,
                };
            }
            // Resolve distributed sync target for per-project Jira/ADO routing
            let projectName;
            if (existsSync(specPath)) {
                try {
                    const specContent = await fs.readFile(specPath, 'utf-8');
                    // Try YAML frontmatter first
                    const fmMatch = specContent.match(/^---\n([\s\S]*?)\n---/);
                    if (fmMatch) {
                        const parsed = yaml.parse(fmMatch[1]);
                        projectName = parsed?.project;
                    }
                    // Fallback: extract from first **Project**: field in body (legacy format)
                    if (!projectName) {
                        const projectFieldMatch = specContent.match(/\*\*Project\*\*:\s*(\S+)/);
                        if (projectFieldMatch) {
                            projectName = projectFieldMatch[1];
                        }
                    }
                }
                catch {
                    // Ignore parse errors
                }
            }
            // Final fallback: use first user story's project field
            if (!projectName && incrementInfo.userStories.length > 0) {
                projectName = incrementInfo.userStories[0].project;
            }
            const resolvedTarget = resolveSyncTarget(projectName, config);
            // Create issues based on provider
            // NOTE (0348): GitHub is handled by LivingDocsSync → GitHubFeatureSync chain.
            // ExternalIssueAutoCreator only handles JIRA and ADO now.
            switch (provider) {
                case 'github':
                    return {
                        success: true,
                        provider: 'github',
                        skipped: true,
                        skipReason: 'GitHub sync handled by LivingDocsSync → GitHubFeatureSync pipeline',
                    };
                case 'jira':
                    return await this.createJiraIssues(resolvedId, incrementInfo, config, resolvedTarget.jira?.projectKey);
                case 'ado':
                    return await this.createAdoIssues(resolvedId, incrementInfo, config, resolvedTarget.ado?.project);
                default:
                    return {
                        success: false,
                        provider: 'none',
                        error: `Unknown provider: ${provider}`,
                    };
            }
        }
        catch (error) {
            const errorMessage = error instanceof Error ? error.message : String(error);
            this.logger.error(`❌ Auto-create failed for ${incrementId}: ${errorMessage}`);
            return {
                success: false,
                provider: 'none',
                error: errorMessage,
            };
        }
    }
    /**
     * Check if auto-create is enabled in config
     *
     * Checks (in order):
     * 1. sync.autoCreateOnIncrement (new, explicit)
     * 2. sync.autoSync (legacy)
     * 3. sync.settings.canUpsertInternalItems (legacy)
     */
    isAutoCreateEnabled(config) {
        // New explicit option (recommended)
        if (config.sync?.autoCreateOnIncrement !== undefined) {
            return config.sync.autoCreateOnIncrement === true;
        }
        // Legacy: autoSync
        if (config.sync?.autoSync === true) {
            return true;
        }
        // Legacy: canUpsertInternalItems
        if (config.sync?.settings?.canUpsertInternalItems === true) {
            return true;
        }
        // Default: false (opt-in)
        return false;
    }
    /**
     * Detect which provider to use for external sync
     *
     * Priority:
     * 1. GitHub (if sync.github.enabled = true)
     * 2. JIRA (if sync.jira.enabled = true)
     * 3. ADO (if sync.ado.enabled = true)
     * 4. GitHub (if issueTracker.provider = 'github')
     * 5. JIRA (if issueTracker.provider = 'jira')
     * 6. ADO (if issueTracker.provider = 'ado')
     */
    detectProvider(config) {
        // Check explicit provider enablement in sync config
        if (config.sync?.github?.enabled === true) {
            return 'github';
        }
        if (config.sync?.jira?.enabled === true) {
            return 'jira';
        }
        if (config.sync?.ado?.enabled === true) {
            return 'ado';
        }
        // Check issueTracker provider
        const trackerProvider = config.issueTracker?.provider;
        if (trackerProvider === 'github') {
            return 'github';
        }
        if (trackerProvider === 'jira') {
            return 'jira';
        }
        if (trackerProvider === 'ado') {
            return 'ado';
        }
        // Check sync profiles for provider hints
        const profiles = config.sync?.profiles || {};
        for (const profile of Object.values(profiles)) {
            if (profile.provider === 'github') {
                return 'github';
            }
            if (profile.provider === 'jira') {
                return 'jira';
            }
            if (profile.provider === 'ado' || profile.provider === 'azure-devops') {
                return 'ado';
            }
        }
        return null;
    }
    /**
     * Load increment information from spec.md and metadata.json
     */
    async loadIncrementInfo(incrementId) {
        const incrementPath = path.join(this.projectRoot, '.specweave/increments', incrementId);
        const specPath = path.join(incrementPath, 'spec.md');
        const metadataPath = path.join(incrementPath, 'metadata.json');
        if (!existsSync(specPath)) {
            return null;
        }
        try {
            const specContent = await fs.readFile(specPath, 'utf-8');
            // Parse frontmatter
            const frontmatterMatch = specContent.match(/^---\n([\s\S]*?)\n---/);
            let frontmatter = {};
            if (frontmatterMatch) {
                frontmatter = yaml.parse(frontmatterMatch[1]) || {};
            }
            // Derive feature ID
            let featureId = frontmatter.feature_id || frontmatter.epic || frontmatter.feature || '';
            if (!featureId) {
                try {
                    featureId = deriveFeatureId(incrementId);
                }
                catch {
                    // CRITICAL FIX: If deriveFeatureId fails, parse manually
                    // Old code: `FS-${incrementId.substring(0, 4)}` created FS-0142 instead of FS-142
                    const numMatch = incrementId.match(/^(\d+)/);
                    if (numMatch) {
                        const num = parseInt(numMatch[1], 10);
                        featureId = `FS-${String(num).padStart(3, '0')}`;
                    }
                    else {
                        // Last resort: use first 3 chars (unlikely to reach here)
                        featureId = `FS-${incrementId.substring(0, 3)}`;
                    }
                }
            }
            // Parse user stories from spec.md body
            const userStories = this.parseUserStories(specContent);
            // Load status and origin from metadata
            let status = 'active';
            let origin;
            if (existsSync(metadataPath)) {
                const metadata = JSON.parse(await fs.readFile(metadataPath, 'utf-8'));
                status = metadata.status || 'active';
                origin = metadata.origin === 'external' ? 'external' : undefined;
            }
            return {
                id: incrementId,
                featureId,
                title: frontmatter.title || incrementId,
                status,
                origin,
                userStories,
            };
        }
        catch (error) {
            this.logger.error(`Failed to load increment info: ${error}`);
            return null;
        }
    }
    /**
     * Parse user stories from spec.md body
     */
    parseUserStories(specContent) {
        const userStories = [];
        // Match ### US-XXX: Title patterns (supports US-001, US-SPE-001, US-VPL-001, etc.)
        const usRegex = /^### (US-(?:[A-Z]+-)*\d+):?\s*(.+)$/gm;
        let match;
        while ((match = usRegex.exec(specContent)) !== null) {
            const usId = match[1];
            const title = match[2].trim();
            // Skip template placeholders — matches TEMPLATE_MARKERS.STORY_TITLE
            // Strip optional trailing priority like (P1) before checking
            const titleForCheck = title.replace(/\s*\(P\d\)\s*$/, '');
            if (titleForCheck === '[Story Title]' || /^\[.+\]$/.test(titleForCheck))
                continue;
            // Extract the US section (from heading to next ### US- or ## heading)
            const usStartIndex = match.index;
            const nextUsMatch = specContent.substring(usStartIndex + match[0].length).match(/^### US-(?:[A-Z]+-)*\d+|^## /m);
            const usEndIndex = nextUsMatch
                ? usStartIndex + match[0].length + (nextUsMatch.index || 0)
                : specContent.length;
            const usSection = specContent.substring(usStartIndex, usEndIndex);
            // Extract description: text between the heading and **Acceptance Criteria** (or end of section)
            const afterHeading = usSection.substring(match[0].length);
            const acIndex = afterHeading.search(/\*\*Acceptance\s+Criteria/i);
            const descBlock = acIndex >= 0 ? afterHeading.substring(0, acIndex) : afterHeading;
            const description = descBlock
                .split('\n')
                .map(l => l.trim())
                .filter(l => l.length > 0 && !l.startsWith('**Project**:'))
                .join('\n')
                .trim() || undefined;
            const projectMatch = usSection.match(/\*\*Project\*\*:\s*(\S+)/);
            const project = projectMatch ? projectMatch[1] : undefined;
            userStories.push({ id: usId, title, description, project });
        }
        return userStories;
    }
    /**
     * Format issue title for external tools.
     * Internal increments: [FS-XXX][US-YYY] Title
     * External imports: plain title (preserve original)
     */
    formatIssueTitle(incrementInfo, usId, usTitle) {
        if (incrementInfo.origin === 'external') {
            return usTitle;
        }
        return `[${incrementInfo.featureId}][${usId}] ${usTitle}`;
    }
    formatEpicTitle(incrementInfo) {
        if (incrementInfo.origin === 'external') {
            return incrementInfo.title.replace(/^\[EXTERNAL\]\s*/, '');
        }
        if (incrementInfo.userStories.length === 1) {
            return `[${incrementInfo.featureId}][${incrementInfo.userStories[0].id}] ${incrementInfo.userStories[0].title}`;
        }
        return `[${incrementInfo.featureId}] ${incrementInfo.title}`;
    }
    /**
     * Convert ADO API URL to browser-friendly URL.
     * API: https://dev.azure.com/org/project/_apis/wit/workItems/39
     * Browser: https://dev.azure.com/org/project/_workitems/edit/39
     */
    toAdoBrowserUrl(apiUrl, workItemId) {
        // If already a browser URL, return as-is
        if (apiUrl.includes('/_workitems/edit/'))
            return apiUrl;
        // Convert API URL to browser URL
        const match = apiUrl.match(/^(https:\/\/dev\.azure\.com\/[^/]+\/[^/]+)\/_apis\/wit\/workItems\/\d+/);
        if (match) {
            return `${match[1]}/_workitems/edit/${workItemId}`;
        }
        // Fallback: return as-is
        return apiUrl;
    }
    /**
     * Check if increment already has external issue linked
     *
     * Checks:
     * 1. metadata.json github/jira/ado fields
     * 2. spec.md frontmatter external links
     */
    async checkExistingIssue(incrementId, provider) {
        const metadataPath = path.join(this.projectRoot, '.specweave/increments', incrementId, 'metadata.json');
        if (!existsSync(metadataPath)) {
            return null;
        }
        try {
            const metadata = JSON.parse(await fs.readFile(metadataPath, 'utf-8'));
            switch (provider) {
                case 'github':
                    if (metadata.github?.issue) {
                        return `#${metadata.github.issue}`;
                    }
                    if (metadata.github?.issues?.length > 0) {
                        return `#${metadata.github.issues[0].number}`;
                    }
                    break;
                case 'jira':
                    if (metadata.jira?.issue) {
                        return metadata.jira.issue;
                    }
                    if (metadata.jira?.issues?.length > 0) {
                        return metadata.jira.issues[0].key;
                    }
                    break;
                case 'ado':
                    if (metadata.ado?.workItem) {
                        return `#${metadata.ado.workItem}`;
                    }
                    if (metadata.ado?.workItems?.length > 0) {
                        return `#${metadata.ado.workItems[0].id}`;
                    }
                    break;
            }
            // FIX (v1.0.302 / 0271): Also check externalLinks format (written by SyncCoordinator v1.0.240+)
            // FIX (0610): Field names must match what backfillExternalLinks() in living-docs-sync.ts writes.
            // JIRA: epicKey (backfill) + issueKey (legacy). ADO: featureId (backfill) + workItemId (legacy).
            switch (provider) {
                case 'github':
                    if (metadata.externalLinks?.github?.issueNumber) {
                        return `#${metadata.externalLinks.github.issueNumber}`;
                    }
                    if (metadata.externalLinks?.github?.issues) {
                        const issues = metadata.externalLinks.github.issues;
                        const firstKey = Object.keys(issues)[0];
                        if (firstKey && issues[firstKey]?.issueNumber) {
                            return `#${issues[firstKey].issueNumber}`;
                        }
                    }
                    break;
                case 'jira':
                    if (metadata.externalLinks?.jira?.epicKey) {
                        return metadata.externalLinks.jira.epicKey;
                    }
                    if (metadata.externalLinks?.jira?.issueKey) {
                        return metadata.externalLinks.jira.issueKey;
                    }
                    break;
                case 'ado':
                    if (metadata.externalLinks?.ado?.featureId) {
                        return `#${metadata.externalLinks.ado.featureId}`;
                    }
                    if (metadata.externalLinks?.ado?.workItemId) {
                        return `#${metadata.externalLinks.ado.workItemId}`;
                    }
                    break;
            }
            return null;
        }
        catch (error) {
            // FIX (v1.0.302 / 0271): Log error instead of silent swallow
            this.logger.warn(`⚠️ Failed to check existing issue for ${incrementId}: ${error}`);
            return null;
        }
    }
    /**
     * Create GitHub issues for the increment
     */
    async createGitHubIssues(incrementId, incrementInfo, config) {
        try {
            // Import GitHub client dynamically to avoid circular deps
            const { GitHubClientV2 } = await import('../../plugins/specweave/lib/integrations/github/github-client-v2.js');
            // Detect repo from config
            const repoInfo = await this.detectGitHubRepo(config);
            if (!repoInfo) {
                return {
                    success: false,
                    provider: 'github',
                    error: 'GitHub repository not configured. Set sync.github.owner and sync.github.repo in config.json',
                };
            }
            const client = GitHubClientV2.fromRepo(repoInfo.owner, repoInfo.repo);
            // Check if GitHub issue already exists via API (Layer 3)
            // Search for both current format (US-XXX:) and legacy format ([FS-XXX])
            const usIds = incrementInfo.userStories.map(us => us.id);
            const legacySearch = `[${incrementInfo.featureId}]`;
            let existingIssue = await client.searchIssueByTitle(legacySearch);
            if (!existingIssue && usIds.length > 0) {
                // Try current format: search for first US ID
                existingIssue = await client.searchIssueByTitle(`${usIds[0]}:`);
            }
            if (existingIssue) {
                // Found existing issue - update metadata and return
                await this.updateMetadataWithGitHubIssue(incrementId, existingIssue.number, existingIssue.html_url);
                return {
                    success: true,
                    provider: 'github',
                    issueNumber: existingIssue.number,
                    issueUrl: existingIssue.html_url,
                    skipped: true,
                    skipReason: `Issue already exists on GitHub: #${existingIssue.number}`,
                };
            }
            // Create issues for each user story
            const createdIssues = [];
            for (const us of incrementInfo.userStories) {
                const body = this.buildGitHubIssueBody(incrementId, incrementInfo, us);
                const issueTitle = this.formatIssueTitle(incrementInfo, us.id, us.title);
                try {
                    const issue = incrementInfo.origin === 'external'
                        ? await client.createEpicIssue(issueTitle, body, undefined, ['specweave', 'auto-created'])
                        : await client.createUserStoryIssue({
                            featureId: incrementInfo.featureId,
                            userStoryId: us.id,
                            title: us.title,
                            body,
                            labels: ['specweave', 'auto-created'],
                        });
                    createdIssues.push(issue.number);
                    this.logger.log(`  ✅ Created GitHub issue #${issue.number} for ${us.id}`);
                }
                catch (error) {
                    this.logger.warn(`  ⚠️ Failed to create issue for ${us.id}: ${error}`);
                }
            }
            if (createdIssues.length === 0) {
                // Fallback: create a single feature-level issue using createEpicIssue
                const title = `[${incrementInfo.featureId}] ${incrementInfo.title}`;
                const body = this.buildFeatureLevelIssueBody(incrementId, incrementInfo);
                const issue = await client.createEpicIssue(title, body, undefined, ['specweave', 'auto-created']);
                await this.updateMetadataWithGitHubIssue(incrementId, issue.number, issue.html_url);
                return {
                    success: true,
                    provider: 'github',
                    issueNumber: issue.number,
                    issueUrl: issue.html_url,
                };
            }
            // Update metadata with first created issue
            await this.updateMetadataWithGitHubIssue(incrementId, createdIssues[0], `https://github.com/${repoInfo.owner}/${repoInfo.repo}/issues/${createdIssues[0]}`);
            return {
                success: true,
                provider: 'github',
                issueNumber: createdIssues[0],
                issueUrl: `https://github.com/${repoInfo.owner}/${repoInfo.repo}/issues/${createdIssues[0]}`,
            };
        }
        catch (error) {
            const errorMessage = error instanceof Error ? error.message : String(error);
            return {
                success: false,
                provider: 'github',
                error: errorMessage,
            };
        }
    }
    /**
     * Create JIRA issues for the increment
     */
    async createJiraIssues(incrementId, incrementInfo, config, overrideProjectKey) {
        try {
            // Import JIRA client dynamically
            const { JiraClient } = await import('../integrations/jira/jira-client.js');
            const jiraConfig = config.issueTracker || config.sync?.jira || {};
            const domain = jiraConfig.domain;
            const projectKey = overrideProjectKey || jiraConfig.projects?.[0]?.key || jiraConfig.projectKey;
            if (!domain || !projectKey) {
                return {
                    success: false,
                    provider: 'jira',
                    error: 'JIRA not configured. Set issueTracker.domain and issueTracker.projects in config.json',
                };
            }
            const client = new JiraClient();
            // Create Epic for the feature
            const epicSummary = this.formatEpicTitle(incrementInfo);
            const epicDescription = this.buildJiraEpicDescription(incrementId, incrementInfo);
            const epic = await client.createIssue({
                issueType: 'Epic',
                summary: epicSummary,
                description: epicDescription,
                labels: ['specweave', 'auto-created'],
            }, projectKey);
            this.logger.log(`✅ Created JIRA Epic: ${epic.key}`);
            // Create per-user-story Story issues under the Epic (for multi-story increments)
            const storyKeyMap = {};
            if (incrementInfo.userStories.length > 1) {
                for (const us of incrementInfo.userStories) {
                    try {
                        const storySummary = this.formatIssueTitle(incrementInfo, us.id, us.title);
                        const storyDesc = us.description
                            ? `${us.description}\n\n---\n*Auto-created by SpecWeave*`
                            : `User story for ${us.title}\n\n---\n*Auto-created by SpecWeave*`;
                        const story = await client.createIssue({
                            issueType: 'Story',
                            summary: storySummary,
                            description: storyDesc,
                            labels: ['specweave', 'auto-created', 'user-story'],
                            parentKey: epic.key,
                        }, projectKey);
                        storyKeyMap[us.id] = {
                            issueKey: story.key,
                            issueUrl: `https://${domain}/browse/${story.key}`,
                        };
                        this.logger.log(`  ✅ Created JIRA Story: ${story.key} (${us.id})`);
                    }
                    catch (storyErr) {
                        this.logger.log(`  ⚠️ Failed to create JIRA Story for ${us.id}: ${storyErr instanceof Error ? storyErr.message : String(storyErr)}`);
                    }
                }
            }
            else if (incrementInfo.userStories.length === 1) {
                // Single-story: the epic IS the user story issue
                storyKeyMap[incrementInfo.userStories[0].id] = {
                    issueKey: epic.key,
                    issueUrl: `https://${domain}/browse/${epic.key}`,
                };
            }
            // Update metadata with JIRA issue + per-US story keys
            const issueUrl = `https://${domain}/browse/${epic.key}`;
            await this.updateMetadataWithJiraIssue(incrementId, epic.key, issueUrl, incrementInfo.userStories, storyKeyMap);
            return {
                success: true,
                provider: 'jira',
                issueNumber: epic.key,
                issueUrl: `https://${domain}/browse/${epic.key}`,
            };
        }
        catch (error) {
            const errorMessage = error instanceof Error ? error.message : String(error);
            return {
                success: false,
                provider: 'jira',
                error: errorMessage,
            };
        }
    }
    /**
     * Create Azure DevOps work items for the increment
     */
    async createAdoIssues(incrementId, incrementInfo, config, overrideProject) {
        try {
            const { AdoClient } = await import('../integrations/ado/ado-client.js');
            const { getAdoPat } = await import('../integrations/ado/ado-pat-provider.js');
            // Prioritize sync.ado config over legacy issueTracker
            const organization = config.sync?.ado?.organization || config.issueTracker?.organization_ado || config.issueTracker?.organization;
            const project = overrideProject || config.sync?.ado?.project || config.issueTracker?.project;
            if (!organization || !project) {
                return {
                    success: false,
                    provider: 'ado',
                    error: 'Azure DevOps not configured. Set issueTracker.organization_ado and issueTracker.project in config.json',
                };
            }
            const pat = getAdoPat(organization);
            if (!pat) {
                return {
                    success: false,
                    provider: 'ado',
                    error: 'AZURE_DEVOPS_PAT not set in environment',
                };
            }
            const client = new AdoClient({
                pat,
                organization,
                project,
            });
            // Create Feature work item
            const title = this.formatEpicTitle(incrementInfo);
            const description = this.buildAdoDescription(incrementId, incrementInfo);
            // Try "Feature" first (Agile/Scrum), fall back to "Epic" (Basic process)
            let workItem;
            try {
                workItem = await client.createWorkItem({
                    workItemType: 'Feature',
                    title,
                    description,
                    tags: ['specweave', 'auto-created'],
                });
            }
            catch (featureErr) {
                const msg = featureErr instanceof Error ? featureErr.message : String(featureErr);
                if (msg.includes('WorkItemTypeNotFoundException') || msg.includes('does not exist')) {
                    this.logger.log(`   ℹ️  "Feature" type not available, using "Epic" (Basic process)`);
                    workItem = await client.createWorkItem({
                        workItemType: 'Epic',
                        title,
                        description,
                        tags: ['specweave', 'auto-created'],
                    });
                }
                else {
                    throw featureErr;
                }
            }
            this.logger.log(`✅ Created ADO work item: #${workItem.id}`);
            // Create per-user-story User Story work items under the Feature (multi-story)
            const storyItemMap = {};
            if (incrementInfo.userStories.length > 1) {
                for (const us of incrementInfo.userStories) {
                    try {
                        const usTitle = this.formatIssueTitle(incrementInfo, us.id, us.title);
                        const usDesc = us.description || `User story for ${us.title}`;
                        // Try "User Story" first (Agile/Scrum), fall back to "Issue" (Basic process)
                        let usItem;
                        try {
                            usItem = await client.createWorkItem({
                                workItemType: 'User Story',
                                title: usTitle,
                                description: usDesc,
                                tags: ['specweave', 'auto-created', 'user-story'],
                                parentId: workItem.id,
                            });
                        }
                        catch (typeErr) {
                            const typeMsg = typeErr instanceof Error ? typeErr.message : String(typeErr);
                            if (typeMsg.includes('WorkItemTypeNotFoundException') || typeMsg.includes('does not exist')) {
                                usItem = await client.createWorkItem({
                                    workItemType: 'Issue',
                                    title: usTitle,
                                    description: usDesc,
                                    tags: ['specweave', 'auto-created', 'user-story'],
                                    parentId: workItem.id,
                                });
                            }
                            else {
                                throw typeErr;
                            }
                        }
                        storyItemMap[us.id] = {
                            workItemId: usItem.id,
                            workItemUrl: this.toAdoBrowserUrl(usItem.url, usItem.id),
                        };
                        this.logger.log(`  ✅ Created ADO User Story: #${usItem.id} (${us.id})`);
                    }
                    catch (usErr) {
                        this.logger.log(`  ⚠️ Failed to create ADO User Story for ${us.id}: ${usErr instanceof Error ? usErr.message : String(usErr)}`);
                    }
                }
            }
            else if (incrementInfo.userStories.length === 1) {
                storyItemMap[incrementInfo.userStories[0].id] = {
                    workItemId: workItem.id,
                    workItemUrl: this.toAdoBrowserUrl(workItem.url, workItem.id),
                };
            }
            const browserUrl = this.toAdoBrowserUrl(workItem.url, workItem.id);
            // Update metadata with ADO work item + per-US story keys
            await this.updateMetadataWithAdoWorkItem(incrementId, workItem.id, browserUrl, incrementInfo.userStories, storyItemMap);
            return {
                success: true,
                provider: 'ado',
                issueNumber: workItem.id,
                issueUrl: browserUrl,
            };
        }
        catch (error) {
            const errorMessage = error instanceof Error ? error.message : String(error);
            return {
                success: false,
                provider: 'ado',
                error: errorMessage,
            };
        }
    }
    /**
     * Detect GitHub repo from config
     */
    async detectGitHubRepo(config) {
        // Check sync.github config
        if (config.sync?.github?.owner && config.sync?.github?.repo) {
            return {
                owner: config.sync.github.owner,
                repo: config.sync.github.repo,
            };
        }
        // Check profiles
        const profiles = config.sync?.profiles || {};
        for (const profile of Object.values(profiles)) {
            if (profile.provider === 'github' && profile.config?.owner && profile.config?.repo) {
                return {
                    owner: profile.config.owner,
                    repo: profile.config.repo,
                };
            }
        }
        // Check issueTracker
        if (config.issueTracker?.owner && config.issueTracker?.repo) {
            return {
                owner: config.issueTracker.owner,
                repo: config.issueTracker.repo,
            };
        }
        // Try git remote detection
        try {
            const { execSync } = await import('child_process');
            const remoteUrl = execSync('git remote get-url origin', {
                cwd: this.projectRoot,
                encoding: 'utf-8',
                stdio: 'pipe'
            }).trim();
            // Parse GitHub URL
            const match = remoteUrl.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
            if (match) {
                return {
                    owner: match[1],
                    repo: match[2],
                };
            }
        }
        catch {
            // Git detection failed
        }
        return null;
    }
    /**
     * Build GitHub issue body for a user story
     */
    buildGitHubIssueBody(incrementId, incrementInfo, us) {
        const descSection = us.description ? `\n${us.description}\n` : '';
        return `## ${us.title}
${descSection}
**Feature**: ${incrementInfo.featureId}
**Increment**: ${incrementId}
**User Story**: ${us.id}
${us.project ? `**Project**: ${us.project}` : ''}

---

📋 See [\`spec.md\`](../../tree/${this.getDefaultBranch()}/.specweave/increments/${incrementId}/spec.md) for full acceptance criteria.

---

🤖 Auto-created by SpecWeave | Updates automatically on task completion
`;
    }
    /**
     * Build GitHub issue body for feature-level issue
     */
    buildFeatureLevelIssueBody(incrementId, incrementInfo) {
        const userStoriesList = incrementInfo.userStories
            .map((us) => `- [ ] ${us.id}: ${us.title}`)
            .join('\n');
        return `## ${incrementInfo.title}

**Feature**: ${incrementInfo.featureId}
**Status**: ${incrementInfo.status}

### User Stories

${userStoriesList || '_No user stories defined_'}

### Links

- **Spec**: [\`spec.md\`](../../tree/${this.getDefaultBranch()}/.specweave/increments/${incrementId}/spec.md)
- **Tasks**: [\`tasks.md\`](../../tree/${this.getDefaultBranch()}/.specweave/increments/${incrementId}/tasks.md)

---

🤖 Auto-created by SpecWeave | Updates automatically on task completion
`;
    }
    /**
     * Build JIRA Epic description
     */
    buildJiraEpicDescription(incrementId, incrementInfo) {
        const userStoriesList = incrementInfo.userStories
            .map((us) => {
            const desc = us.description ? `\n  ${us.description}` : '';
            return `- ${us.id}: ${us.title}${desc}`;
        })
            .join('\n');
        return `${incrementInfo.title}

Feature: ${incrementInfo.featureId}
Increment: ${incrementId}

User Stories
${userStoriesList || '(No user stories defined)'}

---
Auto-created by SpecWeave
`;
    }
    /**
     * Build ADO work item description
     */
    buildAdoDescription(incrementId, incrementInfo) {
        const userStoriesList = incrementInfo.userStories
            .map((us) => {
            const desc = us.description ? `<br/><em>${us.description}</em>` : '';
            return `<li>${us.id}: ${us.title}${desc}</li>`;
        })
            .join('\n');
        return `<h2>${incrementInfo.title}</h2>

<p><strong>Feature</strong>: ${incrementInfo.featureId}<br/>
<strong>Increment</strong>: ${incrementId}</p>

<h3>User Stories</h3>
<ul>
${userStoriesList || '<li><em>No user stories defined</em></li>'}
</ul>

<hr/>
<p>🤖 Auto-created by SpecWeave</p>
`;
    }
    /**
     * Update metadata.json with GitHub issue
     */
    async updateMetadataWithGitHubIssue(incrementId, issueNumber, issueUrl) {
        const metadataPath = path.join(this.projectRoot, '.specweave/increments', incrementId, 'metadata.json');
        let metadata = {};
        if (existsSync(metadataPath)) {
            metadata = JSON.parse(await fs.readFile(metadataPath, 'utf-8'));
        }
        metadata.github = {
            issue: issueNumber,
            url: issueUrl,
            synced: new Date().toISOString(),
        };
        await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2), 'utf-8');
    }
    /**
     * Update metadata.json with JIRA issue
     */
    async updateMetadataWithJiraIssue(incrementId, issueKey, issueUrl, userStories, storyKeyMap) {
        const metadataPath = path.join(this.projectRoot, '.specweave/increments', incrementId, 'metadata.json');
        let metadata = {};
        if (existsSync(metadataPath)) {
            metadata = JSON.parse(await fs.readFile(metadataPath, 'utf-8'));
        }
        const now = new Date().toISOString();
        metadata.jira = {
            issue: issueKey,
            url: issueUrl,
            synced: now,
        };
        // Populate externalLinks.jira with epic-level and per-story JIRA keys
        if (!metadata.externalLinks)
            metadata.externalLinks = {};
        if (!metadata.externalLinks.jira)
            metadata.externalLinks.jira = {};
        metadata.externalLinks.jira.epicKey = issueKey;
        metadata.externalLinks.jira.epicUrl = issueUrl;
        metadata.externalLinks.jira.syncedAt = now;
        if (userStories && userStories.length > 0) {
            if (!metadata.externalLinks.jira.userStories)
                metadata.externalLinks.jira.userStories = {};
            for (const us of userStories) {
                const storyInfo = storyKeyMap?.[us.id];
                metadata.externalLinks.jira.userStories[us.id] = {
                    issueKey: storyInfo?.issueKey || issueKey,
                    issueUrl: storyInfo?.issueUrl || issueUrl,
                    syncedAt: now,
                };
            }
        }
        await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2), 'utf-8');
    }
    /**
     * Update metadata.json with ADO work item
     */
    async updateMetadataWithAdoWorkItem(incrementId, workItemId, workItemUrl, userStories, storyItemMap) {
        const metadataPath = path.join(this.projectRoot, '.specweave/increments', incrementId, 'metadata.json');
        let metadata = {};
        if (existsSync(metadataPath)) {
            metadata = JSON.parse(await fs.readFile(metadataPath, 'utf-8'));
        }
        const now = new Date().toISOString();
        metadata.ado = {
            workItem: workItemId,
            url: workItemUrl,
            synced: now,
        };
        // Populate externalLinks.ado with feature-level and per-story work item IDs
        if (!metadata.externalLinks)
            metadata.externalLinks = {};
        if (!metadata.externalLinks.ado)
            metadata.externalLinks.ado = {};
        metadata.externalLinks.ado.featureId = workItemId.toString();
        metadata.externalLinks.ado.featureUrl = workItemUrl;
        metadata.externalLinks.ado.syncedAt = now;
        if (userStories && userStories.length > 0) {
            if (!metadata.externalLinks.ado.userStories)
                metadata.externalLinks.ado.userStories = {};
            for (const us of userStories) {
                const storyInfo = storyItemMap?.[us.id];
                if (!storyInfo?.workItemId) {
                    this.logger.warn(`⚠️  Skipping ADO metadata for ${us.id}: story was not synced`);
                    continue;
                }
                metadata.externalLinks.ado.userStories[us.id] = {
                    workItemId: storyInfo.workItemId,
                    workItemUrl: storyInfo.workItemUrl,
                    syncedAt: now,
                };
            }
        }
        await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2), 'utf-8');
    }
}
/**
 * Convenience function to auto-create issues for an increment
 *
 * @param targetProvider - Optional: force creation for a specific provider.
 *   Without this, detectProvider() picks the highest-priority enabled provider.
 *   Use this when you know which provider you want (e.g., JIRA section of sync-progress).
 */
export async function autoCreateExternalIssue(projectRoot, incrementId, logger, targetProvider) {
    const creator = new ExternalIssueAutoCreator({
        projectRoot,
        logger,
    });
    return creator.createForIncrement(incrementId, targetProvider);
}
//# sourceMappingURL=external-issue-auto-creator.js.map