#!/usr/bin/env python3
"""
Tatiana's patch v6 — stronger warp, edge fade blending, better shadow, brighter patch.
"""

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

np.random.seed(7)

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

# ============================================================
# PLACEMENT
# ============================================================
PATCH_W, PATCH_H = 78, 60
PASTE_X, PASTE_Y = 713, 742

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()

# ============================================================
# PATCH FABRIC — brighter off-white
# ============================================================
lum_scale = np.clip(avg_lum / 195.0, 0.90, 1.05)
fab_r = int(np.clip(228 * lum_scale, 200, 240))
fab_g = int(np.clip(222 * lum_scale, 195, 236))
fab_b = int(np.clip(210 * lum_scale, 185, 224))
print(f"Fabric: ({fab_r}, {fab_g}, {fab_b})")

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

y_idx = np.arange(PATCH_H)[:, None].astype(np.float32)
x_idx = np.arange(PATCH_W)[None, :].astype(np.float32)
canvas_tex = (np.cos(x_idx * np.pi / 1.8) * 3.0 + 
              np.cos(y_idx * np.pi / 1.8) * 2.5 + 
              np.sin((x_idx + y_idx) * np.pi / 2.2) * 1.8)
noise = np.random.normal(0, 2.8, (PATCH_H, PATCH_W, 3))
fabric[:,:,:3] = np.clip(fabric[:,:,:3] + canvas_tex[:,:,np.newaxis] + noise, 0, 255)

# Lighting from suit
lmod = np.clip(0.88 + 0.22 * arm_lum_map / 255.0, 0.82, 1.08)
fabric[:,:,:3] = np.clip(fabric[:,:,:3] * lmod[:,:,np.newaxis], 0, 255)

fabric[:,:,3] = 255

# ============================================================
# LOGO → EMBROIDERY
# ============================================================
logo_area_w = PATCH_W - 20
logo_area_h = PATCH_H - 16
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}")

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 < 120) & (ld[:,:,3] > 40)

yi = np.arange(lh)[:, None].astype(np.float32)
xi = np.arange(lw)[None, :].astype(np.float32)
stdir = yi * 0.65 + xi
tphase = (stdir % 2.0) / 2.0
tval = np.clip(7 + np.sin(tphase * np.pi) * 35 + 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.90
emb[:,:,1] = logo_px * tval * 0.80
emb[:,:,2] = logo_px * tval * 0.60
emb[:,:,3] = logo_px * 248

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

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 SATIN-STITCH BORDER
# ============================================================
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)

# Horizontal satin stitch (top/bottom)
hsheen = np.sin(xi3 * np.pi / 1.6) * 0.28 + 0.72
# Vertical satin stitch (sides)
vsheen = np.sin(yi3 * np.pi / 1.6) * 0.28 + 0.72

bc_lo = np.array([18, 14, 9], dtype=np.float32)
bc_hi = np.array([32, 26, 17], dtype=np.float32)

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_lo[ch] * hsheen + bc_hi[ch] * (1-hsheen), pa[:,:,ch])
    pa[:,:,ch] = np.where(sb_mask, bc_lo[ch] * vsheen + bc_hi[ch] * (1-vsheen), pa[:,:,ch])

# Merrowed edge highlight (topmost and bottommost rows)
pa[0,BW:-BW,:3] = bc_hi * 1.6
pa[PATCH_H-1,BW:-BW,:3] = bc_lo * 0.8
pa[BW:-BW,0,:3] = bc_hi * 1.5
pa[BW:-BW,PATCH_W-1,:3] = bc_lo * 0.7

# ============================================================
# EDGE FADE — make patch edges blend into suit slightly
# Creates "sewn into fabric" effect rather than "pasted on top"
# ============================================================
# Alpha gradient at edges: full opaque in center, slight fade at outer 2px
alpha_fade = np.ones((PATCH_H, PATCH_W), dtype=np.float32)
# Outer 2px: slightly transparent (blends with suit underneath)
for d in range(2):
    alpha_val = 0.88 + d * 0.06  # 0.88, 0.94
    alpha_fade[d, :] *= alpha_val
    alpha_fade[PATCH_H-1-d, :] *= alpha_val
    alpha_fade[:, d] *= alpha_val
    alpha_fade[:, PATCH_W-1-d] *= alpha_val
pa[:,:,3] = alpha_fade * 255

patch_img = Image.fromarray(np.clip(pa, 0, 255).astype(np.uint8), "RGBA")

# ============================================================
# CYLINDRICAL WARP using scipy.ndimage.map_coordinates
# ============================================================
patch_np = np.array(patch_img, dtype=np.float32)

cy_center = PATCH_H / 2.0
cx_center = PATCH_W / 2.0
out_y_grid, out_x_grid = np.mgrid[0:PATCH_H, 0:PATCH_W].astype(np.float32)

nx = (out_x_grid - cx_center) / (PATCH_W / 2.0)
ny = (out_y_grid - cy_center) / (PATCH_H / 2.0)

# Cylindrical warp: barrel (pincushion for inverse = patch curves around cylinder)
# Output pixel at (nx, ny) samples from flat at (nx', ny')
# Stronger warp for visible effect
warp_h = 0.12  # horizontal (main cylinder)
warp_v = 0.04  # vertical (slight vertical curve too)
flat_nx = nx * (1 + warp_h * nx*nx)
flat_ny = ny * (1 + warp_v * ny*ny)

# Convert to pixel coordinates in source (flat patch)
src_x = flat_nx * (PATCH_W / 2.0) + cx_center
src_y = flat_ny * (PATCH_H / 2.0) + cy_center

src_x = np.clip(src_x, 0, PATCH_W - 1)
src_y = np.clip(src_y, 0, PATCH_H - 1)

# Apply warp to each channel
warped = np.zeros_like(patch_np)
for ch in range(4):
    warped[:,:,ch] = map_coordinates(patch_np[:,:,ch], [src_y, src_x], 
                                      order=1, mode='nearest')

patch_final = Image.fromarray(np.clip(warped, 0, 255).astype(np.uint8), "RGBA")
print("Cylindrical warp done")

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

# Soft shadow under patch (bottom + right edge emphasis)
sh_np = np.zeros((H, W, 4), dtype=np.float32)
shadow_data = np.zeros((PATCH_H + 14, PATCH_W + 14, 4), dtype=np.float32)

# Gradient shadow: stronger at bottom/right (indicates light from upper-left)
for y in range(PATCH_H + 14):
    for x in range(PATCH_W + 14):
        # Distance from patch edges
        dy = max(0, y - PATCH_H)  # 0 inside/top, >0 below
        dx = max(0, x - PATCH_W)  # 0 inside/left, >0 right
        dist = (dy**2 + dx**2)**0.5
        alpha = max(0, 28 - dist * 4.5)
        shadow_data[y, x, 3] = alpha

shadow_img = Image.fromarray(np.clip(shadow_data, 0, 255).astype(np.uint8), "RGBA")
shadow_img = shadow_img.filter(ImageFilter.GaussianBlur(2.5))

sh_layer = Image.new("RGBA", (W, H), (0,0,0,0))
sh_layer.paste(shadow_img, (PASTE_X - 2, PASTE_Y - 2), shadow_img)
result = Image.alpha_composite(result, sh_layer)

# Patch
pl = Image.new("RGBA", (W, H), (0,0,0,0))
pl.paste(patch_final, (PASTE_X, PASTE_Y), patch_final)
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((590, 615, 895, 890))
ctx.save("tatiana-patch-v2-context.jpg", quality=95)

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