#!/usr/bin/env python3
"""
Tatiana's patch v5 — adds cylindrical warp, dimension shadow, and merrowed border.
"""

from PIL import Image, ImageDraw, ImageFilter, ImageTransform
import numpy as np

np.random.seed(77)

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 = 82, 64
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()

# ============================================================
# BUILD PATCH FLAT (larger, then warp)
# ============================================================
PW2, PH2 = PATCH_W + 20, PATCH_H + 16  # slightly oversized for warp room
lum_scale = np.clip(avg_lum / 185.0, 0.88, 1.08)
fab_r = int(np.clip(224 * lum_scale, 185, 240))
fab_g = int(np.clip(218 * lum_scale, 180, 236))
fab_b = int(np.clip(206 * lum_scale, 172, 224))
print(f"Fabric: ({fab_r}, {fab_g}, {fab_b})")

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

# Canvas weave
y_idx = np.arange(PH2)[:, None].astype(np.float32)
x_idx = np.arange(PW2)[None, :].astype(np.float32)
warp_t  = np.cos(x_idx * np.pi / 2.0) * 3.2
weft_t  = np.cos(y_idx * np.pi / 2.0) * 2.8
twill   = np.sin((x_idx + y_idx) * np.pi / 2.5) * 2.0
noise   = np.random.normal(0, 2.2, (PH2, PW2, 3))
fabric[:,:,:3] = np.clip(fabric[:,:,:3] + (warp_t + weft_t + twill)[:,:,np.newaxis] + noise, 0, 255)

# Arm lighting
arm2 = base_rgb[PASTE_Y-8:PASTE_Y+PH2+8, PASTE_X-10:PASTE_X+PW2+10]
if arm2.shape[0] >= PH2 and arm2.shape[1] >= PW2:
    arm2_crop = arm2[:PH2, :PW2]
else:
    arm2_crop = arm[:PH2,:PW2] if arm.shape[0] >= PH2 else arm
lum2 = arm2_crop[:,:,0]*0.299 + arm2_crop[:,:,1]*0.587 + arm2_crop[:,:,2]*0.114
lmod = np.clip(0.87 + 0.26 * lum2 / 255.0, 0.77, 1.12)
fabric[:,:,:3] = np.clip(fabric[:,:,:3] * lmod[:,:,np.newaxis], 0, 255)

# Edge shadow
for d in range(5):
    fade = 1.0 - (5-d) * 0.017
    fabric[d,:,:3] *= fade; fabric[PH2-1-d,:,:3] *= fade
    fabric[:,d,:3] *= fade; fabric[:,PW2-1-d,:3] *= fade
fabric[:,:,3] = 255

# ============================================================
# LOGO → EMBROIDERY
# ============================================================
logo_area_w = PW2 - 28
logo_area_h = PH2 - 22
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_px = ((ld[:,:,0]*0.299 + ld[:,:,1]*0.587 + ld[:,:,2]*0.114) < 120) & (ld[:,:,3] > 40)

yi = np.arange(lh)[:, None].astype(np.float32)
xi = np.arange(lw)[None, :].astype(np.float32)
# Satin stitch at 35° angle
stdir = yi * 0.70 + xi
tphase = (stdir % 2.2) / 2.2
tval = 8 + np.sin(tphase * np.pi) * 36 + np.random.normal(0, 1.5, (lh, lw))
tval = np.clip(tval, 0, 52)

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.62
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))

lx = (PW2 - lw) // 2
ly = (PH2 - lh) // 2

patch_img = Image.fromarray(np.clip(fabric, 0, 255).astype(np.uint8), "RGBA")
patch_img.paste(emb_img, (lx, ly), emb_img)

# ============================================================
# SOLID SATIN-STITCH BORDER (filled band, not dashes)
# ============================================================
pa = np.array(patch_img, dtype=np.float32)
BW = 5
bc_dark  = np.array([20, 16, 10], dtype=np.float32)
bc_light = np.array([38, 30, 20], dtype=np.float32)

yi2 = np.arange(PH2)[:, None].astype(np.float32)
xi2 = np.arange(PW2)[None, :].astype(np.float32)
hsheen = np.sin(xi2 * np.pi / 1.8) * 0.3 + 0.7  # horizontal satin
vsheen = np.sin(yi2 * np.pi / 1.8) * 0.3 + 0.7  # vertical satin

