from PIL import Image, ImageDraw, ImageFont, ImageEnhance, ImageFilter
from pathlib import Path
import textwrap

SRC = Path('/home/spazio/.hermes/cache/images')
OUT = Path('/home/spazio/.hermes/media/cosmos_luminosity_carousel')
OUT.mkdir(parents=True, exist_ok=True)

W, H = 1080, 1350
SERIF = '/usr/share/fonts/truetype/freefont/FreeSerif.ttf'
SERIF_B = '/usr/share/fonts/truetype/freefont/FreeSerifBold.ttf'
SANS = '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf'
SANS_B = '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf'
CREAM = (255, 250, 240, 255)
WHITE = (255, 255, 255, 255)
LILAC = (218, 196, 255, 255)
GOLD = (255, 211, 120, 255)

slides = [
    {
        'img':'img_3cc4464ceb2c.jpg', 'num':'01', 'kind':'cover',
        'title':'5 вопросов,\nкоторые возвращают\nлёгкость',
        'body':'когда жизнь стала слишком тяжёлой',
    },
    {
        'img':'img_7b02ae89a282.jpg', 'num':'02',
        'title':'Что здесь является фактом,\nа что — моим старым выводом?',
        'body':'Иногда тяжесть создаёт история, которую мы уже много раз рассказывали себе об этой ситуации.\n\nОтдели то, что происходит сейчас, от того, что когда-то решила об этом ты.'
    },
    {
        'img':'img_377dc32aec1e.jpg', 'num':'03',
        'title':'Я сейчас расслаблена\nили напряжена?',
        'body':'Сначала заметь состояние тела.\n\nА затем спроси:\n«Каким образом я могу расслабиться ещё на 1%?»\n\nИногда одного процента достаточно, чтобы увидеть ситуацию иначе.'
    },
    {
        'img':'img_c8d51015a0b1.jpg', 'num':'04',
        'title':'Какой вариант я пока\nдаже не рассматриваю?',
        'body':'Старое решение снова показывает знакомые маршруты.\n\nОткрытый вопрос возвращает внимание туда, где могут находиться другие способы, люди, ресурсы и неожиданные ходы.'
    },
    {
        'img':'img_8e6f69e42a10.jpg', 'num':'05',
        'title':'Где здесь уже есть лёгкость,\nкоторую я пока не замечаю?',
        'body':'Один простой шаг. Один честный разговор. Чья-то помощь. Пауза. Другой ритм.\n\nЗамеченное становится доступным.'
    },
    {
        'img':'img_3649c3a605f4.jpg', 'num':'06',
        'title':'Какой следующий живой ход\nоткрывается сейчас?',
        'body':'Тебе не требуется решить всю жизнь за один день.\n\nНайди действие, которое доступно твоему телу, состоянию и реальным обстоятельствам сейчас.'
    },
    {
        'img':'img_6fea458148b1.jpg', 'num':'07', 'kind':'final',
        'title':'Вопрос — живой интерфейс\nмежду вниманием,\nсостоянием и выбором.',
        'body':'Он меняет направление поиска. Ты начинаешь замечать то, что раньше проходило мимо.\n\nА новое замеченное постепенно становится новым выбором, действием и доступной реальностью.',
        'cta':'Сохрани и вернись к этим вопросам,\nкогда снова станет слишком тяжело.'
    },
]

def font(path, size): return ImageFont.truetype(path, size)

def cover_crop(im):
    # crop 9:16 source to 4:5, favor center and preserve upper illuminated area
    target_ratio = W/H
    r = im.width/im.height
    if r < target_ratio:
        nh = int(im.width/target_ratio)
        top = max(0, min(im.height-nh, int((im.height-nh)*0.44)))
        im = im.crop((0, top, im.width, top+nh))
    else:
        nw = int(im.height*target_ratio)
        left = (im.width-nw)//2
        im = im.crop((left,0,left+nw,im.height))
    return im.resize((W,H), Image.Resampling.LANCZOS)

def wrap_pixels(draw, text, fnt, max_width):
    paragraphs = text.split('\n')
    out = []
    for para in paragraphs:
        if not para.strip():
            out.append('')
            continue
        words = para.split()
        line = words[0]
        for word in words[1:]:
            test = line + ' ' + word
            if draw.textlength(test, font=fnt) <= max_width:
                line = test
            else:
                out.append(line)
                line = word
        out.append(line)
    return '\n'.join(out)

def multiline_height(draw, text, fnt, spacing):
    box = draw.multiline_textbbox((0,0), text, font=fnt, spacing=spacing)
    return box[3] - box[1]

