#!/usr/bin/env python3
"""
Tatiana's best Pillow composite.
No warp (distorts logo). Focus: correct logo, solid border, good integration.
"""

from PIL import Image, ImageDraw, ImageFilter
import numpy as np
from scipy.ndimage import map_coordinates

np.random.seed(42)

base = Image.open("v1-03-mid-full-body.png").convert("RGBA")
logo_raw = Image.open("ff-logo-mark.png").convert("RGBA")
base_rgb = np.array(base.convert("RGB"), dtype=np.float32)
W, H = base.size

PATCH_W, PATCH_H = 85, 66
PASTE_X, PASTE_Y = 710, 740

arm = base_rgb[PASTE_Y:PASTE_Y+PATCH_H, PASTE_X:PASTE_X+PATCH_W]
arm_lum_map = arm[:,:,0]*0.299 + arm[:,:,1]*0.587 + arm[:,:,2]*0.114
avg_lum = arm_lum_map.mean()
print(f"Arm avg lum: {avg_lum:.1f}")

# ============================================================
# PATCH FABRIC — warm off-white, MUST be brighter than arm
# ============================================================
lum_scale = np.clip(avg_lum / 200.0, 0.90, 1.05)
fab_r = int(np.clip(230 * lum_scale, 200, 242))
fab_g = int(np.clip(224 * lum_scale, 195, 238))
fab_b = int(np.clip(212 * lum_scale, 185, 226))
print(f"Fabric color: ({fab_r}, {fab_g}, {fab_b})")

fabric = np.full((PATCH_H, PATCH_W, 4), [fab_r, fab_g, fab_b, 255], dtype=np.float32)

# Canvas texture
yi = np.arange(PATCH_H)[:, None].astype(np.float32)
xi = np.arange(PATCH_W)[None, :].astype(np.float32)
canvas = (np.cos(xi * np.pi / 1.8) * 3.0 + 
          np.cos(yi * np.pi / 1.8) * 2.5 +
          np.sin((xi + yi) * np.pi / 2.5) * 1.5)
noise = np.random.normal(0, 2.5, (PATCH_H, PATCH_W, 3))
fabric[:,:,:3] = np.clip(fabric[:,:,:3] + canvas[:,:,np.newaxis] + noise, 0, 255)

# Gentle lighting modulation (very subtle — don't let arm shadow darken patch too much)
lmod = np.clip(0.90 + 0.18 * arm_lum_map / 255.0, 0.85, 1.06)
fabric[:,:,:3] = np.clip(fabric[:,:,:3] * lmod[:,:,np.newaxis], 0, 255)

# Subtle edge shadow (patch thickness creates tiny shadow)
for d in range(3):
    fade = 1.0 - (3-d) * 0.015
    fabric[d,:,:3] *= fade; fabric[PATCH_H-1-d,:,:3] *= fade
    fabric[:,d,:3] *= fade; fabric[:,PATCH_W-1-d,:3] *= fade

fabric[:,:,3] = 255

# ============================================================
# LOGO — use clean threshold, no warp
# ============================================================
logo_area_w = PATCH_W - 24
logo_area_h = PATCH_H - 20
logo_copy = logo_raw.copy()
logo_copy.thumbnail((logo_area_w, logo_area_h), Image.LANCZOS)
lw, lh = logo_copy.size
print(f"Logo: {lw}x{lh} on patch {PATCH_W}x{PATCH_H}")

ld = np.array(logo_copy, dtype=np.float32)
logo_lum = ld[:,:,0]*0.299 + ld[:,:,1]*0.587 + ld[:,:,2]*0.114
logo_px = (logo_lum < 130) & (ld[:,:,3] > 30)

# Satin stitch embroidery
yi2 = np.arange(lh)[:, None].astype(np.float32)
xi2 = np.arange(lw)[None, :].astype(np.float32)
stdir = yi2 * 0.6 + xi2
tphase = (stdir % 2.0) / 2.0
tval = np.clip(8 + np.sin(tphase * np.pi) * 34 + np.random.normal(0, 1.5, (lh, lw)), 0, 50)

emb = np.zeros((lh, lw, 4), dtype=np.float32)
emb[:,:,0] = logo_px * tval * 0.92
emb[:,:,1] = logo_px * tval * 0.82
emb[:,:,2] = logo_px * tval * 0.62
emb[:,:,3] = logo_px * 250

emb_img = Image.fromarray(np.clip(emb, 0, 255).astype(np.uint8), "RGBA")
emb_img = emb_img.filter(ImageFilter.GaussianBlur(0.35))

patch_img = Image.fromarray(np.clip(fabric, 0, 255).astype(np.uint8), "RGBA")
lx = (PATCH_W - lw) // 2
ly = (PATCH_H - lh) // 2
patch_img.paste(emb_img, (lx, ly), emb_img)

# ============================================================
# SOLID MERROWED BORDER — filled band, satin texture
# ============================================================
pa = np.array(patch_img, dtype=np.float32)
BW = 5

