import { StorageManager } from '../../storage/storage-manager.js';
import { promises as fs } from 'fs';
import path from 'path';
export class AgileManager {
    configManager;
    storageManager;
    agileDataPath;
    sprints;
    stories;
    epics;
    standups;
    retrospectives;
    constructor(configManager) {
        this.configManager = configManager;
        this.storageManager = new StorageManager();
        this.agileDataPath = '';
        this.sprints = new Map();
        this.stories = new Map();
        this.epics = new Map();
        this.standups = [];
        this.retrospectives = [];
    }
    async initialize() {
        const location = await this.storageManager.getStorageLocation();
        this.agileDataPath = path.join(location.data, 'agile');
        await this.loadExistingData();
        // Create sample data only in development mode and if explicitly enabled
        const isDev = process.env.NODE_ENV === 'development' || process.env.ATLAS_DEV === 'true';
        const shouldCreateSampleData = process.env.ATLAS_CREATE_SAMPLE_DATA === 'true';
        if ((isDev || shouldCreateSampleData) && this.sprints.size === 0 && this.stories.size === 0 && this.epics.size === 0) {
            console.log('🔧 Development mode detected - creating sample agile data for dashboard testing');
            await this.createSampleData();
        }
    }
    async loadExistingData() {
        try {
            // Load sprints
            const sprintsPath = path.join(this.agileDataPath, 'sprints.json');
            if (await this.fileExists(sprintsPath)) {
                const sprintsData = JSON.parse(await fs.readFile(sprintsPath, 'utf-8'));
                sprintsData.forEach((sprint) => {
                    sprint.startDate = new Date(sprint.startDate);
                    sprint.endDate = new Date(sprint.endDate);
                    this.sprints.set(sprint.id, sprint);
                });
            }
            // Load stories
            const storiesPath = path.join(this.agileDataPath, 'stories.json');
            if (await this.fileExists(storiesPath)) {
                const storiesData = JSON.parse(await fs.readFile(storiesPath, 'utf-8'));
                storiesData.forEach((story) => {
                    story.createdAt = new Date(story.createdAt);
                    story.updatedAt = new Date(story.updatedAt);
                    // Initialize required arrays if undefined
                    story.acceptanceCriteria = story.acceptanceCriteria || [];
                    story.tags = story.tags || [];
                    story.dependencies = story.dependencies || [];
                    story.watchers = story.watchers || [];
                    // Initialize required objects
                    story.timeTracking = story.timeTracking || {
                        estimated: 0,
                        actual: 0,
                        remaining: 0,
                        logs: []
                    };
                    story.attachments = story.attachments || [];
                    story.comments = story.comments || [];
                    story.customFields = story.customFields || [];
                    story.subtasks = story.subtasks || [];
                    // Convert optional dates
                    if (story.dueDate)
                        story.dueDate = new Date(story.dueDate);
                    if (story.startDate)
                        story.startDate = new Date(story.startDate);
                    if (story.completedAt)
                        story.completedAt = new Date(story.completedAt);
                    this.stories.set(story.id, story);
                });
            }
            // Load epics
            const epicsPath = path.join(this.agileDataPath, 'epics.json');
            if (await this.fileExists(epicsPath)) {
                const epicsData = JSON.parse(await fs.readFile(epicsPath, 'utf-8'));
                epicsData.forEach((epic) => {
                    epic.createdAt = new Date(epic.createdAt);
                    epic.updatedAt = new Date(epic.updatedAt);
                    this.epics.set(epic.id, epic);
                });
            }
            // Load standups
            const standupsPath = path.join(this.agileDataPath, 'standups.json');
            if (await this.fileExists(standupsPath)) {
                const standupsData = JSON.parse(await fs.readFile(standupsPath, 'utf-8'));
                this.standups = standupsData.map((standup) => ({
                    ...standup,
                    date: new Date(standup.date)
                }));
            }
            // Load retrospectives
            const retrospectivesPath = path.join(this.agileDataPath, 'retrospectives.json');
            if (await this.fileExists(retrospectivesPath)) {
                const retrospectivesData = JSON.parse(await fs.readFile(retrospectivesPath, 'utf-8'));
                this.retrospectives = retrospectivesData.map((retro) => ({
                    ...retro,
                    date: new Date(retro.date)
                }));
            }
        }
        catch (error) {
            console.error('Error loading agile data:', error);
        }
        // Validate and repair data integrity after loading
        const validationResult = await this.validateAndRepairDataIntegrity();
        if (!validationResult.valid && validationResult.repairs.length > 0) {
            console.log('🔧 Agile data integrity repairs performed:');
            validationResult.repairs.forEach(repair => console.log(`  - ${repair}`));
        }
    }
    async validateAndRepairDataIntegrity() {
        const repairs = [];
        let dataModified = false;
        // Check data version to determine repair strategy
        const dataVersion = await this.getDataVersion();
        const needsEpicSync = this.compareVersions(dataVersion, '1.0.19') < 0;
        if (needsEpicSync) {
            repairs.push(`Detected data version ${dataVersion} < 1.0.19 - performing epic-story relationship migration`);
        }
        // Step 1: Build a map of valid epic IDs
        const validEpicIds = new Set(this.epics.keys());
        // Step 2: Validate and repair story-epic relationships
        if (!needsEpicSync) {
            // For v1.0.19+ data: Only remove truly invalid epic references
            for (const [storyId, story] of this.stories.entries()) {
                if (story.epic && !validEpicIds.has(story.epic)) {
                    repairs.push(`Story ${storyId} had invalid epic reference ${story.epic} - removed`);
                    story.epic = undefined;
                    story.updatedAt = new Date();
                    dataModified = true;
                }
            }
        }
        // For pre-v1.0.19 data: Keep all epic references that point to valid epics
        // Step 3: Rebuild epic.storyIds arrays from story references
        if (needsEpicSync) {
            // For pre-v1.0.19 data: Always rebuild epic.storyIds from story references
            repairs.push('Rebuilding all epic.storyIds arrays from story references');
            // Clear all epic.storyIds arrays first
            for (const [epicId, epic] of this.epics.entries()) {
                epic.storyIds = [];
                epic.updatedAt = new Date();
            }
            // Rebuild from story references
            for (const [storyId, story] of this.stories.entries()) {
                if (story.epic && this.epics.has(story.epic)) {
                    const epic = this.epics.get(story.epic);
                    epic.storyIds.push(storyId);
                    repairs.push(`  - Added story ${storyId} to epic ${story.epic}`);
                }
            }
            dataModified = true;
        }
        else {
            // For v1.0.19+ data: Only fix discrepancies
            const epicStoryMap = new Map();
            // Initialize empty arrays for all epics
            for (const epicId of validEpicIds) {
                epicStoryMap.set(epicId, []);
            }
            // Collect all valid story-epic relationships
            for (const [storyId, story] of this.stories.entries()) {
                if (story.epic && validEpicIds.has(story.epic)) {
                    epicStoryMap.get(story.epic).push(storyId);
                }
            }
            // Update epic.storyIds arrays only if they differ
            for (const [epicId, epic] of this.epics.entries()) {
                const correctStoryIds = epicStoryMap.get(epicId) || [];
                const currentStoryIds = epic.storyIds || [];
                // Check if arrays differ
                if (JSON.stringify(correctStoryIds.sort()) !== JSON.stringify(currentStoryIds.sort())) {
                    repairs.push(`Epic ${epicId} storyIds updated: ${currentStoryIds.length} -> ${correctStoryIds.length} stories`);
                    epic.storyIds = correctStoryIds;
                    epic.updatedAt = new Date();
                    dataModified = true;
                }
            }
        }
        // Step 4: Validate sprint-story relationships
        const validSprintIds = new Set(this.sprints.keys());
        for (const [storyId, story] of this.stories.entries()) {
            if (story.sprintId && !validSprintIds.has(story.sprintId)) {
                repairs.push(`Story ${storyId} had invalid sprint reference ${story.sprintId} - removed`);
                story.sprintId = undefined;
                story.updatedAt = new Date();
                dataModified = true;
            }
        }
        // Save changes if any repairs were made
        if (dataModified) {
            await Promise.all([
                this.saveStories(),
                this.saveEpics(),
                this.saveSprints()
            ]);
            // Update version if we performed a migration
            if (needsEpicSync) {
                await this.updateDataVersion('1.0.19');
                repairs.push('Updated data version to 1.0.19');
            }
        }
        return {
            valid: repairs.length === 0,
            repairs
        };
    }
    async fileExists(filePath) {
        try {
            await fs.access(filePath);
            return true;
        }
        catch {
            return false;
        }
    }
    async createSprint(options) {
        const startDate = options.startDate || new Date();
        const sprint = {
            id: `sprint-${this.generateId()}`,
            name: options.name,
            goal: options.goal,
            status: 'planning',
            startDate: startDate,
            endDate: new Date(startDate.getTime() + (options.duration * 24 * 60 * 60 * 1000)),
            duration: options.duration,
            team: options.team || [],
            storyIds: [],
            capacity: 0,
            velocity: 0,
            burndownData: [],
        };
        this.sprints.set(sprint.id, sprint);
        // Add initial stories if provided
        if (options.initialStoryIds && options.initialStoryIds.length > 0) {
            for (const storyId of options.initialStoryIds) {
                try {
                    await this.addStoryToSprint(storyId, sprint.id);
                }
                catch (error) {
                    console.error(`Failed to add story ${storyId} to sprint: ${error}`);
                }
            }
        }
        await this.saveSprints();
        return sprint;
    }
    async addStory(options) {
        // Handle epicId alias
        const epicId = options.epicId || options.epic;
        // Convert legacy string acceptanceCriteria to structured format
        let acceptanceCriteria = [];
        if (options.acceptanceCriteria) {
            if (typeof options.acceptanceCriteria[0] === 'string') {
                // Legacy format - convert strings to structured criteria
                acceptanceCriteria = options.acceptanceCriteria.map((desc, idx) => ({
                    id: `ac-${Date.now()}-${idx}`,
                    description: desc,
                    status: 'pending',
                }));
            }
            else {
                // New structured format
                acceptanceCriteria = options.acceptanceCriteria;
            }
        }
        const story = {
            id: `story-${this.generateId()}`,
            title: options.title,
            description: options.description,
            status: options.sprintId ? 'todo' : 'backlog',
            priority: options.priority || 'medium',
            storyPoints: options.storyPoints || 0,
            acceptanceCriteria: acceptanceCriteria,
            acceptanceCriteriaLegacy: typeof options.acceptanceCriteria?.[0] === 'string'
                ? options.acceptanceCriteria
                : undefined,
            assignee: options.assignee,
            epic: epicId,
            sprintId: options.sprintId,
            createdAt: new Date(),
            updatedAt: new Date(),
            hoursSpent: 0,
            tags: options.tags || [],
            // Initialize new enhanced properties
            dependencies: options.dependencies || [],
            timeTracking: options.timeEstimate ? {
                estimated: options.timeEstimate,
                actual: 0,
                remaining: options.timeEstimate,
                logs: []
            } : {
                estimated: 0,
                actual: 0,
                remaining: 0,
                logs: []
            },
            attachments: [],
            comments: [],
            customFields: [],
            subtasks: [],
            watchers: options.watchers || [],
            groomedWithUserFeedback: options.groomedWithUserFeedback ?? false,
        };
        this.stories.set(story.id, story);
        // Add to sprint if specified
        if (options.sprintId && this.sprints.has(options.sprintId)) {
            const sprint = this.sprints.get(options.sprintId);
            sprint.storyIds.push(story.id);
            this.sprints.set(sprint.id, sprint);
            await this.saveSprints();
        }
        // Add to epic if specified
        if (epicId && this.epics.has(epicId)) {
            const epic = this.epics.get(epicId);
            if (!epic.storyIds.includes(story.id)) {
                epic.storyIds.push(story.id);
                epic.updatedAt = new Date();
                this.epics.set(epic.id, epic);
                await this.saveEpics();
            }
        }
        await this.saveStories();
        // Return with epicId for API compatibility
        return { ...story, epicId: story.epic };
    }
    async createEpic(options) {
        const epic = {
            id: `epic-${this.generateId()}`,
            title: options.title,
            description: options.description,
            status: 'planned',
            priority: options.priority || 'medium',
            goals: options.goals || [],
            owner: options.owner,
            createdAt: new Date(),
            updatedAt: new Date(),
            storyIds: [],
            stories: [],
            progress: 0,
        };
        this.epics.set(epic.id, epic);
        // Handle stories if provided
        const storiesData = options.stories;
        if (storiesData && Array.isArray(storiesData)) {
            const createdStories = [];
            let totalPoints = 0;
            for (const storyData of storiesData) {
                const story = await this.addStory({
                    title: storyData.title,
                    description: storyData.description,
                    storyPoints: storyData.storyPoints || 0,
                    priority: storyData.priority || 'medium',
                    acceptanceCriteria: storyData.acceptanceCriteria || [],
                    epic: epic.id,
                });
                epic.storyIds.push(story.id);
                createdStories.push(story);
                totalPoints += story.storyPoints;
            }
            // Return epic with stories array populated
            const epicWithStories = { ...epic, stories: createdStories, totalStoryPoints: totalPoints };
            await this.saveEpics();
            return epicWithStories;
        }
        await this.saveEpics();
        return epic;
    }
    async saveEpic(epic) {
        this.epics.set(epic.id, epic);
        await this.saveEpics();
    }
    async updateEpic(epicId, updates) {
        if (!this.epics.has(epicId)) {
            throw new Error(`Epic ${epicId} not found`);
        }
        const epic = this.epics.get(epicId);
        // Update basic properties
        if (updates.title !== undefined)
            epic.title = updates.title;
        if (updates.description !== undefined)
            epic.description = updates.description;
        if (updates.status !== undefined)
            epic.status = updates.status;
        if (updates.priority !== undefined)
            epic.priority = updates.priority;
        if (updates.goals !== undefined)
            epic.goals = updates.goals;
        if (updates.owner !== undefined)
            epic.owner = updates.owner;
        // Update metadata if provided
        if (updates.metadata !== undefined) {
            epic.metadata = {
                ...epic.metadata,
                ...updates.metadata
            };
        }
        // Update timestamp
        epic.updatedAt = new Date();
        // Recalculate progress based on story statuses
        if (epic.storyIds.length > 0) {
            const stories = epic.storyIds
                .map(id => this.stories.get(id))
                .filter(story => story !== undefined);
            const totalStories = stories.length;
            const completedStories = stories.filter(s => s.status === 'done').length;
            epic.progress = totalStories > 0 ? Math.round((completedStories / totalStories) * 100) : 0;
        }
        this.epics.set(epic.id, epic);
        await this.saveEpics();
        return epic;
    }
    async updateSprint(sprintId, updates) {
        if (!this.sprints.has(sprintId)) {
            throw new Error(`Sprint ${sprintId} not found`);
        }
        const sprint = this.sprints.get(sprintId);
        // Update basic properties
        if (updates.name !== undefined)
            sprint.name = updates.name;
        if (updates.goal !== undefined)
            sprint.goal = updates.goal;
        if (updates.status !== undefined)
            sprint.status = updates.status;
        if (updates.endDate !== undefined)
            sprint.endDate = updates.endDate;
        if (updates.team !== undefined)
            sprint.team = updates.team;
        if (updates.capacity !== undefined)
            sprint.capacity = updates.capacity;
        // Recalculate duration if end date changed
        if (updates.endDate !== undefined) {
            sprint.duration = Math.ceil((sprint.endDate.getTime() - sprint.startDate.getTime()) / (1000 * 60 * 60 * 24));
        }
        // Recalculate velocity if sprint is completed
        if (sprint.status === 'completed') {
            const sprintStories = sprint.storyIds
                .map(id => this.stories.get(id))
                .filter(story => story !== undefined);
            sprint.velocity = sprintStories
                .filter(story => story.status === 'done')
                .reduce((sum, story) => sum + story.storyPoints, 0);
        }
        this.sprints.set(sprint.id, sprint);
        await this.saveSprints();
        return sprint;
    }
    async addStoryToSprint(storyId, sprintId) {
        const story = this.stories.get(storyId);
        const sprint = this.sprints.get(sprintId);
        if (!story) {
            throw new Error(`Story ${storyId} not found`);
        }
        if (!sprint) {
            throw new Error(`Sprint ${sprintId} not found`);
        }
        // Update story
        story.sprintId = sprintId;
        story.updatedAt = new Date();
        this.stories.set(story.id, story);
        // Update sprint
        if (!sprint.storyIds.includes(storyId)) {
            sprint.storyIds.push(storyId);
            this.sprints.set(sprint.id, sprint);
        }
        // Save both
        await Promise.all([this.saveStories(), this.saveSprints()]);
    }
    async conductSprintPlanning(options) {
        if (!this.sprints.has(options.sprintId)) {
            throw new Error(`Sprint ${options.sprintId} not found`);
        }
        const sprint = this.sprints.get(options.sprintId);
        const planning = {
            id: `planning-${Date.now()}`,
            sprintId: options.sprintId,
            date: new Date(),
            attendees: options.attendees,
            capacity: options.capacity || 0,
            commitments: options.commitments,
            notes: '',
            outcome: 'ready',
        };
        // Update sprint with planning results
        sprint.capacity = planning.capacity;
        sprint.storyIds = [...new Set([...sprint.storyIds, ...planning.commitments])];
        sprint.status = 'active';
        this.sprints.set(sprint.id, sprint);
        await this.saveSprints();
        return planning;
    }
    async recordStandup(options) {
        const standup = {
            id: `standup-${Date.now()}`,
            sprintId: options.sprintId,
            attendee: options.attendee,
            date: new Date(),
            yesterday: options.yesterday,
            today: options.today,
            blockers: options.blockers,
        };
        this.standups.push(standup);
        await this.saveStandups();
        return standup;
    }
    async conductRetrospective(options) {
        if (!this.sprints.has(options.sprintId)) {
            throw new Error(`Sprint ${options.sprintId} not found`);
        }
        const retrospective = {
            id: `retro-${Date.now()}`,
            sprintId: options.sprintId,
            date: new Date(),
            attendees: options.attendees,
            whatWentWell: options.whatWentWell,
            whatCanImprove: options.whatCanImprove,
            actionItems: options.actionItems,
            sprintRating: 7, // Default rating
        };
        this.retrospectives.push(retrospective);
        await this.saveRetrospectives();
        // Mark sprint as completed
        const sprint = this.sprints.get(options.sprintId);
        sprint.status = 'completed';
        this.sprints.set(sprint.id, sprint);
        await this.saveSprints();
        return retrospective;
    }
    async updateStoryStatus(storyId, updates) {
        if (!this.stories.has(storyId)) {
            throw new Error(`Story ${storyId} not found`);
        }
        const story = this.stories.get(storyId);
        // Validate status transition to in_progress
        if (updates.status === 'in_progress' && !story.groomedWithUserFeedback) {
            throw new Error('Story must be groomed with user feedback before moving to in_progress');
        }
        // Update basic properties
        if (updates.status !== undefined)
            story.status = updates.status;
        if (updates.title !== undefined)
            story.title = updates.title;
        if (updates.description !== undefined)
            story.description = updates.description;
        if (updates.priority !== undefined)
            story.priority = updates.priority;
        if (updates.storyPoints !== undefined)
            story.storyPoints = updates.storyPoints;
        if (updates.assignee !== undefined)
            story.assignee = updates.assignee;
        if (updates.tags !== undefined)
            story.tags = updates.tags;
        if (updates.groomedWithUserFeedback !== undefined)
            story.groomedWithUserFeedback = updates.groomedWithUserFeedback;
        // Handle epic changes
        if (updates.epic !== undefined) {
            const oldEpicId = story.epic;
            const newEpicId = updates.epic;
            // Remove from old epic if it exists
            if (oldEpicId && this.epics.has(oldEpicId)) {
                const oldEpic = this.epics.get(oldEpicId);
                oldEpic.storyIds = oldEpic.storyIds.filter(id => id !== storyId);
                oldEpic.updatedAt = new Date();
                this.epics.set(oldEpicId, oldEpic);
            }
            // Add to new epic if it exists
            if (newEpicId && this.epics.has(newEpicId)) {
                const newEpic = this.epics.get(newEpicId);
                if (!newEpic.storyIds.includes(storyId)) {
                    newEpic.storyIds.push(storyId);
                    newEpic.updatedAt = new Date();
                    this.epics.set(newEpicId, newEpic);
                }
                story.epic = newEpicId;
            }
            else if (newEpicId) {
                throw new Error(`Epic ${newEpicId} not found`);
            }
            else {
                story.epic = undefined;
            }
            await this.saveEpics();
        }
        // Handle acceptance criteria updates
        if (updates.acceptanceCriteria !== undefined) {
            if (typeof updates.acceptanceCriteria[0] === 'string') {
                // Legacy format - convert strings to structured criteria
                story.acceptanceCriteria = updates.acceptanceCriteria.map((desc, idx) => ({
                    id: `ac-${Date.now()}-${idx}`,
                    description: desc,
                    status: 'pending',
                }));
                story.acceptanceCriteriaLegacy = updates.acceptanceCriteria;
            }
            else {
                // New structured format
                story.acceptanceCriteria = updates.acceptanceCriteria;
            }
        }
        // Update enhanced properties
        if (updates.dependencies !== undefined)
            story.dependencies = updates.dependencies;
        if (updates.timeTracking !== undefined) {
            story.timeTracking = { ...story.timeTracking, ...updates.timeTracking };
        }
        // Handle time tracking
        if (updates.hoursSpent !== undefined) {
            story.hoursSpent = updates.hoursSpent;
            if (!story.timeTracking) {
                story.timeTracking = {
                    estimated: 0,
                    actual: updates.hoursSpent,
                    remaining: 0,
                    logs: []
                };
            }
            else {
                story.timeTracking.actual = updates.hoursSpent;
            }
        }
        story.updatedAt = new Date();
        this.stories.set(storyId, story);
        await this.saveStories();
        return story;
    }
    async generateBurndownChart(sprintId) {
        if (!this.sprints.has(sprintId)) {
            throw new Error(`Sprint ${sprintId} not found`);
        }
        const sprint = this.sprints.get(sprintId);
        const sprintStories = sprint.storyIds
            .map(id => this.stories.get(id))
            .filter(story => story !== undefined);
        const totalStoryPoints = sprintStories.reduce((sum, story) => sum + story.storyPoints, 0);
        const completedPoints = sprintStories
            .filter(story => story.status === 'done')
            .reduce((sum, story) => sum + story.storyPoints, 0);
        const sprintStart = sprint.startDate;
        const sprintEnd = sprint.endDate;
        const totalDays = Math.ceil((sprintEnd.getTime() - sprintStart.getTime()) / (1000 * 60 * 60 * 24));
        const currentDate = new Date();
        const daysElapsed = Math.min(Math.ceil((currentDate.getTime() - sprintStart.getTime()) / (1000 * 60 * 60 * 24)), totalDays);
        const idealBurnRate = totalStoryPoints / totalDays;
        const dailyData = [];
        for (let day = 0; day <= Math.min(daysElapsed, totalDays); day++) {
            const idealRemaining = Math.max(0, totalStoryPoints - (idealBurnRate * day));
            const actualRemaining = day === daysElapsed ? totalStoryPoints - completedPoints : idealRemaining;
            dailyData.push({
                day,
                idealPoints: idealRemaining,
                actualPoints: actualRemaining,
                remainingPoints: actualRemaining,
                completedPoints: totalStoryPoints - actualRemaining,
            });
        }
        const onTrack = completedPoints >= (totalStoryPoints * (daysElapsed / totalDays)) * 0.8;
        return {
            sprintId,
            totalStoryPoints,
            completedPoints,
            sprintDays: totalDays,
            dailyData,
            onTrack,
            daysRemaining: Math.max(0, totalDays - daysElapsed),
            trend: completedPoints > 0 ? 'improving' : 'stable',
        };
    }
    async generateVelocityReport(options) {
        const completedSprints = Array.from(this.sprints.values())
            .filter(sprint => sprint.status === 'completed')
            .sort((a, b) => b.endDate.getTime() - a.endDate.getTime())
            .slice(0, options.lastNSprints);
        if (completedSprints.length === 0) {
            throw new Error('No completed sprints found for velocity calculation');
        }
        const sprintData = completedSprints.map(sprint => {
            const sprintStories = sprint.storyIds
                .map(id => this.stories.get(id))
                .filter(story => story !== undefined);
            const velocity = sprintStories
                .filter(story => story.status === 'done')
                .reduce((sum, story) => sum + story.storyPoints, 0);
            const totalPoints = sprintStories.reduce((sum, story) => sum + story.storyPoints, 0);
            const completionRate = totalPoints > 0 ? Math.round((velocity / totalPoints) * 100) : 0;
            return {
                sprintName: sprint.name,
                velocity,
                completionRate,
                startDate: sprint.startDate,
                endDate: sprint.endDate,
            };
        });
        const velocities = sprintData.map(sprint => sprint.velocity);
        const averageVelocity = Math.round(velocities.reduce((sum, v) => sum + v, 0) / velocities.length);
        const highestVelocity = Math.max(...velocities);
        const lowestVelocity = Math.min(...velocities);
        const trend = velocities.length > 1
            ? velocities[0] > velocities[velocities.length - 1] ? 'improving' : 'declining'
            : 'stable';
        const insights = [
            `Team completed ${completedSprints.length} sprints`,
            `Average velocity: ${averageVelocity} story points`,
            `Velocity range: ${lowestVelocity} - ${highestVelocity} points`,
        ];
        const recommendations = [
            averageVelocity < 20 ? 'Consider breaking down stories into smaller tasks' : '',
            trend === 'declining' ? 'Review team capacity and remove blockers' : '',
            'Track velocity consistently for better sprint planning',
        ].filter(rec => rec !== '');
        return {
            teamName: options.teamName || 'Default Team',
            sprintsAnalyzed: completedSprints.length,
            periodStart: completedSprints[completedSprints.length - 1].startDate,
            periodEnd: completedSprints[0].endDate,
            averageVelocity,
            highestVelocity,
            lowestVelocity,
            trend,
            sprintData,
            insights,
            recommendations,
        };
    }
    async getSprintStatus(sprintId) {
        let sprint;
        if (sprintId) {
            if (!this.sprints.has(sprintId)) {
                throw new Error(`Sprint ${sprintId} not found`);
            }
            sprint = this.sprints.get(sprintId);
        }
        else {
            // Get current active sprint
            const activeSprints = Array.from(this.sprints.values())
                .filter(s => s.status === 'active')
                .sort((a, b) => a.startDate.getTime() - b.startDate.getTime());
            if (activeSprints.length === 0) {
                throw new Error('No active sprint found');
            }
            sprint = activeSprints[0];
        }
        const sprintStories = sprint.storyIds
            .map(id => this.stories.get(id))
            .filter(story => story !== undefined);
        const totalStories = sprintStories.length;
        const completedStories = sprintStories.filter(story => story.status === 'done').length;
        const inProgressStories = sprintStories.filter(story => story.status === 'in_progress').length;
        const remainingStories = totalStories - completedStories - inProgressStories;
        const totalStoryPoints = sprintStories.reduce((sum, story) => sum + story.storyPoints, 0);
        const completedStoryPoints = sprintStories
            .filter(story => story.status === 'done')
            .reduce((sum, story) => sum + story.storyPoints, 0);
        const blockers = this.standups
            .filter(standup => standup.sprintId === sprint.id)
            .flatMap(standup => standup.blockers)
            .filter((blocker, index, arr) => arr.indexOf(blocker) === index);
        const daysElapsed = Math.ceil((new Date().getTime() - sprint.startDate.getTime()) / (1000 * 60 * 60 * 24));
        const currentVelocity = daysElapsed > 0 ? Math.round(completedStoryPoints / daysElapsed) : 0;
        const completionPercentage = totalStories > 0 ? (completedStories / totalStories) * 100 : 0;
        const sprintHealth = completionPercentage > 75 ? 'Excellent' :
            completionPercentage > 50 ? 'Good' :
                completionPercentage > 25 ? 'At Risk' : 'Critical';
        return {
            sprint,
            totalStories,
            completedStories,
            inProgressStories,
            remainingStories,
            totalStoryPoints,
            completedStoryPoints,
            remainingStoryPoints: totalStoryPoints - completedStoryPoints,
            sprintHealth,
            blockers,
            currentVelocity,
        };
    }
    async getBacklog(filter = {}) {
        let backlogStories = Array.from(this.stories.values())
            .filter(story => !story.sprintId || story.sprintId === '');
        // Apply filters
        if (filter.epic) {
            backlogStories = backlogStories.filter(story => story.epic === filter.epic);
        }
        if (filter.priority) {
            backlogStories = backlogStories.filter(story => story.priority === filter.priority);
        }
        if (filter.assignee) {
            backlogStories = backlogStories.filter(story => story.assignee === filter.assignee);
        }
        if (filter.status) {
            backlogStories = backlogStories.filter(story => story.status === filter.status);
        }
        if (filter.maxStoryPoints) {
            backlogStories = backlogStories.filter(story => story.storyPoints !== undefined && story.storyPoints <= filter.maxStoryPoints);
        }
        // Sort by priority and creation date
        const priorityOrder = { critical: 4, high: 3, medium: 2, low: 1 };
        return backlogStories.sort((a, b) => {
            const priorityDiff = (priorityOrder[b.priority] || 0) - (priorityOrder[a.priority] || 0);
            if (priorityDiff !== 0)
                return priorityDiff;
            return b.createdAt.getTime() - a.createdAt.getTime();
        });
    }
    async getSprints(filter) {
        let sprints = Array.from(this.sprints.values());
        // Apply filters
        if (filter?.status) {
            sprints = sprints.filter(sprint => sprint.status === filter.status);
        }
        if (filter?.includeCompleted === false) {
            sprints = sprints.filter(sprint => sprint.status !== 'completed');
        }
        // Sort by start date (most recent first)
        return sprints.sort((a, b) => b.startDate.getTime() - a.startDate.getTime());
    }
    async getSprint(sprintId) {
        const sprint = this.sprints.get(sprintId);
        return sprint || null;
    }
    async getEpics(filter) {
        let epics = Array.from(this.epics.values());
        // Apply filters
        if (filter?.status) {
            epics = epics.filter(epic => epic.status === filter.status);
        }
        if (filter?.owner) {
            epics = epics.filter(epic => epic.owner === filter.owner);
        }
        // Sort by priority and creation date
        const priorityOrder = { critical: 4, high: 3, medium: 2, low: 1 };
        return epics.sort((a, b) => {
            const priorityDiff = (priorityOrder[b.priority] || 0) - (priorityOrder[a.priority] || 0);
            if (priorityDiff !== 0)
                return priorityDiff;
            return b.createdAt.getTime() - a.createdAt.getTime();
        });
    }
    async getEpic(epicId) {
        const epic = this.epics.get(epicId);
        return epic || null;
    }
    async getStory(storyId) {
        const story = this.stories.get(storyId);
        return story || null;
    }
    async getEpicsForSprint(sprintId) {
        const sprint = this.sprints.get(sprintId);
        if (!sprint) {
            return [];
        }
        const epicIds = new Set();
        for (const storyId of sprint.storyIds) {
            const story = this.stories.get(storyId);
            if (story && story.epic) {
                epicIds.add(story.epic);
            }
        }
        const epics = [];
        for (const epicId of epicIds) {
            const epic = this.epics.get(epicId);
            if (epic) {
                epics.push(epic);
            }
        }
        return epics;
    }
    async getStoriesForEpicInSprint(epicId, sprintId) {
        const sprint = this.sprints.get(sprintId);
        if (!sprint) {
            return [];
        }
        const stories = [];
        for (const storyId of sprint.storyIds) {
            const story = this.stories.get(storyId);
            if (story && story.epic === epicId) {
                stories.push(story);
            }
        }
        return stories;
    }
    async getSprintProgress(sprintId) {
        const sprint = this.sprints.get(sprintId);
        if (!sprint) {
            return { epicProgress: new Map() };
        }
        const epicProgress = new Map();
        for (const storyId of sprint.storyIds) {
            const story = this.stories.get(storyId);
            if (story && story.epic) {
                const current = epicProgress.get(story.epic) || { total: 0, completed: 0 };
                current.total += story.storyPoints;
                if (story.status === 'done') {
                    current.completed += story.storyPoints;
                }
                epicProgress.set(story.epic, current);
            }
        }
        return { epicProgress };
    }
    async saveSprints() {
        const sprintsPath = path.join(this.agileDataPath, 'sprints.json');
        await fs.mkdir(path.dirname(sprintsPath), { recursive: true });
        await fs.writeFile(sprintsPath, JSON.stringify(Array.from(this.sprints.values()), null, 2));
    }
    async saveStories() {
        const storiesPath = path.join(this.agileDataPath, 'stories.json');
        await fs.mkdir(path.dirname(storiesPath), { recursive: true });
        await fs.writeFile(storiesPath, JSON.stringify(Array.from(this.stories.values()), null, 2));
    }
    async saveEpics() {
        const epicsPath = path.join(this.agileDataPath, 'epics.json');
        await fs.mkdir(path.dirname(epicsPath), { recursive: true });
        await fs.writeFile(epicsPath, JSON.stringify(Array.from(this.epics.values()), null, 2));
    }
    async saveStandups() {
        const standupsPath = path.join(this.agileDataPath, 'standups.json');
        await fs.mkdir(path.dirname(standupsPath), { recursive: true });
        await fs.writeFile(standupsPath, JSON.stringify(this.standups, null, 2));
    }
    async saveRetrospectives() {
        const retrospectivesPath = path.join(this.agileDataPath, 'retrospectives.json');
        await fs.mkdir(path.dirname(retrospectivesPath), { recursive: true });
        await fs.writeFile(retrospectivesPath, JSON.stringify(this.retrospectives, null, 2));
    }
    generateId() {
        return `${Date.now().toString(16)}-${Math.random().toString(16).substring(2, 10)}`;
    }
    async createSampleData() {
        console.log('🔧 Creating sample agile data for dashboard');
        try {
            // Create sample epic
            const epic = await this.createEpic({
                title: 'User Authentication System',
                description: 'Implement comprehensive user authentication including login, registration, password reset, and session management',
                goals: [
                    'Secure user registration and login',
                    'Session management with JWT tokens',
                    'Password reset functionality',
                    'Multi-factor authentication support'
                ],
                priority: 'high',
                owner: 'Product Team'
            });
            // Create sample sprint
            const sprint = await this.createSprint({
                name: 'Sprint 1 - Authentication Foundation',
                goal: 'Build core authentication infrastructure and user login flow',
                duration: 14,
                startDate: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000), // Started 5 days ago
                team: ['Alice Johnson', 'Bob Chen', 'Carol Davis']
            });
            // Update sprint to active status
            await this.updateSprint(sprint.id, {
                status: 'active',
                capacity: 25
            });
            // Create sample stories
            const stories = [
                {
                    title: 'User Registration API',
                    description: 'Create REST API endpoints for user registration with email validation',
                    storyPoints: 8,
                    priority: 'high',
                    status: 'done',
                    acceptanceCriteria: [
                        'API accepts email, password, and basic profile info',
                        'Email validation is performed',
                        'Password meets security requirements',
                        'User account is created in database',
                        'Confirmation email is sent'
                    ],
                    assignee: 'Alice Johnson',
                    epic: epic.id,
                    sprintId: sprint.id,
                    tags: ['backend', 'api', 'security']
                },
                {
                    title: 'Login Form UI',
                    description: 'Design and implement responsive login form with validation',
                    storyPoints: 5,
                    priority: 'high',
                    status: 'in_progress',
                    acceptanceCriteria: [
                        'Responsive design works on mobile and desktop',
                        'Real-time validation feedback',
                        'Clear error messages',
                        'Remember me checkbox',
                        'Forgot password link'
                    ],
                    assignee: 'Bob Chen',
                    epic: epic.id,
                    sprintId: sprint.id,
                    tags: ['frontend', 'ui', 'forms']
                },
                {
                    title: 'JWT Token Management',
                    description: 'Implement JWT token generation, validation, and refresh logic',
                    storyPoints: 8,
                    priority: 'high',
                    status: 'review',
                    acceptanceCriteria: [
                        'Tokens are generated on successful login',
                        'Token expiration is handled',
                        'Refresh token functionality',
                        'Token validation middleware',
                        'Secure token storage'
                    ],
                    assignee: 'Carol Davis',
                    epic: epic.id,
                    sprintId: sprint.id,
                    tags: ['backend', 'security', 'authentication']
                },
                {
                    title: 'Password Reset Flow',
                    description: 'Build password reset functionality with secure email tokens',
                    storyPoints: 5,
                    priority: 'medium',
                    status: 'todo',
                    acceptanceCriteria: [
                        'Password reset email generation',
                        'Secure reset token creation',
                        'Reset form with validation',
                        'Token expiration handling',
                        'Success confirmation'
                    ],
                    assignee: 'Alice Johnson',
                    epic: epic.id,
                    sprintId: sprint.id,
                    tags: ['backend', 'email', 'security']
                }
            ];
            // Add stories to the system
            for (const storyData of stories) {
                await this.addStory(storyData);
            }
            console.log('✅ Sample agile data created successfully');
        }
        catch (error) {
            console.error('❌ Error creating sample data:', error);
        }
    }
    // Version management methods
    async getDataVersion() {
        try {
            const versionPath = path.join(this.agileDataPath, 'version.json');
            if (await this.fileExists(versionPath)) {
                const versionData = JSON.parse(await fs.readFile(versionPath, 'utf-8'));
                return versionData.version || '1.0.0';
            }
        }
        catch (error) {
            console.error('Error reading version file:', error);
        }
        return '1.0.0'; // Default to 1.0.0 if no version file exists
    }
    compareVersions(v1, v2) {
        const parts1 = v1.split('.').map(Number);
        const parts2 = v2.split('.').map(Number);
        for (let i = 0; i < 3; i++) {
            const part1 = parts1[i] || 0;
            const part2 = parts2[i] || 0;
            if (part1 > part2)
                return 1;
            if (part1 < part2)
                return -1;
        }
        return 0;
    }
    async updateDataVersion(version) {
        // Ensure the agile data directory exists
        await fs.mkdir(this.agileDataPath, { recursive: true });
        const versionPath = path.join(this.agileDataPath, 'version.json');
        const versionData = {
            version,
            lastMigration: new Date().toISOString(),
            migrationHistory: []
        };
        // Try to preserve migration history
        try {
            if (await this.fileExists(versionPath)) {
                const existing = JSON.parse(await fs.readFile(versionPath, 'utf-8'));
                if (existing.migrationHistory) {
                    versionData.migrationHistory = existing.migrationHistory;
                }
            }
        }
        catch (error) {
            // Ignore errors reading existing version file
        }
        // Add this migration to history
        versionData.migrationHistory.push({
            fromVersion: await this.getDataVersion(),
            toVersion: version,
            date: new Date().toISOString()
        });
        await fs.writeFile(versionPath, JSON.stringify(versionData, null, 2));
    }
}
//# sourceMappingURL=manager.js.map