"""Safe repository-mesh inventory and synchronization planning.

The permanent synchronization order is intentionally strict: it asks the
operator to keep several mirrors aligned without ever letting an older checkout
overwrite a newer valid change.  This module turns that order into an
auditable, deterministic control surface for the Mais Humana platform.

The module does three things:

* it inventories the declared mirrors with non-destructive Git commands;
* it classifies risks before any synchronization action is proposed;
* it emits artifacts that can be consumed by humans, SQLite, GPT, MCP, and UI.

No function in this file runs reset, checkout, clean, merge, rebase, or pull.
The only built-in command with network effect is ``fetch --all --prune``, and
it is opt-in.  That is deliberate: the synchronization order makes preservation
of the newest valid state more important than blind convergence.
"""

from __future__ import annotations

import csv
import hashlib
import io
import json
import os
import re
import subprocess
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import Any, Callable, Iterable, Mapping, MutableMapping, Sequence

from .identity_policy import CANONICAL_PROJECT_ID, CURRENT_PROJECT_ID, LEGACY_PLATAFORM_ALIAS
from .models import GeneratedFile, as_plain_data, merge_unique, slugify, utc_now


JSON = Mapping[str, Any]


class MeshEnvironmentKind(str, Enum):
    """Known execution environments in the repository mesh."""

    WINDOWS_PRIMARY = "windows_primary"
    WINDOWS_SECONDARY = "windows_secondary"
    CODEX_SERVER = "codex_server"
    GITLAB_SERVER = "gitlab_server"
    GITEA_REMOTE = "gitea_remote"


class MeshPresence(str, Enum):
    """Materialization state for one repository in one environment."""

    PRESENT_GIT = "present_git"
    PRESENT_NOT_GIT = "present_not_git"
    MISSING = "missing"
    UNREACHABLE_ENVIRONMENT = "unreachable_environment"
    UNKNOWN = "unknown"


class GitCleanliness(str, Enum):
    """Working-tree cleanliness classification."""

    CLEAN = "clean"
    DIRTY = "dirty"
    UNKNOWN = "unknown"


class MeshRiskLevel(str, Enum):
    """Risk level used by the planner."""

    OK = "ok"
    ATTENTION = "attention"
    BLOCKED = "blocked"


class MeshActionKind(str, Enum):
    """Non-destructive actions the planner may recommend."""

    NONE = "none"
    FETCH = "fetch"
    CLONE_MISSING = "clone_missing"
    FIX_REMOTE_URL = "fix_remote_url"
    RENAME_LOCAL_FOLDER = "rename_local_folder"
    CREATE_ALIAS_RECORD = "create_alias_record"
    BLOCK_DESTRUCTIVE_SYNC = "block_destructive_sync"
    REQUIRE_CREDENTIAL = "require_credential"
    REQUIRE_ENVIRONMENT_ACCESS = "require_environment_access"
    REQUIRE_HUMAN_DECISION = "require_human_decision"
    REGISTER_EVIDENCE = "register_evidence"


class MeshErrorKind(str, Enum):
    """Structured error categories for Git and filesystem failures."""

    NONE = "none"
    DUBIOUS_OWNERSHIP = "dubious_ownership"
    CREDENTIALS_MISSING = "credentials_missing"
    NETWORK = "network"
    AUTHENTICATION = "authentication"
    NOT_A_REPOSITORY = "not_a_repository"
    COMMAND_NOT_FOUND = "command_not_found"
    PATH_MISSING = "path_missing"
    TIMEOUT = "timeout"
    UNKNOWN = "unknown"


@dataclass(frozen=True, slots=True)
class RepositoryTarget:
    """One repository declared by the permanent synchronization order."""

    declared_name: str
    gitea_repo: str
    expected_local_name: str
    central_folder: str
    aliases: tuple[str, ...] = ()
    canonical_name: str | None = None
    requires_nominal_reconciliation: bool = False
    notes: tuple[str, ...] = ()

    @property
    def all_local_names(self) -> tuple[str, ...]:
        names = [self.expected_local_name, self.declared_name]
        if self.canonical_name:
            names.append(self.canonical_name)
        names.extend(self.aliases)
        return merge_unique(names)

    @property
    def expected_remote_url(self) -> str:
        return f"https://git.ami.app.br/{self.gitea_repo}.git"

    @property
    def platform_id(self) -> str:
        name = self.expected_local_name
        prefix = "tudo-para-ia-"
        suffixes = ("-platform", "-plataform")
        if name.startswith(prefix):
            name = name[len(prefix) :]
        for suffix in suffixes:
            if name.endswith(suffix):
                name = name[: -len(suffix)]
                break
        return name.replace("-", "_")


@dataclass(frozen=True, slots=True)
class MeshEnvironment:
    """One mirror root that should participate in synchronization."""

    environment_id: str
    kind: MeshEnvironmentKind
    root: str
    role: str
    local: bool = True
    expected: bool = True

    @property
    def root_path(self) -> Path:
        return Path(self.root)

    def path_for(self, local_name: str) -> Path:
        return self.root_path / local_name


@dataclass(frozen=True, slots=True)
class CommandResult:
    """Captured command output."""

    argv: tuple[str, ...]
    returncode: int
    stdout: str
    stderr: str
    cwd: str | None = None
    error_kind: MeshErrorKind = MeshErrorKind.NONE

    @property
    def ok(self) -> bool:
        return self.returncode == 0

    @property
    def combined_output(self) -> str:
        return "\n".join(part for part in (self.stdout.strip(), self.stderr.strip()) if part)

    def compact(self, limit: int = 220) -> str:
        text = self.combined_output.replace("\r", "").replace("\n", " | ").strip()
        if len(text) <= limit:
            return text
        return text[: limit - 3] + "..."


@dataclass(frozen=True, slots=True)
class AheadBehind:
    """Ahead/behind counters against the configured upstream."""

    ahead: int | None = None
    behind: int | None = None
    upstream: str | None = None
    error: str | None = None

    @property
    def divergent(self) -> bool:
        return bool((self.ahead or 0) and (self.behind or 0))

    @property
    def has_local_commits(self) -> bool:
        return bool((self.ahead or 0) > 0)

    @property
    def has_remote_commits(self) -> bool:
        return bool((self.behind or 0) > 0)

    def label(self) -> str:
        if self.error:
            return f"unknown:{self.error}"
        return f"ahead={self.ahead or 0};behind={self.behind or 0};upstream={self.upstream or 'none'}"


@dataclass(frozen=True, slots=True)
class GitState:
    """Non-destructive Git state for a repository materialization."""

    branch: str | None = None
    head: str | None = None
    head_summary: str | None = None
    remote_origin: str | None = None
    status_short: tuple[str, ...] = ()
    upstream: str | None = None
    ahead_behind: AheadBehind = field(default_factory=AheadBehind)
    fetch_result: CommandResult | None = None
    command_errors: tuple[CommandResult, ...] = ()

    @property
    def cleanliness(self) -> GitCleanliness:
        if self.status_short:
            return GitCleanliness.DIRTY
        if self.head or self.branch or self.remote_origin:
            return GitCleanliness.CLEAN
        return GitCleanliness.UNKNOWN

    @property
    def has_fetch_error(self) -> bool:
        return self.fetch_result is not None and not self.fetch_result.ok

    @property
    def fetch_error_kind(self) -> MeshErrorKind:
        if self.fetch_result is None:
            return MeshErrorKind.NONE
        return self.fetch_result.error_kind

    def dirty_preview(self, limit: int = 8) -> tuple[str, ...]:
        return self.status_short[:limit]


@dataclass(frozen=True, slots=True)
class MirrorObservation:
    """Observation of one repository target in one environment."""

    target_name: str
    environment_id: str
    environment_kind: MeshEnvironmentKind
    expected_path: str
    matched_path: str | None
    matched_name: str | None
    presence: MeshPresence
    git_state: GitState | None = None
    nomenclature_note: str | None = None
    observed_at: str = field(default_factory=utc_now)

    @property
    def is_git(self) -> bool:
        return self.presence == MeshPresence.PRESENT_GIT

    @property
    def is_missing(self) -> bool:
        return self.presence in {MeshPresence.MISSING, MeshPresence.UNREACHABLE_ENVIRONMENT}

    @property
    def hash_label(self) -> str:
        if self.git_state and self.git_state.head:
            return self.git_state.head
        return ""

    @property
    def branch_label(self) -> str:
        if self.git_state and self.git_state.branch:
            return self.git_state.branch
        return ""

    @property
    def remote_label(self) -> str:
        if self.git_state and self.git_state.remote_origin:
            return self.git_state.remote_origin
        return ""

    @property
    def status_label(self) -> str:
        if self.git_state is None:
            return self.presence.value
        return self.git_state.cleanliness.value


