"""
Command metadata for g-gremlin help output.

Provides command grouping, safety registry, and mechanical feature detection.
"""

from __future__ import annotations

from typing import Any, Dict, Iterable, List, Sequence, Tuple

import click

IMPACT_LEGEND: Dict[str, str] = {
    "R": "read-only (remote reads ok)",
    "W": "local/Sheet writes",
    "X": "external side effects",
    "?": "unknown",
}

DATA_ACCESS_VALUES = ("local_only", "remote_read", "remote_write")
LOCAL_WRITES_MODES = ("never", "conditional", "always")
FALLBACK_MODES = ("none", "dry_run", "local_only", "preview_only")

# HubSpot write control mapping used by write-gate issuance.
HUBSPOT_WRITE_ACTIONS: Dict[str, Dict[str, str]] = {
    "hubspot:pack:apply": {"action_id": "hubspot.pack.apply", "operation_class": "schema_write"},
    "hubspot:props:apply": {"action_id": "hubspot.props.apply", "operation_class": "schema_write"},
    "hubspot:objects:apply": {"action_id": "hubspot.objects.apply", "operation_class": "schema_write"},
    "hubspot:lists:apply": {"action_id": "hubspot.lists.apply", "operation_class": "schema_write"},
    "hubspot:pipelines:apply": {"action_id": "hubspot.pipelines.apply", "operation_class": "schema_write"},
    "hubspot:validations:apply": {"action_id": "hubspot.validations.apply", "operation_class": "schema_write"},
    "hubspot:workflows:apply": {"action_id": "hubspot.workflows.apply", "operation_class": "schema_write"},
    "hubspot:imports:start": {"action_id": "hubspot.imports.start", "operation_class": "import_write"},
    "hubspot:imports:cancel": {"action_id": "hubspot.imports.cancel", "operation_class": "import_write"},
    "hubspot:merge-apply-plan": {"action_id": "hubspot.merge.apply_plan", "operation_class": "merge_write"},
    "hubspot:crm:objects:batch-upsert": {
        "action_id": "hubspot.crm.objects.batch_upsert",
        "operation_class": "object_batch_write",
    },
    "hubspot:crm:lists:membership-mutation": {
        "action_id": "hubspot.crm.lists.membership_mutation",
        "operation_class": "object_batch_write",
    },
}

COMMAND_GROUPS: Dict[str, List[str]] = {
    "Sheets Core": ["pull", "write", "push", "copy-tab", "chain", "sync"],
    "Data Transform": ["diff", "merge", "fuzzy-match", "transform", "filter", "validate", "dedup", "query"],
    "Sheet Analysis": [
        "inspect",
        "inspect-formulas",
        "lint",
        "check-bands",
        "generate-lag",
        "find-refs",
        "schema-validate",
        "trace",
    ],
    "Sheet Repair": ["repair", "rescue", "style", "formula", "names", "sheets"],
    "Enrichment": ["enrich", "enrich-doctor", "fullenrich", "fg", "apollo", "apify"],
    "CRM Integrations": ["sfdc", "hubspot", "dynamics", "zendesk", "commonroom"],
    "Sales Engagement": ["outreach", "sequencer", "heyreach", "amplemarket", "justcall"],
    "BI & Analytics": ["tableau"],
    "Visualization": ["chart", "viz"],
    "Docs & Sinks": ["docs", "gdocs", "gslides", "sink", "pubsub", "notify", "webhook"],
    "Ops & Auth": ["doctor", "auth", "license", "support", "snapshot", "recipe", "script", "google", "provenance", "mcp"],
    "Meta": ["help", "safety", "stats"],
    "Autopilot": ["autopilot", "scout", "autopilot:architect"],
}

GROUP_EXAMPLES: Dict[str, List[str]] = {
    "Sheets Core": ["g-gremlin pull SHEET_ID --range 'Tab!A1:Z' --output data.csv"],
    "Data Transform": ["g-gremlin transform --input data.csv --output data_out.csv --rename Old=New"],
    "Sheet Analysis": ["g-gremlin lint SHEET_ID --rules 'errors'"],
    "Sheet Repair": ["g-gremlin rescue SHEET_ID --dry-run"],
    "Enrichment": ["g-gremlin fullenrich enrich --csv leads.csv --dry-run"],
    "CRM Integrations": ["g-gremlin hubspot pack plan my_pack --json"],
    "Sales Engagement": ["g-gremlin outreach enroll --csv contacts.csv --sequence-id SEQ --dry-run"],
    "BI & Analytics": [
        "g-gremlin tableau doctor",
        "g-gremlin tableau template list --json",
    ],
    "Visualization": [
        "g-gremlin chart add SHEET_ID \"Data\" --type column --x-col \"Month\" --y-col \"Revenue\" --title \"Monthly Revenue\" --replace",
        "g-gremlin gslides chart add PRESENTATION_ID --spreadsheet-id SHEET_ID --chart-id 12345 --slide-index 0 --layout full --replace",
        "g-gremlin viz render --csv data.csv --x date --y revenue --type line --output chart.png",
        "g-gremlin viz embed-slide PRESENTATION_ID --csv data.csv --x date --y revenue --type bar --slide-index 0 --layout left --replace",
        "g-gremlin viz doctor",
    ],
    "Docs & Sinks": ["g-gremlin docs publish --provider notion --database-id DB --template brief.md --apply"],
    "Ops & Auth": ["g-gremlin doctor --format json"],
    "Meta": ["g-gremlin help --json --full"],
    "Autopilot": [
        "g-gremlin autopilot play list",
        "g-gremlin autopilot plan --from scout_scan.json",
        "g-gremlin autopilot policy init --org-alias acme-prod",
        "g-gremlin autopilot apply plan.json --approve-token TOKEN",
        "g-gremlin autopilot receipt show run_01H...",
        "g-gremlin autopilot architect list",
        "g-gremlin autopilot architect init l2a_association_engine --variant salesforce -p target_sandbox=dev -p prod_org_alias=prod",
        "g-gremlin autopilot admin receipts list",
        "g-gremlin autopilot admin pending approve run_01H...",
        "g-gremlin autopilot slack start --app-token xapp-... --bot-token xoxb-...",
        "g-gremlin autopilot daemon start --profile nightly",
    ],
}

