#!/usr/bin/env python3
"""Brand kit from a logo: colour palette (extracted with Pillow, contrast-checked), logo variants (trimmed, mono,
on-colour, padded square), favicons + .ico, social templates (avatar, OG, X header, LinkedIn banner, post, story),
a one-page brand sheet, tokens.json and brand.css.

Examples
  python brandkit.py --logo https://.../logo_transparent.png --name "Nova" --images https://.../post1.png
  python brandkit.py --logo logo.png --name "Brew & Bloom" --colors "#1F6F50,#F4E9D8" --tagline "Cold brew, done right"
"""
from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent))
import kmedia as K  # noqa: E402

SOCIAL = {"avatar_400x400": (400, 400), "og_1200x630": (1200, 630), "x_header_1500x500": (1500, 500),
          "linkedin_banner_1584x396": (1584, 396), "post_1080x1080": (1080, 1080), "story_1080x1920": (1080, 1920)}
FAVICONS = [16, 32, 48, 64, 180, 192, 512]


def trim(img):
    a = img.getchannel("A") if img.mode == "RGBA" else None
    if a is not None and a.getextrema()[0] < 250:
        bb = a.point(lambda v: 255 if v > 8 else 0).getbbox()
    else:
        from PIL import Image, ImageChops
        rgb = img.convert("RGB")
        bg = rgb.getpixel((0, 0))
        bb = ImageChops.difference(rgb, Image.new("RGB", rgb.size, bg)).convert("L").point(lambda v: 255 if v > 20 else 0).getbbox()
    return img.crop(bb) if bb else img


def to_transparent(img):
    """Logo on a flat background -> RGBA (simple colour key; use Kenerate remove_background for photos)."""
    from PIL import Image, ImageChops
    if img.mode == "RGBA" and img.getchannel("A").getextrema()[0] < 250:
        return img, False
    rgb = img.convert("RGB")
    bg = rgb.getpixel((0, 0))
    diff = ImageChops.difference(rgb, Image.new("RGB", rgb.size, bg)).convert("L")
    alpha = diff.point(lambda v: 0 if v < 12 else (255 if v > 48 else int((v - 12) * 255 / 36)))
    out = rgb.convert("RGBA")
    out.putalpha(alpha)
    return out, True


def palette(images, n: int = 6):
    """Dominant colours by area (quantized), ignoring transparent pixels; near-duplicates merged."""
    from PIL import Image
    px = []
    for im in images:
        im = im.convert("RGBA")
        im.thumbnail((200, 200))
        data = im.get_flattened_data() if hasattr(im, "get_flattened_data") else im.getdata()
        px += [p[:3] for p in data if p[3] > 128]
    if not px:
        return []
    strip = Image.new("RGB", (len(px), 1))
    strip.putdata(px)
    q = strip.quantize(colors=16, method=Image.Quantize.MEDIANCUT if hasattr(Image, "Quantize") else 0)
    pal = q.getpalette()[:48]
    counts = sorted(q.getcolors(), reverse=True)
    cols = []
    for cnt, idx in counts:
        c = tuple(pal[idx * 3: idx * 3 + 3])
        if all(sum(abs(c[k] - d[k]) for k in range(3)) > 60 for d, _ in cols):
            cols.append((c, cnt))
        if len(cols) >= n:
            break
    total = sum(c for _, c in cols) or 1
    return [(c, round(cnt / total, 3)) for c, cnt in cols]


def mono(img, color):
    from PIL import Image
    out = Image.new("RGBA", img.size, tuple(color) + (255,))
    out.putalpha(img.getchannel("A"))
    return out