def add_gradient(base, top_alpha=100, bottom_alpha=205):
    ov=Image.new('RGBA',(W,H),(0,0,0,0)); px=ov.load()
    for y in range(H):
        # dark at top and especially bottom; slightly open in middle
        t=y/(H-1)
        a=int(top_alpha*(1-t)+bottom_alpha*t)
        if .28<t<.58: a=max(55,a-45)
        for x in range(W): px[x,y]=(5,7,14,a)
    return Image.alpha_composite(base.convert('RGBA'),ov)

def rounded_panel(im, box, alpha=165):
    layer=Image.new('RGBA',(W,H),(0,0,0,0)); d=ImageDraw.Draw(layer)
    d.rounded_rectangle(box, radius=32, fill=(6,7,13,alpha), outline=(255,255,255,35), width=2)
    return Image.alpha_composite(im,layer)

def draw_brand(draw, num):
    draw.text((72,58),'COSMOS LUMINOSITY  •  ПРАКТИКА',font=font(SANS_B,22),fill=LILAC)
    draw.text((975,58),num,font=font(SANS_B,22),fill=(255,255,255,180),anchor='ra')
    draw.line((72,99,1008,99),fill=(255,255,255,65),width=2)

def render(s, idx):
    im=cover_crop(Image.open(SRC/s['img']).convert('RGB'))
    im=ImageEnhance.Color(im).enhance(0.94)
    im=ImageEnhance.Contrast(im).enhance(1.07)
    im=add_gradient(im,80 if s.get('kind')=='cover' else 105,215)
    draw=ImageDraw.Draw(im)
    draw_brand(draw,s['num'])

    if s.get('kind')=='cover':
        # stronger bottom panel, photo remains luminous
        im=rounded_panel(im,(56,690,1024,1255),150); draw=ImageDraw.Draw(im)
        draw.text((92,735),'COSMOS LUMINOSITY',font=font(SANS_B,24),fill=GOLD)
        draw.multiline_text((92,795),s['title'],font=font(SERIF_B,72),fill=CREAM,spacing=2)
        draw.text((94,1118),s['body'],font=font(SANS,31),fill=(255,255,255,225))
        draw.text((94,1200),'сохрани для момента, когда захочется больше воздуха',font=font(SANS,20),fill=(255,255,255,155))
    else:
        panel_top=560 if idx not in (2,6) else 515
        if s.get('kind')=='final': panel_top=445
        im=rounded_panel(im,(52,panel_top,1028,1270),172); draw=ImageDraw.Draw(im)
        y=panel_top+48
        title_font=font(SERIF_B,52 if s.get('kind')!='final' else 50)
        title_text=wrap_pixels(draw,s['title'],title_font,865)
        draw.multiline_text((88,y),title_text,font=title_font,fill=CREAM,spacing=4)
        y += multiline_height(draw,title_text,title_font,4)+34
        draw.line((88,y,170,y),fill=GOLD,width=5); y+=30
        body_font=font(SANS,29 if s.get('kind')!='final' else 27)
        body_text=wrap_pixels(draw,s['body'],body_font,865)
        draw.multiline_text((88,y),body_text,font=body_font,fill=(255,255,255,230),spacing=10)
        if s.get('cta'):
            y2=1110
            draw.rounded_rectangle((82,y2,998,1235),radius=22,fill=(135,92,190,150))
            cta_font=font(SANS_B,24)
            cta_text=wrap_pixels(draw,s['cta'],cta_font,825)
            draw.multiline_text((112,y2+23),cta_text,font=cta_font,fill=WHITE,spacing=5)
    # footer tiny
    draw.text((1008,1315),'cosmos luminosity',font=font(SANS,17),fill=(255,255,255,120),anchor='ra')
    out=OUT/f'{idx:02d}_cosmos_luminosity.jpg'
    im.convert('RGB').save(out,quality=95,subsampling=0)
    return out

outs=[]
for i,s in enumerate(slides,1): outs.append(render(s,i))

# contact sheet
thumbs=[]
for p in outs:
    t=Image.open(p); t.thumbnail((270,338)); thumbs.append(t.copy())
sheet=Image.new('RGB',(270*4,338*2),(20,20,24))
for i,t in enumerate(thumbs): sheet.paste(t,((i%4)*270,(i//4)*338))
sheet.save(OUT/'00_preview_contact_sheet.jpg',quality=92)
print('\n'.join(str(p) for p in outs))
print(OUT/'00_preview_contact_sheet.jpg')
