"""Robot Framework helper for M6 E2E verification tests.

Exercises the complete M6 success criteria sequence:
  1. Porting action creation from YAML config
  2. Plan use + execute via real CLI subprocess
  3. Hierarchical decomposition: 4+ levels of subplans
  4. Decision correction recomputes only affected subtree
  5. Parallel execution scales to 10+ concurrent subplans
  6. Realistic porting task completes autonomously (lifecycle)
  7. Plan apply transitions to APPLIED state via real CLI subprocess
  8. SubplanFailureHandler retry and stop-others logic

CLI-facing tests (action-create-porting, plan-use-execute, plan-apply-lifecycle)
invoke the real ``agents`` CLI via subprocess without mocking.
Domain-level tests exercise real application code directly.

Each subcommand prints a sentinel string on success and exits 0.
On failure it prints a diagnostic to stderr and exits 1.

Usage:
    python robot/helper_m6_e2e_verification.py <command>
"""

from __future__ import annotations

import os
import re
import sys
from collections.abc import Callable
from datetime import UTC, datetime
from pathlib import Path
from typing import NoReturn

# Ensure src is importable when run from workspace root
_SRC = str(Path(__file__).resolve().parents[1] / "src")
if _SRC not in sys.path:
    sys.path.insert(0, _SRC)

# Ensure robot/ is on the import path for helper_e2e_common.
_ROBOT = str(Path(__file__).resolve().parent)
if _ROBOT not in sys.path:
    sys.path.insert(0, _ROBOT)

from helper_e2e_common import (  # noqa: E402
    cleanup_workspace,
    is_expected_provider_unavailable,
    run_cli,
    setup_workspace,
    write_yaml,
)
from helpers_common import reset_global_state  # noqa: E402
from ulid import ULID  # noqa: E402

from cleveragents.domain.models.core.correction import (  # noqa: E402
    CorrectionDryRunReport,
    CorrectionImpact,
    CorrectionMode,
    CorrectionRequest,
    CorrectionStatus,
)
from cleveragents.domain.models.core.decision import (  # noqa: E402
    Decision,
    DecisionType,
)
from cleveragents.domain.models.core.plan import (  # noqa: E402
    ExecutionMode,
    NamespacedName,
    Plan,
    PlanIdentity,
    PlanPhase,
    ProcessingState,
    SubplanConfig,
    SubplanFailureHandler,
    SubplanMergeStrategy,
    SubplanStatus,
)

# -------------------------------------------------------------------
# Valid ULID constants
# -------------------------------------------------------------------
_ROOT_PLAN_ID = str(ULID())
_CHILD_PLAN_IDS = [str(ULID()) for _ in range(4)]
_GRANDCHILD_PLAN_IDS = [str(ULID()) for _ in range(8)]
_GREAT_GRANDCHILD_IDS = [str(ULID()) for _ in range(4)]
_LEAF_IDS = [str(ULID()) for _ in range(4)]
_ACTION_ULID = str(ULID())

_ROOT_DEC_ID = str(ULID())
_CHILD_DEC_IDS = [str(ULID()) for _ in range(4)]

_ACTION_YAML = """\
name: local/port-to-typescript
description: Autonomous Python to TypeScript porting action
definition_of_done: All Python files converted to TypeScript with tests
strategy_actor: openai/gpt-4
execution_actor: openai/gpt-4
"""


# -------------------------------------------------------------------
# Helpers
# -------------------------------------------------------------------


def _fail(msg: str) -> NoReturn:
    """Print failure message to stderr and exit with code 1."""
    print(f"FAIL: {msg}", file=sys.stderr)
    raise SystemExit(1)


def _extract_plan_id(output: str) -> str | None:
    """Extract a ULID plan_id from plain CLI output."""
    match = re.search(r"\b([0-9A-Z]{26})\b", output)
    return match.group(1) if match else None


def _make_plan(
    *,
    plan_id: str = _ROOT_PLAN_ID,
    parent_plan_id: str | None = None,
    root_plan_id: str | None = None,
    phase: PlanPhase = PlanPhase.STRATEGIZE,
    processing_state: ProcessingState = ProcessingState.QUEUED,
    subplan_config: SubplanConfig | None = None,
    subplan_statuses: list[SubplanStatus] | None = None,
    action_name: str = "local/port-to-typescript",
    description: str = "Port Python to TypeScript",
) -> Plan:
    """Create a Plan for testing with minimal required fields."""
    return Plan(
        identity=PlanIdentity(
            plan_id=plan_id,
            parent_plan_id=parent_plan_id,
            root_plan_id=root_plan_id,
        ),
        namespaced_name=NamespacedName(
            server=None,
            namespace="local",
            name="port-to-typescript",
        ),
        description=description,
        action_name=action_name,
        phase=phase,
        processing_state=processing_state,
        subplan_config=subplan_config,
        subplan_statuses=subplan_statuses or [],
    )


