import { Option } from "commander";
import parseDuration from "parse-duration";
import { stringify as yamlStringify } from "yaml";
import { createExecutionResources, resolveCommandFlags } from "./shared.js";
import { ApiError, ValidationError } from "../errors.js";
import { getTheme, renderTable, withSpinner } from "toolcraft-design";
import { POE_PROVIDER_ID } from "@poe-code/providers";
const modelViewNames = ["capabilities", "pricing", "parameters", "raw"];
function formatTokenCount(tokens) {
    if (tokens >= 1_048_576) {
        const value = tokens / 1_048_576;
        return `${Number.isInteger(value) ? value : value.toFixed(1)}M`;
    }
    if (tokens >= 1_024) {
        const value = tokens / 1_024;
        return `${Number.isInteger(value) ? value : value.toFixed(1)}K`;
    }
    return String(tokens);
}
function formatPrice(perToken) {
    const perMTok = Math.round(perToken * 1_000_000 * 100) / 100;
    return `$${perMTok.toFixed(2)}`;
}
function formatOptionalPrice(value) {
    return value != null ? formatPrice(value) : "-";
}
function formatDate(ms) {
    const date = new Date(ms);
    const year = date.getUTCFullYear();
    const month = String(date.getUTCMonth() + 1).padStart(2, "0");
    const day = String(date.getUTCDate()).padStart(2, "0");
    return `${year}-${month}-${day}`;
}
function formatParameterType(schema) {
    if (schema.enum)
        return "enum";
    return schema.type ?? "unknown";
}
function truncate(value, maxLen) {
    if (value.length > maxLen) {
        return `${value.slice(0, maxLen - 3)}...`;
    }
    return value;
}
const MAX_VALUES_LENGTH = 105;
const MAX_DEFAULT_LENGTH = 36;
function formatParameterValues(schema) {
    let result = "";
    if (schema.enum) {
        result = schema.enum.join(", ");
    }
    else if (schema.type === "number" && (schema.minimum != null || schema.maximum != null)) {
        result = `${schema.minimum ?? ""}..${schema.maximum ?? ""}`;
    }
    return truncate(result, MAX_VALUES_LENGTH);
}
function formatDefaultValue(value) {
    if (value == null)
        return "";
    return truncate(String(value), MAX_DEFAULT_LENGTH);
}
function hasFeature(model, feature) {
    if (feature === "reasoning")
        return model.reasoning != null;
    return (model.supported_features ?? []).includes(feature);
}
function normalizeEndpoint(endpoint) {
    const trimmed = endpoint.trim().toLowerCase();
    return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
}
function preprocessModels(models) {
    const availableEndpoints = new Set();
    const preprocessedModels = models.map((model) => {
        const normalizedSupportedEndpoints = (model.supported_endpoints ?? [])
            .map(normalizeEndpoint);
        for (const endpoint of normalizedSupportedEndpoints) {
            availableEndpoints.add(endpoint);
        }
        return {
            ...model,
            normalized_supported_endpoints: normalizedSupportedEndpoints
        };
    });
    return {
        models: preprocessedModels,
        availableEndpoints: Array.from(availableEndpoints).sort()
    };
}
function toRawModel(model) {
    const { normalized_supported_endpoints: ignored_normalized_supported_endpoints, ...rawModel } = model;
    return rawModel;
}
function hasActiveFilters(options) {
    return options.provider !== undefined ||
        options.model !== undefined ||
        options.search !== undefined ||
        options.feature !== undefined ||
        options.endpoint !== undefined ||
        options.input !== undefined ||
        options.output !== undefined ||
        options.tools === true ||
        options.since !== undefined;
}
function parseSinceDuration(value) {
    if (value === undefined) {
        return undefined;
    }
    const duration = parseDuration(value);
    if (duration == null || !Number.isFinite(duration) || duration <= 0) {
        throw new ValidationError(`Invalid --since duration "${value}". Use a positive duration such as 7d, 2w, 3mo, or 1y.`);
    }
    return duration;
}
function normalizeRequiredFilter(name, value) {
    const normalized = value.trim().toLowerCase();
    if (normalized.length === 0) {
        throw new ValidationError(`Invalid ${name} value: must be non-empty.`);
    }
    return normalized;
}
function parseModalityFilter(name, value) {
    const modalities = value.split(",").map((part) => part.trim().toLowerCase());
    if (modalities.some((part) => part.length === 0)) {
        throw new ValidationError(`Invalid ${name} value: modalities must be non-empty comma-separated values.`);
    }
    return modalities;
}
function validateModelsResponse(models) {
    for (const [index, model] of models.entries()) {
        if (typeof model.created !== "number" || !Number.isFinite(model.created)) {
            throw new ApiError(`Invalid models response: data[${index}].created must be a finite number.`, { endpoint: "/v1/models" });
        }
    }
}
async function fetchModels(container, headers) {
    const response = await container.httpClient(`${container.env.poeBaseUrl}/v1/models`, {
        method: "GET",
        headers
    });
    if (!response.ok) {
        throw new ApiError(`Failed to fetch models (HTTP ${response.status})`, {
            httpStatus: response.status,
            endpoint: "/v1/models"
        });
    }
    return (await response.json());
}
function writeYaml(value) {
    const output = yamlStringify(value);
    process.stdout.write(output.endsWith("\n") ? output : `${output}\n`);
}
export function registerModelsCommand(program, container) {
    program
        .command("models")
        .alias("m")
        .description("List available Poe API models.")
        .option("--provider <name>", "Filter by provider name")
        .option("--model <name>", "Filter by exact model id")
        .option("--search <term>", "Search model id and provider name")
        .option("--feature <name>", "Filter by feature (tools, web_search, reasoning)")
        .option("--endpoint <path>", "Filter by supported endpoint (e.g. /v1/responses)")
        .option("--input <modalities>", "Filter by input modalities (e.g. text,image)")
        .option("--output <modalities>", "Filter by output modalities (e.g. text)")
        .option("--tools", "Show only models with tool support")
        .option("--since <duration>", "Show models added within duration (e.g. 7d, 2w, 3mo)")
        .addOption(new Option("--view <name>", "Table view: capabilities, pricing, parameters, or raw").choices(Array.from(modelViewNames)).default("capabilities"))
        .addHelpText("after", [
        "",
        "Filters:",
        "  --provider   Substring match on provider/owner (e.g. anthropic, openai)",
        "  --model      Exact model id match (case-insensitive, e.g. gpt-5.2-codex)",
        "  --search     Substring match on model id and provider (e.g. sonnet, openai)",
        "  --feature    Exact match: tools, web_search, or reasoning",
        "  --endpoint   Exact supported endpoint match (e.g. /v1/responses)",
        "  --input      Comma-separated input modalities: text, image, audio, video",
        "  --output     Comma-separated output modalities: text, image, audio",
        "  --tools      Shorthand for --feature tools",
        "  --since      Duration: s, m, h, d, w, mo, y (e.g. 7d, 2w, 3mo, 1y)",
        "",
        "Views:",
        "  capabilities  Model features, modalities, and context window (default)",
        "  pricing       Cost per million tokens with cache pricing",
        "  parameters    Model parameters grouped by model with type and defaults",
        "  raw           Full model data in YAML format",
        "",
        "Examples:",
        "  $ poe-code models --provider anthropic",
        "  $ poe-code models --feature reasoning --since 3mo",
        "  $ poe-code models --endpoint /v1/responses",
        "  $ poe-code models --input image --view pricing",
        "  $ poe-code models --search claude --view parameters",
        "  $ poe-code models --model claude-opus-4.7 --view raw",
        "  $ poe-code models --since 2w --output text"
    ].join("\n"))
        .action(async function () {
        const flags = resolveCommandFlags(program);
        const resources = createExecutionResources(container, flags, "models");
        const commandOptions = this.opts();
        const rawView = commandOptions.view === "raw";
        if (!rawView) {
            resources.logger.intro("models");
        }
        try {
            const sinceDuration = parseSinceDuration(commandOptions.since);
            const featureFilter = commandOptions.feature !== undefined
                ? normalizeRequiredFilter("--feature", commandOptions.feature)
                : undefined;
            const inputFilter = commandOptions.input !== undefined
                ? parseModalityFilter("--input", commandOptions.input)
                : undefined;
            const outputFilter = commandOptions.output !== undefined
                ? parseModalityFilter("--output", commandOptions.output)
                : undefined;
            let apiKey = null;
            try {
                apiKey = await container.providerRegistry.resolveCredential(POE_PROVIDER_ID, undefined, {
                    envVars: container.env.variables,
                    readOnly: flags.dryRun
                });
            }
            catch {
                apiKey = null;
            }
            if (flags.dryRun) {
                resources.logger.dryRun("Dry run: would fetch models from Poe API.");
                return;
            }
            const headers = {};
            if (apiKey) {
                headers.Authorization = `Bearer ${apiKey}`;
            }
            const result = rawView
                ? await fetchModels(container, headers)
                : await withSpinner({
                    message: "Fetching models...",
                    fn: () => fetchModels(container, headers),
                    stopMessage: (r) => `${r.data.length} models fetched`
                });
            validateModelsResponse(result.data);
            const { models: allModels, availableEndpoints } = preprocessModels(result.data);
            if (!rawView && allModels.length === 0) {
                resources.logger.info("No models found.");
                return;
            }
            let filtered = allModels;
            if (commandOptions.provider) {
                const term = commandOptions.provider.toLowerCase();
                filtered = filtered.filter((m) => m.owned_by.toLowerCase().includes(term));
            }
            if (commandOptions.model) {
                const term = commandOptions.model.toLowerCase();
                filtered = filtered.filter((m) => m.id.toLowerCase() === term);
            }
            if (commandOptions.search) {
                const term = commandOptions.search.toLowerCase();
                filtered = filtered.filter((m) => m.id.toLowerCase().includes(term) ||
                    m.owned_by.toLowerCase().includes(term));
            }
            if (featureFilter !== undefined) {
                filtered = filtered.filter((m) => hasFeature(m, featureFilter));
            }
            if (commandOptions.endpoint) {
                const endpoint = normalizeEndpoint(commandOptions.endpoint);
                if (!availableEndpoints.includes(endpoint)) {
                    const availableLabel = availableEndpoints.length > 0
                        ? availableEndpoints.join(", ")
                        : "none";
                    throw new ValidationError(`Unsupported endpoint "${endpoint}". Available endpoints: ${availableLabel}`);
                }
                filtered = filtered.filter((m) => m.normalized_supported_endpoints.includes(endpoint));
            }
            if (commandOptions.tools) {
                filtered = filtered.filter((m) => hasFeature(m, "tools"));
            }
            if (inputFilter !== undefined) {
                filtered = filtered.filter((m) => {
                    const modalities = m.architecture?.input_modalities ?? [];
                    return inputFilter.every((r) => modalities.includes(r));
                });
            }
            if (outputFilter !== undefined) {
                filtered = filtered.filter((m) => {
                    const modalities = m.architecture?.output_modalities ?? [];
                    return outputFilter.every((r) => modalities.includes(r));
                });
            }
            if (sinceDuration !== undefined) {
                const cutoff = Date.now() - sinceDuration;
                filtered = filtered.filter((m) => m.created >= cutoff);
            }
            if (!rawView && hasActiveFilters(commandOptions)) {
                resources.logger.info(`${filtered.length}/${allModels.length} models`);
            }
            if (!rawView && filtered.length === 0) {
                resources.logger.info("No models match the given filters.");
                return;
            }
            filtered.sort((a, b) => b.created - a.created);
            if (rawView) {
                writeYaml(filtered.map(toRawModel));
                return;
            }
            const theme = getTheme();
            let columns;
            let rows;
            if (commandOptions.view === "parameters") {
                const withParams = filtered.filter((m) => m.parameters.length > 0);
                if (withParams.length === 0) {
                    resources.logger.info("No models with parameters match the given filters.");
                    return;
                }
                columns = [
                    { name: "Model", title: "Model", alignment: "left", maxLen: 35 },
                    { name: "Parameter", title: "Parameter", alignment: "left", maxLen: 28 },
                    { name: "Type", title: "Type", alignment: "left", maxLen: 9 },
                    { name: "Default", title: "Default", alignment: "left", maxLen: 12 },
                    { name: "Values", title: "Values/Range", alignment: "left", maxLen: 35 }
                ];
                rows = [];
                for (const model of withParams) {
                    const modelLabel = theme.accent(`${model.owned_by.toLowerCase()}/${model.id}`);
                    rows.push({ Model: modelLabel, Parameter: "", Type: "", Default: "", Values: "" });
                    for (const param of model.parameters) {
                        rows.push({
                            Model: "",
                            Parameter: param.name,
                            Type: formatParameterType(param.schema),
                            Default: formatDefaultValue(param.default_value),
                            Values: formatParameterValues(param.schema)
                        });
                    }
                }
            }
            else if (commandOptions.view === "pricing") {
                columns = [
                    { name: "Model", title: "Model", alignment: "left", maxLen: 35 },
                    { name: "Context", title: "Context", alignment: "right", maxLen: 9 },
                    { name: "Input", title: "Input $/MTok", alignment: "right", maxLen: 12 },
                    { name: "Output", title: "Output $/MTok", alignment: "right", maxLen: 13 },
                    { name: "CacheRead", title: "Cache Read", alignment: "right", maxLen: 10 },
                    { name: "CacheWrite", title: "Cache Write", alignment: "right", maxLen: 11 },
                    { name: "Request", title: "Request", alignment: "right", maxLen: 9 }
                ];
                rows = filtered.map((model) => {
                    const pricing = model.pricing;
                    return {
                        Model: theme.accent(`${model.owned_by.toLowerCase()}/${model.id}`),
                        Context: model.context_window?.context_length != null ? formatTokenCount(model.context_window.context_length) : "-",
                        Input: formatOptionalPrice(pricing?.prompt ?? null),
                        Output: formatOptionalPrice(pricing?.completion ?? null),
                        CacheRead: formatOptionalPrice(pricing?.input_cache_read ?? null),
                        CacheWrite: formatOptionalPrice(pricing?.input_cache_write ?? null),
                        Request: formatOptionalPrice(pricing?.request ?? null)
                    };
                });
            }
            else {
                const allFeatures = Array.from(new Set(filtered.flatMap((m) => m.supported_features ?? []))).sort();
                columns = [
                    { name: "Model", title: "Model", alignment: "left", maxLen: 35 },
                    { name: "Date", title: "Date Added", alignment: "left", maxLen: 12 },
                    { name: "Modality", title: "Modality", alignment: "left", maxLen: 24 },
                    { name: "Context", title: "Context", alignment: "right", maxLen: 9 },
                    { name: "Reasoning", title: "Reasoning", alignment: "left", maxLen: 9 },
                    ...allFeatures.map((feature) => ({
                        name: feature,
                        title: feature,
                        alignment: "left",
                        maxLen: Math.max(feature.length, 3)
                    }))
                ];
                rows = filtered.map((model) => {
                    const row = Object.assign(Object.create(null), {
                        Model: theme.accent(`${model.owned_by.toLowerCase()}/${model.id}`),
                        Date: theme.muted(formatDate(model.created)),
                        Modality: model.architecture
                            ? `${model.architecture.input_modalities.join(",")}->${model.architecture.output_modalities.join(",")}`
                            : "-",
                        Context: model.context_window?.context_length != null ? formatTokenCount(model.context_window.context_length) : "-",
                        Reasoning: model.reasoning ? theme.success("✓") : ""
                    });
                    for (const feature of allFeatures) {
                        row[feature] = (model.supported_features ?? []).includes(feature)
                            ? theme.success("✓")
                            : "";
                    }
                    return row;
                });
            }
            resources.logger.info(renderTable({ theme, columns, rows }));
        }
        catch (error) {
            if (error instanceof Error) {
                resources.logger.logException(error, "models", {
                    operation: "fetch-models"
                });
            }
            throw error;
        }
    });
}
//# sourceMappingURL=models.js.map