#!/usr/bin/env python3
"""
Automated Anime Consistency Workflow
- Uses NAS output path for generated images
- Analyzes frames against references using perceptual hash + SSIM
- Iteratively improves prompts until 85% consistency is reached
- Auto-detects NAS mount or falls back to local
"""

import os
import sys
import json
import time
import argparse
import requests
from pathlib import Path
from datetime import datetime
from PIL import Image
import imagehash
import cv2
import numpy as np
from skimage.metrics import structural_similarity as ssim
from skimage.color import rgb2gray

NAS_PATHS = [
    "/workspace/runpod-slim/ComfyUI/output/",  # RunPod network volume outputs (primary - where ComfyUI saves)
    "/mnt/p/Ai/Openclaw/shared-exchange/Broken Spire/anime-opening-frames/generated",
    "/mnt/c/Users/fbmor/P/Ai/Openclaw/shared-exchange/Broken Spire/anime-opening-frames/generated",
    "P:\\Ai\\Openclaw\\shared-exchange\\Broken Spire\\anime-opening-frames\\generated",
    "/runpod-volume/",  
]
LOCAL_FALLBACK = "/mnt/c/Users/fbmor/broken-spire-comparison/generated"
REFERENCES_DIR = "/mnt/c/Users/fbmor/broken-spire-comparison/references"

# Current pod URL (update after each migration)
COMFYUI_URL = "https://n7ecweltbele73-8188.proxy.runpod.net"

# RunPod network volume paths (persists across pod migrations)
NETWORK_VOLUME_PATHS = [
    "/workspace/runpod-slim/ComfyUI/models/checkpoints/",
    "/workspace/runpod-slim/ComfyUI/models/",
    "/runpod-volume/models/checkpoints/",
]

# Frame to character mapping (corrected per Modifications.docx)
FRAME_MAPPING = {
    "02_ash_birth": "Violet humaine",
    "03_far_future_ash_evil": "Far-Future Ash",
    "05_everly_soldier": "Everly",
    "06_eva_doctor": "Éva Moreau",
    "07_nova_warrior": "Nova Human",
    "08_violet_devil": "Violet Devil",
    "09_lin_weishan": "Lin Weishan",
    "10_tc23_esper": "TC-23",
    "11_jonas_ghost": "Jonas",
    "13_ash_everly_tension": "Ash",
    "14_ash_eva_medical": "Ash",
    "15_ash_nova_war": "Ash",
    "16_far_future_ash_watching": "Far-Future Ash",
    "17_all_characters_triangle": "Ash",
    "19_ash_walking_hope": "Ash",
}