# -------------------------------------------------------------------
# Subcommand: action-create-porting (CLI via subprocess)
# -------------------------------------------------------------------


def action_create_porting() -> None:
    """Create a porting action from YAML config via real CLI subprocess.

    Writes a YAML file, invokes ``agents action create --config``
    via subprocess, and verifies the action is persisted by querying
    the database.
    """
    workspace = setup_workspace(prefix="m6_action_")
    yaml_path = write_yaml(_ACTION_YAML)
    try:
        r = run_cli("action", "create", "--config", yaml_path, workspace=workspace)
        if r.returncode != 0:
            _fail(f"action create: {r.stderr}")

        combined = r.stdout + r.stderr
        if "INTERNAL" in combined or "Traceback" in combined:
            _fail(f"action create crashed:\n{combined}")

        # Verify the output references the action name
        if "port-to-typescript" not in combined:
            _fail(f"action name not in output:\n{combined}")

        print("m6-action-create-porting-ok")
    finally:
        os.unlink(yaml_path)
        cleanup_workspace(workspace)


# -------------------------------------------------------------------
# Subcommand: plan-use-execute (CLI via subprocess)
# -------------------------------------------------------------------


def plan_use_execute() -> None:
    """Use and execute a porting plan via real CLI subprocess.

    Creates an action, then runs ``plan use`` and ``plan execute``
    via subprocess.  The plan is in strategize/queued so execute
    correctly rejects with a controlled "not ready" message.
    """
    workspace = setup_workspace(prefix="m6_use_exec_")
    yaml_path = write_yaml(_ACTION_YAML)
    try:
        # Create the action first
        r1 = run_cli("action", "create", "--config", yaml_path, workspace=workspace)
        if r1.returncode != 0:
            _fail(f"action create: {r1.stderr}")

        # Plan use
        r2 = run_cli(
            "plan",
            "use",
            "local/port-to-typescript",
            "local/large-project",
            "--format",
            "plain",
            workspace=workspace,
        )
        if r2.returncode != 0:
            _fail(f"plan use: {r2.stderr}")

        plan_id = _extract_plan_id(r2.stdout)
        if not plan_id:
            _fail(f"could not extract plan_id:\n{r2.stdout}")

        # Plan execute — plan is in strategize/queued, so this will
        # get a controlled rejection (not a crash).
        r3 = run_cli("plan", "execute", plan_id, workspace=workspace)
        combined = r3.stdout + r3.stderr
        if "Traceback" in combined:
            _fail(f"plan execute crashed:\n{combined}")
        if "INTERNAL" in combined and not is_expected_provider_unavailable(combined):
            _fail(f"plan execute crashed:\n{combined}")
        # Verify the output references the plan
        if plan_id not in combined:
            _fail(f"plan execute output missing plan_id {plan_id}")

        print("m6-plan-use-execute-ok")
    finally:
        os.unlink(yaml_path)
        cleanup_workspace(workspace)


# -------------------------------------------------------------------
# Subcommand: hierarchical-decomposition
# -------------------------------------------------------------------


