#!/usr/bin/env python3

import os
import shutil
import subprocess
import sys
import tempfile
import xml.etree.ElementTree as ET
from datetime import datetime, timezone
from pathlib import Path

INKSCAPE_NS = "http://www.inkscape.org/namespace/inkscape/extension"
ET.register_namespace("", INKSCAPE_NS)

REPOS = [
    {
        "name": "botbox3000",
        "url": "https://github.com/jondale/botbox3000.git",
        "branch": "main",
        "directory": "botbox3000",
        "inx": [
            {
                "file": "boxbot.inx",
                "id": "org.knoxmakers.botbox",
                "submenu": ["Knox Makers", "Laser"],
            },
        ],
    },
    {
        "name": "km-living-hinge",
        "url": "https://github.com/KnoxMakers/km-living-hinge.git",
        "branch": "main",
        "directory": "km-living-hinge",
    },
    {
        "name": "km-plot",
        "url": "https://git.knoxmakers.org/KnoxMakers/km-plot.git",
        "branch": "main",
        "directory": "km-plot",
    },
    {
        "name": "km-hatch",
        "url": "https://git.knoxmakers.org/KnoxMakers/km-hatch.git",
        "branch": "main",
        "directory": "km-hatch",
    },
    {
        "name": "km-hershey",
        "url": "https://git.knoxmakers.org/KnoxMakers/km-hershey.git",
        "branch": "main",
        "directory": "km-hershey",
    },
]


def run(cmd: list[str], cwd: str | None = None) -> str:
    result = subprocess.run(cmd, cwd=cwd, capture_output=True, text=True, check=True)
    return result.stdout.strip()


def has_staged_changes(root: Path) -> bool:
    result = subprocess.run(
        ["git", "diff", "--cached", "--quiet"],
        cwd=root,
        capture_output=True,
    )
    return result.returncode != 0


def sync_repo(repo: dict, ext_dir: Path, tmp_dir: Path) -> None:
    name = repo["name"]
    url = repo["url"]
    branch = repo.get("branch", "main")
    directory = repo["directory"]

    work = tmp_dir / name
    target = ext_dir / directory

    print(f"==> Sync {name} from {url} ({branch}) -> extensions/{directory}")

    subprocess.run(
        ["git", "clone", "--depth", "1", "--branch", branch, url, str(work)],
        check=True,
    )

    if target.exists():
        shutil.rmtree(target)
    shutil.copytree(work, target)

    git_dir = target / ".git"
    if git_dir.exists():
        shutil.rmtree(git_dir)

    commit = run(["git", "rev-parse", "HEAD"], cwd=str(work))
    (target / ".upstream_commit").write_text(commit + "\n")
    (target / ".upstream_url").write_text(url + "\n")
    (target / ".upstream_branch").write_text(branch + "\n")


def set_extension_id(root: ET.Element, new_id: str) -> bool:
    ns = {"ink": INKSCAPE_NS}

    id_elem = root.find("ink:id", ns)
    if id_elem is None:
        id_elem = root.find("id")

    if id_elem is not None:
        if id_elem.text != new_id:
            id_elem.text = new_id
            return True
        return False

    name_elem = root.find("ink:name", ns)
    if name_elem is None:
        name_elem = root.find("name")

    id_elem = ET.Element("id")
    id_elem.text = new_id

    if name_elem is not None:
        idx = list(root).index(name_elem)
        root.insert(idx + 1, id_elem)
    else:
        root.insert(0, id_elem)
    return True


def set_effects_submenu(root: ET.Element, submenus: list[str]) -> bool:
    ns = {"ink": INKSCAPE_NS}

    effect_elem = root.find(".//ink:effect", ns)
    if effect_elem is None:
        effect_elem = root.find(".//effect")

    if effect_elem is None:
        return False

    menu_elem = None
    for tag in ["effects-menu", "effectsmenu"]:
        menu_elem = effect_elem.find(tag)
        if menu_elem is not None:
            break
        menu_elem = effect_elem.find(f"ink:{tag}", ns)
        if menu_elem is not None:
            break

    if menu_elem is None:
        return False

    menu_elem.clear()
    menu_elem.tag = "effects-menu"

    parent = menu_elem
    for i, submenu_name in enumerate(submenus):
        submenu = ET.SubElement(parent, "submenu")
        submenu.set("_name", submenu_name)
        if i < len(submenus) - 1:
            parent = submenu

    return True


def patch_inx(inx_path: Path, extension_id: str | None, submenus: list[str] | None) -> bool:
    if not inx_path.exists():
        print(f"ERROR: expected file not found: {inx_path}")
        sys.exit(1)

    print(f"Patching {inx_path}")

    tree = ET.parse(inx_path)
    root = tree.getroot()

    modified = False

    if extension_id is not None:
        if set_extension_id(root, extension_id):
            modified = True

    if submenus is not None:
        if set_effects_submenu(root, submenus):
            modified = True

    if modified:
        ET.indent(tree, space="  ")
        tree.write(inx_path, encoding="UTF-8", xml_declaration=True)

        content = inx_path.read_text(encoding="utf-8")
        if not content.endswith("\n"):
            inx_path.write_text(content + "\n", encoding="utf-8")

    return modified


def process_repo(repo: dict, ext_dir: Path) -> None:
    inx_list = repo.get("inx", [])
    directory = repo["directory"]

    for inx_config in inx_list:
        inx_file = inx_config.get("file")
        extension_id = inx_config.get("id")
        submenus = inx_config.get("submenu")

        if inx_file and (extension_id or submenus):
            inx_path = ext_dir / directory / inx_file
            patch_inx(inx_path, extension_id, submenus)


def main() -> None:
    root = Path(__file__).resolve().parent.parent
    ext_dir = root / "extensions"
    ext_dir.mkdir(exist_ok=True)

    os.chdir(root)

    with tempfile.TemporaryDirectory() as tmp:
        tmp_dir = Path(tmp)

        for repo in REPOS:
            sync_repo(repo, ext_dir, tmp_dir)

    subprocess.run(["git", "add", "extensions"], check=True)

    if not has_staged_changes(root):
        print("No bundle changes.")
        sys.exit(0)

    subprocess.run(["git", "checkout", "extensions"], check=True)

    with tempfile.TemporaryDirectory() as tmp:
        tmp_dir = Path(tmp)

        for repo in REPOS:
            sync_repo(repo, ext_dir, tmp_dir)
            process_repo(repo, ext_dir)

    subprocess.run(["git", "add", "extensions"], check=True)

    if not has_staged_changes(root):
        print("No bundle changes.")
        sys.exit(0)

    date_str = datetime.now(timezone.utc).strftime("%Y-%m-%d")
    subprocess.run(
        ["git", "commit", "-m", f"bundle: update ({date_str})"],
        check=True,
    )


if __name__ == "__main__":
    main()