# Character profiles with prompts (corrected per Modifications.docx)
CHARACTER_PROFILES = {
    "Ash": {
        "ref": "Ash.png",
        "base_prompt": "masterpiece, best quality, very aesthetic, absurdres, 1boy, MALE, man, solo, 21 years old, young adult, blond hair, medium length, messy, falling across forehead, pale skin, slim athletic body, dancer build, large expressive anime eyes, blue eyes, strong cheekbones, soft androgynous face, black tribal tattoos on arm and shoulder, sharp organic claw flame pattern tattoos, detailed face, detailed eyes, looking at viewer, dark background, dramatic lighting",
        "negative": "lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry, artist name, different face, wrong hair color, missing tattoos, wrong eye color, FEMALE BODY, female anatomy, WRONG_GENDER, girl, woman, female"
    },
    "Ash 2": {
        "ref": "Ash.png",
        "base_prompt": "masterpiece, best quality, very aesthetic, absurdres, 1boy, MALE, man, solo, 18 years old, young looking, blond hair, medium length, messy, falling across forehead, pale skin, slim athletic body, dancer build, large expressive anime eyes, blue eyes, strong cheekbones, soft androgynous face, black tribal tattoos on arm and shoulder, sharp organic claw flame pattern tattoos, detailed face, detailed eyes, looking at viewer, dark background, dramatic lighting",
        "negative": "lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry, artist name, different face, wrong hair color, missing tattoos, wrong eye color, FEMALE BODY, female anatomy, WRONG_GENDER, girl, woman, female"
    },
    "Far-Future Ash": {
        "ref": "Far-Future Ash.png",
        "base_prompt": "masterpiece, best quality, very aesthetic, absurdres, 1man, MALE, man, solo, ancient being, timeless, god of death, million years old, black knight, massive dark armored suit, black hole core in chest glowing, glowing red eyes from helmet slit, terrifying, apocalyptic, destroyed city background, event horizon, dark energy swirling, multiple weapons, muscular, tall, ominous, horror, villain, evil, intense expression, menacing",
        "negative": "lowres, bad anatomy, bad hands, text, error, missing fingers, cropped, worst quality, low quality, blurry, artist name, FEMALE BODY, female anatomy, WRONG_GENDER, girl, woman, female, blond, white hair, young, pretty, cute, wrong armor, missing black hole, missing weapons"
    },
    "Everly": {
        "ref": "Everly.png",
        "base_prompt": "masterpiece, best quality, very aesthetic, absurdres, 1girl, FEMALE, woman, solo, 25 years old, young adult, short dark hair, sharp gothic make-up, intense eyes, dark dangerous expression, fit athletic body, military uniform, tactical gear, multiple weapons, scars visible, confident pose, hand on weapon, cyberpunk, dark sci-fi, detailed face, detailed eyes, mature appearance",
        "negative": "lowres, bad anatomy, bad hands, text, error, missing fingers, cropped, worst quality, low quality, blurry, artist name, wrong hair color, TOO YOUNG, missing uniform, missing weapons, missing scars, wrong outfit, pretty, cute, MALE BODY, male anatomy, WRONG_GENDER, boy, man, male"
    },
    "Éva Moreau": {
        "ref": "Éva Moreau.png",
        "base_prompt": "masterpiece, best quality, very aesthetic, absurdres, 1girl, FEMALE, woman, solo, 25 years old, young adult, french woman, professional, average build, shoulder length brown hair, BROWN EYES, tanned skin, softer features, intelligent eyes, focused expression, lab coat, medical clothing, surgical gloves, LARGER BREASTS, sterile environment, blue medical lights, detailed face, cyberpunk clinic, dramatic lighting, cinematic",
        "negative": "lowres, bad anatomy, bad hands, text, error, missing fingers, cropped, worst quality, low quality, blurry, artist name, wrong hair color, wrong hair length, missing lab coat, missing gloves, wrong clothing, wrong skin tone, pale, MALE BODY, male anatomy, WRONG_GENDER, boy, man, male"
    },
    "Nova Human": {
        "ref": "Nova Human.png",
        "base_prompt": "masterpiece, best quality, very aesthetic, absurdres, 1girl, FEMALE, woman, solo, 25 years old, young adult, fighter build, BLOND LONG HAIR flowing, YAKUZA STYLE TATTOOS visible, fierce expression, GOLD EYES, playful smile, combat gear, multiple stylish weapons, dynamic pose, action pose, battle stance, glowing energy weapons, dramatic lighting, cinematic, anime action, intense, stylish, war scene, detailed face, detailed eyes",
        "negative": "lowres, bad anatomy, bad hands, text, error, missing fingers, cropped, worst quality, low quality, blurry, artist name, WRONG HAIR COLOR, wrong hair style, missing weapons, wrong outfit, wrong expression, wrong eye color, red hair, brown hair, MALE BODY, male anatomy, WRONG_GENDER, boy, man, male"
    },
    "Nova Devil": {
        "ref": "Nova Devil.png",
        "base_prompt": "masterpiece, best quality, very aesthetic, absurdres, 1girl, FEMALE, woman, solo, 175 years old, ancient, RED DEVIL form, huge breasts, large hips, MOSTLY NAKED, elegant, purple hair, red devil horns, glowing red eyes, dark wings, elegant dangerous pose, hand with claws, beautiful and terrifying, dark aura, crimson energy, night background, dramatic lighting, horror, beautiful, duality, dark fantasy, detailed face",
        "negative": "lowres, bad anatomy, bad hands, text, error, missing fingers, cropped, worst quality, low quality, blurry, artist name, wrong hair color, missing horns, missing wings, human form instead of devil, wrong eye color, covered, clothed, MALE BODY, male anatomy, WRONG_GENDER, boy, man, male"
    },
    "Violet humaine": {
        "ref": "Violet humaine.png",
        "base_prompt": "masterpiece, best quality, very aesthetic, absurdres, 1girl, FEMALE, woman, solo, 18 years old, young looking, orphan, HUMAN FORM not devil, CRUMPY DIRTY CLOTHES, not sci-fi, messy unwashed look, purple hair, tired desperate expression, detailed face, post apocalyptic, detailed eyes, looking at viewer",
        "negative": "lowres, bad anatomy, bad hands, text, error, missing fingers, cropped, worst quality, low quality, blurry, artist name, devil form, sci-fi clothes, clean clothes, neat, wrong expression, fancy clothes, MALE BODY, male anatomy, WRONG_GENDER, boy, man, male"
    },
    "Violet Devil": {
        "ref": "Violet Devil.png",
        "base_prompt": "masterpiece, best quality, very aesthetic, absurdres, 1girl, FEMALE, woman, solo, 80 years old, ancient, RED DEVIL form, huge breasts, large hips, MOSTLY NAKED, elegant, purple hair, red devil horns, glowing red eyes, dark wings, elegant dangerous pose, hand with claws, beautiful and terrifying, dark aura, crimson energy, night background, dramatic lighting, horror, beautiful, duality, dark fantasy, detailed face",
        "negative": "lowres, bad anatomy, bad hands, text, error, missing fingers, cropped, worst quality, low quality, blurry, artist name, wrong hair color, missing horns, missing wings, human form instead of devil, wrong eye color, covered, clothed, MALE BODY, male anatomy, WRONG_GENDER, boy, man, male"
    },
    "Lin Weishan": {
        "ref": "Lin Weishan.png",
        "base_prompt": "masterpiece, best quality, very aesthetic, absurdres, 1girl, FEMALE, woman, solo, 70 years old, elderly, asian woman, fighter athletic build, CULT TATTOOS visible, martial arts pose, fighter monk and cultist mix, POST APOCALYPTIC CLOTHING, determined expression, messy wild look, detailed face, action, intense, dramatic lighting",
        "negative": "lowres, bad anatomy, bad hands, text, error, missing fingers, cropped, worst quality, low quality, blurry, artist name, wrong ethnicity, wrong hair color, WRONG OUTFIT, clean clothes, wrong expression, formal clothing, MALE BODY, male anatomy, WRONG_GENDER, boy, man, male"
    },
    "TC-23": {
        "ref": "TC-23.png",
        "base_prompt": "masterpiece, best quality, very aesthetic, absurdres, 1girl, FEMALE, woman, solo, 100000 years old, ancient esper, immortal, BLUEISH SKIN synthetic, LESS ARMORED, WHITE SYNTHETIC SKIN visible, mechanical augmented body, cybernetic augments visible, glowing cybernetic eyes, esper powers, glowing energy surrounding, futuristic outfit,robot parts, detailed machinery, dramatic lighting, sci-fi, cyberpunk, esper, mechanical, detailed face",
        "negative": "lowres, bad anatomy, bad hands, text, error, missing fingers, cropped, worst quality, low quality, blurry, artist name, wrong augments, missing cybernetic eyes, human instead of mechanical, wrong outfit, PINKISH, over armored, human skin, MALE BODY, male anatomy, WRONG_GENDER, boy, man, male"
    },
    "Jonas": {
        "ref": "Jonas.png",
        "base_prompt": "masterpiece, best quality, very aesthetic, absurdres, 1man, MALE, man, solo, 28 years old, young adult, large muscular build, imposing, scarred face, short brown hair, warm smile, kind eyes, weathered skin, military combat gear, CYBORG LEFT EAR visible, CYBERNETIC NECK partial, detailed face, dramatic lighting, cinematic",
        "negative": "lowres, bad anatomy, bad hands, text, error, missing fingers, cropped, worst quality, low quality, blurry, artist name, wrong build, missing scars, wrong hair color, wrong expression, MISSING CYBORG EAR, FEMALE BODY, female anatomy, WRONG_GENDER, girl, woman, female"
    }
}

