/**
 * Task Management Integration
 *
 * Provides integration with task management platforms:
 * - Jira (Atlassian)
 * - Linear
 *
 * Features:
 * - Create/update issues
 * - Link commits to issues
 * - Sync status
 * - Query issues
 */
import { EventEmitter } from 'events';
/**
 * Abstract base class for task management integrations
 */
class TaskManagementClient extends EventEmitter {
    config;
    constructor(config) {
        super();
        this.config = config;
    }
}
/**
 * Jira Integration Client
 */
export class JiraClient extends TaskManagementClient {
    baseUrl;
    constructor(config) {
        super(config);
        if (!config.baseUrl) {
            throw new Error('Jira requires baseUrl (e.g., https://yourcompany.atlassian.net)');
        }
        this.baseUrl = config.baseUrl.replace(/\/$/, '');
    }
    async request(method, endpoint, body) {
        const url = `${this.baseUrl}/rest/api/3${endpoint}`;
        const response = await fetch(url, {
            method,
            headers: {
                Authorization: `Basic ${Buffer.from(`email:${this.config.apiKey}`).toString('base64')}`,
                'Content-Type': 'application/json',
                Accept: 'application/json',
            },
            body: body ? JSON.stringify(body) : undefined,
        });
        if (!response.ok) {
            const error = await response.text();
            throw new Error(`Jira API error: ${response.status} - ${error}`);
        }
        if (response.status === 204) {
            return {};
        }
        return response.json();
    }
    mapPriority(priority) {
        const map = {
            Highest: 'urgent',
            High: 'high',
            Medium: 'medium',
            Low: 'low',
            Lowest: 'none',
        };
        return map[priority] || 'medium';
    }
    mapType(type) {
        const map = {
            Bug: 'bug',
            Story: 'story',
            Task: 'task',
            Epic: 'epic',
            'Sub-task': 'subtask',
        };
        return map[type] || 'task';
    }
    parseIssue(data) {
        return {
            id: data.id,
            key: data.key,
            title: data.fields.summary,
            description: data.fields.description?.content?.[0]?.content?.[0]?.text,
            status: data.fields.status.name,
            priority: this.mapPriority(data.fields.priority?.name || 'Medium'),
            type: this.mapType(data.fields.issuetype.name),
            assignee: data.fields.assignee?.displayName,
            labels: data.fields.labels || [],
            url: `${this.baseUrl}/browse/${data.key}`,
            createdAt: new Date(data.fields.created),
            updatedAt: new Date(data.fields.updated),
        };
    }
    async getIssue(key) {
        try {
            const data = await this.request('GET', `/issue/${key}`);
            return this.parseIssue(data);
        }
        catch {
            return null;
        }
    }
    async createIssue(options) {
        const issueData = {
            fields: {
                project: { key: this.config.projectKey },
                summary: options.title,
                description: options.description
                    ? {
                        type: 'doc',
                        version: 1,
                        content: [
                            {
                                type: 'paragraph',
                                content: [{ type: 'text', text: options.description }],
                            },
                        ],
                    }
                    : undefined,
                issuetype: { name: this.getJiraType(options.type || 'task') },
                labels: options.labels,
            },
        };
        const created = await this.request('POST', '/issue', issueData);
        const issue = await this.getIssue(created.key);
        if (!issue)
            throw new Error('Failed to fetch created issue');
        return issue;
    }
    async updateIssue(key, options) {
        const updateData = { fields: {} };
        if (options.title)
            updateData.fields.summary = options.title;
        if (options.labels)
            updateData.fields.labels = options.labels;
        if (options.description) {
            updateData.fields.description = {
                type: 'doc',
                version: 1,
                content: [
                    {
                        type: 'paragraph',
                        content: [{ type: 'text', text: options.description }],
                    },
                ],
            };
        }
        await this.request('PUT', `/issue/${key}`, updateData);
        // Handle status transition separately
        if (options.status) {
            await this.transitionIssue(key, options.status);
        }
        const issue = await this.getIssue(key);
        if (!issue)
            throw new Error('Failed to fetch updated issue');
        return issue;
    }
    async transitionIssue(key, status) {
        const transitions = await this.request('GET', `/issue/${key}/transitions`);
        const statusMap = {
            backlog: ['Backlog', 'Open'],
            todo: ['To Do', 'Open', 'Reopened'],
            in_progress: ['In Progress', 'In Development'],
            review: ['In Review', 'Code Review'],
            done: ['Done', 'Closed', 'Resolved'],
            canceled: ['Canceled', 'Won\'t Do', 'Won\'t Fix'],
        };
        const targetNames = statusMap[status] || [status];
        const transition = transitions.transitions.find((t) => targetNames.some((name) => t.name.toLowerCase() === name.toLowerCase()));
        if (transition) {
            await this.request('POST', `/issue/${key}/transitions`, {
                transition: { id: transition.id },
            });
        }
    }
    async queryIssues(query) {
        const jql = [];
        if (this.config.projectKey) {
            jql.push(`project = ${this.config.projectKey}`);
        }
        if (query.status) {
            const statuses = Array.isArray(query.status) ? query.status : [query.status];
            jql.push(`status IN (${statuses.map((s) => `"${s}"`).join(', ')})`);
        }
        if (query.assignee) {
            jql.push(`assignee = "${query.assignee}"`);
        }
        if (query.labels && query.labels.length > 0) {
            jql.push(`labels IN (${query.labels.map((l) => `"${l}"`).join(', ')})`);
        }
        if (query.search) {
            jql.push(`text ~ "${query.search}"`);
        }
        const jqlString = jql.length > 0 ? jql.join(' AND ') : 'ORDER BY created DESC';
        const limit = query.limit || 50;
        const result = await this.request('GET', `/search?jql=${encodeURIComponent(jqlString)}&maxResults=${limit}`);
        return result.issues.map((issue) => this.parseIssue(issue));
    }
    async addComment(key, body) {
        const result = await this.request('POST', `/issue/${key}/comment`, {
            body: {
                type: 'doc',
                version: 1,
                content: [
                    {
                        type: 'paragraph',
                        content: [{ type: 'text', text: body }],
                    },
                ],
            },
        });
        return {
            id: result.id,
            body: body,
            author: result.author.displayName,
            createdAt: new Date(result.created),
        };
    }
    async linkCommit(key, commitSha, message) {
        await this.addComment(key, `Commit: ${commitSha}\n\n${message}`);
    }
    async getStatuses() {
        const result = await this.request('GET', '/status');
        return result.map((s) => s.name);
    }
    async testConnection() {
        try {
            await this.request('GET', '/myself');
            return true;
        }
        catch {
            return false;
        }
    }
    getJiraType(type) {
        const map = {
            bug: 'Bug',
            feature: 'Story',
            task: 'Task',
            story: 'Story',
            epic: 'Epic',
            subtask: 'Sub-task',
        };
        return map[type] || 'Task';
    }
}
/**
 * Linear Integration Client
 */
