"""Generator für die Workspace-Bulk-Dokumentation.

Enthält die eingebauten Standard-Vorlagen für
- die Workspace-Übersichtsdatei und
- eigenständige Berichtsdateien (wenn mehrere Berichte auf ein Semantic Model
  zugreifen).

Beide Vorlagen werden – falls noch nicht vorhanden – als Datei neben der
Anwendung erzeugt und danach als Standard verwendet. So können sie vom
Benutzer angepasst werden (analog zur Berichts-Dokumentationsvorlage).
"""
from __future__ import annotations

import os

from core.models import (
    WorkspaceItemInfo,
    WorkspaceMetadata,
    WorkspaceUserInfo,
)
from core.template_renderer import TemplateRenderer

# Dateinamen der Standard-Vorlagen (liegen neben der EXE / im Projektstamm)
WORKSPACE_TEMPLATE_FILENAME = "WorkspaceUebersichtsvorlage.md"
REPORT_TEMPLATE_FILENAME = "WorkspaceBerichtsvorlage.md"


# ---------------------------------------------------------------------------
# Eingebaute Standard-Vorlagen
# ---------------------------------------------------------------------------

DEFAULT_WORKSPACE_TEMPLATE = """# Workspace-Dokumentation: {{WS_Name}}

> Automatisch erzeugt am {{WS_ExportDatum}}

## 1. Überblick

| Eigenschaft | Wert |
|---|---|
| Workspace-Name | {{WS_Name}} |
| Workspace-ID | {{WS_ID}} |
| Beschreibung | {{WS_Beschreibung}} |
| Typ | {{WS_Typ}} |
| Dedizierte Kapazität | {{WS_DedizierteKapazitaet}} |
| Kapazitäts-ID | {{WS_Kapazitaet}} |
| Semantic Models | {{WS_AnzahlModelle}} |
| Berichte | {{WS_AnzahlBerichte}} |
| Dataflows | {{WS_AnzahlDataflows}} |
| Notebooks | {{WS_AnzahlNotebooks}} |
| Copyjobs | {{WS_AnzahlCopyjobs}} |
| Data Pipelines | {{WS_AnzahlPipelines}} |
| Lakehouses | {{WS_AnzahlLakehouses}} |
| Warehouses | {{WS_AnzahlWarehouses}} |
| Eventhouses | {{WS_AnzahlEventhouses}} |
| Dashboards | {{WS_AnzahlDashboards}} |
| KQL-Datenbanken | {{WS_AnzahlKQLDatenbanken}} |
| SQL Databases | {{WS_AnzahlSQLDatabases}} |
| Environments | {{WS_AnzahlEnvironments}} |
| Mirrored Databases | {{WS_AnzahlMirroredDatabases}} |
| Eventstreams | {{WS_AnzahlEventstreams}} |
| Spark Job Definitions | {{WS_AnzahlSparkJobs}} |
| KQL-Querysets | {{WS_AnzahlKQLQuerysets}} |
| Reflexe (Data Activator) | {{WS_AnzahlReflexe}} |
| GraphQL APIs | {{WS_AnzahlGraphQLApis}} |
| Sonstige Elemente | {{WS_AnzahlSonstige}} |

## 2. Semantic Models

{{WS_ModelleTabelle}}

## 3. Berichte

{{WS_BerichteTabelle}}

## 4. Dataflows

{{WS_DataflowsTabelle}}

## 5. Notebooks

{{WS_NotebooksTabelle}}

## 6. Copyjobs

{{WS_CopyjobsTabelle}}

## 7. Data Pipelines

{{WS_PipelinesTabelle}}

## 8. Lakehouses

{{WS_LakehousesTabelle}}

## 9. Warehouses

{{WS_WarehousesTabelle}}

## 10. Eventhouses

{{WS_EventhousesTabelle}}

## 11. Dashboards

{{WS_DashboardsTabelle}}

## 12. KQL-Datenbanken

{{WS_KQLDatenbankenTabelle}}

## 13. SQL Databases

{{WS_SQLDatabasesTabelle}}

## 14. Environments

{{WS_EnvironmentsTabelle}}

## 15. Mirrored Databases

{{WS_MirroredDatabasesTabelle}}

## 16. Eventstreams

{{WS_EventstreamsTabelle}}

## 17. Spark Job Definitions

{{WS_SparkJobsTabelle}}

## 18. KQL-Querysets

{{WS_KQLQuerysetsTabelle}}

## 19. Reflexe (Data Activator)

{{WS_ReflexeTabelle}}

## 20. GraphQL APIs

{{WS_GraphQLApisTabelle}}

## 21. Sonstige Elemente

{{WS_SonstigeTabelle}}

## 22. Zugriffsberechtigungen (Benutzer/Gruppen pro Rolle)

{{WS_BenutzerTabelle}}
"""