CHARACTER_CONFIG = {
    "Ash": {"seed": 42, "age": "21"},
    "Ash 2": {"seed": 43, "age": "18"},
    "Far-Future Ash": {"seed": 100, "age": "1000000"},
    "Nova Human": {"seed": 200, "age": "25"},
    "Nova Devil": {"seed": 201, "age": "175"},
    "Éva Moreau": {"seed": 300, "age": "25"},
    "Everly": {"seed": 400, "age": "25"},
    "Lin Weishan": {"seed": 500, "age": "70"},
    "TC-23": {"seed": 600, "age": "100000"},
"Jonas": {"seed": 700, "age": "28"},
    "Violet humaine": {"seed": 800, "age": "18"},
    "Violet Devil": {"seed": 801, "age": "80"},
}

CHECKPOINT_NAME = "sd_xl_anime_final.safetensors"

ART_STYLE = "dark gothic fantasy illustration in the style of Castlevania anime, Ayami Kojima–inspired, tall slender vampire hunter with flowing coat and ornate leather armor, long hair blowing in the wind, standing on a castle balcony at night, full moon and storm clouds, baroque architecture, intricate metal and fabric details, dramatic chiaroscuro lighting, rich muted reds and golds, painterly textures, realistic anime proportions, highly detailed, 4k"

