"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ApiProjectSchema = exports.ApiUserSchema = exports.PROJECT_NAME_MAX_LENGTH = exports.ApiError = void 0;
exports.extractAppId = extractAppId;
exports.fetchAmplitudeUser = fetchAmplitudeUser;
exports.validateProjectName = validateProjectName;
exports.getWizardProxyBase = getWizardProxyBase;
exports.createAmplitudeApp = createAmplitudeApp;
exports.fetchBranches = fetchBranches;
exports.fetchProjectEventTypes = fetchProjectEventTypes;
exports.fetchOwnedDashboards = fetchOwnedDashboards;
exports.fetchSources = fetchSources;
exports.fetchHasAnyEventsMcp = fetchHasAnyEventsMcp;
exports.fetchProjectActivationStatus = fetchProjectActivationStatus;
exports.fetchSlackInstallUrl = fetchSlackInstallUrl;
exports.fetchSlackConnectionStatus = fetchSlackConnectionStatus;
exports.fetchUserData = fetchUserData;
exports.fetchProjectData = fetchProjectData;
const axios_1 = __importDefault(require("axios"));
const zod_1 = require("zod");
const analytics_js_1 = require("../utils/analytics.js");
const debug_js_1 = require("../utils/debug.js");
const constants_js_1 = require("./constants.js");
const mcp_with_fallback_js_1 = require("./mcp-with-fallback.js");
const urls_js_1 = require("../utils/urls.js");
// ── App API URL helper ────────────────────────────────────────────────
/**
 * Builds the org-scoped App API GraphQL endpoint for the given zone and orgId.
 */
function appApiUrl(zone, orgId, queryName) {
    const base = constants_js_1.AMPLITUDE_ZONE_SETTINGS[zone].appApiUrlBase;
    // Ensure the base ends with /graphql/org/ so we can append orgId.
    const graphqlBase = base.endsWith('/graphql/org/')
        ? base
        : `${base.replace(/\/$/, '')}/graphql/org/`;
    const url = `${graphqlBase}${orgId}`;
    return queryName ? `${url}?q=${queryName}` : url;
}
// ── Amplitude GraphQL types ───────────────────────────────────────────
const AmplitudeUserSchema = zod_1.z.object({
    data: zod_1.z.object({
        orgs: zod_1.z.array(zod_1.z.object({
            id: zod_1.z.string(),
            name: zod_1.z.string(),
            user: zod_1.z.object({
                id: zod_1.z.string(),
                firstName: zod_1.z.string(),
                lastName: zod_1.z.string(),
                email: zod_1.z.string(),
            }),
            workspaces: zod_1.z.array(zod_1.z.object({
                id: zod_1.z.string(),
                name: zod_1.z.string(),
                environments: zod_1.z
                    .array(zod_1.z.object({
                    name: zod_1.z.string(),
                    rank: zod_1.z.number(),
                    app: zod_1.z
                        .object({
                        id: zod_1.z.string(),
                        apiKey: zod_1.z.string().nullable().optional(),
                    })
                        .nullable(),
                }))
                    .nullable()
                    .optional(),
            })),
        })),
    }),
});
/**
 * Extract the primary Amplitude app ID from a project.
 * Picks the lowest-rank environment that has an app ID.
 * Returns null if no such environment exists.
 *
 * Note: "app" is the canonical term for the ingestion surface that owns an
 * API key, per amplitude/amplitude (`app_id`) and amplitude/javascript
 * (`App` GraphQL type). Amplitude's UI also labels this "Project ID" in
 * some places — it's the numeric app ID, not the project layer above it.
 */
function extractAppId(project) {
    return ((project.environments ?? [])
        .slice()
        .sort((a, b) => a.rank - b.rank)
        .find((e) => e.app?.id)?.app?.id ?? null);
}
const ORGS_QUERY = `
query orgs {
  orgs {
    id
    name
    user {
      id
      firstName
      lastName
      email
    }
    workspaces {
      id
      name
      environments {
        name
        rank
        app { id apiKey }
      }
    }
  }
}`;
class ApiError extends Error {
    statusCode;
    endpoint;
    code;
    constructor(message, statusCode, endpoint, code) {
        super(message);
        this.statusCode = statusCode;
        this.endpoint = endpoint;
        this.code = code;
        this.name = 'ApiError';
    }
}
exports.ApiError = ApiError;
/**
 * Fetches the authenticated user's org/project info from Amplitude's Data API.
 * Uses the OAuth id_token as the Authorization header (same as ampli CLI).
 *
 * The backend GraphQL response carries a `workspaces` field; we rename it to
 * `projects` at this boundary so the rest of the wizard only sees the
 * user-facing terminology.
 */