GROUP_IMPACT_DEFAULT: Dict[str, str] = {
    "Sales Engagement": "X",
    "Docs & Sinks": "X",
    "CRM Integrations": "X",
}

COMMAND_SAFETY: Dict[str, Dict[str, Any]] = {
    # CLI utilities
    "inspect": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "diff": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "dedup": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "license": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "license:status": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "license:install": {
        "impact": "W",
        "destructive": True,
        "idempotent": True,
        "side_effects": ["local_file_write"],
        "required_capability": "local_tools",
        "meters": [],
        "fallback_mode": "none",
    },
    "license:refresh": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    # Sheets reads
    "pull": {
        "impact": "R",
        "destructive": False,
        "idempotent": True,
        "side_effects": [],
        "data_access": "remote_read",
        "requires_auth": ["google"],
        "requires_network": True,
        "local_writes_mode": "always",
    },
    # Local-only query
    "query": {
        "impact": "R",
        "destructive": False,
        "idempotent": True,
        "side_effects": [],
        "data_access": "local_only",
        "requires_auth": [],
        "requires_network": False,
        "local_writes_mode": "conditional",
    },
    # Sheets charts
    "chart:add": {
        "impact": "X",
        "destructive": False,
        "idempotent": False,
        "side_effects": ["sheets_chart"],
        "data_access": "remote_write",
        "requires_auth": ["google"],
        "requires_network": True,
        "local_writes_mode": "never",
    },
    "chart:list": {
        "impact": "R",
        "destructive": False,
        "idempotent": True,
        "side_effects": [],
        "data_access": "remote_read",
        "requires_auth": ["google"],
        "requires_network": True,
        "local_writes_mode": "never",
    },
    # Enrichment (calls external APIs, writes local CSV output)
    "enrich": {
        "impact": "X",
        "destructive": False,
        "idempotent": True,
        "side_effects": ["enrichment_api_calls"],
        "data_access": "remote_write",
        "requires_auth": ["pdl", "apollo", "clearbit"],
        "requires_network": True,
        "local_writes_mode": "conditional",
    },
    # Sheets writes
    "write": {
        "impact": "W",
        "destructive": True,
        "idempotent": True,
        "side_effects": ["sheets_write"],
        "data_access": "remote_write",
        "requires_auth": ["google"],
        "requires_network": True,
        "local_writes_mode": "conditional",
    },
    "push": {
        "impact": "W",
        "destructive": True,
        "idempotent": True,
        "side_effects": ["sheets_write"],
        "data_access": "remote_write",
        "requires_auth": ["google"],
        "requires_network": True,
        "local_writes_mode": "conditional",
    },
    "sync": {
        "impact": "W",
        "destructive": True,
        "idempotent": True,
        "side_effects": ["sheets_write"],
        "data_access": "remote_write",
        "requires_auth": ["google"],
        "requires_network": True,
        "local_writes_mode": "conditional",
    },
    "rescue": {"impact": "W", "destructive": True, "idempotent": True, "side_effects": ["sheets_write"]},
    "copy-tab": {"impact": "W", "destructive": False, "idempotent": False, "side_effects": ["sheets_write"]},
    "repair": {"impact": "W", "destructive": True, "idempotent": True, "side_effects": ["sheets_write"]},
    "repair:fix": {"impact": "W", "destructive": True, "idempotent": True, "side_effects": ["sheets_write"]},
    "style": {"impact": "W", "destructive": False, "idempotent": True, "side_effects": ["sheets_write"]},
    "formula:set": {"impact": "W", "destructive": True, "idempotent": True, "side_effects": ["sheets_write"]},
    "names:apply-to-formulas": {
        "impact": "W",
        "destructive": True,
        "idempotent": True,
        "side_effects": ["sheets_write"],
    },
    "sheets": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "sheets:tabs": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "sheets:tabs:list": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "sheets:delete": {"impact": "W", "destructive": True, "idempotent": False, "side_effects": ["sheets_write"]},
    "sheets:tabs:delete": {"impact": "W", "destructive": True, "idempotent": False, "side_effects": ["sheets_write"]},
    "sheets:rename": {"impact": "W", "destructive": False, "idempotent": False, "side_effects": ["sheets_write"]},
    "snapshot:restore": {"impact": "W", "destructive": True, "idempotent": False, "side_effects": ["sheets_write"]},
    # Multi-step orchestration (may write/sink)
    "chain": {
        "impact": "X",
        "destructive": True,
        "idempotent": False,
        "side_effects": ["sheets_write", "data_sink"],
    },
    # CRM writes
    "hubspot:pack:apply": {"impact": "X", "destructive": True, "idempotent": True, "side_effects": ["crm_write"]},
    "hubspot:upsert": {"impact": "X", "destructive": True, "idempotent": True, "side_effects": ["crm_write"]},
    "hubspot:props:apply": {"impact": "X", "destructive": True, "idempotent": True, "side_effects": ["crm_write"]},
    "hubspot:objects:apply": {"impact": "X", "destructive": True, "idempotent": True, "side_effects": ["crm_write"]},
    "hubspot:lists:apply": {"impact": "X", "destructive": True, "idempotent": True, "side_effects": ["crm_write"]},
    "hubspot:pipelines:apply": {"impact": "X", "destructive": True, "idempotent": True, "side_effects": ["crm_write"]},
    "hubspot:validations:apply": {"impact": "X", "destructive": True, "idempotent": True, "side_effects": ["crm_write"]},
    "hubspot:workflows:apply": {"impact": "X", "destructive": True, "idempotent": True, "side_effects": ["crm_write"]},
    "hubspot:imports:start": {"impact": "X", "destructive": True, "idempotent": False, "side_effects": ["crm_write"]},
    "hubspot:imports:cancel": {"impact": "X", "destructive": False, "idempotent": True, "side_effects": ["crm_write"]},
    "hubspot:admins:users:ls": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "hubspot:admins:goals:ls": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "hubspot:admins:goals:pull": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "hubspot:associations:labels:ls": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "hubspot:associations:labels:create": {
        "impact": "X",
        "destructive": False,
        "idempotent": False,
        "side_effects": ["crm_write"],
    },
    "hubspot:associations:labels:update": {
        "impact": "X",
        "destructive": False,
        "idempotent": True,
        "side_effects": ["crm_write"],
    },
    "hubspot:associations:labels:delete": {
        "impact": "X",
        "destructive": True,
        "idempotent": True,
        "side_effects": ["crm_write"],
    },
    "hubspot:associations:limits:ls": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "hubspot:associations:limits:set": {
        "impact": "X",
        "destructive": False,
        "idempotent": True,
        "side_effects": ["crm_write"],
    },
    "hubspot:history:pull": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "hubspot:webhooks:subscriptions:ls": {
        "impact": "R",
        "destructive": False,
        "idempotent": True,
        "side_effects": [],
    },
    "hubspot:webhooks:subscriptions:create": {
        "impact": "X",
        "destructive": False,
        "idempotent": False,
        "side_effects": ["crm_write"],
    },
    "hubspot:webhooks:subscriptions:set-active": {
        "impact": "X",
        "destructive": False,
        "idempotent": True,
        "side_effects": ["crm_write"],
    },
    "hubspot:webhooks:subscriptions:delete": {
        "impact": "X",
        "destructive": True,
        "idempotent": True,
        "side_effects": ["crm_write"],
    },
    "hubspot:merge-plan": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "hubspot:merge-apply-plan": {"impact": "X", "destructive": True, "idempotent": False, "side_effects": ["crm_write"]},
    "hubspot:merge": {"impact": "X", "destructive": True, "idempotent": False, "side_effects": ["crm_write"]},
    "hubspot:exports:start": {"impact": "R", "destructive": False, "idempotent": False, "side_effects": []},
    "hubspot:exports:download": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    # Sequencing (NOT idempotent -- enrollments are real)
    "outreach:enroll": {
        "impact": "X",
        "destructive": True,
        "idempotent": False,
        "side_effects": ["sequence_enroll"],
        "data_access": "remote_write",
        "requires_auth": ["outreach"],
        "requires_network": True,
        "local_writes_mode": "conditional",
    },
    "sequencer:push": {"impact": "X", "destructive": True, "idempotent": False, "side_effects": ["sequence_enroll"]},
    "heyreach:enroll": {"impact": "X", "destructive": True, "idempotent": False, "side_effects": ["sequence_enroll"]},
    "amplemarket:enroll": {"impact": "X", "destructive": True, "idempotent": False, "side_effects": ["sequence_enroll"]},
    "amplemarket:sequences:add": {"impact": "X", "destructive": True, "idempotent": False, "side_effects": ["sequence_enroll"]},
    "amplemarket:sequences:list": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "amplemarket:people:search": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "amplemarket:people:find": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "amplemarket:people:batch": {
        "impact": "X",
        "destructive": False,
        "idempotent": True,
        "side_effects": ["enrichment_api_calls"],
    },
    "amplemarket:people:status": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "amplemarket:people:cancel": {"impact": "X", "destructive": False, "idempotent": True, "side_effects": ["enrichment_api_calls"]},
    "amplemarket:companies:search": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "amplemarket:companies:find": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "amplemarket:companies:batch": {
        "impact": "X",
        "destructive": False,
        "idempotent": True,
        "side_effects": ["enrichment_api_calls"],
    },
    "amplemarket:companies:status": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "amplemarket:companies:cancel": {"impact": "X", "destructive": False, "idempotent": True, "side_effects": ["enrichment_api_calls"]},
    "amplemarket:lead-lists:list": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "amplemarket:lead-lists:get": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "amplemarket:lead-lists:create": {"impact": "X", "destructive": False, "idempotent": True, "side_effects": ["lead_list_write"]},
    "amplemarket:lead-lists:add": {"impact": "X", "destructive": False, "idempotent": True, "side_effects": ["lead_list_write"]},
    # External: notifications, docs, sinks
    "notify:webhook": {"impact": "X", "destructive": False, "idempotent": True, "side_effects": ["notification"]},
    "docs:publish": {"impact": "X", "destructive": False, "idempotent": True, "side_effects": ["docs_publish"]},
    "docs:publish-batch": {"impact": "X", "destructive": False, "idempotent": True, "side_effects": ["docs_publish"]},
    "gslides:chart:add": {
        "impact": "X",
        "destructive": False,
        "idempotent": False,
        "side_effects": ["slides_write"],
        "data_access": "remote_write",
        "requires_auth": ["google"],
        "requires_network": True,
        "local_writes_mode": "never",
    },
    "viz:render": {
        "impact": "W",
        "destructive": False,
        "idempotent": True,
        "side_effects": ["local_file_write"],
        "data_access": "local_only",
        "requires_auth": [],
        "requires_network": False,
        "local_writes_mode": "always",
    },
    "viz:doctor": {
        "impact": "R",
        "destructive": False,
        "idempotent": True,
        "side_effects": [],
        "data_access": "local_only",
        "requires_auth": [],
        "requires_network": False,
        "local_writes_mode": "never",
    },
    "viz:embed-slide": {
        "impact": "X",
        "destructive": False,
        "idempotent": False,
        "side_effects": ["slides_write", "drive_upload"],
        "data_access": "remote_write",
        "requires_auth": ["google"],
        "requires_network": True,
        "local_writes_mode": "conditional",
    },
    "sfdc:query": {
        "impact": "R",
        "destructive": False,
        "idempotent": True,
        "side_effects": ["sfdc_read"],
        "data_access": "remote_read",
        "requires_auth": ["salesforce"],
        "requires_network": True,
        "local_writes_mode": "always",
    },
    "sfdc:pack:apply": {
        "impact": "X",
        "destructive": True,
        "idempotent": True,
        "side_effects": ["sfdc_write"],
        "data_access": "remote_write",
        "requires_auth": ["salesforce"],
        "requires_network": True,
        "local_writes_mode": "conditional",
    },
    "sfdc:metadata-pack:apply": {
        "impact": "X",
        "destructive": True,
        "idempotent": True,
        "side_effects": ["sfdc_metadata_write"],
        "data_access": "remote_write",
        "requires_auth": ["salesforce"],
        "requires_network": True,
        "local_writes_mode": "conditional",
    },
    "sfdc:preview-push": {
        "impact": "R",
        "destructive": False,
        "idempotent": True,
        "side_effects": ["sfdc_read"],
        "data_access": "remote_read",
        "requires_auth": ["salesforce"],
        "requires_network": True,
        "local_writes_mode": "always",
    },
    "sfdc:enrich": {
        "impact": "R",
        "destructive": False,
        "idempotent": True,
        "side_effects": ["sfdc_read"],
        "data_access": "remote_read",
        "requires_auth": ["salesforce"],
        "requires_network": True,
        "local_writes_mode": "always",
    },
    "sfdc:describe": {
        "impact": "R",
        "destructive": False,
        "idempotent": True,
        "side_effects": ["sfdc_read"],
        "data_access": "remote_read",
        "requires_auth": ["salesforce"],
        "requires_network": True,
        "local_writes_mode": "never",
    },
    "sfdc:manifest-generate": {
        "impact": "R",
        "destructive": False,
        "idempotent": True,
        "side_effects": [],
        "data_access": "local_only",
        "requires_auth": [],
        "requires_network": False,
        "local_writes_mode": "conditional",
    },
    "sfdc:reports:list": {
        "impact": "R",
        "destructive": False,
        "idempotent": True,
        "side_effects": ["sfdc_read"],
        "data_access": "remote_read",
        "requires_auth": ["salesforce"],
        "requires_network": True,
        "local_writes_mode": "never",
    },
    "sfdc:reports:export": {
        "impact": "R",
        "destructive": False,
        "idempotent": True,
        "side_effects": ["sfdc_read"],
        "data_access": "remote_read",
        "requires_auth": ["salesforce"],
        "requires_network": True,
        "local_writes_mode": "conditional",
    },
    "sfdc:docs:generate": {
        "impact": "R",
        "destructive": False,
        "idempotent": True,
        "side_effects": ["sfdc_read"],
        "data_access": "remote_read",
        "requires_auth": ["salesforce"],
        "requires_network": True,
        "local_writes_mode": "conditional",
    },
    "mcp:serve": {
        "impact": "R",
        "destructive": False,
        "idempotent": True,
        "side_effects": [],
        "data_access": "local_only",
        "requires_auth": [],
        "requires_network": False,
        "local_writes_mode": "never",
        "notes": [
            "Starts MCP server with read/analyze tools by default.",
            "Passing --enable-writes exposes write/apply tools.",
        ],
    },
    "mcp:serve-dynamics": {
        "impact": "R",
        "destructive": False,
        "idempotent": True,
        "side_effects": [],
        "data_access": "local_only",
        "requires_auth": [],
        "requires_network": False,
        "local_writes_mode": "never",
        "notes": [
            "Starts Dynamics MCP server with read/analyze tools by default.",
            "Passing --enable-writes exposes Dataverse apply tools (all require plan_hash).",
        ],
    },
    "zendesk:user:search": {
        "impact": "R",
        "destructive": False,
        "idempotent": True,
        "side_effects": ["zendesk_read"],
        "data_access": "remote_read",
        "requires_auth": ["zendesk"],
        "requires_network": True,
        "local_writes_mode": "never",
    },
    "zendesk:org:search": {
        "impact": "R",
        "destructive": False,
        "idempotent": True,
        "side_effects": ["zendesk_read"],
        "data_access": "remote_read",
        "requires_auth": ["zendesk"],
        "requires_network": True,
        "local_writes_mode": "never",
    },
    "zendesk:org:audit": {
        "impact": "R",
        "destructive": False,
        "idempotent": True,
        "side_effects": ["zendesk_read"],
        "data_access": "remote_read",
        "requires_auth": ["zendesk"],
        "requires_network": True,
        "local_writes_mode": "conditional",
    },
    "zendesk:ticket:search": {
        "impact": "R",
        "destructive": False,
        "idempotent": True,
        "side_effects": ["zendesk_read"],
        "data_access": "remote_read",
        "requires_auth": ["zendesk"],
        "requires_network": True,
        "local_writes_mode": "conditional",
    },
    "zendesk:ticket:audit": {
        "impact": "R",
        "destructive": False,
        "idempotent": True,
        "side_effects": ["zendesk_read"],
        "data_access": "remote_read",
        "requires_auth": ["zendesk"],
        "requires_network": True,
        "local_writes_mode": "conditional",
    },
    "zendesk:user:audit": {
        "impact": "R",
        "destructive": False,
        "idempotent": True,
        "side_effects": ["zendesk_read"],
        "data_access": "remote_read",
        "requires_auth": ["zendesk"],
        "requires_network": True,
        "local_writes_mode": "conditional",
    },
    "zendesk:trace:user": {
        "impact": "R",
        "destructive": False,
        "idempotent": True,
        "side_effects": ["zendesk_read"],
        "data_access": "remote_read",
        "requires_auth": ["zendesk"],
        "requires_network": True,
        "local_writes_mode": "never",
    },
    "sink:bq": {"impact": "X", "destructive": False, "idempotent": True, "side_effects": ["data_sink"]},
    "sink:s3": {"impact": "X", "destructive": False, "idempotent": True, "side_effects": ["data_sink"]},
    "sink:gcs": {"impact": "X", "destructive": False, "idempotent": True, "side_effects": ["data_sink"]},
    "sink:snowflake": {"impact": "X", "destructive": False, "idempotent": True, "side_effects": ["data_sink"]},
    "sink:redshift": {"impact": "X", "destructive": False, "idempotent": True, "side_effects": ["data_sink"]},
    "pubsub:publish": {"impact": "X", "destructive": False, "idempotent": False, "side_effects": ["pubsub_publish"]},
    "commonroom:import": {"impact": "X", "destructive": False, "idempotent": True, "side_effects": ["crm_write"]},
    # Tableau
    "tableau": {"impact": "?", "destructive": False, "idempotent": True, "side_effects": []},
    "tableau:doctor": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "tableau:auth:connect": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "tableau:auth:test": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "tableau:auth:disconnect": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "tableau:jobs:get": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "tableau:jobs:wait": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "tableau:workbooks:list": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "tableau:workbooks:get": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "tableau:workbooks:download": {
        "impact": "R",
        "destructive": False,
        "idempotent": True,
        "side_effects": [],
        "local_writes_mode": "always",
    },
    "tableau:workbooks:publish": {
        "impact": "X",
        "destructive": False,
        "idempotent": False,
        "side_effects": ["tableau_publish"],
        "requires_auth": ["tableau"],
    },
    "tableau:workbooks:refresh": {
        "impact": "X",
        "destructive": False,
        "idempotent": True,
        "side_effects": ["tableau_refresh"],
        "requires_auth": ["tableau"],
    },
    "tableau:datasources:list": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "tableau:datasources:get": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "tableau:datasources:download": {
        "impact": "R",
        "destructive": False,
        "idempotent": True,
        "side_effects": [],
        "local_writes_mode": "always",
    },
    "tableau:datasources:publish": {
        "impact": "X",
        "destructive": False,
        "idempotent": False,
        "side_effects": ["tableau_publish"],
        "requires_auth": ["tableau"],
    },
    "tableau:datasources:refresh": {
        "impact": "X",
        "destructive": False,
        "idempotent": True,
        "side_effects": ["tableau_refresh"],
        "requires_auth": ["tableau"],
    },
    "tableau:schedules:list": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "tableau:schedules:get": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "tableau:schedules:create": {
        "impact": "X",
        "destructive": False,
        "idempotent": False,
        "side_effects": ["tableau_schedule_create"],
        "requires_auth": ["tableau"],
    },
    "tableau:schedules:update": {
        "impact": "X",
        "destructive": False,
        "idempotent": True,
        "side_effects": ["tableau_schedule_update"],
        "requires_auth": ["tableau"],
    },
    "tableau:schedules:delete": {
        "impact": "X",
        "destructive": True,
        "idempotent": False,
        "side_effects": ["tableau_schedule_delete"],
        "requires_auth": ["tableau"],
    },
    "tableau:schedules:run-now": {
        "impact": "X",
        "destructive": False,
        "idempotent": False,
        "side_effects": ["tableau_refresh"],
        "requires_auth": ["tableau"],
    },
    "tableau:views:list": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "tableau:views:download-image": {
        "impact": "R",
        "destructive": False,
        "idempotent": True,
        "side_effects": [],
        "local_writes_mode": "always",
    },
    "tableau:views:download-data": {
        "impact": "R",
        "destructive": False,
        "idempotent": True,
        "side_effects": [],
        "local_writes_mode": "always",
    },
    "tableau:projects:list": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "tableau:projects:get": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "tableau:projects:create": {
        "impact": "X",
        "destructive": False,
        "idempotent": False,
        "side_effects": ["tableau_project_create"],
        "requires_auth": ["tableau"],
    },
    "tableau:metadata:doctor": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "tableau:metadata:query": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "tableau:metadata:fields": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "tableau:metadata:lineage": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "tableau:template:list": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "tableau:template:inspect": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "tableau:template:validate": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "tableau:template:bind": {
        "impact": "R",
        "destructive": False,
        "idempotent": True,
        "side_effects": [],
        "local_writes_mode": "always",
    },
    "tableau:template:publish": {
        "impact": "X",
        "destructive": False,
        "idempotent": False,
        "side_effects": ["tableau_publish"],
        "requires_auth": ["tableau"],
    },
    "tableau:hyper:create": {
        "impact": "R",
        "destructive": False,
        "idempotent": True,
        "side_effects": [],
        "local_writes_mode": "always",
    },
    "tableau:hyper:inspect": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "tableau:hyper:export": {
        "impact": "R",
        "destructive": False,
        "idempotent": True,
        "side_effects": [],
        "local_writes_mode": "always",
    },
    # Autopilot Architect
    "autopilot:architect": {
        "impact": "?",
        "destructive": False,
        "idempotent": True,
        "side_effects": [],
        "data_access": "local_only",
    },
    "autopilot:architect:list": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "autopilot:architect:init": {"impact": "W", "destructive": False, "idempotent": False, "side_effects": []},
    "autopilot:architect:discover": {
        "impact": "R",
        "destructive": False,
        "idempotent": False,
        "side_effects": [],
        "data_access": "remote_read",
        "requires_auth": ["salesforce", "hubspot"],
    },
    "autopilot:architect:propose": {"impact": "R", "destructive": False, "idempotent": False, "side_effects": []},
    "autopilot:architect:token": {"impact": "R", "destructive": False, "idempotent": False, "side_effects": []},
    "autopilot:architect:deploy": {
        "impact": "X",
        "destructive": True,
        "idempotent": False,
        "side_effects": ["metadata_write"],
        "data_access": "remote_write",
        "requires_auth": ["salesforce", "hubspot"],
    },
    "autopilot:architect:verify": {
        "impact": "R",
        "destructive": False,
        "idempotent": False,
        "side_effects": [],
        "data_access": "remote_read",
        "requires_auth": ["salesforce", "hubspot"],
    },
    "autopilot:architect:deploy-prod": {
        "impact": "X",
        "destructive": True,
        "idempotent": False,
        "side_effects": ["metadata_write"],
        "data_access": "remote_write",
        "requires_auth": ["salesforce", "hubspot"],
    },
    "autopilot:architect:promote": {
        "impact": "X",
        "destructive": True,
        "idempotent": False,
        "side_effects": ["metadata_write"],
        "data_access": "remote_write",
        "requires_auth": ["salesforce", "hubspot"],
    },
    "autopilot:architect:status": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
    "autopilot:architect:projects": {"impact": "R", "destructive": False, "idempotent": True, "side_effects": []},
}

