"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadBasePrompts = loadBasePrompts;
exports.getCategories = getCategories;
exports.getPromptsByCategory = getPromptsByCategory;
exports.searchPrompts = searchPrompts;
exports.getPromptDetail = getPromptDetail;
exports.personalizePromptsForCharacter = personalizePromptsForCharacter;
exports.getRandomPrompt = getRandomPrompt;
exports.getContextAwarePrompt = getContextAwarePrompt;
exports.getLibraryStats = getLibraryStats;
exports.personalizeTemplate = personalizeTemplate;
/**
 * Prompt Library Module
 *
 * 846+ base prompt'u karakter DNA'sina gore ozellestir.
 * Her karakter icin kendi fiziksel ozelliklerine uygun prompt'lar uretir.
 */
const promises_1 = require("fs/promises");
const path_1 = require("path");
const logger_js_1 = require("../../utils/logger.js");
const config_js_1 = require("../../utils/config.js");
const log = (0, logger_js_1.createModuleLogger)('prompt-library');
/** Cache for loaded base prompts */
let basePromptsCache = null;
/**
 * Loads base prompts from the extracted JSON file.
 */
async function loadBasePrompts() {
    if (basePromptsCache)
        return basePromptsCache;
    const filePath = (0, path_1.resolve)(config_js_1.config.dataDir, 'prompt-library', 'base-prompts.json');
    try {
        const raw = await (0, promises_1.readFile)(filePath, 'utf-8');
        basePromptsCache = JSON.parse(raw);
        log.info({ count: basePromptsCache.length }, 'Base prompts loaded');
        return basePromptsCache;
    }
    catch (err) {
        log.error({ error: err.message }, 'Failed to load base prompts');
        return [];
    }
}
/**
 * Builds a character-specific subject description from DNA.
 * This replaces the generic physical descriptions in base prompts.
 */
function buildCharacterSubject(dna) {
    const { identity, physicalMeasurements, skinMap, faceAnatomy, hairDNA, bodyBiometrics, expressionMotion, makeupStyle } = dna;
    return [
        `${identity.age} year old ${identity.nationality} woman named ${identity.name}`,
        `${physicalMeasurements.height_cm}cm tall, ${physicalMeasurements.body_type} ${bodyBiometrics.body_type_detail} body`,
        `skin tone ${skinMap.base} with ${skinMap.shadow} shadows and ${skinMap.highlight} highlights`,
        `${faceAnatomy.eye_shape} ${faceAnatomy.eye_color} eyes, ${faceAnatomy.eyebrow_shape} eyebrows`,
        `${faceAnatomy.nose_type} nose, ${faceAnatomy.lip_shape} lips`,
        `${faceAnatomy.cheekbones} cheekbones, ${faceAnatomy.jawline} jawline`,
        faceAnatomy.distinctive_marks.length > 0
            ? `distinctive marks: ${faceAnatomy.distinctive_marks.join(', ')}`
            : '',
        `${hairDNA.length} ${hairDNA.texture} ${hairDNA.style} hair, colored ${hairDNA.root_color} to ${hairDNA.mid_color} with ${hairDNA.highlight_color} highlights`,
        `${expressionMotion.default_expression} expression, ${expressionMotion.smile_type} smile, ${expressionMotion.gaze_style} gaze`,
        `${makeupStyle.philosophy} makeup with ${makeupStyle.skin_finish} finish`,
        `${bodyBiometrics.posture} posture, ${bodyBiometrics.skin_texture} skin texture`,
    ].filter(Boolean).join(', ');
}
/**
 * Builds camera settings string from DNA.
 */
function buildCameraFromDNA(_dna) {
    // Selfie-style camera settings - casual phone camera look
    return 'shot on iPhone 15 Pro front camera, selfie angle, close-up, slightly grainy authentic photo, natural phone camera quality';
}
/**
 * Flattens a nested JSON prompt object into a single descriptive string,
 * replacing physical/subject descriptions with character-specific ones.
 */
function flattenPromptJson(json, characterSubject, cameraSettings) {
    const parts = [];
    // Selfie-style prefix for all prompts
    parts.push('casual selfie photo taken with phone camera');
    // Extract scene/environment
    const scene = extractStringValue(json, ['scene', 'scene_type', 'setting', 'environment.location']);
    if (scene)
        parts.push(scene);
    // Use character subject instead of generic one
    parts.push(characterSubject);
    // Extract action/pose
    const action = extractStringValue(json, ['subject.action', 'subject.pose', 'pose', 'action']);
    if (action)
        parts.push(action);
    // Extract outfit/clothing
    const outfit = extractNestedString(json, 'outfit') || extractNestedString(json, 'subject.outfit') || extractNestedString(json, 'clothing');
    if (outfit)
        parts.push(outfit);
    // Extract environment details
    const envDetails = extractNestedString(json, 'environment') || extractNestedString(json, 'setting');
    if (envDetails)
        parts.push(envDetails);
    // Extract lighting
    const lighting = extractStringValue(json, ['environment.lighting', 'lighting', 'light']);
    if (lighting)
        parts.push(lighting);
    // Add camera settings
    parts.push(cameraSettings);
    // Extract aspect ratio for metadata
    const aspect = extractStringValue(json, ['camera.aspect', 'aspect_ratio', 'meta.aspect_ratio', 'camera.aspect_ratio']);
    // Extract realism details
    const realism = extractNestedString(json, 'realism_details') || extractNestedString(json, 'realism');
    if (realism)
        parts.push(realism);
    // Extract vibe/mood
    const vibe = extractStringValue(json, ['vibe', 'mood', 'atmosphere']);
    if (vibe)
        parts.push(vibe);
    // Selfie-style suffix for authenticity
    parts.push('instagram story style, natural imperfections, authentic casual photo');
    return parts.filter(Boolean).join(', ');
}
/**
 * Extracts a string value from nested object using dot-notation paths.
 */