async function fetchAmplitudeUser(idToken, zone) {
    const { dataApiUrl } = constants_js_1.AMPLITUDE_ZONE_SETTINGS[zone];
    try {
        const response = await axios_1.default.post(dataApiUrl, { query: ORGS_QUERY }, {
            headers: {
                Authorization: idToken,
                'Content-Type': 'application/json',
                'User-Agent': constants_js_1.WIZARD_USER_AGENT,
            },
        });
        const parsed = AmplitudeUserSchema.parse(response.data);
        const orgs = parsed.data.orgs;
        const user = orgs[0]?.user;
        if (!user)
            throw new ApiError('No user data returned from Amplitude API');
        return {
            id: user.id,
            firstName: user.firstName,
            lastName: user.lastName,
            email: user.email,
            orgs: orgs.map((org) => ({
                id: org.id,
                name: org.name,
                projects: org.workspaces,
            })),
        };
    }
    catch (error) {
        const apiError = handleApiError(error, 'fetch Amplitude user data');
        analytics_js_1.analytics.captureException(apiError, { endpoint: dataApiUrl });
        throw apiError;
    }
}
const CreateProjectSuccessSchema = zod_1.z.object({
    appId: zod_1.z.string(),
    apiKey: zod_1.z.string(),
    name: zod_1.z.string(),
});
const CreateProjectErrorSchema = zod_1.z.object({
    error: zod_1.z.object({
        code: zod_1.z.enum([
            'NAME_TAKEN',
            'QUOTA_REACHED',
            'FORBIDDEN',
            'INVALID_REQUEST',
            'INTERNAL',
        ]),
        message: zod_1.z.string(),
    }),
});
function fallbackCreateProjectErrorForStatus(status, url) {
    // If the backend error body is unavailable or malformed, preserve the
    // HTTP-specific meaning so callers don't receive a vague generic error.
    if (status === 400) {
        return new ApiError('Invalid request while creating project', status, url, 'INVALID_REQUEST');
    }
    if (status === 402) {
        return new ApiError('Your org has reached its project quota. Create one in Amplitude settings and try again.', status, url, 'QUOTA_REACHED');
    }
    if (status === 403) {
        return new ApiError("You don't have permission to create projects in this org.", status, url, 'FORBIDDEN');
    }
    if (status === 409) {
        return new ApiError('A project with this name already exists.', status, url, 'NAME_TAKEN');
    }
    if (status === 429) {
        return new ApiError('Rate limited while creating project. Please wait a moment and retry.', status, url, 'INTERNAL');
    }
    return new ApiError(`Failed to create project (HTTP ${status})`, status, url, 'INTERNAL');
}
/** Max project-name length accepted by the backend (inclusive). */
exports.PROJECT_NAME_MAX_LENGTH = 255;
/**
 * Local-only validation for a project name. Mirrors the backend rules
 * (1–255 chars trimmed, no control chars) so the UI can reject bad input
 * without a round-trip.
 */
function validateProjectName(name) {
    const trimmed = name.trim();
    if (trimmed.length === 0) {
        return { code: 'empty', message: 'Project name cannot be empty.' };
    }
    if (trimmed.length > exports.PROJECT_NAME_MAX_LENGTH) {
        return {
            code: 'too_long',
            message: `Project name must be ${exports.PROJECT_NAME_MAX_LENGTH} characters or fewer.`,
        };
    }
    // Reject ASCII control characters (C0 + DEL) — names are shown in UI and URLs.
    // eslint-disable-next-line no-control-regex
    if (/[\x00-\x1f\x7f]/.test(trimmed)) {
        return {
            code: 'control_chars',
            message: 'Project name cannot contain control characters.',
        };
    }
    return null;
}
/**
 * Derive the wizard proxy base URL (e.g. `https://core.amplitude.com/wizard`)
 * from a zone by starting from the API host and stripping any trailing
 * `/v1/messages` suffix the gateway appends for the Claude SDK.
 *
 * Exported so callers + tests can assert the exact endpoint.
 */
