#!/usr/bin/env python3
"""Nano Banana Image Generator — Bundle-Skill.

Google Gemini Image Generation API Wrapper.
Standard-Modell: gemini-3.1-flash-image-preview (Nano Banana 2)

Modell-Übersicht:
  gemini-3.1-flash-image-preview  → Nano Banana 2  (Default, Streaming)
  gemini-2.5-flash-image          → Nano Banana 1
  gemini-3-pro-image-preview      → Nano Banana Pro (4K, 14 Referenzbilder)

Verwendung: python generate_image.py --help

Output-Schema (Pflicht-Schlüssel der Wrapper-Funktion):
  {
      "path": Path,                  # Absoluter Pfad zur erzeugten Bild-Datei
      "format": str,                 # "png" | "webp" | "jpeg"
      "size": tuple[int, int],       # (Breite, Höhe) in Pixeln
      "prompt_hash": str,            # SHA-256 hex des Prompts
  }
"""

from __future__ import annotations

import argparse
import base64
import hashlib
import io
import mimetypes
import os
import re as _re
import sys
from pathlib import Path


# Windows-Encoding-Fix: UTF-8 erzwingen für Emoji-Ausgabe
if sys.stdout and hasattr(sys.stdout, "buffer") and sys.stdout.encoding and sys.stdout.encoding.lower() not in ("utf-8", "utf8"):
    sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
if sys.stderr and hasattr(sys.stderr, "buffer") and sys.stderr.encoding and sys.stderr.encoding.lower() not in ("utf-8", "utf8"):
    sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace")


# Modell-Mapping
MODEL_IDS = {
    "nano-banana-2":                  "gemini-3.1-flash-image-preview",
    "nano-banana-1":                  "gemini-2.5-flash-image",
    "nano-banana":                    "gemini-2.5-flash-image",
    "nano-banana-pro":                "gemini-3-pro-image-preview",
    # Direkte Modell-IDs ebenfalls akzeptieren
    "gemini-3.1-flash-image-preview": "gemini-3.1-flash-image-preview",
    "gemini-2.5-flash-image":         "gemini-2.5-flash-image",
    "gemini-3-pro-image-preview":     "gemini-3-pro-image-preview",
}

DEFAULT_MODEL     = "gemini-3.1-flash-image-preview"   # Nano Banana 2
STREAMING_MODELS  = {"gemini-3.1-flash-image-preview"}
THINKING_MODELS   = {"gemini-3.1-flash-image-preview"}
RESOLUTION_MODELS = {"gemini-3.1-flash-image-preview", "gemini-3-pro-image-preview"}


# API-Key-Auflösung delegiert an scripts/find_api_key.py (seit v0.17.3).
# Dort liegt die Skill-spezifische skill.config.yaml-Auflösung, die wiederum
# _lib.api_keys.get_api_key (zentrale Drei-Stufen-Cascade) konsumiert.
# sys.path-Setup für Bundle-_lib + Scripts-Verzeichnis robust auch unter
# spec_from_file_location-Lade-Pfaden (Cross-Skill-Eval-Mock).
_SCRIPTS_DIR = Path(__file__).resolve().parent
_SKILL_DIR_GI = _SCRIPTS_DIR.parent
_SKILLS_ROOT_GI = _SKILL_DIR_GI.parent
if str(_SKILLS_ROOT_GI) not in sys.path:
    sys.path.insert(0, str(_SKILLS_ROOT_GI))
if str(_SCRIPTS_DIR) not in sys.path:
    sys.path.insert(0, str(_SCRIPTS_DIR))

from find_api_key import _resolve_env_var_name, find_api_key  # noqa: E402, F401


STOP_WORDS = {
    "a", "an", "the", "for", "with", "of", "in", "on", "and", "or", "to",
    "is", "it", "its", "that", "this", "my", "your", "our", "set", "against",
    "called", "create", "design", "generate", "make", "build", "featuring",
    "features", "using", "from", "must", "should", "very", "pure", "no",
    "not", "like", "similar", "between", "has", "have", "been", "being",
    "clean", "sleek", "abstract", "modern", "premium", "minimalist",
    "professional", "high", "end", "ultra", "beautiful", "stunning",
    "over", "into", "about", "through", "under", "above", "below",
    "ein", "eine", "einen", "einer", "eines", "der", "die", "das",
    "und", "oder", "mit", "für", "von", "im", "am", "auf",
    "aus", "bei", "nach", "zum", "zur",
}