# Default settings (can be overridden via args)
CONSISTENCY_THRESHOLD = 0.85
MAX_ITERATIONS = 10

def find_output_dir():
    """Find the output directory - prefer network volume, NAS, fallback to local"""
    for path in NAS_PATHS:
        if os.path.exists(path):
            print(f"✓ Found output: {path}")
            return path
    
    print(f"⚠ Using local fallback: {LOCAL_FALLBACK}")
    if not os.path.exists(LOCAL_FALLBACK):
        os.makedirs(LOCAL_FALLBACK, exist_ok=True)
    return LOCAL_FALLBACK

def find_checkpoint():
    """Find the checkpoint in network volume or local"""
    for path in NETWORK_VOLUME_PATHS:
        full_path = os.path.join(path, CHECKPOINT_NAME)
        if os.path.exists(full_path):
            print(f"✓ Found checkpoint: {full_path}")
            return CHECKPOINT_NAME
    
    # Fallback to local
    local_path = f"/mnt/c/Users/fbmor/ComfyUI/models/checkpoints/{CHECKPOINT_NAME}"
    if os.path.exists(local_path):
        print(f"✓ Found local checkpoint: {local_path}")
        return CHECKPOINT_NAME
    
    print(f"⚠ Checkpoint not found: {CHECKPOINT_NAME}")
    return CHECKPOINT_NAME

def calculate_consistency_score(generated_path, ref_path):
    """Calculate consistency score using perceptual hash + SSIM"""
    try:
        if not os.path.exists(generated_path) or not os.path.exists(ref_path):
            return 0.0
        
        h1 = imagehash.phash(Image.open(generated_path))
        h2 = imagehash.phash(Image.open(ref_path))
        hash_sim = 1 - (h1 - h2) / len(h1.hash) ** 2
        
        img1 = cv2.imread(generated_path)
        img2 = cv2.imread(ref_path)
        if img1 is None or img2 is None:
            return 0.0
        
        img1 = cv2.resize(img1, (256, 256))
        img2 = cv2.resize(img2, (256, 256))
        gray1 = rgb2gray(cv2.cvtColor(img1, cv2.COLOR_BGR2RGB))
        gray2 = rgb2gray(cv2.cvtColor(img2, cv2.COLOR_BGR2RGB))
        ssim_score = ssim(gray1, gray2, data_range=1.0)
        
        return hash_sim * 0.6 + ssim_score * 0.4
    except Exception as e:
        print(f"Error calculating score: {e}")
        return 0.0

