#!/usr/bin/env python3
"""
Tatiana's PREMIUM embroidered patch composite.
Focus on thread simulation, brightness, physical realism.
"""

from PIL import Image, ImageDraw, ImageFilter, ImageChops, ImageEnhance
import numpy as np

np.random.seed(17)

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 = 80, 62
PASTE_X, PASTE_Y = 712, 740

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

# ============================================================
# PATCH FABRIC — with guaranteed min brightness
# ============================================================
# Force patch to be visibly lighter than shadow arm (it's white fabric!)
# Base off-white: ~210-220 range even in shadow (real white fabric behavior)
# Modulate gently by local lighting (not color-match)
lum_norm = np.clip(arm_lum.mean() / 160.0, 0.75, 1.15)

fab_base = np.array([222, 216, 204], dtype=np.float32) * lum_norm
fab_base = np.clip(fab_base, 165, 240)
fab_r, fab_g, fab_b = int(fab_base[0]), int(fab_base[1]), int(fab_base[2])
print(f"Patch 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)

# Fine weave texture
noise = np.random.normal(0, 4.5, (PATCH_H, PATCH_W, 3))
# Coarser warp/weft pattern (fabric grain)
grain_y = np.sin(np.arange(PATCH_H)[:, None] * 6.28 * 2 / 3) * 2.5
grain_x = np.sin(np.arange(PATCH_W)[None, :] * 6.28 * 2 / 4) * 2.5
grain = grain_y + grain_x
fabric[:,:,:3] += noise + grain[:,:,np.newaxis]

# Local lighting gradient applied to fabric
arm_lum_norm = arm_lum / 255.0
lum_mod = 0.72 + 0.55 * arm_lum_norm
lum_mod = np.clip(lum_mod, 0.60, 1.18)[:,:,np.newaxis]
fabric[:,:,:3] = np.clip(fabric[:,:,:3] * lum_mod, 0, 255)

# Patch edge shadow (stitching raises patch slightly off surface)
edge_shadow = np.zeros((PATCH_H, PATCH_W), dtype=np.float32)
for d in range(6):
    val = (6 - d) * 0.02
    edge_shadow[d,:] += val
    edge_shadow[PATCH_H-1-d,:] += val
    edge_shadow[:,d] += val
    edge_shadow[:,PATCH_W-1-d] += val
edge_shadow = np.clip(edge_shadow, 0, 0.12)
fabric[:,:,:3] *= (1 - edge_shadow[:,:,np.newaxis])
fabric[:,:,3] = 255

patch = Image.fromarray(np.clip(fabric, 0, 255).astype(np.uint8), "RGBA")

# ============================================================
# EMBROIDERY THREAD SIMULATION
# ============================================================
logo_area_w = PATCH_W - 22
logo_area_h = PATCH_H - 18
logo_copy = logo_raw.copy()
logo_copy.thumbnail((logo_area_w, logo_area_h), Image.LANCZOS)
lw, lh = logo_copy.size

logo_arr = np.array(logo_copy, dtype=np.float32)
lum_logo = logo_arr[:,:,0]*0.299 + logo_arr[:,:,1]*0.587 + logo_arr[:,:,2]*0.114
logo_px = (lum_logo < 128) & (logo_arr[:,:,3] > 40)

# --- Thread lighting simulation ---
# Satin stitch: threads run in rows (horizontal), each thread is a small cylinder
# Top of cylinder = highlight, edges = darker
y_coords = np.arange(lh)[:, None]
x_coords = np.arange(lw)[None, :]

# Each thread row = 2px high, simulating individual thread bumps
thread_period = 2.2
# Brightness modulation: peak at top of each thread, dark at bottom
thread_phase = (y_coords % thread_period) / thread_period  # 0..1
# Cylinder top (0.3) = bright, cylinder bottom (0.8) = dark
thread_bright = np.cos((thread_phase - 0.3) * np.pi) * 0.5 + 0.5  # 0..1

# Also slight side sheen (highlight on one side of each thread)
x_phase = (x_coords % 3.0) / 3.0
x_sheen = np.sin(x_phase * np.pi) * 0.3

# Base thread color range: 8 (dark) to 48 (highlight)
thread_val = 8 + thread_bright * 32 + x_sheen * 12
thread_val = np.clip(thread_val, 4, 55)

# Add micro-noise for fiber variation
fiber_noise = np.random.normal(0, 2, (lh, lw))
thread_val += fiber_noise
thread_val = np.clip(thread_val, 0, 65)

# Build embroidery RGBA
emb = np.zeros((lh, lw, 4), dtype=np.float32)
emb[:,:,0] = logo_px * thread_val * 0.95
emb[:,:,1] = logo_px * thread_val * 0.85
emb[:,:,2] = logo_px * thread_val * 0.65
emb[:,:,3] = logo_px * 242  # high opacity

# Slight shadow/depth: where logo pixel meets non-logo, add tiny shadow on non-logo side
from scipy.ndimage import binary_dilation
try:
    from scipy.ndimage import binary_dilation as dilate
    logo_dilated = dilate(logo_px, iterations=1)
    shadow_border = logo_dilated & ~logo_px
    emb[shadow_border, 0] = emb[shadow_border, 0] * 0 + fab_r * 0.82
    emb[shadow_border, 1] = emb[shadow_border, 1] * 0 + fab_g * 0.82
    emb[shadow_border, 2] = emb[shadow_border, 2] * 0 + fab_b * 0.82
    emb[shadow_border, 3] = 140  # semi-transparent shadow
    print("Thread depth shadows: applied")
except ImportError:
    print("scipy not available, skipping thread depth")

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

# Center on patch
lx = (PATCH_W - lw) // 2
ly = (PATCH_H - lh) // 2
patch.paste(emb_img, (lx, ly), emb_img)

# ============================================================
# STITCH BORDER — realistic double-stitch
# ============================================================
draw = ImageDraw.Draw(patch)
bm = 5
dark_thread = (30, 24, 16, 220)
mid_thread  = (55, 45, 30, 170)

def stitches(d, x1, y1, x2, y2, col, orient, sl=3, sg=3):
    if orient == 'h':
        for x in range(x1, x2, sl + sg):
            d.line([(x, y1), (min(x+sl, x2), y1)], fill=col, width=1)
    else:
        for y in range(y1, y2, sl + sg):
            d.line([(x1, y), (x1, min(y+sl, y2))], fill=col, width=1)

# Outer stitch row
stitches(draw, bm,           bm,           PATCH_W-bm, bm,           dark_thread, 'h')
stitches(draw, bm,           PATCH_H-bm-1, PATCH_W-bm, PATCH_H-bm-1,dark_thread, 'h')
stitches(draw, bm,           bm,           bm,          PATCH_H-bm,  dark_thread, 'v')
stitches(draw, PATCH_W-bm-1, bm,           PATCH_W-bm-1,PATCH_H-bm,  dark_thread, 'v')

# Inner stitch row (double border)
ib = bm + 4
stitches(draw, ib,           ib,           PATCH_W-ib, ib,           mid_thread, 'h', sl=2, sg=2)
stitches(draw, ib,           PATCH_H-ib-1, PATCH_W-ib, PATCH_H-ib-1,mid_thread, 'h', sl=2, sg=2)
stitches(draw, ib,           ib,           ib,          PATCH_H-ib,  mid_thread, 'v', sl=2, sg=2)
stitches(draw, PATCH_W-ib-1, ib,           PATCH_W-ib-1,PATCH_H-ib,  mid_thread, 'v', sl=2, sg=2)

# Outermost hairline (merrowed edge simulation)
draw.rectangle([1, 1, PATCH_W-2, PATCH_H-2], outline=(45, 36, 25, 200), width=1)

# Final integration blur
patch = patch.filter(ImageFilter.GaussianBlur(0.3))

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

# Directional drop shadow
sh = Image.new("RGBA", (W, H), (0,0,0,0))
for i in range(8):
    a = max(0, 36 - i * 5)
    block = Image.new("RGBA", (PATCH_W+i*2, PATCH_H+i*2), (6, 5, 4, a))
    block = block.filter(ImageFilter.GaussianBlur(1))
    sh.paste(block, (PASTE_X - i + 2, PASTE_Y - i + 3), block)
result = Image.alpha_composite(result, sh)

# Patch
pl = Image.new("RGBA", (W, H), (0,0,0,0))
pl.paste(patch, (PASTE_X, PASTE_Y), patch)
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((595, 620, 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.")