DEFAULT_REPORT_TEMPLATE = """# Berichts-Dokumentation: {{RPT_Name}}

> Automatisch erzeugt am {{RPT_ExportDatum}}

## 1. Überblick

| Eigenschaft | Wert |
|---|---|
| Berichtsname | {{RPT_Name}} |
| Beschreibung | {{RPT_Beschreibung}} |
| Web-URL | {{RPT_WebUrl}} |
| Endorsement | {{RPT_Endorsement}} |

## 2. Semantic Model

Dieser Bericht basiert auf dem Semantic Model **{{RPT_ModellName}}**.
Die vollständige Modell-Dokumentation befindet sich in: {{RPT_ModellReferenz}}

## 3. Berichtsseiten & Visualisierungen

{{RPT_Visuals}}

## 4. Berichtszugriffe

{{RPT_Zugriffe}}
"""


# ---------------------------------------------------------------------------
# Vorlagen-Verwaltung
# ---------------------------------------------------------------------------

def ensure_template(directory: str, filename: str, default_content: str) -> str:
    """Stellt sicher, dass eine Vorlagendatei existiert, und gibt ihren Pfad zurück.

    Existiert die Datei noch nicht, wird sie mit dem eingebauten Standard
    erzeugt. Andernfalls bleibt die (ggf. angepasste) Datei unverändert.

    Args:
        directory: Verzeichnis, in dem die Vorlage liegen soll.
        filename: Dateiname der Vorlage.
        default_content: Eingebauter Standardinhalt.

    Returns:
        Absoluter Pfad zur Vorlagendatei.
    """
    path = os.path.join(directory, filename)
    if not os.path.isfile(path):
        os.makedirs(directory, exist_ok=True)
        with open(path, "w", encoding="utf-8") as f:
            f.write(default_content)
    return path


def load_template(directory: str, filename: str, default_content: str) -> str:
    """Lädt eine Vorlage; erzeugt sie zuvor als Standard, falls nicht vorhanden."""
    path = ensure_template(directory, filename, default_content)
    try:
        with open(path, encoding="utf-8") as f:
            return f.read()
    except OSError:
        return default_content


# ---------------------------------------------------------------------------
# Markdown-Hilfsfunktionen
# ---------------------------------------------------------------------------

def _escape_cell(value: str) -> str:
    """Escaped einen Wert für die Verwendung in einer Markdown-Tabellenzelle."""
    if value is None:
        return ""
    return str(value).replace("|", "\\|").replace("\n", " ").strip()


def _md_link(label: str, rel_path: str) -> str:
    """Erzeugt einen Markdown-Link mit URL-kodiertem Pfad (Leerzeichen -> %20)."""
    href = rel_path.replace("\\", "/").replace(" ", "%20")
    return f"[{_escape_cell(label)}]({href})"


# ---------------------------------------------------------------------------
# Tabellen-Generierung
# ---------------------------------------------------------------------------

def build_models_table(
    datasets: list[WorkspaceItemInfo],
    model_file_map: dict[str, str],
    report_count_map: dict[str, int],
) -> str:
    """Baut die Markdown-Tabelle der Semantic Models.

    Args:
        datasets: Liste der Semantic Models.
        model_file_map: dataset_id -> relativer Pfad zur Modell-Doku.
        report_count_map: dataset_id -> Anzahl zugehöriger Berichte.
    """
    if not datasets:
        return "_Keine Semantic Models vorhanden._"
    lines = [
        "| Semantic Model | Berichte | Endorsement | Dokumentation |",
        "|---|---|---|---|",
    ]
    for ds in datasets:
        rel = model_file_map.get(ds.id, "")
        doc = _md_link(os.path.basename(rel), rel) if rel else "_nicht dokumentiert_"
        count = report_count_map.get(ds.id, 0)
        lines.append(
            f"| {_escape_cell(ds.name)} | {count} | "
            f"{_escape_cell(ds.endorsement) or '–'} | {doc} |"
        )
    return "\n".join(lines)