COMMAND_EXIT_CODE_ACTIONS: Dict[str, Dict[str, str]] = {
    "pull": {
        "2": "Missing creds, invalid range, or missing/invalid --output path.",
        "3": "Sheets API failure or network error.",
    },
    "query": {
        "2": "Invalid SQL, missing file(s), or missing DuckDB dependency.",
        "3": "Unexpected remote failure (should not happen).",
    },
}

_COMMAND_TO_GROUP: Dict[str, str] = {}
for _group_name, _cmds in COMMAND_GROUPS.items():
    for _cmd in _cmds:
        _COMMAND_TO_GROUP[_cmd] = _group_name

HIDDEN_COMMANDS: set[str] = {
    # Aliases that should not appear in --help listings.
    "ranges",
}


def filter_display_commands(command_names: Iterable[str]) -> List[str]:
    return [name for name in command_names if name not in HIDDEN_COMMANDS]


def build_command_id(path_parts: Sequence[str]) -> str:
    return ":".join(path_parts)


def resolve_hubspot_write_action(command_id: str) -> Dict[str, str] | None:
    action = HUBSPOT_WRITE_ACTIONS.get(str(command_id or "").strip().lower())
    if not action:
        return None
    return dict(action)


def find_group_for_command(command_name: str) -> str | None:
    return _COMMAND_TO_GROUP.get(command_name)


