"""Administrative consolidation for ecosystem service-order folders.

The service-order router may ask for a cross-platform administrative report
before a technical round starts.  This module builds that report from the
central project dossiers without changing any platform repository.  It keeps
the output deterministic, redacts secret-shaped values, and separates two
classes of pending work:

* external blockers: credentials, authorization, WAF, runner, remote sync, or
  institutional decisions;
* internal Codex work: documentation, tests, indexes, SQL, code, and reports
  that can usually be handled by a normal execution round.
"""

from __future__ import annotations

import csv
import io
import json
import re
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
from typing import Any, Iterable, Mapping, Sequence

from .models import GeneratedFile, as_plain_data, merge_unique, slugify, utc_now
from .redaction import redact_sensitive_text
from .service_order_lifecycle import discover_orders


class PendingClass(str, Enum):
    """Administrative class for one pending item."""

    EXTERNAL = "external_blocker"
    INTERNAL = "internal_codex_action"
    OBSERVATION = "observation"


EXTERNAL_PATTERNS: tuple[tuple[str, re.Pattern[str]], ...] = (
    ("credential_or_secret", re.compile(r"(?i)\b(credencial|credential|token|secret|api[_-]?key|bearer|cfat|cloudflare[_-]?token|cf[_-]?token)\b")),
    ("authorization_decision", re.compile(r"(?i)\b(autorizacao|autorizacao|autorizar|aprovacao|decisao|owner|signoff|janela)\b")),
    ("cloudflare_edge", re.compile(r"(?i)\b(waf|cloudflare access|http\s*403|code\s*1010|tls|sec_e_no_credentials)\b")),
    ("runner_environment", re.compile(r"(?i)\b(runner|eperm|spawn|node|wrangler|ci|powershell/curl)\b")),
    ("git_remote_sync", re.compile(r"(?i)\b(fetch|push|remote|origin/main|ahead|behind|index\.lock|gitea|gitlab|acl)\b")),
    ("live_external_surface", re.compile(r"(?i)\b(live|endpoint publicado|url publicada|tenant real|readback real)\b")),
)

INTERNAL_PATTERNS: tuple[tuple[str, re.Pattern[str]], ...] = (
    ("documentation", re.compile(r"(?i)\b(readme|documentacao|docs|markdown|relatorio|indice|status|overview)\b")),
    ("semantic_sql", re.compile(r"(?i)\b(sqlite|sql semantico|controle-semantico|semantico)\b")),
    ("tests_or_validation", re.compile(r"(?i)\b(teste|validacao|suite|compileall|unittest|regressao)\b")),
    ("code_or_contract", re.compile(r"(?i)\b(codigo|python|typescript|javascript|contrato|schema|payload|hash|rulebook)\b")),
    ("mcp_admin_shape", re.compile(r"(?i)\b(mcp|admin_ui|same-source|sameSource|traceId|auditId|evidenceId)\b")),
    ("central_records", re.compile(r"(?i)\b(ordem|executado|pendencia|auditoria|fila|current|reports)\b")),
)

PLUGIN_OBSERVATION_RE = re.compile(r"(?i)\b(plugin cloudflare|mcp tool call|user rejected mcp)\b")
ORDER_STATUS_RE = re.compile(r"^\s*-\s*status\s*:\s*`?([^`\n]+)`?", re.I | re.M)
PROJECT_ID_RE = re.compile(r"^\s*-\s*project_id\s*:\s*`?([^`\n]+)`?", re.I | re.M)
REPO_NAME_RE = re.compile(r"^\s*-\s*repo_name\s*:\s*`?([^`\n]+)`?", re.I | re.M)
REAL_REPO_RE = re.compile(r"(?im)^\s*Projeto real\s*:\s*\n+\s*`?([^`\n]+)`?")
STATUS_LINE_RE = re.compile(r"^\s*-\s*(status|status_gerencial)\s*:\s*`?([^`\n]+)`?", re.I | re.M)


@dataclass(frozen=True, slots=True)
class PendingItem:
    """One pending item found in central documents or active orders."""

    platform_folder: str
    platform_id: str
    source_path: str
    source_kind: str
    classification: PendingClass
    reason: str
    text: str
    order_id: str = ""
    order_type: str = ""

    def to_dict(self) -> dict[str, Any]:
        return as_plain_data(self)


