import fs from "node:fs";
import { z } from "zod";
const GoogleServiceAccountSchema = z.object({
    type: z.string().min(1),
    project_id: z.string().min(1),
    private_key_id: z.string().min(1),
    private_key: z.string().min(1),
    client_email: z.string().min(1),
    client_id: z.string().min(1),
    auth_uri: z.string().optional(),
    token_uri: z.string().min(1),
});
const AwsConfigSchema = z.object({
    region: z.string().min(1),
    // bearerToken is no longer issued by onboarding server (IAM credentials
    // always take precedence client-side, see client-factory.ts). Kept
    // optional/nullable for backwards-compat with older provisioning JSON
    // snapshots that still embed it.
    bearerToken: z.string().optional().nullable(),
    accessKeyId: z.string().min(1),
    secretAccessKey: z.string().min(1),
    sessionToken: z.string().optional().nullable(),
});
const GoogleWorkspaceConfigSchema = z.object({
    licenseSku: z.string().optional().nullable(),
    licenseProductId: z.string().optional().nullable(),
    delegatedAdminEmail: z.string().trim().min(1),
    adminSdkServiceAccount: GoogleServiceAccountSchema,
});
const GoogleOAuthConfigSchema = z.object({
    clientId: z.string().optional().nullable(),
    clientSecret: z.string().optional().nullable(),
}).default({});
const TelegramConfigSchema = z.object({
    botToken: z.string().min(1),
    botName: z.string().min(1),
    profilePhotoSources: z.array(z.string().min(1)).optional().nullable(),
});
const CloudflaredConfigSchema = z.object({
    enabled: z.boolean().default(false),
    tunnelName: z.string().optional().nullable(),
    tunnelHostname: z.string().optional().nullable(),
}).default({ enabled: false });
const GithubConfigSchema = z.object({
    enabled: z.boolean().default(false),
    createAccountIfMissing: z.boolean().default(true),
    pat: z.string().min(1).optional().nullable(),
}).default({ enabled: false, createAccountIfMissing: true, pat: null });
const OnboardingServiceConfigSchema = z.object({
    url: z.url(),
    apiKey: z.string().min(1),
});
export const ProvisioningSpecSchema = z.object({
    provider: z.literal("bedrock").default("bedrock"),
    agentName: z.string().trim().min(1),
    emailUsername: z.string().trim().regex(/^[a-z0-9][a-z0-9._-]*$/i, {
        message: "emailUsername must be alphanumeric and may include . _ -",
    }),
    workspaceDomain: z.string().trim().min(1).default("visionclaw.dev"),
    gmailPassword: z.string().optional().nullable(),
    googleActivated: z.boolean().optional().default(false),
    systemPassword: z.string().optional().nullable(),
    aws: AwsConfigSchema,
    serpApiKey: z.string().min(1),
    googleWorkspace: GoogleWorkspaceConfigSchema.optional(),
    googleOAuth: GoogleOAuthConfigSchema,
    telegram: TelegramConfigSchema,
    cloudflared: CloudflaredConfigSchema,
    github: GithubConfigSchema,
    installService: z.boolean().default(true),
    onboardingService: OnboardingServiceConfigSchema.optional(),
    geminiApiKey: z.string().optional().nullable(),
});
function isRemoteSource(source) {
    return /^https:\/\//i.test(source);
}
/**
 * An onboarding key is a 32-char hex string (the agent's API key).
 * It is used to fetch the provisioning spec from the onboarding service.
 * Defaults to https://onboarding.visionclaw.dev; override with VISIONCLAW_ONBOARDING_SERVICE_URL.
 */
function isOnboardingKey(source) {
    return /^[0-9a-f]{32}$/i.test(source);
}
/**
 * A 6-digit numeric setup code, used as a shorter alternative to the full API key
 * for `visionclaw setup <code>`.
 */
function isSetupCode(source) {
    return /^\d{6}$/.test(source);
}
function getOnboardingServiceBase() {
    const serviceUrl = process.env.VISIONCLAW_ONBOARDING_SERVICE_URL ?? "https://onboarding.visionclaw.dev";
    return serviceUrl.replace(/\/+$/, "");
}
async function fetchFromOnboardingService(url) {
    let response;
    try {
        response = await fetch(url, {
            signal: AbortSignal.timeout(30_000),
        });
    }
    catch (err) {
        throw new Error(`Cannot reach onboarding service at ${url} — ${err instanceof Error ? err.message : String(err)}\n` +
            `Set VISIONCLAW_ONBOARDING_SERVICE_URL if the service is running elsewhere.`);
    }
    if (!response.ok) {
        throw new Error(`Failed to fetch provisioning spec from ${url}: HTTP ${response.status}`);
    }
    return response.text();
}
async function readProvisioningSource(source) {
    console.log(`Resolving provisioning source: "${source}" (remote=${isRemoteSource(source)}, onboardingKey=${isOnboardingKey(source)}, setupCode=${isSetupCode(source)})`);
    if (isRemoteSource(source)) {
        const response = await fetch(source, {
            signal: AbortSignal.timeout(30_000),
        });
        if (!response.ok) {
            throw new Error(`Failed to fetch provisioning JSON: HTTP ${response.status}`);
        }
        return {
            sourceType: "remote",
            content: await response.text(),
        };
    }
    if (isSetupCode(source)) {
        const base = getOnboardingServiceBase();
        const url = `${base}/api/provisioning/setup-code/${source}`;
        console.log(`Fetching provisioning spec via setup code from: ${url}`);
        return {
            sourceType: "remote",
            content: await fetchFromOnboardingService(url),
            setupCode: source,
        };
    }
    if (isOnboardingKey(source)) {
        const base = getOnboardingServiceBase();
        const url = `${base}/api/provisioning/${source}`;
        console.log(`Fetching provisioning spec from: ${url}`);
        return {
            sourceType: "remote",
            content: await fetchFromOnboardingService(url),
        };
    }
    if (!fs.existsSync(source)) {
        throw new Error(`Provisioning JSON file not found: ${source}`);
    }
    return {
        sourceType: "local",
        content: fs.readFileSync(source, "utf-8"),
    };
}
export async function loadProvisioningSpec(source) {
    const trimmedSource = source.trim();
    if (!trimmedSource) {
        throw new Error("Provisioning source is required");
    }
    const { sourceType, content, setupCode } = await readProvisioningSource(trimmedSource);
    const parsed = JSON.parse(content);
    return {
        source: trimmedSource,
        sourceType,
        ...(setupCode ? { setupCode } : {}),
        spec: ProvisioningSpecSchema.parse(parsed),
    };
}
//# sourceMappingURL=provisioning-spec.js.map