import { z } from "zod";
import * as fs from "fs";
import * as path from "path";
export const TYPES_THAT_DONT_TRIGGER_NEW_PART = [
    "typeAlias",
    "comment",
    "newLine",
    "importStatement",
    "importNodeStatement",
];
/**
 * Maps Agency built-in function names to TypeScript equivalents.
 * Most map to themselves; exceptions are names that shadow JS globals.
 */
export const BUILTIN_FUNCTIONS = {};
export const BUILTIN_TOOLS = [];
export const BUILTIN_VARIABLES = ["color"];
// --- Zod schema for runtime validation of agency.json ---
export const AgencyConfigSchema = z
    .object({
    verbose: z.boolean(),
    logLevel: z.enum(["debug", "info", "warn", "error"]),
    outDir: z.string(),
    excludeNodeTypes: z.array(z.string()),
    excludeBuiltinFunctions: z.array(z.string()),
    allowedFetchDomains: z.array(z.string()),
    disallowedFetchDomains: z.array(z.string()),
    tarsecTraceHost: z.string(),
    maxToolCallRounds: z.number(),
    observability: z.boolean(),
    log: z
        .object({
        host: z.string(),
        projectId: z.string(),
        debugMode: z.boolean(),
        apiKey: z.string(),
        logFile: z.string(),
        requestTimeoutMs: z.number().int().positive(),
        metadata: z
            .object({
            tags: z.array(z.string()),
            environment: z.string(),
            userId: z.string(),
            agentVersion: z.string(),
            custom: z.record(z.string(), z.string()),
        })
            .partial(),
    })
        .partial(),
    client: z
        .object({
        logLevel: z.enum(["error", "warn", "info", "debug"]),
        defaultModel: z.string(),
        openAiApiKey: z.string(),
        googleApiKey: z.string(),
        statelog: z
            .object({
            host: z.string(),
            projectId: z.string(),
            apiKey: z.string(),
        })
            .partial(),
    })
        .partial(),
    typechecker: z
        .object({
        enabled: z.boolean(),
        strict: z.boolean(),
        strictTypes: z.boolean(),
        undefinedFunctions: z.enum(["silent", "warn", "error"]),
        undefinedVariables: z.enum(["silent", "warn", "error"]),
    })
        .partial(),
    debugger: z.boolean(),
    instrument: z.boolean(),
    checkpoints: z.object({ maxRestores: z.number() }).partial(),
    trace: z.boolean(),
    traceFile: z.string(),
    traceDir: z.string(),
    distDir: z.string(),
    test: z.object({ parallel: z.number() }).partial(),
    doc: z.object({ outDir: z.string(), baseUrl: z.string() }).partial(),
    viewer: z
        .object({
        slowMs: z.number(),
        fastMs: z.number(),
        expensiveUsd: z.number(),
    })
        .partial(),
    coverage: z
        .object({
        outDir: z.string(),
        threshold: z.number().min(0).max(100),
        perFileThreshold: z.number().min(0).max(100),
        exclude: z.array(z.string()),
    })
        .partial(),
    pack: z
        .object({
        format: z.enum(["esm", "cjs"]),
        target: z.string(),
        external: z.array(z.string()),
    })
        .partial(),
    memory: z.object({
        dir: z.string(),
        model: z.string().optional(),
        autoExtract: z
            .object({ interval: z.number().optional() })
            .optional(),
        compaction: z
            .object({
            trigger: z.enum(["token", "messages"]).optional(),
            threshold: z.number().optional(),
        })
            .optional(),
        embeddings: z
            .object({ model: z.string().optional() })
            .optional(),
    }),
})
    .partial()
    .passthrough();
/**
 * Load agency.json at the given path without calling process.exit.
 * Returns the parsed config, or an error message if the file is invalid.
 * Returns an empty config if the file doesn't exist.
 */
export function loadConfigSafe(configPath) {
    if (!fs.existsSync(configPath)) {
        return { config: {} };
    }
    try {
        const content = fs.readFileSync(configPath, "utf-8");
        const parsed = JSON.parse(content);
        const result = AgencyConfigSchema.safeParse(parsed);
        if (!result.success) {
            const issues = result.error.issues
                .map((issue) => `  - ${issue.path.join(".")}: ${issue.message}`)
                .join("\n");
            return {
                config: {},
                error: `Invalid agency.json config:\n${issues}`,
            };
        }
        if (result.data.verbose) {
            console.log(`Loaded config from ${configPath}:`);
        }
        return { config: result.data };
    }
    catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        return {
            config: {},
            error: `Error loading config from ${configPath}: ${message}`,
        };
    }
}
/**
 * Find the agency.json for a given file path by searching upward.
 * Returns the directory containing agency.json, or null if not found.
 */
export function findProjectRoot(startPath) {
    let current = fs.existsSync(startPath) && fs.statSync(startPath).isDirectory()
        ? startPath
        : path.dirname(startPath);
    while (true) {
        if (fs.existsSync(path.join(current, "agency.json"))) {
            return current;
        }
        const parent = path.dirname(current);
        if (parent === current) {
            return null;
        }
        current = parent;
    }
}
