from __future__ import annotations

from pathlib import Path
from typing import Iterable

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


ROOT = Path("/home/nikola/codex-cli/projects/potkrovlje-dizajn-2026")
OUT = ROOT / "za-pregled" / "warm-nordic-highres"
OUT.mkdir(parents=True, exist_ok=True)


def font(size: int, bold: bool = False) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:
    options = [
        "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" if bold else "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
        "/usr/share/fonts/truetype/liberation2/LiberationSans-Bold.ttf" if bold else "/usr/share/fonts/truetype/liberation2/LiberationSans-Regular.ttf",
    ]
    for path in options:
        try:
            return ImageFont.truetype(path, size)
        except OSError:
            continue
    return ImageFont.load_default()


TITLE = font(66, bold=True)
HEAD = font(38, bold=True)
TEXT = font(28)
SMALL = font(22)


def add_noise(arr: np.ndarray, strength: float) -> np.ndarray:
    noise = np.random.normal(0, strength, arr.shape).astype(np.float32)
    out = np.clip(arr.astype(np.float32) + noise, 0, 255)
    return out.astype(np.uint8)


def vertical_gradient(size: tuple[int, int], top: tuple[int, int, int], bottom: tuple[int, int, int]) -> Image.Image:
    w, h = size
    base = np.zeros((h, w, 3), dtype=np.uint8)
    for i in range(h):
        t = i / max(h - 1, 1)
        base[i, :, :] = [int(top[c] * (1 - t) + bottom[c] * t) for c in range(3)]
    return Image.fromarray(base, "RGB")


def wood_texture(size: tuple[int, int], base=(194, 159, 120), dark=(125, 90, 58), direction: str = "horizontal") -> Image.Image:
    w, h = size
    axis = w if direction == "horizontal" else h
    img = np.zeros((h, w, 3), dtype=np.uint8)
    grain = np.zeros(axis, dtype=np.float32)
    phase = np.random.uniform(0, np.pi)
    for i in range(axis):
        t = i / axis
        wave = (
            0.55 * np.sin(t * 28 + phase)
            + 0.22 * np.sin(t * 91 + 0.7)
            + 0.18 * np.sin(t * 160 + 1.9)
        )
        grain[i] = wave
    grain += np.random.normal(0, 0.09, axis)
    grain = (grain - grain.min()) / (grain.max() - grain.min() + 1e-6)
    if direction == "horizontal":
        val = np.tile(grain, (h, 1))
    else:
        val = np.tile(grain[:, None], (1, w))
    for c in range(3):
        img[:, :, c] = (dark[c] * (1 - val) + base[c] * val).astype(np.uint8)
    img = add_noise(img, 5)
    return Image.fromarray(img, "RGB").filter(ImageFilter.GaussianBlur(0.6))


def concrete_texture(size: tuple[int, int], base=(194, 190, 183)) -> Image.Image:
    w, h = size
    arr = np.full((h, w, 3), base, dtype=np.uint8)
    arr = add_noise(arr, 12)
    specks = np.random.uniform(0, 1, (h, w))
    arr[specks > 0.995] = np.clip(arr[specks > 0.995] - 30, 0, 255)
    return Image.fromarray(arr, "RGB").filter(ImageFilter.GaussianBlur(0.4))