function getWizardProxyBase(zone) {
    const gateway = (0, urls_js_1.getLlmGatewayUrlFromHost)((0, urls_js_1.getHostFromRegion)(zone));
    // getLlmGatewayUrlFromHost returns the base without `/v1/messages`, but if a
    // WIZARD_LLM_PROXY_URL override includes it we need to strip it so the base
    // stays consistent.
    return gateway.replace(/\/v1\/messages\/?$/, '').replace(/\/$/, '');
}
/**
 * Create a new Amplitude analytics project (app) in the given org via the
 * wizard proxy.
 *
 * - Expects an OAuth *access token* (not the id_token) — sent as
 *   `Authorization: Bearer <token>`. The wizard-proxy validates it
 *   against Hydra introspection, which only accepts access tokens.
 * - Errors from the backend are surfaced as `ApiError` with `code` set to one
 *   of `CreateProjectErrorCode` so callers can branch (NAME_TAKEN → retry,
 *   QUOTA_REACHED → fallback, etc.).
 *
 * Returns `{ appId, apiKey, name }` on success. `apiKey` is sensitive — never
 * log it, and redact it from any analytics/NDJSON output.
 */
async function createAmplitudeApp(accessToken, zone, input) {
    const base = getWizardProxyBase(zone);
    const url = `${base}/projects`;
    // Validate locally first so we fail fast and don't hit the network with a
    // payload the backend will reject.
    const issue = validateProjectName(input.name);
    if (issue) {
        throw new ApiError(issue.message, 400, url, 'INVALID_REQUEST');
    }
    if (!input.orgId || input.orgId.trim() === '') {
        throw new ApiError('orgId is required', 400, url, 'INVALID_REQUEST');
    }
    try {
        const response = await axios_1.default.post(url, {
            orgId: input.orgId,
            name: input.name.trim(),
            // Only send description when provided to keep the payload minimal.
            ...(input.description ? { description: input.description } : {}),
        }, {
            headers: {
                // The wizard-proxy auth middleware introspects via Hydra, which
                // only accepts OAuth access tokens (not id_tokens), sent with
                // the `Bearer ` prefix.
                Authorization: `Bearer ${accessToken}`,
                'Content-Type': 'application/json',
                'User-Agent': constants_js_1.WIZARD_USER_AGENT,
            },
            timeout: 20_000,
        });
        const parsed = CreateProjectSuccessSchema.parse(response.data);
        // Best-effort analytics — `apiKey` intentionally omitted so it never
        // leaves this function in plaintext.
        analytics_js_1.analytics.wizardCapture('Project Created', {
            source: 'wizard_cli',
            app_id: parsed.appId,
            zone,
            org_id: input.orgId,
        });
        return parsed;
    }
    catch (error) {
        // Re-throw our own ApiError instances untouched.
        if (error instanceof ApiError) {
            analytics_js_1.analytics.captureException(error, { endpoint: url, code: error.code });
            throw error;
        }
        // Axios errors include non-2xx responses and network failures.
        if (axios_1.default.isAxiosError(error)) {
            const status = error.response?.status;
            if (status) {
                // Preferred path: parse the backend's structured error payload.
                const errBody = CreateProjectErrorSchema.safeParse(error.response?.data);
                if (errBody.success) {
                    const { code, message } = errBody.data.error;
                    const apiError = new ApiError(message, status, url, code);
                    analytics_js_1.analytics.captureException(apiError, { endpoint: url, code });
                    throw apiError;
                }
                // If the payload shape is unexpected, preserve HTTP semantics (e.g. 429).
                const apiError = fallbackCreateProjectErrorForStatus(status, url);
                analytics_js_1.analytics.captureException(apiError, {
                    endpoint: url,
                    code: apiError.code,
                });
                throw apiError;
            }
            // Network failures (DNS, timeout, reset, etc.) have no HTTP response.
            const apiError = new ApiError(error.message || 'Network error creating project', undefined, url, 'INTERNAL');
            analytics_js_1.analytics.captureException(apiError, { endpoint: url, code: 'INTERNAL' });
            throw apiError;
        }
        if (error instanceof zod_1.z.ZodError) {
            const apiError = new ApiError('Invalid response from create-project endpoint', undefined, url, 'INTERNAL');
            analytics_js_1.analytics.captureException(apiError, { endpoint: url, code: 'INTERNAL' });
            throw apiError;
        }
        const apiError = new ApiError(`Unexpected error creating project: ${error instanceof Error ? error.message : 'Unknown error'}`, undefined, url, 'INTERNAL');
        analytics_js_1.analytics.captureException(apiError, { endpoint: url, code: 'INTERNAL' });
        throw apiError;
    }
}
// ── Branches ──────────────────────────────────────────────────────────────
const BranchesSchema = zod_1.z.object({
    data: zod_1.z.object({
        orgs: zod_1.z.array(zod_1.z.object({
            workspaces: zod_1.z.array(zod_1.z.object({
                branches: zod_1.z.array(zod_1.z.object({
                    id: zod_1.z.string(),
                    name: zod_1.z.string(),
                    default: zod_1.z.boolean(),
                    currentVersionId: zod_1.z.string(),
                })),
            })),
        })),
    }),
});
const BRANCHES_QUERY = `
query branches($orgId: ID!, $projectId: ID!) {
  orgs(id: $orgId) {
    workspaces(id: $projectId) {
      branches {
        id
        name
        default
        currentVersionId
      }
    }
  }
}`;
/** Fetches branches for a project. */
async function fetchBranches(idToken, zone, orgId, projectId) {
    const { dataApiUrl } = constants_js_1.AMPLITUDE_ZONE_SETTINGS[zone];
    try {
        const response = await axios_1.default.post(dataApiUrl, { query: BRANCHES_QUERY, variables: { orgId, projectId } }, {
            headers: {
                Authorization: idToken,
                'Content-Type': 'application/json',
                'User-Agent': constants_js_1.WIZARD_USER_AGENT,
            },
        });
        const parsed = BranchesSchema.parse(response.data);
        return parsed.data.orgs[0]?.workspaces[0]?.branches ?? [];
    }
    catch (error) {
        const apiError = handleApiError(error, 'fetch branches');
        analytics_js_1.analytics.captureException(apiError, { endpoint: dataApiUrl });
        throw apiError;
    }
}
// ── Project event types ──────────────────────────────────────────────────
const ProjectEventsSchema = zod_1.z.object({
    data: zod_1.z.object({
        orgs: zod_1.z.array(zod_1.z.object({
            workspaces: zod_1.z.array(zod_1.z.object({
                branches: zod_1.z.array(zod_1.z.object({
                    versions: zod_1.z.array(zod_1.z.object({
                        events: zod_1.z.array(zod_1.z.object({ id: zod_1.z.string(), name: zod_1.z.string() })),
                    })),
                })),
            })),
        })),
    }),
});
const PROJECT_EVENTS_QUERY = `
query projectEvents($orgId: ID!, $projectId: ID!, $branchId: ID!, $versionId: ID!) {
  orgs(id: $orgId) {
    workspaces(id: $projectId) {
      branches(id: $branchId) {
        versions(id: $versionId) {
          events { id name }
        }
      }
    }
  }
}`;
/**
 * Fetches the event type names cataloged in the default branch of a project.
 * Returns an empty array if the project has no events or the query fails.
 */