@dataclass(frozen=True, slots=True)
class MeshAction:
    """A planned action or blocked action."""

    action_id: str
    target_name: str
    environment_id: str
    kind: MeshActionKind
    risk: MeshRiskLevel
    reason: str
    command_preview: tuple[str, ...] = ()
    required_before: tuple[str, ...] = ()
    evidence: tuple[str, ...] = ()
    destructive: bool = False
    can_execute_automatically: bool = False

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


@dataclass(frozen=True, slots=True)
class MeshRepositorySummary:
    """Summary of one target across all environments."""

    target: RepositoryTarget
    observations: tuple[MirrorObservation, ...]
    actions: tuple[MeshAction, ...]
    risk: MeshRiskLevel
    aligned_hash: str | None
    hash_set: tuple[str, ...]
    dirty_count: int
    missing_count: int
    remote_mismatch_count: int
    nominal_mismatch_count: int
    credential_error_count: int
    destructive_block_count: int
    summary: str

    @property
    def present_git_count(self) -> int:
        return sum(1 for item in self.observations if item.presence == MeshPresence.PRESENT_GIT)

    @property
    def status_label(self) -> str:
        return self.risk.value


@dataclass(frozen=True, slots=True)
class MeshReport:
    """Full repository mesh report."""

    report_id: str
    generated_at: str
    ecosystem_root: str
    central_root: str | None
    targets: tuple[RepositoryTarget, ...]
    environments: tuple[MeshEnvironment, ...]
    summaries: tuple[MeshRepositorySummary, ...]
    fetch_enabled: bool
    plugin_auth_attempt: str = ""

    @property
    def total_observations(self) -> int:
        return sum(len(summary.observations) for summary in self.summaries)

    @property
    def blocked_count(self) -> int:
        return sum(1 for summary in self.summaries if summary.risk == MeshRiskLevel.BLOCKED)

    @property
    def attention_count(self) -> int:
        return sum(1 for summary in self.summaries if summary.risk == MeshRiskLevel.ATTENTION)

    @property
    def ok_count(self) -> int:
        return sum(1 for summary in self.summaries if summary.risk == MeshRiskLevel.OK)

    @property
    def missing_materializations(self) -> int:
        return sum(summary.missing_count for summary in self.summaries)

    @property
    def dirty_repositories(self) -> int:
        return sum(summary.dirty_count for summary in self.summaries)

    @property
    def credential_errors(self) -> int:
        return sum(summary.credential_error_count for summary in self.summaries)

    @property
    def destructive_blocks(self) -> int:
        return sum(summary.destructive_block_count for summary in self.summaries)

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


GitRunner = Callable[[Sequence[str], Path | None, int], CommandResult]


def classify_command_error(text: str, returncode: int = 1) -> MeshErrorKind:
    """Classify a command failure into a stable operational category."""

    if returncode == 0:
        return MeshErrorKind.NONE
    lowered = text.lower()
    if "dubious ownership" in lowered:
        return MeshErrorKind.DUBIOUS_OWNERSHIP
    if "sec_e_no_credentials" in lowered or "credenciais" in lowered or "credentials" in lowered:
        return MeshErrorKind.CREDENTIALS_MISSING
    if "authentication failed" in lowered or "401" in lowered or "403" in lowered:
        return MeshErrorKind.AUTHENTICATION
    if "could not resolve host" in lowered or "failed to connect" in lowered or "timed out" in lowered:
        return MeshErrorKind.NETWORK
    if "not a git repository" in lowered:
        return MeshErrorKind.NOT_A_REPOSITORY
    if "not recognized" in lowered or "not found" in lowered and "git" in lowered:
        return MeshErrorKind.COMMAND_NOT_FOUND
    return MeshErrorKind.UNKNOWN


def default_git_runner(argv: Sequence[str], cwd: Path | None = None, timeout: int = 60) -> CommandResult:
    """Run a Git command and capture output without shell interpolation."""

    try:
        completed = subprocess.run(
            list(argv),
            cwd=str(cwd) if cwd is not None else None,
            text=True,
            encoding="utf-8",
            errors="replace",
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            timeout=timeout,
            check=False,
        )
        combined = "\n".join(part for part in (completed.stdout, completed.stderr) if part)
        return CommandResult(
            argv=tuple(str(item) for item in argv),
            returncode=completed.returncode,
            stdout=completed.stdout,
            stderr=completed.stderr,
            cwd=str(cwd) if cwd is not None else None,
            error_kind=classify_command_error(combined, completed.returncode),
        )
    except subprocess.TimeoutExpired as exc:
        return CommandResult(
            argv=tuple(str(item) for item in argv),
            returncode=124,
            stdout=exc.stdout or "",
            stderr=exc.stderr or "command timed out",
            cwd=str(cwd) if cwd is not None else None,
            error_kind=MeshErrorKind.TIMEOUT,
        )
    except OSError as exc:
        return CommandResult(
            argv=tuple(str(item) for item in argv),
            returncode=127,
            stdout="",
            stderr=str(exc),
            cwd=str(cwd) if cwd is not None else None,
            error_kind=classify_command_error(str(exc), 127),
        )


def git_command(repo_path: Path, *args: str) -> tuple[str, ...]:
    """Build a Git argv with safe.directory enabled for inspected mirrors."""

    return ("git", "-c", "safe.directory=*", "-C", str(repo_path), *args)


def read_command_text(result: CommandResult) -> str | None:
    if not result.ok:
        return None
    text = result.stdout.strip()
    return text or None


def split_status_lines(text: str | None) -> tuple[str, ...]:
    if not text:
        return ()
    return tuple(line.rstrip() for line in text.splitlines() if line.strip())


def parse_ahead_behind(text: str | None, upstream: str | None, error: str | None = None) -> AheadBehind:
    if error:
        return AheadBehind(upstream=upstream, error=error)
    if not text:
        return AheadBehind(upstream=upstream)
    parts = text.strip().split()
    if len(parts) < 2:
        return AheadBehind(upstream=upstream, error=f"saida inesperada: {text!r}")
    try:
        return AheadBehind(ahead=int(parts[0]), behind=int(parts[1]), upstream=upstream)
    except ValueError:
        return AheadBehind(upstream=upstream, error=f"saida nao numerica: {text!r}")


def stable_digest(value: object, length: int = 16) -> str:
    text = json.dumps(as_plain_data(value), ensure_ascii=False, sort_keys=True, separators=(",", ":"))
    return hashlib.sha256(text.encode("utf-8")).hexdigest()[:length]