export class LinearClient extends TaskManagementClient {
    apiUrl = 'https://api.linear.app/graphql';
    async graphql(query, variables) {
        const response = await fetch(this.apiUrl, {
            method: 'POST',
            headers: {
                Authorization: this.config.apiKey,
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({ query, variables }),
        });
        if (!response.ok) {
            const error = await response.text();
            throw new Error(`Linear API error: ${response.status} - ${error}`);
        }
        const result = (await response.json());
        if (result.errors) {
            throw new Error(`Linear GraphQL error: ${result.errors[0].message}`);
        }
        return result.data;
    }
    mapPriority(priority) {
        const map = {
            0: 'none',
            1: 'urgent',
            2: 'high',
            3: 'medium',
            4: 'low',
        };
        return map[priority] || 'medium';
    }
    parseIssue(data) {
        return {
            id: data.id,
            key: data.identifier,
            title: data.title,
            description: data.description,
            status: data.state.name,
            priority: this.mapPriority(data.priority),
            type: data.labels?.nodes?.some((l) => l.name.toLowerCase() === 'bug') ? 'bug' : 'task',
            assignee: data.assignee?.name,
            labels: data.labels?.nodes?.map((l) => l.name) || [],
            url: data.url,
            createdAt: new Date(data.createdAt),
            updatedAt: new Date(data.updatedAt),
        };
    }
    async getIssue(key) {
        const query = `
      query GetIssue($key: String!) {
        issue(id: $key) {
          id
          identifier
          title
          description
          priority
          url
          createdAt
          updatedAt
          state { name }
          assignee { name }
          labels { nodes { name } }
        }
      }
    `;
        try {
            const result = await this.graphql(query, { key });
            return result.issue ? this.parseIssue(result.issue) : null;
        }
        catch {
            return null;
        }
    }
    async createIssue(options) {
        const query = `
      mutation CreateIssue($input: IssueCreateInput!) {
        issueCreate(input: $input) {
          success
          issue {
            id
            identifier
            title
            description
            priority
            url
            createdAt
            updatedAt
            state { name }
            assignee { name }
            labels { nodes { name } }
          }
        }
      }
    `;
        const priorityMap = {
            urgent: 1,
            high: 2,
            medium: 3,
            low: 4,
            none: 0,
        };
        const result = await this.graphql(query, {
            input: {
                teamId: this.config.teamId,
                title: options.title,
                description: options.description,
                priority: priorityMap[options.priority || 'medium'],
                labelIds: options.labels,
                assigneeId: options.assignee,
            },
        });
        return this.parseIssue(result.issueCreate.issue);
    }
    async updateIssue(key, options) {
        // First get the issue to find its ID
        const issue = await this.getIssue(key);
        if (!issue)
            throw new Error(`Issue not found: ${key}`);
        const query = `
      mutation UpdateIssue($id: String!, $input: IssueUpdateInput!) {
        issueUpdate(id: $id, input: $input) {
          success
          issue {
            id
            identifier
            title
            description
            priority
            url
            createdAt
            updatedAt
            state { name }
            assignee { name }
            labels { nodes { name } }
          }
        }
      }
    `;
        const input = {};
        if (options.title)
            input.title = options.title;
        if (options.description)
            input.description = options.description;
        if (options.priority) {
            const priorityMap = {
                urgent: 1,
                high: 2,
                medium: 3,
                low: 4,
                none: 0,
            };
            input.priority = priorityMap[options.priority];
        }
        const result = await this.graphql(query, {
            id: issue.id,
            input,
        });
        return this.parseIssue(result.issueUpdate.issue);
    }
    async queryIssues(query) {
        const gqlQuery = `
      query Issues($teamId: String, $first: Int, $filter: IssueFilter) {
        issues(first: $first, filter: $filter) {
          nodes {
            id
            identifier
            title
            description
            priority
            url
            createdAt
            updatedAt
            state { name }
            assignee { name }
            labels { nodes { name } }
          }
        }
      }
    `;
        const filter = {};
        if (this.config.teamId) {
            filter.team = { id: { eq: this.config.teamId } };
        }
        if (query.search) {
            filter.title = { contains: query.search };
        }
        if (query.assignee) {
            filter.assignee = { name: { eq: query.assignee } };
        }
        const result = await this.graphql(gqlQuery, {
            teamId: this.config.teamId,
            first: query.limit || 50,
            filter: Object.keys(filter).length > 0 ? filter : undefined,
        });
        return result.issues.nodes.map((issue) => this.parseIssue(issue));
    }
    async addComment(key, body) {
        const issue = await this.getIssue(key);
        if (!issue)
            throw new Error(`Issue not found: ${key}`);
        const query = `
      mutation CreateComment($input: CommentCreateInput!) {
        commentCreate(input: $input) {
          success
          comment {
            id
            body
            createdAt
            user { name }
          }
        }
      }
    `;
        const result = await this.graphql(query, {
            input: {
                issueId: issue.id,
                body,
            },
        });
        return {
            id: result.commentCreate.comment.id,
            body: result.commentCreate.comment.body,
            author: result.commentCreate.comment.user.name,
            createdAt: new Date(result.commentCreate.comment.createdAt),
        };
    }
    async linkCommit(key, commitSha, message) {
        await this.addComment(key, `Linked commit: \`${commitSha}\`\n\n${message}`);
    }
    async getStatuses() {
        const query = `
      query WorkflowStates($teamId: String) {
        workflowStates(filter: { team: { id: { eq: $teamId } } }) {
          nodes { name }
        }
      }
    `;
        const result = await this.graphql(query, {
            teamId: this.config.teamId,
        });
        return result.workflowStates.nodes.map((s) => s.name);
    }
    async testConnection() {
        try {
            const query = `query { viewer { id } }`;
            await this.graphql(query);
            return true;
        }
        catch {
            return false;
        }
    }
}
/**
 * Task Management Integration Manager
 */
export class TaskManagementIntegration extends EventEmitter {
    client = null;
    config = null;
    /**
     * Configure the task management integration
     */
    configure(config) {
        this.config = config;
        switch (config.platform) {
            case 'jira':
                this.client = new JiraClient(config);
                break;
            case 'linear':
                this.client = new LinearClient(config);
                break;
            default:
                throw new Error(`Unsupported platform: ${config.platform}`);
        }
        this.emit('configured', config.platform);
    }
    /**
     * Get the current client
     */
    getClient() {
        if (!this.client) {
            throw new Error('Task management not configured. Call configure() first.');
        }
        return this.client;
    }
    /**
     * Get an issue by key
     */
    async getIssue(key) {
        return this.getClient().getIssue(key);
    }
    /**
     * Create a new issue
     */
    async createIssue(options) {
        const issue = await this.getClient().createIssue(options);
        this.emit('issueCreated', issue);
        return issue;
    }
    /**
     * Update an existing issue
     */
    async updateIssue(key, options) {
        const issue = await this.getClient().updateIssue(key, options);
        this.emit('issueUpdated', issue);
        return issue;
    }
    /**
     * Query issues
     */
    async queryIssues(query) {
        return this.getClient().queryIssues(query);
    }
    /**
     * Add a comment to an issue
     */
    async addComment(key, body) {
        const comment = await this.getClient().addComment(key, body);
        this.emit('commentAdded', { key, comment });
        return comment;
    }
    /**
     * Link a commit to an issue
     */
    async linkCommit(key, commitSha, message) {
        await this.getClient().linkCommit(key, commitSha, message);
        this.emit('commitLinked', { key, commitSha });
    }
    /**
     * Get available statuses
     */
    async getStatuses() {
        return this.getClient().getStatuses();
    }
    /**
     * Test the connection
     */
    async testConnection() {
        return this.getClient().testConnection();
    }
    /**
     * Extract issue keys from commit message
     */
    extractIssueKeys(message) {
        // Match patterns like: PROJ-123, ABC-456, fixes #123
        const patterns = [
            /([A-Z]+-\d+)/g, // Jira/Linear style
            /(?:fixes?|closes?|resolves?)\s+#(\d+)/gi, // GitHub style
        ];
        const keys = [];
        for (const pattern of patterns) {
            const matches = message.matchAll(pattern);
            for (const match of matches) {
                keys.push(match[1]);
            }
        }
        return [...new Set(keys)];
    }
    /**
     * Get current platform
     */
    getPlatform() {
        return this.config?.platform || null;
    }
    /**
     * Check if configured
     */
    isConfigured() {
        return this.client !== null;
    }
}
// Singleton instance
let taskManagementInstance = null;
/**
 * Get the task management integration instance
 */
export function getTaskManagement() {
    if (!taskManagementInstance) {
        taskManagementInstance = new TaskManagementIntegration();
    }
    return taskManagementInstance;
}
export default TaskManagementIntegration;
//# sourceMappingURL=task-management-integration.js.map