def build_reports_table(
    reports: list[WorkspaceItemInfo],
    dataset_name_map: dict[str, str],
    report_file_map: dict[str, str],
) -> str:
    """Baut die Markdown-Tabelle der Berichte.

    Args:
        reports: Liste der (Power BI) Berichte.
        dataset_name_map: dataset_id -> Modell-Name.
        report_file_map: report_id -> relativer Pfad zur Doku (Bericht ODER Modell).
    """
    if not reports:
        return "_Keine Berichte vorhanden._"
    lines = [
        "| Bericht | Semantic Model | Endorsement | Dokumentation |",
        "|---|---|---|---|",
    ]
    for rep in reports:
        model_name = dataset_name_map.get(rep.dataset_id, "–")
        rel = report_file_map.get(rep.id, "")
        doc = _md_link(os.path.basename(rel), rel) if rel else "_nicht dokumentiert_"
        lines.append(
            f"| {_escape_cell(rep.name)} | {_escape_cell(model_name)} | "
            f"{_escape_cell(rep.endorsement) or '–'} | {doc} |"
        )
    return "\n".join(lines)


def build_other_items_table(other_items: list[WorkspaceItemInfo]) -> str:
    """Baut die Tabelle sonstiger Elemente (ohne bereits gesondert dokumentierte Typen)."""
    rest = [
        i for i in other_items
        if i.item_type not in (
            "Dataflow", "Notebook", "CopyJob", "Lakehouse", "Warehouse",
            "Eventhouse", "DataPipeline", "Dashboard", "KQLDatabase",
            "SQLDatabase", "Environment", "MirroredDatabase", "Eventstream",
            "SparkJobDefinition", "KQLQueryset", "Reflex", "GraphQLApi",
        )
    ]
    if not rest:
        return "_Keine weiteren Elemente vorhanden._"
    lines = [
        "| Element | Typ | Beschreibung |",
        "|---|---|---|",
    ]
    for item in rest:
        lines.append(
            f"| {_escape_cell(item.name)} | {_escape_cell(item.item_type)} | "
            f"{_escape_cell(item.description) or '–'} |"
        )
    return "\n".join(lines)


def _build_item_doc_table(
    other_items: list[WorkspaceItemInfo],
    item_type: str,
    header_label: str,
    file_map: dict[str, str] | None,
    empty_hint: str,
) -> str:
    """Generische Doku-Tabelle (Name | Beschreibung | Dokumentation) je Item-Typ."""
    file_map = file_map or {}
    matches = [i for i in other_items if i.item_type == item_type]
    if not matches:
        return empty_hint
    lines = [
        f"| {header_label} | Beschreibung | Dokumentation |",
        "|---|---|---|",
    ]
    for it in matches:
        rel = file_map.get(it.id, "")
        doc = _md_link(os.path.basename(rel), rel) if rel else "_nicht dokumentiert_"
        lines.append(
            f"| {_escape_cell(it.name)} | "
            f"{_escape_cell(it.description) or '–'} | {doc} |"
        )
    return "\n".join(lines)


def build_warehouses_table(
    other_items: list[WorkspaceItemInfo],
    warehouse_file_map: dict[str, str] | None = None,
) -> str:
    """Baut die Tabelle der Warehouses mit optionaler Doku-Verlinkung."""
    return _build_item_doc_table(
        other_items, "Warehouse", "Warehouse", warehouse_file_map,
        "_Keine Warehouses vorhanden._",
    )


def build_eventhouses_table(
    other_items: list[WorkspaceItemInfo],
    eventhouse_file_map: dict[str, str] | None = None,
) -> str:
    """Baut die Tabelle der Eventhouses mit optionaler Doku-Verlinkung."""
    return _build_item_doc_table(
        other_items, "Eventhouse", "Eventhouse", eventhouse_file_map,
        "_Keine Eventhouses vorhanden._",
    )