@dataclass(frozen=True, slots=True)
class PlatformAdministrativeSnapshot:
    """Administrative state extracted from one platform dossier."""

    folder_name: str
    platform_id: str
    repo_name: str
    real_repo_path: str
    status: str
    purpose_summary: str
    current_highlights: tuple[str, ...]
    active_order_count: int
    active_executive_count: int
    active_managerial_count: int
    external_pending: tuple[PendingItem, ...]
    internal_pending: tuple[PendingItem, ...]
    observations: tuple[PendingItem, ...]

    def to_dict(self) -> dict[str, Any]:
        return as_plain_data(self)

    @property
    def total_pending(self) -> int:
        return len(self.external_pending) + len(self.internal_pending)


@dataclass(frozen=True, slots=True)
class ConsolidatedAdministrativeReport:
    """Full administrative consolidation across platform dossiers."""

    report_id: str
    generated_at: str
    central_projects_root: str
    project_root: str
    plugin_auth_attempt: str
    git_sync_status: str
    platforms: tuple[PlatformAdministrativeSnapshot, ...]
    executive_summary: tuple[str, ...]
    mcp_control_plane_review: tuple[str, ...]
    internal_codex_debt: tuple[PendingItem, ...]
    external_blockers: tuple[PendingItem, ...]

    def to_dict(self) -> dict[str, Any]:
        return as_plain_data(self)

    @property
    def platform_count(self) -> int:
        return len(self.platforms)

    @property
    def external_pending_count(self) -> int:
        return len(self.external_blockers)

    @property
    def internal_pending_count(self) -> int:
        return len(self.internal_codex_debt)


def _read_text(path: Path, limit_chars: int = 250_000) -> str:
    try:
        text = path.read_text(encoding="utf-8", errors="ignore")
    except OSError:
        return ""
    if len(text) <= limit_chars:
        return text
    return text[:limit_chars]


def _first_match(pattern: re.Pattern[str], text: str, default: str = "") -> str:
    match = pattern.search(text)
    if not match:
        return default
    return match.group(1).strip()


def _status_from_readme(text: str) -> str:
    match = STATUS_LINE_RE.search(text)
    if not match:
        return ""
    return match.group(2).strip()


def _extract_section(text: str, heading: str, *, limit: int = 1400) -> str:
    wanted = slugify(heading)
    lines = text.splitlines()
    capture = False
    output: list[str] = []
    for line in lines:
        if line.lstrip().startswith("#"):
            normalized = slugify(line.lstrip("#").strip())
            if capture:
                break
            capture = normalized == wanted
            continue
        if capture:
            output.append(line)
    section = "\n".join(output).strip()
    if len(section) > limit:
        section = section[: limit - 3].rstrip() + "..."
    return section


def _purpose_from_readme(text: str) -> str:
    for heading in ("Papel no ecossistema", "Missao", "Finalidade", "Funcao"):
        section = _extract_section(text, heading)
        if section:
            return _compact_text(section, limit=520)
    first_paragraph: list[str] = []
    for line in text.splitlines()[1:]:
        stripped = line.strip()
        if not stripped and first_paragraph:
            break
        if stripped and not stripped.startswith("#"):
            first_paragraph.append(stripped)
    return _compact_text(" ".join(first_paragraph), limit=520)


def _compact_text(text: str, *, limit: int = 240) -> str:
    cleaned = " ".join(redact_sensitive_text(text).replace("\r", " ").split())
    if len(cleaned) <= limit:
        return cleaned
    return cleaned[: limit - 3].rstrip() + "..."


def _bullet_lines(text: str) -> tuple[str, ...]:
    items: list[str] = []
    for line in text.splitlines():
        stripped = line.strip()
        if not stripped.startswith("-"):
            continue
        cleaned = stripped.lstrip("-").strip()
        if cleaned:
            items.append(_compact_text(cleaned, limit=420))
    return merge_unique(items)


def _current_highlights(platform_folder: Path) -> tuple[str, ...]:
    candidates = (
        platform_folder / "current" / "current-project-state.md",
        platform_folder / "status" / "overview.md",
        platform_folder / "current" / "active-order-queue.md",
    )
    highlights: list[str] = []
    for path in candidates:
        text = _read_text(path, limit_chars=80_000)
        if not text:
            continue
        for heading in ("Avanco desta rodada", "Destaques da rodada", "Release", "Bloqueios reais remanescentes"):
            section = _extract_section(text, heading, limit=2200)
            if section:
                highlights.extend(_bullet_lines(section)[:8])
        if len(highlights) >= 12:
            break
    return merge_unique(highlights)[:12]