def normalize_command_groups(command_names: Iterable[str]) -> Tuple[Dict[str, List[str]], List[str]]:
    all_commands = filter_display_commands(command_names)
    available = set(all_commands)
    categorized: set[str] = set()
    grouped: Dict[str, List[str]] = {}

    for group_name, cmd_names in COMMAND_GROUPS.items():
        existing = [name for name in cmd_names if name in available]
        if existing:
            grouped[group_name] = existing
            categorized.update(existing)

    uncategorized = sorted(available - categorized)
    return grouped, uncategorized


def _option_supports_json(param: click.Option) -> bool:
    if "--json" in param.opts:
        return True
    if "--format" in param.opts:
        if isinstance(param.type, click.Choice):
            choices = [str(c).lower() for c in param.type.choices]
            return "json" in choices
    return False


def _has_option(cmd: click.Command, opt: str) -> bool:
    for param in cmd.params:
        if isinstance(param, click.Option) and opt in param.opts:
            return True
    return False


def derive_mechanical_support(cmd: click.Command) -> Dict[str, bool]:
    flags = _extract_flag_names(cmd)
    supports_dry_run = "--dry-run" in flags
    supports_apply = "--apply" in flags or "--yes" in flags
    supports_json_summary = "--json-summary" in flags
    supports_json = any(
        _option_supports_json(param) for param in cmd.params if isinstance(param, click.Option)
    )
    structured_output = supports_json or supports_json_summary
    return {
        "supports_dry_run": supports_dry_run,
        "supports_apply": supports_apply,
        "supports_json": supports_json,
        "supports_json_summary": supports_json_summary,
        "structured_output": structured_output,
    }


