"""
Unified MCP tools for Hierarchy Builder (Phase 3C-4 consolidation).

Consolidates hierarchy tools into 7 action-dispatched tools:
- hierarchy_manage(action, ...) — 11 actions for project and node CRUD
- hierarchy_mapping(action, ...) — 8 actions for source mappings and formulas
- hierarchy_io(action, ...) — 12 actions for import/export/scripts
- hierarchy_property(action, ...) — 12 actions for property management
- hierarchy_backend(action, ...) — 16 actions for NestJS backend sync
- hierarchy_graph(action, ...) — graph bridge + hierarchy intelligence/RAG actions

Original tool names are preserved as deprecation wrappers in mcp_tools.py.
Note: Legacy wrappers return json.dumps() strings; unified tools return dicts.
"""

import json
import logging
import re
from typing import Any, Dict, List, Optional

from .intelligence.advisory import build_executive_brief, suggest_reports, suggest_workflow_improvements
from .intelligence.contracts import SkillContext
from .intelligence.hierarchy_rag import build_hierarchy_rag, search_hierarchy_rag
from .config_governance import AppConfigGovernanceService

logger = logging.getLogger(__name__)

_SCRIPT_TYPES = {"insert", "view", "all"}
_SQL_IDENTIFIER_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_$]*$")

# ============================================================================
# Lazy singletons
# ============================================================================

_service = None
_sync_service = None
_auto_sync_manager = None
_graph_bridge = None
_flexible_import_service = None
_flexible_import_enabled = None
_config_governance_service = None


def _normalize_project_kwargs(kwargs: Dict[str, Any]) -> Dict[str, Any]:
    """Back-compat: allow legacy `id` as an alias for `project_id`."""
    normalized = dict(kwargs)
    if not normalized.get("project_id") and normalized.get("id"):
        normalized["project_id"] = normalized["id"]
    return normalized


def _ensure_project_payload_shape(project: Dict[str, Any]) -> Dict[str, Any]:
    """Add `project_id` mirror key when payload still uses `id`."""
    payload = dict(project)
    if payload.get("id") and not payload.get("project_id"):
        payload["project_id"] = payload["id"]
    return payload


def _ensure_service(data_dir=None):
    global _service
    if _service is None:
        from .service import HierarchyService
        _service = HierarchyService(data_dir=data_dir or "data")
    return _service


def _ensure_sync_service():
    global _sync_service, _auto_sync_manager
    if _sync_service is None:
        try:
            from .api_sync import HierarchyApiSync
            from ..config import settings
            if settings.nestjs_sync_enabled:
                _sync_service = HierarchyApiSync(
                    base_url=settings.nestjs_backend_url,
                    api_key=settings.nestjs_api_key,
                    auto_sync=True,
                )
                service = _ensure_service()
                _sync_service.set_local_service(service)
                _auto_sync_manager = _sync_service.auto_sync_manager
            else:
                _sync_service = False  # Sentinel: checked, but disabled
        except Exception:
            _sync_service = False
    return _sync_service if _sync_service is not False else None


def _ensure_auto_sync():
    global _auto_sync_manager
    if _auto_sync_manager is None:
        _ensure_sync_service()
    return _auto_sync_manager


def _ensure_graph_bridge(data_dir=None):
    global _graph_bridge
    if _graph_bridge is None:
        try:
            from .graph_bridge import HierarchyGraphBridge
            service = _ensure_service(data_dir)
            _graph_bridge = HierarchyGraphBridge(hierarchy_service=service)
        except Exception:
            _graph_bridge = False
    return _graph_bridge if _graph_bridge is not False else None


def _try_init_graph_bridge_stores():
    """Ensure GraphRAG is initialized and wire vector store / embedder into bridge."""
    bridge = _ensure_graph_bridge()
    if bridge:
        try:
            from ..config import settings as app_settings
            from ..graphrag import unified as graphrag_unified

            # Initialize GraphRAG runtime if not already ready.
            graphrag_unified._ensure_initialized(app_settings)

            if graphrag_unified._vector_store and graphrag_unified._embedder:
                bridge._vector_store = graphrag_unified._vector_store
                bridge._embedder = graphrag_unified._embedder
                return True
        except Exception as e:
            logger.debug(f"Graph bridge store init skipped: {e}")
    return False


def _ensure_flexible_import(data_dir=None):
    global _flexible_import_service, _flexible_import_enabled
    if _flexible_import_enabled is None:
        try:
            from .flexible_import import FlexibleImportService
            service = _ensure_service(data_dir)
            _flexible_import_service = FlexibleImportService(service)
            _flexible_import_enabled = True
        except ImportError:
            _flexible_import_enabled = False
    return _flexible_import_service if _flexible_import_enabled else None


def _ensure_config_governance(data_dir=None):
    global _config_governance_service
    if _config_governance_service is None:
        service = _ensure_service(data_dir)
        _config_governance_service = AppConfigGovernanceService(
            hierarchy_service=service,
            data_dir=data_dir or "data",
        )
    return _config_governance_service


def _auto_sync_operation(
    operation: str,
    project_id: str,
    hierarchy_id: Optional[str] = None,
    data: Optional[dict] = None,
) -> dict:
    """Helper to perform auto-sync after local operations."""
    asm = _ensure_auto_sync()
    if asm and asm.is_enabled:
        try:
            return asm.on_local_change(
                operation=operation,
                project_id=project_id,
                hierarchy_id=hierarchy_id,
                data=data,
            )
        except Exception as e:
            logger.warning(f"Auto-sync failed: {e}")
            return {"auto_sync": "failed", "error": str(e)}
    return {"auto_sync": "disabled"}


# ============================================================================
# hierarchy_manage action handlers (11 actions)
# ============================================================================

def _manage_create_project(data_dir, **kwargs):
    name = kwargs.get("name")
    description = kwargs.get("description", "")
    if not name:
        return {"error": "name is required for 'create_project'"}
    service = _ensure_service(data_dir)
    project = service.create_project(name, description)
    sync_result = _auto_sync_operation(
        "create_project", project.id, data={"name": name, "description": description},
    )
    return {
        "status": "success",
        "project_id": project.id,
        "project": _ensure_project_payload_shape(project.model_dump()),
        "sync": sync_result,
    }


def _manage_list_projects(data_dir, **kwargs):
    service = _ensure_service(data_dir)
    projects = service.list_projects()
    normalized_projects = [
        _ensure_project_payload_shape(p) if isinstance(p, dict) else p
        for p in projects
    ]
    return {"total": len(normalized_projects), "projects": normalized_projects}


