import { Command } from "commander";
import { readFile } from "node:fs/promises";
import { resolve } from "node:path";
import { SHORT_FICTION_DEFAULT_CHAPTERS, SHORT_FICTION_DEFAULT_CHARS_PER_CHAPTER, SHORT_FICTION_MAX_CHAPTERS, SHORT_FICTION_MAX_CHARS_PER_CHAPTER, SHORT_FICTION_MIN_CHAPTERS, SHORT_FICTION_MIN_CHARS_PER_CHAPTER, createLLMClient, runShortFictionProduction, } from "@actalk/inkos-core";
import { buildPipelineConfig, findProjectRoot, loadConfig, log, logError } from "../utils.js";
export { extractResponsesImageBase64, resolveCoverApiKey } from "@actalk/inkos-core";
export const shortCommand = new Command("short")
    .description("Short fiction production workflow");
shortCommand
    .command("run")
    .description("Run a short fiction chain from a direction")
    .requiredOption("--direction <text>", "Story direction, e.g. 女频短篇 婚姻背叛 证据反杀")
    .option("--reference <path>", "Optional reference notes/text")
    .option("--story-id <id>", "Output story id under shorts/")
    .option("--out-dir <path>", "Output directory", "shorts")
    .option("--chapters <n>", "Complete short chapter count (12-18)", String(SHORT_FICTION_DEFAULT_CHAPTERS))
    .option("--chars <n>", "Target characters per chapter (900-1200)", String(SHORT_FICTION_DEFAULT_CHARS_PER_CHAPTER))
    .option("--stage <stage>", "Execution stage (outline | draft | package | all)", "all")
    .option("--llm-base-url <url>", "Override LLM base URL")
    .option("--model <model>", "Fallback model for all short stages")
    .option("--planner-model <model>", "Model for outline creation/revision")
    .option("--outline-review-model <model>", "Model for outline review")
    .option("--writer-model <model>", "Model for first full draft")
    .option("--draft-review-model <model>", "Model for draft review")
    .option("--revise-model <model>", "Model for second full draft")
    .option("--package-model <model>", "Model for synopsis and cover prompt packaging")
    .option("--cover-base-url <url>", "OpenAI-compatible Responses API base URL for cover generation, e.g. https://api.openai.com/v1")
    .option("--cover-endpoint <url>", "Exact Responses endpoint for cover generation; overrides --cover-base-url")
    .option("--cover-model <model>", "Image-capable Responses model for cover generation", "gpt-5.5")
    .option("--cover-size <size>", "Cover image size", "1024x1360")
    .option("--cover-api-key-env <name>", "Env var containing cover API key", "INKOS_COVER_API_KEY")
    .option("--no-cover", "Skip cover image generation")
    .option("--json", "Output JSON")
    .action(async (opts) => {
    try {
        const root = findProjectRoot();
        const chapterCount = parseBoundedInteger(opts.chapters, SHORT_FICTION_DEFAULT_CHAPTERS, "chapters", SHORT_FICTION_MIN_CHAPTERS, SHORT_FICTION_MAX_CHAPTERS);
        const charsPerChapter = parseBoundedInteger(opts.chars, SHORT_FICTION_DEFAULT_CHARS_PER_CHAPTER, "chars", SHORT_FICTION_MIN_CHARS_PER_CHAPTER, SHORT_FICTION_MAX_CHARS_PER_CHAPTER);
        const reference = opts.reference ? await readReference(root, opts.reference) : undefined;
        const models = resolveShortRunModels(opts);
        const plannerRuntime = await createShortRuntime(root, {
            llmBaseUrl: opts.llmBaseUrl,
            model: models.planner,
            quiet: Boolean(opts.json),
        });
        const outlineReviewRuntime = await createShortRuntime(root, {
            llmBaseUrl: opts.llmBaseUrl,
            model: models.outlineReview,
            quiet: Boolean(opts.json),
        });
        const writerRuntime = await createShortRuntime(root, {
            llmBaseUrl: opts.llmBaseUrl,
            model: models.writer,
            quiet: Boolean(opts.json),
        });
        const draftReviewRuntime = await createShortRuntime(root, {
            llmBaseUrl: opts.llmBaseUrl,
            model: models.draftReview,
            quiet: Boolean(opts.json),
        });
        const reviseRuntime = await createShortRuntime(root, {
            llmBaseUrl: opts.llmBaseUrl,
            model: models.revise,
            quiet: Boolean(opts.json),
        });
        const packageRuntime = await createShortRuntime(root, {
            llmBaseUrl: opts.llmBaseUrl,
            model: models.package,
            quiet: Boolean(opts.json),
        });
        const result = await runShortFictionProduction({
            projectRoot: root,
            direction: opts.direction,
            runtimes: {
                planner: { ...plannerRuntime, projectRoot: root },
                outlineReview: { ...outlineReviewRuntime, projectRoot: root },
                writer: { ...writerRuntime, projectRoot: root },
                draftReview: { ...draftReviewRuntime, projectRoot: root },
                revise: { ...reviseRuntime, projectRoot: root },
                package: { ...packageRuntime, projectRoot: root },
            },
            reference,
            storyId: opts.storyId,
            outDir: opts.outDir,
            chapterCount,
            charsPerChapter,
            stage: opts.stage,
            cover: opts.cover,
            coverBaseUrl: opts.coverBaseUrl,
            coverEndpoint: opts.coverEndpoint,
            coverModel: opts.coverModel,
            coverSize: opts.coverSize,
            coverApiKeyEnv: opts.coverApiKeyEnv,
            onProgress: opts.json ? undefined : (message) => log(message),
        });
        const payload = {
            ...result,
            models,
        };
        if (opts.json) {
            log(JSON.stringify(payload, null, 2));
        }
        else {
            log(`Short run complete: ${result.storyId}`);
            log(`Final: ${payload.finalMarkdownPath}`);
            log(`Sales package: ${payload.salesPackagePath}`);
            log(formatCoverStatus(payload.coverImagePath, payload.coverError));
        }
    }
    catch (e) {
        logCommandError("Short run failed", e, opts.json);
    }
});
function resolveShortRunModels(options) {
    return {
        planner: options.plannerModel || options.model,
        outlineReview: options.outlineReviewModel || options.model,
        writer: options.writerModel || options.model,
        draftReview: options.draftReviewModel || options.model,
        revise: options.reviseModel || options.model,
        package: options.packageModel || options.model,
    };
}
async function createShortRuntime(root, options) {
    try {
        const config = await loadConfig({ projectRoot: root });
        if (options.llmBaseUrl)
            config.llm.baseUrl = options.llmBaseUrl;
        if (options.model)
            config.llm.model = options.model;
        const pipelineConfig = buildPipelineConfig(config, root, { quiet: options.quiet });
        return {
            client: pipelineConfig.client,
            model: pipelineConfig.model,
            logger: pipelineConfig.logger,
            onStreamProgress: pipelineConfig.onStreamProgress,
        };
    }
    catch (e) {
        if (!String(e).includes("inkos.json not found"))
            throw e;
        const llmConfig = buildEnvLLMConfig(options);
        return {
            client: createLLMClient(llmConfig),
            model: llmConfig.model,
        };
    }
}
function buildEnvLLMConfig(options) {
    const baseUrl = options.llmBaseUrl ?? process.env.INKOS_LLM_BASE_URL;
    const model = options.model ?? process.env.INKOS_LLM_MODEL;
    if (!baseUrl)
        throw new Error("LLM base URL is required. Set INKOS_LLM_BASE_URL or pass --llm-base-url.");
    if (!model)
        throw new Error("LLM model is required. Set INKOS_LLM_MODEL or pass --model.");
    return {
        provider: "openai",
        service: process.env.INKOS_LLM_SERVICE ?? "custom",
        configSource: "env",
        baseUrl,
        apiKey: process.env.INKOS_LLM_API_KEY ?? "",
        model,
        temperature: parseEnvNumber(process.env.INKOS_LLM_TEMPERATURE, 0.1),
        thinkingBudget: parseEnvInteger(process.env.INKOS_LLM_THINKING_BUDGET, 0),
        apiFormat: process.env.INKOS_LLM_API_FORMAT === "responses" ? "responses" : "chat",
        stream: process.env.INKOS_LLM_STREAM === "false" ? false : true,
    };
}
async function readReference(root, path) {
    const resolved = resolve(root, path);
    return {
        path,
        text: await readFile(resolved, "utf-8"),
    };
}
function parseBoundedInteger(value, fallback, name, min, max) {
    const parsed = value ? Number.parseInt(value, 10) : fallback;
    if (!Number.isInteger(parsed) || parsed < min || parsed > max) {
        throw new Error(`${name} must be an integer between ${min} and ${max}.`);
    }
    return parsed;
}
function parseEnvNumber(value, fallback) {
    if (!value)
        return fallback;
    const parsed = Number.parseFloat(value);
    return Number.isFinite(parsed) ? parsed : fallback;
}
function parseEnvInteger(value, fallback) {
    if (!value)
        return fallback;
    const parsed = Number.parseInt(value, 10);
    return Number.isFinite(parsed) ? parsed : fallback;
}
function formatCoverStatus(coverImagePath, coverError) {
    if (coverImagePath)
        return `Cover: ${coverImagePath}`;
    if (coverError)
        return `Cover: skipped (${coverError})`;
    return "Cover: skipped";
}
function logCommandError(prefix, error, json) {
    if (json) {
        log(JSON.stringify({ error: `${prefix}: ${String(error)}` }, null, 2));
        return;
    }
    logError(`${prefix}: ${String(error)}`);
}
//# sourceMappingURL=short-fiction.js.map