def default_repository_targets() -> tuple[RepositoryTarget, ...]:
    """Return repository declarations from the permanent synchronization order."""

    return (
        RepositoryTarget(
            declared_name="tudo-para-ia-business-platform",
            expected_local_name="tudo-para-ia-business-platform",
            gitea_repo="admin/tudo-para-ia-business-platform",
            central_folder="01_repo_tudo-para-ia-business-platform",
        ),
        RepositoryTarget(
            declared_name="tudo-para-ia-compliance-platform",
            expected_local_name="tudo-para-ia-compliance-platform",
            gitea_repo="admin/tudo-para-ia-compliance-platform",
            central_folder="02_repo_tudo-para-ia-compliance-platform",
        ),
        RepositoryTarget(
            declared_name="tudo-para-ia-customer-ops-platform",
            expected_local_name="tudo-para-ia-customer-ops-platform",
            gitea_repo="admin/tudo-para-ia-customer-ops-platform",
            central_folder="03_repo_tudo-para-ia-customer-ops-platform",
        ),
        RepositoryTarget(
            declared_name="tudo-para-ia-docs-plataform",
            expected_local_name="tudo-para-ia-docs-plataform",
            gitea_repo="admin/tudo-para-ia-docs-plataform",
            central_folder="04_repo_tudo-para-ia-docs-plataform",
        ),
        RepositoryTarget(
            declared_name="tudo-para-ia-finance-platform",
            expected_local_name="tudo-para-ia-finance-platform",
            gitea_repo="admin/tudo-para-ia-finance-platform",
            central_folder="05_repo_tudo-para-ia-finance-platform",
        ),
        RepositoryTarget(
            declared_name="tudo-para-ia-gettys-platform",
            expected_local_name="tudo-para-ia-gettys-platform",
            gitea_repo="admin/tudo-para-ia-gettys-platform",
            central_folder="06_repo_tudo-para-ia-gettys-platform",
        ),
        RepositoryTarget(
            declared_name="tudo-para-ia-identity-platform",
            expected_local_name="tudo-para-ia-identity-platform",
            gitea_repo="admin/tudo-para-ia-identity-platform",
            central_folder="07_repo_tudo-para-ia-identity-platform",
        ),
        RepositoryTarget(
            declared_name="tudo-para-ia-integracoes-platform",
            expected_local_name="tudo-para-ia-integracoes-platform",
            gitea_repo="admin/tudo-para-ia-integracoes-platform",
            central_folder="08_repo_tudo-para-ia-integracoes-plataform",
            aliases=("tudo-para-ia-integracoes-plataform",),
            canonical_name="tudo-para-ia-integracoes-plataform",
            requires_nominal_reconciliation=True,
            notes=("ordem declarou platform, espelho local conhecido usa plataform",),
        ),
        RepositoryTarget(
            declared_name="tudo-para-ia-intelligence-platform",
            expected_local_name="tudo-para-ia-intelligence-platform",
            gitea_repo="admin/tudo-para-ia-intelligence-platform",
            central_folder="09_repo_tudo-para-ia-intelligence-platform",
        ),
        RepositoryTarget(
            declared_name=CANONICAL_PROJECT_ID,
            expected_local_name=CANONICAL_PROJECT_ID,
            gitea_repo=f"admin/{CANONICAL_PROJECT_ID}",
            central_folder="15_repo_tudo-para-ia-mais-humana-platform",
            aliases=(CURRENT_PROJECT_ID, LEGACY_PLATAFORM_ALIAS),
            canonical_name=CANONICAL_PROJECT_ID,
            requires_nominal_reconciliation=True,
            notes=(
                "decisao institucional aprovou -platform como canonico; repo local/remoto historico sem sufixo permanece alias ate migracao coordenada",
            ),
        ),
        RepositoryTarget(
            declared_name="tudo-para-ia-mcps-internos-plataform",
            expected_local_name="tudo-para-ia-mcps-internos-plataform",
            gitea_repo="admin/tudo-para-ia-mcps-internos-plataform",
            central_folder="10_repo_tudo-para-ia-mcps-internos-plataform",
        ),
        RepositoryTarget(
            declared_name="tudo-para-ia-platform-base",
            expected_local_name="tudo-para-ia-platform-base",
            gitea_repo="admin/tudo-para-ia-platform-base",
            central_folder="11_repo_tudo-para-ia-platform-base",
        ),
        RepositoryTarget(
            declared_name="tudo-para-ia-public-platform",
            expected_local_name="tudo-para-ia-public-platform",
            gitea_repo="admin/tudo-para-ia-public-platform",
            central_folder="12_repo_tudo-para-ia-public-platform",
        ),
        RepositoryTarget(
            declared_name="tudo-para-ia-stj-platform",
            expected_local_name="tudo-para-ia-stj-platform",
            gitea_repo="admin/tudo-para-ia-stj-platform",
            central_folder="13_repo_tudo-para-ia-stj-platform",
        ),
        RepositoryTarget(
            declared_name="tudo-para-ia-ui-platform",
            expected_local_name="tudo-para-ia-ui-platform",
            gitea_repo="admin/tudo-para-ia-ui-platform",
            central_folder="14_repo_tudo-para-ia-ui-platform",
        ),
    )


def default_mesh_environments(ecosystem_root: Path | str = "G:/_codex-git") -> tuple[MeshEnvironment, ...]:
    """Return environments from the permanent order."""

    root = str(Path(ecosystem_root))
    return (
        MeshEnvironment(
            environment_id="windows-primary",
            kind=MeshEnvironmentKind.WINDOWS_PRIMARY,
            root=root,
            role="espelho local Windows principal",
        ),
        MeshEnvironment(
            environment_id="windows-secondary",
            kind=MeshEnvironmentKind.WINDOWS_SECONDARY,
            root="G:/codex_vm/_codex-git",
            role="espelho local Windows secundario",
        ),
        MeshEnvironment(
            environment_id="codex-server",
            kind=MeshEnvironmentKind.CODEX_SERVER,
            root="/root/__gpt-codex",
            role="repositorios no servidor Codex",
            local=False,
        ),
        MeshEnvironment(
            environment_id="gitlab-server",
            kind=MeshEnvironmentKind.GITLAB_SERVER,
            root="/root/_codex-git",
            role="repositorios no ambiente GitLab",
            local=False,
        ),
    )


def environment_reachable(environment: MeshEnvironment) -> bool:
    """Check local path reachability from this Windows execution context."""

    try:
        return environment.root_path.exists()
    except OSError:
        return False


def find_materialized_path(target: RepositoryTarget, environment: MeshEnvironment) -> tuple[Path, str | None, str | None]:
    """Find expected or aliased repository path for a target."""

    expected = environment.path_for(target.expected_local_name)
    if expected.exists():
        return expected, target.expected_local_name, None
    for alias in target.all_local_names:
        alias_path = environment.path_for(alias)
        if alias_path.exists():
            note = f"materializado como alias {alias}; esperado {target.expected_local_name}"
            return alias_path, alias, note
    return expected, None, None


def inspect_git_state(
    repo_path: Path,
    *,
    runner: GitRunner = default_git_runner,
    fetch: bool = False,
    timeout: int = 60,
) -> GitState:
    """Inspect a repository without destructive commands."""

    errors: list[CommandResult] = []

    def run(*args: str) -> CommandResult:
        result = runner(git_command(repo_path, *args), None, timeout)
        if not result.ok:
            errors.append(result)
        return result

    fetch_result: CommandResult | None = None
    if fetch:
        fetch_result = run("fetch", "--all", "--prune")

    branch_result = run("branch", "--show-current")
    head_result = run("rev-parse", "HEAD")
    remote_result = run("remote", "get-url", "origin")
    status_result = run("status", "--short")
    summary_result = run("log", "-1", "--oneline")
    upstream_result = run("rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}")

    upstream = read_command_text(upstream_result)
    if upstream:
        ahead_result = run("rev-list", "--left-right", "--count", f"HEAD...{upstream}")
        ahead_behind = parse_ahead_behind(read_command_text(ahead_result), upstream, None if ahead_result.ok else ahead_result.compact())
    else:
        ahead_behind = AheadBehind(upstream=None, error=upstream_result.compact() or "sem upstream")

    return GitState(
        branch=read_command_text(branch_result),
        head=read_command_text(head_result),
        head_summary=read_command_text(summary_result),
        remote_origin=read_command_text(remote_result),
        status_short=split_status_lines(read_command_text(status_result)),
        upstream=upstream,
        ahead_behind=ahead_behind,
        fetch_result=fetch_result,
        command_errors=tuple(errors),
    )