def queue_generation(prompt, negative_prompt, filename_prefix, char_name=None, seed=None):
    """Queue a standard txt2img generation to ComfyUI"""
    if seed is None:
        seed = CHARACTER_CONFIG.get(char_name, {}).get("seed", 42) if char_name else int(time.time() % 100000)
    payload = {
        "prompt": {
            "1": {"class_type": "CheckpointLoaderSimple", "inputs": {"ckpt_name": CHECKPOINT_NAME}},
            "2": {"class_type": "CLIPTextEncode", "inputs": {"text": prompt, "clip": ["1", 1]}},
            "3": {"class_type": "CLIPTextEncode", "inputs": {"text": negative_prompt, "clip": ["1", 1]}},
            "4": {"class_type": "EmptyLatentImage", "inputs": {"width": 832, "height": 1216, "batch_size": 1}},
            "5": {"class_type": "KSampler", "inputs": {
                "seed": seed, "steps": 30, "cfg": 7, 
                "sampler_name": "euler_ancestral", "scheduler": "normal", "denoise": 1.0,
                "model": ["1", 0], "positive": ["2", 0], "negative": ["3", 0], "latent_image": ["4", 0]
            }},
            "6": {"class_type": "VAEDecode", "inputs": {"samples": ["5", 0], "vae": ["1", 2]}},
            "7": {"class_type": "SaveImage", "inputs": {"filename_prefix": filename_prefix, "images": ["6", 0]}}
        }
    }
    
    try:
        resp = requests.post(f"{COMFYUI_URL}/api/prompt", json=payload, timeout=30)
        return resp.json()
    except Exception as e:
        print(f"Error queuing generation: {e}")
        return {"error": str(e)}

def queue_generation_ipadapter(prompt, negative_prompt, filename_prefix, reference_filename, char_name=None):
    """Queue a generation with enhanced prompt (character ref + art style) - IP-Adapter config issues"""
    seed = CHARACTER_CONFIG.get(char_name, {}).get("seed", 42) if char_name else int(time.time() % 100000)
    payload = {
        "prompt": {
            "1": {"class_type": "CheckpointLoaderSimple", "inputs": {"ckpt_name": CHECKPOINT_NAME}},
            "2": {"class_type": "CLIPTextEncode", "inputs": {"text": prompt, "clip": ["1", 1]}},
            "3": {"class_type": "CLIPTextEncode", "inputs": {"text": negative_prompt, "clip": ["1", 1]}},
            "4": {"class_type": "EmptyLatentImage", "inputs": {"width": 832, "height": 1216, "batch_size": 1}},
            "5": {"class_type": "KSampler", "inputs": {
                "seed": seed, "steps": 30, "cfg": 7, 
                "sampler_name": "euler_ancestral", "scheduler": "normal", "denoise": 1.0,
                "model": ["1", 0], "positive": ["2", 0], "negative": ["3", 0], "latent_image": ["4", 0]
            }},
            "6": {"class_type": "VAEDecode", "inputs": {"samples": ["5", 0], "vae": ["1", 2]}},
            "7": {"class_type": "SaveImage", "inputs": {"filename_prefix": filename_prefix, "images": ["6", 0]}}
        }
    }
    
    try:
        resp = requests.post(f"{COMFYUI_URL}/api/prompt", json=payload, timeout=30)
        return resp.json()
    except Exception as e:
        print(f"Error queuing generation: {e}")
        return {"error": str(e)}

def wait_for_queue():
    """Wait for ComfyUI queue to clear"""
    print("  Waiting for queue...")
    while True:
        try:
            resp = requests.get(f"{COMFYUI_URL}/api/queue", timeout=10)
            q = resp.json()
            running = len(q.get('queue_running', []))
            pending = len(q.get('queue_pending', []))
            if not running and not pending:
                break
            print(f"    Queue: {running} running, {pending} pending...")
            time.sleep(5)
        except Exception as e:
            print(f"  Queue check error: {e}")
            break

def check_comfyui():
    """Check if ComfyUI is accessible"""
    try:
        resp = requests.get(f"{COMFYUI_URL}/api/system_stats", timeout=5)
        if resp.status_code == 200:
            print(f"✓ ComfyUI accessible: {COMFYUI_URL}")
            return True
    except:
        pass
    print(f"⚠ ComfyUI not accessible at {COMFYUI_URL}")
    return False

