import { ServiceBubble } from '../../../types/service-bubble-class.js';
import { CredentialType, decodeCredentialPayload, } from '@bubblelab/shared-schemas';
import { ConfluenceParamsSchema, ConfluenceResultSchema, } from './confluence.schema.js';
import { markdownToConfluenceStorage, storageToText, enhanceErrorMessage, } from './confluence.utils.js';
/**
 * Confluence Service Bubble
 *
 * Integration with Confluence Cloud for wiki and content management.
 *
 * Operations:
 * - list_spaces: List spaces with optional filtering
 * - get_space: Get space details by ID
 * - list_pages: List pages with filtering (by space, title, status)
 * - get_page: Get page by ID with body content
 * - create_page: Create a new page in a space
 * - update_page: Update page (auto-fetches current version)
 * - delete_page: Delete/trash a page
 * - search: Search via CQL (v1 endpoint)
 * - add_comment: Add footer comment to a page
 * - get_comments: List footer comments for a page
 *
 * Features:
 * - Markdown content auto-converted to Confluence storage format (XHTML)
 * - Auto-version management for page updates
 * - Uses REST API v2 for most operations, v1 for search/labels
 */
export class ConfluenceBubble extends ServiceBubble {
    static type = 'service';
    static service = 'confluence';
    static authType = 'oauth';
    static bubbleName = 'confluence';
    static schema = ConfluenceParamsSchema;
    static resultSchema = ConfluenceResultSchema;
    static shortDescription = 'Confluence integration for wiki pages and content management';
    static longDescription = `
    Confluence Cloud integration for managing wiki content, spaces, and pages.

    Operations:
    - list_spaces: List Confluence spaces with filtering
    - get_space: Get space details by ID
    - list_pages: List pages (filter by space, title, status)
    - get_page: Get page by ID with body content
    - create_page: Create a new page in a space
    - update_page: Update page content (auto-increments version)
    - delete_page: Delete/trash a page
    - search: Search via CQL (Confluence Query Language)
    - add_comment: Add footer comment to a page
    - get_comments: List footer comments for a page

    Features:
    - Markdown content auto-converted to Confluence storage format (XHTML)
    - Auto-version management for page updates
    - CQL search support for powerful content discovery

    Authentication:
    - OAuth 2.0 via Atlassian Cloud (same provider as Jira)
  `;
    static alias = 'confluence';
    constructor(params, context) {
        super(params, context);
    }
    async testCredential() {
        const apiToken = this.chooseCredential();
        if (!apiToken) {
            throw new Error('Confluence credentials are required');
        }
        try {
            const response = await this.makeConfluenceApiRequest('/wiki/api/v2/spaces?limit=1', 'GET');
            return Boolean(response && 'results' in response);
        }
        catch {
            return false;
        }
    }
    /**
     * Parse Confluence credentials: base64-encoded JSON { accessToken, cloudId, siteUrl }
     * Same format as Jira since both use Atlassian Cloud OAuth.
     */
    parseCredentials() {
        const { credentials } = this.params;
        if (!credentials || typeof credentials !== 'object') {
            return null;
        }
        const confluenceCredRaw = credentials[CredentialType.CONFLUENCE_CRED];
        if (!confluenceCredRaw) {
            return null;
        }
        try {
            const parsed = decodeCredentialPayload(confluenceCredRaw);
            if (parsed.accessToken && parsed.cloudId) {
                return {
                    accessToken: parsed.accessToken,
                    baseUrl: `https://api.atlassian.com/ex/confluence/${parsed.cloudId}`,
                };
            }
        }
        catch {
            // Invalid credential format
        }
        return null;
    }
    /**
     * Resolve a space_key to a space_id via the Confluence API.
     */
    async resolveSpaceKey(spaceKey) {
        const response = await this.makeConfluenceApiRequest(`/wiki/api/v2/spaces?keys=${encodeURIComponent(spaceKey)}&limit=1`, 'GET');
        const results = Array.isArray(response.results) ? response.results : [];
        if (results.length === 0) {
            throw new Error(`Space not found for key: ${spaceKey}`);
        }
        return results[0].id;
    }
    /**
     * Get the space_id from params, resolving space_key if needed.
     */
    async getSpaceId(params) {
        if (params.space_id)
            return params.space_id;
        if (params.space_key)
            return this.resolveSpaceKey(params.space_key);
        return undefined;
    }
    /**
     * Make a request to the Confluence REST API v2.
     */
    async makeConfluenceApiRequest(endpoint, method = 'GET', body) {
        const creds = this.parseCredentials();
        if (!creds) {
            throw new Error('Invalid Confluence credentials. Expected base64-encoded JSON with { accessToken, cloudId }.');
        }
        const url = `${creds.baseUrl}${endpoint}`;
        const requestHeaders = {
            Authorization: `Bearer ${creds.accessToken}`,
            'Content-Type': 'application/json',
            Accept: 'application/json',
        };
        const requestInit = {
            method,
            headers: requestHeaders,
        };
        if (body && method !== 'GET') {
            requestInit.body = JSON.stringify(body);
        }
        const response = await fetch(url, requestInit);
        if (!response.ok) {
            const errorText = await response.text();
            const enhancedError = enhanceErrorMessage(errorText, response.status, response.statusText);
            throw new Error(enhancedError);
        }
        if (response.status === 204) {
            return {};
        }
        const contentType = response.headers.get('content-type');
        if (contentType && contentType.includes('application/json')) {
            return (await response.json());
        }
        return {};
    }
    /**
     * Make a request to the Confluence REST API v1 (used for search and labels).
     */
    async makeConfluenceV1ApiRequest(endpoint, method = 'GET', body) {
        const creds = this.parseCredentials();
        if (!creds) {
            throw new Error('Invalid Confluence credentials. Expected base64-encoded JSON with { accessToken, cloudId }.');
        }
        const url = `${creds.baseUrl}${endpoint}`;
        const requestHeaders = {
            Authorization: `Bearer ${creds.accessToken}`,
            'Content-Type': 'application/json',
            Accept: 'application/json',
        };
        const requestInit = {
            method,
            headers: requestHeaders,
        };
        if (body && method !== 'GET') {
            requestInit.body = JSON.stringify(body);
        }
        const response = await fetch(url, requestInit);
        if (!response.ok) {
            const errorText = await response.text();
            const enhancedError = enhanceErrorMessage(errorText, response.status, response.statusText);
            throw new Error(enhancedError);
        }
        if (response.status === 204) {
            return {};
        }
        const contentType = response.headers.get('content-type');
        if (contentType && contentType.includes('application/json')) {
            return (await response.json());
        }
        return {};
    }
    async performAction(context) {
        void context;
        const { operation } = this.params;
        try {
            const result = await (async () => {
                const parsedParams = this.params;
                switch (operation) {
                    case 'list_spaces':
                        return await this.listSpaces(parsedParams);
                    case 'get_space':
                        return await this.getSpace(parsedParams);
                    case 'list_pages':
                        return await this.listPages(parsedParams);
                    case 'get_page':
                        return await this.getPage(parsedParams);
                    case 'create_page':
                        return await this.createPage(parsedParams);
                    case 'update_page':
                        return await this.updatePage(parsedParams);
                    case 'delete_page':
                        return await this.deletePage(parsedParams);
                    case 'search':
                        return await this.search(parsedParams);
                    case 'add_comment':
                        return await this.addComment(parsedParams);
                    case 'get_comments':
                        return await this.getComments(parsedParams);
                    default:
                        throw new Error(`Unsupported operation: ${operation}`);
                }
            })();
            return result;
        }
        catch (error) {
            return {
                operation,
                success: false,
                error: error instanceof Error ? error.message : 'Unknown error occurred',
            };
        }
    }
    // -------------------------------------------------------------------------
    // OPERATION 1: list_spaces
    // -------------------------------------------------------------------------
    async listSpaces(params) {
        const queryParams = new URLSearchParams();
        queryParams.set('limit', String(params.limit ?? 25));
        if (params.cursor) {
            queryParams.set('cursor', params.cursor);
        }
        if (params.type) {
            queryParams.set('type', params.type);
        }
        if (params.status) {
            queryParams.set('status', params.status);
        }
        const response = await this.makeConfluenceApiRequest(`/wiki/api/v2/spaces?${queryParams.toString()}`, 'GET');
        const results = Array.isArray(response.results) ? response.results : [];
        const links = response._links;
        const nextCursor = links?.next
            ? (new URL(links.next, 'https://placeholder.com').searchParams.get('cursor') ?? undefined)
            : undefined;
        return {
            operation: 'list_spaces',
            success: true,
            spaces: results,
            cursor: nextCursor,
            error: '',
        };
    }
    // -------------------------------------------------------------------------
    // OPERATION 2: get_space
    // -------------------------------------------------------------------------
    async getSpace(params) {
        const response = await this.makeConfluenceApiRequest(`/wiki/api/v2/spaces/${encodeURIComponent(params.space_id)}?description-format=plain`, 'GET');
        return {
            operation: 'get_space',
            success: true,
            space: response,
            error: '',
        };
    }
    // -------------------------------------------------------------------------
    // OPERATION 3: list_pages
    // -------------------------------------------------------------------------
    async listPages(params) {
        const queryParams = new URLSearchParams();
        queryParams.set('limit', String(params.limit ?? 25));
        const spaceId = await this.getSpaceId(params);
        if (spaceId) {
            queryParams.set('space-id', spaceId);
        }
        if (params.title) {
            queryParams.set('title', params.title);
        }
        if (params.status) {
            queryParams.set('status', params.status);
        }
        if (params.cursor) {
            queryParams.set('cursor', params.cursor);
        }
        const response = await this.makeConfluenceApiRequest(`/wiki/api/v2/pages?${queryParams.toString()}`, 'GET');
        const results = Array.isArray(response.results) ? response.results : [];
        const links = response._links;
        const nextCursor = links?.next
            ? (new URL(links.next, 'https://placeholder.com').searchParams.get('cursor') ?? undefined)
            : undefined;
        return {
            operation: 'list_pages',
            success: true,
            pages: results,
            cursor: nextCursor,
            error: '',
        };
    }
    // -------------------------------------------------------------------------
    // OPERATION 4: get_page
    // -------------------------------------------------------------------------
    async getPage(params) {
        const queryParams = new URLSearchParams();
        if (params.include_body !== false) {
            queryParams.set('body-format', 'storage');
        }
        const queryString = queryParams.toString();
        const endpoint = `/wiki/api/v2/pages/${encodeURIComponent(params.page_id)}${queryString ? `?${queryString}` : ''}`;
        const response = await this.makeConfluenceApiRequest(endpoint, 'GET');
        // Convert storage body to plain text for easier consumption
        if (response.body &&
            typeof response.body === 'object' &&
            'storage' in response.body) {
            const body = response.body;
            if (body.storage?.value) {
                // Keep original storage format and add a plain text version
                response.bodyText = storageToText(body.storage.value);
            }
        }
        return {
            operation: 'get_page',
            success: true,
            page: response,
            error: '',
        };
    }
    // -------------------------------------------------------------------------
    // OPERATION 5: create_page
    // -------------------------------------------------------------------------
    async createPage(params) {
        const spaceId = await this.getSpaceId(params);
        if (!spaceId) {
            throw new Error('Either space_id or space_key is required');
        }
        const requestBody = {
            spaceId,
            status: params.status ?? 'current',
            title: params.title,
        };
        if (params.body) {
            requestBody.body = {
                representation: 'storage',
                value: markdownToConfluenceStorage(params.body),
            };
        }
        if (params.parent_id) {
            requestBody.parentId = params.parent_id;
        }
        const response = await this.makeConfluenceApiRequest('/wiki/api/v2/pages', 'POST', requestBody);
        return {
            operation: 'create_page',
            success: true,
            page: {
                id: response.id,
                title: response.title,
                status: response.status,
                _links: response._links,
            },
            error: '',
        };
    }
    // -------------------------------------------------------------------------
    // OPERATION 6: update_page
    // -------------------------------------------------------------------------
    async updatePage(params) {
        // Fetch current page to get version number, title, and body (for preservation)
        const currentPage = await this.makeConfluenceApiRequest(`/wiki/api/v2/pages/${encodeURIComponent(params.page_id)}?body-format=storage`, 'GET');
        const currentVersion = currentPage.version;
        if (!currentVersion) {
            throw new Error('Could not determine current page version');
        }
        const requestBody = {
            id: params.page_id,
            status: params.status ?? 'current',
            title: params.title ?? currentPage.title,
            version: {
                number: currentVersion.number + 1,
                message: params.version_message,
            },
        };
        if (params.body !== undefined) {
            // User provided new body — convert markdown to storage format
            requestBody.body = {
                representation: 'storage',
                value: markdownToConfluenceStorage(params.body),
            };
        }
        else {
            // Preserve existing body when only updating title/status
            const currentBody = currentPage.body;
            if (currentBody?.storage?.value) {
                requestBody.body = {
                    representation: 'storage',
                    value: currentBody.storage.value,
                };
            }
        }
        const response = await this.makeConfluenceApiRequest(`/wiki/api/v2/pages/${encodeURIComponent(params.page_id)}`, 'PUT', requestBody);
        return {
            operation: 'update_page',
            success: true,
            page: {
                id: response.id,
                title: response.title,
                version: response.version,
            },
            error: '',
        };
    }
    // -------------------------------------------------------------------------
    // OPERATION 7: delete_page
    // -------------------------------------------------------------------------
    async deletePage(params) {
        await this.makeConfluenceApiRequest(`/wiki/api/v2/pages/${encodeURIComponent(params.page_id)}`, 'DELETE');
        return {
            operation: 'delete_page',
            success: true,
            page_id: params.page_id,
            error: '',
        };
    }
    // -------------------------------------------------------------------------
    // OPERATION 8: search (uses v1 CQL endpoint)
    // -------------------------------------------------------------------------
    async search(params) {
        const queryParams = new URLSearchParams({
            cql: params.cql,
            limit: String(params.limit ?? 25),
            start: String(params.start ?? 0),
        });
        const response = await this.makeConfluenceV1ApiRequest(`/wiki/rest/api/search?${queryParams.toString()}`, 'GET');
        const rawResults = Array.isArray(response.results) ? response.results : [];
        const results = rawResults.map((r) => ({
            id: r.content?.id,
            type: r.content?.type,
            title: r.content?.title ?? r.title,
            status: r.content?.status,
            excerpt: r.excerpt,
            url: r.url,
            lastModified: r.lastModified,
            _links: r.content?._links,
            content: r.content,
        }));
        return {
            operation: 'search',
            success: true,
            results: results,
            total: (response.totalSize ?? response.size),
            start: response.start,
            limit: response.limit,
            error: '',
        };
    }
    // -------------------------------------------------------------------------
    // OPERATION 9: add_comment
    // -------------------------------------------------------------------------
    async addComment(params) {
        const requestBody = {
            pageId: params.page_id,
            body: {
                representation: 'storage',
                value: markdownToConfluenceStorage(params.body),
            },
        };
        const response = await this.makeConfluenceApiRequest('/wiki/api/v2/footer-comments', 'POST', requestBody);
        return {
            operation: 'add_comment',
            success: true,
            comment: {
                id: response.id,
                body: {
                    storage: {
                        value: params.body, // Return original text for readability
                        representation: 'storage',
                    },
                },
                createdAt: response.version?.createdAt,
            },
            error: '',
        };
    }
    // -------------------------------------------------------------------------
    // OPERATION 10: get_comments
    // -------------------------------------------------------------------------
    async getComments(params) {
        const queryParams = new URLSearchParams();
        queryParams.set('limit', String(params.limit ?? 25));
        queryParams.set('body-format', 'storage');
        if (params.cursor) {
            queryParams.set('cursor', params.cursor);
        }
        const response = await this.makeConfluenceApiRequest(`/wiki/api/v2/pages/${encodeURIComponent(params.page_id)}/footer-comments?${queryParams.toString()}`, 'GET');
        const results = Array.isArray(response.results) ? response.results : [];
        const links = response._links;
        const nextCursor = links?.next
            ? (new URL(links.next, 'https://placeholder.com').searchParams.get('cursor') ?? undefined)
            : undefined;
        // Convert storage format to plain text for each comment
        const comments = results.map((comment) => {
            const body = comment.body;
            if (body?.storage?.value) {
                comment.bodyText = storageToText(body.storage.value);
            }
            return comment;
        });
        return {
            operation: 'get_comments',
            success: true,
            comments: comments,
            cursor: nextCursor,
            error: '',
        };
    }
    chooseCredential() {
        const { credentials } = this.params;
        if (!credentials || typeof credentials !== 'object') {
            return undefined;
        }
        return credentials[CredentialType.CONFLUENCE_CRED];
    }
}
//# sourceMappingURL=confluence.js.map