import { ServiceBubble } from '../../../types/service-bubble-class.js';
import { CredentialType, decodeCredentialPayload, } from '@bubblelab/shared-schemas';
import { XeroParamsSchema, XeroResultSchema, } from './xero.schema.js';
/**
 * Xero Accounting Service Bubble
 *
 * Xero accounting integration for managing invoices, contacts, and accounts.
 *
 * Features:
 * - Create, retrieve, and list invoices (sales and purchase)
 * - Create, retrieve, and list contacts (customers and suppliers)
 * - List chart of accounts
 *
 * Use cases:
 * - Invoice creation and management
 * - Customer and supplier contact management
 * - Financial reporting and account tracking
 *
 * Security Features:
 * - OAuth 2.0 authentication with Xero
 * - Scoped access permissions
 * - Secure credential handling with tenant isolation
 */
export class XeroBubble extends ServiceBubble {
    static type = 'service';
    static service = 'xero';
    static authType = 'oauth';
    static bubbleName = 'xero';
    static schema = XeroParamsSchema;
    static resultSchema = XeroResultSchema;
    static shortDescription = 'Xero accounting integration for invoices, contacts, and accounts';
    static longDescription = `
    Xero accounting service integration for financial management.

    Features:
    - Create, retrieve, and list invoices (accounts receivable and payable)
    - Create, retrieve, and list contacts (customers and suppliers)
    - List chart of accounts with filtering
    - Multi-tenant support for managing multiple Xero organizations

    Use cases:
    - Automated invoice creation and tracking
    - Customer and supplier contact management
    - Financial data synchronization and reporting
    - Accounts payable and receivable automation

    Security Features:
    - OAuth 2.0 authentication with Xero
    - Scoped access permissions for accounting operations
    - Tenant-isolated API access
    - Secure credential handling and validation
  `;
    static alias = 'accounting';
    constructor(params = {
        operation: 'list_invoices',
    }, context) {
        super(params, context);
    }
    async testCredential() {
        const creds = this.parseCredentials();
        if (!creds) {
            throw new Error('Xero credentials are required');
        }
        const response = await fetch('https://api.xero.com/api.xro/2.0/Organisation', {
            headers: {
                Authorization: `Bearer ${creds.accessToken}`,
                'Xero-Tenant-Id': creds.tenantId,
                Accept: 'application/json',
            },
        });
        if (!response.ok) {
            const text = await response.text();
            throw new Error(`Xero API error (${response.status}): ${text}`);
        }
        return true;
    }
    /**
     * Xero credential format:
     * Base64-encoded JSON: { accessToken, tenantId }
     * The tenantId identifies which Xero organization to access.
     */
    parseCredentials() {
        const { credentials } = this.params;
        if (!credentials || typeof credentials !== 'object') {
            return null;
        }
        const xeroCredRaw = credentials[CredentialType.XERO_CRED];
        if (!xeroCredRaw) {
            return null;
        }
        try {
            const parsed = decodeCredentialPayload(xeroCredRaw);
            if (parsed.accessToken && parsed.tenantId) {
                return {
                    accessToken: parsed.accessToken,
                    tenantId: parsed.tenantId,
                };
            }
        }
        catch {
            // If decoding fails, treat the raw value as an access token (validator path)
            // In this case, we can't make API calls without tenantId
        }
        return null;
    }
    chooseCredential() {
        const creds = this.parseCredentials();
        return creds?.accessToken;
    }
    async makeXeroApiRequest(endpoint, method = 'GET', body) {
        const creds = this.parseCredentials();
        if (!creds) {
            throw new Error('Invalid Xero credentials. Expected base64-encoded JSON with { accessToken, tenantId }.');
        }
        const url = endpoint.startsWith('https://')
            ? endpoint
            : `https://api.xero.com/api.xro/2.0${endpoint}`;
        const requestInit = {
            method,
            headers: {
                Authorization: `Bearer ${creds.accessToken}`,
                'Xero-Tenant-Id': creds.tenantId,
                'Content-Type': 'application/json',
                Accept: 'application/json',
            },
        };
        if (body && method !== 'GET') {
            requestInit.body = JSON.stringify(body);
        }
        const response = await fetch(url, requestInit);
        if (!response.ok) {
            const errorText = await response.text();
            throw new Error(`Xero API error (${response.status}): ${errorText}`);
        }
        const contentType = response.headers.get('content-type');
        if (contentType && contentType.includes('application/json')) {
            return await response.json();
        }
        return await response.text();
    }
    async performAction(context) {
        void context;
        const { operation } = this.params;
        try {
            const result = await (async () => {
                const parsedParams = this.params;
                switch (operation) {
                    case 'create_invoice':
                        return await this.createInvoice(parsedParams);
                    case 'get_invoice':
                        return await this.getInvoice(parsedParams);
                    case 'list_invoices':
                        return await this.listInvoices(parsedParams);
                    case 'create_contact':
                        return await this.createContact(parsedParams);
                    case 'get_contact':
                        return await this.getContact(parsedParams);
                    case 'list_contacts':
                        return await this.listContacts(parsedParams);
                    case 'list_accounts':
                        return await this.listAccounts(parsedParams);
                    case 'get_report':
                        return await this.getReport(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',
            };
        }
    }
    async createInvoice(params) {
        const invoiceBody = {
            Type: params.type,
            Contact: { ContactID: params.contact_id },
            LineItems: params.line_items,
            Status: params.status || 'DRAFT',
        };
        if (params.date)
            invoiceBody.Date = params.date;
        if (params.due_date)
            invoiceBody.DueDate = params.due_date;
        if (params.reference)
            invoiceBody.Reference = params.reference;
        if (params.currency_code)
            invoiceBody.CurrencyCode = params.currency_code;
        const response = await this.makeXeroApiRequest('/Invoices', 'POST', invoiceBody);
        const invoice = response.Invoices?.[0];
        return {
            operation: 'create_invoice',
            success: true,
            invoice: invoice
                ? {
                    InvoiceID: invoice.InvoiceID,
                    InvoiceNumber: invoice.InvoiceNumber,
                    Type: invoice.Type,
                    Status: invoice.Status,
                    Contact: invoice.Contact,
                    Date: invoice.Date,
                    DueDate: invoice.DueDate,
                    Total: invoice.Total,
                    AmountDue: invoice.AmountDue,
                    AmountPaid: invoice.AmountPaid,
                    CurrencyCode: invoice.CurrencyCode,
                    Reference: invoice.Reference,
                    LineItems: invoice.LineItems,
                }
                : undefined,
            error: '',
        };
    }
    async getInvoice(params) {
        const response = await this.makeXeroApiRequest(`/Invoices/${params.invoice_id}`);
        const invoice = response.Invoices?.[0];
        return {
            operation: 'get_invoice',
            success: true,
            invoice: invoice
                ? {
                    InvoiceID: invoice.InvoiceID,
                    InvoiceNumber: invoice.InvoiceNumber,
                    Type: invoice.Type,
                    Status: invoice.Status,
                    Contact: invoice.Contact,
                    Date: invoice.Date,
                    DueDate: invoice.DueDate,
                    Total: invoice.Total,
                    AmountDue: invoice.AmountDue,
                    AmountPaid: invoice.AmountPaid,
                    CurrencyCode: invoice.CurrencyCode,
                    Reference: invoice.Reference,
                    LineItems: invoice.LineItems,
                }
                : undefined,
            error: '',
        };
    }
    async listInvoices(params) {
        const queryParams = new URLSearchParams();
        if (params.page)
            queryParams.set('page', String(params.page));
        if (params.where)
            queryParams.set('where', params.where);
        if (params.status) {
            const existingWhere = queryParams.get('where');
            const statusFilter = `Status=="${params.status}"`;
            queryParams.set('where', existingWhere ? `${existingWhere} AND ${statusFilter}` : statusFilter);
        }
        const queryString = queryParams.toString();
        const endpoint = `/Invoices${queryString ? `?${queryString}` : ''}`;
        const response = await this.makeXeroApiRequest(endpoint);
        return {
            operation: 'list_invoices',
            success: true,
            invoices: (response.Invoices || []).map((inv) => ({
                InvoiceID: inv.InvoiceID,
                InvoiceNumber: inv.InvoiceNumber,
                Type: inv.Type,
                Status: inv.Status,
                Contact: inv.Contact,
                Date: inv.Date,
                DueDate: inv.DueDate,
                Total: inv.Total,
                AmountDue: inv.AmountDue,
                AmountPaid: inv.AmountPaid,
                CurrencyCode: inv.CurrencyCode,
                Reference: inv.Reference,
            })),
            error: '',
        };
    }
    async createContact(params) {
        const contactBody = {
            Name: params.name,
        };
        if (params.email)
            contactBody.EmailAddress = params.email;
        if (params.first_name)
            contactBody.FirstName = params.first_name;
        if (params.last_name)
            contactBody.LastName = params.last_name;
        if (params.account_number)
            contactBody.AccountNumber = params.account_number;
        if (params.tax_number)
            contactBody.TaxNumber = params.tax_number;
        if (params.phone) {
            contactBody.Phones = [
                { PhoneType: 'DEFAULT', PhoneNumber: params.phone },
            ];
        }
        const response = await this.makeXeroApiRequest('/Contacts', 'POST', contactBody);
        const contact = response.Contacts?.[0];
        return {
            operation: 'create_contact',
            success: true,
            contact: contact
                ? {
                    ContactID: contact.ContactID,
                    Name: contact.Name,
                    FirstName: contact.FirstName,
                    LastName: contact.LastName,
                    EmailAddress: contact.EmailAddress,
                    AccountNumber: contact.AccountNumber,
                    TaxNumber: contact.TaxNumber,
                    ContactStatus: contact.ContactStatus,
                    Phones: contact.Phones,
                    Addresses: contact.Addresses,
                }
                : undefined,
            error: '',
        };
    }
    async getContact(params) {
        const response = await this.makeXeroApiRequest(`/Contacts/${params.contact_id}`);
        const contact = response.Contacts?.[0];
        return {
            operation: 'get_contact',
            success: true,
            contact: contact
                ? {
                    ContactID: contact.ContactID,
                    Name: contact.Name,
                    FirstName: contact.FirstName,
                    LastName: contact.LastName,
                    EmailAddress: contact.EmailAddress,
                    AccountNumber: contact.AccountNumber,
                    TaxNumber: contact.TaxNumber,
                    ContactStatus: contact.ContactStatus,
                    Phones: contact.Phones,
                    Addresses: contact.Addresses,
                }
                : undefined,
            error: '',
        };
    }
    async listContacts(params) {
        const queryParams = new URLSearchParams();
        if (params.page)
            queryParams.set('page', String(params.page));
        if (params.where)
            queryParams.set('where', params.where);
        const queryString = queryParams.toString();
        const endpoint = `/Contacts${queryString ? `?${queryString}` : ''}`;
        const response = await this.makeXeroApiRequest(endpoint);
        return {
            operation: 'list_contacts',
            success: true,
            contacts: (response.Contacts || []).map((c) => ({
                ContactID: c.ContactID,
                Name: c.Name,
                FirstName: c.FirstName,
                LastName: c.LastName,
                EmailAddress: c.EmailAddress,
                AccountNumber: c.AccountNumber,
                TaxNumber: c.TaxNumber,
                ContactStatus: c.ContactStatus,
                Phones: c.Phones,
                Addresses: c.Addresses,
            })),
            error: '',
        };
    }
    async listAccounts(params) {
        const queryParams = new URLSearchParams();
        if (params.type)
            queryParams.set('where', `Type=="${params.type}"`);
        const queryString = queryParams.toString();
        const endpoint = `/Accounts${queryString ? `?${queryString}` : ''}`;
        const response = await this.makeXeroApiRequest(endpoint);
        return {
            operation: 'list_accounts',
            success: true,
            accounts: (response.Accounts || []).map((a) => ({
                AccountID: a.AccountID,
                Code: a.Code,
                Name: a.Name,
                Type: a.Type,
                Status: a.Status,
                Description: a.Description,
                Class: a.Class,
                TaxType: a.TaxType,
            })),
            error: '',
        };
    }
    async getReport(params) {
        // Validate aged reports require contact_id
        if ((params.report_type === 'AgedReceivablesByContact' ||
            params.report_type === 'AgedPayablesByContact') &&
            !params.contact_id) {
            return {
                operation: 'get_report',
                success: false,
                error: `contact_id is required for ${params.report_type}`,
            };
        }
        const queryParams = new URLSearchParams();
        if (params.date)
            queryParams.set('date', params.date);
        if (params.from_date)
            queryParams.set('fromDate', params.from_date);
        if (params.to_date)
            queryParams.set('toDate', params.to_date);
        if (params.contact_id)
            queryParams.set('contactID', params.contact_id);
        if (params.payments_only)
            queryParams.set('paymentsOnly', 'true');
        if (params.periods)
            queryParams.set('periods', String(params.periods));
        // BudgetSummary uses numeric timeframe (1=month, 3=quarter, 12=year)
        if (params.timeframe) {
            if (params.report_type === 'BudgetSummary') {
                const numericTimeframe = params.timeframe === 'MONTH'
                    ? '1'
                    : params.timeframe === 'QUARTER'
                        ? '3'
                        : '12';
                queryParams.set('timeframe', numericTimeframe);
            }
            else {
                queryParams.set('timeframe', params.timeframe);
            }
        }
        if (params.tracking_category_id)
            queryParams.set('trackingCategoryID', params.tracking_category_id);
        if (params.tracking_option_id)
            queryParams.set('trackingOptionID', params.tracking_option_id);
        const queryString = queryParams.toString();
        const endpoint = `/Reports/${params.report_type}${queryString ? `?${queryString}` : ''}`;
        const response = await this.makeXeroApiRequest(endpoint);
        const report = response.Reports?.[0];
        if (!report) {
            return {
                operation: 'get_report',
                success: false,
                error: 'No report data returned from Xero',
            };
        }
        // Parse the abstract rows/cells structure into a cleaner format
        const parseRows = (rows) => (rows || []).map((row) => ({
            rowType: row.RowType,
            ...(row.Title ? { title: row.Title } : {}),
            ...(row.Cells
                ? {
                    cells: row.Cells.map((cell) => ({
                        value: cell.Value ?? '',
                        ...(cell.Attributes?.find((a) => a.Id === 'account')?.Value
                            ? {
                                accountId: cell.Attributes.find((a) => a.Id === 'account').Value,
                            }
                            : {}),
                    })),
                }
                : {}),
            ...(row.Rows ? { rows: parseRows(row.Rows) } : {}),
        }));
        return {
            operation: 'get_report',
            success: true,
            report: {
                reportName: report.ReportName,
                reportType: report.ReportType,
                reportDate: report.ReportDate,
                reportTitles: report.ReportTitles,
                rows: parseRows(report.Rows),
            },
            error: '',
        };
    }
}
//# sourceMappingURL=xero.js.map