def _extract_flag_names(cmd: click.Command) -> set[str]:
    names: set[str] = set()
    for param in cmd.params:
        if isinstance(param, click.Option) and param.is_flag:
            names.update(param.opts)
    return names


def infer_safety(cmd: click.Command, cmd_id: str, group_name: str | None) -> Dict[str, Any]:
    supports = derive_mechanical_support(cmd)
    has_mutator_flags = supports["supports_apply"] or supports["supports_dry_run"]

    if not has_mutator_flags:
        return {
            "impact": "R",
            "destructive": False,
            "idempotent": True,
            "side_effects": [],
            "safety_source": "heuristic",
        }

    impact = GROUP_IMPACT_DEFAULT.get(group_name, "?") if group_name else "?"
    return {
        "impact": impact,
        "destructive": supports["supports_apply"],
        "idempotent": True,
        "side_effects": [],
        "safety_source": "heuristic",
    }


def resolve_exit_code_actions(cmd_id: str) -> Dict[str, str]:
    return dict(COMMAND_EXIT_CODE_ACTIONS.get(cmd_id, {}))


def _infer_data_access(impact: str, group_name: str | None) -> str:
    if impact in ("W", "X"):
        return "remote_write"
    if group_name in ("Sheets Core", "Sheet Analysis", "Sheet Repair"):
        return "remote_read"
    if group_name in ("CRM Integrations", "Sales Engagement", "Docs & Sinks", "Ops & Auth", "Autopilot"):
        return "remote_read"
    return "local_only"