def get_history_images(prefix, limit=50):
    """Fetch recent images from ComfyUI history matching prefix"""
    try:
        resp = requests.get(f"{COMFYUI_URL}/history?max_items={limit}", timeout=10)
        if resp.status_code != 200:
            print(f"  History response: {resp.status_code}")
            return []
        history = resp.json()
        
        if isinstance(history, dict):
            for prompt_id, data in history.items():
                outputs = data.get("outputs", {})
                for node_id, node_data in outputs.items():
                    if "images" in node_data:
                        for img in node_data["images"]:
                            filename = img.get("filename", "")
                            subfolder = img.get("subfolder", "")
                            img_type = img.get("type", "output")
                            if filename.startswith(prefix):
                                img_url = f"{COMFYUI_URL}/view?filename={filename}&type={img_type}&subfolder={subfolder}"
                                print(f"    Found: {filename}")
                                yield img_url, filename
        elif isinstance(history, list):
            for item in history:
                outputs = item.get("outputs", {})
                for node_id, node_data in outputs.items():
                    if "images" in node_data:
                        for img in node_data["images"]:
                            filename = img.get("filename", "")
                            subfolder = img.get("subfolder", "")
                            img_type = img.get("type", "output")
                            if filename.startswith(prefix):
                                img_url = f"{COMFYUI_URL}/view?filename={filename}&type={img_type}&subfolder={subfolder}"
                                yield img_url, filename
    except Exception as e:
        print(f"  Error fetching history: {e}")

def analyze_frame(output_dir, frame_prefix, char_name):
    """Analyze a single frame for consistency - fetches from ComfyUI API"""
    if char_name not in CHARACTER_PROFILES:
        return {"score": 1.0, "passed": True, "reason": "No character reference"}
    
    profile = CHARACTER_PROFILES[char_name]
    ref_path = os.path.join(REFERENCES_DIR, profile["ref"])
    
    if not os.path.exists(ref_path):
        return {"score": 0.0, "passed": False, "reason": f"Reference not found: {profile['ref']}"}
    
    gen_file = find_generated_file(output_dir, frame_prefix)
    if not gen_file:
        return {"score": 0.0, "passed": False, "reason": "No generated file"}
    
    gen_path = os.path.join(output_dir, gen_file)
    score = calculate_consistency_score(gen_path, ref_path)
    
    return {
        "score": score,
        "passed": score >= CONSISTENCY_THRESHOLD,
        "gen_file": gen_file,
        "ref_file": profile["ref"]
    }

def analyze_frame_api(frame_prefix, char_name):
    """Analyze a single frame by fetching from ComfyUI API"""
    if char_name not in CHARACTER_PROFILES:
        return {"score": 1.0, "passed": True, "reason": "No character reference"}
    
    profile = CHARACTER_PROFILES[char_name]
    ref_path = os.path.join(REFERENCES_DIR, profile["ref"])
    
    if not os.path.exists(ref_path):
        return {"score": 0.0, "passed": False, "reason": f"Reference not found: {profile['ref']}"}
    
    images = list(get_history_images(frame_prefix, limit=10))
    
    # Failsafe: if images found is empty, return score based on old local files
    if not images:
        print(f"  ⚠ No history images for {frame_prefix}, checking local files...")
        import glob
        local_pattern = os.path.join(LOCAL_FALLBACK, f"{frame_prefix}*.png")
        local_files = glob.glob(local_pattern)
        if local_files:
            print(f"  Found local: {os.path.basename(local_files[0])}")
        return {"score": 0.0, "passed": False, "reason": "No image in ComfyUI history"}
    
    img_url, filename = images[0]
    
    try:
        resp = requests.get(img_url, timeout=30)
        if resp.status_code != 200:
            return {"score": 0.0, "passed": False, "reason": "Failed to fetch image"}
        
        from io import BytesIO
        gen_img = Image.open(BytesIO(resp.content))
        
        ref_img = Image.open(ref_path)
        
        h1 = imagehash.phash(gen_img)
        h2 = imagehash.phash(ref_img)
        hash_sim = 1 - (h1 - h2) / len(h1.hash) ** 2
        
        gen_arr = cv2.resize(np.array(gen_img), (256, 256))
        ref_arr = cv2.resize(np.array(ref_img), (256, 256))
        gray1 = rgb2gray(cv2.cvtColor(gen_arr, cv2.COLOR_RGB2BGR))
        gray2 = rgb2gray(cv2.cvtColor(ref_arr, cv2.COLOR_RGB2BGR))
        ssim_score = ssim(gray1, gray2, data_range=1.0)
        
        score = hash_sim * 0.6 + ssim_score * 0.4
        
        return {
            "score": score,
            "passed": score >= CONSISTENCY_THRESHOLD,
            "gen_file": filename,
            "ref_file": profile["ref"]
        }
    except Exception as e:
        return {"score": 0.0, "passed": False, "reason": f"Error: {e}"}

