#!/usr/bin/env python3
# udt-appthemes: install the generated app themes that need more than a copy.
# Copyright (C) 2026 Danilo M. <danix@danix.xyz>
# Licensed under the GNU General Public License v2 only.
"""Install the app themes that need more than a copy: Obsidian, Typora,
btop, Joplin and Feishin.

Separate from install.sh because enabling an Obsidian snippet means editing
JSON that the user also owns: appearance.json lists every enabled snippet, so
the entry has to be appended rather than the file rewritten, or a vault loses
whatever layout snippets it had.
"""

import json
import shutil
import sys
from pathlib import Path

REPO = Path(__file__).resolve().parent.parent
SNIPPET = "udt"
OBSIDIAN_REGISTRY = Path.home() / ".config" / "obsidian" / "obsidian.json"
TYPORA_THEMES = Path.home() / ".config" / "Typora" / "themes"
BTOP = Path.home() / ".config" / "btop"
JOPLIN = Path.home() / ".config" / "joplin-desktop"
FEISHIN = Path.home() / ".config" / "feishin"


def vaults():
    """The vaults Obsidian itself knows about.

    Read from Obsidian's own registry rather than found by scanning for
    .obsidian directories. A scan cannot tell a vault from an abandoned one:
    ~/Documents/Obsidian holds a leftover .obsidian from a vault that no longer
    exists, and writing to it touched config for something Obsidian never
    opens.
    """
    try:
        data = json.loads(OBSIDIAN_REGISTRY.read_text())
    except (OSError, json.JSONDecodeError):
        return []

    found = []
    for entry in data.get("vaults", {}).values():
        path = entry.get("path")
        if not path:
            continue
        config = Path(path) / ".obsidian"
        if config.is_dir():
            found.append(config)
    return sorted(found)


def install_obsidian(source):
    """Copy the snippet into each vault and enable it, preserving the rest."""
    done = []
    for config in vaults():
        snippets = config / "snippets"
        snippets.mkdir(exist_ok=True)
        shutil.copyfile(source, snippets / f"{SNIPPET}.css")

        appearance = config / "appearance.json"
        try:
            data = json.loads(appearance.read_text())
        except (OSError, json.JSONDecodeError):
            data = {}

        enabled = data.get("enabledCssSnippets", [])
        if SNIPPET not in enabled:
            # Append: a vault may already have snippets on, and replacing the
            # list would silently switch them off.
            enabled.append(SNIPPET)
            data["enabledCssSnippets"] = enabled
            appearance.write_text(json.dumps(data, indent=2) + "\n")

        done.append(config.parent.name or str(config.parent))
    return done


def install_typora(source):
    """Drop the theme in. Typora picks it from Themes once the file exists."""
    if not TYPORA_THEMES.is_dir():
        return False
    shutil.copyfile(source, TYPORA_THEMES / "udt.css")
    return True


def install_btop(source):
    """Install the theme and point btop.conf's color_theme at it.

    Only that one line is rewritten: btop.conf is btop's own, and it writes
    the file back on exit, so a btop left running across a scheme switch
    restores its old color_theme. Restart it after installing.
    """
    if not BTOP.is_dir():
        return False
    (BTOP / "themes").mkdir(exist_ok=True)
    shutil.copyfile(source, BTOP / "themes" / "udt.theme")
    conf = BTOP / "btop.conf"
    try:
        lines = conf.read_text().splitlines()
    except OSError:
        lines = []
    want = 'color_theme = "udt"'
    lines = [want if line.startswith("color_theme") else line for line in lines]
    if want not in lines:
        lines.append(want)
    conf.write_text("\n".join(lines) + "\n")
    return True


def install_joplin(source):
    """Write userchrome.css, unless the user already keeps their own there.

    Joplin has one userchrome.css per profile and no include, so a file this
    script did not write is left alone rather than replaced.
    """
    if not JOPLIN.is_dir():
        return None
    dest = JOPLIN / "userchrome.css"
    if dest.exists() and "Generated by udt-palette" not in dest.read_text():
        return False
    shutil.copyfile(source, dest)
    return True


def install_feishin(source):
    """Drop the theme into Feishin's Themes folder, which it watches live."""
    if not FEISHIN.is_dir():
        return False
    (FEISHIN / "Themes").mkdir(exist_ok=True)
    shutil.copyfile(source, FEISHIN / "Themes" / "UDT.json")
    return True


def main():
    obsidian = REPO / "templates" / "obsidian" / "udt.css"
    typora = REPO / "templates" / "typora" / "udt.css"

    if not obsidian.exists() or not typora.exists():
        print("udt-appthemes: run udt-palette first", file=sys.stderr)
        return 1

    names = install_obsidian(obsidian)
    if names:
        print(f"obsidian: {len(names)} vaults ({', '.join(names)})")

    if install_typora(typora):
        print("typora: udt.css installed (select it in Themes)")

    templates = REPO / "templates"
    if install_btop(templates / "btop" / "udt.theme"):
        print("btop: udt theme set (restart btop)")

    joplin = install_joplin(templates / "joplin" / "userchrome.css")
    if joplin:
        print("joplin: userchrome.css installed (restart Joplin)")
    elif joplin is False:
        print("joplin: userchrome.css is your own, left alone")

    if install_feishin(templates / "feishin" / "UDT.json"):
        print("feishin: UDT theme installed (select it once in Settings, it then follows live)")

    return 0


if __name__ == "__main__":
    sys.exit(main())