def build_pipelines_table(
    other_items: list[WorkspaceItemInfo],
    pipeline_file_map: dict[str, str] | None = None,
) -> str:
    """Baut die Tabelle der Data Pipelines mit optionaler Doku-Verlinkung."""
    return _build_item_doc_table(
        other_items, "DataPipeline", "Data Pipeline", pipeline_file_map,
        "_Keine Data Pipelines vorhanden._",
    )


def build_dashboards_table(
    other_items: list[WorkspaceItemInfo],
    dashboard_file_map: dict[str, str] | None = None,
) -> str:
    """Baut die Tabelle der Dashboards mit optionaler Doku-Verlinkung."""
    return _build_item_doc_table(
        other_items, "Dashboard", "Dashboard", dashboard_file_map,
        "_Keine Dashboards vorhanden._",
    )


def build_kqldatabases_table(
    other_items: list[WorkspaceItemInfo],
    kqldatabase_file_map: dict[str, str] | None = None,
) -> str:
    """Baut die Tabelle der KQL-Datenbanken mit optionaler Doku-Verlinkung."""
    return _build_item_doc_table(
        other_items, "KQLDatabase", "KQL-Datenbank", kqldatabase_file_map,
        "_Keine KQL-Datenbanken vorhanden._",
    )


def build_sqldatabases_table(
    other_items: list[WorkspaceItemInfo],
    sqldatabase_file_map: dict[str, str] | None = None,
) -> str:
    """Baut die Tabelle der SQL Databases mit optionaler Doku-Verlinkung."""
    return _build_item_doc_table(
        other_items, "SQLDatabase", "SQL Database", sqldatabase_file_map,
        "_Keine SQL Databases vorhanden._",
    )


def build_environments_table(
    other_items: list[WorkspaceItemInfo],
    environment_file_map: dict[str, str] | None = None,
) -> str:
    """Baut die Tabelle der Environments mit optionaler Doku-Verlinkung."""
    return _build_item_doc_table(
        other_items, "Environment", "Environment", environment_file_map,
        "_Keine Environments vorhanden._",
    )


def build_mirroreddatabases_table(
    other_items: list[WorkspaceItemInfo],
    mirroreddb_file_map: dict[str, str] | None = None,
) -> str:
    """Baut die Tabelle der Mirrored Databases mit optionaler Doku-Verlinkung."""
    return _build_item_doc_table(
        other_items, "MirroredDatabase", "Mirrored Database", mirroreddb_file_map,
        "_Keine Mirrored Databases vorhanden._",
    )


def build_eventstreams_table(
    other_items: list[WorkspaceItemInfo],
    eventstream_file_map: dict[str, str] | None = None,
) -> str:
    """Baut die Tabelle der Eventstreams mit optionaler Doku-Verlinkung."""
    return _build_item_doc_table(
        other_items, "Eventstream", "Eventstream", eventstream_file_map,
        "_Keine Eventstreams vorhanden._",
    )


def build_sparkjobs_table(
    other_items: list[WorkspaceItemInfo],
    sparkjob_file_map: dict[str, str] | None = None,
) -> str:
    """Baut die Tabelle der Spark Job Definitions mit optionaler Doku-Verlinkung."""
    return _build_item_doc_table(
        other_items, "SparkJobDefinition", "Spark Job Definition", sparkjob_file_map,
        "_Keine Spark Job Definitions vorhanden._",
    )


def build_kqlquerysets_table(
    other_items: list[WorkspaceItemInfo],
    kqlqueryset_file_map: dict[str, str] | None = None,
) -> str:
    """Baut die Tabelle der KQL-Querysets mit optionaler Doku-Verlinkung."""
    return _build_item_doc_table(
        other_items, "KQLQueryset", "KQL-Queryset", kqlqueryset_file_map,
        "_Keine KQL-Querysets vorhanden._",
    )


def build_reflexe_table(
    other_items: list[WorkspaceItemInfo],
    reflex_file_map: dict[str, str] | None = None,
) -> str:
    """Baut die Tabelle der Reflexe (Data Activator) mit optionaler Doku-Verlinkung."""
    return _build_item_doc_table(
        other_items, "Reflex", "Reflex", reflex_file_map,
        "_Keine Reflexe vorhanden._",
    )


