# OPENRGD CORE - ALIVE MODULE
# ------------------------------------------------------------------------------
# This module takes a *partial* RGD spec produced by an importer (URDF/USD/etc.)
# and merges it with a full Seed profile (00_core .. 06_ether), producing a
# fully instantiated RGD project directory:
#
#   RGD-<ROBOT_NAME>/
#       manifest.json
#       README.txt
#       spec/
#         00_core/...
#         01_foundation/...
#         ...
#         06_ether/...
#
# The goal is to transform a raw robot description into a "living" OpenRGD
# profile with identity, structure, behavior, and evolution channels.
# ------------------------------------------------------------------------------

from __future__ import annotations

import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Dict, Mapping


# Root directory where Seed profiles live.
# Expected structure:
#   src/openrgd/seed/example/seeds/<seed_name>/spec/...
SEEDS_ROOT = (
    Path(__file__).resolve().parent.parent / "seeds"
)


def _load_seed_spec(seed_name: str) -> Dict[str, str]:
    """
    Load a Seed RGD spec from disk.

    Returns a mapping of:
        "00_core/kernel.jsonc"      -> "<file contents>"
        "01_foundation/identity..." -> "<file contents>"
        ...

    The seed is considered the canonical multi-domain skeleton, on top of which
    importer-generated data will be merged.
    """
    seed_spec_root = SEEDS_ROOT / seed_name / "spec"
    if not seed_spec_root.exists():
        raise FileNotFoundError(
            f"Seed spec not found for seed '{seed_name}' at: {seed_spec_root}"
        )

    spec_map: Dict[str, str] = {}
    for path in seed_spec_root.rglob("*"):
        if path.is_file():
            rel_path = path.relative_to(seed_spec_root).as_posix()
            spec_map[rel_path] = path.read_text(encoding="utf-8")

    return spec_map


def _apply_robot_placeholders(content: str, robot_name: str, project_id: str) -> str:
    """
    Apply simple token replacement so Seeds can be generic and robots specific.

    Supported placeholders:
        {{ROBOT_NAME}}  -> robot_name
        {{PROJECT_ID}}  -> project_id
    """
    c = content.replace("{{ROBOT_NAME}}", robot_name)
    c = c.replace("{{PROJECT_ID}}", project_id)
    return c


def alive_rgd_spec(
    base_spec: Mapping[str, str],
    robot_name: str,
    seed_name: str = "default",
) -> Dict[str, str]:
    """
    Merge a partial importer-generated spec with a full Seed profile.

    The importer usually provides Foundation/Operation files, while the Seed
    provides a multi-domain skeleton (00_core .. 06_ether).

    Rules:
    - Paths in base_spec are normalized (remove leading "spec/" or "./spec/").
    - Seed files are loaded and placeholders are applied.
    - base_spec overrides Seed files on path collisions.

    Returns:
        A mapping of "rel/path.ext" -> "file contents" ready to be written
        under: RGD-<ROBOT_NAME>/spec/<rel/path.ext>
    """
    project_id = f"RGD-{robot_name}"

    # 0. Normalize base_spec paths (avoid "spec/spec/..." on disk)
    normalized_base: Dict[str, str] = {}
    for rel_path, content in base_spec.items():
        rel = rel_path

        if rel.startswith("spec/"):
            rel = rel[len("spec/") :]
        elif rel.startswith("./spec/"):
            rel = rel[len("./spec/") :]

        normalized_base[rel] = content

    base_spec = normalized_base

    # 1. Load Seed spec from disk
    seed_spec = _load_seed_spec(seed_name)

    # 2. Apply robot-specific placeholders across all Seed files
    seeded: Dict[str, str] = {}
    for rel_path, content in seed_spec.items():
        seeded[rel_path] = _apply_robot_placeholders(
            content,
            robot_name=robot_name,
            project_id=project_id,
        )

    # 3. Merge: Seed provides the skeleton, importer overrides details
    full_spec: Dict[str, str] = dict(seeded)
    full_spec.update(base_spec)

    return full_spec


def write_manifest(
    rgd_root: Path,
    robot_name: str,
    standard_version: str = "0.1.0",
) -> Path:
    """
    Write a minimal manifest.json at the root of the RGD project,
    if it does not already exist.
    """
    manifest_path = rgd_root / "manifest.json"
    if manifest_path.exists():
        return manifest_path

    project_id = f"RGD-{robot_name}"
    now_utc = datetime.now(timezone.utc).isoformat()

    manifest = {
        "rgd_manifest_version": "0.1.0",
        "project_id": project_id,
        "robot_name": robot_name,
        "standard_version": standard_version,
        "created_at": now_utc,
        "notes": "Auto-generated by the OpenRGD 'alive' pipeline.",
    }

    manifest_path.write_text(
        json.dumps(manifest, indent=2),
        encoding="utf-8",
    )
    return manifest_path


def write_readme(
    rgd_root: Path,
    robot_name: str,
    standard_version: str = "0.1.0",
) -> Path:
    """
    Write a human-readable README.txt at the root of the RGD project,
    if it does not already exist.

    This is intentionally plain text to avoid Markdown rendering issues
    in certain environments.
    """
    readme_path = rgd_root / "README.txt"
    if readme_path.exists():
        return readme_path

    project_id = f"RGD-{robot_name}"

    content = (
        f"RGD Profile: {robot_name}\n"
        f"---------------------------------------------\n"
        f"This directory contains a fully instantiated\n"
        f"OpenRGD profile for this robot.\n\n"
        f"Format: OpenRGD v{standard_version}\n"
        f"Project ID: {project_id}\n\n"
        f"Structure:\n"
        f"- manifest.json     (project metadata)\n"
        f"- spec/             (domains 00_core .. 06_ether)\n\n"
        f"Next steps:\n"
        f"- rgd compile-spec {project_id}\n"
        f"- rgd export ros2 --out synapse/ros2\n\n"
        f"Contribute back with improvements! Contact:\n"
        f"hacker@openrgd.org\n"
    )

    readme_path.write_text(content, encoding="utf-8")
    return readme_path
