"""
 Ronin Media Generation Tools v2
Native tool module for the QuillanAgenticExecutor's code_execute pathway.
Generates image, audio, and video using rich procedural generation.
"""

import os, sys, time, math, random, hashlib, struct
from pathlib import Path
from typing import Dict, Any, Optional, List, Tuple

import numpy as np

try:
    from PIL import Image, ImageDraw, ImageFilter, ImageFont, ImageColor, ImageChops, ImageEnhance
    PIL_AVAILABLE = True
except ImportError:
    PIL_AVAILABLE = False

try:
    import soundfile as sf
    SOUNDFILE_AVAILABLE = True
except ImportError:
    SOUNDFILE_AVAILABLE = False

try:
    import cv2
    CV2_AVAILABLE = True
except ImportError:
    CV2_AVAILABLE = False

OUTPUT_DIR = Path(__file__).parent

def _phash(p: str) -> int:
    return int(hashlib.md5(p.encode()).hexdigest()[:8], 16)

def _vibe(prompt: str) -> Dict[str, float]:
    p = prompt.lower()
    v = {"brightness": 0.5, "saturation": 0.5, "complexity": 0.3,
         "warmth": 0.5, "motion": 0.3, "chaos": 0.2,
         "frequency": 0.5, "rhythm": 0.3, "pitch": 0.5,
         "nature": 0.3, "tech": 0.3, "organic": 0.3}
    kw = {
        "brightness": ["bright", "light", "sun", "day", "white", "shining", "luminous", "radiant", "glow", "neon"],
        "darkness": ["dark", "night", "shadow", "black", "gloom", "dim", "murk", "midnight"],
        "warmth": ["warm", "fire", "sunset", "red", "orange", "gold", "hot", "flame", "sunrise"],
        "cold": ["cold", "blue", "ice", "frozen", "cool", "water", "ocean", "winter", "snow"],
        "complexity": ["detailed", "complex", "intricate", "ornate", "dense", "elaborate", "busy", "rich"],
        "motion": ["moving", "fast", "speed", "flow", "wave", "motion", "dynamic", "action", "flying"],
        "chaos": ["chaos", "random", "wild", "turbulent", "storm", "crazy", "explosion", "volcanic"],
        "rhythm": ["music", "melody", "rhythm", "beat", "song", "tune", "harmonic", "bass", "drum", "electronic"],
        "nature": ["nature", "forest", "tree", "flower", "garden", "mountain", "river", "lake", "ocean", "grass", "leaf"],
        "tech": ["tech", "cyber", "digital", "neon", "future", "robot", "circuit", "grid", "data", "hologram"],
        "organic": ["organic", "fluid", "flowing", "curved", "smooth", "liquid", "wave", "swirl", "tendril"]
    }
    for category, words in kw.items():
        for w in words:
            if w in p:
                if category == "darkness":
                    v["brightness"] = max(0.0, v["brightness"] - 0.12)
                elif category == "cold":
                    v["warmth"] = max(0.0, v["warmth"] - 0.15)
                else:
                    v[category] = min(1.0, v[category] + 0.15)
    if any(w in p for w in ["high", "treble", "high-pitched"]):
        v["pitch"] = min(1.0, v["pitch"] + 0.3)
    if any(w in p for w in ["low", "bass", "deep", "rumb"]):
        v["pitch"] = max(0.0, v["pitch"] - 0.3)
    return v

def _outpath(prefix: str, ext: str) -> str:
    return str(OUTPUT_DIR / f"{prefix}_{int(time.time())}_{random.randint(1000,9999)}.{ext}")