def hierarchical_decomposition() -> None:
    """Verify hierarchical decomposition creates 4+ levels of subplans.

    Constructs a tree: root → L1 (4 children) → L2 (8 grandchildren)
    → L3 (4 great-grandchildren) → L4 (4 leaves).
    Verifies identity flags and root_plan_id propagation.
    """
    # Level 0: root plan
    root = _make_plan(plan_id=_ROOT_PLAN_ID, root_plan_id=_ROOT_PLAN_ID)
    if not root.is_root_plan:
        _fail("root should be is_root_plan")
    if root.is_subplan:
        _fail("root should not be is_subplan")

    # Level 1: 4 children
    level1 = []
    for cid in _CHILD_PLAN_IDS:
        p = _make_plan(
            plan_id=cid,
            parent_plan_id=_ROOT_PLAN_ID,
            root_plan_id=_ROOT_PLAN_ID,
            description=f"L1 subplan {cid[:8]}",
        )
        if not p.is_subplan:
            _fail(f"L1 {cid} should be subplan")
        if p.identity.root_plan_id != _ROOT_PLAN_ID:
            _fail(f"L1 root_plan_id mismatch: {p.identity.root_plan_id}")
        level1.append(p)

    if len(level1) < 4:
        _fail(f"expected >= 4 L1 subplans, got {len(level1)}")

    # Level 2: 8 grandchildren (2 per L1)
    level2 = []
    for i, gcid in enumerate(_GRANDCHILD_PLAN_IDS):
        parent = _CHILD_PLAN_IDS[i % len(_CHILD_PLAN_IDS)]
        p = _make_plan(
            plan_id=gcid,
            parent_plan_id=parent,
            root_plan_id=_ROOT_PLAN_ID,
            description=f"L2 subplan {gcid[:8]}",
        )
        if p.identity.root_plan_id != _ROOT_PLAN_ID:
            _fail(f"L2 root mismatch: {p.identity.root_plan_id}")
        level2.append(p)

    if len(level2) < 4:
        _fail(f"expected >= 4 L2 subplans, got {len(level2)}")

    # Level 3: 4 great-grandchildren
    level3 = []
    for i, ggid in enumerate(_GREAT_GRANDCHILD_IDS):
        parent = _GRANDCHILD_PLAN_IDS[i % len(_GRANDCHILD_PLAN_IDS)]
        p = _make_plan(
            plan_id=ggid,
            parent_plan_id=parent,
            root_plan_id=_ROOT_PLAN_ID,
            description=f"L3 subplan {ggid[:8]}",
        )
        level3.append(p)

    # Level 4: 4 leaves
    level4 = []
    for i, lid in enumerate(_LEAF_IDS):
        parent = _GREAT_GRANDCHILD_IDS[i % len(_GREAT_GRANDCHILD_IDS)]
        p = _make_plan(
            plan_id=lid,
            parent_plan_id=parent,
            root_plan_id=_ROOT_PLAN_ID,
            description=f"L4 leaf subplan {lid[:8]}",
        )
        level4.append(p)

    total = 1 + len(level1) + len(level2) + len(level3) + len(level4)
    levels = 5  # L0, L1, L2, L3, L4
    if levels < 5:
        _fail(f"expected >= 5 levels (4+ subplan levels), got {levels}")

    # Verify all non-root plans propagate root_plan_id
    for plan in [*level1, *level2, *level3, *level4]:
        if plan.identity.root_plan_id != _ROOT_PLAN_ID:
            _fail(f"root_plan_id not propagated: {plan.identity.plan_id}")
        if not plan.is_subplan:
            _fail(f"should be subplan: {plan.identity.plan_id}")

    if total < 21:
        _fail(f"expected >= 21 total plans, got {total}")

    print("m6-hierarchical-decomposition-ok")


# -------------------------------------------------------------------
# Subcommand: correction-affected-subtree
# -------------------------------------------------------------------