def build_graphqlapis_table(
    other_items: list[WorkspaceItemInfo],
    graphqlapi_file_map: dict[str, str] | None = None,
) -> str:
    """Baut die Tabelle der GraphQL APIs mit optionaler Doku-Verlinkung."""
    return _build_item_doc_table(
        other_items, "GraphQLApi", "GraphQL API", graphqlapi_file_map,
        "_Keine GraphQL APIs vorhanden._",
    )


def build_lakehouses_table(
    other_items: list[WorkspaceItemInfo],
    lakehouse_file_map: dict[str, str] | None = None,
) -> str:
    """Baut die Tabelle der Lakehouses mit optionaler Doku-Verlinkung.

    Args:
        other_items: Alle sonstigen Workspace-Elemente (Lakehouses werden gefiltert).
        lakehouse_file_map: lakehouse_id -> relativer Pfad zur Lakehouse-Doku.
    """
    lakehouse_file_map = lakehouse_file_map or {}
    lakehouses = [i for i in other_items if i.item_type == "Lakehouse"]
    if not lakehouses:
        return "_Keine Lakehouses vorhanden._"
    lines = [
        "| Lakehouse | Beschreibung | Dokumentation |",
        "|---|---|---|",
    ]
    for lh in lakehouses:
        rel = lakehouse_file_map.get(lh.id, "")
        doc = _md_link(os.path.basename(rel), rel) if rel else "_nicht dokumentiert_"
        lines.append(
            f"| {_escape_cell(lh.name)} | "
            f"{_escape_cell(lh.description) or '–'} | {doc} |"
        )
    return "\n".join(lines)


def build_copyjobs_table(
    other_items: list[WorkspaceItemInfo],
    copyjob_file_map: dict[str, str] | None = None,
) -> str:
    """Baut die Tabelle der Copyjobs mit optionaler Doku-Verlinkung.

    Args:
        other_items: Alle sonstigen Workspace-Elemente (Copyjobs werden gefiltert).
        copyjob_file_map: copyjob_id -> relativer Pfad zur Copyjob-Doku.
    """
    copyjob_file_map = copyjob_file_map or {}
    copyjobs = [i for i in other_items if i.item_type == "CopyJob"]
    if not copyjobs:
        return "_Keine Copyjobs vorhanden._"
    lines = [
        "| Copyjob | Beschreibung | Dokumentation |",
        "|---|---|---|",
    ]
    for cj in copyjobs:
        rel = copyjob_file_map.get(cj.id, "")
        doc = _md_link(os.path.basename(rel), rel) if rel else "_nicht dokumentiert_"
        lines.append(
            f"| {_escape_cell(cj.name)} | "
            f"{_escape_cell(cj.description) or '–'} | {doc} |"
        )
    return "\n".join(lines)


def build_notebooks_table(
    other_items: list[WorkspaceItemInfo],
    notebook_file_map: dict[str, str] | None = None,
) -> str:
    """Baut die Tabelle der Notebooks mit optionaler Doku-Verlinkung.

    Args:
        other_items: Alle sonstigen Workspace-Elemente (Notebooks werden gefiltert).
        notebook_file_map: notebook_id -> relativer Pfad zur Notebook-Doku.
    """
    notebook_file_map = notebook_file_map or {}
    notebooks = [i for i in other_items if i.item_type == "Notebook"]
    if not notebooks:
        return "_Keine Notebooks vorhanden._"
    lines = [
        "| Notebook | Beschreibung | Dokumentation |",
        "|---|---|---|",
    ]
    for nb in notebooks:
        rel = notebook_file_map.get(nb.id, "")
        doc = _md_link(os.path.basename(rel), rel) if rel else "_nicht dokumentiert_"
        lines.append(
            f"| {_escape_cell(nb.name)} | "
            f"{_escape_cell(nb.description) or '–'} | {doc} |"
        )
    return "\n".join(lines)