def _infer_requires_auth(data_access: str, group_name: str | None) -> List[str]:
    if data_access == "local_only":
        return []
    if group_name in ("Sheets Core", "Sheet Analysis", "Sheet Repair"):
        return ["google"]
    if group_name == "CRM Integrations":
        return []
    if group_name == "Sales Engagement":
        return []
    if group_name == "Docs & Sinks":
        return []
    if group_name == "Ops & Auth":
        return []
    if group_name == "Autopilot":
        return []
    return []


def _infer_local_writes_mode(cmd: click.Command) -> str:
    output_required = False
    output_optional = False
    for param in cmd.params:
        if isinstance(param, click.Option) and "--output" in param.opts:
            if param.required:
                output_required = True
            else:
                output_optional = True
    if output_required:
        return "always"
    if output_optional:
        return "conditional"
    return "never"


def _infer_required_capability(
    cmd: click.Command,
    cmd_id: str,
    safety: Dict[str, Any],
    group_name: str | None,
) -> str | None:
    explicit = safety.get("required_capability")
    if explicit:
        return str(explicit)

    side_effects = set(str(s) for s in (safety.get("side_effects") or []))
    impact = str(safety.get("impact", "?"))
    cmd_id_l = cmd_id.lower()

    # Meta/local utilities remain ungated by paid capability.
    if cmd_id_l.startswith(("help", "doctor", "auth:", "license:", "support:", "stats")):
        return None

    if cmd_id_l.startswith("fg") or "foundrygraph" in cmd_id_l:
        return "foundrygraph"

    if cmd_id_l.startswith("fuzzy-match:cloud"):
        return "cloud_fuzzymatch"

    if "sheets_write" in side_effects or "slides_write" in side_effects or cmd_id_l in {
        "write",
        "push",
        "sync",
        "copy-tab",
        "repair",
        "repair:fix",
        "rescue",
        "style",
        "formula:set",
        "names:apply-to-formulas",
        "snapshot:restore",
    }:
        return "google_write"

    if cmd_id_l.startswith(("gdocs:", "gslides:")) or "docs_publish" in side_effects:
        return "workspace_sinks"

    if cmd_id_l.startswith("notify:") or "notification" in side_effects:
        return "notify_webhook"

    if cmd_id_l.startswith("sink:s3"):
        return "s3_export"

    if cmd_id_l.startswith("hubspot:"):
        if any(tag in cmd_id_l for tag in (":snapshot", ":diff", ":doctor", ":pack", ":apply", ":upsert", ":objects", ":lists", ":pipelines", ":validations", ":workflows", ":props")):
            return "hubspot_admin"
        if impact in ("W", "X"):
            return "hubspot_admin"
        return None

    if cmd_id_l.startswith(("sfdc:", "dynamics:", "zendesk:")) and impact in ("W", "X"):
        return "multi_crm"

    if "enrichment_api_calls" in side_effects or cmd_id_l.startswith(("enrich", "fullenrich:")):
        return "enrichment_apply" if impact in ("W", "X") else None

    if "sequence_enroll" in side_effects or cmd_id_l.startswith(("outreach:", "sequencer:", "heyreach:", "amplemarket:")):
        if impact in ("W", "X"):
            return "sequencer_enroll_apply"
        return None

    if cmd_id_l.startswith("autopilot:"):
        if any(tag in cmd_id_l for tag in (":apply", ":approve", ":reject", ":deploy", ":promote")):
            return "autopilot_apply"
        if "daemon:start" in cmd_id_l:
            return "scheduling"
        if impact in ("W", "X"):
            return "autopilot_apply"
        return None

    # Read-only Google inspections are always available on local_free.
    if group_name in ("Sheets Core", "Sheet Analysis", "Sheet Repair") and impact == "R":
        return "google_read"

    # Any remaining mutating commands at least map to local tool capability.
    if impact in ("W", "X"):
        return "local_tools"

    return None