def classify_pending_text(text: str) -> tuple[PendingClass, str]:
    """Classify a pending line and return the administrative reason."""

    clean = redact_sensitive_text(text).strip()
    if not clean:
        return PendingClass.OBSERVATION, "item vazio"
    if PLUGIN_OBSERVATION_RE.search(clean):
        return PendingClass.OBSERVATION, "falha do plugin Cloudflare registrada apenas como observacao"
    for reason, pattern in EXTERNAL_PATTERNS:
        if pattern.search(clean):
            return PendingClass.EXTERNAL, reason
    for reason, pattern in INTERNAL_PATTERNS:
        if pattern.search(clean):
            return PendingClass.INTERNAL, reason
    return PendingClass.INTERNAL, "acao administrativa ou tecnica sem bloqueio externo explicito"


def _pending_from_line(
    *,
    platform_folder: Path,
    platform_id: str,
    source_path: Path,
    source_kind: str,
    text: str,
    order_id: str = "",
    order_type: str = "",
) -> PendingItem:
    classification, reason = classify_pending_text(text)
    return PendingItem(
        platform_folder=platform_folder.name,
        platform_id=platform_id,
        source_path=str(source_path),
        source_kind=source_kind,
        classification=classification,
        reason=reason,
        text=_compact_text(text, limit=520),
        order_id=order_id,
        order_type=order_type,
    )


def _pending_items_from_file(platform_folder: Path, platform_id: str, path: Path, source_kind: str) -> tuple[PendingItem, ...]:
    text = _read_text(path, limit_chars=180_000)
    if not text:
        return ()
    items = []
    for line in _bullet_lines(text):
        lowered = line.lower()
        if any(
            token in lowered
            for token in (
                "pendencia",
                "bloque",
                "falh",
                "catalog",
                "credencial",
                "credential",
                "token",
                "secret",
                "runner",
                "mcp",
                "docs",
                "sync",
                "waf",
                "http 403",
                "1010",
                "eperm",
                "wrangler",
            )
        ):
            items.append(
                _pending_from_line(
                    platform_folder=platform_folder,
                    platform_id=platform_id,
                    source_path=path,
                    source_kind=source_kind,
                    text=line,
                )
            )
    return tuple(items)


def _order_pending_items(platform_folder: Path, platform_id: str) -> tuple[PendingItem, ...]:
    items: list[PendingItem] = []
    for order in discover_orders(platform_folder):
        status = order.status.value
        if status in {"concluida", "completed", "substituida", "superseded"}:
            continue
        text = " ".join(
            part
            for part in (
                order.title,
                order.purpose,
                order.object_scope,
                order.reason,
                order.expected_result,
                " ".join(order.validations),
            )
            if part
        )
        items.append(
            _pending_from_line(
                platform_folder=platform_folder,
                platform_id=platform_id,
                source_path=Path(order.path),
                source_kind="active_order",
                text=text,
                order_id=order.order_id,
                order_type=order.order_type.value,
            )
        )
    return tuple(items)


def _identity_from_readme(platform_folder: Path) -> tuple[str, str, str, str]:
    readme = _read_text(platform_folder / "README.md")
    platform_id = _first_match(PROJECT_ID_RE, readme, platform_folder.name)
    repo_name = _first_match(REPO_NAME_RE, readme, platform_id)
    real_repo_path = _first_match(REAL_REPO_RE, readme, "")
    status = _status_from_readme(readme)
    return platform_id, repo_name, real_repo_path, status