async function fetchProjectEventTypes(idToken, zone, orgId, projectId) {
    const { dataApiUrl } = constants_js_1.AMPLITUDE_ZONE_SETTINGS[zone];
    try {
        // Step 1: get default branch + its current version
        const branches = await fetchBranches(idToken, zone, orgId, projectId);
        const defaultBranch = branches.find((b) => b.default) ?? branches[0];
        if (!defaultBranch)
            return [];
        // Step 2: fetch events for that version
        const response = await axios_1.default.post(dataApiUrl, {
            query: PROJECT_EVENTS_QUERY,
            variables: {
                orgId,
                projectId,
                branchId: defaultBranch.id,
                versionId: defaultBranch.currentVersionId,
            },
        }, {
            headers: {
                Authorization: idToken,
                'Content-Type': 'application/json',
                'User-Agent': constants_js_1.WIZARD_USER_AGENT,
            },
        });
        const parsed = ProjectEventsSchema.parse(response.data);
        return (parsed.data.orgs[0]?.workspaces[0]?.branches[0]?.versions[0]?.events.map((e) => e.name) ?? []);
    }
    catch {
        return [];
    }
}
// ── Owned dashboards ──────────────────────────────────────────────────────────
const OwnedDashboardsSchema = zod_1.z.object({
    data: zod_1.z.object({
        ownedDashboards: zod_1.z.array(zod_1.z.object({
            id: zod_1.z.string(),
            chartIds: zod_1.z.array(zod_1.z.string()),
        })),
    }),
});
const OWNED_DASHBOARDS_QUERY = `
query OwnedDashboards {
  ownedDashboards {
    id
    chartIds
  }
}`;
/**
 * Checks whether the authenticated user has any charts or dashboards in their org.
 * Uses the App API org-scoped GraphQL endpoint.
 *
 * Detection is org-scoped — App API has no project-level chart/dashboard listing
 * API. For a typical new-project user this is equivalent to project-scoped.
 *
 * Returns { hasCharts: false, hasDashboards: false } on any error so the
 * checklist falls back to the default empty state rather than crashing.
 */