def on_bg(logo, W: int, H: int, bg, scale: float = 0.6):
    from PIL import Image
    c = Image.new("RGBA", (W, H), tuple(bg) + (255,))
    l2 = logo.copy()
    l2.thumbnail((int(W * scale), int(H * scale)), Image.LANCZOS)
    c.alpha_composite(l2, ((W - l2.width) // 2, (H - l2.height) // 2))
    return c


def main() -> None:
    ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    ap.add_argument("--logo", required=True, help="logo (transparent PNG best; Kenerate remove_background result)")
    ap.add_argument("--name", required=True, help="brand name, exactly as written")
    ap.add_argument("--tagline", default="")
    ap.add_argument("--images", nargs="*", default=[], help="extra brand images to sample colours from")
    ap.add_argument("--colors", default="", help="override palette: '#hex,#hex,...' (first = primary)")
    K.add_font_args(ap)
    ap.add_argument("--out-dir", default="kenerate-out/brand-kit")
    a = K.parse_args(ap)

    st = K.Status(5, "brand-kit")
    K.require(ffmpeg=False, status=st)
    from PIL import Image, ImageDraw
    tmp = K.workdir()
    out = Path(a.out_dir)
    for sub in ("logo", "favicon", "social"):
        (out / sub).mkdir(parents=True, exist_ok=True)

    st.step("Fetching the logo and images")
    logo_raw = K.open_image(K.fetch(a.logo, tmp, "logo", st))
    logo, keyed = to_transparent(logo_raw)
    if keyed:
        st.warn("logo had no transparency: keyed out the flat background. For photos or soft edges use Kenerate remove_background.")
    logo = trim(logo.convert("RGBA"))
    extra = [K.open_image(p) for p in K.fetch_all(a.images, tmp, st, prefix="img")] if a.images else []

    st.step("Extracting the palette")
    if a.colors:
        pal = [(K.hex_rgb(c), 0.0) for c in a.colors.split(",") if c.strip()]
    else:
        pal = palette([logo] + extra)
    if not pal:
        K.fail("Could not read any colours from the logo.", "Pass --colors '#hex,#hex'.")
    chromatic = sorted(pal, key=lambda pc: -(max(pc[0]) - min(pc[0])) * (0.5 + pc[1]))
    primary = chromatic[0][0]
    secondary = chromatic[1][0] if len(chromatic) > 1 else tuple(255 - v for v in primary)
    darks = [c for c, _ in pal if K.luminance(c) < 0.05]
    dark = min(darks, key=K.luminance) if darks else (17, 24, 39)
    light = (250, 250, 247)
    tokens = {"name": a.name, "primary": K.rgb_hex(primary), "secondary": K.rgb_hex(secondary), "dark": K.rgb_hex(dark),
              "light": K.rgb_hex(light), "palette": [{"hex": K.rgb_hex(c), "share": s} for c, s in pal],
              "contrast": {"primary_on_light": K.contrast(primary, light), "primary_on_dark": K.contrast(primary, dark),
                           "light_on_primary": K.contrast(light, primary), "dark_on_primary": K.contrast(dark, primary)}}
    on_primary = light if tokens["contrast"]["light_on_primary"] >= tokens["contrast"]["dark_on_primary"] else dark
    tokens["text_on_primary"] = K.rgb_hex(on_primary)
    st.info("palette: " + ", ".join(t["hex"] for t in tokens["palette"]) + f"  (primary {tokens['primary']})")

    st.step("Logo variants + favicons")
    files = {}
    variants = {"logo_trimmed.png": logo, "logo_mono_black.png": mono(logo, (0, 0, 0)), "logo_mono_white.png": mono(logo, (255, 255, 255))}
    side = int(max(logo.size) * 1.25)
    variants["logo_square_padded.png"] = on_bg(logo, side, side, (255, 255, 255), 0.8).convert("RGBA")
    variants["logo_on_primary.png"] = on_bg(mono(logo, on_primary), side, side, primary, 0.7)
    variants["logo_on_dark.png"] = on_bg(logo if K.luminance(primary) > 0.08 else mono(logo, light), side, side, dark, 0.7)
    for fn, im in variants.items():
        im.save(out / "logo" / fn)
        files[fn] = str((out / "logo" / fn).resolve())
    sq = Image.new("RGBA", (512, 512), (0, 0, 0, 0))
    l2 = logo.copy()
    l2.thumbnail((460, 460), Image.LANCZOS)
    sq.alpha_composite(l2, ((512 - l2.width) // 2, (512 - l2.height) // 2))
    for s in FAVICONS:
        sq.resize((s, s), Image.LANCZOS).save(out / "favicon" / f"favicon-{s}.png")
    sq.save(out / "favicon" / "favicon.ico", sizes=[(16, 16), (32, 32), (48, 48)])
    st.progress(100)

    st.step("Social templates")
    for i, (fn, (W, H)) in enumerate(SOCIAL.items(), 1):
        if fn.startswith("avatar"):
            im = on_bg(logo, W, H, primary if K.contrast(primary, light) > 1.5 else dark, 0.62)
        else:
            im = K.gradient(W, H, primary, tuple(int(v * 0.55) for v in primary), vertical=H > W).convert("RGBA")
            l2 = logo.copy()
            short = min(W, H)
            l2.thumbnail((int(short * 0.42), int(short * 0.42)), Image.LANCZOS)
            if W > H * 1.8:  # banners: logo left, name right
                im.alpha_composite(l2, (int(W * 0.06), (H - l2.height) // 2))
                K.draw_block(im, a.name, (int(W * 0.06) + l2.width + int(W * 0.03), int(H * 0.2), int(W * 0.92), int(H * 0.8)),
                             fill=on_primary, align="left", max_size=int(H * 0.32), kind="bold", font_override=a.font, max_lines=1)
            else:
                im.alpha_composite(l2, ((W - l2.width) // 2, int(H * 0.5 - l2.height * 0.85)))
                K.draw_block(im, a.name, (int(W * 0.08), int(H * 0.52), int(W * 0.92), int(H * 0.66)), fill=on_primary,
                             max_size=int(short * 0.12), kind="bold", font_override=a.font, max_lines=1)
                if a.tagline:
                    K.draw_block(im, a.tagline, (int(W * 0.1), int(H * 0.67), int(W * 0.9), int(H * 0.76)), fill=on_primary,
                                 max_size=int(short * 0.05), kind="regular", max_lines=2)
        p = out / "social" / f"{fn}.png"
        im.convert("RGB").save(p)
        files[fn] = str(p.resolve())
        st.progress(i * 100 / len(SOCIAL), fn)

    st.step("Brand sheet + tokens")
    canvas = Image.new("RGBA", (1600, 1000), light + (255,))
    K.draw_block(canvas, a.name, (60, 40, 1000, 150), fill=dark, align="left", max_size=90, kind="bold", font_override=a.font, max_lines=1)
    if a.tagline:
        K.draw_block(canvas, a.tagline, (60, 150, 1000, 200), fill=(90, 90, 90), align="left", max_size=34, kind="regular", max_lines=1)
    l2 = logo.copy()
    l2.thumbnail((420, 420), Image.LANCZOS)
    canvas.alpha_composite(l2, (1120 + (420 - l2.width) // 2, 40 + (420 - l2.height) // 2))
    d = ImageDraw.Draw(canvas)
    f = K.load_font(22, "regular")
    fb = K.load_font(26, "bold")
    sw = [("Primary", primary), ("Secondary", secondary), ("Dark", dark), ("Light", light)] + \
         [(f"#{i + 1}", c) for i, (c, _) in enumerate(pal[:4])]
    for i, (lab, c) in enumerate(sw):
        x, y = 60 + (i % 4) * 255, 260 + (i // 4) * 330
        d.rounded_rectangle((x, y, x + 230, y + 200), radius=18, fill=tuple(c) + (255,), outline=(220, 220, 220, 255))
        tc, ratio = K.best_text_color(c)
        d.text((x + 16, y + 16), lab, font=fb, fill=tc)
        d.text((x + 16, y + 150), K.rgb_hex(c), font=f, fill=tc)
        d.text((x, y + 210), f"white {K.contrast(c, (255, 255, 255))}:1  black {K.contrast(c, (0, 0, 0))}:1", font=f, fill=(80, 80, 80))
    canvas.convert("RGB").save(out / "brand_sheet.png")
    (out / "tokens.json").write_text(json.dumps(tokens, indent=2), encoding="utf-8")
    css = ":root {\n" + "\n".join(f"  --brand-{k}: {tokens[k]};" for k in ("primary", "secondary", "dark", "light", "text_on_primary")) + \
          "\n" + "\n".join(f"  --brand-palette-{i + 1}: {p['hex']};" for i, p in enumerate(tokens["palette"])) + "\n}\n"
    (out / "brand.css").write_text(css.replace("text_on_primary", "on-primary"), encoding="utf-8")
    K.finish({"tokens": tokens, "brand_sheet": str((out / "brand_sheet.png").resolve()), "files": files,
              "favicons": str((out / "favicon").resolve()), "elapsed_s": st.elapsed()})


if __name__ == "__main__":
    K.main_guard(main)