def build_dataflows_table(
    other_items: list[WorkspaceItemInfo],
    dataflow_file_map: dict[str, str] | None = None,
) -> str:
    """Baut die Tabelle der Dataflows (Gen1/Gen2) mit optionaler Doku-Verlinkung.

    Args:
        other_items: Alle sonstigen Workspace-Elemente (Dataflows werden gefiltert).
        dataflow_file_map: dataflow_id -> relativer Pfad zur Dataflow-Doku.
    """
    dataflow_file_map = dataflow_file_map or {}
    dataflows = [i for i in other_items if i.item_type == "Dataflow"]
    if not dataflows:
        return "_Keine Dataflows vorhanden._"
    lines = [
        "| Dataflow | Generation | Beschreibung | Dokumentation |",
        "|---|---|---|---|",
    ]
    for df in dataflows:
        rel = dataflow_file_map.get(df.id, "")
        doc = _md_link(os.path.basename(rel), rel) if rel else "_nicht dokumentiert_"
        generation = df.subtype or "Gen1"
        lines.append(
            f"| {_escape_cell(df.name)} | {_escape_cell(generation)} | "
            f"{_escape_cell(df.description) or '–'} | {doc} |"
        )
    return "\n".join(lines)


def build_users_table(users: list[WorkspaceUserInfo]) -> str:
    """Baut die Tabelle der freigegebenen Benutzer/Gruppen pro Rolle."""
    if not users:
        return (
            "_Keine Zugriffsinformationen verfügbar "
            "(ggf. fehlende Berechtigungen)._"
        )
    # Nach Rolle gruppieren
    order = ["Admin", "Member", "Contributor", "Viewer"]

    def _sort_key(u: WorkspaceUserInfo) -> tuple[int, str]:
        try:
            idx = order.index(u.access_right)
        except ValueError:
            idx = len(order)
        return idx, u.display_name.lower()

    lines = [
        "| Name | Typ | Rolle | Kennung |",
        "|---|---|---|---|",
    ]
    for u in sorted(users, key=_sort_key):
        lines.append(
            f"| {_escape_cell(u.display_name)} | {_escape_cell(u.principal_type) or '–'} | "
            f"{_escape_cell(u.access_right) or '–'} | {_escape_cell(u.identifier) or '–'} |"
        )
    return "\n".join(lines)


# ---------------------------------------------------------------------------
# Overview-Rendering
# ---------------------------------------------------------------------------