def brick_texture(size: tuple[int, int]) -> Image.Image:
    w, h = size
    img = Image.new("RGB", size, (206, 191, 177))
    draw = ImageDraw.Draw(img)
    brick_h = 42
    brick_w = 105
    shades = [(163, 111, 84), (174, 122, 91), (145, 97, 74), (186, 133, 99)]
    for row, y in enumerate(range(0, h, brick_h)):
        offset = 0 if row % 2 == 0 else brick_w // 2
        for x in range(-offset, w, brick_w):
            c = shades[(row + x // max(brick_w, 1)) % len(shades)]
            inset = 4
            draw.rounded_rectangle(
                (x + inset, y + inset, x + brick_w - 8, y + brick_h - 6),
                radius=3,
                fill=c,
            )
    return img.filter(ImageFilter.GaussianBlur(0.2))


def linen_texture(size: tuple[int, int], base=(223, 213, 198), accent=(190, 177, 159)) -> Image.Image:
    w, h = size
    arr = np.zeros((h, w, 3), dtype=np.uint8)
    for y in range(h):
        for x in range(w):
            mix = 0.55 + 0.25 * np.sin(x / 12.0) + 0.2 * np.sin(y / 14.0)
            mix = max(0.0, min(1.0, mix))
            arr[y, x] = [int(base[c] * mix + accent[c] * (1 - mix)) for c in range(3)]
    arr = add_noise(arr, 9)
    return Image.fromarray(arr, "RGB").filter(ImageFilter.GaussianBlur(0.5))


def slat_texture(size: tuple[int, int]) -> Image.Image:
    w, h = size
    base = Image.new("RGB", size, (74, 67, 62))
    draw = ImageDraw.Draw(base)
    wood = wood_texture((46, h), base=(172, 136, 94), dark=(110, 76, 46), direction="vertical")
    for x in range(0, w, 56):
        base.paste(wood, (x + 8, 0))
    return base.filter(ImageFilter.GaussianBlur(0.3))


def rug_texture(size: tuple[int, int], base=(206, 197, 184), line=(161, 150, 132)) -> Image.Image:
    img = linen_texture(size, base, line)
    draw = ImageDraw.Draw(img)
    w, h = size
    for y in range(40, h, 58):
        draw.line((40, y, w - 40, y), fill=(line[0] - 8, line[1] - 8, line[2] - 8), width=2)
    return img


def repeat_texture(tex: Image.Image, size: tuple[int, int]) -> Image.Image:
    w, h = size
    tw, th = tex.size
    out = Image.new("RGB", size)
    for y in range(0, h, th):
        for x in range(0, w, tw):
            out.paste(tex, (x, y))
    return out


def find_perspective_coeffs(dst_pts, src_pts):
    matrix = []
    for (x, y), (u, v) in zip(dst_pts, src_pts):
        matrix.append([x, y, 1, 0, 0, 0, -u * x, -u * y])
        matrix.append([0, 0, 0, x, y, 1, -v * x, -v * y])
    a = np.array(matrix, dtype=np.float64)
    b = np.array(src_pts).reshape(8)
    coeffs = np.linalg.solve(a, b)
    return coeffs


def polygon_mask(size: tuple[int, int], pts: list[tuple[int, int]]) -> Image.Image:
    mask = Image.new("L", size, 0)
    ImageDraw.Draw(mask).polygon(pts, fill=255)
    return mask


def warp_texture_to_polygon(tex: Image.Image, polygon: list[tuple[int, int]], canvas_size: tuple[int, int]) -> tuple[Image.Image, Image.Image]:
    xs = [p[0] for p in polygon]
    ys = [p[1] for p in polygon]
    min_x, max_x = min(xs), max(xs)
    min_y, max_y = min(ys), max(ys)
    bw = max_x - min_x
    bh = max_y - min_y
    local = [(x - min_x, y - min_y) for x, y in polygon]
    src = [(0, 0), (tex.size[0], 0), (tex.size[0], tex.size[1]), (0, tex.size[1])]
    coeffs = find_perspective_coeffs(local, src)
    tiled = repeat_texture(tex, (bw + 20, bh + 20))
    warped = tiled.transform((bw, bh), Image.PERSPECTIVE, coeffs, Image.Resampling.BICUBIC)
    layer = Image.new("RGBA", canvas_size, (0, 0, 0, 0))
    mask = polygon_mask((bw, bh), local)
    layer.paste(warped.convert("RGBA"), (min_x, min_y), mask)
    canvas_mask = Image.new("L", canvas_size, 0)
    canvas_mask.paste(mask, (min_x, min_y))
    return layer, canvas_mask


def paste_polygon_texture(base: Image.Image, polygon: list[tuple[int, int]], tex: Image.Image, tint: tuple[int, int, int] | None = None, alpha: int = 255) -> None:
    layer, mask = warp_texture_to_polygon(tex, polygon, base.size)
    if tint is not None:
        tint_img = Image.new("RGBA", base.size, (*tint, 0))
        tint_img.putalpha(mask.point(lambda p: int(p * alpha / 255)))
        layer = Image.blend(layer, tint_img, 0.18)
    base.alpha_composite(layer)


def add_polygon_overlay(base: Image.Image, polygon: list[tuple[int, int]], color: tuple[int, int, int], opacity: int) -> None:
    overlay = Image.new("RGBA", base.size, (0, 0, 0, 0))
    draw = ImageDraw.Draw(overlay)
    draw.polygon(polygon, fill=(*color, opacity))
    base.alpha_composite(overlay)


def add_shadow(base: Image.Image, shape_box: tuple[int, int, int, int], offset=(18, 18), blur=24, opacity=80, radius=30) -> None:
    shadow = Image.new("RGBA", base.size, (0, 0, 0, 0))
    draw = ImageDraw.Draw(shadow)
    x1, y1, x2, y2 = shape_box
    draw.rounded_rectangle((x1 + offset[0], y1 + offset[1], x2 + offset[0], y2 + offset[1]), radius=radius, fill=(25, 24, 22, opacity))
    shadow = shadow.filter(ImageFilter.GaussianBlur(blur))
    base.alpha_composite(shadow)


def rounded_box(base: Image.Image, box, fill, outline=None, width=1, radius=28, texture: Image.Image | None = None) -> None:
    if texture is not None:
        mask = Image.new("L", base.size, 0)
        ImageDraw.Draw(mask).rounded_rectangle(box, radius=radius, fill=255)
        tex = repeat_texture(texture, base.size).convert("RGBA")
        tex.putalpha(mask)
        base.alpha_composite(tex)
    overlay = Image.new("RGBA", base.size, (0, 0, 0, 0))
    draw = ImageDraw.Draw(overlay)
    draw.rounded_rectangle(box, radius=radius, fill=fill, outline=outline, width=width)
    base.alpha_composite(overlay)


def add_text_panel(base: Image.Image, title: str, subtitle: str) -> None:
    panel = Image.new("RGBA", base.size, (0, 0, 0, 0))
    draw = ImageDraw.Draw(panel)
    draw.rounded_rectangle((56, 48, 1030, 220), radius=30, fill=(244, 239, 230, 220))
    draw.text((92, 78), title, font=HEAD, fill=(34, 34, 34))
    draw.text((94, 138), subtitle, font=TEXT, fill=(82, 76, 68))
    base.alpha_composite(panel)


def draw_lounge_render() -> None:
    size = (3200, 1800)
    base = vertical_gradient(size, (243, 234, 223), (226, 214, 199)).convert("RGBA")

    left_roof = [(100, 450), (860, 170), (1180, 980), (420, 1270)]
    right_roof = [(2140, 170), (3100, 450), (2780, 1270), (2020, 980)]
    back_wall = [(860, 170), (2140, 170), (2560, 450), (2080, 980), (1180, 980), (640, 450)]
    floor = [(420, 1270), (2780, 1270), (2080, 980), (1180, 980)]

    paste_polygon_texture(base, floor, wood_texture((1200, 900), base=(152, 118, 84), dark=(98, 70, 44), direction="horizontal"))
    add_polygon_overlay(base, floor, (40, 24, 10), 24)
    paste_polygon_texture(base, back_wall, brick_texture((1400, 900)))
    add_polygon_overlay(base, back_wall, (86, 67, 48), 18)
    paste_polygon_texture(base, left_roof, wood_texture((1600, 1200), base=(215, 184, 147), dark=(160, 122, 84), direction="vertical"))
    paste_polygon_texture(base, right_roof, wood_texture((1600, 1200), base=(215, 184, 147), dark=(160, 122, 84), direction="vertical"))
    add_polygon_overlay(base, left_roof, (75, 50, 25), 16)
    add_polygon_overlay(base, right_roof, (75, 50, 25), 26)

    beam = Image.new("RGBA", size, (0, 0, 0, 0))
    draw = ImageDraw.Draw(beam)
    draw.line((840, 200, 2160, 200), fill=(116, 73, 44, 255), width=30)
    for x in [660, 920, 1180, 1440, 1700, 1960, 2220, 2480]:
        draw.line((x, 280, x + 150, 930), fill=(124, 80, 50, 255), width=24)
    base.alpha_composite(beam)

    light = Image.new("RGBA", size, (0, 0, 0, 0))
    draw = ImageDraw.Draw(light)
    draw.polygon([(640, 380), (1020, 250), (1060, 1270), (560, 1270)], fill=(255, 243, 214, 38))
    draw.polygon([(0, 820), (520, 710), (1600, 1770), (0, 1770)], fill=(255, 218, 154, 36))
    base.alpha_composite(light.filter(ImageFilter.GaussianBlur(35)))

    wall_overlay = Image.new("RGBA", size, (0, 0, 0, 0))
    dwo = ImageDraw.Draw(wall_overlay)
    dwo.rounded_rectangle((860, 330, 1120, 930), radius=12, fill=(140, 97, 60, 255), outline=(104, 68, 41, 255), width=5)
    dwo.rectangle((885, 355, 1090, 900), fill=(181, 208, 223, 255))
    dwo.line((988, 355, 988, 900), fill=(235, 241, 245, 255), width=5)
    dwo.rounded_rectangle((1360, 325, 1600, 935), radius=12, fill=(138, 95, 58, 255), outline=(100, 66, 39, 255), width=5)
    dwo.rectangle((1385, 350, 1570, 905), fill=(189, 209, 220, 255))
    dwo.rounded_rectangle((1640, 345, 1870, 910), radius=12, fill=(138, 95, 58, 255), outline=(100, 66, 39, 255), width=5)
    dwo.rectangle((1664, 372, 1844, 880), fill=(192, 212, 222, 255))
    base.alpha_composite(wall_overlay)

    # Central storage / media core
    core_front = [(1730, 575), (2070, 575), (2070, 1210), (1730, 1210)]
    core_side = [(2070, 575), (2285, 685), (2285, 1325), (2070, 1210)]
    paste_polygon_texture(base, core_front, slat_texture((600, 900)))
    paste_polygon_texture(base, core_side, wood_texture((500, 900), base=(183, 150, 113), dark=(118, 87, 58), direction="vertical"))
    add_polygon_overlay(base, core_front, (46, 38, 30), 22)
    add_polygon_overlay(base, core_side, (38, 30, 24), 28)

    storage_draw = ImageDraw.Draw(base)
    storage_draw.rounded_rectangle((1770, 660, 2030, 790), radius=12, fill=(28, 28, 30, 255))
    storage_draw.rounded_rectangle((1788, 835, 2015, 960), radius=16, fill=(133, 98, 64, 255))
    storage_draw.rectangle((1810, 870, 1992, 908), fill=(70, 56, 45, 255))
    storage_draw.rounded_rectangle((1768, 1010, 2017, 1165), radius=18, outline=(206, 181, 148, 180), width=3)
    storage_draw.line((2190, 745, 2190, 1255), fill=(86, 64, 45, 200), width=3)

    # TV, floating media shelf
    rounded_box(base, (1835, 680, 2010, 775), (18, 18, 20, 255), outline=(42, 42, 42, 255), width=4, radius=10)
    rounded_box(base, (1815, 805, 2025, 845), (126, 93, 63, 220), radius=10)

    # Coffee nook
    add_shadow(base, (920, 935, 1275, 1210), blur=20, opacity=70, radius=24)
    rounded_box(base, (920, 935, 1275, 1210), (142, 109, 77, 255), texture=wood_texture((400, 400), base=(162, 126, 92), dark=(109, 79, 53), direction="vertical"))
    top = Image.new("RGBA", size, (0, 0, 0, 0))
    ImageDraw.Draw(top).rounded_rectangle((900, 905, 1295, 965), radius=18, fill=(230, 224, 212, 255))
    base.alpha_composite(top)
    coffee = Image.new("RGBA", size, (0, 0, 0, 0))
    dc = ImageDraw.Draw(coffee)
    dc.rectangle((987, 820, 1180, 900), fill=(188, 170, 142, 220))
    dc.rectangle((1055, 760, 1115, 820), fill=(230, 230, 228, 255))
    dc.ellipse((1070, 730, 1110, 770), fill=(250, 250, 250, 255))
    dc.rounded_rectangle((1210, 855, 1260, 905), radius=10, fill=(72, 72, 72, 255))
    base.alpha_composite(coffee)

    # Sofa and rug
    add_shadow(base, (1090, 1110, 1730, 1445), blur=32, opacity=80, radius=46)
    rounded_box(base, (1010, 1135, 1760, 1450), (196, 181, 163, 235), texture=linen_texture((900, 500), base=(203, 189, 171), accent=(173, 158, 140)))
    rounded_box(base, (1010, 1055, 1450, 1205), (205, 193, 178, 245), texture=linen_texture((600, 260), base=(209, 196, 180), accent=(185, 170, 151)))
    rounded_box(base, (1350, 1015, 1745, 1185), (202, 189, 174, 245), texture=linen_texture((550, 260), base=(206, 193, 177), accent=(181, 166, 149)))
    add_shadow(base, (1115, 1270, 1615, 1530), blur=25, opacity=60, radius=60)
    rounded_box(base, (1125, 1280, 1625, 1525), (235, 227, 214, 215), texture=rug_texture((800, 420)))
    add_shadow(base, (1290, 1180, 1510, 1350), blur=20, opacity=55, radius=80)
    rounded_box(base, (1295, 1188, 1515, 1348), (188, 153, 112, 235), texture=wood_texture((300, 220), base=(192, 157, 114), dark=(128, 95, 62), direction="horizontal"), radius=90)

    # Sideboard under slope
    rounded_box(base, (2440, 980, 2890, 1135), (173, 154, 137, 220), texture=wood_texture((600, 220), base=(177, 152, 124), dark=(119, 94, 68), direction="horizontal"))
    deco = Image.new("RGBA", size, (0, 0, 0, 0))
    dd = ImageDraw.Draw(deco)
    dd.ellipse((2480, 880, 2570, 1035), fill=(104, 124, 88, 255))
    dd.rectangle((2515, 975, 2535, 1035), fill=(122, 94, 70, 255))
    dd.ellipse((2605, 905, 2660, 990), fill=(205, 186, 166, 255))
    dd.ellipse((2690, 912, 2745, 995), fill=(222, 214, 202, 255))
    base.alpha_composite(deco)

    add_text_panel(base, "Warm Nordic Lounge", "Terrace side: living room, TV wall on storage core, integrated coffee nook.")

    draw = ImageDraw.Draw(base)
    draw.text((74, 1660), "Storage koncept: hrastove letvice oko centralnog volumena skrivaju posteljinu, kofer, sezonske stvari i utility zonu.", font=SMALL, fill=(58, 53, 47))

    base = base.filter(ImageFilter.UnsharpMask(radius=2, percent=130, threshold=2))
    base.save(OUT / "warm-nordic-lounge-render.png", quality=95)


def draw_studio_render() -> None:
    size = (3200, 1800)
    base = vertical_gradient(size, (239, 233, 224), (219, 209, 196)).convert("RGBA")

    left_roof = [(90, 420), (880, 160), (1220, 980), (380, 1290)]
    right_roof = [(2060, 160), (3110, 420), (2820, 1290), (1980, 980)]
    back_wall = [(880, 160), (2060, 160), (2520, 420), (2040, 980), (1220, 980), (640, 420)]
    floor = [(380, 1290), (2820, 1290), (2040, 980), (1220, 980)]

    paste_polygon_texture(base, floor, wood_texture((1200, 900), base=(148, 115, 84), dark=(92, 67, 45), direction="horizontal"))
    add_polygon_overlay(base, floor, (30, 18, 8), 34)
    paste_polygon_texture(base, back_wall, brick_texture((1400, 900)))
    add_polygon_overlay(base, back_wall, (74, 56, 40), 30)
    paste_polygon_texture(base, left_roof, wood_texture((1600, 1200), base=(209, 177, 139), dark=(149, 111, 76), direction="vertical"))
    paste_polygon_texture(base, right_roof, wood_texture((1600, 1200), base=(209, 177, 139), dark=(149, 111, 76), direction="vertical"))
    add_polygon_overlay(base, left_roof, (68, 48, 28), 22)
    add_polygon_overlay(base, right_roof, (50, 34, 18), 35)

    beam = Image.new("RGBA", size, (0, 0, 0, 0))
    draw = ImageDraw.Draw(beam)
    draw.line((860, 200, 2100, 200), fill=(96, 60, 38, 255), width=28)
    for x in [690, 965, 1240, 1515, 1790, 2065, 2340, 2615]:
        draw.line((x, 260, x - 160, 960), fill=(105, 68, 44, 255), width=23)
    base.alpha_composite(beam)

    win = Image.new("RGBA", size, (0, 0, 0, 0))
    dw = ImageDraw.Draw(win)
    for box in [(1180, 325, 1400, 930), (1570, 345, 1795, 920)]:
        dw.rounded_rectangle(box, radius=10, fill=(137, 98, 61, 255), outline=(103, 67, 39, 255), width=5)
        x1, y1, x2, y2 = box
        dw.rectangle((x1 + 24, y1 + 24, x2 - 24, y2 - 24), fill=(190, 210, 222, 255))
        dw.line(((x1 + x2) // 2, y1 + 24, (x1 + x2) // 2, y2 - 24), fill=(234, 240, 244, 255), width=4)
    base.alpha_composite(win)

    light = Image.new("RGBA", size, (0, 0, 0, 0))
    dl = ImageDraw.Draw(light)
    dl.polygon([(1115, 380), (1835, 395), (1920, 1450), (1020, 1450)], fill=(255, 236, 205, 32))
    base.alpha_composite(light.filter(ImageFilter.GaussianBlur(40)))

    # Storage / acoustic core
    core_front = [(930, 560), (1320, 560), (1320, 1240), (930, 1240)]
    core_side = [(1320, 560), (1545, 675), (1545, 1358), (1320, 1240)]
    paste_polygon_texture(base, core_front, slat_texture((650, 950)))
    paste_polygon_texture(base, core_side, wood_texture((400, 950), base=(176, 145, 110), dark=(112, 80, 54), direction="vertical"))
    add_polygon_overlay(base, core_front, (38, 33, 29), 28)
    add_polygon_overlay(base, core_side, (28, 22, 18), 22)

    drawc = ImageDraw.Draw(base)
    drawc.rounded_rectangle((980, 675, 1268, 1010), radius=18, fill=(160, 128, 92, 100), outline=(220, 192, 152, 180), width=3)
    drawc.rounded_rectangle((1000, 1035, 1248, 1180), radius=18, outline=(220, 192, 152, 180), width=3)
    drawc.rounded_rectangle((1360, 760, 1508, 900), radius=12, fill=(42, 42, 44, 255))

    # Studio desk
    add_shadow(base, (1580, 990, 2500, 1235), blur=24, opacity=82, radius=24)
    rounded_box(base, (1580, 990, 2500, 1235), (120, 88, 61, 240), texture=wood_texture((1000, 320), base=(140, 105, 74), dark=(91, 64, 41), direction="horizontal"), radius=16)
    desk = Image.new("RGBA", size, (0, 0, 0, 0))
    dd = ImageDraw.Draw(desk)
    dd.rounded_rectangle((1755, 830, 2005, 980), radius=16, fill=(28, 28, 30, 255))
    dd.rounded_rectangle((2040, 830, 2295, 980), radius=16, fill=(28, 28, 30, 255))
    dd.rectangle((1715, 905, 2340, 930), fill=(38, 38, 40, 255))
    dd.rectangle((1720, 780, 1785, 990), fill=(45, 45, 47, 255))
    dd.rectangle((2260, 780, 2325, 990), fill=(45, 45, 47, 255))
    dd.rectangle((1910, 870, 2050, 955), fill=(231, 225, 214, 255))
    dd.rectangle((2060, 1040, 2125, 1235), fill=(53, 53, 55, 255))
    base.alpha_composite(desk)

    # Lounge daybed
    add_shadow(base, (430, 1090, 1220, 1435), blur=28, opacity=72, radius=40)
    rounded_box(base, (430, 1105, 1220, 1435), (191, 178, 161, 235), texture=linen_texture((900, 400), base=(198, 186, 170), accent=(166, 151, 135)), radius=34)
    rounded_box(base, (465, 1045, 1150, 1175), (202, 190, 175, 245), texture=linen_texture((760, 180), base=(205, 194, 179), accent=(172, 157, 141)), radius=28)
    rounded_box(base, (615, 1240, 900, 1380), (145, 110, 84, 235), texture=linen_texture((360, 180), base=(150, 118, 93), accent=(120, 93, 71)), radius=22)

    # Acoustic wall and guitars
    paste_polygon_texture(base, [(2530, 520), (2850, 635), (2850, 1160), (2530, 1060)], slat_texture((500, 700)))
    acc = Image.new("RGBA", size, (0, 0, 0, 0))
    da = ImageDraw.Draw(acc)
    da.line((2670, 870, 2670, 1160), fill=(99, 74, 52, 255), width=10)
    da.ellipse((2630, 785, 2710, 905), outline=(148, 113, 76, 255), width=8)
    da.line((2760, 845, 2760, 1140), fill=(99, 74, 52, 255), width=10)
    da.ellipse((2720, 760, 2800, 880), outline=(148, 113, 76, 255), width=8)
    base.alpha_composite(acc)

    # Low storage under slope
    rounded_box(base, (2330, 1080, 2900, 1220), (173, 155, 137, 220), texture=wood_texture((620, 220), base=(178, 151, 123), dark=(118, 93, 67), direction="horizontal"), radius=20)

    # Rug
    add_shadow(base, (1520, 1200, 2450, 1490), blur=20, opacity=50, radius=40)
    rounded_box(base, (1540, 1210, 2460, 1490), (227, 221, 210, 220), texture=rug_texture((1000, 320), base=(221, 213, 201), line=(175, 162, 145)), radius=28)

    add_text_panel(base, "Warm Nordic Home Studio", "Stair side: recording desk, acoustic slat storage spine, lounge/daybed for listening sessions.")
    draw = ImageDraw.Draw(base)
    draw.text((74, 1660), "Storage spine: zatvoreni ormari gore za kabaste stvari, otvorene nishe za knjige/ploca, utility rack prema studiju.", font=SMALL, fill=(58, 53, 47))

    base = base.filter(ImageFilter.UnsharpMask(radius=2, percent=130, threshold=2))
    base.save(OUT / "warm-nordic-studio-render.png", quality=95)


def draw_layout_plan() -> None:
    size = (2600, 1800)
    base = Image.new("RGBA", size, (244, 240, 234, 255))
    draw = ImageDraw.Draw(base)
    draw.rounded_rectangle((160, 160, 2440, 1640), radius=24, fill=(248, 245, 241, 255), outline=(66, 60, 55, 255), width=6)
    draw.rectangle((300, 1220, 760, 1540), fill=(238, 236, 232, 255), outline=(140, 90, 90, 255), width=5)
    draw.text((362, 1350), "Stepenice", font=HEAD, fill=(124, 55, 55))
    draw.rectangle((1040, 460, 1510, 1320), fill=(186, 156, 120, 255), outline=(110, 84, 58, 255), width=5)
    draw.text((1080, 780), "Storage\ncore", font=TITLE, fill=(60, 40, 26))

    # lounge zone
    draw.rounded_rectangle((250, 250, 980, 1070), radius=26, fill=(230, 214, 195, 255), outline=(150, 123, 94, 255), width=4)
    draw.text((390, 310), "Living / Lounge", font=TITLE, fill=(82, 58, 39))
    draw.rounded_rectangle((330, 655, 740, 820), radius=30, fill=(197, 183, 166, 255))
    draw.rounded_rectangle((565, 875, 790, 995), radius=60, fill=(190, 153, 112, 255))
    draw.rectangle((790, 640, 950, 760), fill=(28, 28, 30, 255))
    draw.rectangle((300, 905, 500, 1025), fill=(145, 110, 80, 255))
    draw.text((300, 1045), "coffee nook", font=TEXT, fill=(90, 65, 45))
    draw.text((750, 785), "TV", font=TEXT, fill=(240, 240, 240))

    # studio zone
    draw.rounded_rectangle((1560, 250, 2320, 1440), radius=26, fill=(220, 227, 221, 255), outline=(109, 128, 112, 255), width=4)
    draw.text((1720, 310), "Music Studio", font=TITLE, fill=(57, 80, 65))
    draw.rectangle((1680, 650, 2120, 790), fill=(121, 88, 63, 255))
    draw.rectangle((1760, 520, 1885, 630), fill=(28, 28, 30, 255))
    draw.rectangle((1925, 520, 2050, 630), fill=(28, 28, 30, 255))
    draw.rounded_rectangle((1680, 1030, 2150, 1210), radius=26, fill=(194, 181, 165, 255))
    draw.text((1685, 800), "recording desk", font=TEXT, fill=(49, 40, 33))
    draw.text((1710, 1225), "listening lounge / daybed", font=TEXT, fill=(67, 80, 72))

    # storage logic
    draw.rounded_rectangle((930, 250, 1620, 390), radius=22, fill=(216, 204, 186, 255), outline=(147, 126, 104, 255), width=3)
    draw.text((960, 295), "gornji zatvoreni storage za posteljinu, kofere, sezonske stvari", font=TEXT, fill=(69, 56, 43))
    draw.rounded_rectangle((930, 1380, 1620, 1520), radius=22, fill=(216, 204, 186, 255), outline=(147, 126, 104, 255), width=3)
    draw.text((995, 1425), "nise / utility / skriveni kablovi / rack", font=TEXT, fill=(69, 56, 43))

    draw.text((170, 70), "Warm Nordic Loft - finalni koncept rasporeda", font=TITLE, fill=(28, 28, 28))
    draw.text((170, 1700), "Centralni storage volumen resava stubove i odzak kao dizajn feature, a ne kao smetnju.", font=TEXT, fill=(44, 44, 44))
    base.save(OUT / "warm-nordic-layout-plan.png", quality=95)


def write_concept_note() -> None:
    note = """# Warm Nordic Lounge + Studio

## Finalni pravac
- terasa strana: living room / lounge
- TV je vezan za centralni storage volumen
- coffee making kutak je uz lounge zonu, blizu terase
- stepenice strana: home music recording studio + listening lounge
- izmedju: storage spine oko stubova i odzaka

## Kako storage poboljsava ambijent
- obloziti centralni volumen hrastovim vertikalnim letvicama ili finim furnirom
- gornje fronte zatvorene i mirne, push-open, bez vizuelnog nereda
- po jedna otvorena nisa prema svakoj zoni za dekor, knjige, ploce ili kafe set
- TV strana ostaje cista i smirena
- studio strana dobija integrisanu akusticku logiku i skriveni kablovski management

## Materijali
- svetliji hrast / smoked oak u toplom tonu
- opeka ostaje vidljiva i ociscena
- plafonska drvena obloga prirodan mat ton
- tekstil: lan, greige, pesak, maslina
- metalni detalji: crni ili tamno bronzani

## Napomena
Ovi vizuali su high-res textured concept renders, ne pravi V-Ray / Corona / AI photo renders.
Za potpuno fotorealistican rezultat na tacnoj geometriji treba ili:
1. originalne fotografije kao lokalni fajlovi za photomontage, ili
2. 3D model u SketchUp / Blender + render engine.
"""
    (OUT / "warm-nordic-koncept.md").write_text(note)


if __name__ == "__main__":
    draw_layout_plan()
    draw_lounge_render()
    draw_studio_render()
    write_concept_note()