function extractStringValue(obj, paths) {
    for (const path of paths) {
        const parts = path.split('.');
        let current = obj;
        for (const part of parts) {
            if (current && typeof current === 'object' && !Array.isArray(current)) {
                current = current[part];
            }
            else {
                current = undefined;
                break;
            }
        }
        if (typeof current === 'string' && current.length > 0)
            return current;
    }
    return null;
}
/**
 * Recursively extracts all string values from a nested object section.
 */
function extractNestedString(obj, key) {
    const parts = key.split('.');
    let current = obj;
    for (const part of parts) {
        if (current && typeof current === 'object' && !Array.isArray(current)) {
            current = current[part];
        }
        else {
            return null;
        }
    }
    if (!current || typeof current !== 'object')
        return null;
    return collectStrings(current).join(', ');
}
function collectStrings(obj) {
    if (typeof obj === 'string')
        return [obj];
    if (typeof obj === 'boolean' || typeof obj === 'number')
        return [];
    if (Array.isArray(obj))
        return obj.flatMap(collectStrings);
    if (obj && typeof obj === 'object') {
        return Object.values(obj).flatMap(collectStrings);
    }
    return [];
}
/**
 * Extracts aspect ratio from a prompt JSON.
 */
function extractAspectRatio(json) {
    const val = extractStringValue(json, [
        'camera.aspect', 'camera.aspect_ratio', 'aspect_ratio',
        'meta.aspect_ratio', 'camera_settings.aspect_ratio',
    ]);
    if (val && /^\d+:\d+$/.test(val))
        return val;
    return '9:16'; // default
}
/**
 * Extracts tags/keywords from a prompt for search/filtering.
 */
function extractTags(json, category) {
    const tags = new Set([category]);
    const allText = JSON.stringify(json).toLowerCase();
    const tagKeywords = [
        'beach', 'pool', 'gym', 'cafe', 'bedroom', 'bathroom', 'kitchen',
        'outdoor', 'indoor', 'night', 'sunset', 'morning', 'mirror',
        'selfie', 'bikini', 'dress', 'casual', 'lingerie', 'sporty',
        'car', 'boat', 'yacht', 'hotel', 'club', 'restaurant',
        'rain', 'snow', 'garden', 'balcony', 'rooftop', 'street',
    ];
    for (const kw of tagKeywords) {
        if (allText.includes(kw))
            tags.add(kw);
    }
    return [...tags];
}
/**
 * Maps a BasePrompt to a PromptTemplate.
 * Extracts name, description, aspectRatio, and tags from the JSON structure.
 */
function basePromptToTemplate(bp) {
    const sceneType = extractStringValue(bp.json, ['scene_type', 'scene', 'setting']) || '';
    const vibe = extractStringValue(bp.json, ['vibe', 'mood', 'atmosphere']) || '';
    const name = sceneType
        ? sceneType.length > 80 ? sceneType.slice(0, 80).trim() : sceneType
        : `${bp.category} #${bp.promptIndex}`;
    const description = vibe || sceneType || `${bp.category} prompt`;
    return {
        id: bp.id,
        category: bp.category,
        name,
        description,
        promptText: JSON.stringify(bp.json),
        aspectRatio: extractAspectRatio(bp.json),
        tags: extractTags(bp.json, bp.category),
    };
}
/**
 * Returns unique category names from loaded base prompts.
 */
async function getCategories() {
    const prompts = await loadBasePrompts();
    const categories = new Set();
    for (const p of prompts) {
        categories.add(p.category);
    }
    return [...categories].sort();
}
/**
 * Filters prompts by category, returning PromptTemplate objects.
 */
async function getPromptsByCategory(category) {
    const prompts = await loadBasePrompts();
    return prompts
        .filter((p) => p.category === category)
        .map(basePromptToTemplate);
}
/**
 * Text search across name, category, description fields (case-insensitive).
 */
async function searchPrompts(query) {
    if (!query || query.trim().length === 0)
        return [];
    const prompts = await loadBasePrompts();
    const lowerQuery = query.toLowerCase();
    return prompts
        .filter((bp) => {
        const template = basePromptToTemplate(bp);
        return (template.name.toLowerCase().includes(lowerQuery) ||
            template.category.toLowerCase().includes(lowerQuery) ||
            template.description.toLowerCase().includes(lowerQuery));
    })
        .map(basePromptToTemplate);
}
/**
 * Returns a single prompt by ID.
 */