def build_overview_values(
    ws: WorkspaceMetadata,
    model_file_map: dict[str, str],
    report_file_map: dict[str, str],
    report_count_map: dict[str, int],
    dataflow_file_map: dict[str, str] | None = None,
    notebook_file_map: dict[str, str] | None = None,
    copyjob_file_map: dict[str, str] | None = None,
    lakehouse_file_map: dict[str, str] | None = None,
    warehouse_file_map: dict[str, str] | None = None,
    eventhouse_file_map: dict[str, str] | None = None,
    pipeline_file_map: dict[str, str] | None = None,
    dashboard_file_map: dict[str, str] | None = None,
    kqldatabase_file_map: dict[str, str] | None = None,
    sqldatabase_file_map: dict[str, str] | None = None,
    environment_file_map: dict[str, str] | None = None,
    mirroreddb_file_map: dict[str, str] | None = None,
    eventstream_file_map: dict[str, str] | None = None,
    sparkjob_file_map: dict[str, str] | None = None,
    kqlqueryset_file_map: dict[str, str] | None = None,
    reflex_file_map: dict[str, str] | None = None,
    graphqlapi_file_map: dict[str, str] | None = None,
) -> dict[str, str]:
    """Baut das Platzhalter-Wörterbuch für die Übersichtsvorlage."""
    dataset_name_map = {d.id: d.name for d in ws.datasets}

    def _count(item_type: str) -> int:
        return sum(1 for i in ws.other_items if i.item_type == item_type)

    dataflow_count = _count("Dataflow")
    notebook_count = _count("Notebook")
    copyjob_count = _count("CopyJob")
    lakehouse_count = _count("Lakehouse")
    warehouse_count = _count("Warehouse")
    eventhouse_count = _count("Eventhouse")
    pipeline_count = _count("DataPipeline")
    dashboard_count = _count("Dashboard")
    kqldatabase_count = _count("KQLDatabase")
    sqldatabase_count = _count("SQLDatabase")
    environment_count = _count("Environment")
    mirroreddb_count = _count("MirroredDatabase")
    eventstream_count = _count("Eventstream")
    sparkjob_count = _count("SparkJobDefinition")
    kqlqueryset_count = _count("KQLQueryset")
    reflex_count = _count("Reflex")
    graphqlapi_count = _count("GraphQLApi")
    other_count = (
        len(ws.other_items)
        - dataflow_count
        - notebook_count
        - copyjob_count
        - lakehouse_count
        - warehouse_count
        - eventhouse_count
        - pipeline_count
        - dashboard_count
        - kqldatabase_count
        - sqldatabase_count
        - environment_count
        - mirroreddb_count
        - eventstream_count
        - sparkjob_count
        - kqlqueryset_count
        - reflex_count
        - graphqlapi_count
    )
    return {
        "WS_Name": ws.workspace_name,
        "WS_ID": ws.workspace_id,
        "WS_Beschreibung": ws.description or "–",
        "WS_Typ": ws.workspace_type or "–",
        "WS_Kapazitaet": ws.capacity_id or "–",
        "WS_DedizierteKapazitaet": "Ja" if ws.is_on_dedicated_capacity else "Nein",
        "WS_ExportDatum": ws.extracted_at or "",
        "WS_AnzahlModelle": str(len(ws.datasets)),
        "WS_AnzahlBerichte": str(len(ws.reports)),
        "WS_AnzahlDataflows": str(dataflow_count),
        "WS_AnzahlNotebooks": str(notebook_count),
        "WS_AnzahlCopyjobs": str(copyjob_count),
        "WS_AnzahlPipelines": str(pipeline_count),
        "WS_AnzahlLakehouses": str(lakehouse_count),
        "WS_AnzahlWarehouses": str(warehouse_count),
        "WS_AnzahlEventhouses": str(eventhouse_count),
        "WS_AnzahlDashboards": str(dashboard_count),
        "WS_AnzahlKQLDatenbanken": str(kqldatabase_count),
        "WS_AnzahlSQLDatabases": str(sqldatabase_count),
        "WS_AnzahlEnvironments": str(environment_count),
        "WS_AnzahlMirroredDatabases": str(mirroreddb_count),
        "WS_AnzahlEventstreams": str(eventstream_count),
        "WS_AnzahlSparkJobs": str(sparkjob_count),
        "WS_AnzahlKQLQuerysets": str(kqlqueryset_count),
        "WS_AnzahlReflexe": str(reflex_count),
        "WS_AnzahlGraphQLApis": str(graphqlapi_count),
        "WS_AnzahlSonstige": str(other_count),
        "WS_ModelleTabelle": build_models_table(
            ws.datasets, model_file_map, report_count_map
        ),
        "WS_BerichteTabelle": build_reports_table(
            ws.reports, dataset_name_map, report_file_map
        ),
        "WS_DataflowsTabelle": build_dataflows_table(
            ws.other_items, dataflow_file_map
        ),
        "WS_NotebooksTabelle": build_notebooks_table(
            ws.other_items, notebook_file_map
        ),
        "WS_CopyjobsTabelle": build_copyjobs_table(
            ws.other_items, copyjob_file_map
        ),
        "WS_PipelinesTabelle": build_pipelines_table(
            ws.other_items, pipeline_file_map
        ),
        "WS_LakehousesTabelle": build_lakehouses_table(
            ws.other_items, lakehouse_file_map
        ),
        "WS_WarehousesTabelle": build_warehouses_table(
            ws.other_items, warehouse_file_map
        ),
        "WS_EventhousesTabelle": build_eventhouses_table(
            ws.other_items, eventhouse_file_map
        ),
        "WS_DashboardsTabelle": build_dashboards_table(
            ws.other_items, dashboard_file_map
        ),
        "WS_KQLDatenbankenTabelle": build_kqldatabases_table(
            ws.other_items, kqldatabase_file_map
        ),
        "WS_SQLDatabasesTabelle": build_sqldatabases_table(
            ws.other_items, sqldatabase_file_map
        ),
        "WS_EnvironmentsTabelle": build_environments_table(
            ws.other_items, environment_file_map
        ),
        "WS_MirroredDatabasesTabelle": build_mirroreddatabases_table(
            ws.other_items, mirroreddb_file_map
        ),
        "WS_EventstreamsTabelle": build_eventstreams_table(
            ws.other_items, eventstream_file_map
        ),
        "WS_SparkJobsTabelle": build_sparkjobs_table(
            ws.other_items, sparkjob_file_map
        ),
        "WS_KQLQuerysetsTabelle": build_kqlquerysets_table(
            ws.other_items, kqlqueryset_file_map
        ),
        "WS_ReflexeTabelle": build_reflexe_table(
            ws.other_items, reflex_file_map
        ),
        "WS_GraphQLApisTabelle": build_graphqlapis_table(
            ws.other_items, graphqlapi_file_map
        ),
        "WS_SonstigeTabelle": build_other_items_table(ws.other_items),
        "WS_BenutzerTabelle": build_users_table(ws.users),
    }