def scan_platform_dossier(platform_folder: Path) -> PlatformAdministrativeSnapshot:
    """Scan one central platform folder and return a compact snapshot."""

    platform_id, repo_name, real_repo_path, status = _identity_from_readme(platform_folder)
    readme_text = _read_text(platform_folder / "README.md")
    purpose = _purpose_from_readme(readme_text)
    pending: list[PendingItem] = []
    for path, kind in (
        (platform_folder / "indexes" / "pending-index.md", "pending_index"),
        (platform_folder / "current" / "current-project-state.md", "current_state"),
        (platform_folder / "status" / "overview.md", "status_overview"),
    ):
        pending.extend(_pending_items_from_file(platform_folder, platform_id, path, kind))
    pending.extend(_order_pending_items(platform_folder, platform_id))
    deduped: list[PendingItem] = []
    seen: set[tuple[str, str, str]] = set()
    for item in pending:
        key = (item.classification.value, item.order_id, item.text)
        if key in seen:
            continue
        seen.add(key)
        deduped.append(item)
    orders = discover_orders(platform_folder)
    external = tuple(item for item in deduped if item.classification == PendingClass.EXTERNAL)
    internal = tuple(item for item in deduped if item.classification == PendingClass.INTERNAL)
    observations = tuple(item for item in deduped if item.classification == PendingClass.OBSERVATION)
    return PlatformAdministrativeSnapshot(
        folder_name=platform_folder.name,
        platform_id=platform_id,
        repo_name=repo_name,
        real_repo_path=real_repo_path,
        status=status,
        purpose_summary=purpose,
        current_highlights=_current_highlights(platform_folder),
        active_order_count=len(orders),
        active_executive_count=sum(1 for item in orders if item.order_type.value == "executiva"),
        active_managerial_count=sum(1 for item in orders if item.order_type.value == "gerencial"),
        external_pending=external,
        internal_pending=internal,
        observations=observations,
    )


def _iter_platform_folders(central_projects_root: Path) -> Iterable[Path]:
    if not central_projects_root.exists():
        return ()
    folders = []
    for path in sorted(central_projects_root.iterdir(), key=lambda item: item.name):
        if not path.is_dir():
            continue
        if path.name in {"sincronizacao-de-repositorios", "plataformas-backups"}:
            continue
        if (path / "README.md").exists() or (path / "orders").exists() or (path / "current").exists():
            folders.append(path)
    return tuple(folders)


def _mcp_control_review(platforms: Sequence[PlatformAdministrativeSnapshot]) -> tuple[str, ...]:
    mcps = [item for item in platforms if "mcp" in item.platform_id.lower() or "mcps" in item.folder_name.lower()]
    docs = [item for item in platforms if item.platform_id in {"docs", "tudo-para-ia-docs-plataform"} or "docs" in item.folder_name]
    business = [item for item in platforms if item.platform_id in {"business", "tudo-para-ia-business-platform"} or "business" in item.folder_name]
    identity = [item for item in platforms if item.platform_id in {"identity", "tudo-para-ia-identity-platform"} or "identity" in item.folder_name]
    internal_mcp = sum(len(item.internal_pending) for item in mcps)
    external_mcp = sum(len(item.external_pending) for item in mcps)
    return (
        f"MCPs Internos continua sendo o control-plane administrativo; pendencias MCP internas={internal_mcp}, externas={external_mcp}.",
        f"Docs aparece em {len(docs)} dossie(s) e deve resolver responseReady minimo ou excecao formal para nao contaminar maturidade.",
        f"Identity aparece em {len(identity)} dossie(s) como base de ator, permissao, tenant e auditoria.",
        f"Business aparece em {len(business)} dossie(s) como fonte comercial de plano, entitlement, consumo e bloqueio.",
        "Alteracao relevante deve seguir pelo MCP com origin, destination, tool, payload, actor, permission, result, traceId, auditId e timestamp.",
    )


def build_consolidated_report(
    central_projects_root: Path,
    project_root: Path,
    *,
    plugin_auth_attempt: str = "",
    git_sync_status: str = "",
) -> ConsolidatedAdministrativeReport:
    """Build the cross-platform administrative pending report."""

    platforms = tuple(scan_platform_dossier(path) for path in _iter_platform_folders(central_projects_root))
    external = tuple(item for platform in platforms for item in platform.external_pending)
    internal = tuple(item for platform in platforms for item in platform.internal_pending)
    with_orders = sum(1 for platform in platforms if platform.active_order_count)
    summary = (
        f"Plataformas/dossies avaliados: {len(platforms)}.",
        f"Dossies com ordens ativas: {with_orders}.",
        f"Pendencias externas abertas: {len(external)}.",
        f"Pendencias internas acionaveis pelo Codex: {len(internal)}.",
        f"Tentativa inicial do plugin Cloudflare: {plugin_auth_attempt or 'nao registrada neste artefato'}.",
        f"Estado de sincronizacao Git desta rodada: {git_sync_status or 'nao informado'}.",
    )
    return ConsolidatedAdministrativeReport(
        report_id=f"relatorio-consolidado-{slugify(utc_now())}",
        generated_at=utc_now(),
        central_projects_root=str(central_projects_root),
        project_root=str(project_root),
        plugin_auth_attempt=redact_sensitive_text(plugin_auth_attempt),
        git_sync_status=redact_sensitive_text(git_sync_status),
        platforms=platforms,
        executive_summary=summary,
        mcp_control_plane_review=_mcp_control_review(platforms),
        internal_codex_debt=internal,
        external_blockers=external,
    )


