import { ServiceBubble } from '../../../types/service-bubble-class.js';
import { CredentialType, decodeCredentialPayload, } from '@bubblelab/shared-schemas';
import { LookerParamsSchema, LookerResultSchema, } from './looker.schema.js';
const LOOKER_API_VERSION = '4.0';
/**
 * Looker Analytics Service Bubble
 *
 * Looker integration for querying LookML models, pulling saved Looks and
 * dashboard data via the Looker REST API v4.0. Uses Google OAuth for
 * authentication since Looker is a Google Cloud product.
 */
export class LookerBubble extends ServiceBubble {
    static type = 'service';
    static service = 'looker';
    static authType = 'oauth';
    static bubbleName = 'looker';
    static schema = LookerParamsSchema;
    static resultSchema = LookerResultSchema;
    static shortDescription = 'Looker analytics integration for LookML queries, Looks, and dashboards';
    static longDescription = `
    Looker analytics integration for querying data models and pulling dashboard content.

    Features:
    - Run inline queries against LookML models/explores with fields, filters, and sorts
    - Retrieve results from saved Looks by ID
    - Fetch dashboard metadata, filters, and tile definitions
    - Discover available LookML models and their explores
    - List saved Looks and dashboards

    Security Features:
    - Google OAuth 2.0 authentication (Looker is a Google Cloud product)
    - Instance-scoped access via per-client Looker URLs
    - Secure credential handling with base64-encoded payloads
  `;
    static alias = '';
    constructor(params = {
        operation: 'list_models',
        instance_url: '',
    }, context) {
        super(params, context);
    }
    async testCredential() {
        const creds = this.parseCredentials();
        if (!creds) {
            throw new Error('Looker credentials are required');
        }
        const baseUrl = this.getBaseUrl();
        const response = await fetch(`${baseUrl}/api/${LOOKER_API_VERSION}/user`, {
            headers: {
                Authorization: `Bearer ${creds.accessToken}`,
                Accept: 'application/json',
            },
        });
        if (!response.ok) {
            const text = await response.text();
            throw new Error(`Looker API error (${response.status}): ${text}`);
        }
        return true;
    }
    parseCredentials() {
        const { credentials } = this.params;
        if (!credentials || typeof credentials !== 'object') {
            return null;
        }
        const raw = credentials[CredentialType.LOOKER_CRED];
        if (!raw) {
            return null;
        }
        try {
            const parsed = decodeCredentialPayload(raw);
            if (parsed.accessToken) {
                return { accessToken: parsed.accessToken };
            }
        }
        catch {
            // If decoding fails, treat the raw value as an access token (validator path)
        }
        return { accessToken: raw };
    }
    chooseCredential() {
        const creds = this.parseCredentials();
        return creds?.accessToken;
    }
    getBaseUrl() {
        const p = this.params;
        let url = p.instance_url || '';
        // Strip trailing slashes and /api paths
        url = url.replace(/\/+$/, '').replace(/\/api\/.*$/, '');
        // Ensure https:// prefix
        if (!url.startsWith('http://') && !url.startsWith('https://')) {
            url = `https://${url}`;
        }
        return url;
    }
    async lookerRequest(endpoint, method = 'GET', body) {
        const creds = this.parseCredentials();
        if (!creds) {
            throw new Error('Looker credentials are required');
        }
        const baseUrl = this.getBaseUrl();
        const url = `${baseUrl}/api/${LOOKER_API_VERSION}${endpoint}`;
        const init = {
            method,
            headers: {
                Authorization: `Bearer ${creds.accessToken}`,
                'Content-Type': 'application/json',
                Accept: 'application/json',
            },
        };
        if (body && method !== 'GET') {
            init.body = JSON.stringify(body);
        }
        const response = await fetch(url, init);
        if (!response.ok) {
            const errorText = await response.text();
            throw new Error(`Looker API error (${response.status}): ${errorText}`);
        }
        if (response.status === 204) {
            return undefined;
        }
        return await response.json();
    }
    async performAction(context) {
        void context;
        const { operation } = this.params;
        try {
            const result = await (async () => {
                const p = this.params;
                switch (operation) {
                    case 'run_inline_query':
                        return await this.runInlineQuery(p);
                    case 'get_look':
                        return await this.getLook(p);
                    case 'get_dashboard':
                        return await this.getDashboard(p);
                    case 'list_models':
                        return await this.listModels();
                    case 'list_explores':
                        return await this.listExplores(p);
                    case 'list_looks':
                        return await this.listLooks(p);
                    case 'list_dashboards':
                        return await this.listDashboards(p);
                    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 Implementations ---
    async runInlineQuery(params) {
        const { model, explore, fields, filters, sorts, limit } = params;
        const queryBody = {
            model,
            view: explore,
            fields,
            limit: String(limit),
        };
        if (filters && Object.keys(filters).length > 0) {
            queryBody.filters = filters;
        }
        if (sorts && sorts.length > 0) {
            queryBody.sorts = sorts;
        }
        const data = (await this.lookerRequest('/queries/run/json', 'POST', queryBody));
        return {
            operation: 'run_inline_query',
            success: true,
            data,
            rowCount: data.length,
            error: '',
        };
    }
    async getLook(params) {
        const { look_id, limit, result_format } = params;
        // Get the Look metadata
        const lookMeta = (await this.lookerRequest(`/looks/${look_id}`));
        // Run the Look to get results
        const data = (await this.lookerRequest(`/looks/${look_id}/run/${result_format}?limit=${limit}`));
        return {
            operation: 'get_look',
            success: true,
            data,
            look: {
                id: look_id,
                title: lookMeta.title,
                description: lookMeta.description,
                model: lookMeta.model,
                space: lookMeta.space,
            },
            rowCount: data.length,
            error: '',
        };
    }
    async getDashboard(params) {
        const { dashboard_id } = params;
        const dashboard = (await this.lookerRequest(`/dashboards/${dashboard_id}`));
        return {
            operation: 'get_dashboard',
            success: true,
            dashboard: {
                id: String(dashboard.id),
                title: dashboard.title,
                description: dashboard.description,
                dashboard_filters: dashboard.dashboard_filters,
                dashboard_elements: dashboard.dashboard_elements,
            },
            error: '',
        };
    }
    async listModels() {
        const models = (await this.lookerRequest('/lookml_models'));
        return {
            operation: 'list_models',
            success: true,
            models: models.map((m) => ({
                name: m.name,
                label: m.label,
                explores: m.explores?.map((e) => ({
                    name: e.name,
                    label: e.label,
                    description: e.description,
                })),
            })),
            error: '',
        };
    }
    async listExplores(params) {
        const { model } = params;
        const modelData = (await this.lookerRequest(`/lookml_models/${model}`));
        const explores = modelData.explores || [];
        // Fetch detailed field info for each explore
        const detailedExplores = await Promise.all(explores.map(async (e) => {
            const exploreName = e.name;
            try {
                const detail = (await this.lookerRequest(`/lookml_models/${model}/explores/${exploreName}`));
                const fields = detail.fields;
                return {
                    name: exploreName,
                    label: (detail.label || e.label),
                    description: (detail.description || e.description),
                    fields: fields
                        ? {
                            dimensions: fields.dimensions?.map((d) => ({
                                name: d.name,
                                label: d.label,
                                type: d.type,
                                description: d.description,
                            })),
                            measures: fields.measures?.map((m) => ({
                                name: m.name,
                                label: m.label,
                                type: m.type,
                                description: m.description,
                            })),
                        }
                        : undefined,
                };
            }
            catch {
                // If detail fetch fails, return basic info
                return {
                    name: exploreName,
                    label: e.label,
                    description: e.description,
                };
            }
        }));
        return {
            operation: 'list_explores',
            success: true,
            explores: detailedExplores,
            model,
            error: '',
        };
    }
    async listLooks(params) {
        const { limit } = params;
        const looks = (await this.lookerRequest(`/looks?limit=${limit}`));
        return {
            operation: 'list_looks',
            success: true,
            looks: looks.map((l) => ({
                id: l.id,
                title: l.title,
                description: l.description,
                model: l.model,
                space: l.space,
            })),
            totalCount: looks.length,
            error: '',
        };
    }
    async listDashboards(params) {
        const { limit } = params;
        const dashboards = (await this.lookerRequest(`/dashboards?limit=${limit}`));
        return {
            operation: 'list_dashboards',
            success: true,
            dashboards: dashboards.map((d) => ({
                id: String(d.id),
                title: d.title,
                description: d.description,
                folder: d.folder,
            })),
            totalCount: dashboards.length,
            error: '',
        };
    }
}
//# sourceMappingURL=looker.js.map