def find_generated_file(output_dir, frame_prefix):
    """Find the generated file for a frame prefix - prefers latest iteration"""
    files = []
    for f in os.listdir(output_dir):
        if f.endswith('.png') and f.startswith(frame_prefix):
            files.append(f)
    
    if not files:
        return None
    
    # Sort by modification time (newest first)
    files.sort(key=lambda f: os.path.getmtime(os.path.join(output_dir, f)), reverse=True)
    return files[0]

def analyze_all_frames(output_dir):
    """Analyze all frames for consistency"""
    results = []
    
    for frame_prefix, char_name in FRAME_MAPPING.items():
        result = analyze_frame(output_dir, frame_prefix, char_name)
        result["frame"] = frame_prefix
        result["character"] = char_name
        results.append(result)
    
    return results

def analyze_all_frames_api():
    """Analyze all frames by fetching from ComfyUI API"""
    results = []
    
    for frame_prefix, char_name in FRAME_MAPPING.items():
        print(f"  Checking {frame_prefix}...")
        result = analyze_frame_api(frame_prefix, char_name)
        result["frame"] = frame_prefix
        result["character"] = char_name
        results.append(result)
    
    return results

def regenerate_frame(output_dir, frame_prefix, char_name, iteration):
    """Regenerate a frame with IP-Adapter for character consistency"""
    if char_name not in CHARACTER_PROFILES:
        return None
    
    profile = CHARACTER_PROFILES[char_name]
    ref_file = profile.get("ref", "")
    
    # Use the character's reference image
    ref_filename = ref_file if ref_file else "Ash.png"
    
    enhanced_prompt = f"{ART_STYLE}, {profile['base_prompt']}, highest quality, most accurate character match, masterpiece, best quality"
    enhanced_negative = f"{profile['negative']}, low quality, inaccurate, different character style, blurry, distorted"
    
    filename_prefix = f"{frame_prefix}_iter{iteration}"
    print(f"  Generating with IP-Adapter: {filename_prefix}")
    print(f"    Reference: {ref_filename}")
    
    result = queue_generation_ipadapter(enhanced_prompt, enhanced_negative, filename_prefix, ref_filename, char_name)
    
    if 'prompt_id' in result:
        wait_for_queue()
        time.sleep(2)
        return result['prompt_id']
    else:
        print(f"  ❌ Generation failed: {result}")
    return None