def observe_target_in_environment(
    target: RepositoryTarget,
    environment: MeshEnvironment,
    *,
    runner: GitRunner = default_git_runner,
    fetch: bool = False,
) -> MirrorObservation:
    """Observe one target in one environment."""

    expected_path = environment.path_for(target.expected_local_name)
    if not environment_reachable(environment):
        return MirrorObservation(
            target_name=target.declared_name,
            environment_id=environment.environment_id,
            environment_kind=environment.kind,
            expected_path=str(expected_path),
            matched_path=None,
            matched_name=None,
            presence=MeshPresence.UNREACHABLE_ENVIRONMENT,
            nomenclature_note=f"ambiente nao acessivel a partir desta sessao: {environment.root}",
        )
    matched_path, matched_name, note = find_materialized_path(target, environment)
    if not matched_path.exists():
        return MirrorObservation(
            target_name=target.declared_name,
            environment_id=environment.environment_id,
            environment_kind=environment.kind,
            expected_path=str(expected_path),
            matched_path=None,
            matched_name=None,
            presence=MeshPresence.MISSING,
            nomenclature_note=note,
        )
    git_dir = matched_path / ".git"
    if not git_dir.exists():
        return MirrorObservation(
            target_name=target.declared_name,
            environment_id=environment.environment_id,
            environment_kind=environment.kind,
            expected_path=str(expected_path),
            matched_path=str(matched_path),
            matched_name=matched_name,
            presence=MeshPresence.PRESENT_NOT_GIT,
            nomenclature_note=note or "diretorio existe, mas .git nao foi encontrado",
        )
    state = inspect_git_state(matched_path, runner=runner, fetch=fetch)
    return MirrorObservation(
        target_name=target.declared_name,
        environment_id=environment.environment_id,
        environment_kind=environment.kind,
        expected_path=str(expected_path),
        matched_path=str(matched_path),
        matched_name=matched_name,
        presence=MeshPresence.PRESENT_GIT,
        git_state=state,
        nomenclature_note=note,
    )


def remote_matches_expected(remote: str | None, target: RepositoryTarget) -> bool:
    if not remote:
        return False
    return normalize_remote_url(remote) == normalize_remote_url(target.expected_remote_url)


def normalize_remote_url(value: str) -> str:
    cleaned = value.strip().replace("\\", "/")
    cleaned = cleaned.removesuffix("/")
    if cleaned.endswith(".git"):
        cleaned = cleaned[:-4]
    return cleaned.lower()


def action(
    target: RepositoryTarget,
    observation: MirrorObservation,
    kind: MeshActionKind,
    risk: MeshRiskLevel,
    reason: str,
    *,
    command_preview: Iterable[str] = (),
    required_before: Iterable[str] = (),
    evidence: Iterable[str] = (),
    destructive: bool = False,
    can_execute_automatically: bool = False,
) -> MeshAction:
    seed = {
        "target": target.declared_name,
        "environment": observation.environment_id,
        "kind": kind.value,
        "reason": reason,
        "commands": list(command_preview),
    }
    return MeshAction(
        action_id=f"mesh-action-{stable_digest(seed, 12)}",
        target_name=target.declared_name,
        environment_id=observation.environment_id,
        kind=kind,
        risk=risk,
        reason=reason,
        command_preview=tuple(command_preview),
        required_before=tuple(required_before),
        evidence=tuple(evidence),
        destructive=destructive,
        can_execute_automatically=can_execute_automatically,
    )


def actions_for_observation(target: RepositoryTarget, observation: MirrorObservation) -> tuple[MeshAction, ...]:
    """Build safe actions for a single observation."""

    actions: list[MeshAction] = []
    if observation.presence == MeshPresence.UNREACHABLE_ENVIRONMENT:
        actions.append(
            action(
                target,
                observation,
                MeshActionKind.REQUIRE_ENVIRONMENT_ACCESS,
                MeshRiskLevel.BLOCKED,
                "ambiente declarado na OS nao esta acessivel nesta sessao",
                required_before=("abrir sessao no ambiente indicado", "rodar inventario local com a mesma rotina"),
                evidence=(observation.nomenclature_note or observation.expected_path,),
            )
        )
        return tuple(actions)
    if observation.presence == MeshPresence.MISSING:
        actions.append(
            action(
                target,
                observation,
                MeshActionKind.CLONE_MISSING,
                MeshRiskLevel.ATTENTION,
                "espelho ausente; clonar somente se destino continuar vazio e credencial estiver disponivel",
                command_preview=(
                    f"git clone {target.expected_remote_url} {observation.expected_path}",
                ),
                required_before=("confirmar credencial Git", "confirmar que nao existe diretorio de destino", "executar clone sem sobrescrever"),
                evidence=(observation.expected_path,),
                can_execute_automatically=False,
            )
        )
        return tuple(actions)
    if observation.presence == MeshPresence.PRESENT_NOT_GIT:
        actions.append(
            action(
                target,
                observation,
                MeshActionKind.BLOCK_DESTRUCTIVE_SYNC,
                MeshRiskLevel.BLOCKED,
                "diretorio existe sem .git; clone ou replace automatico poderia apagar conteudo local",
                required_before=("inventariar conteudo do diretorio", "decidir se e backup, alias ou erro de materializacao"),
                evidence=(observation.matched_path or observation.expected_path,),
                destructive=True,
            )
        )
        return tuple(actions)

    state = observation.git_state
    if state is None:
        actions.append(
            action(
                target,
                observation,
                MeshActionKind.REGISTER_EVIDENCE,
                MeshRiskLevel.ATTENTION,
                "estado Git nao foi coletado",
                evidence=(observation.matched_path or observation.expected_path,),
            )
        )
        return tuple(actions)

    if observation.nomenclature_note:
        kind = MeshActionKind.RENAME_LOCAL_FOLDER if target.requires_nominal_reconciliation else MeshActionKind.CREATE_ALIAS_RECORD
        auto = kind == MeshActionKind.RENAME_LOCAL_FOLDER and state.cleanliness == GitCleanliness.CLEAN
        actions.append(
            action(
                target,
                observation,
                kind,
                MeshRiskLevel.ATTENTION,
                observation.nomenclature_note,
                command_preview=(f"Rename-Item -LiteralPath '{observation.matched_path}' -NewName '{target.expected_local_name}'",)
                if kind == MeshActionKind.RENAME_LOCAL_FOLDER
                else (),
                required_before=("confirmar destino inexistente", "registrar hash HEAD antes do renome"),
                evidence=(observation.matched_path or "", observation.expected_path, state.head or ""),
                can_execute_automatically=auto,
            )
        )
    if not remote_matches_expected(state.remote_origin, target):
        actions.append(
            action(
                target,
                observation,
                MeshActionKind.FIX_REMOTE_URL,
                MeshRiskLevel.ATTENTION,
                "remote origin nao corresponde ao Gitea esperado",
                command_preview=(f"git remote set-url origin {target.expected_remote_url}",),
                required_before=("registrar remote anterior", "executar fetch apos ajuste", "nao alterar branch nem working tree"),
                evidence=(state.remote_origin or "sem remote", target.expected_remote_url),
                can_execute_automatically=False,
            )
        )
    if state.cleanliness == GitCleanliness.DIRTY:
        actions.append(
            action(
                target,
                observation,
                MeshActionKind.BLOCK_DESTRUCTIVE_SYNC,
                MeshRiskLevel.BLOCKED,
                "working tree sujo; pull/reset/merge/rebase poderiam sobrescrever alteracao valida",
                required_before=("commit ou stash consciente das alteracoes", "registrar diff resumido", "decidir precedencia"),
                evidence=state.dirty_preview(),
                destructive=True,
            )
        )
    if state.ahead_behind.divergent:
        actions.append(
            action(
                target,
                observation,
                MeshActionKind.BLOCK_DESTRUCTIVE_SYNC,
                MeshRiskLevel.BLOCKED,
                "branch divergente ahead/behind; reconciliacao automatica poderia escolher versao antiga",
                required_before=("comparar commits", "decidir fonte valida mais recente", "fazer merge/rebase apenas com operador"),
                evidence=(state.ahead_behind.label(),),
                destructive=True,
            )
        )
    elif state.ahead_behind.has_local_commits:
        actions.append(
            action(
                target,
                observation,
                MeshActionKind.REGISTER_EVIDENCE,
                MeshRiskLevel.ATTENTION,
                "ha commits locais nao enviados; push exige credencial e revisao de precedencia",
                command_preview=("git push origin HEAD",),
                required_before=("confirmar remoto correto", "confirmar que push nao reverte estado remoto mais recente"),
                evidence=(state.ahead_behind.label(), state.head or ""),
                can_execute_automatically=False,
            )
        )
    elif state.ahead_behind.has_remote_commits:
        actions.append(
            action(
                target,
                observation,
                MeshActionKind.BLOCK_DESTRUCTIVE_SYNC,
                MeshRiskLevel.BLOCKED if state.cleanliness == GitCleanliness.DIRTY else MeshRiskLevel.ATTENTION,
                "ha commits remotos nao aplicados; pull automatico so e seguro com working tree limpo e comparacao de hash",
                command_preview=("git fetch --all --prune", "git merge --ff-only @{u}"),
                required_before=("confirmar working tree limpo", "usar somente fast-forward", "bloquear se nao for fast-forward"),
                evidence=(state.ahead_behind.label(),),
                destructive=False,
                can_execute_automatically=state.cleanliness == GitCleanliness.CLEAN,
            )
        )
    if state.has_fetch_error:
        kind = (
            MeshActionKind.REQUIRE_CREDENTIAL
            if state.fetch_error_kind in {MeshErrorKind.CREDENTIALS_MISSING, MeshErrorKind.AUTHENTICATION}
            else MeshActionKind.REGISTER_EVIDENCE
        )
        risk = MeshRiskLevel.BLOCKED if kind == MeshActionKind.REQUIRE_CREDENTIAL else MeshRiskLevel.ATTENTION
        actions.append(
            action(
                target,
                observation,
                kind,
                risk,
                f"fetch falhou: {state.fetch_error_kind.value}",
                required_before=("fornecer credencial Git valida", "reexecutar fetch nao destrutivo"),
                evidence=(state.fetch_result.compact() if state.fetch_result else "",),
            )
        )
    if not actions:
        actions.append(
            action(
                target,
                observation,
                MeshActionKind.FETCH,
                MeshRiskLevel.OK,
                "espelho materializado; fetch nao destrutivo e acao padrao de manutencao",
                command_preview=("git fetch --all --prune",),
                evidence=(state.head or "", state.remote_origin or ""),
                can_execute_automatically=True,
            )
        )
    return tuple(actions)