def _infer_meters(cmd_id: str, required_capability: str | None, safety: Dict[str, Any]) -> list[str]:
    explicit = safety.get("meters")
    if isinstance(explicit, list):
        return [str(v) for v in explicit]

    meters: list[str] = []
    cmd_id_l = cmd_id.lower()
    side_effects = set(str(s) for s in (safety.get("side_effects") or []))

    if required_capability == "google_write":
        meters.append("google_write_ops_per_day")
        if cmd_id_l in {"push", "write", "sync"}:
            meters.append("cells_per_push_max")

    if required_capability == "foundrygraph":
        meters.append("fg_rows_per_month")

    if required_capability == "cloud_fuzzymatch":
        meters.append("cloud_fuzzymatch_rows_per_month")

    if required_capability == "notify_webhook":
        meters.append("webhook_events_per_day")

    if required_capability == "s3_export" or cmd_id_l.startswith("sink:s3"):
        meters.append("s3_exports_per_day")

    if cmd_id_l == "hubspot:pack:apply":
        meters.append("hubspot_pack_applies_per_month")
    if cmd_id_l.startswith("hubspot:"):
        meters.append("hubspot_portals_max")

    if "notification" in side_effects and "webhook_events_per_day" not in meters:
        meters.append("webhook_events_per_day")

    return meters