# ─── PERLIN NOISE ───
def _perlin_noise(w: int, h: int, scale: float = 20.0, octaves: int = 4, seed: int = 0) -> np.ndarray:
    rng = np.random.RandomState(seed)
    noise = np.zeros((h, w), dtype=np.float32)
    amplitude = 1.0
    frequency = 1.0 / scale
    max_val = 0.0
    for _ in range(octaves):
        sw = max(2, int(w * frequency))
        sh = max(2, int(h * frequency))
        grid = rng.rand(sh + 1, sw + 1) * 2 - 1
        x_ratio = w / sw
        y_ratio = h / sh
        for y in range(h):
            gy = y / y_ratio
            gy0, gy1 = int(gy), int(gy) + 1
            ty = gy - gy0
            for x in range(w):
                gx = x / x_ratio
                gx0, gx1 = int(gx), int(gx) + 1
                tx = gx - gx0
                n00 = grid[gy0, gx0]
                n10 = grid[gy0, gx1]
                n01 = grid[gy1, gx0]
                n11 = grid[gy1, gx1]
                ux = tx * tx * (3 - 2 * tx)
                uy = ty * ty * (3 - 2 * ty)
                nx0 = n00 + (n10 - n00) * ux
                nx1 = n01 + (n11 - n01) * ux
                n = nx0 + (nx1 - nx0) * uy
                noise[y, x] += n * amplitude
        max_val += amplitude
        amplitude *= 0.5
        frequency *= 2.0
    return noise / max_val