def _manage_get_project(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'get_project'"}
    service = _ensure_service(data_dir)
    project = service.get_project(project_id)
    if not project:
        return {"error": f"Project '{project_id}' not found"}
    if isinstance(project, dict):
        return _ensure_project_payload_shape(project)
    return project


def _manage_delete_project(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'delete_project'"}
    service = _ensure_service(data_dir)
    success = service.delete_project(project_id)
    if success:
        sync_result = _auto_sync_operation("delete_project", project_id)
        return {"status": "success", "message": "Project deleted", "sync": sync_result}
    return {"error": "Project not found"}


def _manage_create_node(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    name = kwargs.get("name")
    if not project_id or not name:
        return {"error": "project_id and name are required for 'create_node'"}
    service = _ensure_service(data_dir)
    flags_str = kwargs.get("flags", "{}")
    flags_dict = json.loads(flags_str) if isinstance(flags_str, str) else (flags_str or {})
    hierarchy = service.create_hierarchy(
        project_id=project_id,
        hierarchy_name=name,
        parent_id=kwargs.get("parent_id") or None,
        description=kwargs.get("description", ""),
        flags=flags_dict,
    )
    sync_result = _auto_sync_operation(
        "create_hierarchy", project_id, hierarchy.hierarchy_id,
        data={
            "hierarchy_name": name,
            "parent_id": kwargs.get("parent_id"),
            "description": kwargs.get("description", ""),
            "flags": flags_dict,
        },
    )
    return {"status": "success", "hierarchy": hierarchy.model_dump(), "sync": sync_result}


def _manage_get_node(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    node_id = kwargs.get("node_id")
    if not project_id or not node_id:
        return {"error": "project_id and node_id are required for 'get_node'"}
    service = _ensure_service(data_dir)
    hierarchy = service.get_hierarchy(project_id, node_id)
    if not hierarchy:
        return {"error": f"Hierarchy '{node_id}' not found"}
    return hierarchy


def _manage_update_node(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    node_id = kwargs.get("node_id")
    if not project_id or not node_id:
        return {"error": "project_id and node_id are required for 'update_node'"}
    updates_str = kwargs.get("updates", "{}")
    updates_dict = json.loads(updates_str) if isinstance(updates_str, str) else (updates_str or {})
    service = _ensure_service(data_dir)
    hierarchy = service.update_hierarchy(project_id, node_id, updates_dict)
    if not hierarchy:
        return {"error": "Hierarchy not found"}
    sync_result = _auto_sync_operation(
        "update_hierarchy", project_id, node_id, data=hierarchy,
    )
    return {"status": "success", "hierarchy": hierarchy, "sync": sync_result}


def _manage_delete_node(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    node_id = kwargs.get("node_id")
    if not project_id or not node_id:
        return {"error": "project_id and node_id are required for 'delete_node'"}
    service = _ensure_service(data_dir)
    success = service.delete_hierarchy(project_id, node_id)
    if success:
        sync_result = _auto_sync_operation("delete_hierarchy", project_id, node_id)
        return {"status": "success", "message": "Hierarchy deleted", "sync": sync_result}
    return {"error": "Hierarchy not found"}


def _manage_list_nodes(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'list_nodes'"}
    service = _ensure_service(data_dir)
    hierarchies = service.list_hierarchies(project_id)
    return {"total": len(hierarchies), "hierarchies": hierarchies}


def _manage_get_tree(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'get_tree'"}
    service = _ensure_service(data_dir)
    tree = service.get_hierarchy_tree(project_id)
    return {"root_count": len(tree), "tree": tree}


def _manage_validate(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'validate'"}
    service = _ensure_service(data_dir)
    return service.validate_project(project_id)


# ============================================================================
# hierarchy_mapping action handlers (8 actions)
# ============================================================================

def _mapping_add(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    node_id = kwargs.get("node_id")
    source_table = kwargs.get("source_table")
    source_column = kwargs.get("source_column")
    if not all([project_id, node_id, source_table, source_column]):
        return {"error": "project_id, node_id, source_table, source_column are required for 'add'"}
    service = _ensure_service(data_dir)
    hierarchy = service.add_source_mapping(
        project_id=project_id,
        hierarchy_id=node_id,
        source_database=kwargs.get("source_database", ""),
        source_schema=kwargs.get("source_schema", ""),
        source_table=source_table,
        source_column=source_column,
        source_uid=kwargs.get("source_uid", ""),
        precedence_group=kwargs.get("precedence_group", "1"),
    )
    if not hierarchy:
        return {"error": "Hierarchy not found"}
    sync_result = _auto_sync_operation(
        "add_mapping", project_id, node_id,
        data={
            "source_database": kwargs.get("source_database", ""),
            "source_schema": kwargs.get("source_schema", ""),
            "source_table": source_table,
            "source_column": source_column,
            "source_uid": kwargs.get("source_uid", ""),
            "precedence_group": kwargs.get("precedence_group", "1"),
        },
    )
    return {"status": "success", "hierarchy": hierarchy, "sync": sync_result}


def _mapping_remove(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    node_id = kwargs.get("node_id")
    mapping_index = kwargs.get("mapping_index")
    if not project_id or not node_id:
        return {"error": "project_id and node_id are required for 'remove'"}
    if mapping_index is None:
        return {"error": "mapping_index is required for 'remove'"}
    service = _ensure_service(data_dir)
    hierarchy = service.remove_source_mapping(project_id, node_id, int(mapping_index))
    if not hierarchy:
        return {"error": "Hierarchy not found"}
    return {"status": "success", "hierarchy": hierarchy}


def _mapping_inherited(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    node_id = kwargs.get("node_id")
    if not project_id or not node_id:
        return {"error": "project_id and node_id are required for 'inherited'"}
    service = _ensure_service(data_dir)
    return service.get_inherited_mappings(project_id, node_id)


def _mapping_summary(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'summary'"}
    service = _ensure_service(data_dir)
    return service.get_mapping_summary(project_id)


def _mapping_by_precedence(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    node_id = kwargs.get("node_id")
    if not project_id or not node_id:
        return {"error": "project_id and node_id are required for 'by_precedence'"}
    precedence_group = kwargs.get("precedence_group")
    service = _ensure_service(data_dir)
    result = service.get_inherited_mappings(project_id, node_id)
    if "error" in result:
        return result
    by_precedence = result.get("by_precedence", {})
    if precedence_group:
        filtered = {precedence_group: by_precedence.get(precedence_group, [])}
        return {
            "hierarchy_id": result.get("hierarchy_id"),
            "precedence_group": precedence_group,
            "mappings": filtered.get(precedence_group, []),
            "count": len(filtered.get(precedence_group, [])),
        }
    return {
        "hierarchy_id": result.get("hierarchy_id"),
        "precedence_groups": result.get("precedence_groups", []),
        "by_precedence": by_precedence,
        "counts_by_group": {k: len(v) for k, v in by_precedence.items()},
    }


def _mapping_create_formula_group(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    group_name = kwargs.get("group_name")
    node_id = kwargs.get("node_id")
    if not project_id or not group_name or not node_id:
        return {"error": "project_id, node_id, and group_name are required for 'create_formula_group'"}
    rules_str = kwargs.get("rules", "[]")
    rules_list = json.loads(rules_str) if isinstance(rules_str, str) else (rules_str or [])
    service = _ensure_service(data_dir)
    hierarchy = service.create_formula_group(
        project_id=project_id,
        main_hierarchy_id=node_id,
        group_name=group_name,
        rules=rules_list,
    )
    if not hierarchy:
        return {"error": "Hierarchy not found"}
    return {"status": "success", "hierarchy": hierarchy}


def _mapping_add_formula_rule(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    node_id = kwargs.get("node_id")
    operation = kwargs.get("operation")
    source_hierarchy_id = kwargs.get("source_hierarchy_id")
    if not all([project_id, node_id, operation, source_hierarchy_id]):
        return {"error": "project_id, node_id, operation, source_hierarchy_id are required for 'add_formula_rule'"}
    const_str = kwargs.get("constant_number", "")
    const = float(const_str) if const_str else None
    service = _ensure_service(data_dir)
    hierarchy = service.add_formula_rule(
        project_id=project_id,
        main_hierarchy_id=node_id,
        operation=operation,
        source_hierarchy_id=source_hierarchy_id,
        precedence=int(kwargs.get("precedence", 1)),
        constant_number=const,
    )
    if not hierarchy:
        return {"error": "Hierarchy not found"}
    return {"status": "success", "hierarchy": hierarchy}


def _mapping_list_formula_groups(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'list_formula_groups'"}
    service = _ensure_service(data_dir)
    groups = service.list_formula_groups(project_id)
    return {"total": len(groups), "formula_groups": groups}


# ============================================================================
# hierarchy_io action handlers (12 actions)
# ============================================================================

def _io_export_csv(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'export_csv'"}
    service = _ensure_service(data_dir)
    csv_content = service.export_hierarchy_csv(project_id)
    project = service.get_project(project_id)
    project_name = project.get("name", "export").upper().replace(" ", "_")
    filename = f"{project_name}_HIERARCHY.csv"
    return {
        "filename": filename,
        "content": csv_content,
        "row_count": len(csv_content.split("\n")) - 1,
        "note": "Also export mappings using hierarchy_io(action='export_mapping_csv') for complete backup",
    }


def _io_export_mapping_csv(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'export_mapping_csv'"}
    service = _ensure_service(data_dir)
    csv_content = service.export_mapping_csv(project_id)
    project = service.get_project(project_id)
    project_name = project.get("name", "export").upper().replace(" ", "_")
    filename = f"{project_name}_HIERARCHY_MAPPING.csv"
    return {
        "filename": filename,
        "content": csv_content,
        "row_count": len(csv_content.split("\n")) - 1,
        "note": "Also export hierarchy structure using hierarchy_io(action='export_csv') for complete backup",
    }


def _io_export_json(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'export_json'"}
    service = _ensure_service(data_dir)
    return service.export_project_json(project_id)


def _io_export_simplified(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'export_simplified'"}
    flex = _ensure_flexible_import()
    if not flex:
        return {"error": "Flexible import module not available"}
    service = _ensure_service()
    project = service.get_project(project_id)
    if not project:
        return {"error": f"Project '{project_id}' not found"}
    target_tier = kwargs.get("target_tier", "tier_2")
    result = flex.export_simplified(project_id=project_id, target_tier=target_tier)
    if "error" in result:
        return {"error": result["error"]}
    return {
        "status": "success",
        "project_id": project_id,
        "project_name": project.get("name"),
        **result,
    }


def _io_import_csv(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    csv_content = kwargs.get("csv_content")
    if not project_id or not csv_content:
        return {"error": "project_id and csv_content are required for 'import_csv'"}
    service = _ensure_service(data_dir)
    result = service.import_hierarchy_csv(project_id, csv_content)
    result["next_step"] = "Now import the mapping CSV using hierarchy_io(action='import_mapping_csv')"
    result["expected_file"] = "File ending with HIERARCHY_MAPPING.CSV"
    return result


def _io_import_mapping_csv(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    csv_content = kwargs.get("csv_content")
    if not project_id or not csv_content:
        return {"error": "project_id and csv_content are required for 'import_mapping_csv'"}
    service = _ensure_service(data_dir)
    return service.import_mapping_csv(project_id, csv_content)


def _io_generate_scripts(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'generate_scripts'"}

    script_type = str(kwargs.get("script_type", "all")).strip().lower()
    if script_type not in _SCRIPT_TYPES:
        return {
            "error": f"Invalid script_type '{script_type}'. Use one of: insert, view, all",
            "valid_script_types": sorted(_SCRIPT_TYPES),
        }

    service = _ensure_service(data_dir)
    project = service.get_project(project_id)
    if not project:
        return {"error": f"Project '{project_id}' not found"}

    table_name = kwargs.get("table_name", "HIERARCHY_MASTER")
    view_name = kwargs.get("view_name", "V_HIERARCHY_MASTER")
    if not isinstance(table_name, str) or not _SQL_IDENTIFIER_RE.match(table_name):
        return {
            "error": f"Invalid table_name '{table_name}'. Must be a simple SQL identifier.",
            "example": "HIERARCHY_MASTER",
        }
    if not isinstance(view_name, str) or not _SQL_IDENTIFIER_RE.match(view_name):
        return {
            "error": f"Invalid view_name '{view_name}'. Must be a simple SQL identifier.",
            "example": "V_HIERARCHY_MASTER",
        }

    hierarchies = service.list_hierarchies(project_id)
    scripts = {}
    generated = []
    notes = []

    if script_type in ["insert", "all"]:
        scripts["insert"] = service.generate_insert_script(project_id, table_name)
        generated.append("insert")
        if not hierarchies:
            notes.append("Project has no hierarchy nodes; insert script contains no INSERT statements.")
    if script_type in ["view", "all"]:
        scripts["view"] = service.generate_view_script(project_id, view_name)
        generated.append("view")

    metadata = {
        "insert_line_count": len((scripts.get("insert") or "").splitlines()) if "insert" in scripts else 0,
        "view_line_count": len((scripts.get("view") or "").splitlines()) if "view" in scripts else 0,
    }

    return {
        "status": "success",
        "project_id": project_id,
        "project_name": project.get("name", ""),
        "script_type": script_type,
        "generated_scripts": generated,
        "hierarchy_count": len(hierarchies),
        "backend_config_parity": {
            "tableName": table_name,
            "viewName": view_name,
            "includeInsert": "insert" in generated,
            "includeView": "view" in generated,
        },
        "scripts": scripts,
        "metadata": metadata,
        "notes": notes,
    }


def _io_detect_format(data_dir, **kwargs):
    csv_content = kwargs.get("csv_content")
    if not csv_content:
        return {"error": "csv_content is required for 'detect_format'"}
    try:
        from .flexible_import import FormatDetector
    except ImportError:
        return {"error": "Flexible import module not available"}
    filename = kwargs.get("filename", "")
    analysis = FormatDetector.analyze(csv_content, filename)
    return {"status": "success", **analysis}


def _io_configure_defaults(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'configure_defaults'"}
    flex = _ensure_flexible_import(data_dir)
    if not flex:
        return {"error": "Flexible import module not available"}
    service = _ensure_service(data_dir)
    project = service.get_project(project_id)
    if not project:
        return {"error": f"Project '{project_id}' not found"}
    defaults = flex.configure_defaults(
        project_id=project_id,
        source_database=kwargs.get("source_database", ""),
        source_schema=kwargs.get("source_schema", ""),
        source_table=kwargs.get("source_table", ""),
        source_column=kwargs.get("source_column", ""),
    )
    return {
        "status": "success",
        "project_id": project_id,
        "project_name": project.get("name"),
        "defaults": defaults.to_dict(),
        "is_complete": defaults.is_complete(),
    }


def _io_get_defaults(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'get_defaults'"}
    flex = _ensure_flexible_import(data_dir)
    if not flex:
        return {"error": "Flexible import module not available"}
    service = _ensure_service(data_dir)
    project = service.get_project(project_id)
    if not project:
        return {"error": f"Project '{project_id}' not found"}
    defaults = flex.get_defaults(project_id)
    if defaults:
        return {
            "status": "success",
            "project_id": project_id,
            "project_name": project.get("name"),
            "defaults": defaults.to_dict(),
            "is_complete": defaults.is_complete(),
        }
    return {
        "status": "success",
        "project_id": project_id,
        "project_name": project.get("name"),
        "defaults": None,
        "is_complete": False,
        "message": "No defaults configured. Use hierarchy_io(action='configure_defaults') to set them.",
    }


def _io_preview_import(data_dir, **kwargs):
    csv_content = kwargs.get("csv_content")
    if not csv_content:
        return {"error": "csv_content is required for 'preview_import'"}
    flex = _ensure_flexible_import(data_dir)
    if not flex:
        return {"error": "Flexible import module not available"}
    format_type = kwargs.get("format_type", "auto")
    source_defaults_str = kwargs.get("source_defaults", "{}")
    defaults_dict = json.loads(source_defaults_str) if isinstance(source_defaults_str, str) else (source_defaults_str or {})
    normalized_defaults = {
        "source_database": defaults_dict.get("source_database") or defaults_dict.get("database", ""),
        "source_schema": defaults_dict.get("source_schema") or defaults_dict.get("schema", ""),
        "source_table": defaults_dict.get("source_table") or defaults_dict.get("table", ""),
        "source_column": defaults_dict.get("source_column") or defaults_dict.get("column", ""),
    }
    limit = int(kwargs.get("limit", 10))
    preview = flex.preview_import(
        content=csv_content, format_type=format_type,
        source_defaults=normalized_defaults, limit=limit,
    )
    return {"status": "success", **preview}


def _io_import_flexible(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    csv_content = kwargs.get("csv_content")
    if not project_id or not csv_content:
        return {"error": "project_id and csv_content are required for 'import_flexible'"}
    flex = _ensure_flexible_import(data_dir)
    if not flex:
        return {"error": "Flexible import module not available"}
    format_type = kwargs.get("format_type", "auto")
    tier_hint = kwargs.get("tier_hint", "auto")
    source_defaults_str = kwargs.get("source_defaults", "{}")
    defaults_dict = json.loads(source_defaults_str) if isinstance(source_defaults_str, str) else (source_defaults_str or {})
    normalized_defaults = None
    if defaults_dict:
        normalized_defaults = {
            "source_database": defaults_dict.get("source_database") or defaults_dict.get("database", ""),
            "source_schema": defaults_dict.get("source_schema") or defaults_dict.get("schema", ""),
            "source_table": defaults_dict.get("source_table") or defaults_dict.get("table", ""),
            "source_column": defaults_dict.get("source_column") or defaults_dict.get("column", ""),
        }
    result = flex.import_flexible(
        project_id=project_id, content=csv_content,
        format_type=format_type, source_defaults=normalized_defaults,
        tier_hint=tier_hint,
    )
    if "error" in result:
        return {"error": result["error"]}
    # Auto-sync created hierarchies
    if result.get("created_hierarchies"):
        for h in result["created_hierarchies"]:
            _auto_sync_operation(
                "create_hierarchy", project_id, h.get("hierarchy_id"), data=h,
            )
    return {"status": result.get("status", "success"), **result}


# ============================================================================
# hierarchy_property action handlers (12 actions)
# ============================================================================

def _prop_add(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    node_id = kwargs.get("node_id")
    key = kwargs.get("key")
    value = kwargs.get("value")
    if not all([project_id, node_id, key]):
        return {"error": "project_id, node_id, and key are required for 'add'"}
    if value is None:
        return {"error": "value is required for 'add'"}
    service = _ensure_service(data_dir)
    # Parse value — try JSON first
    try:
        parsed_value = json.loads(value) if isinstance(value, str) else value
    except (json.JSONDecodeError, TypeError):
        parsed_value = value
    category = kwargs.get("category", "custom")
    level_str = kwargs.get("level", "")
    parsed_level = int(level_str) if level_str and str(level_str).isdigit() else None
    inherit_str = kwargs.get("inherit", "true")
    inherit = str(inherit_str).lower() == "true"
    override_str = kwargs.get("override_allowed", "true")
    override_allowed = str(override_str).lower() == "true"
    description = kwargs.get("description", "")
    result = service.add_property(
        project_id=project_id, hierarchy_id=node_id, name=key,
        value=parsed_value, category=category, level=parsed_level,
        inherit=inherit, override_allowed=override_allowed,
        description=description,
    )
    if not result:
        return {"error": f"Hierarchy '{node_id}' not found"}
    sync_result = _auto_sync_operation(
        "update_hierarchy", project_id, node_id, data={"property_added": key},
    )
    return {
        "status": "success",
        "property": {
            "name": key, "value": parsed_value, "category": category,
            "level": parsed_level, "inherit": inherit,
        },
        "hierarchy_id": node_id,
        "sync": sync_result,
    }


def _prop_remove(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    node_id = kwargs.get("node_id")
    key = kwargs.get("key")
    if not all([project_id, node_id, key]):
        return {"error": "project_id, node_id, and key are required for 'remove'"}
    service = _ensure_service(data_dir)
    level_str = kwargs.get("level", "")
    parsed_level = int(level_str) if level_str and str(level_str).isdigit() else None
    result = service.remove_property(
        project_id=project_id, hierarchy_id=node_id,
        name=key, level=parsed_level,
    )
    if not result:
        return {"error": f"Property '{key}' not found on '{node_id}'"}
    sync_result = _auto_sync_operation(
        "update_hierarchy", project_id, node_id, data={"property_removed": key},
    )
    return {
        "status": "success",
        "message": f"Property '{key}' removed",
        "hierarchy_id": node_id,
        "sync": sync_result,
    }


def _prop_get(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    node_id = kwargs.get("node_id")
    if not project_id or not node_id:
        return {"error": "project_id and node_id are required for 'get'"}
    service = _ensure_service(data_dir)
    category = kwargs.get("category", "")
    include_inherited_str = kwargs.get("include_inherited", "true")
    include_inherited = str(include_inherited_str).lower() == "true"
    # Try hierarchy_id first, then UUID
    hierarchy = service.get_hierarchy(project_id, node_id)
    if not hierarchy:
        try:
            hierarchy = service.get_hierarchy_by_id(node_id)
        except Exception:
            pass
    if not hierarchy:
        return {"error": f"Hierarchy '{node_id}' not found"}
    hierarchy_uuid = hierarchy.get("id")
    if include_inherited:
        result = service.get_inherited_properties(project_id, hierarchy_uuid)
        if "error" in result:
            return {"error": result["error"]}
    else:
        own_props = service.get_properties(
            project_id, hierarchy.get("hierarchy_id"),
            category=category if category else None,
        )
        result = {
            "hierarchy_id": hierarchy.get("hierarchy_id"),
            "hierarchy_name": hierarchy.get("hierarchy_name"),
            "own_properties": own_props,
            "effective_properties": own_props,
            "inherited_properties": [],
        }
    if category:
        result["effective_properties"] = [
            p for p in result.get("effective_properties", [])
            if p.get("category") == category
        ]
    result["dimension_props"] = hierarchy.get("dimension_props")
    result["fact_props"] = hierarchy.get("fact_props")
    result["filter_props"] = hierarchy.get("filter_props")
    result["display_props"] = hierarchy.get("display_props")
    result["property_template_id"] = hierarchy.get("property_template_id")
    return {"status": "success", **result}


def _prop_set_dimension(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    node_id = kwargs.get("node_id")
    if not project_id or not node_id:
        return {"error": "project_id and node_id are required for 'set_dimension'"}
    props_str = kwargs.get("props", "{}")
    props_dict = json.loads(props_str) if isinstance(props_str, str) else (props_str or {})
    service = _ensure_service(data_dir)
    result = service.set_dimension_props(project_id, node_id, props_dict)
    if not result:
        return {"error": f"Hierarchy '{node_id}' not found"}
    sync_result = _auto_sync_operation(
        "update_hierarchy", project_id, node_id, data={"dimension_props_updated": True},
    )
    return {"status": "success", "hierarchy_id": node_id, "dimension_props": props_dict, "sync": sync_result}


def _prop_set_fact(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    node_id = kwargs.get("node_id")
    if not project_id or not node_id:
        return {"error": "project_id and node_id are required for 'set_fact'"}
    props_str = kwargs.get("props", "{}")
    props_dict = json.loads(props_str) if isinstance(props_str, str) else (props_str or {})
    service = _ensure_service(data_dir)
    result = service.set_fact_props(project_id, node_id, props_dict)
    if not result:
        return {"error": f"Hierarchy '{node_id}' not found"}
    sync_result = _auto_sync_operation(
        "update_hierarchy", project_id, node_id, data={"fact_props_updated": True},
    )
    return {"status": "success", "hierarchy_id": node_id, "fact_props": props_dict, "sync": sync_result}


def _prop_set_filter(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    node_id = kwargs.get("node_id")
    if not project_id or not node_id:
        return {"error": "project_id and node_id are required for 'set_filter'"}
    props_str = kwargs.get("props", "{}")
    props_dict = json.loads(props_str) if isinstance(props_str, str) else (props_str or {})
    service = _ensure_service(data_dir)
    result = service.set_filter_props(project_id, node_id, props_dict)
    if not result:
        return {"error": f"Hierarchy '{node_id}' not found"}
    sync_result = _auto_sync_operation(
        "update_hierarchy", project_id, node_id, data={"filter_props_updated": True},
    )
    return {"status": "success", "hierarchy_id": node_id, "filter_props": props_dict, "sync": sync_result}


def _prop_set_display(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    node_id = kwargs.get("node_id")
    if not project_id or not node_id:
        return {"error": "project_id and node_id are required for 'set_display'"}
    props_str = kwargs.get("props", "{}")
    props_dict = json.loads(props_str) if isinstance(props_str, str) else (props_str or {})
    service = _ensure_service(data_dir)
    result = service.set_display_props(project_id, node_id, props_dict)
    if not result:
        return {"error": f"Hierarchy '{node_id}' not found"}
    sync_result = _auto_sync_operation(
        "update_hierarchy", project_id, node_id, data={"display_props_updated": True},
    )
    return {"status": "success", "hierarchy_id": node_id, "display_props": props_dict, "sync": sync_result}


def _prop_list_templates(data_dir, **kwargs):
    service = _ensure_service(data_dir)
    templates = service.get_property_templates()
    return {
        "status": "success",
        "total": len(templates),
        "templates": [
            {
                "id": t["id"],
                "name": t["name"],
                "description": t.get("description"),
                "category": t.get("category"),
                "tags": t.get("tags", []),
            }
            for t in templates
        ],
    }


def _prop_get_template(data_dir, **kwargs):
    template_name = kwargs.get("template_name")
    if not template_name:
        return {"error": "template_name is required for 'get_template'"}
    service = _ensure_service(data_dir)
    templates = service.get_property_templates()
    template = next((t for t in templates if t["id"] == template_name), None)
    if not template:
        return {"error": f"Template '{template_name}' not found"}
    return {"status": "success", "template": template}


def _prop_apply_template(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    node_id = kwargs.get("node_id")
    template_name = kwargs.get("template_name")
    if not all([project_id, node_id, template_name]):
        return {"error": "project_id, node_id, and template_name are required for 'apply_template'"}
    merge_str = kwargs.get("merge", "true")
    merge = str(merge_str).lower() == "true"
    service = _ensure_service(data_dir)
    result = service.apply_property_template(
        project_id=project_id, hierarchy_id=node_id,
        template_id=template_name, merge=merge,
    )
    if not result:
        return {"error": f"Hierarchy '{node_id}' not found"}
    if "error" in result:
        return {"error": result["error"]}
    sync_result = _auto_sync_operation(
        "update_hierarchy", project_id, node_id, data={"template_applied": template_name},
    )
    return {
        "status": "success",
        "hierarchy_id": node_id,
        "template_id": template_name,
        "merge_mode": merge,
        "sync": sync_result,
    }


def _prop_bulk_set(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    properties_json = kwargs.get("properties_json")
    if not project_id:
        return {"error": "project_id is required for 'bulk_set'"}
    if not properties_json:
        return {"error": "properties_json is required for 'bulk_set'"}
    # properties_json can be: {"hierarchy_ids": [...], "name": "...", "value": "...", "category": "...", "inherit": "true"}
    props = json.loads(properties_json) if isinstance(properties_json, str) else properties_json
    hierarchy_ids = props.get("hierarchy_ids", [])
    name = props.get("name")
    value = props.get("value")
    if not hierarchy_ids or not name:
        return {"error": "properties_json must contain 'hierarchy_ids' (list) and 'name'"}
    # Parse value
    try:
        parsed_value = json.loads(value) if isinstance(value, str) else value
    except (json.JSONDecodeError, TypeError):
        parsed_value = value
    category = props.get("category", "custom")
    inherit_str = props.get("inherit", "true")
    inherit = str(inherit_str).lower() == "true"
    service = _ensure_service(data_dir)
    result = service.bulk_set_property(
        project_id=project_id, hierarchy_ids=hierarchy_ids,
        name=name, value=parsed_value, category=category, inherit=inherit,
    )
    return {"status": "success" if result["error_count"] == 0 else "partial", **result}


def _prop_summary(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'summary'"}
    service = _ensure_service(data_dir)
    project = service.get_project(project_id)
    if not project:
        return {"error": f"Project '{project_id}' not found"}
    result = service.get_properties_summary(project_id)
    return {"status": "success", "project_name": project.get("name"), **result}


# ============================================================================
# hierarchy_backend action handlers (16 actions)
# ============================================================================

def _backend_health(data_dir, **kwargs):
    sync = _ensure_sync_service()
    if not sync:
        return {"error": "Backend sync not enabled", "hint": "Set NESTJS_SYNC_ENABLED=true in config"}
    result = sync.health_check()
    sync_status = sync.get_sync_status()
    result.update(sync_status)
    return result


def _backend_configure_sync(data_dir, **kwargs):
    sync = _ensure_sync_service()
    if not sync:
        return {"error": "Backend sync not enabled", "hint": "Set NESTJS_SYNC_ENABLED=true in config"}
    enabled_str = kwargs.get("enabled", "true")
    enabled = str(enabled_str).lower() == "true"
    if enabled:
        sync.enable_auto_sync()
    else:
        sync.disable_auto_sync()
    status = sync.get_sync_status()
    return {
        "status": "success",
        "message": f"Auto-sync {'enabled' if enabled else 'disabled'}",
        "sync_config": status,
    }


def _backend_push(data_dir, **kwargs):
    sync = _ensure_sync_service()
    if not sync:
        return {"error": "Backend sync not enabled"}
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'push'"}
    service = _ensure_service(data_dir)
    backend_project_id = kwargs.get("backend_project_id") or None
    return sync.sync_project_to_backend(
        local_service=service, local_project_id=project_id,
        backend_project_id=backend_project_id,
    )


def _backend_pull(data_dir, **kwargs):
    sync = _ensure_sync_service()
    if not sync:
        return {"error": "Backend sync not enabled"}
    backend_project_id = kwargs.get("backend_project_id")
    if not backend_project_id:
        return {"error": "backend_project_id is required for 'pull'"}
    service = _ensure_service(data_dir)
    local_project_id = kwargs.get("project_id") or None
    return sync.sync_project_from_backend(
        local_service=service, backend_project_id=backend_project_id,
        local_project_id=local_project_id,
    )


def _backend_list_projects(data_dir, **kwargs):
    sync = _ensure_sync_service()
    if not sync:
        return {"error": "Backend sync not enabled"}
    projects = sync.list_projects()
    return {"source": "backend", "total": len(projects), "projects": projects}


def _backend_get_tree(data_dir, **kwargs):
    sync = _ensure_sync_service()
    if not sync:
        return {"error": "Backend sync not enabled"}
    backend_project_id = kwargs.get("backend_project_id")
    if not backend_project_id:
        return {"error": "backend_project_id is required for 'get_tree'"}
    tree = sync.get_hierarchy_tree(backend_project_id)
    return {
        "source": "backend",
        "project_id": backend_project_id,
        "root_count": len(tree),
        "tree": tree,
    }


def _backend_dashboard_stats(data_dir, **kwargs):
    sync = _ensure_sync_service()
    if not sync:
        return {"error": "Backend sync not enabled"}
    stats = sync.get_dashboard_stats()
    return {"source": "backend", "stats": stats}


def _backend_recent_activities(data_dir, **kwargs):
    sync = _ensure_sync_service()
    if not sync:
        return {"error": "Backend sync not enabled"}
    limit = int(kwargs.get("limit", 10))
    activities = sync.get_dashboard_activities(limit=limit)
    return {"source": "backend", "total": len(activities), "activities": activities}


def _backend_search(data_dir, **kwargs):
    sync = _ensure_sync_service()
    if not sync:
        return {"error": "Backend sync not enabled"}
    project_id = kwargs.get("project_id")
    query = kwargs.get("query")
    if not project_id or not query:
        return {"error": "project_id and query are required for 'search'"}
    results = sync.search_hierarchies(project_id, query)
    return {
        "source": "backend",
        "project_id": project_id,
        "query": query,
        "total": len(results),
        "results": results,
    }


def _backend_generate_deployment(data_dir, **kwargs):
    sync = _ensure_sync_service()
    if not sync:
        return {"error": "Backend sync not enabled"}
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'generate_deployment'"}
    config = {
        "tableName": kwargs.get("table_name", "HIERARCHY_MASTER"),
        "viewName": kwargs.get("view_name", "V_HIERARCHY_MASTER"),
        "includeInsert": kwargs.get("include_insert", True),
        "includeView": kwargs.get("include_view", True),
    }
    result = sync.generate_deployment_scripts(project_id, config)
    return {
        "source": "backend",
        "project_id": project_id,
        "config": config,
        "scripts": result,
    }


def _backend_push_snowflake(data_dir, **kwargs):
    sync = _ensure_sync_service()
    if not sync:
        return {"error": "Backend sync not enabled"}
    project_id = kwargs.get("project_id")
    connection_id = kwargs.get("connection_id")
    target_database = kwargs.get("target_database")
    target_schema = kwargs.get("target_schema")
    target_table = kwargs.get("target_table")
    if not all([project_id, connection_id, target_database, target_schema, target_table]):
        return {"error": "project_id, connection_id, target_database, target_schema, target_table are required for 'push_snowflake'"}
    dto = {
        "projectId": project_id,
        "connectionId": connection_id,
        "target": {
            "database": target_database,
            "schema": target_schema,
            "table": target_table,
        },
    }
    result = sync.push_to_snowflake(dto)
    return {
        "source": "backend",
        "operation": "snowflake_deployment",
        "project_id": project_id,
        "target": f"{target_database}.{target_schema}.{target_table}",
        "result": result,
    }


def _backend_deployment_history(data_dir, **kwargs):
    sync = _ensure_sync_service()
    if not sync:
        return {"error": "Backend sync not enabled"}
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'deployment_history'"}
    limit = int(kwargs.get("limit", 50))
    history = sync.get_deployment_history(project_id, limit=limit)
    return {
        "source": "backend",
        "project_id": project_id,
        "total": len(history),
        "history": history,
    }


def _backend_export_csv(data_dir, **kwargs):
    sync = _ensure_sync_service()
    if not sync:
        return {"error": "Backend sync not enabled"}
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'export_csv'"}
    from datetime import datetime
    csv_content = sync.export_hierarchy_csv_backend(project_id)
    filename = f"HIERARCHY_{project_id}_{datetime.now().strftime('%Y%m%d')}.csv"
    return {
        "source": "backend",
        "project_id": project_id,
        "filename": filename,
        "content": csv_content,
        "row_count": len(csv_content.split("\n")) - 1 if csv_content else 0,
    }


def _backend_import_csv(data_dir, **kwargs):
    sync = _ensure_sync_service()
    if not sync:
        return {"error": "Backend sync not enabled"}
    project_id = kwargs.get("project_id")
    csv_content = kwargs.get("csv_content")
    if not project_id or not csv_content:
        return {"error": "project_id and csv_content are required for 'import_csv'"}
    result = sync.import_hierarchy_csv_backend(project_id, csv_content)
    return {"source": "backend", "project_id": project_id, "result": result}


def _backend_create_filter_group(data_dir, **kwargs):
    sync = _ensure_sync_service()
    if not sync:
        return {"error": "Backend sync not enabled"}
    project_id = kwargs.get("project_id")
    group_name = kwargs.get("group_name")
    filters = kwargs.get("filters")
    if not all([project_id, group_name, filters]):
        return {"error": "project_id, group_name, and filters are required for 'create_filter_group'"}
    filters_list = json.loads(filters) if isinstance(filters, str) else filters
    body = {
        "projectId": project_id,
        "groupName": group_name,
        "filters": filters_list,
    }
    result = sync.create_filter_group(body)
    return {"source": "backend", "operation": "create_filter_group", "result": result}


def _backend_list_filter_groups(data_dir, **kwargs):
    sync = _ensure_sync_service()
    if not sync:
        return {"error": "Backend sync not enabled"}
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'list_filter_groups'"}
    groups = sync.list_filter_groups(project_id)
    return {
        "source": "backend",
        "project_id": project_id,
        "total": len(groups),
        "filter_groups": groups,
    }


# ============================================================================
# hierarchy_graph action handlers
# ============================================================================

def _graph_status(data_dir, **kwargs):
    _try_init_graph_bridge_stores()
    bridge = _ensure_graph_bridge(data_dir)
    if not bridge:
        return {"error": "Graph bridge not available"}
    service = _ensure_service(data_dir)
    status = bridge.get_status()
    project_id = kwargs.get("project_id")
    projects = service.list_projects()
    if project_id:
        projects = [p for p in projects if p.get("id") == project_id]
    project_statuses = []
    for proj in projects:
        pid = proj.get("id", "")
        hierarchies = service.list_hierarchies(pid)
        indexed_count = 0
        for h in hierarchies:
            hier_id = h.get("hierarchy_id", "")
            doc_id = f"hierarchy:{pid}:{hier_id}"
            if bridge._vector_store and bridge._vector_store.get(doc_id):
                indexed_count += 1
        project_statuses.append({
            "project_id": pid,
            "project_name": proj.get("name", ""),
            "total_hierarchies": len(hierarchies),
            "indexed_in_vector_store": indexed_count,
            "not_indexed": len(hierarchies) - indexed_count,
        })
    hierarchy_rag_path = kwargs.get("index_path", "data/hierarchy_rag/index.json")
    hierarchy_rag_status = {"index_path": hierarchy_rag_path, "exists": False}
    try:
        with open(hierarchy_rag_path, "r", encoding="utf-8") as f:
            payload = json.load(f)
        hierarchy_rag_status = {
            "index_path": hierarchy_rag_path,
            "exists": True,
            "stats": payload.get("stats", {}),
            "project_id": payload.get("project_id"),
        }
    except Exception:
        pass
    return {
        "status": "success",
        "bridge_status": status,
        "projects": project_statuses,
        "hierarchy_rag": hierarchy_rag_status,
    }


def _graph_reindex(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'reindex'"}
    _try_init_graph_bridge_stores()
    bridge = _ensure_graph_bridge(data_dir)
    if not bridge or not bridge._vector_store:
        return {"error": "Vector store not available. GraphRAG module may not be initialized."}
    result = bridge.reindex_project(project_id)
    return {"status": "success", **result}


def _graph_build_lineage(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'build_lineage'"}
    _try_init_graph_bridge_stores()
    bridge = _ensure_graph_bridge(data_dir)
    if not bridge:
        return {"error": "Graph bridge not available"}
    service = _ensure_service(data_dir)
    hierarchies = service.list_hierarchies(project_id)
    built = 0
    errors = 0
    for hier in hierarchies:
        if bridge.sync_lineage(project_id, hier):
            built += 1
        else:
            errors += 1
    return {
        "status": "success",
        "project_id": project_id,
        "lineage_nodes_built": built,
        "errors": errors,
        "lineage_tracker_available": bridge._lineage_tracker is not None,
    }


def _graph_search(data_dir, **kwargs):
    query = kwargs.get("query")
    if not query:
        return {"error": "query is required for 'search'"}
    _try_init_graph_bridge_stores()
    bridge = _ensure_graph_bridge(data_dir)
    if not bridge or not bridge._vector_store or not bridge._embedder:
        return {"error": "Vector store or embedder not available."}
    project_id = kwargs.get("project_id")
    max_results = int(kwargs.get("max_results", 5))
    query_embedding = bridge._embedder.embed(query)
    filter_metadata = {"source_type": "hierarchy"}
    if project_id:
        filter_metadata["project_id"] = project_id
    results = bridge._vector_store.search(
        embedding=query_embedding, top_k=max_results,
        filter_metadata=filter_metadata,
    )
    matches = []
    for r in results:
        matches.append({
            "hierarchy_id": r.metadata.get("hierarchy_id", ""),
            "name": r.metadata.get("name", ""),
            "project_id": r.metadata.get("project_id", ""),
            "score": round(r.score, 4) if hasattr(r, "score") else None,
            "content": r.content if hasattr(r, "content") else "",
            "has_mappings": r.metadata.get("has_mappings", False),
            "has_formula": r.metadata.get("has_formula", False),
            "level_depth": r.metadata.get("level_depth", 0),
        })
    return {"status": "success", "query": query, "results": matches, "total_results": len(matches)}


def _graph_impact_analysis(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    node_id = kwargs.get("node_id")
    if not project_id or not node_id:
        return {"error": "project_id and node_id are required for 'impact_analysis'"}
    service = _ensure_service(data_dir)
    hierarchy = service.get_hierarchy(project_id, node_id)
    if not hierarchy:
        return {"error": f"Hierarchy '{node_id}' not found"}
    operation = kwargs.get("operation", "delete")
    children = service.get_child_hierarchies(project_id, hierarchy.get("id", ""))
    descendants = service.get_all_descendants(project_id, hierarchy.get("id", ""))
    all_hierarchies = service.list_hierarchies(project_id)
    formula_dependents = []
    for h in all_hierarchies:
        fc = h.get("formula_config", {}) or {}
        fg = fc.get("formula_group", {}) or {}
        for rule in fg.get("rules", []):
            if rule.get("hierarchy_id") == node_id:
                formula_dependents.append({
                    "hierarchy_id": h.get("hierarchy_id"),
                    "hierarchy_name": h.get("hierarchy_name"),
                    "operation": rule.get("operation"),
                })
    own_mappings = hierarchy.get("mapping", [])
    descendant_mappings = sum(len(d.get("mapping", [])) for d in descendants)
    impact = {
        "hierarchy_id": node_id,
        "hierarchy_name": hierarchy.get("hierarchy_name"),
        "operation": operation,
        "direct_children": len(children),
        "total_descendants": len(descendants),
        "formula_dependents": formula_dependents,
        "own_mappings": len(own_mappings),
        "descendant_mappings": descendant_mappings,
        "total_affected": len(descendants) + len(formula_dependents) + 1,
    }
    total = impact["total_affected"]
    if total <= 1:
        impact["severity"] = "low"
        impact["recommendation"] = f"Safe to {operation}. No downstream dependencies."
    elif total <= 5:
        impact["severity"] = "medium"
        impact["recommendation"] = f"Moderate impact. {total} items affected. Review before proceeding."
    else:
        impact["severity"] = "high"
        impact["recommendation"] = f"High impact! {total} items affected. Strongly recommend reviewing all dependents first."
    if formula_dependents:
        impact["warning"] = f"{len(formula_dependents)} formula(s) reference this hierarchy and will break if deleted."
    return {"status": "success", **impact}


def _graph_build_hierarchy_rag(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'build_hierarchy_rag'"}
    index_path = kwargs.get("index_path", "data/hierarchy_rag/index.json")
    return build_hierarchy_rag(project_id=project_id, data_dir=data_dir or "data", index_path=index_path)


def _graph_search_hierarchy_rag(data_dir, **kwargs):
    query = kwargs.get("query")
    if not query:
        return {"error": "query is required for 'search_hierarchy_rag'"}
    index_path = kwargs.get("index_path", "data/hierarchy_rag/index.json")
    max_results = int(kwargs.get("max_results", 10))
    return search_hierarchy_rag(index_path=index_path, query=query, k=max_results)


def _graph_suggest_reports(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'suggest_reports'"}
    ctx = SkillContext(
        project_id=project_id,
        role=(kwargs.get("role") or "balanced").lower(),
        system=kwargs.get("system") or "unknown",
        industry=kwargs.get("industry") or "general",
        objective=kwargs.get("query") or "",
        data_dir=data_dir or "data",
    )
    return suggest_reports(ctx)


def _graph_suggest_workflow_improvements(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'suggest_workflow_improvements'"}
    ctx = SkillContext(
        project_id=project_id,
        role=(kwargs.get("role") or "balanced").lower(),
        system=kwargs.get("system") or "unknown",
        industry=kwargs.get("industry") or "general",
        objective=kwargs.get("query") or "",
        data_dir=data_dir or "data",
    )
    return suggest_workflow_improvements(ctx)


def _graph_executive_brief(data_dir, **kwargs):
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'executive_brief'"}
    ctx = SkillContext(
        project_id=project_id,
        role=(kwargs.get("role") or "balanced").lower(),
        system=kwargs.get("system") or "unknown",
        industry=kwargs.get("industry") or "general",
        objective=kwargs.get("query") or "",
        data_dir=data_dir or "data",
    )
    return build_executive_brief(ctx)


# ============================================================================
# hierarchy_config action handlers
# ============================================================================

def _config_init_project(data_dir, **kwargs):
    service = _ensure_config_governance(data_dir)
    name = kwargs.get("name")
    if not name:
        return {"error": "name is required for 'init_project'"}
    return service.init_config_project(
        project_name=name,
        description=kwargs.get("description", ""),
        app_name=kwargs.get("app_name", "NON_OP_EDITOR"),
        env=kwargs.get("env", "PROD"),
        scope=kwargs.get("scope", "DEFAULT"),
        modules=kwargs.get("modules"),
    )


def _config_add_or_update(data_dir, **kwargs):
    service = _ensure_config_governance(data_dir)
    required = ["project_id", "app_name", "env", "scope", "module", "setting_key"]
    missing = [k for k in required if not kwargs.get(k)]
    if missing:
        return {"error": f"Missing required fields: {', '.join(missing)}"}
    return service.add_or_update_setting(
        project_id=kwargs["project_id"],
        app_name=kwargs["app_name"],
        env=kwargs["env"],
        scope=kwargs["scope"],
        module=kwargs["module"],
        setting_key=kwargs["setting_key"],
        setting_value=kwargs.get("setting_value"),
        value_type=kwargs.get("value_type"),
        description=kwargs.get("description", ""),
        owner=kwargs.get("owner", ""),
        tags=kwargs.get("tags"),
        is_secret=bool(kwargs.get("is_secret", False)),
    )


def _config_create_configuration(data_dir, **kwargs):
    service = _ensure_config_governance(data_dir)
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'create_configuration'"}
    app_name = kwargs.get("app_name")
    env = kwargs.get("env")
    scope = kwargs.get("scope")
    if not app_name or not env or not scope:
        return {"error": "app_name, env, and scope are required for 'create_configuration'"}
    return service.create_configuration(
        project_id=project_id,
        app_name=app_name,
        env=env,
        scope=scope,
        modules=kwargs.get("modules"),
    )


def _config_list_configurations(data_dir, **kwargs):
    service = _ensure_config_governance(data_dir)
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'list_configurations'"}
    return service.list_configurations(
        project_id=project_id,
        app_name=kwargs.get("app_name", ""),
        env=kwargs.get("env", ""),
    )


def _config_validate_project(data_dir, **kwargs):
    service = _ensure_config_governance(data_dir)
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'validate_project'"}
    return service.validate_project(project_id=project_id)


def _config_publish(data_dir, **kwargs):
    service = _ensure_config_governance(data_dir)
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'publish'"}
    return service.publish_to_snowflake(
        project_id=project_id,
        connection_name=kwargs.get("connection_name", "default"),
        config_file=kwargs.get("config_file", ".snowflake_cli/config.toml"),
        changed_by=kwargs.get("changed_by", "hierarchy_publish"),
        note=kwargs.get("note", "Published from hierarchy"),
        refresh_hierarchy_objects=bool(kwargs.get("refresh_hierarchy_objects", True)),
    )


def _config_pull(data_dir, **kwargs):
    service = _ensure_config_governance(data_dir)
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'pull'"}
    app_name = kwargs.get("app_name")
    env = kwargs.get("env")
    if not app_name or not env:
        return {"error": "app_name and env are required for 'pull'"}
    return service.pull_from_snowflake(
        project_id=project_id,
        app_name=app_name,
        env=env,
        operator=kwargs.get("operator", ""),
        connection_name=kwargs.get("connection_name", "default"),
        config_file=kwargs.get("config_file", ".snowflake_cli/config.toml"),
    )


def _config_audit(data_dir, **kwargs):
    service = _ensure_config_governance(data_dir)
    return service.list_audit(
        app_name=kwargs.get("app_name", ""),
        env=kwargs.get("env", ""),
        operator=kwargs.get("operator", ""),
        limit=int(kwargs.get("limit", 200)),
        connection_name=kwargs.get("connection_name", "default"),
        config_file=kwargs.get("config_file", ".snowflake_cli/config.toml"),
    )


def _config_export_bundle(data_dir, **kwargs):
    service = _ensure_config_governance(data_dir)
    project_id = kwargs.get("project_id")
    if not project_id:
        return {"error": "project_id is required for 'export_bundle'"}
    return service.export_bundle(
        project_id=project_id,
        bundle_name=kwargs.get("bundle_name", ""),
        bundle_root=kwargs.get("bundle_root", ""),
        include_sql=bool(kwargs.get("include_sql", True)),
    )


# ============================================================================
# Action dispatch dictionaries
# ============================================================================

_MANAGE_ACTIONS = {
    "create_project": _manage_create_project,
    "list_projects": _manage_list_projects,
    "get_project": _manage_get_project,
    "delete_project": _manage_delete_project,
    "create_node": _manage_create_node,
    "get_node": _manage_get_node,
    "update_node": _manage_update_node,
    "delete_node": _manage_delete_node,
    "list_nodes": _manage_list_nodes,
    "get_tree": _manage_get_tree,
    "validate": _manage_validate,
}

_MAPPING_ACTIONS = {
    "add": _mapping_add,
    "remove": _mapping_remove,
    "inherited": _mapping_inherited,
    "summary": _mapping_summary,
    "by_precedence": _mapping_by_precedence,
    "create_formula_group": _mapping_create_formula_group,
    "add_formula_rule": _mapping_add_formula_rule,
    "list_formula_groups": _mapping_list_formula_groups,
}

_IO_ACTIONS = {
    "export_csv": _io_export_csv,
    "export_mapping_csv": _io_export_mapping_csv,
    "export_json": _io_export_json,
    "export_simplified": _io_export_simplified,
    "import_csv": _io_import_csv,
    "import_mapping_csv": _io_import_mapping_csv,
    "generate_scripts": _io_generate_scripts,
    "detect_format": _io_detect_format,
    "configure_defaults": _io_configure_defaults,
    "get_defaults": _io_get_defaults,
    "preview_import": _io_preview_import,
    "import_flexible": _io_import_flexible,
}

_PROPERTY_ACTIONS = {
    "add": _prop_add,
    "remove": _prop_remove,
    "get": _prop_get,
    "set_dimension": _prop_set_dimension,
    "set_fact": _prop_set_fact,
    "set_filter": _prop_set_filter,
    "set_display": _prop_set_display,
    "list_templates": _prop_list_templates,
    "get_template": _prop_get_template,
    "apply_template": _prop_apply_template,
    "bulk_set": _prop_bulk_set,
    "summary": _prop_summary,
}

_BACKEND_ACTIONS = {
    "health": _backend_health,
    "configure_sync": _backend_configure_sync,
    "push": _backend_push,
    "pull": _backend_pull,
    "list_projects": _backend_list_projects,
    "get_tree": _backend_get_tree,
    "dashboard_stats": _backend_dashboard_stats,
    "recent_activities": _backend_recent_activities,
    "search": _backend_search,
    "generate_deployment": _backend_generate_deployment,
    "push_snowflake": _backend_push_snowflake,
    "deployment_history": _backend_deployment_history,
    "export_csv": _backend_export_csv,
    "import_csv": _backend_import_csv,
    "create_filter_group": _backend_create_filter_group,
    "list_filter_groups": _backend_list_filter_groups,
}

_GRAPH_ACTIONS = {
    "status": _graph_status,
    "reindex": _graph_reindex,
    "build_lineage": _graph_build_lineage,
    "search": _graph_search,
    "impact_analysis": _graph_impact_analysis,
    "build_hierarchy_rag": _graph_build_hierarchy_rag,
    "search_hierarchy_rag": _graph_search_hierarchy_rag,
    "suggest_reports": _graph_suggest_reports,
    "suggest_workflow_improvements": _graph_suggest_workflow_improvements,
    "executive_brief": _graph_executive_brief,
}

_CONFIG_ACTIONS = {
    "init_project": _config_init_project,
    "create_configuration": _config_create_configuration,
    "list_configurations": _config_list_configurations,
    "add_or_update_setting": _config_add_or_update,
    "validate_project": _config_validate_project,
    "publish": _config_publish,
    "pull": _config_pull,
    "audit": _config_audit,
    "export_bundle": _config_export_bundle,
}


# ============================================================================
# Unified dispatch functions
# ============================================================================

def dispatch_hierarchy_manage(data_dir, action: str, **kwargs) -> Dict[str, Any]:
    """Dispatch a hierarchy_manage action."""
    handler = _MANAGE_ACTIONS.get(action)
    if not handler:
        return {"error": f"Unknown action: '{action}'", "valid_actions": sorted(_MANAGE_ACTIONS.keys())}
    try:
        normalized_kwargs = _normalize_project_kwargs(kwargs)
        return handler(data_dir, **normalized_kwargs)
    except Exception as e:
        logger.error(f"hierarchy_manage({action}) failed: {e}")
        return {"error": f"hierarchy_manage({action}) failed: {e}"}


def dispatch_hierarchy_mapping(data_dir, action: str, **kwargs) -> Dict[str, Any]:
    """Dispatch a hierarchy_mapping action."""
    handler = _MAPPING_ACTIONS.get(action)
    if not handler:
        return {"error": f"Unknown action: '{action}'", "valid_actions": sorted(_MAPPING_ACTIONS.keys())}
    try:
        normalized_kwargs = _normalize_project_kwargs(kwargs)
        return handler(data_dir, **normalized_kwargs)
    except Exception as e:
        logger.error(f"hierarchy_mapping({action}) failed: {e}")
        return {"error": f"hierarchy_mapping({action}) failed: {e}"}


def dispatch_hierarchy_io(data_dir, action: str, **kwargs) -> Dict[str, Any]:
    """Dispatch a hierarchy_io action."""
    handler = _IO_ACTIONS.get(action)
    if not handler:
        return {"error": f"Unknown action: '{action}'", "valid_actions": sorted(_IO_ACTIONS.keys())}
    try:
        normalized_kwargs = _normalize_project_kwargs(kwargs)
        return handler(data_dir, **normalized_kwargs)
    except Exception as e:
        logger.error(f"hierarchy_io({action}) failed: {e}")
        return {"error": f"hierarchy_io({action}) failed: {e}"}


def dispatch_hierarchy_property(data_dir, action: str, **kwargs) -> Dict[str, Any]:
    """Dispatch a hierarchy_property action."""
    handler = _PROPERTY_ACTIONS.get(action)
    if not handler:
        return {"error": f"Unknown action: '{action}'", "valid_actions": sorted(_PROPERTY_ACTIONS.keys())}
    try:
        normalized_kwargs = _normalize_project_kwargs(kwargs)
        return handler(data_dir, **normalized_kwargs)
    except Exception as e:
        logger.error(f"hierarchy_property({action}) failed: {e}")
        return {"error": f"hierarchy_property({action}) failed: {e}"}


def dispatch_hierarchy_backend(data_dir, action: str, **kwargs) -> Dict[str, Any]:
    """Dispatch a hierarchy_backend action."""
    handler = _BACKEND_ACTIONS.get(action)
    if not handler:
        return {"error": f"Unknown action: '{action}'", "valid_actions": sorted(_BACKEND_ACTIONS.keys())}
    try:
        normalized_kwargs = _normalize_project_kwargs(kwargs)
        return handler(data_dir, **normalized_kwargs)
    except Exception as e:
        logger.error(f"hierarchy_backend({action}) failed: {e}")
        return {"error": f"hierarchy_backend({action}) failed: {e}"}


def dispatch_hierarchy_graph(data_dir, action: str, **kwargs) -> Dict[str, Any]:
    """Dispatch a hierarchy_graph action."""
    handler = _GRAPH_ACTIONS.get(action)
    if not handler:
        return {"error": f"Unknown action: '{action}'", "valid_actions": sorted(_GRAPH_ACTIONS.keys())}
    try:
        normalized_kwargs = _normalize_project_kwargs(kwargs)
        return handler(data_dir, **normalized_kwargs)
    except Exception as e:
        logger.error(f"hierarchy_graph({action}) failed: {e}")
        return {"error": f"hierarchy_graph({action}) failed: {e}"}


def dispatch_hierarchy_config(data_dir, action: str, **kwargs) -> Dict[str, Any]:
    """Dispatch a hierarchy_config action."""
    handler = _CONFIG_ACTIONS.get(action)
    if not handler:
        return {"error": f"Unknown action: '{action}'", "valid_actions": sorted(_CONFIG_ACTIONS.keys())}
    try:
        normalized_kwargs = _normalize_project_kwargs(kwargs)
        return handler(data_dir, **normalized_kwargs)
    except Exception as e:
        logger.error(f"hierarchy_config({action}) failed: {e}")
        return {"error": f"hierarchy_config({action}) failed: {e}"}


# ============================================================================
# Registration
# ============================================================================

def register_unified_hierarchy_tools(mcp, data_dir: str):
    """Register the 6 unified hierarchy MCP tools. Returns service instance."""

    @mcp.tool()
    def hierarchy_manage(
        action: str,
        project_id: Optional[str] = None,
        id: Optional[str] = None,
        name: Optional[str] = None,
        description: Optional[str] = None,
        node_id: Optional[str] = None,
        parent_id: Optional[str] = None,
        flags: Optional[str] = None,
        updates: Optional[str] = None,
        level: Optional[str] = None,
    ) -> Dict[str, Any]:
        """
        Unified hierarchy project and node management (11 actions).

        Actions:
        - create_project: Create a new hierarchy project (requires name)
        - list_projects: List all hierarchy projects
        - get_project: Get project details (requires project_id)
        - delete_project: Delete a project (requires project_id)
        - create_node: Create a hierarchy node (requires project_id, name)
        - get_node: Get hierarchy node (requires project_id, node_id)
        - update_node: Update a node (requires project_id, node_id; updates as JSON string)
        - delete_node: Delete a node (requires project_id, node_id)
        - list_nodes: List all nodes in project (requires project_id)
        - get_tree: Get hierarchy tree (requires project_id)
        - validate: Validate project (requires project_id)

        Returns:
            Action-specific result dict
        """
        kw = {k: v for k, v in {
            "project_id": project_id, "id": id, "name": name, "description": description,
            "node_id": node_id, "parent_id": parent_id, "flags": flags,
            "updates": updates, "level": level,
        }.items() if v is not None}
        return dispatch_hierarchy_manage(data_dir, action, **kw)

    @mcp.tool()
    def hierarchy_mapping(
        action: str,
        project_id: Optional[str] = None,
        id: Optional[str] = None,
        node_id: Optional[str] = None,
        source_database: Optional[str] = None,
        source_schema: Optional[str] = None,
        source_table: Optional[str] = None,
        source_column: Optional[str] = None,
        source_uid: Optional[str] = None,
        precedence_group: Optional[str] = None,
        mapping_index: Optional[int] = None,
        group_name: Optional[str] = None,
        rules: Optional[str] = None,
        operation: Optional[str] = None,
        source_hierarchy_id: Optional[str] = None,
        precedence: Optional[int] = None,
        constant_number: Optional[str] = None,
    ) -> Dict[str, Any]:
        """
        Unified hierarchy mapping and formula management (8 actions).

        Actions:
        - add: Add source mapping (requires project_id, node_id, source_table, source_column)
        - remove: Remove mapping by index (requires project_id, node_id, mapping_index)
        - inherited: Get inherited mappings (requires project_id, node_id)
        - summary: Get mapping summary (requires project_id)
        - by_precedence: Get mappings by precedence (requires project_id, node_id)
        - create_formula_group: Create formula group (requires project_id, node_id, group_name)
        - add_formula_rule: Add formula rule (requires project_id, node_id, operation, source_hierarchy_id)
        - list_formula_groups: List formula groups (requires project_id)

        Returns:
            Action-specific result dict
        """
        kw = {k: v for k, v in {
            "project_id": project_id, "id": id, "node_id": node_id,
            "source_database": source_database, "source_schema": source_schema,
            "source_table": source_table, "source_column": source_column,
            "source_uid": source_uid, "precedence_group": precedence_group,
            "mapping_index": mapping_index, "group_name": group_name,
            "rules": rules, "operation": operation,
            "source_hierarchy_id": source_hierarchy_id,
            "precedence": precedence, "constant_number": constant_number,
        }.items() if v is not None}
        return dispatch_hierarchy_mapping(data_dir, action, **kw)

    @mcp.tool()
    def hierarchy_io(
        action: str,
        project_id: Optional[str] = None,
        id: Optional[str] = None,
        csv_content: Optional[str] = None,
        format_type: Optional[str] = None,
        source_defaults: Optional[str] = None,
        tier_hint: Optional[str] = None,
        target_tier: Optional[str] = None,
        script_type: Optional[str] = None,
        table_name: Optional[str] = None,
        view_name: Optional[str] = None,
        filename: Optional[str] = None,
        source_database: Optional[str] = None,
        source_schema: Optional[str] = None,
        source_table: Optional[str] = None,
        source_column: Optional[str] = None,
        limit: Optional[int] = None,
    ) -> Dict[str, Any]:
        """
        Unified hierarchy import/export and script generation (12 actions).

        Actions:
        - export_csv: Export hierarchy structure CSV (requires project_id)
        - export_mapping_csv: Export mapping CSV (requires project_id)
        - export_json: Export full project as JSON (requires project_id)
        - export_simplified: Export in simplified tier format (requires project_id)
        - import_csv: Import hierarchy CSV (requires project_id, csv_content)
        - import_mapping_csv: Import mapping CSV (requires project_id, csv_content)
        - generate_scripts: Generate SQL scripts (requires project_id)
        - detect_format: Detect hierarchy format (requires csv_content)
        - configure_defaults: Configure import defaults (requires project_id)
        - get_defaults: Get project import defaults (requires project_id)
        - preview_import: Preview import without persisting (requires csv_content)
        - import_flexible: Import from flexible format (requires project_id, csv_content)

        Returns:
            Action-specific result dict
        """
        kw = {k: v for k, v in {
            "project_id": project_id, "id": id, "csv_content": csv_content,
            "format_type": format_type, "source_defaults": source_defaults,
            "tier_hint": tier_hint, "target_tier": target_tier,
            "script_type": script_type, "table_name": table_name,
            "view_name": view_name, "filename": filename,
            "source_database": source_database, "source_schema": source_schema,
            "source_table": source_table, "source_column": source_column,
            "limit": limit,
        }.items() if v is not None}
        return dispatch_hierarchy_io(data_dir, action, **kw)

    @mcp.tool()
    def hierarchy_property(
        action: str,
        project_id: Optional[str] = None,
        id: Optional[str] = None,
        node_id: Optional[str] = None,
        key: Optional[str] = None,
        value: Optional[str] = None,
        category: Optional[str] = None,
        level: Optional[str] = None,
        inherit: Optional[str] = None,
        override_allowed: Optional[str] = None,
        description: Optional[str] = None,
        include_inherited: Optional[str] = None,
        props: Optional[str] = None,
        template_name: Optional[str] = None,
        merge: Optional[str] = None,
        properties_json: Optional[str] = None,
    ) -> Dict[str, Any]:
        """
        Unified hierarchy property management (12 actions).

        Actions:
        - add: Add property (requires project_id, node_id, key, value)
        - remove: Remove property (requires project_id, node_id, key)
        - get: Get properties (requires project_id, node_id)
        - set_dimension: Set dimension properties (requires project_id, node_id; props as JSON)
        - set_fact: Set fact/measure properties (requires project_id, node_id; props as JSON)
        - set_filter: Set filter properties (requires project_id, node_id; props as JSON)
        - set_display: Set display properties (requires project_id, node_id; props as JSON)
        - list_templates: List property templates
        - get_template: Get template details (requires template_name)
        - apply_template: Apply template to hierarchy (requires project_id, node_id, template_name)
        - bulk_set: Bulk set properties (requires project_id, properties_json)
        - summary: Get project property summary (requires project_id)

        Returns:
            Action-specific result dict
        """
        kw = {k: v for k, v in {
            "project_id": project_id, "id": id, "node_id": node_id,
            "key": key, "value": value, "category": category,
            "level": level, "inherit": inherit, "override_allowed": override_allowed,
            "description": description, "include_inherited": include_inherited,
            "props": props, "template_name": template_name,
            "merge": merge, "properties_json": properties_json,
        }.items() if v is not None}
        return dispatch_hierarchy_property(data_dir, action, **kw)

    @mcp.tool()
    def hierarchy_backend(
        action: str,
        project_id: Optional[str] = None,
        id: Optional[str] = None,
        backend_project_id: Optional[str] = None,
        enabled: Optional[str] = None,
        query: Optional[str] = None,
        table_name: Optional[str] = None,
        view_name: Optional[str] = None,
        include_insert: Optional[bool] = None,
        include_view: Optional[bool] = None,
        connection_id: Optional[str] = None,
        target_database: Optional[str] = None,
        target_schema: Optional[str] = None,
        target_table: Optional[str] = None,
        csv_content: Optional[str] = None,
        group_name: Optional[str] = None,
        filters: Optional[str] = None,
        limit: Optional[int] = None,
    ) -> Dict[str, Any]:
        """
        Unified NestJS backend sync and management (16 actions).

        Actions:
        - health: Check backend health and sync status
        - configure_sync: Enable/disable auto-sync (enabled='true'/'false')
        - push: Push local project to backend (requires project_id)
        - pull: Pull project from backend (requires backend_project_id)
        - list_projects: List backend projects
        - get_tree: Get backend hierarchy tree (requires backend_project_id)
        - dashboard_stats: Get dashboard statistics
        - recent_activities: Get recent activities (limit=10)
        - search: Search hierarchies (requires project_id, query)
        - generate_deployment: Generate deployment scripts (requires project_id)
        - push_snowflake: Push to Snowflake (requires project_id, connection_id, target_*)
        - deployment_history: Get deployment history (requires project_id)
        - export_csv: Export hierarchy CSV via backend (requires project_id)
        - import_csv: Import hierarchy CSV via backend (requires project_id, csv_content)
        - create_filter_group: Create filter group (requires project_id, group_name, filters)
        - list_filter_groups: List filter groups (requires project_id)

        Returns:
            Action-specific result dict
        """
        kw = {k: v for k, v in {
            "project_id": project_id, "id": id, "backend_project_id": backend_project_id,
            "enabled": enabled, "query": query,
            "table_name": table_name, "view_name": view_name,
            "include_insert": include_insert, "include_view": include_view,
            "connection_id": connection_id, "target_database": target_database,
            "target_schema": target_schema, "target_table": target_table,
            "csv_content": csv_content, "group_name": group_name,
            "filters": filters, "limit": limit,
        }.items() if v is not None}
        return dispatch_hierarchy_backend(data_dir, action, **kw)

    @mcp.tool()
    def hierarchy_graph(
        action: str,
        project_id: Optional[str] = None,
        id: Optional[str] = None,
        node_id: Optional[str] = None,
        query: Optional[str] = None,
        max_results: Optional[int] = None,
        operation: Optional[str] = None,
        role: Optional[str] = None,
        system: Optional[str] = None,
        industry: Optional[str] = None,
        index_path: Optional[str] = None,
    ) -> Dict[str, Any]:
        """
        Unified hierarchy-graph bridge and hierarchy intelligence actions.

        Actions:
        - status: Show indexing status (optional project_id filter)
        - reindex: Reindex project hierarchies (requires project_id)
        - build_lineage: Build lineage graph (requires project_id)
        - search: Semantic search across hierarchies (requires query)
        - impact_analysis: Analyze change impact (requires project_id, node_id)
        - build_hierarchy_rag: Build hierarchy-centric RAG index (requires project_id)
        - search_hierarchy_rag: Search hierarchy-centric RAG index (requires query)
        - suggest_reports: Recommend finance/executive report packs (requires project_id)
        - suggest_workflow_improvements: Recommend process/workflow improvements (requires project_id)
        - executive_brief: Role-aware CIO/CFO/CEO brief (requires project_id)

        Returns:
            Action-specific result dict
        """
        kw = {k: v for k, v in {
            "project_id": project_id, "id": id, "node_id": node_id,
            "query": query, "max_results": max_results,
            "operation": operation,
            "role": role, "system": system, "industry": industry,
            "index_path": index_path,
        }.items() if v is not None}
        return dispatch_hierarchy_graph(data_dir, action, **kw)

    @mcp.tool()
    def hierarchy_config(
        action: str,
        project_id: Optional[str] = None,
        id: Optional[str] = None,
        name: Optional[str] = None,
        description: Optional[str] = None,
        app_name: Optional[str] = None,
        env: Optional[str] = None,
        scope: Optional[str] = None,
        module: Optional[str] = None,
        setting_key: Optional[str] = None,
        setting_value: Optional[str] = None,
        value_type: Optional[str] = None,
        owner: Optional[str] = None,
        tags: Optional[str] = None,
        is_secret: Optional[bool] = None,
        modules: Optional[str] = None,
        connection_name: Optional[str] = None,
        config_file: Optional[str] = None,
        changed_by: Optional[str] = None,
        note: Optional[str] = None,
        refresh_hierarchy_objects: Optional[bool] = None,
        operator: Optional[str] = None,
        limit: Optional[int] = None,
        bundle_name: Optional[str] = None,
        bundle_root: Optional[str] = None,
        include_sql: Optional[bool] = None,
    ) -> Dict[str, Any]:
        """
        Hierarchy-first app configuration governance actions.

        Actions:
        - init_project: create config hierarchy project scaffold
        - create_configuration: add another APP/ENV/SCOPE configuration set under a project
        - list_configurations: list configuration sets and setting counts in project
        - add_or_update_setting: write typed config setting into hierarchy path
        - validate_project: validate publishability of config leaves
        - publish: publish hierarchy settings to Snowflake APP_CONFIG
        - pull: pull APP_CONFIG rows into hierarchy project
        - audit: list APP_CONFIG audit rows
        - export_bundle: write deployable app_config bundle under data/ (manifest/runbook/sql/settings)
        """
        parsed_tags = None
        if tags is not None:
            try:
                parsed_tags = json.loads(tags) if isinstance(tags, str) else tags
            except Exception:
                parsed_tags = [t.strip() for t in str(tags).split(",") if t.strip()]
        parsed_modules = None
        if modules is not None:
            try:
                parsed_modules = json.loads(modules) if isinstance(modules, str) else modules
            except Exception:
                parsed_modules = [m.strip() for m in str(modules).split(",") if m.strip()]
        parsed_value = setting_value
        if setting_value is not None and value_type and value_type.upper() == "JSON":
            try:
                parsed_value = json.loads(setting_value)
            except Exception:
                parsed_value = setting_value
        kw = {k: v for k, v in {
            "project_id": project_id, "id": id, "name": name, "description": description,
            "app_name": app_name, "env": env, "scope": scope, "module": module,
            "setting_key": setting_key, "setting_value": parsed_value, "value_type": value_type,
            "owner": owner, "tags": parsed_tags, "is_secret": is_secret,
            "modules": parsed_modules, "connection_name": connection_name,
            "config_file": config_file, "changed_by": changed_by, "note": note,
            "refresh_hierarchy_objects": refresh_hierarchy_objects, "operator": operator,
            "limit": limit, "bundle_name": bundle_name, "bundle_root": bundle_root,
            "include_sql": include_sql,
        }.items() if v is not None}
        return dispatch_hierarchy_config(data_dir, action, **kw)

    return _ensure_service(data_dir)