# Wörter, die den Bildtyp beschreiben — werden als Präfix verwendet
TYPE_WORDS = {
    "logo", "icon", "banner", "poster", "wallpaper", "mockup", "illustration",
    "photo", "portrait", "landscape", "infographic", "sticker", "badge",
    "header", "thumbnail", "cover", "artwork", "sketch", "diagram",
}


def prompt_to_filename(prompt: str) -> str:
    """Leitet einen kurzen Dateinamen ab: Typ + Hauptobjekt (2-3 Wörter max)."""
    text = prompt.lower()
    quoted = _re.findall(r"['\"]([^'\"]+)['\"]", prompt)
    clean = _re.sub(r"[^a-z0-9äöüß\s\-]", " ", text)
    words = [w for w in clean.split() if w not in STOP_WORDS and len(w) > 1]
    if not words and not quoted:
        return "generated_image"
    type_word = next((w for w in words if w in TYPE_WORDS), None)
    subject_words = [w.capitalize() for w in words if w != type_word and w not in TYPE_WORDS]
    subject = quoted[0] if quoted else ("_".join(subject_words[:2]) if subject_words else None)
    parts = [p for p in [type_word.capitalize() if type_word else None, subject] if p]
    if not parts:
        return "generated_image"
    name = "_".join(parts)
    name = _re.sub(r"[^a-zA-Z0-9äöüÄÖÜß_\-]", "", name)
    return name or "generated_image"


def resolve_model(model_arg: str) -> str:
    return MODEL_IDS.get(model_arg.lower(), model_arg)


# Chroma-Key Transparenz
_CHROMAKEY_PATTERN = _re.compile(
    r"#(?:FF00FF|00FF00|0000FF)|magenta\s+background|green\s+screen|chroma|solid\s+color\s+background",
    _re.IGNORECASE,
)

_COLOR_NAMES = {
    "#FF00FF": "magenta",
    "#00FF00": "green",
    "#0000FF": "blue",
    "#FF0000": "red",
}


def augment_prompt_for_chromakey(prompt: str, key_color_hex: str) -> str:
    """Hängt Chroma-Key-Hintergrund-Anweisung an den Prompt an, falls nicht bereits vorhanden."""
    if _CHROMAKEY_PATTERN.search(prompt):
        return prompt
    color_name = _COLOR_NAMES.get(key_color_hex.upper(), key_color_hex)
    suffix = (
        f" The subject must be placed on a perfectly uniform, solid {color_name} "
        f"({key_color_hex}) background with absolutely no gradients, shadows, patterns, "
        f"or variations. The background must be a single flat color."
    )
    return prompt + suffix