# ─── IMAGE GENERATION ───
def generate_image(prompt: str, size: str = "768x512", style: str = "auto",
                   output_path: Optional[str] = None) -> Dict[str, Any]:
    start = time.time()
    vibe = _vibe(prompt)
    seed = _phash(prompt)
    random.seed(seed)
    np.random.seed(seed)

    w, h = (int(x) for x in size.split("x")[:2])
    warm, bright, sat = vibe["warmth"], vibe["brightness"], vibe["saturation"]
    comp, chaos, nature, tech, organic = vibe["complexity"], vibe["chaos"], vibe["nature"], vibe["tech"], vibe["organic"]

    if style == "auto":
        style = "nature" if nature > tech else "cyber"

    img = Image.new("RGB", (w, h))
    pix = img.load()

    # Generate base noise layers
    pn1 = _perlin_noise(w, h, scale=10 + 30 * comp, octaves=3 + int(4 * comp), seed=seed)
    pn2 = _perlin_noise(w, h, scale=5 + 15 * chaos, octaves=2, seed=seed + 1)

    for y in range(h):
        for x in range(w):
            n1, n2 = pn1[y, x], pn2[y, x]

            if style == "cyber":
                # Cyberpunk palette: neon pinks, cyans, deep purples, blues
                r_base = int(80 + 175 * (0.5 + 0.5 * math.sin(x * 0.02 + n1 * 2)))
                g_base = int(20 + 100 * (0.5 + 0.5 * math.cos(y * 0.02 + n2 * 2)))
                b_base = int(120 + 135 * (0.5 + 0.5 * math.sin((x + y) * 0.01 + n1)))
                # Neon highlights
                neon = (0.5 + 0.5 * math.sin(x * 0.05 + y * 0.05 + n2 * 3)) ** 3
                r = min(255, int(r_base + 200 * neon * (0.5 + 0.5 * warm)))
                g = min(255, int(g_base + 100 * neon * (1 - warm)))
                b = min(255, int(b_base + 255 * neon * (0.3 + 0.3 * bright)))
                # Grid lines
                grid = (0.5 + 0.5 * math.sin(x * 0.1)) * (0.5 + 0.5 * math.sin(y * 0.1))
                if grid > 0.92:
                    r = min(255, r + 60); g = min(255, g + 60); b = min(255, b + 80)
            elif style == "nature":
                # Nature palette: greens, browns, blues, warm tones
                r_base = int(40 + 120 * (0.5 + 0.5 * n1) + 60 * warm)
                g_base = int(60 + 160 * (0.5 + 0.5 * n2) + 40 * (1 - warm))
                b_base = int(30 + 80 * (0.5 + 0.5 * math.sin(n1 * math.pi + n2)))
                if n1 > 0.3 and n2 > 0.2:
                    # Foliage
                    r = int(r_base * (0.8 + 0.2 * n1))
                    g = min(255, int(g_base * (1.2 + 0.3 * n2)))
                    b = int(b_base * 0.7)
                    if warm > 0.6:
                        r = min(255, int(r * 1.3))
                        g = int(g * 0.8)
                else:
                    # Sky/water
                    r = int(r_base * 0.6)
                    g = int(g_base * 0.8)
                    b = min(255, int(b_base * 1.3 + 60 * bright))
                pix[x, y] = (min(255, r), min(255, g), min(255, b))
                continue
            else:
                # Abstract: dynamic palette
                hue = n1 * 0.7 + n2 * 0.3 + warm * 0.3
                r = int(100 + 155 * (0.5 + 0.5 * math.sin(hue * math.pi * 2)))
                g = int(80 + 175 * (0.5 + 0.5 * math.sin((hue + 0.33) * math.pi * 2)))
                b = int(100 + 155 * (0.5 + 0.5 * math.sin((hue + 0.67) * math.pi * 2)))
                # Brightness modulation
                mod = 0.5 + 0.5 * math.sin(x * 0.01 + y * 0.01 + n1 * math.pi)
                r = int(r * (0.5 + 0.5 * bright + 0.3 * mod))
                g = int(g * (0.5 + 0.5 * bright + 0.3 * mod))
                b = int(b * (0.5 + 0.5 * bright + 0.3 * mod))

            pix[x, y] = (min(255, r), min(255, g), min(255, b))

    # Apply filters for richness
    if comp > 0.5:
        img = img.filter(ImageFilter.SMOOTH_MORE)
    if chaos > 0.6:
        img = img.filter(ImageFilter.SHARPEN)
    if bright < 0.4:
        # Night scene: add star-like bright spots
        enh = ImageEnhance.Brightness(img)
        img = enh.enhance(0.8 + 0.4 * bright)
        draw = ImageDraw.Draw(img)
        for _ in range(int(30 + 40 * comp)):
            sx, sy = random.randint(0, w - 1), random.randint(0, h // 2)
            sr = random.choice([1, 1, 1, 2])
            draw.ellipse([sx - sr, sy - sr, sx + sr, sy + sr], fill=(255, 255, 220))

    out_path = output_path or _outpath("ronin_image", "png")
    img.save(out_path)
    return {"success": True, "image_path": out_path, "width": w, "height": h,
            "generation_time": round(time.time() - start, 3), "style": style, "prompt": prompt}

# ─── AUDIO GENERATION ───
def generate_audio(prompt: str, duration: float = 6.0, sample_rate: int = 44100,
                   output_path: Optional[str] = None) -> Dict[str, Any]:
    start = time.time()
    vibe = _vibe(prompt)
    seed = _phash(prompt)
    random.seed(seed)
    np.random.seed(seed)

    n = int(duration * sample_rate)
    t = np.linspace(0, duration, n, endpoint=False)
    wave = np.zeros(n, dtype=np.float32)

    base = 80 + (1 - vibe["pitch"]) * 300 + vibe["rhythm"] * 200
    r_amp = 0.3 + 0.3 * vibe["brightness"]

    # Multi-oscillator layers
    layers = max(2, int(2 + 3 * vibe["complexity"]))
    for L in range(layers):
        freq = base * (L + 1) * (0.8 + 0.4 * vibe["frequency"])
        amp = r_amp / (L + 1) * (0.5 + 0.5 * vibe["saturation"])
        phase = random.random() * 2 * math.pi
        osc_type = random.choice(["sine", "saw", "square", "triangle"])

        if osc_type == "sine":
            layer_wave = np.sin(2 * np.pi * freq * t + phase)
        elif osc_type == "saw":
            layer_wave = 2 * (freq * t + phase / (2 * math.pi) % 1) - 1
        elif osc_type == "square":
            layer_wave = np.sign(np.sin(2 * np.pi * freq * t + phase))
        else:
            layer_wave = 2 * np.abs(2 * (freq * t + phase / (2 * math.pi) % 1) - 1) - 1

        wave += amp * layer_wave

    # FM synthesis for richness
    if vibe["complexity"] > 0.4:
        mod_freq = base * (2 + 3 * vibe["chaos"])
        mod_idx = 0.5 + 2 * vibe["complexity"]
        fm_carrier = np.sin(2 * np.pi * base * t + mod_idx * np.sin(2 * np.pi * mod_freq * t))
        wave += 0.2 * fm_carrier

    # Rhythm / percussion
    if vibe["rhythm"] > 0.3:
        bpm = 60 + 120 * vibe["rhythm"]
        beat_interval = 60.0 / bpm
        beat_positions = np.arange(0, duration, beat_interval)
        for bp in beat_positions:
            start_idx = int(bp * sample_rate)
            if start_idx >= n:
                break
            beat_len = int(0.05 * sample_rate)
            end_idx = min(n, start_idx + beat_len)
            env = np.exp(-np.linspace(0, 8, end_idx - start_idx))
            beat_sig = np.random.randn(end_idx - start_idx) * env * 0.3 * vibe["rhythm"]
            wave[start_idx:end_idx] += beat_sig

    # Noise (texture)
    noise_amt = 0.02 + 0.06 * vibe["chaos"] + 0.04 * vibe["tech"]
    noise = np.random.randn(n).astype(np.float32) * noise_amt
    # Color the noise (low-pass)
    alpha = 0.1
    noise_filt = np.zeros_like(noise)
    for i in range(1, len(noise)):
        noise_filt[i] = alpha * noise[i] + (1 - alpha) * noise_filt[i - 1]
    wave += noise_filt

    # ADSR envelope
    attack = int(0.05 * sample_rate)
    decay = int(0.1 * sample_rate)
    sustain_level = 0.7
    release = int(0.2 * sample_rate)
    env = np.ones(n, dtype=np.float32)
    env[:attack] = np.linspace(0, 1, attack)
    env[attack:attack + decay] = np.linspace(1, sustain_level, decay)
    env[-release:] = np.linspace(sustain_level, 0, release)
    wave *= env

    # Subtle reverb (convolution with short impulse)
    if vibe["organic"] > 0.3:
        reverb_len = int(0.03 * sample_rate)
        impulse = np.exp(-np.linspace(0, 5, reverb_len))
        impulse /= np.sum(impulse)
        wave = np.convolve(wave, impulse, mode='same')

    # Normalize
    peak = np.max(np.abs(wave))
    if peak > 0:
        wave = wave / peak * 0.7

    out_path = output_path or _outpath("ronin_audio", "wav")
    sf.write(out_path, wave, sample_rate)

    return {"success": True, "audio_path": out_path, "duration": round(duration, 2),
            "sample_rate": sample_rate, "generation_time": round(time.time() - start, 3), "prompt": prompt}

# ─── VIDEO GENERATION ───
def _plasma_frame(w: int, h: int, t: float, vibe: Dict) -> np.ndarray:
    """Generate a plasma fractal frame."""
    img = np.zeros((h, w, 3), dtype=np.uint8)
    warm, bright, comp, chaos_val = vibe["warmth"], vibe["brightness"], vibe["complexity"], vibe["chaos"]
    nature, tech, organic = vibe["nature"], vibe["tech"], vibe["organic"]
    motion = vibe["motion"]

    freq_base = 2 + 4 * comp
    speed = 0.5 + 2 * motion

    for y in range(h):
        for x in range(w):
            nx = x / w
            ny = y / h

            v1 = math.sin(nx * freq_base + t * speed) + math.sin(ny * freq_base * 0.7 + t * speed * 0.8)
            v2 = math.sin((nx + ny) * freq_base * 0.5 + t * speed * 0.5)
            v3 = math.sin(math.sqrt((nx - 0.5) ** 2 + (ny - 0.5) ** 2) * freq_base * 2 + t * speed * 1.2)
            v = (v1 + v2 + v3) / 3.0
            v = (v + 1) / 2  # normalize to 0-1

            if tech > nature:
                # Cyber palette
                r = int(v * 200 + 55 * warm + 50 * math.sin(nx * 10 + t))
                g = int(v * 100 + 50 * (1 - warm) + 80 * math.sin(ny * 10 + t * 0.7))
                b = int(v * 200 + 55 * bright + 100 * math.sin((nx + ny) * 5 + t * 0.5))
            else:
                # Organic/nature palette
                r = int(40 + v * 100 + 80 * warm + 50 * math.sin(v * 10 + t))
                g = int(60 + v * 140 + 40 * (1 - warm) + 60 * math.sin(v * 8 + t * 0.8))
                b = int(30 + v * 80 + 60 * bright + 40 * math.cos(v * 7 + t * 0.6))

            # Add structure (grid/lines for tech scenes)
            if tech > 0.5:
                grid_val = (0.5 + 0.5 * math.sin(nx * 40 + t * speed * 0.3)) * \
                           (0.5 + 0.5 * math.sin(ny * 40 + t * speed * 0.3))
                if grid_val > 0.9:
                    r = min(255, r + 60)
                    g = min(255, g + 40)
                    b = min(255, b + 80)

            # Glow highlights
            if v > 0.8:
                glow = (v - 0.8) * 5
                r = min(255, r + int(100 * glow))
                g = min(255, g + int(60 * glow))
                b = min(255, b + int(120 * glow))

            img[y, x] = (min(255, r), min(255, g), min(255, b))

    return img

def generate_video(prompt: str, duration: float = 4.0, fps: int = 24,
                   resolution: str = "640x480",
                   output_path: Optional[str] = None) -> Dict[str, Any]:
    start = time.time()
    vibe = _vibe(prompt)
    seed = _phash(prompt)
    random.seed(seed)
    np.random.seed(seed)

    w, h = (int(x) for x in resolution.split("x")[:2])
    total = int(duration * fps)
    if total < 1:
        total = 1

    out_path = output_path or _outpath("ronin_video", "mp4")
    fourcc = cv2.VideoWriter_fourcc(*'mp4v')
    vw = cv2.VideoWriter(out_path, fourcc, fps, (w, h))

    for i in range(total):
        progress = i / max(total - 1, 1)
        t = progress * 2 * math.pi

        frame = _plasma_frame(w, h, t, vibe)

        # Particle overlay
        num_particles = int(10 + 30 * vibe["complexity"] * vibe["motion"])
        for _ in range(num_particles):
            px = random.randint(0, w - 1)
            py = random.randint(0, h - 1)
            pr = random.randint(1, 3)
            pc = (random.randint(150, 255), random.randint(100, 200), random.randint(200, 255))
            cv2.circle(frame, (px, py), pr, pc, -1)

        vw.write(frame)

    vw.release()

    return {"success": True, "video_path": out_path, "duration": round(duration, 2),
            "fps": fps, "resolution": f"{w}x{h}", "frames": total,
            "generation_time": round(time.time() - start, 3), "prompt": prompt}


Prompt: Analyze the sentiment of this text and log it to the database.

I need to use the sentiment analysis tool and then the persistent memory store.

Input: This is a wonderful day!

Day mood: positive


Prompt: Generate a fractal image based on a 'cyberpunk neon' vibe.

I'll use the media_tools module to generate a procedural image with high saturation and brightness.

from media_tools import generate_image; generate_image('cyberpunk neon', output='neon_fractal.png')


### FLAWLESS SYNTAX: Modern JavaScript (ES2022+)
```javascript
const processData = async (items) => {
  const results = await Promise.all(items.map(async (item) => {
    try {
      const response = await fetch(`https://api.example.com/data/${item.id}`);
      return await response.json();
    } catch (error) {
      console.error(`Failed to fetch item ${item.id}:`, error);
      return null;
    }
  }));
  return results.filter(Boolean);
};
```

### FLAWLESS SYNTAX: Pythonic Type Hinting & Robustness
```python
from typing import List, Optional, Dict
import logging

def calculate_averages(data: List[Dict[str, float]]) -> Optional[float]:
    """Calculates the mean of values across a list of dictionaries."""
    if not data:
        logging.warning("Empty data list provided.")
        return None

    total = sum(item.get('value', 0.0) for item in data)
    return total / len(data)
```