async function fetchOwnedDashboards(accessToken, zone, orgId) {
    const url = appApiUrl(zone, orgId, 'OwnedDashboards');
    try {
        const response = await axios_1.default.post(url, { query: OWNED_DASHBOARDS_QUERY }, {
            headers: {
                'x-amp-authorization': `Bearer ${accessToken}`,
                'Content-Type': 'application/json',
                'User-Agent': constants_js_1.WIZARD_USER_AGENT,
            },
        });
        const parsed = OwnedDashboardsSchema.parse(response.data);
        const dashboards = parsed.data.ownedDashboards;
        return {
            hasDashboards: dashboards.length > 0,
            hasCharts: dashboards.some((d) => d.chartIds.length > 0),
        };
    }
    catch {
        return { hasCharts: false, hasDashboards: false };
    }
}
// ── Sources ───────────────────────────────────────────────────────────────
const SourcesSchema = zod_1.z.object({
    data: zod_1.z.object({
        orgs: zod_1.z.array(zod_1.z.object({
            workspaces: zod_1.z.array(zod_1.z.object({
                branches: zod_1.z.array(zod_1.z.object({
                    versions: zod_1.z.array(zod_1.z.object({
                        id: zod_1.z.string(),
                        sources: zod_1.z.array(zod_1.z.object({
                            id: zod_1.z.string(),
                            name: zod_1.z.string(),
                            runtime: zod_1.z
                                .object({
                                id: zod_1.z.string(),
                                platformId: zod_1.z.string(),
                                platformName: zod_1.z.string(),
                                languageId: zod_1.z.string(),
                                languageName: zod_1.z.string(),
                            })
                                .nullable(),
                            destinations: zod_1.z.array(zod_1.z.object({ name: zod_1.z.string(), serviceId: zod_1.z.string() })),
                        })),
                    })),
                })),
            })),
        })),
    }),
});
const SOURCES_QUERY = `
query sources($orgId: ID!, $projectId: ID!, $branchId: ID!, $versionId: ID!) {
  orgs(id: $orgId) {
    workspaces(id: $projectId) {
      branches(id: $branchId) {
        versions(id: $versionId) {
          id
          sources {
            id
            name
            runtime {
              id
              platformId
              platformName
              languageId
              languageName
            }
            destinations { name serviceId }
          }
        }
      }
    }
  }
}`;
/** Fetches sources for a specific branch version. */
async function fetchSources(idToken, zone, orgId, projectId, branchId, versionId) {
    const { dataApiUrl } = constants_js_1.AMPLITUDE_ZONE_SETTINGS[zone];
    try {
        const response = await axios_1.default.post(dataApiUrl, {
            query: SOURCES_QUERY,
            variables: { orgId, projectId, branchId, versionId },
        }, {
            headers: {
                Authorization: idToken,
                'Content-Type': 'application/json',
                'User-Agent': constants_js_1.WIZARD_USER_AGENT,
            },
        });
        const parsed = SourcesSchema.parse(response.data);
        return (parsed.data.orgs[0]?.workspaces[0]?.branches[0]?.versions[0]?.sources ??
            []);
    }
    catch (error) {
        const apiError = handleApiError(error, 'fetch sources');
        analytics_js_1.analytics.captureException(apiError, { endpoint: dataApiUrl });
        throw apiError;
    }
}
/**
 * Checks whether the project has received any events via the Amplitude MCP server.
 * Uses get_users with `_all` as the primary signal (userCount > 0 → events exist),
 * then fetches get_events for display names.
 *
 * Falls back to a Claude agent with the Amplitude MCP configured if the direct
 * HTTP call fails, so the check survives MCP API drift.
 *
 * Requires the numeric Amplitude app ID (from project.environments[].app.id),
 * not the project UUID. The downstream Amplitude MCP tool API still accepts
 * this as a `projectId` parameter (external contract we don't control).
 * Returns false on any error so callers can fall through.
 */