def consolidated_pending_rows(report: ConsolidatedAdministrativeReport) -> list[list[str]]:
    rows = [
        [
            "platform_folder",
            "platform_id",
            "classification",
            "reason",
            "source_kind",
            "order_id",
            "order_type",
            "source_path",
            "text",
        ]
    ]
    for item in tuple(report.external_blockers) + tuple(report.internal_codex_debt):
        rows.append(
            [
                item.platform_folder,
                item.platform_id,
                item.classification.value,
                item.reason,
                item.source_kind,
                item.order_id,
                item.order_type,
                item.source_path,
                item.text,
            ]
        )
    return rows


def consolidated_pending_csv(report: ConsolidatedAdministrativeReport) -> str:
    buffer = io.StringIO()
    writer = csv.writer(buffer, lineterminator="\n")
    writer.writerows(consolidated_pending_rows(report))
    return buffer.getvalue()


def _pending_markdown_block(title: str, items: Sequence[PendingItem], *, limit: int = 80) -> list[str]:
    lines = [f"## {title}", ""]
    if not items:
        lines.append("- Nenhum item encontrado.")
        lines.append("")
        return lines
    for item in items[:limit]:
        order = f" `{item.order_id}`" if item.order_id else ""
        lines.append(
            f"- `{item.platform_folder}`{order} `{item.reason}`: {item.text}"
        )
    if len(items) > limit:
        lines.append(f"- ... {len(items) - limit} itens adicionais no CSV/JSON.")
    lines.append("")
    return lines


def consolidated_report_markdown(report: ConsolidatedAdministrativeReport) -> str:
    lines = [
        "# Relatorio consolidado administrativo das plataformas",
        "",
        f"- report_id: `{report.report_id}`",
        f"- generated_at: `{report.generated_at}`",
        f"- central_projects_root: `{report.central_projects_root}`",
        f"- project_root: `{report.project_root}`",
        f"- plataformas_avaliadas: `{report.platform_count}`",
        f"- pendencias_externas: `{report.external_pending_count}`",
        f"- pendencias_internas_codex: `{report.internal_pending_count}`",
        "",
        "## Sumario executivo",
        "",
    ]
    lines.extend(f"- {item}" for item in report.executive_summary)
    lines.extend(["", "## Relacao com MCPs Internos", ""])
    lines.extend(f"- {item}" for item in report.mcp_control_plane_review)
    lines.extend(["", "## O que tem sido feito para a finalidade das plataformas", ""])
    for platform in report.platforms:
        lines.append(f"### {platform.folder_name}")
        lines.append("")
        lines.append(f"- project_id: `{platform.platform_id}`")
        lines.append(f"- repo_name: `{platform.repo_name}`")
        if platform.status:
            lines.append(f"- status: `{platform.status}`")
        if platform.real_repo_path:
            lines.append(f"- projeto_real: `{platform.real_repo_path}`")
        lines.append(f"- finalidade: {platform.purpose_summary or 'nao identificada no README'}")
        lines.append(
            f"- ordens_ativas: `{platform.active_order_count}` "
            f"(executivas `{platform.active_executive_count}`, gerenciais `{platform.active_managerial_count}`)"
        )
        if platform.current_highlights:
            lines.append("- evidencias_de_avanco:")
            for highlight in platform.current_highlights[:6]:
                lines.append(f"  - {highlight}")
        lines.append("")
    lines.extend(_pending_markdown_block("Pendencias externas em aberto", report.external_blockers))
    lines.extend(_pending_markdown_block("Pendencias internas que o Codex ainda deve resolver", report.internal_codex_debt))
    lines.extend(
        [
            "## Leitura administrativa",
            "",
            "- Pendencia externa nao deve virar desculpa para interromper trabalho local que possa ser feito com seguranca.",
            "- Pendencia interna deve ser convertida em execucao, teste, documentacao ou ordem de saida especifica.",
            "- Falha do plugin Cloudflare e apenas observacao inicial; operacao Cloudflare segue por wrangler.",
            "- Segredos detectados nos textos de origem sao redigidos antes de sair em JSON, CSV ou Markdown.",
            "",
        ]
    )
    return "\n".join(lines).strip() + "\n"


