import path from "node:path";
import { collectEnvOverrides, deepMergeDocuments, loadProviderShapeBaseUrls, readMergedDocument, readMergedDocumentReadonly } from "@poe-code/poe-code-config";
import { createLoggingCommandRunner } from "../context.js";
import { resolveIsolatedTargetDirectory } from "../isolated-env.js";
import { getSpawnConfig } from "@poe-code/agent-spawn";
import { allAgents, formatAgentSpecifier, parseAgentSpecifier, resolveAgentId } from "@poe-code/agent-defs";
import { knownConfigScopes, loadConfiguredServices } from "../../services/config.js";
import { resolveApiShape } from "@poe-code/providers";
import { OperationCancelledError, ValidationError } from "../errors.js";
export async function buildActiveProvider(input) {
    const apiShape = resolveApiShape(input.provider, input.agent);
    if (!apiShape) {
        throw new Error(`Provider "${input.provider.id}" cannot configure agent "${input.agent.id}".`);
    }
    const shape = input.provider.apiShapes?.find((candidate) => candidate.id === apiShape);
    if (!shape) {
        throw new Error(`Provider "${input.provider.id}" does not declare base URL for API shape "${apiShape}".`);
    }
    const providerBaseUrlEnv = resolveProviderBaseUrlEnv(input.container, input.provider);
    const environmentBaseUrl = resolveBaseUrlRoot(providerBaseUrlEnv, input.provider.baseUrlEnvPath);
    const configuredBaseUrl = resolveNonEmpty(input.explicitShapeBaseUrls?.[apiShape]) ??
        resolveProviderShapeBaseUrl(input.provider, shape, input.explicitBaseUrl) ??
        resolveShapeBaseUrl(environmentBaseUrl, shape.envBaseUrlPath ?? shape.baseUrlPath) ??
        (await resolveStoredShapeBaseUrl(input.container, input.provider.id, apiShape, {
            readOnly: input.readOnly
        }));
    if (input.provider.requiresBaseUrl === true && configuredBaseUrl === undefined) {
        throw new Error(`Provider "${input.provider.id}" requires a base URL for API shape "${apiShape}". Run \`poe-code provider login ${input.provider.id} --base-url <url>\`, set ${input.provider.baseUrlEnvVar ?? "the provider base URL env var"}, or pass --base-url.`);
    }
    const defaultBaseUrl = resolveDefaultShapeBaseUrl(input.provider, shape);
    if (configuredBaseUrl === undefined && defaultBaseUrl === undefined) {
        throw new Error(`Provider "${input.provider.id}" does not declare a default base URL for API shape "${apiShape}". Pass --base-url or --shape-base-url ${apiShape}=<url>.`);
    }
    const baseUrl = configuredBaseUrl ?? defaultBaseUrl;
    if (baseUrl === undefined) {
        throw new Error(`Provider "${input.provider.id}" does not declare a default base URL for API shape "${apiShape}". Pass --base-url or --shape-base-url ${apiShape}=<url>.`);
    }
    assertHttpBaseUrl(input.provider.id, baseUrl);
    const agentBaseUrl = resolveShapeBaseUrl(environmentBaseUrl, input.provider.agentBaseUrlPath) ??
        input.provider.agentBaseUrl ??
        baseUrl;
    return {
        id: input.provider.id,
        apiShape,
        baseUrl,
        agentBaseUrl,
        credential: input.credential,
        modelInput: input.provider.modelInput,
        extraEnv: resolveProviderEnv(input.provider, {
            baseUrl,
            credential: input.credential
        })
    };
}
function resolveDefaultShapeBaseUrl(provider, shape) {
    if (shape.defaultBaseUrl !== undefined) {
        return shape.defaultBaseUrl;
    }
    const providerDefaultRoot = resolveBaseUrlRoot(provider.baseUrl, provider.baseUrlEnvPath);
    return resolveShapeBaseUrl(providerDefaultRoot, shape.envBaseUrlPath ?? shape.baseUrlPath);
}
function resolveProviderEnv(provider, input) {
    return Object.fromEntries(Object.entries(provider.env ?? {}).map(([key, source]) => [
        key,
        resolveProviderEnvValue(provider, source, input)
    ]));
}
function resolveProviderEnvValue(provider, source, input) {
    switch (source.kind) {
        case "literal":
            return source.value;
        case "providerCredential":
            return `${source.prefix ?? ""}${input.credential}`;
        case "providerBaseUrl":
            return input.baseUrl;
        case "providerField": {
            const value = provider[source.path];
            if (typeof value !== "string") {
                throw new Error(`Provider field "${source.path}" must resolve to a string.`);
            }
            return value;
        }
    }
}
function assertHttpBaseUrl(providerId, baseUrl) {
    let parsed;
    try {
        parsed = new URL(baseUrl);
    }
    catch {
        throw new Error(`Provider "${providerId}" base URL must be an http(s) URL.`);
    }
    if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
        throw new Error(`Provider "${providerId}" base URL must be an http(s) URL.`);
    }
}
export function parseProviderShapeBaseUrls(provider, values) {
    const exposedShapes = provider.apiShapes?.map((shape) => shape.id) ?? [];
    const exposed = new Set(exposedShapes);
    const shapeBaseUrls = {};
    for (const value of values) {
        const separatorIndex = value.indexOf("=");
        if (separatorIndex <= 0 || separatorIndex === value.length - 1) {
            throw new Error(`Invalid --shape-base-url value "${value}". Use <shape-id>=<url>.`);
        }
        const shapeId = value.slice(0, separatorIndex).trim();
        const baseUrl = value.slice(separatorIndex + 1).trim();
        if (!exposed.has(shapeId)) {
            throw new Error(`Unknown API shape "${shapeId}" for provider "${provider.id}". Exposed shapes: ${formatApiShapeList(exposedShapes)}.`);
        }
        if (baseUrl.length === 0) {
            throw new Error(`Invalid --shape-base-url value "${value}". Use <shape-id>=<url>.`);
        }
        shapeBaseUrls[shapeId] = baseUrl;
    }
    return shapeBaseUrls;
}
function formatApiShapeList(shapeIds) {
    return shapeIds.length > 0 ? shapeIds.join(", ") : "none";
}
export function resolveNonEmpty(value) {
    const trimmed = value?.trim();
    return trimmed && trimmed.length > 0 ? trimmed : undefined;
}
function resolveProviderBaseUrlEnv(container, provider) {
    const envVar = provider.baseUrlEnvVar;
    return envVar ? resolveNonEmpty(container.env.getVariable(envVar)) : undefined;
}
export function resolveShapeBaseUrl(baseUrl, pathSuffix) {
    if (baseUrl === undefined) {
        return undefined;
    }
    const suffix = resolveNonEmpty(pathSuffix);
    if (suffix === undefined) {
        return baseUrl;
    }
    const normalizedBaseUrl = stripTrailingPathSegment(trimTrailingSlash(baseUrl), "compat");
    return `${normalizedBaseUrl}/${trimLeadingSlash(suffix)}`;
}
export function resolveProviderShapeBaseUrl(provider, shape, baseUrl) {
    const normalizedBaseUrl = resolveNonEmpty(baseUrl);
    if (normalizedBaseUrl === undefined) {
        return undefined;
    }
    const providerRoot = resolveBaseUrlRoot(normalizedBaseUrl, provider.baseUrlEnvPath);
    if (providerRoot !== normalizedBaseUrl) {
        return resolveShapeBaseUrl(providerRoot, shape.envBaseUrlPath ?? shape.baseUrlPath);
    }
    return resolveShapeBaseUrl(normalizedBaseUrl, shape.baseUrlPath) ?? normalizedBaseUrl;
}
function resolveBaseUrlRoot(baseUrl, pathSuffix) {
    const normalizedBaseUrl = resolveNonEmpty(baseUrl);
    const normalizedSuffix = resolveNonEmpty(pathSuffix);
    if (normalizedBaseUrl === undefined || normalizedSuffix === undefined) {
        return normalizedBaseUrl;
    }
    return stripTrailingPathSegment(trimTrailingSlash(normalizedBaseUrl), trimLeadingSlash(normalizedSuffix));
}
function trimTrailingSlash(value) {
    return value.endsWith("/") ? value.slice(0, -1) : value;
}
function trimLeadingSlash(value) {
    return value.startsWith("/") ? value.slice(1) : value;
}
function stripTrailingPathSegment(value, segment) {
    const suffix = `/${segment}`;
    return value.endsWith(suffix) ? value.slice(0, -suffix.length) : value;
}
export async function resolveActiveProviderForService(container, serviceName, options = {}) {
    const configuredServices = await loadConfiguredServices({
        fs: container.fs,
        filePath: container.env.configPath,
        projectFilePath: container.env.projectConfigPath,
        readOnly: options.readOnly
    });
    const agent = resolveAgentDefinition(serviceName) ?? { id: serviceName };
    const metadata = configuredServices[serviceName];
    const configuredProviderId = metadata?.provider;
    const provider = configuredProviderId
        ? container.providerRegistry.get(configuredProviderId)
        : await resolveSingleProviderCandidate(container, agent, options);
    if (!provider || provider.auth.kind !== "api-key") {
        return undefined;
    }
    let credential;
    try {
        credential = await container.providerRegistry.resolveCredential(provider.id, undefined, {
            envVars: container.env.variables,
            readOnly: options.readOnly
        });
    }
    catch {
        return undefined;
    }
    return buildActiveProvider({
        container,
        provider,
        agent,
        credential,
        explicitBaseUrl: metadata?.baseUrl,
        explicitShapeBaseUrls: parseProviderShapeBaseUrls(provider, metadata?.shapeBaseUrl ?? []),
        readOnly: options.readOnly
    });
}
async function resolveSingleProviderCandidate(container, agent, options) {
    const candidates = container.providerRegistry.forAgent(agent);
    if (candidates.length === 1) {
        return candidates[0];
    }
    const loggedIn = [];
    for (const candidate of candidates) {
        if (await container.providerRegistry.isLoggedIn(candidate.id, options)) {
            loggedIn.push(candidate);
        }
    }
    if (loggedIn.length === 1) {
        return loggedIn[0];
    }
    return undefined;
}
async function resolveStoredShapeBaseUrl(container, providerId, apiShape, options = {}) {
    const shapeBaseUrls = await loadProviderShapeBaseUrls({
        fs: container.fs,
        filePath: container.env.servicesConfigPath,
        providerId,
        readOnly: options.readOnly
    });
    const value = shapeBaseUrls[apiShape];
    return typeof value === "string" && value.trim().length > 0 ? value : undefined;
}
export function resolveAgentDefinition(serviceName) {
    const { agent } = parseAgentSpecifier(serviceName);
    const resolvedId = resolveAgentId(agent) ?? agent;
    return allAgents.find((candidate) => candidate.id === resolvedId);
}
export function resolveCommandFlags(program) {
    const opts = program.optsWithGlobals();
    return {
        dryRun: Boolean(opts.dryRun),
        assumeYes: Boolean(opts.yes),
        verbose: Boolean(opts.verbose)
    };
}
export function requireInteractiveStdin(message) {
    if (process.stdin.isTTY !== true) {
        throw new ValidationError(message);
    }
}
export function createExecutionResources(container, flags, scope, env) {
    const baseLogger = container.loggerFactory.create({
        dryRun: flags.dryRun,
        verbose: flags.verbose,
        scope
    });
    const runner = createLoggingCommandRunner(env
        ? (command, args, options) => container.commandRunner(command, args, {
            ...options,
            env: { ...(options?.env ?? {}), ...env }
        })
        : container.commandRunner, baseLogger);
    const context = container.contextFactory.create({
        dryRun: flags.dryRun,
        logger: baseLogger,
        runner
    });
    return {
        logger: baseLogger,
        context
    };
}
export function buildProviderContext(container, adapter, resources, options) {
    const runCheck = createCheckRunner(resources);
    return {
        env: container.env,
        command: resources.context,
        logger: resources.logger,
        model: options?.model,
        hooks: options?.hooks,
        activeProvider: options?.activeProvider,
        runCheck
    };
}
function createCheckRunner(resources) {
    return async (check) => {
        await check.run({
            isDryRun: resources.logger.context.dryRun,
            runCommand: resources.context.runCommand,
            logDryRun: (message) => resources.logger.dryRun(message),
            logWarning: (message) => resources.logger.warn(message)
        });
    };
}
export function listIsolatedServiceIds(container) {
    return listServiceNames(container.registry.list().filter((provider) => Boolean(provider.isolatedEnv)));
}
export function listServiceNames(services) {
    const names = [];
    const add = (value) => {
        const normalized = value?.trim();
        if (!normalized || names.includes(normalized)) {
            return;
        }
        names.push(normalized);
    };
    for (const service of services) {
        add(service.name);
        for (const alias of service.aliases ?? []) {
            add(alias);
        }
    }
    return names;
}
export function resolveServiceAdapter(container, service) {
    const adapter = container.registry.get(service);
    if (!adapter) {
        throw new Error(`Unknown agent "${service}".`);
    }
    return adapter;
}
export function createSecretPrompter(container) {
    return async (prompt) => {
        const descriptor = {
            name: "apiKey",
            message: prompt.title,
            type: "password"
        };
        const response = await container.prompts(descriptor);
        const value = response["apiKey"];
        if (typeof value !== "string" || !value.trim()) {
            throw new OperationCancelledError();
        }
        return value.trim();
    };
}
export function formatServiceList(names) {
    const unique = Array.from(new Set(names.filter((name) => name.length > 0)));
    if (unique.length === 0) {
        return "";
    }
    return ` (${unique.join(" | ")})`;
}
export function buildResumeCommand(canonicalService, threadId, cwd) {
    const spawnConfig = getSpawnConfig(canonicalService);
    if (spawnConfig?.kind !== "cli" || !spawnConfig.resume) {
        return undefined;
    }
    const resolvedId = resolveAgentId(canonicalService) ?? canonicalService;
    const agentDefinition = allAgents.find((agent) => agent.id === resolvedId);
    const binaryName = agentDefinition?.binaryName;
    if (!binaryName) {
        return undefined;
    }
    const resumeCwd = path.resolve(cwd);
    const composer = spawnConfig.resume.hintArgs ?? spawnConfig.resume.args;
    const args = composer(threadId, resumeCwd);
    const agentCommand = [binaryName, ...args.map(shlexQuote)].join(" ");
    const needsCdPrefix = !args.includes(resumeCwd);
    return needsCdPrefix ? `cd ${shlexQuote(resumeCwd)} && ${agentCommand}` : agentCommand;
}
export async function applyIsolatedConfiguration(input) {
    await input.adapter.configure({
        fs: input.providerContext.command.fs,
        env: input.providerContext.env,
        command: input.providerContext.command,
        options: input.payload,
        pathMapper: {
            mapTargetDirectory: ({ targetDirectory }) => resolveIsolatedTargetDirectory({
                targetDirectory,
                isolated: input.isolated,
                env: input.providerContext.env,
                providerName: input.providerName
            })
        }
    }, input.observers ? { observers: input.observers } : undefined);
}
export async function resolveMergedDocument(container, options = {}) {
    const mergedDocument = await (options.readOnly ? readMergedDocumentReadonly : readMergedDocument)(container.fs, container.env.configPath, container.env.projectConfigPath);
    const envOverrides = collectEnvOverrides(knownConfigScopes, container.env.variables);
    return deepMergeDocuments(mergedDocument, envOverrides.document);
}
export async function resolveDefaultAgent(container, options = {}) {
    const document = await resolveMergedDocument(container, options);
    const value = typeof document.core?.defaultAgent === "string" ? document.core.defaultAgent : "";
    const trimmed = value.trim();
    if (trimmed.length === 0) {
        return null;
    }
    const specifier = parseAgentSpecifier(trimmed);
    const resolvedAgent = resolveAgentId(specifier.agent);
    if (!resolvedAgent || !allAgents.some((agent) => agent.id === resolvedAgent)) {
        const supportedAgents = allAgents.map((agent) => agent.id).join(", ");
        throw new ValidationError(`Invalid value for core.defaultAgent: "${value}". Supported agents: ${supportedAgents}`);
    }
    return formatAgentSpecifier({
        agent: resolvedAgent,
        model: specifier.model
    });
}
export function shlexQuote(value) {
    if (value.length === 0) {
        return "''";
    }
    let isSafe = true;
    for (let index = 0; index < value.length; index += 1) {
        if (!isSafeShellChar(value.charCodeAt(index))) {
            isSafe = false;
            break;
        }
    }
    if (isSafe) {
        return value;
    }
    let output = "'";
    for (let index = 0; index < value.length; index += 1) {
        const char = value[index];
        if (char === "'") {
            output += `"'"'"'`;
            continue;
        }
        output += char;
    }
    output += "'";
    return output;
}
function isSafeShellChar(code) {
    if (code >= 48 && code <= 57) {
        return true;
    }
    if (code >= 65 && code <= 90) {
        return true;
    }
    if (code >= 97 && code <= 122) {
        return true;
    }
    switch (code) {
        case 95: // _
        case 64: // @
        case 37: // %
        case 43: // +
        case 61: // =
        case 58: // :
        case 44: // ,
        case 46: // .
        case 47: // /
        case 45: // -
            return true;
        default:
            return false;
    }
}
//# sourceMappingURL=shared.js.map