async function fetchHasAnyEventsMcp(accessToken, appId, 
/**
 * Optional abort signal — propagated to the underlying MCP fetch so the
 * caller can cancel an in-flight ingestion poll on shutdown / Ctrl+C
 * instead of letting it run to completion in the background.
 */
abortSignal) {
    const NONE = {
        hasEvents: false,
        csvRows: [],
        activeEventNames: [],
        activeUsers: [],
    };
    const result = await (0, mcp_with_fallback_js_1.callAmplitudeMcp)({
        accessToken,
        abortSignal,
        label: 'fetchHasAnyEventsMcp',
        direct: async (callTool) => {
            // get_users with _all — primary signal for whether any events have been received.
            // '_all' covers every event type without requiring taxonomy setup.
            // metadata.userCount > 0 means at least one device has sent events to this app.
            // NOTE: MCP tool param name is `projectId` (their API) — we pass our appId.
            const usersText = await callTool(1, 'get_users', {
                projectId: appId,
                event: { event_type: '_all', filters: [] },
                limit: 5,
            });
            if (!usersText) {
                (0, debug_js_1.logToFile)('[MCP] get_users: no text in response');
                return null;
            }
            let activeUsers;
            let hasEvents;
            try {
                const parsed = JSON.parse(usersText);
                const userCount = parsed.metadata?.userCount ?? parsed.users?.length ?? 0;
                hasEvents = userCount > 0;
                activeUsers = (parsed.users ?? [])
                    .filter((u) => u.amplitudeId ?? u.amplitude_id)
                    .map((u) => ({
                    amplitudeId: (u.amplitudeId ?? u.amplitude_id),
                    userId: u.user_id ?? null,
                }))
                    .slice(0, 5);
                (0, debug_js_1.logToFile)(`[MCP] get_users: userCount=${userCount}, hasEvents=${hasEvents}`);
            }
            catch (parseErr) {
                (0, debug_js_1.logToFile)(`[MCP] get_users parse error: ${parseErr instanceof Error ? parseErr.message : String(parseErr)}`);
                return null;
            }
            if (!hasEvents)
                return NONE;
            // get_events — fetch active event names for the celebration display.
            // isActive=true means events arrived within ~30 days.
            const eventsText = await callTool(2, 'get_events', {
                projectId: appId,
                limit: 10,
            });
            let activeEventNames = [];
            if (eventsText) {
                try {
                    const parsed = JSON.parse(eventsText);
                    activeEventNames = (parsed.events ?? [])
                        .filter((e) => e.isActive && !e.isHidden && !e.isDeleted && e.name)
                        .map((e) => e.name)
                        .slice(0, 10);
                    (0, debug_js_1.logToFile)(`[MCP] get_events: ${activeEventNames.length} active events`);
                }
                catch (parseErr) {
                    (0, debug_js_1.logToFile)(`[MCP] get_events parse error: ${parseErr instanceof Error ? parseErr.message : String(parseErr)}`);
                }
            }
            return { hasEvents: true, csvRows: [], activeEventNames, activeUsers };
        },
        agentPrompt: `Use the Amplitude MCP to check whether app ${appId} has received any events.
Call get_users with event_type "_all" and limit 5. If userCount > 0, also call get_events with limit 10.
Respond with JSON only — no prose, no markdown fences:
{"hasEvents":true/false,"activeEventNames":["..."],"activeUsers":[{"amplitudeId":"...","userId":"..."}]}`,
        parseAgent: (text) => {
            const match = text.match(/\{[\s\S]*\}/);
            if (!match)
                return null;
            try {
                const parsed = JSON.parse(match[0]);
                return {
                    hasEvents: parsed.hasEvents ?? false,
                    csvRows: [],
                    activeEventNames: parsed.activeEventNames ?? [],
                    activeUsers: (parsed.activeUsers ?? []).map((u) => ({
                        amplitudeId: u.amplitudeId ?? '',
                        userId: u.userId ?? null,
                    })),
                };
            }
            catch {
                return null;
            }
        },
    });
    return result ?? NONE;
}
// ── Project activation status ──────────────────────────────────────────────
const ActivationStatusSchema = zod_1.z.object({
    data: zod_1.z.object({
        hasAnyDefaultEventTrackingSourceAndEvents: zod_1.z.object({
            hasDetSource: zod_1.z.boolean(),
            hasPageViewedEvent: zod_1.z.boolean(),
            hasSessionStartEvent: zod_1.z.boolean(),
            hasSessionEndEvent: zod_1.z.boolean(),
        }),
    }),
});
const ACTIVATION_STATUS_QUERY = `
query hasAnyDefaultEventTrackingSourceAndEvents($appId: ID!) {
  hasAnyDefaultEventTrackingSourceAndEvents(appId: $appId) {
    hasDetSource
    hasPageViewedEvent
    hasSessionStartEvent
    hasSessionEndEvent
  }
}`;
/**
 * Checks whether an Amplitude project has ingested any events and whether
 * the SDK snippet is configured.
 *
 * Always routes to App API (the main Amplitude app GraphQL server) at
 * /t/graphql/org/:orgId using a Bearer access_token.
 */
