/**
 * Browser-friendly packages only.
 * (No Node.js built-ins like fs, path, dotenv, etc.)
 * Works in both Node.js and modern browsers.
 */
import { isNull } from "graphai";
import { userAssert } from "../utils/utils.js";
import { slideThemes } from "../data/slideThemes.js";
import { text2ImageProviderSchema, text2HtmlImageProviderSchema, text2MovieProviderSchema, text2SpeechProviderSchema, mulmoCanvasDimensionSchema, mulmoTransitionSchema, } from "../types/schema.js";
import { provider2ImageAgent, provider2MovieAgent, provider2LLMAgent, provider2TTSAgent, provider2SoundEffectAgent, provider2LipSyncAgent, defaultProviders, } from "../types/provider2agent.js";
const defaultTextSlideStyles = [
    '*,*::before,*::after{box-sizing:border-box}body,h1,h2,h3,h4,p,figure,blockquote,dl,dd{margin:0}ul[role="list"],ol[role="list"]{list-style:none}html:focus-within{scroll-behavior:smooth}body{min-height:100vh;text-rendering:optimizeSpeed;line-height:1.5}a:not([class]){text-decoration-skip-ink:auto}img,picture{max-width:100%;display:block}input,button,textarea,select{font:inherit}@media(prefers-reduced-motion:reduce){html:focus-within{scroll-behavior:auto}*,*::before,*::after{animation-duration:.01ms !important;animation-iteration-count:1 !important;transition-duration:.01ms !important;scroll-behavior:auto !important}}',
    "body { margin: 60px; margin-top: 40px; color:#333; background-color:#fff; font-size: 30px; font-family: Arial, sans-serif; box-sizing: border-box; height: 100vh }",
    "h1 { font-size: 56px; margin-bottom: 20px; text-align: center }",
    "h2 { font-size: 48px; text-align: center }",
    "h3 { font-size: 36px }",
    "ul { margin-left: 40px } ",
    "pre { background: #eeeecc; font-size: 16px; padding:4px }",
    "p { margin-left: 40px }",
    "table { font-size: 36px; margin: auto; border: 1px solid gray; border-collapse: collapse }",
    "th { border-bottom: 1px solid gray }",
    "td, th { padding: 8px }",
    "tr:nth-child(even) { background-color: #eee }",
];
export const MulmoPresentationStyleMethods = {
    getCanvasSize(presentationStyle) {
        return mulmoCanvasDimensionSchema.parse(presentationStyle.canvasSize);
    },
    getAllSpeechProviders(presentationStyle) {
        const providers = new Set();
        Object.values(presentationStyle.speechParams.speakers).forEach((speaker) => {
            const provider = text2SpeechProviderSchema.parse(speaker.provider);
            providers.add(provider);
        });
        return providers;
    },
    getTextSlideStyle(presentationStyle, beat) {
        const styles = presentationStyle.textSlideParams?.cssStyles ?? [];
        // NOTES: Taking advantage of CSS override rule (you can redefine it to override)
        const extraStyles = beat.textSlideParams?.cssStyles ?? [];
        // This code allows us to support both string and array of strings for cssStyles
        return [...defaultTextSlideStyles, ...[styles], ...[extraStyles]].flat().join("\n");
    },
    /**
     * Resolve the effective theme for a slide-typed beat.
     * Priority (high → low):
     *   1. `beat.image.theme` — per-beat override
     *   2. `presentationStyle.slideParams.theme` — deck-level default
     *   3. `slideThemes.corporate` — built-in fallback
     *
     * Returns the corporate fallback (without throwing) when called on a
     * non-slide beat, so callers driving the deck preview from a mixed
     * script can hand any beat in without a special-case check.
     *
     * Exposed as a method so the editor (`@mulmocast/deck-web`) can
     * import it via `mulmocast/browser` and surface the same priority
     * its render-time slide.ts already enforces — closing the editor
     * vs. PDF/movie theme mismatch reported on mulmoclaude#1622.
     */
    getResolvedSlideTheme(presentationStyle, beat) {
        if (beat.image?.type === "slide" && beat.image.theme) {
            return beat.image.theme;
        }
        return presentationStyle.slideParams?.theme ?? slideThemes.corporate;
    },
    getDefaultSpeaker(presentationStyle) {
        const speakers = presentationStyle?.speechParams?.speakers ?? {};
        const keys = Object.keys(speakers).sort();
        userAssert(keys.length !== 0, "presentationStyle.speechParams.speakers is not set!!");
        const defaultSpeaker = keys.find((key) => speakers[key].isDefault);
        if (!isNull(defaultSpeaker)) {
            return defaultSpeaker;
        }
        return keys[0];
    },
    getSpeaker(context, beat, targetLang) {
        userAssert(!!context.presentationStyle?.speechParams?.speakers, "presentationStyle.speechParams.speakers is not set!!");
        const speakerId = beat?.speaker ?? MulmoPresentationStyleMethods.getDefaultSpeaker(context.presentationStyle);
        const speaker = context.presentationStyle.speechParams.speakers[speakerId];
        userAssert(!!speaker, `speaker is not set: speaker "${speakerId}"`);
        // Check if the speaker has a language-specific version.
        // Normally, lang is determined by the context, but lang may be specified when using the API.
        const lang = targetLang ?? context.lang ?? context?.studio?.script?.lang;
        if (speaker.lang && lang && speaker.lang[lang]) {
            return speaker.lang[lang];
        }
        return speaker;
    },
    getMovieTransition(context, beat) {
        const transitionData = beat.movieParams?.transition ?? context.presentationStyle.movieParams?.transition;
        if (!transitionData)
            return null;
        return mulmoTransitionSchema.parse(transitionData);
    },
    /* NOTE: This method is not used.
    getTTSModel(context: MulmoStudioContext, beat: MulmoBeat): string | undefined {
      const speaker = MulmoPresentationStyleMethods.getSpeaker(context, beat);
      return speaker.model;
    },
    */
    getText2ImageProvider(provider) {
        return text2ImageProviderSchema.parse(provider);
    },
    getImageAgentInfo(presentationStyle, beat) {
        // Notice that we copy imageParams from presentationStyle and update
        // provider and model appropriately.
        const imageParams = { ...presentationStyle.imageParams, ...beat?.imageParams };
        const provider = MulmoPresentationStyleMethods.getText2ImageProvider(imageParams?.provider) ?? defaultProviders.text2image;
        const agentInfo = provider2ImageAgent[provider];
        // The default text2image model is gpt-image-1 from OpenAI.
        const defaultImageParams = {
            provider,
            model: agentInfo.defaultModel,
        };
        return {
            agent: agentInfo.agentName,
            imageParams: { ...defaultImageParams, ...imageParams },
            keyName: agentInfo.keyName,
        };
    },
    getMovieAgentInfo(presentationStyle, beat) {
        const movieParams = { ...presentationStyle.movieParams, ...beat?.movieParams };
        const movieProvider = text2MovieProviderSchema.parse(movieParams?.provider ?? defaultProviders.text2movie);
        const agentInfo = provider2MovieAgent[movieProvider];
        return {
            agent: agentInfo.agentName,
            movieParams,
            keyName: agentInfo.keyName,
        };
    },
    getSoundEffectAgentInfo(presentationStyle, beat) {
        const soundEffectProvider = (beat.soundEffectParams?.provider ??
            presentationStyle.soundEffectParams?.provider ??
            defaultProviders.soundEffect);
        const agentInfo = provider2SoundEffectAgent[soundEffectProvider];
        return agentInfo;
    },
    getLipSyncAgentInfo(presentationStyle, beat) {
        const lipSyncProvider = (beat.lipSyncParams?.provider ??
            presentationStyle.lipSyncParams?.provider ??
            defaultProviders.lipSync);
        const agentInfo = provider2LipSyncAgent[lipSyncProvider];
        return agentInfo;
    },
    /** Concurrency for image/movie generation graph (uses min of imageParams/movieParams) */
    getImageConcurrency(presentationStyle) {
        const imageConcurrency = presentationStyle.imageParams?.concurrency;
        const movieConcurrency = presentationStyle.movieParams?.concurrency;
        // User-specified concurrency takes precedence.
        // Use the smaller of imageParams/movieParams since they share the same graph.
        if (imageConcurrency !== undefined || movieConcurrency !== undefined) {
            const values = [imageConcurrency, movieConcurrency].filter((v) => v !== undefined);
            return Math.min(...values);
        }
        // Fallback: provider-based auto-detection
        const imageAgentInfo = MulmoPresentationStyleMethods.getImageAgentInfo(presentationStyle);
        if (imageAgentInfo.imageParams.provider === "openai") {
            // NOTE: Here are the rate limits of OpenAI's text2image API (1token = 32x32 patch).
            // gpt-image-1: 3,000,000 TPM, 150 images per minute
            if (imageAgentInfo.imageParams.model === provider2ImageAgent.openai.defaultModel) {
                return 16;
            }
        }
        return 4;
    },
    /** Concurrency for audio/TTS generation graph */
    getAudioConcurrency(presentationStyle) {
        // User-specified concurrency takes precedence
        const userConcurrency = presentationStyle.audioParams?.concurrency;
        if (userConcurrency !== undefined) {
            return userConcurrency;
        }
        // Fallback: provider-based auto-detection
        const hasLimitedConcurrencyProvider = Object.values(presentationStyle.speechParams.speakers).some((speaker) => {
            const provider = text2SpeechProviderSchema.parse(speaker.provider);
            return provider2TTSAgent[provider].hasLimitedConcurrency;
        });
        return hasLimitedConcurrencyProvider ? 1 : 8;
    },
    getHtmlImageAgentInfo(presentationStyle) {
        const provider = text2HtmlImageProviderSchema.parse(presentationStyle.htmlImageParams?.provider);
        const defaultConfig = provider2LLMAgent[provider];
        const model = presentationStyle.htmlImageParams?.model ? presentationStyle.htmlImageParams?.model : defaultConfig.defaultModel;
        return {
            provider,
            agent: defaultConfig.agentName,
            model,
            max_tokens: defaultConfig.max_tokens,
        };
    },
    getImageType(_, beat) {
        return beat.image?.type == "movie" ? "movie" : "image";
    },
};