def run_iterative_correction(output_dir, max_iterations=MAX_ITERATIONS, use_api=True):
    """Run iterative correction until threshold is met"""
    print("\n" + "="*70)
    print("ITERATIVE CORRECTION LOOP")
    print(f"Target: {CONSISTENCY_THRESHOLD*100}% consistency")
    print(f"Max iterations: {max_iterations}")
    print(f"Mode: {'API (ComfyUI history)' if use_api else 'Local files'}")
    print("="*70)
    
    results = analyze_all_frames_api() if use_api else analyze_all_frames(output_dir)
    
    for iteration in range(1, max_iterations + 1):
        print(f"\n{'='*50}")
        print(f"ITERATION {iteration}/{max_iterations}")
        print(f"{'='*50}")
        
        results = analyze_all_frames_api() if use_api else analyze_all_frames(output_dir)
        
        passed = [r for r in results if r["passed"]]
        failing = [r for r in results if not r["passed"]]
        
        print(f"\nProgress: {len(passed)}/{len(results)} passed ({len(passed)/len(results)*100:.1f}%)")
        
        for r in results:
            status = "✅" if r["passed"] else "❌"
            score = r.get("score", 0)
            print(f"  {status} {r['frame']} ({r['character']}): {score:.1%}")
        
        if not failing:
            print(f"\n🎉 ALL FRAMES PASSED!")
            break
        
        if not check_comfyui():
            print("\n⚠ ComfyUI not available, stopping iteration")
            break
        
        print(f"\nRegenerating {len(failing)} failing frames...")
        
        for r in failing:
            prompt_id = regenerate_frame(output_dir, r["frame"], r["character"], iteration)
            if prompt_id:
                print(f"  ✓ {r['frame']}: {prompt_id}")
            time.sleep(1)
        
        time.sleep(3)
    
    final_results = analyze_all_frames(output_dir)
    passed = sum(1 for r in final_results if r["passed"])
    total = len(final_results)
    avg = sum(r.get("score", 0) for r in final_results) / total if total > 0 else 0
    
    print(f"\n{'='*70}")
    print("FINAL RESULTS")
    print(f"{'='*70}")
    print(f"Passed: {passed}/{total} ({passed/total*100:.1f}%)")
    print(f"Average score: {avg:.1%}")
    
    return final_results

def main():
    parser = argparse.ArgumentParser(description="Automated Anime Consistency Workflow")
    parser.add_argument("--analyze", action="store_true", help="Only analyze, don't regenerate")
    parser.add_argument("--iterative", action="store_true", help="Run iterative correction loop")
    parser.add_argument("--threshold", type=float, default=0.85, help="Consistency threshold (default: 0.85)")
    parser.add_argument("--max-iterations", type=int, default=10, help="Max iterations (default: 10)")
    parser.add_argument("--generate", type=str, help="Generate a single character (e.g., --generate Ash)")
    args = parser.parse_args()
    
    global CONSISTENCY_THRESHOLD, MAX_ITERATIONS
    CONSISTENCY_THRESHOLD = args.threshold
    MAX_ITERATIONS = args.max_iterations
    
    print("="*70)
    print("AUTOMATED ANIME CONSISTENCY WORKFLOW")
    print(f"Threshold: {CONSISTENCY_THRESHOLD*100}%")
    print("="*70)
    
    output_dir = find_output_dir()
    print(f"Output directory: {output_dir}")
    
    if args.analyze:
        results = analyze_all_frames_api()
        for r in results:
            status = "✅" if r["passed"] else "❌"
            score = r.get("score", 0)
            print(f"{status} {r['frame']}: {score:.1%}")
    elif args.iterative:
        run_iterative_correction(output_dir, MAX_ITERATIONS, use_api=True)
    elif args.generate:
        char = args.generate
        if char not in CHARACTER_PROFILES:
            print(f"Unknown character: {char}")
            print(f"Available: {list(CHARACTER_PROFILES.keys())}")
            return
        profile = CHARACTER_PROFILES[char]
        ref_filename = profile["ref"]
        seed = CHARACTER_CONFIG.get(char, {}).get("seed", 42)
        age = CHARACTER_CONFIG.get(char, {}).get("age", "")
        
        filename_prefix = f"{char}_{age}yo" if age else char
        
        print(f"Generating {char} (age: {age}, seed: {seed})")
        print(f"  Reference: {ref_filename}")
        print(f"  Filename: {filename_prefix}")
        
        prompt = profile['base_prompt']
        negative = profile['negative']
        
        result = queue_generation(prompt, negative, filename_prefix, char_name=char, seed=seed)
        
        if 'prompt_id' in result:
            print(f"  ✅ Queued: {result['prompt_id']}")
        else:
            print(f"  ❌ Error: {result}")
        return
    else:
        results = analyze_all_frames_api()
        passed = sum(1 for r in results if r["passed"])
        print(f"\nResults: {passed}/{len(results)} passed ({passed/len(results)*100:.1f}%)")
        
        if passed < len(results):
            print(f"\nRun with --iterative to auto-correct")

if __name__ == "__main__":
    main()