def detect_background_color(image_path: Path, sample_size: int = 20):
    """Erkennt die dominante Hintergrundfarbe aus den 4 Ecken des Bildes."""
    import numpy as np
    from PIL import Image

    img = Image.open(image_path).convert("RGB")
    pixels = np.array(img, dtype=np.float32)
    h, w = pixels.shape[:2]
    s = min(sample_size, h // 4, w // 4)
    if s < 3:
        return None

    corners = [
        pixels[:s, :s],
        pixels[:s, -s:],
        pixels[-s:, :s],
        pixels[-s:, -s:],
    ]
    all_samples = np.concatenate([c.reshape(-1, 3) for c in corners], axis=0)
    median_color = np.median(all_samples, axis=0)

    std_per_channel = np.std(all_samples, axis=0)
    max_std = np.max(std_per_channel)
    if max_std > 40:
        return None

    return tuple(int(round(c)) for c in median_color)


def remove_background_chromakey(
    image_path: Path,
    key_color_hex: str = "#FF00FF",
    tolerance: int = 60,
    auto_detect: bool = True,
):
    """Ersetzt Chroma-Key-Hintergrund durch Transparenz (Alpha-Kanal)."""
    import numpy as np
    from PIL import Image

    img = Image.open(image_path).convert("RGBA")
    pixels = np.array(img, dtype=np.float32)
    h, w = pixels.shape[:2]

    hx = key_color_hex.lstrip("#")
    key = np.array([int(hx[i:i + 2], 16) for i in (0, 2, 4)], dtype=np.float32)

    if auto_detect:
        detected_color = detect_background_color(image_path)
        if detected_color is not None:
            detected_key = np.array(detected_color, dtype=np.float32)
            color_diff = np.sqrt(np.sum((detected_key - key) ** 2))
            if color_diff > tolerance:
                r, g, b = detected_color
                print(f"Modell verwendete RGB({r},{g},{b}) statt {key_color_hex} — Auto-Korrektur aktiv")
                key = detected_key

    distance = np.sqrt(np.sum((pixels[:, :, :3] - key) ** 2, axis=2))
    inner = tolerance * 0.7
    alpha = np.full((h, w), 255.0, dtype=np.float32)
    alpha[distance < inner] = 0.0
    feather = (distance >= inner) & (distance <= tolerance)
    alpha[feather] = ((distance[feather] - inner) / (tolerance - inner)) * 255.0

    if np.any(feather):
        blend = 1.0 - (alpha[feather] / 255.0)
        for c in range(3):
            chan = pixels[:, :, c]
            orig = chan[feather]
            mean_val = orig.mean() if orig.size > 0 else 128.0
            chan[feather] = orig * (1.0 - blend * 0.5) + mean_val * (blend * 0.5)

    pixels[:, :, 3] = alpha

    result = Image.fromarray(pixels.astype(np.uint8), "RGBA")
    output = image_path.with_suffix(".png")
    result.save(output, "PNG")

    if image_path.suffix.lower() != ".png" and image_path.exists():
        image_path.unlink()

    transparent_pixels = int(np.sum(alpha < 128))
    total_pixels = h * w
    transparent_pct = (transparent_pixels / total_pixels) * 100 if total_pixels > 0 else 0.0

    if transparent_pct >= 10:
        print(f"Hintergrund entfernt: {transparent_pct:.1f}% transparent ({output.name})")
    else:
        print(f"Hintergrund-Entfernung unvollständig: nur {transparent_pct:.1f}% transparent ({output.name})")

    return output, transparent_pct


def load_image_bytes(image_path: str):
    path = Path(image_path)
    if not path.exists():
        raise FileNotFoundError(f"Eingabebild nicht gefunden: {image_path}")
    suffix = path.suffix.lower()
    mime_map = {
        ".jpg": "image/jpeg", ".jpeg": "image/jpeg",
        ".png": "image/png",  ".gif": "image/gif", ".webp": "image/webp",
    }
    mime_type = mime_map.get(suffix, "image/jpeg")
    return path.read_bytes(), mime_type


def parse_ref_group(value: str):
    """Parst eine Referenzbild-Gruppe im Format 'Beschreibung::pfad1.jpg,pfad2.jpg'."""
    if "::" not in value:
        raise ValueError(
            f"Referenz-Format ungültig: '{value}'\n"
            "Erwartet: 'Beschreibung::pfad1.jpg,pfad2.jpg'"
        )
    label, paths_str = value.split("::", 1)
    paths = [p.strip() for p in paths_str.split(",") if p.strip()]
    if not paths:
        raise ValueError(f"Keine Bildpfade in Referenz: '{value}'")
    for p in paths:
        if not Path(p).exists():
            raise FileNotFoundError(f"Referenzbild nicht gefunden: {p}")
    return label.strip(), paths


def build_ref_parts(characters=None, objects=None):
    """Baut beschriftete Referenzbild-Parts für die API auf."""
    from google.genai import types

    parts = []
    total_images = 0

    for i, (label, paths) in enumerate(characters or [], 1):
        parts.append(types.Part.from_text(
            text=f"Reference images of Character {i} ({label}):"
        ))
        for img_path in paths:
            img_bytes, mime_type = load_image_bytes(img_path)
            parts.append(types.Part(
                inline_data=types.Blob(mime_type=mime_type, data=img_bytes)
            ))
            total_images += 1
            print(f"  Charakter {i}: {Path(img_path).name} ({len(img_bytes) // 1024}KB)")

    for i, (label, paths) in enumerate(objects or [], 1):
        parts.append(types.Part.from_text(
            text=f"Reference images of {label}:"
        ))
        for img_path in paths:
            img_bytes, mime_type = load_image_bytes(img_path)
            parts.append(types.Part(
                inline_data=types.Blob(mime_type=mime_type, data=img_bytes)
            ))
            total_images += 1
            print(f"  Objekt {i}: {Path(img_path).name} ({len(img_bytes) // 1024}KB)")

    return parts, total_images


def save_image(data: bytes, output_path: Path, mime_type: str = "image/png") -> Path:
    ext = mimetypes.guess_extension(mime_type) or ".png"
    if ext == ".jpe":
        ext = ".jpg"
    out = output_path.with_suffix(ext)
    out.write_bytes(data)
    print(f"Bild gespeichert: {out.resolve()}")
    return out


def _format_from_path(path: Path) -> str:
    """Mappt die Datei-Endung auf einen kanonischen Format-Namen."""
    suffix = path.suffix.lower().lstrip(".")
    if suffix in ("jpg", "jpe", "jpeg"):
        return "jpeg"
    if suffix in ("png", "webp"):
        return suffix
    return suffix or "png"


def _detect_size(path: Path) -> tuple[int, int]:
    """Liest Pixel-Dimensionen aus einer Bild-Datei via PIL.

    Bei nicht-lesbaren Dateien (z.B. Test-Mock-Bytes) wird (0, 0) zurückgegeben.
    """
    try:
        from PIL import Image
    except ImportError:
        return (0, 0)
    try:
        with Image.open(path) as img:
            return (int(img.width), int(img.height))
    except Exception:
        return (0, 0)


def _build_result(path: Path | None, prompt: str) -> dict:
    """Baut das Wrapper-Output-Dict mit den vier Pflicht-Schlüsseln."""
    prompt_hash = hashlib.sha256(prompt.encode("utf-8")).hexdigest()
    if path is None:
        return {
            "path": None,
            "format": "png",
            "size": (0, 0),
            "prompt_hash": prompt_hash,
        }
    resolved = path.resolve()
    return {
        "path": resolved,
        "format": _format_from_path(resolved),
        "size": _detect_size(resolved),
        "prompt_hash": prompt_hash,
    }


def generate_nb2_streaming(
    prompt, client, model, aspect_ratio, resolution,
    output_path, input_image, thinking_level="minimal",
    use_google_search=False,
    characters=None, objects=None,
):
    from google.genai import types

    image_cfg = {"aspect_ratio": aspect_ratio}
    if resolution:
        image_cfg["image_size"] = resolution

    config_kwargs = {
        "thinking_config": types.ThinkingConfig(thinking_level=thinking_level),
        "image_config": types.ImageConfig(**image_cfg),
        "response_modalities": ["IMAGE", "TEXT"],
    }
    if use_google_search:
        config_kwargs["tools"] = [{"google_search": {}}]

    gen_config = types.GenerateContentConfig(**config_kwargs)

    has_refs = (characters and len(characters) > 0) or (objects and len(objects) > 0)
    if has_refs:
        ref_parts, total_ref = build_ref_parts(characters, objects)
        ref_parts.append(types.Part.from_text(text=prompt))
        parts = ref_parts
        print(f"{total_ref} Referenzbilder geladen")
    else:
        parts = [types.Part.from_text(text=prompt)]

    if input_image and not has_refs:
        img_bytes, mime_type = load_image_bytes(input_image)
        parts.append(types.Part(
            inline_data=types.Blob(mime_type=mime_type, data=img_bytes)
        ))
        print(f"Eingabebild geladen: {input_image}")

    contents = [types.Content(role="user", parts=parts)]

    print(f"Nano Banana 2 | Streaming | Modell: {model}")
    print(f"   Prompt: {prompt[:100]}{'...' if len(prompt) > 100 else ''}")
    print(f"   Format: {aspect_ratio}" + (f" | {resolution}" if resolution else "") +
          f" | Thinking: {thinking_level}")
    if use_google_search:
        print("   Google Search Tool: aktiv")
    print("   Generiere...")

    file_index = 0
    last_saved: Path | None = None
    stem   = output_path.stem
    parent = output_path.parent

    try:
        for chunk in client.models.generate_content_stream(
            model=model, contents=contents, config=gen_config
        ):
            if chunk.parts is None:
                continue
            for part in chunk.parts:
                if part.inline_data and part.inline_data.data:
                    suffix = mimetypes.guess_extension(part.inline_data.mime_type) or ".png"
                    if suffix == ".jpe":
                        suffix = ".jpg"
                    fname = f"{stem}{suffix}" if file_index == 0 else f"{stem}_{file_index}{suffix}"
                    out   = parent / fname
                    try:
                        out.write_bytes(part.inline_data.data)
                        print(f"Bild gespeichert: {out.resolve()}")
                        file_index += 1
                        last_saved = out
                    except OSError as e:
                        print(f"Fehler beim Speichern von {fname}: {e}")
                elif hasattr(part, "text") and part.text:
                    print(f"{part.text}")
    except Exception as e:
        # Security-Hinweis (v0.16.1 H.1-01-Fix, analog openai-image-gen v0.15.1 F-02):
        # nur Typ + str(e) gekürzt auf 200 Zeichen auf stderr, keine Stack-Frames
        # oder Request-Details, die ggf. den API-Key enthalten könnten.
        print(f"API-Fehler (Streaming): {type(e).__name__}: {str(e)[:200]}", file=sys.stderr)
        return None

    return last_saved


def generate_standard(
    prompt, client, model, aspect_ratio, resolution,
    output_path, input_image, use_google_search,
    characters=None, objects=None,
):
    from google.genai import types

    image_cfg = {"aspect_ratio": aspect_ratio}
    if resolution and model in RESOLUTION_MODELS:
        image_cfg["image_size"] = resolution

    config_kwargs = {
        "response_modalities": ["TEXT", "IMAGE"],
        "image_config": types.ImageConfig(**image_cfg),
    }
    if use_google_search:
        config_kwargs["tools"] = [{"google_search": {}}]

    gen_config = types.GenerateContentConfig(**config_kwargs)

    has_refs = (characters and len(characters) > 0) or (objects and len(objects) > 0)
    if has_refs:
        ref_parts, total_ref = build_ref_parts(characters, objects)
        ref_parts.append(types.Part.from_text(text=prompt))
        contents = [types.Content(role="user", parts=ref_parts)]
        print(f"{total_ref} Referenzbilder geladen")
    else:
        contents = [prompt]
        if input_image:
            try:
                from PIL import Image as PILImage
                contents.append(PILImage.open(input_image))
                print(f"Eingabebild geladen: {input_image}")
            except ImportError:
                img_bytes, mime_type = load_image_bytes(input_image)
                contents = [types.Content(role="user", parts=[
                    types.Part.from_text(text=prompt),
                    types.Part(inline_data=types.Blob(mime_type=mime_type, data=img_bytes)),
                ])]
                print(f"Eingabebild geladen (base64): {input_image}")

    label = {
        "gemini-2.5-flash-image":     "Nano Banana 1",
        "gemini-3-pro-image-preview": "Nano Banana Pro",
    }.get(model, model)

    print(f"{label} | Modell: {model}")
    print(f"   Prompt: {prompt[:100]}{'...' if len(prompt) > 100 else ''}")
    print(f"   Format: {aspect_ratio}" + (f" | {resolution}" if resolution else ""))
    if use_google_search:
        print("   Google Search Tool: aktiv")
    print("   Generiere...")

    try:
        response = client.models.generate_content(model=model, contents=contents, config=gen_config)
    except Exception as e:
        # Security-Hinweis (v0.16.1 H.1-01-Fix, analog openai-image-gen v0.15.1 F-02):
        # nur Typ + str(e) gekürzt auf 200 Zeichen auf stderr.
        print(f"API-Fehler: {type(e).__name__}: {str(e)[:200]}", file=sys.stderr)
        return None

    last_saved: Path | None = None

    for part in response.parts:
        if getattr(part, "thought", False):
            continue
        if part.text is not None:
            print(f"{part.text}")
        elif part.inline_data is not None:
            data = part.inline_data.data
            if isinstance(data, str):
                data = base64.b64decode(data)
            mime = getattr(part.inline_data, "mime_type", "image/png")
            saved = save_image(data, output_path, mime)
            last_saved = saved

    return last_saved


def generate_image(
    prompt,
    model=DEFAULT_MODEL,
    aspect_ratio="1:1",
    resolution=None,
    output_path="generated_image.png",
    input_image=None,
    use_google_search=False,
    thinking_level="minimal",
    transparent_bg=False,
    bg_key_color="#FF00FF",
    bg_tolerance=60,
    characters=None,
    objects=None,
) -> dict:
    """Wrapper-Funktion mit deterministischem Output-Schema.

    Returns: dict mit vier Pflicht-Schlüsseln (path, format, size, prompt_hash).

    Bei Generierungs-Fehler ist ``path`` None, die übrigen drei Felder bleiben
    konsistent (format='png', size=(0,0), prompt_hash gesetzt). Aufrufer
    erkennen Fehler an ``path is None``.
    """
    try:
        from google import genai
    except ImportError:
        print("Fehler: google-genai Paket nicht installiert.")
        print("   Bitte ausführen: pip install google-genai pillow numpy")
        return _build_result(None, prompt)

    skill_dir = Path(__file__).resolve().parent.parent
    api_key, _ = find_api_key(skill_dir)
    if not api_key:
        print("API-Key nicht gefunden in Umgebung.")
        return _build_result(None, prompt)

    if transparent_bg:
        prompt = augment_prompt_for_chromakey(prompt, bg_key_color)
        print(f"Transparenter Hintergrund: Chroma-Key {bg_key_color}, Toleranz {bg_tolerance}")

    client = genai.Client(api_key=api_key)
    out    = Path(output_path)

    if model in STREAMING_MODELS:
        saved = generate_nb2_streaming(
            prompt=prompt, client=client, model=model,
            aspect_ratio=aspect_ratio, resolution=resolution,
            output_path=out, input_image=input_image,
            thinking_level=thinking_level,
            use_google_search=use_google_search,
            characters=characters, objects=objects,
        )
    else:
        saved = generate_standard(
            prompt=prompt, client=client, model=model,
            aspect_ratio=aspect_ratio, resolution=resolution,
            output_path=out, input_image=input_image,
            use_google_search=use_google_search,
            characters=characters, objects=objects,
        )

    final_path = saved

    if saved is not None and transparent_bg:
        parent = out.parent
        stem = out.stem
        for img_file in sorted(parent.glob(f"{stem}*")):
            if img_file.suffix.lower() in (".png", ".jpg", ".jpeg", ".webp"):
                try:
                    new_path, pct = remove_background_chromakey(
                        img_file, bg_key_color, bg_tolerance, auto_detect=True,
                    )
                    final_path = new_path
                    if pct < 10:
                        print(f"Transparenz-Ergebnis für {img_file.name} ist niedrig ({pct:.1f}%).")
                        print("   Tipp: --bg-tolerance erhöhen oder Prompt anpassen.")
                except Exception as e:
                    print(f"Chroma-Key-Fehler bei {img_file.name}: {e}")

    return _build_result(final_path, prompt)


def main():
    parser = argparse.ArgumentParser(
        description="Nano Banana Image Generator — Google Gemini API",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Modelle (--model):
  gemini-3.1-flash-image-preview  → Nano Banana 2  DEFAULT
  gemini-2.5-flash-image          → Nano Banana 1
  gemini-3-pro-image-preview      → Nano Banana Pro

Beispiele:
  # Standard — Nano Banana 2 (Default)
  python generate_image.py --prompt "A sunset over Berlin" --aspect-ratio 16:9

  # Nano Banana Pro in 4K
  python generate_image.py --prompt "Minimalist logo" \\
    --model gemini-3-pro-image-preview --resolution 4K --aspect-ratio 1:1

  # Bildbearbeitung mit Eingabebild
  python generate_image.py --prompt "Mache den Hintergrund weiß" \\
    --input-image foto.jpg --output bearbeitet.png

  # Multi-Referenzbild: Charaktere + Szene
  python generate_image.py --prompt "Both characters performing on stage" \\
    --character "Person in dark suit::p1_a.jpg,p1_b.jpg" \\
    --character "Person in light blazer::p2_a.jpg,p2_b.jpg" \\
    --object "Concert venue::venue1.jpg,venue2.jpg" \\
    --aspect-ratio 16:9 --resolution 2K --thinking-level high
        """
    )

    parser.add_argument("--prompt", "-p", required=True,
                        help="Bildgenerierungs-Prompt")
    parser.add_argument("--model", "-m", default=DEFAULT_MODEL,
                        help=f"Modell-ID oder Alias (Standard: {DEFAULT_MODEL})")
    parser.add_argument("--aspect-ratio", "-a", default="1:1",
                        choices=["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9", "1:4", "4:1", "1:8", "8:1"],
                        help="Seitenverhältnis (Standard: 1:1)")
    parser.add_argument("--resolution", "-r", choices=["512", "1K", "2K", "4K"],
                        help="Auflösung — verfügbar für Nano Banana 2 und Pro")
    parser.add_argument("--output", "-o", default=None,
                        help="Ausgabedatei (Standard: assets/generated_images/<prompt>.png)")
    parser.add_argument("--input-image", "-i",
                        help="Eingabebild für Bildbearbeitung (optionaler Pfad)")
    parser.add_argument("--google-search", action="store_true",
                        help="Google Search Tool aktivieren (Nano Banana 2 und Pro)")
    parser.add_argument("--thinking-level", default="minimal",
                        choices=["minimal", "high"],
                        help="Thinking Level für Nano Banana 2 (Standard: MINIMAL)")
    parser.add_argument("--transparent-bg", "-t", action="store_true",
                        help="Transparenten Hintergrund erzeugen (Chroma-Key → Alpha)")
    parser.add_argument("--bg-key-color", default="#FF00FF",
                        help="Chroma-Key Farbe als Hex (Standard: #FF00FF Magenta)")
    parser.add_argument("--bg-tolerance", type=int, default=60,
                        help="Farbdistanz-Schwellwert 0-255 (Standard: 60)")
    parser.add_argument("--character", "-c", action="append", default=[],
                        help="Charakter-Referenz: 'Beschreibung::bild1.jpg,bild2.jpg' (wiederholbar)")
    parser.add_argument("--object", action="append", default=[],
                        help="Objekt/Szene-Referenz: 'Beschreibung::bild1.jpg,bild2.jpg' (wiederholbar)")

    args  = parser.parse_args()
    model = resolve_model(args.model)

    skill_dir = Path(__file__).resolve().parent.parent
    api_key, source = find_api_key(skill_dir)
    if not api_key:
        env_var = _resolve_env_var_name(skill_dir)
        print(f"API-Key in Umgebungsvariable {env_var} nicht gefunden.")
        print("Optionen:")
        print(f"  1. Shell-Profil: export {env_var}=...")
        print(f"  2. .env-Datei im Projekt-Root: {env_var}=...")
        print("  3. 1Password CLI mit op-run-Wrapper (siehe INSTALL.md)")
        sys.exit(1)
    print("API-Key gefunden: OK")

    if args.output is None:
        output_dir = Path.cwd() / "assets" / "generated_images"
        output_dir.mkdir(parents=True, exist_ok=True)
        args.output = str(output_dir / (prompt_to_filename(args.prompt) + ".png"))

    if args.transparent_bg and not args.output.lower().endswith(".png"):
        args.output = str(Path(args.output).with_suffix(".png"))

    resolution = args.resolution
    if resolution is None and model in RESOLUTION_MODELS:
        resolution = "1K"

    characters = []
    for ref_str in args.character:
        try:
            characters.append(parse_ref_group(ref_str))
        except (ValueError, FileNotFoundError) as e:
            print(f"Fehler in --character: {e}")
            sys.exit(1)

    objects = []
    for ref_str in args.object:
        try:
            objects.append(parse_ref_group(ref_str))
        except (ValueError, FileNotFoundError) as e:
            print(f"Fehler in --object: {e}")
            sys.exit(1)

    num_char_images = sum(len(paths) for _, paths in characters)
    num_obj_images = sum(len(paths) for _, paths in objects)
    total_ref = num_char_images + num_obj_images
    if total_ref > 0:
        if model == "gemini-3.1-flash-image-preview" and (len(characters) > 4 or num_obj_images > 10 or total_ref > 14):
            print(f"Warnung: NB2 max. 4 Charaktere + 10 Objekte = 14 Referenzbilder (aktuell: {total_ref})")
        elif model == "gemini-3-pro-image-preview" and (len(characters) > 5 or num_obj_images > 6 or total_ref > 11):
            print(f"Warnung: NB Pro max. 5 Charaktere + 6 Objekte = 11 Referenzbilder (aktuell: {total_ref})")
        elif model == "gemini-2.5-flash-image" and total_ref > 3:
            print(f"Warnung: NB1 funktioniert optimal mit max. 3 Eingabebildern (aktuell: {total_ref})")
        print(f"Referenzbilder: {len(characters)} Charakter(e), {len(objects)} Objekt(e), {total_ref} Bilder gesamt")

    result = generate_image(
        prompt=args.prompt,
        model=model,
        aspect_ratio=args.aspect_ratio,
        resolution=resolution,
        output_path=args.output,
        input_image=args.input_image,
        use_google_search=args.google_search,
        thinking_level=args.thinking_level,
        transparent_bg=args.transparent_bg,
        bg_key_color=args.bg_key_color,
        bg_tolerance=args.bg_tolerance,
        characters=characters if characters else None,
        objects=objects if objects else None,
    )

    success = result.get("path") is not None
    print("\nFertig." if success else "\nKein Bild generiert.")
    if not success:
        sys.exit(1)


if __name__ == "__main__":
    main()