def render_overview(
    template_content: str,
    ws: WorkspaceMetadata,
    model_file_map: dict[str, str],
    report_file_map: dict[str, str],
    report_count_map: dict[str, int],
    dataflow_file_map: dict[str, str] | None = None,
    notebook_file_map: dict[str, str] | None = None,
    copyjob_file_map: dict[str, str] | None = None,
    lakehouse_file_map: dict[str, str] | None = None,
    warehouse_file_map: dict[str, str] | None = None,
    eventhouse_file_map: dict[str, str] | None = None,
    pipeline_file_map: dict[str, str] | None = None,
    dashboard_file_map: dict[str, str] | None = None,
    kqldatabase_file_map: dict[str, str] | None = None,
    sqldatabase_file_map: dict[str, str] | None = None,
    environment_file_map: dict[str, str] | None = None,
    mirroreddb_file_map: dict[str, str] | None = None,
    eventstream_file_map: dict[str, str] | None = None,
    sparkjob_file_map: dict[str, str] | None = None,
    kqlqueryset_file_map: dict[str, str] | None = None,
    reflex_file_map: dict[str, str] | None = None,
    graphqlapi_file_map: dict[str, str] | None = None,
) -> str:
    """Rendert die Workspace-Übersicht anhand der Vorlage."""
    values = build_overview_values(
        ws,
        model_file_map,
        report_file_map,
        report_count_map,
        dataflow_file_map,
        notebook_file_map,
        copyjob_file_map,
        lakehouse_file_map,
        warehouse_file_map,
        eventhouse_file_map,
        pipeline_file_map,
        dashboard_file_map,
        kqldatabase_file_map,
        sqldatabase_file_map,
        environment_file_map,
        mirroreddb_file_map,
        eventstream_file_map,
        sparkjob_file_map,
        kqlqueryset_file_map,
        reflex_file_map,
        graphqlapi_file_map,
    )
    rendered, _warnings = TemplateRenderer().render(template_content, values)
    return rendered


def build_visuals_table_from_metadata(metadata) -> str:  # type: ignore[no-untyped-def]
    """Baut eine Seiten-/Visual-Tabelle aus ReportMetadata.visuals."""
    visuals = getattr(metadata, "visuals", None) or []
    if not visuals:
        return "_Keine Visualisierungen verfügbar._"
    lines = [
        "| Seite | Visualtyp | Anzeigename |",
        "|---|---|---|",
    ]
    for v in visuals:
        lines.append(
            f"| {_escape_cell(v.page_name)} | {_escape_cell(v.visual_type)} | "
            f"{_escape_cell(v.display_name) or '–'} |"
        )
    return "\n".join(lines)


def render_standalone_report(
    template_content: str,
    report: WorkspaceItemInfo,
    model_name: str,
    model_ref_rel_path: str,
    metadata,  # type: ignore[no-untyped-def]
    export_date: str,
    report_access_md: str = "",
) -> str:
    """Rendert eine eigenständige Berichtsdatei (mehrere Berichte pro Modell)."""
    values = {
        "RPT_Name": report.name,
        "RPT_Beschreibung": report.description or "–",
        "RPT_WebUrl": report.web_url or "–",
        "RPT_Endorsement": report.endorsement or "–",
        "RPT_ModellName": model_name,
        "RPT_ModellReferenz": _md_link(
            os.path.basename(model_ref_rel_path), model_ref_rel_path
        )
        if model_ref_rel_path
        else "–",
        "RPT_Visuals": build_visuals_table_from_metadata(metadata),
        "RPT_Zugriffe": report_access_md or "–",
        "RPT_ExportDatum": export_date,
    }
    rendered, _warnings = TemplateRenderer().render(template_content, values)
    return rendered
