#!/usr/bin/env python3
from __future__ import annotations

import argparse
import os
import re
import sys
from pathlib import Path


SECTION_RE = re.compile(r"^##\s+(.+)$")
FRONTMATTER_RE = re.compile(r"^---\n(.*?)\n---\n?", re.DOTALL)
TITLE_RE = re.compile(r"^#\s+(.+)$", re.MULTILINE)
MOC_TYPE_RE = re.compile(r"^moc_type:\s*(\S+)", re.MULTILINE)
UP_LINE_RE = re.compile(r"^Up:\s*\[\[([^\]]+)\]\]\s*$", re.MULTILINE)


def find_vault_root(start: str, marker: str | None = None) -> Path:
    """Vault ルート探索。`.obsidian/` の存在で判定する。
    marker を指定すると、ディレクトリ名一致でも探索する（後方互換のフォールバック）。
    """
    path = Path(start).resolve()
    while True:
        if (path / ".obsidian").is_dir():
            return path
        if marker and path.name == marker:
            return path
        if path.parent == path:
            label = marker or ".obsidian/"
            raise RuntimeError(f"Vault root ({label}) が見つかりません: {start}")
        path = path.parent


VAULT_ROOT = find_vault_root(os.path.dirname(os.path.abspath(__file__)))
MOC_ROOT = VAULT_ROOT / "Brain" / "MOC"


def resolve_path(raw: str) -> Path:
    path = Path(raw)
    if path.is_absolute():
        return path.resolve()
    return (VAULT_ROOT / path).resolve()


def split_frontmatter(content: str) -> tuple[str, str]:
    match = FRONTMATTER_RE.match(content)
    if not match:
        return "", content
    return match.group(1), content[match.end():]


def extract_title(content: str, path: Path) -> str:
    match = TITLE_RE.search(content)
    return match.group(1).strip() if match else path.stem


def extract_moc_type(frontmatter: str) -> str:
    match = MOC_TYPE_RE.search(frontmatter)
    return match.group(1).strip() if match else "synthesis"


def ensure_moc_path(path: Path, role: str) -> None:
    if not path.exists():
        raise FileNotFoundError(f"{role} が見つかりません: {path}")
    if MOC_ROOT.resolve() not in path.parents:
        raise ValueError(f"{role} は Brain/MOC 配下である必要があります: {path}")


def build_root_entry(content: str, section: str, link_title: str, summary: str) -> str:
    if f"[[{link_title}]]" in content:
        raise ValueError(f"この MOC はすでに root に存在します: [[{link_title}]]")

    lines = content.splitlines(keepends=True)
    section_line_index = None
    next_section_line_index = len(lines)

    for idx, line in enumerate(lines):
        match = SECTION_RE.match(line.strip("\n"))
        if not match:
            continue
        heading = match.group(1).strip()
        if section_line_index is None and heading == section:
            section_line_index = idx
            continue
        if section_line_index is not None:
            next_section_line_index = idx
            break

    if section_line_index is None:
        raise ValueError(f"指定セクションが見つかりません: {section}")

    bullet = f"- [[{link_title}]]\n"

    insert_at = next_section_line_index
    while insert_at > section_line_index + 1 and lines[insert_at - 1].strip() == "":
        insert_at -= 1

    new_lines = lines[:insert_at] + [bullet, "\n"] + lines[insert_at:]
    return "".join(new_lines)


def sync_up_link(content: str, root_title: str, *, force: bool) -> tuple[str, str]:
    desired = f"Up: [[{root_title}]]"
    match = UP_LINE_RE.search(content)
    if match:
        current = match.group(0)
        if current == desired:
            return content, "unchanged"
        if not force:
            raise ValueError(
                "子 MOC に別の Up リンクがあります: "
                f"`{current}`. 置き換えるなら `--force-up-link` を使ってください。"
            )
        start, end = match.span()
        return content[:start] + desired + content[end:], "replaced"

    lines = content.splitlines(keepends=True)
    h1_index = None
    for idx, line in enumerate(lines):
        if line.startswith("# "):
            h1_index = idx
            break

    if h1_index is None:
        new_content = content.rstrip() + f"\n\n{desired}\n"
        return new_content, "appended"

    insert_at = h1_index + 1
    if insert_at < len(lines) and lines[insert_at].strip() != "":
        lines.insert(insert_at, "\n")
        insert_at += 1
    elif insert_at >= len(lines):
        lines.append("\n")
        insert_at += 1

    lines.insert(insert_at, desired + "\n")
    lines.insert(insert_at + 1, "\n")
    return "".join(lines), "inserted"


def main() -> None:
    parser = argparse.ArgumentParser(description="子 MOC を root MOC に安全に追加し、必要なら Up リンクも同期する。")
    parser.add_argument("--root", required=True, help="追記先 root MOC のパス")
    parser.add_argument("--child", required=True, help="root にぶら下げる子 MOC のパス")
    parser.add_argument("--section", required=True, help="root 側で追記する見出し名（## を除く）")
    parser.add_argument("--summary", default="", help="root 側に添える短い文脈説明")
    parser.add_argument("--sync-up-link", action="store_true", help="子 MOC の Up リンクを root に合わせて同期する")
    parser.add_argument("--force-up-link", action="store_true", help="既存の Up リンクが別でも置き換える")
    parser.add_argument("--dry-run", action="store_true", help="書き込まず変更内容だけ確認する")
    args = parser.parse_args()

    root_path = resolve_path(args.root)
    child_path = resolve_path(args.child)
    ensure_moc_path(root_path, "root MOC")
    ensure_moc_path(child_path, "子 MOC")
    if root_path == child_path:
        raise ValueError("root MOC と子 MOC は別ファイルである必要があります。")

    root_content = root_path.read_text(encoding="utf-8")
    child_content = child_path.read_text(encoding="utf-8")
    root_frontmatter, _ = split_frontmatter(root_content)
    child_frontmatter, _ = split_frontmatter(child_content)
    root_title = extract_title(root_content, root_path)
    child_title = extract_title(child_content, child_path)
    root_moc_type = extract_moc_type(root_frontmatter)
    child_moc_type = extract_moc_type(child_frontmatter)

    if root_moc_type != "root":
        raise ValueError(f"追記先は root MOC である必要があります: {root_title} ({root_moc_type})")
    if child_moc_type == "root" and root_title == child_title:
        raise ValueError("同じ root MOC を自分自身に追加することはできません。")

    new_root_content = build_root_entry(
        root_content,
        args.section,
        child_title,
        args.summary,
    )

    up_status = "skipped"
    new_child_content = child_content
    if args.sync_up_link:
        new_child_content, up_status = sync_up_link(
            child_content,
            root_title,
            force=args.force_up_link,
        )

    if args.dry_run:
        print(f"ROOT: {root_path}")
        print(f"CHILD: {child_path}")
        print(f"ENTRY: - [[{child_title}]]" + (f"：{args.summary.strip()}" if args.summary.strip() else ""))
        print(f"UP_LINK: {up_status}")
        return

    root_path.write_text(new_root_content, encoding="utf-8")
    if args.sync_up_link and new_child_content != child_content:
        child_path.write_text(new_child_content, encoding="utf-8")

    print(f"✅ root 追記完了: {root_path}")
    print(f"entry=- [[{child_title}]]" + (f"：{args.summary.strip()}" if args.summary.strip() else ""))
    if args.sync_up_link:
        print(f"✅ Up リンク同期: {child_path}")
        print(f"up_link={up_status}")


if __name__ == "__main__":
    try:
        main()
    except Exception as exc:
        print(f"ERROR: {exc}", file=sys.stderr)
        sys.exit(1)