def _infer_fallback_mode(cmd: click.Command, impact: str, required_capability: str | None) -> str:
    explicit_dry_run = _has_option(cmd, "--dry-run")
    explicit_apply = _has_option(cmd, "--apply") or _has_option(cmd, "--yes")

    if required_capability in {"google_write", "hubspot_admin", "multi_crm", "autopilot_apply"} and explicit_dry_run:
        return "dry_run"
    if required_capability in {"enrichment_apply", "sequencer_enroll_apply"}:
        return "local_only"
    if required_capability and impact == "R":
        return "preview_only"
    if impact in ("W", "X") and explicit_apply and explicit_dry_run:
        return "dry_run"
    return "none"


def _apply_safety_defaults(
    safety: Dict[str, Any],
    cmd: click.Command,
    cmd_id: str,
    group_name: str | None,
) -> Dict[str, Any]:
    impact = safety.get("impact", "?")
    data_access = safety.get("data_access")
    if data_access not in DATA_ACCESS_VALUES:
        data_access = _infer_data_access(impact, group_name)
    safety["data_access"] = data_access

    requires_auth = safety.get("requires_auth")
    if requires_auth is None:
        safety["requires_auth"] = _infer_requires_auth(data_access, group_name)

    requires_network = safety.get("requires_network")
    if requires_network is None:
        safety["requires_network"] = data_access != "local_only"

    local_writes_mode = safety.get("local_writes_mode")
    if local_writes_mode not in LOCAL_WRITES_MODES:
        safety["local_writes_mode"] = _infer_local_writes_mode(cmd)

    required_capability = _infer_required_capability(cmd, cmd_id, safety, group_name)
    safety["required_capability"] = required_capability

    safety["meters"] = _infer_meters(cmd_id, required_capability, safety)

    fallback_mode = safety.get("fallback_mode")
    if fallback_mode not in FALLBACK_MODES:
        fallback_mode = _infer_fallback_mode(cmd, impact, required_capability)
    safety["fallback_mode"] = fallback_mode

    return safety


def resolve_safety(cmd: click.Command, cmd_id: str, group_name: str | None) -> Dict[str, Any]:
    if cmd_id in COMMAND_SAFETY:
        safety = dict(COMMAND_SAFETY[cmd_id])
        safety["safety_source"] = "registry"
        safety["safety_confidence"] = "high"
        return _apply_safety_defaults(safety, cmd, cmd_id, group_name)

    safety = infer_safety(cmd, cmd_id, group_name)
    safety["safety_confidence"] = "medium" if safety.get("safety_source") == "heuristic" else "low"
    return _apply_safety_defaults(safety, cmd, cmd_id, group_name)


def resolve_safety_for_schema(cmd: click.Command, cmd_id: str, group_name: str | None, is_group: bool) -> Dict[str, Any]:
    safety = resolve_safety(cmd, cmd_id, group_name)
    if is_group:
        # Namespaces are not directly executable; default to impact unknown,
        # unless explicitly tagged in the registry.
        if cmd_id not in COMMAND_SAFETY:
            safety["impact"] = "?"
            safety["safety_source"] = "heuristic"
            safety["safety_confidence"] = "low"
            safety["side_effects"] = []
            safety["destructive"] = False
            safety["idempotent"] = True
            safety["data_access"] = "local_only"
            safety["requires_auth"] = []
            safety["requires_network"] = False
            safety["local_writes_mode"] = "never"
            safety["required_capability"] = None
            safety["meters"] = []
            safety["fallback_mode"] = "none"
    return safety
