import { allAgents } from "@poe-code/agent-defs";
import { saveProviderShapeBaseUrls } from "@poe-code/poe-code-config";
import { createExecutionResources, createSecretPrompter, applyIsolatedConfiguration, buildProviderContext, parseProviderShapeBaseUrls, resolveProviderShapeBaseUrl, resolveCommandFlags, resolveNonEmpty, resolveServiceAdapter, } from "./shared.js";
import { getTheme, renderTable } from "toolcraft-design";
import { createConfigurePayload } from "./configure-payload.js";
import { createOverlayFileSystem } from "./configure.js";
import { loadConfiguredServices, unconfigureService } from "../../services/config.js";
import { createMutationReporter } from "../../services/mutation-events.js";
import { resolveIsolatedTargetDirectory } from "../isolated-env.js";
const apiShapeLabels = {
    "openai-chat-completions": "chat-completions",
    "openai-responses": "responses",
    "anthropic-messages": "messages",
    "google-generations": "generations"
};
export function registerProviderCommand(program, container) {
    const providerCmd = program
        .command("provider")
        .description("Manage auth providers for coding agents.");
    providerCmd
        .command("list")
        .description("List available providers and their login status.")
        .action(async () => {
        await executeProviderList(program, container);
    });
    providerCmd
        .command("login")
        .description("Log in to a provider.")
        .argument("<id>", "Provider id (e.g. poe, anthropic)")
        .option("--api-key <key>", "API key for the provider")
        .option("--base-url <url>", "Provider gateway root URL")
        .option("--shape-base-url <shape-id>=<url>", "Base URL for one provider API shape", collectRepeatedOption)
        .action(async (id, options) => {
        await executeProviderLogin(program, container, id, options);
    });
    providerCmd
        .command("logout")
        .description("Log out from a provider.")
        .argument("<id>", "Provider id (e.g. poe, anthropic)")
        .action(async (id) => {
        await executeProviderLogout(program, container, id);
    });
}
async function executeProviderList(program, container) {
    const flags = resolveCommandFlags(program);
    const resources = createExecutionResources(container, flags, "provider:list");
    resources.logger.intro("provider list");
    const providers = container.providerRegistry.list();
    const theme = getTheme();
    const rows = await Promise.all(providers.map(async (provider) => {
        const loggedIn = await container.providerRegistry.isLoggedIn(provider.id, {
            readOnly: flags.dryRun
        });
        const apiShapes = provider.apiShapes?.map((shape) => shape.id) ?? [];
        return {
            Provider: theme.accent(provider.id),
            Status: loggedIn ? theme.success("[logged in]") : theme.muted("[-]"),
            Env: formatProviderEnv(provider),
            "API shapes": formatProviderApiShapes(apiShapes),
            Agents: listShapeCompatibleAgents(apiShapes).join(", ")
        };
    }));
    const columns = [
        { name: "Provider", title: "Provider", alignment: "left", maxLen: 20 },
        { name: "Status", title: "Status", alignment: "left", maxLen: 14 },
        { name: "Env", title: "Env", alignment: "left", maxLen: 34 },
        { name: "API shapes", title: "API shapes", alignment: "left", maxLen: 52 },
        { name: "Agents", title: "Agents", alignment: "left", maxLen: 60 }
    ];
    resources.logger.info(renderTable({ theme, columns, rows }));
}
async function executeProviderLogin(program, container, id, options) {
    const flags = resolveCommandFlags(program);
    const resources = createExecutionResources(container, flags, `provider:login:${id}`);
    resources.logger.intro(`provider login ${id}`);
    const provider = container.providerRegistry.get(id);
    if (!provider) {
        throw new Error(`Unknown provider "${id}". Run \`poe-code provider list\` to see available providers.`);
    }
    const parsedShapeBaseUrls = parseProviderShapeBaseUrls(provider, options.shapeBaseUrl ?? []);
    validateProviderLoginBaseUrlOptions({
        provider,
        options,
        container,
        flags,
        parsedShapeBaseUrls
    });
    const shapeBaseUrls = await resolveProviderLoginShapeBaseUrls({
        provider,
        options,
        container,
        flags,
        logger: resources.logger,
        parsedShapeBaseUrls
    });
    let rollbackCredential;
    const transaction = flags.dryRun ? undefined : createOverlayFileSystem(resources.context.fs);
    const executionFs = transaction?.fs ?? resources.context.fs;
    let credential;
    try {
        if (flags.dryRun) {
            validateDryRunCredentialAvailability({ provider, options, container, flags });
        }
        else {
            const staged = await stageProviderLogin({
                id,
                options,
                container,
                flags,
                fs: executionFs
            });
            credential = staged.credential;
            rollbackCredential = staged.rollback;
        }
        await saveProviderShapeBaseUrls({
            fs: executionFs,
            filePath: container.env.servicesConfigPath,
            providerId: id,
            shapeBaseUrls
        });
        await refreshConfiguredServicesForProvider({
            container,
            providerId: id,
            credential,
            flags,
            resources,
            fs: executionFs
        });
        await transaction?.commit();
    }
    catch (error) {
        await rollbackCredential?.().catch(() => undefined);
        throw error;
    }
    const dryMessage = flags.dryRun && provider.auth.kind === "api-key" && provider.auth.preferredLogin === "oauth" &&
        resolveNonEmpty(options.apiKey) === undefined &&
        resolveNonEmpty(container.env.getVariable(provider.auth.envVar)) === undefined
        ? `Dry run: would authenticate with ${provider.label}.`
        : `Dry run: would save credential for ${id}.`;
    resources.context.complete({
        success: `Saved credential for ${id}.`,
        dry: dryMessage
    });
    resources.context.finalize();
}
function validateDryRunCredentialAvailability(input) {
    if (!input.flags.assumeYes || input.provider.auth.kind !== "api-key") {
        return;
    }
    if (input.provider.auth.preferredLogin === "oauth") {
        return;
    }
    if (resolveNonEmpty(input.options.apiKey) === undefined &&
        resolveNonEmpty(input.container.env.getVariable(input.provider.auth.envVar)) === undefined) {
        throw new Error(`No API key available for provider "${input.provider.id}". Pass --api-key or run interactively.`);
    }
}
function validateProviderLoginBaseUrlOptions(input) {
    for (const baseUrl of Object.values(input.parsedShapeBaseUrls)) {
        if (baseUrl !== undefined) {
            assertHttpBaseUrl(input.provider.id, baseUrl);
        }
    }
    const explicitBaseUrl = resolveNonEmpty(input.options.baseUrl);
    if (explicitBaseUrl !== undefined) {
        assertHttpBaseUrl(input.provider.id, explicitBaseUrl);
        return;
    }
    if (input.flags.assumeYes &&
        input.provider.requiresBaseUrl === true &&
        Object.keys(input.parsedShapeBaseUrls).length === 0 &&
        resolveProviderBaseUrlEnv(input.container, input.provider) === undefined) {
        throw new Error(`Provider "${input.provider.id}" requires a base URL. Pass --base-url or set ${input.provider.baseUrlEnvVar ?? "the provider base URL env var"}.`);
    }
}
async function resolveProviderLoginShapeBaseUrls(input) {
    const shapeBaseUrls = input.parsedShapeBaseUrls;
    const explicitBaseUrl = resolveNonEmpty(input.options.baseUrl);
    if (explicitBaseUrl !== undefined) {
        assertHttpBaseUrl(input.provider.id, explicitBaseUrl);
        return {
            ...deriveShapeBaseUrls(input.provider, explicitBaseUrl),
            ...shapeBaseUrls
        };
    }
    if (Object.keys(shapeBaseUrls).length > 0 || input.provider.requiresBaseUrl !== true) {
        return shapeBaseUrls;
    }
    if (resolveProviderBaseUrlEnv(input.container, input.provider) !== undefined) {
        return shapeBaseUrls;
    }
    if (input.flags.assumeYes) {
        throw new Error(`Provider "${input.provider.id}" requires a base URL. Pass --base-url or set ${input.provider.baseUrlEnvVar ?? "the provider base URL env var"}.`);
    }
    const descriptor = input.container.promptLibrary.providerBaseUrl(input.provider.label);
    while (true) {
        const baseUrl = await input.container.options.ensure({ descriptor });
        if (isHttpBaseUrl(baseUrl)) {
            return deriveShapeBaseUrls(input.provider, baseUrl);
        }
        input.logger.warn("Base URL must start with http:// or https://. Paste the Cloudflare gateway URL, not the API token.");
    }
}
function deriveShapeBaseUrls(provider, baseUrl) {
    const result = {};
    for (const shape of provider.apiShapes ?? []) {
        result[shape.id] = resolveProviderShapeBaseUrl(provider, shape, baseUrl) ?? baseUrl;
    }
    return result;
}
function resolveProviderBaseUrlEnv(container, provider) {
    const envVar = provider.baseUrlEnvVar;
    return envVar ? resolveNonEmpty(container.env.getVariable(envVar)) : undefined;
}
function assertHttpBaseUrl(providerId, baseUrl) {
    if (!isHttpBaseUrl(baseUrl)) {
        throw new Error(`Provider "${providerId}" base URL must be an http(s) URL.`);
    }
}
function isHttpBaseUrl(value) {
    try {
        const parsed = new URL(value);
        return parsed.protocol === "http:" || parsed.protocol === "https:";
    }
    catch {
        return false;
    }
}
function collectRepeatedOption(value, previous) {
    return [...(previous ?? []), value];
}
function formatProviderApiShapes(apiShapes) {
    return apiShapes.map((shapeId) => apiShapeLabels[shapeId]).join(", ");
}
function formatProviderEnv(provider) {
    const envVars = [
        provider.auth.kind === "api-key" ? provider.auth.envVar : undefined,
        provider.baseUrlEnvVar
    ].filter((value) => typeof value === "string" && value.length > 0);
    return envVars.join(", ");
}
function listShapeCompatibleAgents(providerApiShapes) {
    const shapeIds = new Set(providerApiShapes);
    return allAgents
        .filter((agent) => agent.apiShapes?.some((shapeId) => shapeIds.has(shapeId)))
        .map((agent) => agent.id)
        .sort();
}
async function executeProviderLogout(program, container, id) {
    const flags = resolveCommandFlags(program);
    const resources = createExecutionResources(container, flags, `provider:logout:${id}`);
    resources.logger.intro(`provider logout ${id}`);
    const provider = container.providerRegistry.get(id);
    if (!provider) {
        throw new Error(`Unknown provider "${id}". Run \`poe-code provider list\` to see available providers.`);
    }
    let rollbackCredential;
    const transaction = flags.dryRun ? undefined : createOverlayFileSystem(resources.context.fs);
    const executionFs = transaction?.fs ?? resources.context.fs;
    try {
        if (flags.dryRun) {
            const previewStore = container.createPreviewProviderStore(id, executionFs);
            if (previewStore) {
                await container.providerRegistry.logout(id, { store: previewStore });
            }
        }
        else {
            rollbackCredential = await stageProviderLogout({ id, container, fs: executionFs });
        }
        await unconfigureServicesForProvider({
            container,
            providerId: id,
            flags,
            resources,
            fs: executionFs
        });
        await transaction?.commit();
    }
    catch (error) {
        await rollbackCredential?.().catch(() => undefined);
        throw error;
    }
    const credentialEnvVar = provider.auth.kind === "api-key" ? provider.auth.envVar : undefined;
    const environmentCredential = credentialEnvVar
        ? container.env.getVariable(credentialEnvVar)
        : undefined;
    const hasEnvironmentCredential = typeof environmentCredential === "string"
        && environmentCredential.trim().length > 0;
    resources.context.complete({
        success: hasEnvironmentCredential
            ? `Stored credential removed, but ${credentialEnvVar} remains set; unset it to log out from ${id}.`
            : `Logged out from ${id}.`,
        dry: `Dry run: would log out from ${id}.`
    });
    resources.context.finalize();
}
async function stageProviderLogin(input) {
    const loginContext = {
        envVars: input.flags.assumeYes ? input.container.env.variables : {},
        promptForSecret: input.flags.assumeYes ? undefined : createSecretPrompter(input.container),
        resolvePreferredLogin: async (preferred) => input.container.options.resolveApiKey({
            value: preferred.apiKey,
            envValue: preferred.envValue,
            dryRun: true,
            assumeYes: input.flags.assumeYes,
            allowStored: false
        })
    };
    const previewStore = input.container.createPreviewProviderStore(input.id, input.fs);
    if (previewStore) {
        await input.container.providerRegistry.login(input.id, { apiKey: input.options.apiKey }, {
            ...loginContext,
            store: previewStore
        });
        const credential = await previewStore.get() ?? resolveNonEmpty(input.options.apiKey);
        if (typeof credential !== "string" || credential.trim().length === 0) {
            throw new Error(`No API key available for provider "${input.id}".`);
        }
        return { credential: credential.trim() };
    }
    const previousCredential = await input.container.providerRegistry
        .resolveCredential(input.id, {}, { envVars: {} })
        .catch(() => undefined);
    await input.container.providerRegistry.login(input.id, { apiKey: input.options.apiKey }, loginContext);
    const credential = await input.container.providerRegistry.resolveCredential(input.id, {}, { envVars: {} });
    return {
        credential,
        rollback: () => restoreProviderCredential(input.container, input.id, previousCredential)
    };
}
async function stageProviderLogout(input) {
    const previewStore = input.container.createPreviewProviderStore(input.id, input.fs);
    if (previewStore) {
        await input.container.providerRegistry.logout(input.id, { store: previewStore });
        return undefined;
    }
    const previousCredential = await input.container.providerRegistry
        .resolveCredential(input.id, {}, { envVars: {} })
        .catch(() => undefined);
    await input.container.providerRegistry.logout(input.id);
    return () => restoreProviderCredential(input.container, input.id, previousCredential);
}
async function restoreProviderCredential(container, providerId, credential) {
    if (credential === undefined) {
        await container.providerRegistry.logout(providerId);
        return;
    }
    await container.providerRegistry.login(providerId, { apiKey: credential });
}
async function refreshConfiguredServicesForProvider(input) {
    const configuredServices = await loadConfiguredServices({
        fs: input.fs,
        filePath: input.container.env.configPath,
        projectFilePath: input.container.env.projectConfigPath,
        readOnly: input.flags.dryRun
    });
    const stagedContainer = { ...input.container, fs: input.fs };
    for (const [service, metadata] of Object.entries(configuredServices)) {
        if (metadata.provider !== input.providerId) {
            continue;
        }
        const adapter = resolveServiceAdapter(input.container, service);
        const providerContext = createProviderContextWithFileSystem(input.container, adapter, input.resources, input.fs);
        const options = {
            model: metadata.model,
            reasoningEffort: metadata.reasoningEffort,
            baseUrl: metadata.baseUrl,
            shapeBaseUrl: metadata.shapeBaseUrl,
            apiKey: input.credential
        };
        const payload = await createConfigurePayload({
            container: stagedContainer,
            flags: { ...input.flags, assumeYes: true },
            options,
            context: providerContext,
            adapter,
            logger: input.resources.logger,
            providerId: input.providerId
        });
        await input.container.registry.invoke(service, "configure", async (entry) => {
            await entry.configure({ fs: input.fs, env: providerContext.env, command: providerContext.command, options: payload }, { observers: createMutationReporter(input.resources.logger) });
            if (adapter.isolatedEnv && adapter.isolatedEnv.requiresConfig !== false) {
                await applyIsolatedConfiguration({
                    adapter: entry,
                    providerContext,
                    payload,
                    isolated: adapter.isolatedEnv,
                    providerName: adapter.name,
                    observers: createMutationReporter(input.resources.logger)
                });
            }
        });
    }
}
async function unconfigureServicesForProvider(input) {
    const configuredServices = await loadConfiguredServices({
        fs: input.fs,
        filePath: input.container.env.configPath,
        projectFilePath: input.container.env.projectConfigPath,
        readOnly: input.flags.dryRun
    });
    for (const [service, metadata] of Object.entries(configuredServices)) {
        if (metadata.provider !== input.providerId) {
            continue;
        }
        const adapter = resolveServiceAdapter(input.container, service);
        const providerContext = createProviderContextWithFileSystem(input.container, adapter, input.resources, input.fs);
        const payload = { env: providerContext.env, provider: { id: input.providerId } };
        await input.container.registry.invoke(service, "unconfigure", async (entry) => {
            const observers = createMutationReporter(input.resources.logger);
            await entry.unconfigure({ fs: input.fs, env: providerContext.env, command: providerContext.command, options: payload }, { observers });
            if (adapter.isolatedEnv && adapter.isolatedEnv.requiresConfig !== false) {
                await entry.unconfigure({
                    fs: input.fs,
                    env: providerContext.env,
                    command: providerContext.command,
                    options: payload,
                    pathMapper: {
                        mapTargetDirectory: ({ targetDirectory }) => resolveIsolatedTargetDirectory({
                            targetDirectory,
                            isolated: adapter.isolatedEnv,
                            env: providerContext.env,
                            providerName: adapter.name
                        })
                    }
                }, { observers });
            }
            if (!input.flags.dryRun) {
                await unconfigureService({
                    fs: input.fs,
                    filePath: input.container.env.configPath,
                    projectFilePath: input.container.env.projectConfigPath,
                    service
                });
            }
        });
    }
}
function createProviderContextWithFileSystem(container, adapter, resources, fs) {
    const providerContext = buildProviderContext(container, adapter, resources);
    return {
        ...providerContext,
        command: { ...providerContext.command, fs }
    };
}
//# sourceMappingURL=provider.js.map