def correction_affected_subtree() -> None:
    """Verify decision correction recomputes only affected subtree.

    Creates a decision tree (root → 4 children), then simulates
    correction targeting one child. Verifies CorrectionImpact
    contains only the affected subtree, not the entire tree.
    """
    # Root decision
    root = Decision(
        decision_id=_ROOT_DEC_ID,
        plan_id=_ROOT_PLAN_ID,
        parent_decision_id=None,
        sequence_number=0,
        decision_type=DecisionType.PROMPT_DEFINITION,
        question="How to port this project?",
        chosen_option="Module-by-module porting strategy",
        rationale="Systematic approach for large codebase",
        downstream_decision_ids=_CHILD_DEC_IDS,
    )

    if root.parent_decision_id is not None:
        _fail("root decision should have no parent")
    if len(root.downstream_decision_ids) != len(_CHILD_DEC_IDS):
        _fail(f"root downstream count: {len(root.downstream_decision_ids)}")

    # 4 child decisions (one per module)
    children = []
    for i, cid in enumerate(_CHILD_DEC_IDS):
        d = Decision(
            decision_id=cid,
            plan_id=_ROOT_PLAN_ID,
            parent_decision_id=_ROOT_DEC_ID,
            sequence_number=i + 1,
            decision_type=DecisionType.SUBPLAN_SPAWN,
            question=f"How to port module {i}?",
            chosen_option=f"Direct translation for module {i}",
            rationale=f"Module {i} has 1:1 Python→TS mapping",
        )
        children.append(d)

    # Target child[1] for correction
    target_id = _CHILD_DEC_IDS[1]

    # Build CorrectionImpact scoped to just the target subtree
    impact = CorrectionImpact(
        affected_decisions=[target_id],
        affected_files=[f"src/module_1/file_{j}.py" for j in range(5)],
        affected_child_plans=[],
        risk_level="low",
        rollback_tier="phase",
        artifacts_to_archive=[],
    )

    # Verify only 1 decision affected (not the root or other children)
    if len(impact.affected_decisions) != 1:
        _fail(f"expected 1 affected decision, got {len(impact.affected_decisions)}")
    if impact.affected_decisions[0] != target_id:
        _fail(f"wrong affected decision: {impact.affected_decisions[0]}")

    # Verify root is NOT affected
    if _ROOT_DEC_ID in impact.affected_decisions:
        _fail("root should not be in affected decisions")

    # Verify other children not affected
    for i, cid in enumerate(_CHILD_DEC_IDS):
        if i == 1:
            continue
        if cid in impact.affected_decisions:
            _fail(f"child {i} should not be affected")

    # Verify rollback_tier supports subtree-only
    if impact.rollback_tier not in ("full", "phase", "append_only"):
        _fail(f"unexpected rollback_tier: {impact.rollback_tier}")

    # Build dry-run report
    report = CorrectionDryRunReport(
        correction_id=str(ULID()),
        mode=CorrectionMode.REVERT,
        impact=impact,
        decisions_to_invalidate=[target_id],
    )

    if len(report.decisions_to_invalidate) != 1:
        _fail(
            f"expected 1 decision to invalidate, "
            f"got {len(report.decisions_to_invalidate)}"
        )

    # Verify CorrectionRequest
    request = CorrectionRequest(
        plan_id=_ROOT_PLAN_ID,
        target_decision_id=target_id,
        mode=CorrectionMode.REVERT,
        guidance="Re-evaluate module 1 porting strategy",
        dry_run=True,
    )
    if request.status != CorrectionStatus.PENDING:
        _fail(f"initial status should be PENDING: {request.status}")

    print("m6-correction-affected-subtree-ok")


# -------------------------------------------------------------------
# Subcommand: parallel-execution-scale
# -------------------------------------------------------------------


def parallel_execution_scale() -> None:
    """Verify parallel execution scales to 10+ concurrent subplans.

    Constructs a SubplanConfig with PARALLEL mode and max_parallel=15,
    creates 15 SubplanStatuses, and verifies the plan model handles
    them correctly.
    """
    config = SubplanConfig(
        execution_mode=ExecutionMode.PARALLEL,
        merge_strategy=SubplanMergeStrategy.GIT_THREE_WAY,
        max_parallel=15,
        fail_fast=False,
        retry_failed=True,
        max_retries=2,
    )

    if config.execution_mode != ExecutionMode.PARALLEL:
        _fail(f"execution_mode mismatch: {config.execution_mode}")
    if config.max_parallel != 15:
        _fail(f"max_parallel mismatch: {config.max_parallel}")

    # Create 15 subplan statuses (simulating 15 concurrent subplans)
    statuses: list[SubplanStatus] = []
    for i in range(15):
        st = SubplanStatus(
            subplan_id=str(ULID()),
            action_name="local/port-module",
            target_resources=[f"src/module_{i:02d}/"],
            status=ProcessingState.PROCESSING,
        )
        statuses.append(st)

    if len(statuses) < 10:
        _fail(f"expected >= 10 concurrent subplans, got {len(statuses)}")

    # Create plan with parallel config
    plan = _make_plan(
        subplan_config=config,
        subplan_statuses=statuses,
        phase=PlanPhase.EXECUTE,
        processing_state=ProcessingState.PROCESSING,
    )

    if not plan.has_subplans:
        _fail("plan should have subplans")
    if len(plan.subplan_statuses) != 15:
        _fail(f"expected 15 subplans, got {len(plan.subplan_statuses)}")
    if plan.subplan_config is None:
        _fail("subplan_config is None")
    if plan.subplan_config.max_parallel < 10:
        _fail(f"max_parallel should be >= 10: {plan.subplan_config.max_parallel}")

    # Verify CLI dict includes subplan info
    cli = plan.as_cli_dict()
    if "subplan_count" not in cli:
        _fail("as_cli_dict missing subplan_count")
    if cli["subplan_count"] != 15:
        _fail(f"subplan_count mismatch: {cli['subplan_count']}")

    print("m6-parallel-execution-scale-ok")