# Top/bottom
for ch in range(3):
    tb = (yi2 < BW) | (yi2 >= PH2 - BW)
    sb = (xi2 < BW) | (xi2 >= PW2 - BW)
    # Top & bottom (horizontal stitches)
    tb_only = tb
    pa[:,:,ch] = np.where(tb_only, bc_dark[ch] * hsheen + bc_light[ch] * (1-hsheen), pa[:,:,ch])
    # Sides (vertical stitches, don't overwrite corners already set)
    sb_only = sb & ~tb
    pa[:,:,ch] = np.where(sb_only, bc_dark[ch] * vsheen + bc_light[ch] * (1-vsheen), pa[:,:,ch])

# Outer edge highlight (merrowed edge has slight sheen at outermost stitch row)
pa[0,:,:3]     = np.clip(bc_dark * 1.5, 0, 255)
pa[PH2-1,:,:3] = np.clip(bc_dark * 0.75, 0, 255)
pa[:,0,:3]     = np.clip(bc_dark * 1.4, 0, 255)
pa[:,PW2-1,:3] = np.clip(bc_dark * 0.65, 0, 255)

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

# ============================================================
# CYLINDRICAL WARP — arm curves left to right
# ============================================================
# The arm is a rough cylinder; the patch wraps around it slightly.
# Simulate: compress the left/right edges slightly (barrel distortion inward)
# This makes the flat patch look like it's curved around the arm.

patch_np = np.array(patch_img, dtype=np.float32)
warped = np.zeros((PATCH_H, PATCH_W, 4), dtype=np.float32)

# Map each output pixel (warped) to input pixel (flat)
cy = PATCH_H / 2.0
cx = PATCH_W / 2.0
radius = PATCH_W * 0.9  # cylinder radius (larger = less curvature)

for out_y in range(PATCH_H):
    for out_x in range(PATCH_W):
        # Normalize to -1..1
        nx = (out_x - cx) / (PATCH_W / 2.0)
        ny = (out_y - cy) / (PATCH_H / 2.0)
        
        # Cylindrical warp: expand center, compress edges (horizontal cylinder)
        # Inverse mapping: where to sample from flat patch
        # A point at normalized x on cylinder maps to angle theta = asin(x / r)
        # But we want: flat_x = radius * sin(out_x_angle)
        # For subtle effect:
        warp_strength = 0.06  # 0=no warp, 0.15=noticeable
        flat_nx = nx * (1 + warp_strength * nx * nx)  # barrel: edges squeeze in
        flat_ny = ny * (1 + warp_strength * 0.3 * ny * ny)
        
        # Convert back to pixel coords in the larger source patch
        src_x = flat_nx * (PW2 / 2.0) + PW2 / 2.0
        src_y = flat_ny * (PH2 / 2.0) + PH2 / 2.0
        
        # Bilinear sample
        sx, sy = int(src_x), int(src_y)
        if 0 <= sx < PW2-1 and 0 <= sy < PH2-1:
            fx, fy = src_x - sx, src_y - sy
            warped[out_y, out_x] = (
                patch_np[sy, sx] * (1-fx) * (1-fy) +
                patch_np[sy, sx+1] * fx * (1-fy) +
                patch_np[sy+1, sx] * (1-fx) * fy +
                patch_np[sy+1, sx+1] * fx * fy
            )
        elif 0 <= sx < PW2 and 0 <= sy < PH2:
            warped[out_y, out_x] = patch_np[sy, sx]

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

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

# Drop shadow (the lifted edge of the patch casts shadow on the suit)
sh = Image.new("RGBA", (W, H), (0,0,0,0))
for i in range(7):
    a = max(0, 33 - i*5)
    block = Image.new("RGBA", (PATCH_W + i, PATCH_H + i), (5, 4, 3, a))
    sp = Image.new("RGBA", (W, H), (0,0,0,0))
    sp.paste(block, (PASTE_X + 1 - i//2, PASTE_Y + 2 + i//2), block)
    sh = Image.alpha_composite(sh, sp)
sh = sh.filter(ImageFilter.GaussianBlur(2.2))
result = Image.alpha_composite(result, sh)

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)

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.")