async function getPromptDetail(promptId) {
    const prompts = await loadBasePrompts();
    const bp = prompts.find((p) => p.id === promptId);
    if (!bp)
        return undefined;
    return basePromptToTemplate(bp);
}
/**
 * Personalizes all base prompts for a specific character.
 * Returns an array of PersonalizedPrompt ready for image generation.
 */
async function personalizePromptsForCharacter(dna) {
    const basePrompts = await loadBasePrompts();
    if (basePrompts.length === 0) {
        log.warn({ characterId: dna.id }, 'No base prompts available');
        return [];
    }
    const characterSubject = buildCharacterSubject(dna);
    const cameraSettings = buildCameraFromDNA(dna);
    const results = [];
    for (const bp of basePrompts) {
        try {
            const flatPrompt = flattenPromptJson(bp.json, characterSubject, cameraSettings);
            const aspectRatio = extractAspectRatio(bp.json);
            const tags = extractTags(bp.json, bp.category);
            results.push({
                id: `${dna.id}-${bp.id}`,
                category: bp.category,
                originalId: bp.id,
                flatPrompt,
                aspectRatio,
                tags,
            });
        }
        catch (err) {
            log.warn({ promptId: bp.id, error: err.message }, 'Failed to personalize prompt');
        }
    }
    log.info({ characterId: dna.id, count: results.length }, 'Prompts personalized for character');
    return results;
}
/**
 * Gets a random personalized prompt for a character, optionally filtered by tags.
 */
async function getRandomPrompt(dna, filterTags) {
    const prompts = await personalizePromptsForCharacter(dna);
    if (prompts.length === 0)
        return null;
    let filtered = prompts;
    if (filterTags && filterTags.length > 0) {
        filtered = prompts.filter((p) => filterTags.some((tag) => p.tags.includes(tag.toLowerCase())));
        if (filtered.length === 0)
            filtered = prompts; // fallback to all
    }
    const idx = Math.floor(Math.random() * filtered.length);
    return filtered[idx];
}
/**
 * Gets a context-aware prompt based on conversation context.
 * Analyzes recent messages to pick a relevant category.
 */
async function getContextAwarePrompt(dna, recentUserMessages) {
    const contextText = recentUserMessages.join(' ').toLowerCase();
    // Map conversation keywords to prompt categories/tags
    const contextMap = [
        [['sahil', 'deniz', 'plaj', 'beach', 'y\u00FCzme', 'bikini', 'mayo'], ['beach', 'outdoor', 'bikini', 'pool']],
        [['gece', 'parti', 'club', 'disko', 'e\u011Flence', 'bar'], ['nightlife-party', 'night', 'club']],
        [['spor', 'gym', 'fitness', 'egzersiz', 'ko\u015Fu'], ['gym', 'sporty']],
        [['ev', 'yatak', 'oda', 'rahat', 'pijama'], ['bedroom', 'indoor', 'casual']],
        [['araba', 'car', 's\u00FCr\u00FC\u015F', 'yolculuk'], ['car']],
        [['tekne', 'yat', 'boat', 'yacht', 'deniz'], ['boat', 'yacht']],
        [['selfie', 'ayna', 'mirror', 'foto'], ['selfie', 'mirror']],
        [['elbise', 'dress', 'moda', 'fashion', '\u015F\u0131k'], ['dress', 'fashion-editorial']],
        [['otel', 'hotel', 'tatil', 'seyahat'], ['hotel', 'outdoor-adventure']],
        [['ya\u011Fmur', 'rain', 'kar', 'snow'], ['rain', 'snow']],
        [['kafe', 'cafe', 'kahve', 'coffee'], ['cafe', 'restaurant']],
        [['balkon', 'teras', 'rooftop', 'manzara'], ['balcony', 'rooftop']],
    ];
    const matchedTags = [];
    for (const [keywords, tags] of contextMap) {
        if (keywords.some((kw) => contextText.includes(kw))) {
            matchedTags.push(...tags);
        }
    }
    return getRandomPrompt(dna, matchedTags.length > 0 ? matchedTags : undefined);
}
/**
 * Gets prompt library stats.
 */
async function getLibraryStats() {
    const prompts = await loadBasePrompts();
    const categories = {};
    for (const p of prompts) {
        categories[p.category] = (categories[p.category] ?? 0) + 1;
    }
    return { totalPrompts: prompts.length, categories };
}
/**
 * Personalizes a single template by ID for a specific character.
 * Returns the personalized prompt string, or undefined if template not found.
 */
async function personalizeTemplate(templateId, dna) {
    const prompts = await loadBasePrompts();
    const bp = prompts.find((p) => p.id === templateId);
    if (!bp)
        return undefined;
    const characterSubject = buildCharacterSubject(dna);
    const cameraSettings = buildCameraFromDNA(dna);
    const flatPrompt = flattenPromptJson(bp.json, characterSubject, cameraSettings);
    return { personalizedPrompt: flatPrompt };
}
//# sourceMappingURL=index.js.map