async function fetchProjectActivationStatus(opts) {
    const { accessToken, zone, appId, orgId } = opts;
    const url = appApiUrl(zone, orgId, 'hasAnyDefaultEventTrackingSourceAndEvents');
    try {
        const response = await axios_1.default.post(url, { query: ACTIVATION_STATUS_QUERY, variables: { appId: String(appId) } }, {
            headers: {
                'x-amp-authorization': `Bearer ${accessToken}`,
                'Content-Type': 'application/json',
                'User-Agent': constants_js_1.WIZARD_USER_AGENT,
            },
        });
        const parsed = ActivationStatusSchema.parse(response.data);
        const s = parsed.data.hasAnyDefaultEventTrackingSourceAndEvents;
        return {
            ...s,
            hasAnyEvents: s.hasPageViewedEvent || s.hasSessionStartEvent || s.hasSessionEndEvent,
        };
    }
    catch (error) {
        const apiError = handleApiError(error, 'fetch project activation status');
        analytics_js_1.analytics.captureException(apiError, { endpoint: url });
        throw apiError;
    }
}
// ── Slack install URL ─────────────────────────────────────────────────────
const SlackInstallUrlSchema = zod_1.z.object({
    data: zod_1.z.object({
        slackInstallUrl: zod_1.z.object({
            installUrl: zod_1.z.string().url(),
        }),
    }),
});
const SLACK_INSTALL_URL_QUERY = `
query SlackInstallUrl($action: SlackInstallUrlAction!, $originalPath: String) {
  slackInstallUrl(action: $action, originalPath: $originalPath) {
    installUrl
  }
}`;
/**
 * Fetches the direct Slack OAuth install URL from App API.
 * This lets the wizard open the Slack authorization page directly instead of
 * routing through Amplitude Settings.
 *
 * Returns `null` on any error so callers can fall back to the settings page.
 */