def summarize_repository(target: RepositoryTarget, observations: Sequence[MirrorObservation]) -> MeshRepositorySummary:
    """Summarize risks and actions for one target."""

    all_actions: list[MeshAction] = []
    for observation in observations:
        all_actions.extend(actions_for_observation(target, observation))

    hashes = merge_unique(obs.hash_label for obs in observations if obs.hash_label)
    aligned_hash = hashes[0] if len(hashes) == 1 and hashes else None
    dirty = sum(1 for obs in observations if obs.git_state and obs.git_state.cleanliness == GitCleanliness.DIRTY)
    missing = sum(1 for obs in observations if obs.is_missing)
    remote_mismatch = sum(
        1
        for obs in observations
        if obs.git_state is not None and not remote_matches_expected(obs.git_state.remote_origin, target)
    )
    nominal_mismatch = sum(1 for obs in observations if bool(obs.nomenclature_note))
    credential_errors = sum(
        1
        for obs in observations
        if obs.git_state is not None
        and obs.git_state.fetch_error_kind in {MeshErrorKind.CREDENTIALS_MISSING, MeshErrorKind.AUTHENTICATION}
    )
    destructive_blocks = sum(1 for action_item in all_actions if action_item.kind == MeshActionKind.BLOCK_DESTRUCTIVE_SYNC)
    if destructive_blocks or credential_errors or any(action_item.risk == MeshRiskLevel.BLOCKED for action_item in all_actions):
        risk = MeshRiskLevel.BLOCKED
    elif missing or remote_mismatch or nominal_mismatch or len(hashes) > 1:
        risk = MeshRiskLevel.ATTENTION
    else:
        risk = MeshRiskLevel.OK

    summary_parts: list[str] = []
    if aligned_hash:
        summary_parts.append(f"hash unico {aligned_hash[:12]}")
    elif hashes:
        summary_parts.append(f"{len(hashes)} hashes distintos")
    else:
        summary_parts.append("sem hash HEAD confirmado")
    if missing:
        summary_parts.append(f"{missing} materializacoes ausentes/inacessiveis")
    if dirty:
        summary_parts.append(f"{dirty} worktrees sujos")
    if remote_mismatch:
        summary_parts.append(f"{remote_mismatch} remotes divergentes")
    if nominal_mismatch:
        summary_parts.append(f"{nominal_mismatch} divergencias nominais")
    if credential_errors:
        summary_parts.append(f"{credential_errors} falhas de credencial")
    if destructive_blocks:
        summary_parts.append(f"{destructive_blocks} bloqueios contra sync destrutiva")

    return MeshRepositorySummary(
        target=target,
        observations=tuple(observations),
        actions=tuple(all_actions),
        risk=risk,
        aligned_hash=aligned_hash,
        hash_set=hashes,
        dirty_count=dirty,
        missing_count=missing,
        remote_mismatch_count=remote_mismatch,
        nominal_mismatch_count=nominal_mismatch,
        credential_error_count=credential_errors,
        destructive_block_count=destructive_blocks,
        summary="; ".join(summary_parts),
    )


def build_mesh_report(
    ecosystem_root: Path,
    *,
    central_root: Path | None = None,
    targets: Sequence[RepositoryTarget] | None = None,
    environments: Sequence[MeshEnvironment] | None = None,
    runner: GitRunner = default_git_runner,
    fetch: bool = False,
    plugin_auth_attempt: str = "",
) -> MeshReport:
    """Build a full report from local observation."""

    selected_targets = tuple(targets or default_repository_targets())
    selected_environments = tuple(environments or default_mesh_environments(ecosystem_root))
    summaries: list[MeshRepositorySummary] = []
    for target in selected_targets:
        observations = tuple(
            observe_target_in_environment(target, environment, runner=runner, fetch=fetch)
            for environment in selected_environments
        )
        summaries.append(summarize_repository(target, observations))
    report_seed = {
        "ecosystem": str(ecosystem_root),
        "central": str(central_root) if central_root else "",
        "targets": [target.declared_name for target in selected_targets],
        "generated": utc_now(),
    }
    return MeshReport(
        report_id=f"repository-mesh-{stable_digest(report_seed, 12)}",
        generated_at=utc_now(),
        ecosystem_root=str(ecosystem_root),
        central_root=str(central_root) if central_root else None,
        targets=selected_targets,
        environments=selected_environments,
        summaries=tuple(summaries),
        fetch_enabled=fetch,
        plugin_auth_attempt=plugin_auth_attempt,
    )


def mesh_inventory_rows(report: MeshReport) -> list[list[str]]:
    """Rows required by the permanent synchronization order."""

    rows = [
        [
            "nome_declarado",
            "nome_esperado",
            "repositorio_gitea",
            "ambiente",
            "caminho_esperado",
            "nome_encontrado",
            "caminho_encontrado",
            "presenca",
            "branch",
            "head",
            "remote_origin",
            "status_worktree",
            "ahead_behind",
            "fetch",
            "observacao_nomenclatura",
        ]
    ]
    for summary in report.summaries:
        target = summary.target
        for obs in summary.observations:
            state = obs.git_state
            rows.append(
                [
                    target.declared_name,
                    target.expected_local_name,
                    target.gitea_repo,
                    obs.environment_id,
                    obs.expected_path,
                    obs.matched_name or "",
                    obs.matched_path or "",
                    obs.presence.value,
                    state.branch if state else "",
                    state.head if state else "",
                    state.remote_origin if state else "",
                    state.cleanliness.value if state else obs.presence.value,
                    state.ahead_behind.label() if state else "",
                    state.fetch_result.compact() if state and state.fetch_result else ("not-run" if not report.fetch_enabled else ""),
                    obs.nomenclature_note or "",
                ]
            )
    return rows


def mesh_action_rows(report: MeshReport) -> list[list[str]]:
    rows = [
        [
            "action_id",
            "nome_declarado",
            "ambiente",
            "tipo",
            "risco",
            "automatico",
            "destrutivo",
            "motivo",
            "comandos",
            "pre_requisitos",
            "evidencias",
        ]
    ]
    for summary in report.summaries:
        for item in summary.actions:
            rows.append(
                [
                    item.action_id,
                    item.target_name,
                    item.environment_id,
                    item.kind.value,
                    item.risk.value,
                    "sim" if item.can_execute_automatically else "nao",
                    "sim" if item.destructive else "nao",
                    item.reason,
                    " | ".join(item.command_preview),
                    " | ".join(item.required_before),
                    " | ".join(item.evidence),
                ]
            )
    return rows