yi3 = np.arange(PATCH_H)[:, None].astype(np.float32)
xi3 = np.arange(PATCH_W)[None, :].astype(np.float32)
hsheen = np.sin(xi3 * np.pi / 1.6) * 0.25 + 0.75
vsheen = np.sin(yi3 * np.pi / 1.6) * 0.25 + 0.75

bc = np.array([18, 14, 9], dtype=np.float32)  # very dark thread
bc_h = np.array([32, 26, 16], dtype=np.float32)  # highlight thread

tb_mask = (yi3 < BW) | (yi3 >= PATCH_H - BW)
sb_mask = ((xi3 < BW) | (xi3 >= PATCH_W - BW)) & ~tb_mask

for ch in range(3):
    pa[:,:,ch] = np.where(tb_mask, bc[ch] * hsheen + bc_h[ch] * (1-hsheen), pa[:,:,ch])
    pa[:,:,ch] = np.where(sb_mask, bc[ch] * vsheen + bc_h[ch] * (1-vsheen), pa[:,:,ch])

# Outermost rows: merrowed edge accent
pa[0,   BW:-BW, :3] = bc_h * 1.8  # highlight
pa[PATCH_H-1, BW:-BW, :3] = bc * 0.9   # shadow
pa[BW:-BW, 0,   :3] = bc_h * 1.7
pa[BW:-BW, PATCH_W-1, :3] = bc * 0.8

# ============================================================
# EDGE ALPHA VIGNETTE — blends edges slightly into suit
# ============================================================
alpha_map = np.ones((PATCH_H, PATCH_W), dtype=np.float32) * 255
# Very slight edge fade (1px at border gets 92% alpha)
alpha_map[0,:] = 235; alpha_map[PATCH_H-1,:] = 230
alpha_map[:,0] = 235; alpha_map[:,PATCH_W-1] = 230
pa[:,:,3] = alpha_map

patch_img = Image.fromarray(np.clip(pa, 0, 255).astype(np.uint8), "RGBA")
# Micro-blur for integration
patch_img = patch_img.filter(ImageFilter.GaussianBlur(0.22))

# ============================================================
# COMPOSITE
# ============================================================
result = base.copy()

# Shadow: concentrated on bottom+right edges (light from upper-left in image)
sh_arr = np.zeros((H, W, 4), dtype=np.float32)
# Build shadow as gradient patch
for dy in range(-2, 10):
    for dx in range(-2, 10):
        # Shadow goes below-right
        dist = max(0, max(dy, dx))
        if dx < 0 or dy < 0:
            alpha = max(0, 12 - abs(min(dx,dy)) * 4)
        else:
            alpha = max(0, 28 - dist * 3.5)
        if alpha > 0:
            sy, sx = PASTE_Y + PATCH_H + dy, PASTE_X + dx
            ey, ex = sy + 1, sx + PATCH_W - dx*0
            if 0 <= sy < H and 0 <= sx < W:
                for y in range(max(0, PASTE_Y+PATCH_H-3), min(H, PASTE_Y+PATCH_H+8)):
                    x_s = max(0, PASTE_X + 2)
                    x_e = min(W, PASTE_X + PATCH_W + 5)
                    pass

# Simpler approach: just draw shadow as blurred rect  
sh_img = Image.new("RGBA", (W, H), (0,0,0,0))
# Under-patch shadow (right and bottom)
shadow_patch_under = Image.new("RGBA", (PATCH_W + 10, PATCH_H + 10), (0,0,0,0))
shadow_draw = ImageDraw.Draw(shadow_patch_under)
shadow_draw.rectangle([0, 0, PATCH_W + 9, PATCH_H + 9], fill=(5, 5, 5, 30))
shadow_patch_under = shadow_patch_under.filter(ImageFilter.GaussianBlur(3))
sh_img.paste(shadow_patch_under, (PASTE_X - 3, PASTE_Y + 3), shadow_patch_under)
result = Image.alpha_composite(result, sh_img)

# Patch layer
pl = Image.new("RGBA", (W, H), (0,0,0,0))
pl.paste(patch_img, (PASTE_X, PASTE_Y), patch_img)
result = Image.alpha_composite(result, pl)

# ============================================================
# SAVE
# ============================================================
out = result.convert("RGB")
out.save("tatiana-patch-v2.jpg", quality=96)
print("✓ Saved tatiana-patch-v2.jpg")

ctx = out.crop((585, 610, 900, 895))
ctx.save("tatiana-patch-v2-context.jpg", quality=95)

zp = out.crop((PASTE_X-18, PASTE_Y-18, PASTE_X+PATCH_W+18, PASTE_Y+PATCH_H+18))
zp = zp.resize((zp.width*4, zp.height*4), Image.LANCZOS)
zp.save("tatiana-patch-v2-zoom.jpg", quality=95)
print("✓ Context + Zoom saved.")