def write_consolidated_artifacts(
    report: ConsolidatedAdministrativeReport,
    project_root: Path,
    *,
    central_platform_folder: Path | None = None,
) -> tuple[GeneratedFile, ...]:
    """Write project and optional central artifacts for the consolidated report."""

    generated: list[GeneratedFile] = []
    central_failures: list[dict[str, str]] = []
    targets: list[tuple[Path, str, str, str, str]] = [
        (
            project_root / "dados" / "relatorio-consolidado-operacional.json",
            json.dumps(report.to_dict(), ensure_ascii=False, indent=2, sort_keys=True),
            "Relatorio consolidado administrativo em JSON.",
            "relatorio consolidado",
            "json",
        ),
        (
            project_root / "matrizes" / "pendencias-consolidadas.csv",
            consolidated_pending_csv(report),
            "Matriz CSV de pendencias externas e internas por plataforma.",
            "matriz pendencias",
            "csv",
        ),
        (
            project_root / "ecossistema" / "RELATORIO-CONSOLIDADO-OPERACIONAL.md",
            consolidated_report_markdown(report),
            "Relatorio administrativo consolidado em Markdown.",
            "relatorio consolidado",
            "markdown",
        ),
    ]
    if central_platform_folder is not None:
        targets.append(
            (
                central_platform_folder / "reports" / "RELATORIO-CONSOLIDADO-ADMINISTRATIVO__RODADA015.md",
                consolidated_report_markdown(report),
                "Copia central do relatorio consolidado administrativo da rodada 015.",
                "relatorio consolidado central",
                "markdown",
            )
        )
    for path, content, description, function, file_type in targets:
        try:
            path.parent.mkdir(parents=True, exist_ok=True)
            path.write_text(redact_sensitive_text(content), encoding="utf-8")
        except OSError as exc:
            if central_platform_folder is not None and central_platform_folder in path.parents:
                central_failures.append(
                    {
                        "path": str(path),
                        "operation": "write_text",
                        "error": f"{type(exc).__name__}: {exc}",
                    }
                )
                continue
            raise
        generated.append(
            GeneratedFile(
                path=str(path),
                description=description,
                function=function,
                file_type=file_type,
                changed_by="mais_humana.central_consolidation",
                change_summary="Criado ou atualizado relatorio consolidado administrativo de pendencias.",
                relation_to_order="015-ROTEADOR-PERMANENTE-DE-ORDEM_DE_SERVICO",
            )
        )
    if central_failures:
        status_path = project_root / "dados" / "relatorio-consolidado-central-write-status.json"
        status_path.parent.mkdir(parents=True, exist_ok=True)
        status_payload = {
            "generatedAt": utc_now(),
            "centralPlatformFolder": str(central_platform_folder) if central_platform_folder is not None else "",
            "ok": False,
            "failureCount": len(central_failures),
            "failures": central_failures,
            "policy": "falha de escrita central nao deve abortar relatorio consolidado do projeto real",
        }
        status_path.write_text(
            json.dumps(status_payload, ensure_ascii=False, indent=2, sort_keys=True),
            encoding="utf-8",
        )
        generated.append(
            GeneratedFile(
                path=str(status_path),
                description="Status da tentativa de escrita central do relatorio consolidado.",
                function="relatorio consolidado central write status",
                file_type="json",
                changed_by="mais_humana.central_consolidation",
                change_summary="Registrada falha de escrita central sem abortar artefatos do projeto real.",
                relation_to_order="015-ROTEADOR-PERMANENTE-DE-ORDEM_DE_SERVICO",
            )
        )
    return tuple(generated)


def run_consolidated_report(
    central_projects_root: Path,
    project_root: Path,
    *,
    central_platform_folder: Path | None = None,
    plugin_auth_attempt: str = "",
    git_sync_status: str = "",
) -> tuple[ConsolidatedAdministrativeReport, tuple[GeneratedFile, ...]]:
    """Build and write the consolidated report in one call."""

    report = build_consolidated_report(
        central_projects_root,
        project_root,
        plugin_auth_attempt=plugin_auth_attempt,
        git_sync_status=git_sync_status,
    )
    records = write_consolidated_artifacts(report, project_root, central_platform_folder=central_platform_folder)
    return report, records