# -------------------------------------------------------------------
# Subcommand: porting-task-autonomous
# -------------------------------------------------------------------


def porting_task_autonomous() -> None:
    """Verify a realistic porting task completes autonomously.

    Simulates the full lifecycle: ACTION → STRATEGIZE → EXECUTE →
    APPLY with subplan decomposition and completion.
    """
    # Phase 1: ACTION → STRATEGIZE (ACTION can always transition)
    plan_action = _make_plan(
        phase=PlanPhase.ACTION,
        processing_state=ProcessingState.QUEUED,
    )
    if not plan_action.can_transition_to_next_phase:
        _fail("ACTION/QUEUED should allow transition")

    # Phase 2: STRATEGIZE with subplan config
    plan_strat = _make_plan(
        phase=PlanPhase.STRATEGIZE,
        processing_state=ProcessingState.COMPLETE,
        subplan_config=SubplanConfig(
            execution_mode=ExecutionMode.PARALLEL,
            max_parallel=10,
        ),
    )
    if not plan_strat.can_transition_to_next_phase:
        _fail("STRATEGIZE/COMPLETE should allow transition")

    # Phase 3: EXECUTE with 10 subplans all COMPLETE
    exec_statuses = []
    for i in range(10):
        exec_statuses.append(
            SubplanStatus(
                subplan_id=str(ULID()),
                action_name="local/port-module",
                target_resources=[f"src/module_{i}/"],
                status=ProcessingState.COMPLETE,
                started_at=datetime.now(tz=UTC),
                completed_at=datetime.now(tz=UTC),
                files_changed=i * 10 + 5,
            )
        )
    plan_exec = _make_plan(
        phase=PlanPhase.EXECUTE,
        processing_state=ProcessingState.COMPLETE,
        subplan_config=SubplanConfig(
            execution_mode=ExecutionMode.PARALLEL,
            max_parallel=10,
        ),
        subplan_statuses=exec_statuses,
    )
    if not plan_exec.can_transition_to_next_phase:
        _fail("EXECUTE/COMPLETE should allow transition")
    if not plan_exec.has_subplans:
        _fail("execute plan should have subplans")

    # Verify all subplans completed
    for st in plan_exec.subplan_statuses:
        if st.status != ProcessingState.COMPLETE:
            _fail(f"subplan {st.subplan_id} not complete: {st.status}")

    # Phase 4: APPLY
    plan_apply = _make_plan(
        phase=PlanPhase.APPLY,
        processing_state=ProcessingState.APPLIED,
        subplan_config=plan_exec.subplan_config,
        subplan_statuses=exec_statuses,
    )
    if not plan_apply.is_terminal:
        _fail("APPLIED should be terminal")

    # Verify total files changed across all subplans
    total_files = sum(st.files_changed for st in plan_apply.subplan_statuses)
    if total_files < 50:
        _fail(f"expected >= 50 files changed, got {total_files}")

    print("m6-porting-task-autonomous-ok")


# -------------------------------------------------------------------
# Subcommand: plan-apply-lifecycle
# -------------------------------------------------------------------


def plan_apply_lifecycle() -> None:
    """Verify ``agents plan apply`` via real CLI subprocess.

    Creates an action and plan via subprocess, then runs
    ``plan apply``.  The plan is in strategize/queued
    (not execute/complete) so apply correctly rejects
    with a controlled error message.
    """
    workspace = setup_workspace(prefix="m6_apply_")
    yaml_path = write_yaml(_ACTION_YAML)
    try:
        r1 = run_cli("action", "create", "--config", yaml_path, workspace=workspace)
        if r1.returncode != 0:
            _fail(f"action create: {r1.stderr}")

        r2 = run_cli(
            "plan",
            "use",
            "local/port-to-typescript",
            "--format",
            "plain",
            workspace=workspace,
        )
        if r2.returncode != 0:
            _fail(f"plan use: {r2.stderr}")
        plan_id = _extract_plan_id(r2.stdout)
        if not plan_id:
            _fail(f"could not extract plan_id:\n{r2.stdout}")

        # apply on a strategize/queued plan — should get
        # a controlled rejection, not a crash.
        r3 = run_cli("plan", "apply", "--yes", plan_id, workspace=workspace)
        combined = r3.stdout + r3.stderr
        if "INTERNAL" in combined or "Traceback" in combined:
            _fail(f"apply crashed:\n{combined}")

        print("m6-plan-apply-lifecycle-ok")
    finally:
        os.unlink(yaml_path)
        cleanup_workspace(workspace)


