#!/usr/bin/env python3
"""Read a selected 1C object module from base/effective/extension context."""

from __future__ import annotations

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

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


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 | None) -> str:
    return re.sub(r"[\s._-]+", "", str(value or "")).casefold()


def module_aliases(name: str) -> set[str]:
    n = name.strip()
    result = {normalize(n)}
    aliases = {
        "object_module": "МодульОбъекта",
        "manager_module": "МодульМенеджера",
        "form_module": "МодульФормы",
        "command_module": "МодульКоманды",
    }
    if n in aliases:
        result.add(normalize(aliases[n]))
    return result


def match_module(module: dict[str, Any], wanted: str) -> bool:
    aliases = module_aliases(wanted)
    values = {
        module.get("name"),
        module.get("kind"),
        module.get("relative_path"),
        Path(str(module.get("relative_path") or "")).stem,
    }
    return any(normalize(value) in aliases or normalize(wanted) in normalize(value) for value in values)


def read_text(path: str) -> str:
    return Path(path).read_text(encoding="utf-8-sig", errors="replace")


def routine_span(text: str, routine: str) -> tuple[int, int] | None:
    wanted = normalize(routine)
    lines = text.splitlines()
    start = None
    end_pattern = None
    for index, line in enumerate(lines):
        match = re.match(r"^\s*(Процедура|Функция)\s+([A-Za-zА-Яа-я0-9_]+)", line, re.IGNORECASE)
        if not match:
            continue
        if normalize(match.group(2)) != wanted:
            continue
        start = index
        end_pattern = "КонецПроцедуры" if match.group(1).casefold() == "процедура" else "КонецФункции"
        break
    if start is None or end_pattern is None:
        return None
    for index in range(start + 1, len(lines)):
        if re.match(rf"^\s*{end_pattern}\b", lines[index], re.IGNORECASE):
            return start, index + 1
    return start, len(lines)


def snippet(text: str, *, max_chars: int, routine: str | None) -> dict[str, Any]:
    source = text
    line_start = 1
    found_routine = None
    if routine:
        span = routine_span(text, routine)
        if span:
            lines = text.splitlines()
            source = "\n".join(lines[span[0] : span[1]])
            line_start = span[0] + 1
            found_routine = routine
        else:
            source = ""
    truncated = len(source) > max_chars
    return {
        "routine": found_routine,
        "line_start": line_start if source else None,
        "text": source[:max_chars],
        "truncated": truncated,
        "char_count": len(source),
    }


def materialize(module: dict[str, Any], *, max_chars: int, routine: str | None) -> dict[str, Any]:
    text = read_text(str(module["path"]))
    return {
        "name": module.get("name"),
        "kind": module.get("kind"),
        "origin": module.get("origin"),
        "effective_action": module.get("effective_action"),
        "relative_path": module.get("relative_path"),
        "path": module.get("path"),
        "size": module.get("size"),
        "content": snippet(text, max_chars=max_chars, routine=routine),
    }


def build_module_result(
    index: dict[str, Any],
    *,
    kind: str,
    name: str,
    module_name: str,
    view: str,
    extension: str | None,
    max_chars: int,
    routine: str | None,
) -> dict[str, Any]:
    context = build_context(index, kind=kind, name=name, view=view, extension=extension)
    matches = [module for module in context.get("modules") or [] if match_module(module, module_name)]
    if not matches:
        raise SystemExit(f"Module not found: {module_name}")
    modules = []
    for module in matches:
        modules.append(materialize(module, max_chars=max_chars, routine=routine))
        for overlay in module.get("extension_overlays") or []:
            modules.append(materialize(overlay, max_chars=max_chars, routine=routine))
    return {
        "schema": "onec_module_content.v1",
        "view": view,
        "extension": extension if view == "extension" else None,
        "object": context.get("object"),
        "query": {"module": module_name, "routine": routine, "max_chars": max_chars},
        "modules": modules,
        "counts": {"modules": len(modules)},
    }


def main() -> int:
    parser = argparse.ArgumentParser(description="Get 1C module content.")
    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("--module", required=True)
    parser.add_argument("--view", choices=["effective", "base", "extension"], default="effective")
    parser.add_argument("--extension")
    parser.add_argument("--routine")
    parser.add_argument("--max-chars", type=int, default=20000)
    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.")
    result = build_module_result(
        load_json(args.index),
        kind=kind,
        name=name,
        module_name=args.module,
        view=args.view,
        extension=args.extension,
        max_chars=args.max_chars,
        routine=args.routine,
    )
    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"], "view": result["view"]}, ensure_ascii=False))
    else:
        print(text)
    return 0


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