#!/usr/bin/env python3
"""
Tatiana's patch composite v4 — SOLID satin-stitch border, canvas weave texture, 
brighter off-white fabric. The patch must look physically real.
"""

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

np.random.seed(99)

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()
print(f"Arm lum: {avg_lum:.1f}")

# ============================================================
# PATCH FABRIC — canvas weave texture, forced bright off-white
# ============================================================
# A real patch on a spacesuit is sewn-on white twill — it's bright
# even if the arm around it is medium-dark (same as a white sticker
# in partial shadow: it's still WHITE, just slightly shadowed)

# Force a minimum brightness: patch won't go below 185 even in shadow
min_bright = 182
lum_scale = np.clip(avg_lum / 185.0, 0.85, 1.10)
fab_r = int(np.clip(226 * lum_scale, min_bright, 240))
fab_g = int(np.clip(220 * lum_scale, min_bright - 4, 236))
fab_b = int(np.clip(208 * lum_scale, min_bright - 10, 224))
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)

# Canvas weave — twill pattern: diagonal ribs at 45°
y_idx = np.arange(PATCH_H)[:, None]
x_idx = np.arange(PATCH_W)[None, :]
# Warp threads (vertical, every 3px)
warp = np.cos((x_idx * 2.0) * np.pi / 3.0) * 3.5
# Weft threads (horizontal, every 3px)
weft = np.cos((y_idx * 2.0) * np.pi / 3.0) * 3.0
# Diagonal twill character
twill = np.sin(((x_idx + y_idx) * 1.5) * np.pi / 4.0) * 2.5
canvas = (warp + weft + twill)[:,:,np.newaxis]
# Fine fiber noise
noise = np.random.normal(0, 2.5, (PATCH_H, PATCH_W, 3))
fabric[:,:,:3] = np.clip(fabric[:,:,:3] + canvas * np.array([1,1,1]) + noise, 0, 255)

# Lighting from suit surface (subtle — patch is its own brighter material)
lum_norm = arm_lum_map / 255.0
light_mod = 0.85 + 0.28 * lum_norm  # very gentle: 0.85 to 1.13 range
fabric[:,:,:3] *= light_mod[:,:,np.newaxis]
fabric[:,:,:3] = np.clip(fabric[:,:,:3], 0, 255)

# Edge shadow
for d in range(5):
    fade = 1.0 - (5-d) * 0.018
    for edge in [slice(d, d+1), slice(PATCH_H-d-1, PATCH_H-d)]:
        fabric[edge,:,:3] *= fade
    for edge in [slice(None, None), slice(None, None)]:
        fabric[:, d, :3] *= fade
        fabric[:, PATCH_W-1-d, :3] *= fade
fabric[:,:,3] = 255
patch = Image.fromarray(np.clip(fabric, 0, 255).astype(np.uint8), "RGBA")

# ============================================================
# LOGO → EMBROIDERY (satin stitch)
# ============================================================
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}")

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

# Satin-stitch simulation: threads run at ~30° angle
y_idx = np.arange(lh)[:, np.newaxis].astype(np.float32)
x_idx = np.arange(lw)[np.newaxis, :].astype(np.float32)
# Thread direction (diagonal satin stitch at 30°)
stitch_dir = y_idx * 0.577 + x_idx  # tan(30°) = 0.577
thread_period = 2.5
thread_phase = (stitch_dir % thread_period) / thread_period
thread_bright = np.sin(thread_phase * np.pi)  # 0→1→0 per thread
# High = highlight, low = shadow between threads
# Dark thread range: 8 to 45
thread_val = 8 + thread_bright * 38
thread_noise = np.random.normal(0, 1.8, (lh, lw))
thread_val = np.clip(thread_val + thread_noise, 0, 55)

