import { BaseAgent } from "./base.js";
import { countChapterLength } from "../utils/length-metrics.js";
import { buildShortFictionDraftReviewSystemPrompt, buildShortFictionDraftReviewUserPrompt, buildShortFictionDraftRevisionFollowup, buildShortFictionOutlineReviewSystemPrompt, buildShortFictionOutlineReviewUserPrompt, buildShortFictionOutlineRevisionFollowup, buildShortFictionOutlineSystemPrompt, buildShortFictionOutlineUserPrompt, buildShortFictionPackageSystemPrompt, buildShortFictionPackageUserPrompt, buildShortFictionWriterSystemPrompt, buildShortFictionWriterUserPrompt, } from "../prompts/short-fiction.js";
export const SHORT_FICTION_DEFAULT_CHAPTERS = 12;
export const SHORT_FICTION_MIN_CHAPTERS = 12;
export const SHORT_FICTION_MAX_CHAPTERS = 18;
export const SHORT_FICTION_DEFAULT_CHARS_PER_CHAPTER = 1000;
export const SHORT_FICTION_MIN_CHARS_PER_CHAPTER = 900;
export const SHORT_FICTION_MAX_CHARS_PER_CHAPTER = 1200;
export class ShortFictionOutlineAgent extends BaseAgent {
    get name() {
        return "short-fiction-outline";
    }
    async createOutline(input) {
        const response = await retryShortFictionCall(() => this.chat([
            { role: "system", content: buildShortFictionOutlineSystemPrompt() },
            { role: "user", content: buildShortFictionOutlineUserPrompt(input) },
        ], { temperature: 0.55, maxTokens: 8192 }), this.name, this.log);
        return parseShortFictionOutline(response.content);
    }
}
export class ShortFictionOutlineReviewerAgent extends BaseAgent {
    get name() {
        return "short-fiction-outline-reviewer";
    }
    async reviewOutline(input) {
        const response = await retryShortFictionCall(() => this.chat([
            { role: "system", content: buildShortFictionOutlineReviewSystemPrompt() },
            { role: "user", content: buildShortFictionOutlineReviewUserPrompt(input) },
        ], { temperature: 0.3, maxTokens: 4096 }), this.name, this.log);
        return response.content.trim();
    }
}
export class ShortFictionOutlineReviserAgent extends BaseAgent {
    get name() {
        return "short-fiction-outline-reviser";
    }
    async reviseOutline(input) {
        const response = await retryShortFictionCall(() => this.chat([
            { role: "system", content: buildShortFictionOutlineSystemPrompt() },
            { role: "user", content: buildShortFictionOutlineUserPrompt(input) },
            { role: "assistant", content: input.outline.rawContent.trim() },
            { role: "user", content: buildShortFictionOutlineRevisionFollowup(input) },
        ], { temperature: 0.45, maxTokens: 8192 }), this.name, this.log);
        return parseShortFictionOutline(response.content);
    }
}
export class ShortFictionWriterAgent extends BaseAgent {
    get name() {
        return "short-fiction-writer";
    }
    async writeDraft(input) {
        const response = await retryShortFictionCall(() => this.chat([
            { role: "system", content: buildShortFictionWriterSystemPrompt() },
            { role: "user", content: buildShortFictionWriterUserPrompt(input) },
        ], {
            temperature: 0.58,
            maxTokens: estimateShortFictionMaxTokens(input.chapterCount, input.charsPerChapter),
        }), this.name, this.log);
        return parseShortFictionBatchDraft(response.content, { expectedChapters: input.chapterCount });
    }
}
export class ShortFictionDraftReviewerAgent extends BaseAgent {
    get name() {
        return "short-fiction-draft-reviewer";
    }
    async reviewDraft(input) {
        const response = await retryShortFictionCall(() => this.chat([
            { role: "system", content: buildShortFictionDraftReviewSystemPrompt() },
            { role: "user", content: buildShortFictionDraftReviewUserPrompt({
                    ...input,
                    draftMarkdown: renderShortFictionDraftMarkdown(input.draft),
                }) },
        ], { temperature: 0.3, maxTokens: 8192 }), this.name, this.log);
        return response.content.trim();
    }
}
export class ShortFictionDraftReviserAgent extends BaseAgent {
    get name() {
        return "short-fiction-draft-reviser";
    }
    async reviseDraft(input) {
        const response = await retryShortFictionCall(() => this.chat([
            { role: "system", content: buildShortFictionWriterSystemPrompt() },
            { role: "user", content: buildShortFictionWriterUserPrompt(input) },
            { role: "assistant", content: input.draft.rawContent.trim() || renderShortFictionDraftMarkdown(input.draft) },
            { role: "user", content: buildShortFictionDraftRevisionFollowup(input) },
        ], {
            temperature: 0.45,
            maxTokens: estimateShortFictionMaxTokens(input.chapterCount, input.charsPerChapter),
        }), this.name, this.log);
        return parseShortFictionBatchDraft(response.content, { expectedChapters: input.chapterCount });
    }
}
export class ShortFictionPackagingAgent extends BaseAgent {
    get name() {
        return "short-fiction-packaging";
    }
    async generatePackage(input) {
        const response = await retryShortFictionCall(() => this.chat([
            { role: "system", content: buildShortFictionPackageSystemPrompt() },
            { role: "user", content: buildShortFictionPackageUserPrompt({
                    direction: input.direction,
                    outlineMarkdown: input.outlineMarkdown,
                    draftMarkdown: renderShortFictionDraftMarkdown(input.draft),
                    draftTitle: input.draft.storyTitle,
                }) },
        ], { temperature: 0.45, maxTokens: 4096 }), this.name, this.log);
        return parseShortFictionSalesPackage(response.content, input.draft.storyTitle);
    }
}
export function parseShortFictionOutline(rawContent) {
    const storyTitle = normalizeTitle(extractTaggedBlock(rawContent, "SHORT_FICTION_PLAN_TITLE")
        || extractTaggedBlock(rawContent, "SHORT_FICTION_TITLE")
        || extractFirstHeading(rawContent)
        || "未命名短篇") || "未命名短篇";
    return { storyTitle, rawContent: rawContent.trim() };
}
export function parseShortFictionBatchDraft(rawContent, options) {
    const expectedChapters = options?.expectedChapters ?? SHORT_FICTION_DEFAULT_CHAPTERS;
    const storyTitle = normalizeTitle(extractTaggedBlock(rawContent, "SHORT_FICTION_TITLE")
        || extractFirstHeading(rawContent)
        || "未命名短篇") || "未命名短篇";
    const openingHook = extractTaggedBlock(rawContent, "SHORT_FICTION_OPENING_HOOK")
        || extractTaggedBlock(rawContent, "OPENING_HOOK");
    const chapters = [];
    for (let number = 1; number <= expectedChapters; number += 1) {
        const title = normalizeChapterTitle(extractTaggedBlock(rawContent, `CHAPTER ${number} TITLE`)
            || extractMarkdownChapterTitle(rawContent, number)
            || `第${number}章`, number);
        const content = sanitizeChapterContent(extractTaggedBlock(rawContent, `CHAPTER ${number} CONTENT`)
            || extractDuplicateTitleTaggedChapterContent(rawContent, number)
            || extractMarkdownChapterContent(rawContent, number)
            || "");
        chapters.push({
            number,
            title,
            content,
            charCount: countChapterLength(content, "zh_chars"),
        });
    }
    return {
        storyTitle,
        openingHook: openingHook.trim() || undefined,
        chapters,
        rawContent,
    };
}
export function validateShortFictionDraftForFinal(draft, options) {
    if (options?.expectedChapters !== undefined && draft.chapters.length !== options.expectedChapters) {
        throw new Error(`Short-hit draft is incomplete; expected ${options.expectedChapters} chapters, got ${draft.chapters.length}.`);
    }
    const emptyChapters = draft.chapters
        .filter((chapter) => !chapter.content.trim())
        .map((chapter) => chapter.number);
    if (emptyChapters.length > 0) {
        throw new Error(`Short-hit draft is incomplete; empty chapters: ${emptyChapters.join(", ")}.`);
    }
}
export function renderShortFictionDraftMarkdown(draft) {
    return [
        `# ${draft.storyTitle}`,
        draft.openingHook ? `## 开篇钩子\n\n${draft.openingHook}` : "",
        ...draft.chapters.map((chapter) => [
            `## ${formatShortFictionChapterHeading(chapter.number, chapter.title)}`,
            "",
            chapter.content,
        ].join("\n")),
    ].filter(Boolean).join("\n\n");
}
export function parseShortFictionSalesPackage(rawContent, fallbackTitle = "未命名短篇") {
    const title = normalizeTitle(extractTaggedBlock(rawContent, "SHORT_FICTION_PACKAGE_TITLE")
        || extractTaggedBlock(rawContent, "SHORT_FICTION_TITLE")
        || fallbackTitle) || fallbackTitle;
    const intro = extractTaggedBlock(rawContent, "SHORT_FICTION_INTRO")
        || extractTaggedBlock(rawContent, "INTRO")
        || "";
    const sellingRaw = extractTaggedBlock(rawContent, "SHORT_FICTION_SELLING_POINTS")
        || extractTaggedBlock(rawContent, "SELLING_POINTS")
        || "";
    const coverPrompt = extractTaggedBlock(rawContent, "SHORT_FICTION_COVER_PROMPT")
        || extractTaggedBlock(rawContent, "COVER_PROMPT")
        || "";
    return {
        title,
        intro: intro.trim(),
        sellingPoints: sellingRaw
            .split(/\n+/)
            .map((line) => line.replace(/^\s*[-*]\s*/, "").trim())
            .filter(Boolean),
        coverPrompt: coverPrompt.trim(),
        rawContent: rawContent.trim(),
    };
}
function extractTaggedBlock(raw, tag) {
    const escaped = tag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
    const pattern = new RegExp(`^\\s*===\\s*${escaped}\\s*===\\s*\\n([\\s\\S]*?)(?=^\\s*===\\s*[A-Z0-9_ ]+\\s*===\\s*$|(?![\\s\\S]))`, "im");
    return pattern.exec(raw)?.[1]?.trim() ?? "";
}
function extractFirstHeading(raw) {
    return raw.match(/^#\s+(.+)$/m)?.[1]?.trim() ?? "";
}
function extractMarkdownChapterTitle(raw, number) {
    const pattern = new RegExp(`^##\\s*(?:第\\s*${number}\\s*章\\s*)?(.+)$`, "m");
    return pattern.exec(raw)?.[1]?.trim() ?? "";
}
function extractMarkdownChapterContent(raw, number) {
    const pattern = new RegExp(`^##\\s*(?:第\\s*${number}\\s*章\\s*)?.*$\\n([\\s\\S]*?)(?=^##\\s*(?:第\\s*${number + 1}\\s*章\\s*)?.*$|(?![\\s\\S]))`, "m");
    return pattern.exec(raw)?.[1]?.trim() ?? "";
}
function extractDuplicateTitleTaggedChapterContent(raw, number) {
    const escapedTag = `CHAPTER ${number} TITLE`.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
    const titlePattern = new RegExp(`^\\s*===\\s*${escapedTag}\\s*===\\s*$`, "gim");
    const matches = Array.from(raw.matchAll(titlePattern));
    const duplicateTitle = matches[1];
    if (!duplicateTitle || duplicateTitle.index === undefined)
        return "";
    const start = duplicateTitle.index + duplicateTitle[0].length;
    const rest = raw.slice(start).replace(/^\s*\n/, "");
    const nextTag = rest.search(/^\\s*===\\s*(?:CHAPTER\\s+\\d+\\s+(?:TITLE|CONTENT)|SHORT_FICTION_[A-Z0-9_ ]+)\\s*===\\s*$/im);
    return (nextTag >= 0 ? rest.slice(0, nextTag) : rest).trim();
}
function sanitizeChapterContent(raw) {
    return raw
        .replace(/^```(?:md|markdown)?\s*/i, "")
        .replace(/```\s*$/i, "")
        .replace(/^===\s*[A-Z0-9_ ]+\s*===\s*$/gim, "")
        .trim();
}
function normalizeTitle(raw) {
    return raw
        .split("\n")
        .map((line) => line.replace(/^#+\s*/, "").trim())
        .find(Boolean)
        ?.replace(/^《(.+)》$/, "$1")
        .trim() ?? "";
}
function normalizeChapterTitle(raw, number) {
    const title = normalizeTitle(raw).replace(new RegExp(`^第\\s*${number}\\s*章\\s*`), "").trim();
    return title || `第${number}章`;
}
function formatShortFictionChapterHeading(number, title) {
    const trimmed = title.trim();
    if (!trimmed)
        return `第${number}章`;
    if (new RegExp(`^第\\s*${number}\\s*章`).test(trimmed))
        return trimmed;
    return `第${number}章 ${trimmed}`;
}
function estimateShortFictionMaxTokens(chapterCount, charsPerChapter) {
    return Math.max(12_288, Math.ceil(chapterCount * charsPerChapter * 2.2) + 4096);
}
async function retryShortFictionCall(operation, label, logger) {
    let lastError;
    for (let attempt = 1; attempt <= 2; attempt += 1) {
        try {
            return await operation();
        }
        catch (e) {
            lastError = e;
            if (attempt >= 2 || !isTransientShortFictionError(e))
                throw e;
            logger?.warn(`[${label}] transient LLM interruption, retrying once: ${String(e)}`);
        }
    }
    throw lastError;
}
function isTransientShortFictionError(error) {
    const message = String(error).toLowerCase();
    return message.includes("unexpected eof")
        || message.includes("econnreset")
        || message.includes("socket hang up")
        || message.includes("terminated")
        || message.includes("fetch failed");
}
//# sourceMappingURL=short-fiction.js.map