# -------------------------------------------------------------------
# Subcommand: failure-handler-logic
# -------------------------------------------------------------------


def failure_handler_logic() -> None:
    """Verify SubplanFailureHandler retry and stop-others logic.

    Tests:
    - fail_fast=True stops others
    - SEQUENTIAL mode stops others
    - Retriable errors are retried
    - Non-retriable errors are not retried
    - Max retries exceeded stops retry
    """
    handler = SubplanFailureHandler()

    # fail_fast stops others
    config_ff = SubplanConfig(
        execution_mode=ExecutionMode.PARALLEL,
        fail_fast=True,
    )
    errored = SubplanStatus(
        subplan_id=str(ULID()),
        action_name="local/sub",
        status=ProcessingState.ERRORED,
        error="TimeoutError: timed out",
    )
    if not handler.should_stop_others(config_ff, errored):
        _fail("fail_fast=True should stop others")

    # SEQUENTIAL stops others regardless of fail_fast
    config_seq = SubplanConfig(
        execution_mode=ExecutionMode.SEQUENTIAL,
        fail_fast=False,
    )
    if not handler.should_stop_others(config_seq, errored):
        _fail("SEQUENTIAL should stop others")

    # Retriable error should retry
    config_retry = SubplanConfig(retry_failed=True, max_retries=2)
    retriable = SubplanStatus(
        subplan_id=str(ULID()),
        action_name="local/sub",
        status=ProcessingState.ERRORED,
        error="TimeoutError: timed out",
        attempt_number=1,
    )
    if not handler.should_retry(config_retry, retriable):
        _fail("retriable error should be retried")

    # Non-retriable error should NOT retry
    non_retriable = SubplanStatus(
        subplan_id=str(ULID()),
        action_name="local/sub",
        status=ProcessingState.ERRORED,
        error="ConfigurationError: bad config",
        attempt_number=1,
    )
    if handler.should_retry(config_retry, non_retriable):
        _fail("non-retriable error should not be retried")

    # Max retries exceeded
    exhausted = SubplanStatus(
        subplan_id=str(ULID()),
        action_name="local/sub",
        status=ProcessingState.ERRORED,
        error="TimeoutError: timed out",
        attempt_number=3,
    )
    if handler.should_retry(config_retry, exhausted):
        _fail("max retries exceeded should not retry")

    print("m6-failure-handler-logic-ok")


# -------------------------------------------------------------------
# Subcommand: subplan-config-modes
# -------------------------------------------------------------------


def subplan_config_modes() -> None:
    """Verify SubplanConfig supports all execution modes and merge strategies."""
    # All execution modes
    for mode in ExecutionMode:
        cfg = SubplanConfig(execution_mode=mode)
        if cfg.execution_mode != mode:
            _fail(f"execution_mode mismatch: {cfg.execution_mode} != {mode}")

    # All merge strategies
    for strat in SubplanMergeStrategy:
        cfg = SubplanConfig(merge_strategy=strat)
        if cfg.merge_strategy != strat:
            _fail(f"merge_strategy mismatch: {cfg.merge_strategy} != {strat}")

    # max_parallel bounds
    cfg_min = SubplanConfig(max_parallel=1)
    if cfg_min.max_parallel != 1:
        _fail(f"max_parallel=1 mismatch: {cfg_min.max_parallel}")

    cfg_max = SubplanConfig(max_parallel=50)
    if cfg_max.max_parallel != 50:
        _fail(f"max_parallel=50 mismatch: {cfg_max.max_parallel}")

    # Timeout
    cfg_timeout = SubplanConfig(timeout_per_subplan_seconds=600)
    if cfg_timeout.timeout_per_subplan_seconds != 600:
        _fail(f"timeout mismatch: {cfg_timeout.timeout_per_subplan_seconds}")

    # Default values
    default = SubplanConfig()
    if default.execution_mode != ExecutionMode.SEQUENTIAL:
        _fail(f"default mode: {default.execution_mode}")
    if default.merge_strategy != SubplanMergeStrategy.GIT_THREE_WAY:
        _fail(f"default merge: {default.merge_strategy}")
    if default.max_parallel != 5:
        _fail(f"default max_parallel: {default.max_parallel}")

    print("m6-subplan-config-modes-ok")


