#!/usr/bin/env python3
"""List forms, templates, commands, and module files related to a 1C object."""

from __future__ import annotations

import argparse
import base64
import json
import sys
from pathlib import Path
from typing import Any

sys.path.insert(0, str(Path(__file__).resolve().parent))
from resolve_1c_object import resolve_object  # noqa: E402


def load_json(path: Path) -> dict[str, Any]:
    return json.loads(path.read_text(encoding="utf-8-sig"))


def decode_arg(value: str | None, encoded: str | None) -> str | None:
    if encoded:
        return base64.b64decode(encoded).decode("utf-8")
    return value


def normalize(value: str) -> str:
    return value.casefold().replace(" ", "")


def top_objects(item: dict[str, Any]) -> list[dict[str, Any]]:
    return item.get("xml_top_objects") or []


def find_owner(index: dict[str, Any], *, kind: str, name: str) -> tuple[str, dict[str, Any], dict[str, Any], dict[str, Any]]:
    resolution = resolve_object(index, kind=kind, name=name, limit=50)
    canonical = resolution.get("canonical")
    if not canonical:
        raise SystemExit(f"Object not found: {kind}.{name}")
    guid = canonical["guid"]
    item = (index.get("objects") or {}).get(guid)
    if not item:
        raise SystemExit(f"Resolved object is missing from index: {guid}")
    wanted_path = canonical.get("relative_path")
    owner_top = next((top for top in top_objects(item) if top.get("relative_path") == wanted_path), None)
    if not owner_top:
        owner_top = next((top for top in top_objects(item) if top.get("name") == canonical.get("name")), None)
    if not owner_top:
        raise SystemExit(f"Resolved object XML route is missing from index: {guid}")
    return guid, item, owner_top, resolution


def owner_prefix(relative_path: str) -> str:
    if relative_path.lower().endswith(".xml"):
        return relative_path[:-4]
    return relative_path


def classify_child(owner: str, top: dict[str, Any]) -> str | None:
    relative = str(top.get("relative_path") or "")
    if not relative.startswith(owner + "\\"):
        return None
    rest = relative[len(owner) + 1 :]
    first = rest.split("\\", 1)[0]
    if first in {"Forms", "Templates", "Commands"}:
        return first[:-1].lower() if first.endswith("s") else first.lower()
    return "other"


def collect_xml_children(index: dict[str, Any], owner: str) -> dict[str, list[dict[str, Any]]]:
    result: dict[str, list[dict[str, Any]]] = {"form": [], "template": [], "command": [], "other": []}
    for guid, item in (index.get("objects") or {}).items():
        for top in top_objects(item):
            category = classify_child(owner, top)
            if not category:
                continue
            result.setdefault(category, []).append(
                {
                    "guid": guid,
                    "xml_kind": top.get("xml_kind"),
                    "name": top.get("name"),
                    "synonym": top.get("synonym"),
                    "relative_path": top.get("relative_path"),
                    "path": top.get("path"),
                    "route_kind": item.get("route_kind") or [],
                    "dbnames": item.get("dbnames") or [],
                    "extension_routes": item.get("extension_routes") or [],
                }
            )
    for values in result.values():
        values.sort(key=lambda row: row.get("relative_path") or "")
    return result


def file_record(path: Path, *, root: Path) -> dict[str, Any]:
    try:
        relative = str(path.relative_to(root))
    except ValueError:
        relative = str(path)
    return {
        "path": str(path),
        "relative_to_object_dir": relative,
        "size": path.stat().st_size,
        "suffix": path.suffix.lower(),
    }


def collect_files(owner_top: dict[str, Any]) -> dict[str, list[dict[str, Any]]]:
    owner_xml = Path(str(owner_top.get("path") or ""))
    owner_dir = owner_xml.with_suffix("")
    result = {"object_modules": [], "form_modules": [], "form_xml": [], "template_xml": [], "command_xml": [], "command_modules": [], "other": []}
    if not owner_dir.exists():
        return result
    for path in sorted(item for item in owner_dir.rglob("*") if item.is_file()):
        relative = str(path.relative_to(owner_dir))
        record = file_record(path, root=owner_dir)
        if relative in {"Ext\\ObjectModule.bsl", "Ext\\ManagerModule.bsl"}:
            result["object_modules"].append(record)
        elif relative.endswith("\\Ext\\Form\\Module.bsl"):
            result["form_modules"].append(record)
        elif relative.startswith("Forms\\") and path.suffix.lower() == ".xml":
            result["form_xml"].append(record)
        elif relative.startswith("Templates\\") and path.suffix.lower() == ".xml":
            result["template_xml"].append(record)
        elif relative.startswith("Commands\\") and path.suffix.lower() == ".xml":
            result["command_xml"].append(record)
        elif relative.startswith("Commands\\") and path.suffix.lower() == ".bsl":
            result["command_modules"].append(record)
        else:
            result["other"].append(record)
    return result


def main() -> int:
    parser = argparse.ArgumentParser(description="Get related 1C object artifacts.")
    parser.add_argument("--index", type=Path, required=True)
    parser.add_argument("--kind")
    parser.add_argument("--name")
    parser.add_argument("--kind-b64")
    parser.add_argument("--name-b64")
    parser.add_argument("--output", type=Path)
    args = parser.parse_args()

    kind = decode_arg(args.kind, args.kind_b64)
    name = decode_arg(args.name, args.name_b64)
    if not kind or not name:
        raise SystemExit("Use --kind/--name or --kind-b64/--name-b64.")

    index = load_json(args.index)
    guid, item, owner_top, resolution = find_owner(index, kind=kind, name=name)
    owner = owner_prefix(str(owner_top.get("relative_path") or ""))
    children = collect_xml_children(index, owner)
    files = collect_files(owner_top)
    result = {
        "schema": "onec_object_artifacts.v1",
        "query": {"kind": kind, "name": name},
        "resolution": {
            "schema": resolution.get("schema"),
            "canonical": resolution.get("canonical"),
            "summary": resolution.get("summary"),
        },
        "owner": {
            "guid": guid,
            "xml_kind": owner_top.get("xml_kind"),
            "name": owner_top.get("name"),
            "synonym": owner_top.get("synonym"),
            "relative_path": owner_top.get("relative_path"),
            "path": owner_top.get("path"),
            "route_kind": item.get("route_kind") or [],
            "dbnames": item.get("dbnames") or [],
            "config_routes": item.get("config_routes") or [],
            "extension_routes": item.get("extension_routes") or [],
        },
        "children": children,
        "files": files,
        "counts": {
            "forms": len(children.get("form") or []),
            "templates": len(children.get("template") or []),
            "commands": len(children.get("command") or []),
            "command_xml_files": len(files.get("command_xml") or []),
            "command_modules": len(files.get("command_modules") or []),
            "object_modules": len(files.get("object_modules") or []),
            "form_modules": len(files.get("form_modules") or []),
        },
    }
    text = json.dumps(result, ensure_ascii=False, indent=2) + "\n"
    if args.output:
        args.output.parent.mkdir(parents=True, exist_ok=True)
        args.output.write_text(text, encoding="utf-8")
        print(json.dumps({"output": str(args.output), "counts": result["counts"]}, ensure_ascii=True))
    else:
        print(json.dumps(result, ensure_ascii=True, indent=2))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