def rows_to_csv(rows: Sequence[Sequence[str]]) -> str:
    buffer = io.StringIO()
    writer = csv.writer(buffer, lineterminator="\n")
    writer.writerows(rows)
    return buffer.getvalue()


def mesh_inventory_csv(report: MeshReport) -> str:
    return rows_to_csv(mesh_inventory_rows(report))


def mesh_actions_csv(report: MeshReport) -> str:
    return rows_to_csv(mesh_action_rows(report))


def mesh_summary_payload(report: MeshReport) -> dict[str, Any]:
    """Compact JSON for dashboards and SQLite."""

    return {
        "reportId": report.report_id,
        "generatedAt": report.generated_at,
        "fetchEnabled": report.fetch_enabled,
        "pluginAuthAttempt": report.plugin_auth_attempt,
        "targets": len(report.targets),
        "environments": len(report.environments),
        "observations": report.total_observations,
        "ok": report.ok_count,
        "attention": report.attention_count,
        "blocked": report.blocked_count,
        "missingMaterializations": report.missing_materializations,
        "dirtyRepositories": report.dirty_repositories,
        "credentialErrors": report.credential_errors,
        "destructiveBlocks": report.destructive_blocks,
        "repositories": [
            {
                "declaredName": summary.target.declared_name,
                "expectedLocalName": summary.target.expected_local_name,
                "giteaRepo": summary.target.gitea_repo,
                "risk": summary.risk.value,
                "alignedHash": summary.aligned_hash,
                "hashes": list(summary.hash_set),
                "dirty": summary.dirty_count,
                "missing": summary.missing_count,
                "remoteMismatch": summary.remote_mismatch_count,
                "nominalMismatch": summary.nominal_mismatch_count,
                "credentialErrors": summary.credential_error_count,
                "fetchErrorKinds": merge_unique(
                    obs.git_state.fetch_error_kind.value
                    for obs in summary.observations
                    if obs.git_state is not None and obs.git_state.fetch_error_kind != MeshErrorKind.NONE
                ),
                "actionKinds": merge_unique(action_item.kind.value for action_item in summary.actions),
                "summary": summary.summary,
            }
            for summary in report.summaries
        ],
    }


def mesh_markdown(report: MeshReport) -> str:
    """Human-readable evidence report."""

    lines = [
        "# Repository Mesh Sync",
        "",
        f"- report_id: `{report.report_id}`",
        f"- generated_at: `{report.generated_at}`",
        f"- ecosystem_root: `{report.ecosystem_root}`",
        f"- fetch_enabled: `{report.fetch_enabled}`",
        f"- plugin_cloudflare_auth_attempt: `{report.plugin_auth_attempt or 'nao informado'}`",
        f"- repositorios declarados: `{len(report.targets)}`",
        f"- ambientes declarados: `{len(report.environments)}`",
        f"- observacoes: `{report.total_observations}`",
        f"- ok: `{report.ok_count}`",
        f"- attention: `{report.attention_count}`",
        f"- blocked: `{report.blocked_count}`",
        f"- worktrees sujos: `{report.dirty_repositories}`",
        f"- falhas de credencial: `{report.credential_errors}`",
        f"- bloqueios contra sync destrutiva: `{report.destructive_blocks}`",
        "",
        "## Regra de seguranca aplicada",
        "",
        "- Nenhum reset, checkout, clean, pull, merge ou rebase automatico foi executado por esta rotina.",
        "- Fetch e inventario usam `git -c safe.directory=*` para evitar falso negativo de ownership sem alterar config global.",
        "- Worktree sujo, divergencia ahead/behind, diretorio sem `.git`, credencial ausente e ambiente inacessivel viram bloqueio ou atencao documentada.",
        "- A rotina privilegia preservacao da alteracao valida mais recente e bloqueia qualquer acao que possa reintroduzir estado antigo.",
        "",
        "## Repositorios",
        "",
    ]
    for summary in report.summaries:
        lines.append(f"### {summary.target.declared_name}")
        lines.append("")
        lines.append(f"- esperado_local: `{summary.target.expected_local_name}`")
        lines.append(f"- repositorio_gitea: `{summary.target.gitea_repo}`")
        lines.append(f"- remote_esperado: `{summary.target.expected_remote_url}`")
        lines.append(f"- risco: `{summary.risk.value}`")
        lines.append(f"- resumo: {summary.summary}")
        if summary.target.notes:
            lines.append("- notas:")
            for note in summary.target.notes:
                lines.append(f"  - {note}")
        lines.append("")
        lines.append("| ambiente | presenca | branch | head | remote | worktree | observacao |")
        lines.append("| --- | --- | --- | --- | --- | --- | --- |")
        for obs in summary.observations:
            state = obs.git_state
            head = (state.head[:12] if state and state.head else "")
            remote = state.remote_origin if state and state.remote_origin else ""
            branch = state.branch if state and state.branch else ""
            worktree = state.cleanliness.value if state else obs.presence.value
            note = (obs.nomenclature_note or "").replace("|", "/")
            lines.append(
                f"| `{obs.environment_id}` | `{obs.presence.value}` | `{branch}` | `{head}` | `{remote}` | `{worktree}` | {note} |"
            )
        lines.append("")
        if summary.actions:
            lines.append("Acoes/decisoes:")
            for item in summary.actions[:12]:
                auto = "auto" if item.can_execute_automatically else "manual"
                lines.append(f"- `{item.kind.value}` `{item.risk.value}` `{auto}`: {item.reason}")
        lines.append("")
    return "\n".join(lines).strip() + "\n"


def automation_markdown(report: MeshReport) -> str:
    """Describe the automatic synchronization mechanism required by the OS."""

    lines = [
        "# Repository Mesh Automation",
        "",
        "## Objetivo",
        "",
        "Executar a cada 5 minutos um ciclo nao destrutivo de inventario, fetch, comparacao de hashes e bloqueio de risco.",
        "",
        "## Contrato operacional",
        "",
        "- Etapa 1: criar lock por ambiente para evitar duas sincronizacoes simultaneas.",
        "- Etapa 2: inventariar caminho, `.git`, branch, HEAD, remote, status e upstream.",
        "- Etapa 3: executar somente `git fetch --all --prune` nos repositorios materializados.",
        "- Etapa 4: calcular ahead/behind sem modificar working tree.",
        "- Etapa 5: permitir fast-forward apenas quando worktree estiver limpo e o merge for comprovadamente `--ff-only`.",
        "- Etapa 6: bloquear qualquer dirty tree, divergencia, falta de credencial, ambiente ausente, remote divergente ou renome pendente.",
        "- Etapa 7: gravar JSON, CSV e Markdown com hash, timestamp, comando e decisao.",
        "- Etapa 8: nunca executar reset, clean, checkout, merge nao fast-forward, rebase ou pull generico.",
        "",
        "## Scheduled Task Windows sugerida",
        "",
        "```powershell",
        "$python = 'C:\\Users\\Ami\\.cache\\codex-runtimes\\codex-primary-runtime\\dependencies\\python\\python.exe'",
        "$repo = 'G:\\_codex-git\\tudo-para-ia-mais-humana'",
        "$central = 'G:\\_codex-git\\nucleo-gestao-operacional\\central-de-ordem-de-servico\\projects\\15_repo_tudo-para-ia-mais-humana-platform'",
        "$action = New-ScheduledTaskAction -Execute $python -Argument \"-m mais_humana.cli repo-mesh --ecosystem-root G:\\_codex-git --project-root $repo --central-platform-folder $central --fetch\"",
        "$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 5)",
        "Register-ScheduledTask -TaskName 'TudoParaIA-RepositoryMeshSync' -Action $action -Trigger $trigger",
        "```",
        "",
        "## Cron Linux sugerido",
        "",
        "```cron",
        "*/5 * * * * cd /root/__gpt-codex/tudo-para-ia-mais-humana && python -m mais_humana.cli repo-mesh --ecosystem-root /root/__gpt-codex --fetch",
        "```",
        "",
        "## Estado desta rodada",
        "",
        f"- report_id: `{report.report_id}`",
        f"- blocked: `{report.blocked_count}`",
        f"- attention: `{report.attention_count}`",
        f"- credential_errors: `{report.credential_errors}`",
        f"- missing_materializations: `{report.missing_materializations}`",
        "",
    ]
    blockers = [
        f"{summary.target.declared_name}: {summary.summary}"
        for summary in report.summaries
        if summary.risk != MeshRiskLevel.OK
    ]
    if blockers:
        lines.append("## Bloqueios para ativacao plena")
        lines.append("")
        lines.extend(f"- {item}" for item in blockers[:40])
        lines.append("")
    return "\n".join(lines).strip() + "\n"