async function fetchSlackInstallUrl(accessToken, zone, orgId, originalPath) {
    const url = appApiUrl(zone, orgId, 'SlackInstallUrl');
    try {
        const response = await axios_1.default.post(url, {
            query: SLACK_INSTALL_URL_QUERY,
            variables: { action: 'profile', originalPath },
        }, {
            headers: {
                'x-amp-authorization': `Bearer ${accessToken}`,
                'Content-Type': 'application/json',
                'User-Agent': constants_js_1.WIZARD_USER_AGENT,
            },
            timeout: 10_000,
        });
        (0, debug_js_1.logToFile)(`[fetchSlackInstallUrl] response status=${response.status} data=${JSON.stringify(response.data)}`);
        const parsed = SlackInstallUrlSchema.parse(response.data);
        return parsed.data.slackInstallUrl.installUrl;
    }
    catch (err) {
        const detail = axios_1.default.isAxiosError(err)
            ? `status=${err.response?.status} data=${JSON.stringify(err.response?.data)}`
            : err instanceof Error
                ? err.message
                : String(err);
        (0, debug_js_1.logToFile)(`[fetchSlackInstallUrl] failed: ${detail}`);
        return null;
    }
}
// ── Slack connection status ───────────────────────────────────────────────
const SlackConnectionStatusSchema = zod_1.z.object({
    data: zod_1.z.object({
        slackConnectionStatus: zod_1.z.object({
            isConnected: zod_1.z.boolean(),
        }),
    }),
});
const SLACK_CONNECTION_STATUS_QUERY = `
query SlackConnectionStatus {
  slackConnectionStatus {
    isConnected
  }
}`;
/**
 * Checks whether the authenticated user already has Slack connected.
 * Returns `null` on any error so callers treat it as unknown.
 */
async function fetchSlackConnectionStatus(accessToken, zone, orgId) {
    const url = appApiUrl(zone, orgId, 'SlackConnectionStatus');
    try {
        const response = await axios_1.default.post(url, { query: SLACK_CONNECTION_STATUS_QUERY }, {
            headers: {
                'x-amp-authorization': `Bearer ${accessToken}`,
                'Content-Type': 'application/json',
                'User-Agent': constants_js_1.WIZARD_USER_AGENT,
            },
            timeout: 10_000,
        });
        const parsed = SlackConnectionStatusSchema.parse(response.data);
        return parsed.data.slackConnectionStatus.isConnected;
    }
    catch {
        return null;
    }
}
function handleApiError(error, operation) {
    if (axios_1.default.isAxiosError(error)) {
        const axiosError = error;
        const status = axiosError.response?.status;
        const endpoint = axiosError.config?.url;
        const gqlMessage = axiosError.response?.data?.errors?.[0]?.message;
        if (status === 401)
            return new ApiError(`Authentication failed while trying to ${operation}`, status, endpoint);
        if (status === 403)
            return new ApiError(`Access denied while trying to ${operation}`, status, endpoint);
        const message = gqlMessage ?? `Failed to ${operation}`;
        return new ApiError(message, status, endpoint);
    }
    if (error instanceof zod_1.z.ZodError) {
        return new ApiError(`Invalid response format while trying to ${operation}`);
    }
    return new ApiError(`Unexpected error while trying to ${operation}: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
// ── Legacy stubs — kept so files referencing the old Amplitude types compile ──
exports.ApiUserSchema = zod_1.z.object({ distinct_id: zod_1.z.string() });
exports.ApiProjectSchema = zod_1.z.object({
    id: zod_1.z.number(),
    uuid: zod_1.z.string(),
    organization: zod_1.z.string(),
    api_token: zod_1.z.string(),
    name: zod_1.z.string(),
});
/** @deprecated Amplitude stub. Use fetchAmplitudeUser instead. */
// eslint-disable-next-line @typescript-eslint/require-await
async function fetchUserData(_accessToken, _baseUrl) {
    throw new Error('fetchUserData is a Amplitude stub — use fetchAmplitudeUser instead');
}
/** @deprecated Amplitude stub. Use fetchAmplitudeUser instead. */
// eslint-disable-next-line @typescript-eslint/require-await
async function fetchProjectData(_accessToken, _projectId, _baseUrl) {
    throw new Error('fetchProjectData is a Amplitude stub — use fetchAmplitudeUser instead');
}
