"""Блобы таскбара C4D под каждую версию: c4d.reg (пин «Maxon Cinema 4D 2026») -> c4d_<rel>.reg.

Пин хранит имя ярлыка и путь к exe внутри PIDL-элемента (блоки BEEF0004 и BEEF001D,
у каждого своя длина) и в FavoritesResolve (.lnk на ярлык: тот же PIDL + LinkInfo с
ANSI-путём). Меняем строки и пересчитываем все вложенные длины -- пин показывает
нужную версию (имя, иконка) и запущенная C4D встаёт на его место, а не вторым значком.
Программно запинить на Server 2025 нельзя, снимать блоб руками на каждую версию -- долго;
формат проверен на живой ноде (R23, 2026-10-12). Зовёт deploy.sh на VPS при сборке
scripts.zip; агент (Step-ApplyTaskbar) берёт блоб версии из плана проекта.

  python3 taskbar_blob.py <c4d.reg> <software_matrix.json> <outdir>
"""
import json
import os
import re
import struct
import sys

OLD_NAME = "Maxon Cinema 4D 2026"


def load_reg(path):
    text = open(path, encoding="utf-16").read()
    vals, order, cur = {}, [], None
    for line in text.splitlines():
        m = re.match(r'^"(\w+)"=hex:(.*)', line)
        if m:
            cur = m.group(1); vals[cur] = m.group(2).rstrip("\\"); order.append(cur); continue
        if cur and line.startswith("  "):
            vals[cur] += line.strip().rstrip("\\")
        else:
            cur = None
    return text, {k: bytes.fromhex(v.replace(",", "")) for k, v in vals.items()}


def reg_hex(name, data):
    # Формат regedit: "name"=hex:xx,xx,... с переносом строк по ~80 символов.
    parts = [f"{b:02x}" for b in data]
    first = f'"{name}"=hex:'
    lines, cur = [], first
    for i, p in enumerate(parts):
        piece = p + ("," if i < len(parts) - 1 else "")
        if len(cur) + len(piece) > 78:
            lines.append(cur + "\\")
            cur = "  "
        cur += piece
    lines.append(cur)
    return "\r\n".join(lines)


def patch_item(item, repl16):
    first_ext = struct.unpack_from("<H", item, len(item) - 2)[0]
    head = bytearray(item[:first_ext])
    blocks = b""
    o = first_ext
    while o < len(item) - 2:
        size = struct.unpack_from("<H", item, o)[0]
        blk = item[o:o + size]
        body = blk[2:-2]
        for a, b in repl16:
            body = body.replace(a, b)
        blocks += struct.pack("<H", len(body) + 4) + body + blk[-2:]
        o += size
    # Последний блок сам кончается полем first_ext — отдельного хвоста у элемента нет.
    out = bytes(head) + blocks
    return struct.pack("<H", len(out)) + out[2:]


def patch_pidl(pidl, repl16, needle16):
    out, p, changed = b"", 0, False
    while True:
        cb = struct.unpack_from("<H", pidl, p)[0]
        if cb == 0:
            return out + b"\x00\x00", changed
        item = pidl[p:p + cb]
        if needle16 in item:
            item = patch_item(item, repl16); changed = True
        out += item
        p += cb


def patch_favorites(fav, repl16, needle16):
    out, pos = fav[:1], 1
    while pos + 4 <= len(fav):
        size = struct.unpack_from("<I", fav, pos)[0]
        entry = fav[pos + 4: pos + 4 + size]
        new, _ = patch_pidl(entry, repl16, needle16)
        # PIDL занимает всю запись (хвоста нет, проверено разбором)
        out += struct.pack("<I", len(new)) + new
        pos += 4 + size
        out += fav[pos:pos + 1]   # разделитель 00 / финальный ff
        pos += 1
    return out


def patch_lnk(lnk, repl16, repl8, needle16):
    idl = struct.unpack_from("<H", lnk, 76)[0]
    pidl, changed = patch_pidl(lnk[78:78 + idl], repl16, needle16)
    if not changed:
        return lnk
    p = 78 + idl
    li = lnk[p:p + struct.unpack_from("<I", lnk, p)[0]]
    size, hsize, flags, vol_off, lbp_off, cnrl_off, cps_off = struct.unpack_from("<IIIIIII", li, 0)
    end = li.index(b"\x00", lbp_off)
    path = li[lbp_off:end]
    for a, b in repl8:
        path = path.replace(a, b)
    delta = len(path) - (end - lbp_off)
    new_li = bytearray(li[:lbp_off] + path + li[end:])
    struct.pack_into("<I", new_li, 0, len(new_li))
    if cps_off > lbp_off:
        struct.pack_into("<I", new_li, 24, cps_off + delta)
    if cnrl_off and cnrl_off > lbp_off:
        struct.pack_into("<I", new_li, 20, cnrl_off + delta)
    return lnk[:76] + struct.pack("<H", len(pidl)) + pidl + bytes(new_li) + lnk[p + len(li):]


def patch_resolve(res, repl16, repl8, needle16):
    out, pos = b"", 0
    while pos + 4 <= len(res):
        size = struct.unpack_from("<I", res, pos)[0]
        lnk = patch_lnk(res[pos + 4:pos + 4 + size], repl16, repl8, needle16)
        out += struct.pack("<I", len(lnk)) + lnk
        pos += 4 + size
    return out


def build(reg_path, release):
    text, vals = load_reg(reg_path)
    new_name = f"Maxon Cinema 4D {release}"
    pairs = [(OLD_NAME + ".lnk", new_name + ".lnk"), (OLD_NAME + "\\Cinema 4D.exe", new_name + "\\Cinema 4D.exe")]
    repl16 = [(a.encode("utf-16-le"), b.encode("utf-16-le")) for a, b in pairs]
    repl8 = [(a.encode("ascii"), b.encode("ascii")) for a, b in pairs]
    needle16 = (OLD_NAME + ".lnk").encode("utf-16-le")
    fav = patch_favorites(vals["Favorites"], repl16, needle16)
    res = patch_resolve(vals["FavoritesResolve"], repl16, repl8, needle16)
    out = text
    for name, data in (("Favorites", fav), ("FavoritesResolve", res)):
        out, n = re.subn(r'"%s"=hex:(?:[^\n]*\\\n)*[^\n]*' % name, lambda m: reg_hex(name, data).replace("\r\n", "\n"), out, count=1)
        assert n == 1, name
    return out, fav, res


def write(reg_path, release, out_path):
    out, _, _ = build(reg_path, release)
    with open(out_path, "w", encoding="utf-16", newline="") as f:
        f.write(out.replace("\r\n", "\n").replace("\n", "\r\n"))


if __name__ == "__main__":
    reg, matrix, outdir = sys.argv[1], sys.argv[2], sys.argv[3]
    with open(matrix, encoding="utf-8") as f:
        releases = [r["release"] for r in json.load(f)["c4d"]]
    for rel in releases:
        if rel == "2026":
            continue
        path = os.path.join(outdir, f"c4d_{rel}.reg")
        write(reg, rel, path)
        print("taskbar blob", path)