def central_orders_for_target(target: RepositoryTarget, central_projects_root: Path | None) -> tuple[str, ...]:
    """Return active service-order files for a target's central folder."""

    if central_projects_root is None:
        return ()
    folder = central_projects_root / target.central_folder
    if not folder.exists():
        return ()
    orders: list[str] = []
    for sub in ("orders/executivas", "orders/gerenciais"):
        order_dir = folder / sub
        if not order_dir.exists():
            continue
        for path in sorted(order_dir.glob("*.md"), key=lambda item: item.name):
            orders.append(str(path))
    return tuple(orders)


def mesh_orders_payload(report: MeshReport) -> dict[str, Any]:
    """Return repository to service-order mapping for the permanent OS."""

    central_root = Path(report.central_root) if report.central_root else None
    return {
        "reportId": report.report_id,
        "generatedAt": report.generated_at,
        "repositories": [
            {
                "declaredName": summary.target.declared_name,
                "expectedLocalName": summary.target.expected_local_name,
                "centralFolder": summary.target.central_folder,
                "risk": summary.risk.value,
                "activeOrders": list(central_orders_for_target(summary.target, central_root)),
                "actions": [action_item.to_dict() for action_item in summary.actions],
            }
            for summary in report.summaries
        ],
    }


def mesh_orders_markdown(report: MeshReport) -> str:
    payload = mesh_orders_payload(report)
    lines = [
        "# Ordens relacionadas por repositorio",
        "",
        f"- report_id: `{report.report_id}`",
        f"- generated_at: `{report.generated_at}`",
        "",
    ]
    for item in payload["repositories"]:
        lines.append(f"## {item['declaredName']}")
        lines.append("")
        lines.append(f"- pasta_central: `{item['centralFolder']}`")
        lines.append(f"- risco: `{item['risk']}`")
        orders = item["activeOrders"]
        if orders:
            lines.append("- ordens_ativas:")
            for order_path in orders[:30]:
                lines.append(f"  - `{order_path}`")
        else:
            lines.append("- ordens_ativas: nenhuma ordem encontrada ou pasta central ausente")
        lines.append("")
    return "\n".join(lines).strip() + "\n"


def write_json(path: Path, payload: object) -> Path:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(as_plain_data(payload), ensure_ascii=False, indent=2, sort_keys=True), encoding="utf-8")
    return path


def write_text(path: Path, text: str) -> Path:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(text, encoding="utf-8")
    return path


def mesh_generated_records(project_root: Path, central_folder: Path | None = None) -> tuple[GeneratedFile, ...]:
    relation = "000_sincronizacao-dos-espelhos"
    records = []
    for rel, description, function, file_type in (
        ("dados/repository-mesh-inventory.json", "Inventario completo da malha de repositorios.", "repository mesh", "json"),
        ("dados/repository-mesh-summary.json", "Resumo executivo da malha de repositorios.", "repository mesh summary", "json"),
        ("dados/repository-mesh-orders.json", "Mapa de ordens relacionadas por repositorio.", "repository mesh orders", "json"),
        ("dados/repository-mesh-central-write-status.json", "Status da escrita central dos artefatos repository mesh.", "repository mesh central status", "json"),
        ("matrizes/repository-mesh-inventory.csv", "Inventario tabular por ambiente e repositorio.", "repository mesh inventory", "csv"),
        ("matrizes/repository-mesh-actions.csv", "Acoes seguras e bloqueios por repositorio.", "repository mesh actions", "csv"),
        ("ecossistema/REPOSITORY-MESH-SYNC.md", "Relatorio humano de sincronizacao segura da malha.", "repository mesh report", "markdown"),
        ("ecossistema/REPOSITORY-MESH-AUTOMATION.md", "Especificacao da rotina automatica de 5 minutos.", "repository mesh automation", "markdown"),
        ("ecossistema/REPOSITORY-MESH-ORDERS.md", "Ordens relacionadas por repositorio consolidado.", "repository mesh orders", "markdown"),
        ("ecossistema/REPOSITORY-MESH-CENTRAL-WRITE-STATUS.md", "Relatorio de status da escrita central repository mesh.", "repository mesh central status", "markdown"),
    ):
        records.append(
            GeneratedFile(
                path=rel,
                description=description,
                function=function,
                file_type=file_type,
                changed_by="mais_humana.repository_mesh",
                change_summary=description,
                relation_to_order=relation,
            )
        )
    if central_folder is not None:
        for rel in (
            "reports/EXECUTADO__repository-mesh-sync.md",
            "reports/PENDENCIAS-CODEX__repository-mesh-sync.md",
            "audit/AUDITORIA-GPT__repository-mesh-sync.md",
            "indexes/repository-mesh-index.md",
        ):
            records.append(
                GeneratedFile(
                    path=str(central_folder / rel),
                    description="Registro da execucao da sincronizacao segura na central.",
                    function="repository mesh central record",
                    file_type="markdown",
                    changed_by="mais_humana.repository_mesh",
                    change_summary="Registro central da OS permanente de sincronizacao.",
                    relation_to_order=relation,
                )
            )
    return tuple(records)


def central_write_status_payload(
    central_platform_folder: Path | None,
    written_paths: Sequence[Path],
    failures: Sequence[Mapping[str, str]],
) -> dict[str, Any]:
    return {
        "generatedAt": utc_now(),
        "centralPlatformFolder": str(central_platform_folder) if central_platform_folder is not None else "",
        "requested": central_platform_folder is not None,
        "ok": central_platform_folder is None or not failures,
        "writtenCount": len(written_paths),
        "failureCount": len(failures),
        "written": [str(path) for path in written_paths],
        "failures": list(failures),
        "policy": "falha de escrita central nao deve abortar artefatos do projeto real",
    }


def central_write_status_markdown(status: Mapping[str, Any]) -> str:
    lines = [
        "# Repository Mesh Central Write Status",
        "",
        f"- generated_at: `{status.get('generatedAt', '')}`",
        f"- central_platform_folder: `{status.get('centralPlatformFolder', '')}`",
        f"- requested: `{status.get('requested')}`",
        f"- ok: `{status.get('ok')}`",
        f"- written_count: `{status.get('writtenCount')}`",
        f"- failure_count: `{status.get('failureCount')}`",
        "",
        "## Arquivos escritos",
        "",
    ]
    written = status.get("written") or []
    if written:
        lines.extend(f"- `{path}`" for path in written)
    else:
        lines.append("- Nenhum arquivo central escrito nesta etapa.")
    lines.extend(["", "## Falhas", ""])
    failures = status.get("failures") or []
    if failures:
        for item in failures:
            lines.append(f"- `{item.get('path', '')}` `{item.get('operation', '')}`: {item.get('error', '')}")
    else:
        lines.append("- Nenhuma falha de escrita central registrada.")
    lines.extend(
        [
            "",
            "## Politica",
            "",
            "- A falha local de escrita central deve ser registrada e a geracao do projeto real deve continuar.",
            "- Sincronizacao destrutiva, reset, checkout, clean, pull, merge ou rebase continuam proibidos por esta rotina.",
        ]
    )
    return "\n".join(lines).strip() + "\n"


def _central_generated_record(path: Path) -> GeneratedFile:
    return GeneratedFile(
        path=str(path),
        description="Registro da execucao da sincronizacao segura na central.",
        function="repository mesh central record",
        file_type="markdown",
        changed_by="mais_humana.repository_mesh",
        change_summary="Registro central da OS permanente de sincronizacao.",
        relation_to_order="000_sincronizacao-dos-espelhos",
    )