# -------------------------------------------------------------------
# Subcommand: decision-tree-porting
# -------------------------------------------------------------------


def decision_tree_porting() -> None:
    """Verify decision tree structure for a porting task.

    Creates a decision tree with PROMPT_DEFINITION root,
    SUBPLAN_PARALLEL_SPAWN decisions for module decomposition,
    and verifies parent-child relationships.
    """
    root = Decision(
        decision_id=_ROOT_DEC_ID,
        plan_id=_ROOT_PLAN_ID,
        parent_decision_id=None,
        sequence_number=0,
        decision_type=DecisionType.PROMPT_DEFINITION,
        question="Port Python project to TypeScript",
        chosen_option="Hierarchical module-by-module porting",
        rationale="Break down into parallel workstreams",
        downstream_decision_ids=_CHILD_DEC_IDS,
    )

    # PROMPT_DEFINITION must be root (no parent)
    if root.parent_decision_id is not None:
        _fail("PROMPT_DEFINITION should have no parent")

    # Spawn decisions for each module
    spawn_decisions = []
    for i, cid in enumerate(_CHILD_DEC_IDS):
        d = Decision(
            decision_id=cid,
            plan_id=_ROOT_PLAN_ID,
            parent_decision_id=_ROOT_DEC_ID,
            sequence_number=i + 1,
            decision_type=DecisionType.SUBPLAN_PARALLEL_SPAWN,
            question=f"How to port module_{i:02d}?",
            chosen_option=f"Create subplan for module_{i:02d}",
            rationale=f"Module {i} is independent",
            downstream_plan_ids=[_CHILD_PLAN_IDS[i]],
        )
        spawn_decisions.append(d)

    # Verify parent-child links
    for d in spawn_decisions:
        if d.parent_decision_id != _ROOT_DEC_ID:
            _fail(f"child {d.decision_id} parent mismatch")
        if d.decision_type != DecisionType.SUBPLAN_PARALLEL_SPAWN:
            _fail(f"child type should be SUBPLAN_PARALLEL_SPAWN: {d.decision_type}")
        if len(d.downstream_plan_ids) != 1:
            _fail(f"child should spawn 1 plan: {len(d.downstream_plan_ids)}")

    # Root should reference all children
    if len(root.downstream_decision_ids) != len(_CHILD_DEC_IDS):
        _fail(
            f"root downstream count: "
            f"{len(root.downstream_decision_ids)} != {len(_CHILD_DEC_IDS)}"
        )

    # Verify superseded_by for correction flow
    corrected_root = root.with_superseded_by(str(ULID()))
    if corrected_root.superseded_by is None:
        _fail("superseded_by should be set")
    # Original should be untouched (frozen model)
    if root.superseded_by is not None:
        _fail("original root superseded_by should still be None")

    print("m6-decision-tree-porting-ok")


# -------------------------------------------------------------------
# Dispatcher
# -------------------------------------------------------------------

_COMMANDS: dict[str, Callable[[], None]] = {
    "action-create-porting": action_create_porting,
    "plan-use-execute": plan_use_execute,
    "hierarchical-decomposition": hierarchical_decomposition,
    "correction-affected-subtree": correction_affected_subtree,
    "parallel-execution-scale": parallel_execution_scale,
    "porting-task-autonomous": porting_task_autonomous,
    "plan-apply-lifecycle": plan_apply_lifecycle,
    "failure-handler-logic": failure_handler_logic,
    "subplan-config-modes": subplan_config_modes,
    "decision-tree-porting": decision_tree_porting,
}


def main() -> int:
    """Entry point called by Robot Framework ``Run Process``."""
    if len(sys.argv) < 2:
        print(
            f"Usage: helper_m6_e2e_verification.py <{'|'.join(_COMMANDS)}>",
        )
        return 1
    command = sys.argv[1]
    handler = _COMMANDS.get(command)
    if handler is None:
        print(f"Unknown command: {command}")
        return 1
    reset_global_state()
    handler()
    return 0


if __name__ == "__main__":
    sys.exit(main())