emb = np.zeros((lh, lw, 4), dtype=np.float32)
emb[:,:,0] = logo_px * thread_val * 0.88
emb[:,:,1] = logo_px * thread_val * 0.78
emb[:,:,2] = logo_px * thread_val * 0.58
emb[:,:,3] = logo_px * 245

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

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

# ============================================================
# SATIN-STITCH BORDER — SOLID, not dashed
# ============================================================
patch_arr2 = np.array(patch, dtype=np.float32)

# Border width: 5px solid band all around (filled rectangle)
BW = 5  # border width in pixels
# Outer solid filled border (satin stitch band)
border_color_dark = np.array([22, 18, 11], dtype=np.float32)
border_color_light = np.array([35, 29, 18], dtype=np.float32)

# Create border mask
border_mask = np.zeros((PATCH_H, PATCH_W), dtype=bool)
border_mask[:BW, :] = True
border_mask[PATCH_H-BW:, :] = True
border_mask[:, :BW] = True
border_mask[:, PATCH_W-BW:] = True

# Fill border with satin-stitch texture
# Horizontal stitches on top/bottom, vertical on sides
y_arr = np.arange(PATCH_H)[:, None]
x_arr = np.arange(PATCH_W)[None, :]

# Top/bottom: horizontal stitch texture
horiz_texture = np.sin(x_arr * np.pi / 1.5) * 0.25 + 0.75  # subtle brightness variation
vert_texture  = np.sin(y_arr * np.pi / 1.5) * 0.25 + 0.75

# Top border
tb_mask = np.zeros((PATCH_H, PATCH_W), dtype=bool)
tb_mask[:BW, :] = True
tb_mask[PATCH_H-BW:, :] = True
# Side border
sb_mask = np.zeros((PATCH_H, PATCH_W), dtype=bool)
sb_mask[:, :BW] = True
sb_mask[:, PATCH_W-BW:] = True

# Apply border
for ch in range(3):
    # Top & bottom: horizontal satin stitch
    patch_arr2[:,:,ch] = np.where(
        tb_mask,
        border_color_dark[ch] * horiz_texture + border_color_light[ch] * (1 - horiz_texture),
        patch_arr2[:,:,ch]
    )
    # Sides: vertical satin stitch  
    patch_arr2[:,:,ch] = np.where(
        sb_mask & ~tb_mask,  # sides only (corners already set)
        border_color_dark[ch] * vert_texture + border_color_light[ch] * (1 - vert_texture),
        patch_arr2[:,:,ch]
    )

# Outermost row: slightly lighter (highlight at top edge)
patch_arr2[0, :, :3] = np.clip(border_color_dark * 1.4, 0, 255)
patch_arr2[PATCH_H-1, :, :3] = np.clip(border_color_dark * 0.8, 0, 255)
patch_arr2[:, 0, :3] = np.clip(border_color_dark * 1.3, 0, 255)
patch_arr2[:, PATCH_W-1, :3] = np.clip(border_color_dark * 0.7, 0, 255)

# Slight border softness (threads have slight anti-aliased edge)
patch = Image.fromarray(np.clip(patch_arr2, 0, 255).astype(np.uint8), "RGBA")
# Very slight blur only at inner edge of border
# Keep sharp outer edge (merrowed patches have clean outer edge)
patch = patch.filter(ImageFilter.GaussianBlur(0.25))

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

# Shadow layer
sh = Image.new("RGBA", (W, H), (0,0,0,0))
# Soft directional shadow (light from upper-right: shadow goes lower-left)
for i in range(6):
    a = max(0, 32 - i * 5)
    block = Image.new("RGBA", (PATCH_W + i*2, PATCH_H + i*2), (5, 4, 3, a))
    sh_offset = Image.new("RGBA", (W, H), (0,0,0,0))
    sh_offset.paste(block, (PASTE_X - i - 1, PASTE_Y + i + 1), block)
    sh = Image.alpha_composite(sh, sh_offset)
sh = sh.filter(ImageFilter.GaussianBlur(1.8))
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((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.")