def write_mesh_artifacts(
    report: MeshReport,
    project_root: Path,
    *,
    central_platform_folder: Path | None = None,
) -> tuple[GeneratedFile, ...]:
    """Write JSON, CSV, Markdown, and central records for a mesh report."""

    write_json(project_root / "dados" / "repository-mesh-inventory.json", report)
    write_json(project_root / "dados" / "repository-mesh-summary.json", mesh_summary_payload(report))
    write_json(project_root / "dados" / "repository-mesh-orders.json", mesh_orders_payload(report))
    write_text(project_root / "matrizes" / "repository-mesh-inventory.csv", mesh_inventory_csv(report))
    write_text(project_root / "matrizes" / "repository-mesh-actions.csv", mesh_actions_csv(report))
    write_text(project_root / "ecossistema" / "REPOSITORY-MESH-SYNC.md", mesh_markdown(report))
    write_text(project_root / "ecossistema" / "REPOSITORY-MESH-AUTOMATION.md", automation_markdown(report))
    write_text(project_root / "ecossistema" / "REPOSITORY-MESH-ORDERS.md", mesh_orders_markdown(report))
    central_written: list[Path] = []
    central_failures: list[dict[str, str]] = []
    if central_platform_folder is not None:
        reports = central_platform_folder / "reports"
        audit = central_platform_folder / "audit"
        indexes = central_platform_folder / "indexes"
        try:
            for folder in (reports, audit, indexes):
                folder.mkdir(parents=True, exist_ok=True)
        except OSError as exc:
            central_failures.append(
                {
                    "path": str(central_platform_folder),
                    "operation": "mkdir",
                    "error": f"{exc.__class__.__name__}: {exc}",
                }
            )
        else:
            central_targets = (
                (reports / "EXECUTADO__repository-mesh-sync.md", mesh_markdown(report)),
                (reports / "PENDENCIAS-CODEX__repository-mesh-sync.md", mesh_pending_markdown(report)),
                (audit / "AUDITORIA-GPT__repository-mesh-sync.md", mesh_audit_markdown(report)),
                (indexes / "repository-mesh-index.md", mesh_orders_markdown(report)),
            )
            for path, text in central_targets:
                try:
                    write_text(path, text)
                except OSError as exc:
                    central_failures.append(
                        {
                            "path": str(path),
                            "operation": "write_text",
                            "error": f"{exc.__class__.__name__}: {exc}",
                        }
                    )
                else:
                    central_written.append(path)
    central_status = central_write_status_payload(central_platform_folder, central_written, central_failures)
    write_json(project_root / "dados" / "repository-mesh-central-write-status.json", central_status)
    write_text(project_root / "ecossistema" / "REPOSITORY-MESH-CENTRAL-WRITE-STATUS.md", central_write_status_markdown(central_status))
    records = list(mesh_generated_records(project_root, None))
    records.extend(_central_generated_record(path) for path in central_written)
    return tuple(records)


def mesh_pending_markdown(report: MeshReport) -> str:
    lines = ["# PENDENCIAS-CODEX - Repository Mesh Sync", ""]
    pending = []
    for summary in report.summaries:
        for item in summary.actions:
            if item.risk != MeshRiskLevel.OK:
                pending.append(f"{summary.target.declared_name} / {item.environment_id}: {item.reason}")
    if pending:
        lines.extend(f"- {item}" for item in pending)
    else:
        lines.append("- Nenhuma pendencia material detectada pela rotina repository mesh.")
    lines.append("")
    lines.append("## Observacao Cloudflare")
    lines.append("")
    lines.append("- A tentativa do plugin Cloudflare foi registrada somente como premissa inicial; falha do plugin nao foi tratada como blocker da OS.")
    lines.append("- Trabalho operacional Cloudflare permanece por `wrangler`, fora do teste de autenticacao inicial do plugin.")
    return "\n".join(lines).strip() + "\n"


def mesh_audit_markdown(report: MeshReport) -> str:
    lines = [
        "# AUDITORIA-GPT - Repository Mesh Sync",
        "",
        "## Confirmado",
        "",
        f"- Repositorios declarados: `{len(report.targets)}`",
        f"- Ambientes declarados: `{len(report.environments)}`",
        f"- Observacoes geradas: `{report.total_observations}`",
        f"- Fetch habilitado: `{report.fetch_enabled}`",
        f"- Bloqueios contra sync destrutiva: `{report.destructive_blocks}`",
        f"- Falhas de credencial: `{report.credential_errors}`",
        f"- Materializacoes ausentes/inacessiveis: `{report.missing_materializations}`",
        "",
        "## Parcial ou bloqueado",
        "",
    ]
    for summary in report.summaries:
        if summary.risk == MeshRiskLevel.OK:
            continue
        lines.append(f"- `{summary.target.declared_name}` risco `{summary.risk.value}`: {summary.summary}")
    lines.extend(
        [
            "",
            "## Regra de nao reversao",
            "",
            "- A rotina nao executou reset, checkout, clean, pull, merge ou rebase.",
            "- Toda acao que poderia sobrescrever estado local foi convertida em bloqueio ou decisao manual.",
            "- Fetch com credencial ausente foi registrado como pendencia material, nao como sucesso presumido.",
        ]
    )
    return "\n".join(lines).strip() + "\n"


DESTRUCTIVE_GIT_PATTERN = re.compile(
    r"\b(reset|clean|checkout|restore|merge|rebase|pull)\b",
    re.IGNORECASE,
)


def command_is_destructive(command: str) -> bool:
    """Return True if a command contains a forbidden sync primitive."""

    lowered = command.lower()
    if "merge --ff-only" in lowered:
        return False
    return bool(DESTRUCTIVE_GIT_PATTERN.search(lowered))


def validate_action_set(actions: Sequence[MeshAction]) -> tuple[str, ...]:
    """Validate that auto actions do not carry forbidden commands."""

    problems: list[str] = []
    for item in actions:
        if item.can_execute_automatically:
            for command in item.command_preview:
                if command_is_destructive(command):
                    problems.append(f"{item.action_id}: comando automatico destrutivo: {command}")
        if item.destructive and item.can_execute_automatically:
            problems.append(f"{item.action_id}: acao destrutiva marcada como automatica")
    return tuple(problems)


def validate_report(report: MeshReport) -> tuple[str, ...]:
    """Validate internal safety invariants of a report."""

    problems: list[str] = []
    for summary in report.summaries:
        problems.extend(validate_action_set(summary.actions))
        if summary.dirty_count and summary.risk == MeshRiskLevel.OK:
            problems.append(f"{summary.target.declared_name}: worktree sujo classificado como ok")
        if summary.credential_error_count and summary.risk == MeshRiskLevel.OK:
            problems.append(f"{summary.target.declared_name}: credencial ausente classificada como ok")
        if summary.destructive_block_count and summary.risk == MeshRiskLevel.OK:
            problems.append(f"{summary.target.declared_name}: bloqueio destrutivo classificado como ok")
    return tuple(problems)


def run_repository_mesh(
    ecosystem_root: Path,
    project_root: Path,
    *,
    central_platform_folder: Path | None = None,
    fetch: bool = False,
    plugin_auth_attempt: str = "",
    runner: GitRunner = default_git_runner,
) -> tuple[MeshReport, tuple[GeneratedFile, ...]]:
    """Build, validate, and write repository mesh artifacts."""

    central_root = central_platform_folder.parent if central_platform_folder is not None else None
    report = build_mesh_report(
        ecosystem_root,
        central_root=central_root,
        runner=runner,
        fetch=fetch,
        plugin_auth_attempt=plugin_auth_attempt,
    )
    problems = validate_report(report)
    if problems:
        problem_text = "\n".join(problems)
        raise ValueError(f"repository mesh safety validation failed:\n{problem_text}")
    records = write_mesh_artifacts(report, project_root, central_platform_folder=central_platform_folder)
    return report, records


def repository_mesh_artifact_records(project_root: Path, central_platform_folder: Path | None = None) -> tuple[dict[str, str], ...]:
    """Return semantic file records for storage integration."""

    return tuple(
        {
            "path": item.path if os.path.isabs(item.path) else str(project_root / item.path),
            "description": item.description,
            "function": item.function,
            "file_type": item.file_type,
        }
        for item in mesh_generated_records(project_root, central_platform_folder)
    )
