"""Implementation inventory and update impact, independent of execution success."""
from collections import Counter
from .arbeit_graph import compile_all


# These are implementation facts, not observations from a running game.
PROFILES = {
    "context.identify": ("concrete_planner", "typed character root + source HUD pixels", "read only", "fresh owned identity/HUD", "other canvas profiles need current confirmation"),
    "quest.offer": ("conditional_planner", "typed acceptable collection + native condition and giver joins", "read only", "current exact offer and conditions", "current server eligibility/giver evidence; no inferred offer"),
    "move.target": ("concrete_planner", "typed current map/MMML/markers and exact waiting-instance cards", "map source planner or native card autoplay before acceptance", "current arrival or exact native acceptance with acceptance-prefix skip", "unresolved static targets and current server effects"),
    "dialogue.accept": ("conditional_planner", "typed dialogue/prompt/SelectIndex and owned request component + current giver/offer", "source probe/click with final transaction reread", "strict-later typed acceptance of the saved waiting instance", "current game input/effect unverified; no inferred server action-to-quest mapping"),
    "quest.accepted": ("typed_state", "typed current QuestComponent", "read only", "current instance accept time", "live observation not performed"),
    "inventory.hold": ("concrete_planner", "official bag stacks + current objective semantics + life detail", "preserved gather/processing candidates", "fresh exact bag quantity with lock/reservation scope", "ambiguous item-route join and unsupported supply routes"),
    "objective.deliver": ("conditional_planner", "typed DeliverUI exact NPC or group-only request occurrence + allowed item IDs and quantity", "source item-select then submit with intermediate effects", "same-occurrence native objective effect", "current exact control geometry/raycast and server selection state"),
    "dialogue.talk": ("conditional_planner", "typed dialogue player/prompt + source model events + objective program", "native quest autoplay or owned dialogue action", "same-occurrence native objective effect", "current owned branch and exact control geometry/raycast"),
    "dialogue.report": ("conditional_planner", "typed dialogue player/prompt + source model events + objective program", "NPC travel then exact current request or legacy owned report action", "strict-later same-occurrence native objective or previous-step termination", "current game input/effect unverified; ambiguous request constraints remain unsupported"),
    "objective.move": ("concrete_planner", "typed Channel/Region + pinned region definition + current map converter/terrain", "source map view and projected point planner or native autoplay", "same-occurrence native objective IsAchieved; geometric arrival is approach only", "current operands/map state and native effect; unbundled region assets remain an update boundary"),
    "objective.inspect": ("conditional_planner", "typed objective + current replicated InstantProp generation + rooted HUD callback", "current target projection/approach then source HUD probe/click, or native quest autoplay", "same-occurrence native objective effect", "current unique spawned target/geometry/final-hit; other interaction families remain explicit"),
    "life.craft": ("concrete_planner", "current typed recipe/model/material rows + source recipe/output joins", "field/menu/recipe/quantity/submit with owned result boundary", "same-occurrence native effect or source-material bag goal, plus current UI cleanup", "current geometry/raycast; constrained equipment output and quest fake-item supply"),
    "life.alter": ("concrete_planner", "typed selected recipe/slots/materials/scroll + exact official works/items", "preserved select/travel/register/collect; material child graph", "owned work and current objective proof", "current control raycast and owned close target; live register/collect effects"),
    "objective.dungeon_clear": ("conditional_planner", "typed current objective program + native quest autoplay request", "current quest-instance autoplay callback", "same-occurrence native objective effect", "current source-supported quest autoplay; custom admission actions not invented"),
    "objective.read_story": ("conditional_planner", "typed story definition + rooted server StoryBox pages joined to current UI", "native request to selected-goal UI adoption then reveal/next/close", "same-occurrence native objective effect; page/adoption changes are intermediate", "current owned native target and server/UI page join required; no inferred localization or server quest owner"),
    "life.gather": ("concrete_planner", "typed life detail + 78 pinned route ID joins + current official activity", "preserved selection/nearby tape and restart reconciliation", "owned activity end + current objective proof", "unsupported route domains and unobserved current eligibility/activity"),
    "objective.unresolved": ("conditional_planner", "exact typed current objective; display hint is diagnostic only", "source-bound current native quest autoplay request", "same-occurrence native objective effect", "missing/unsupported typed program remains a named gap"),
    "quest.step_complete": ("typed_state", "source-pinned active QuestComponent step predicate", "read only", "current instance step transition; patch guard", "live observation not performed; historical shortcut excluded"),
    "reward.claim": ("conditional_planner", "current QuestClearUI plus exact ClearedQuests occurrence", "source-owned enabled reward button", "same accept-time reward/completion record", "current native button geometry/raycast and reward dismissal effect"),
    "quest.completed": ("typed_state", "typed completed record + current instance correlation", "read only", "same accept-time reward/completion record", "live observation not performed"),
}


# Additional current typed operations do not change the static catalog denominator.
RUNTIME_PROFILES = {
    'objective.use_quest_item': ('conditional_planner',
        'current typed UseQuestFakeItem + exact model/filter/category/source card',
        'HUD inventory -> owned category/filter/registered scroll -> exact item row -> card Use with fresh pointer guards',
        'same-occurrence native objective effect; card disappearance is intermediate only',
        'Current source-owned default subcategory reset; inventory horizontal/custom contexts and UniqueName selection remain; live effect unverified'),
}

CAPABILITIES = {
    'objective-gap-matrix': {'prefixes': ['masukja2/arbeit_gap_matrix.py', 'data/arbeit-gap-matrix.json', 'data/r7-dialogue-gaps.json'], 'ops': list(PROFILES | RUNTIME_PROFILES), 'requirements': ['AR-01','AR-08','AR-11','AR-12']},
    'collection-scroll-axis': {'prefixes': ['masukja2/collection_scroll_axis.py', 'data/r7-scroll-source/'], 'ops': ['move.target','dialogue.accept','objective.use_quest_item'], 'requirements': ['AR-03','AR-04','AR-06','AR-08','AR-11']},
    'map-quest-card': {'prefixes': ['masukja2/map_quest_accept', 'masukja2/acceptance_movement.py', 'data/r4-map-accept/', 'data/r5-map-card/', 'data/r6-map-card/'], 'ops': ['move.target','dialogue.accept'], 'requirements': ['AR-03','AR-04','AR-11']},
    'group-npc': {'prefixes': ['masukja2/group_npc_', 'data/arbeit-group-npc/'], 'ops': ['move.target','dialogue.talk','objective.deliver'], 'requirements': ['AR-04','AR-05','AR-10','AR-11']},
    'quest-item-acquisition': {'prefixes': ['masukja2/quest_item_acquisition.py'], 'ops': ['inventory.hold','objective.inspect','life.craft'], 'requirements': ['AR-06','AR-07','AR-09','AR-11']},
    'quest-item-delivery': {'prefixes': ['masukja2/quest_item_delivery', 'data/r4-item-delivery/'], 'ops': ['objective.deliver'], 'requirements': ['AR-05','AR-07','AR-10','AR-11']},
    'direct-dialogue-entry': {'prefixes': ['masukja2/direct_dialogue_', 'data/direct-dialogue-source.json', 'data/r6-direct-dialogue/', 'data/r6-dialogue-operands/'], 'ops': ['dialogue.accept','dialogue.report','dialogue.talk','objective.deliver'], 'requirements': ['AR-03','AR-05','AR-10','AR-11']},
    'story-ui': {'prefixes': ['masukja2/story_ui_', 'masukja2/story_read_source.py', 'masukja2/story_native_handoff.py', 'masukja2/quest_item_card_open.py', 'masukja2/quest_item_inventory_navigation.py', 'data/arbeit-story-ui.json', 'data/r6-story-read/', 'data/r6-inventory-navigation/','data/r7-inventory-navigation/'], 'ops': ['objective.read_story','objective.use_quest_item'], 'requirements': ['AR-05','AR-08','AR-11']},
    "test-diagnostics": {"prefixes": ["masukja2/support_bundle.py"], "ops": list(PROFILES | RUNTIME_PROFILES), "requirements": ["AR-11", "AR-12"]},
    "typed-region": {"prefixes": ["masukja2/typed_region_movement.py", "data/arbeit-region-movement.json"], "ops": ["objective.move", "objective.inspect", "objective.unresolved"], "requirements": ["AR-04", "AR-08", "AR-11"]},
    "world-object": {"prefixes": ["masukja2/world_target_observer.py", "masukja2/world_object_observer.py", "data/arbeit-world-targets/", "data/arbeit-world-object.json"], "ops": ["objective.inspect", "objective.unresolved"], "requirements": ["AR-04", "AR-05", "AR-08", "AR-11"]},
    "typed-routing": {"prefixes": ["masukja2/operation_binding.py", "masukja2/typed_objective_operands.py"], "ops": list(PROFILES | RUNTIME_PROFILES), "requirements": ["AR-03", "AR-04", "AR-05", "AR-06", "AR-08", "AR-11"]},
    "recipe-scroll": {"prefixes": ["masukja2/recipe_scroll.py", "masukja2/recipe_scroll_native.py", "data/arbeit-recipe-scroll/"], "ops": ["inventory.hold", "life.craft", "life.alter"], "requirements": ["AR-06", "AR-07"]},
    "typed-inventory": {"prefixes": ["masukja2/objective_inventory", "data/objective-inventory/"], "ops": ["inventory.hold", "life.craft", "objective.deliver"], "requirements": ["AR-06", "AR-07", "AR-09"]},
    "current-dialogue-owner": {"prefixes": ["masukja2/dialogue_ownership.py"], "ops": ["dialogue.accept", "dialogue.report", "dialogue.talk"], "requirements": ["AR-03", "AR-05", "AR-10"]},
    "dynamic-map-objective": {"prefixes": ["data/arbeit-dynamic-map-targets.json"], "ops": ["objective.move", "objective.inspect", "objective.unresolved"], "requirements": ["AR-04", "AR-08"]},
    "native-control-input": {"prefixes": ["data/arbeit-quest-ui-r3/", "data/arbeit-material-escape", "masukja2/quest_ui_actions.py", "masukja2/material_escape"], "ops": ["dialogue.accept", "dialogue.talk", "dialogue.report", "objective.deliver", "reward.claim", "inventory.hold", "life.craft", "life.alter"], "requirements": ["AR-03", "AR-05", "AR-06", "AR-07", "AR-09", "AR-10", "AR-11"]},
    "objective-program": {"prefixes": ["masukja2/quest_program.py", "masukja2/objective_binding.py", "masukja2/quest_autoplay.py", "data/arbeit-quest-program", "masukja2/vendor/map_quest_actions.py"], "ops": list(PROFILES | RUNTIME_PROFILES), "requirements": ["AR-03", "AR-04", "AR-05", "AR-08"]},
    "dialogue-effects": {"prefixes": ["masukja2/dialogue_binding.py", "masukja2/ui_effects.py", "masukja2/quest_ui_actions.py", "data/arbeit-dialogue", "data/arbeit-quest-ui"], "ops": ["dialogue.accept", "dialogue.talk", "dialogue.report", "objective.deliver", "reward.claim"], "requirements": ["AR-03", "AR-05", "AR-10"]},
    "craft": {"prefixes": ["masukja2/craft_observer.py", "masukja2/arbeit_crafting.py", "data/arbeit-craft/"], "ops": ["life.craft", "inventory.hold"], "requirements": ["AR-06", "AR-07"]},
    "material-supply": {"prefixes": ["masukja2/material_", "masukja2/supply_selection.py", "data/arbeit-material-return/"], "ops": ["life.craft", "life.alter", "inventory.hold", "life.gather"], "requirements": ["AR-06", "AR-07", "AR-09", "AR-11"]},
    "continuous-runner": {"prefixes": ["masukja2/continuous_runner.py", "masukja2/wait_contract.py", "masukja2/official_stop.py", "masukja2/ui.py", "masukja2/cli.py"], "ops": list(PROFILES | RUNTIME_PROFILES), "requirements": ["AR-02", "AR-11", "AR-12"]},
    "catalog": {"prefixes": ["data/arbeit-catalog", "data/arbeit-source/", "masukja2/arbeit_graph.py"], "ops": list(PROFILES | RUNTIME_PROFILES), "requirements": ["AR-01", "AR-08", "AR-12"]},
    "context": {"prefixes": ["masukja2/current_context.py", "masukja2/coordinate_geometry.py", "data/arbeit-context/", "masukja2/vendor/map_character"], "ops": list(PROFILES | RUNTIME_PROFILES), "requirements": ["AR-02"]},
    "map": {"prefixes": ["data/arbeit-movement", "masukja2/map_observer.py", "masukja2/arbeit_movement.py", "masukja2/vendor/map_"], "ops": ["move.target", "objective.move", "objective.dungeon_clear"], "requirements": ["AR-04"]},
    "quest-state": {"prefixes": ["data/arbeit-quest-state/", "masukja2/quest_state_observer.py"], "ops": ["quest.offer", "dialogue.accept", "quest.accepted", "quest.step_complete", "reward.claim", "quest.completed"], "requirements": ["AR-03", "AR-05", "AR-10"]},
    "ui": {"prefixes": ["data/arbeit-ui", "masukja2/quest_ui_actions.py", "masukja2/arbeit_targets.py", "masukja2/ui_observer.py", "masukja2/ui_layout_observer.py"], "ops": ["dialogue.accept", "dialogue.talk", "dialogue.report", "objective.deliver", "reward.claim", "inventory.hold", "life.gather", "life.alter", "life.craft"], "requirements": ["AR-03", "AR-05", "AR-07", "AR-10"]},
    "life": {"prefixes": ["data/arbeit-life", "data/r3-life-selection/", "data/arbeit-processing-observer/", "masukja2/life_observer.py", "masukja2/processing_observer.py", "masukja2/processing_completion.py"], "ops": ["inventory.hold", "life.gather", "life.craft", "life.alter"], "requirements": ["AR-06", "AR-07"]},
    "bag-recovery": {"prefixes": ["data/arbeit-bag", "masukja2/bag_recovery.py", "masukja2/goal_reservations.py"], "ops": list(PROFILES | RUNTIME_PROFILES), "requirements": ["AR-09", "AR-11"]},
    "executor-input": {"prefixes": ["masukja2/graph_executor.py", "masukja2/arbeit_operations.py", "masukja2/game_observer.py", "masukja2/arbeit_runtime.py", "masukja2/action_port.py", "masukja2/live_io.py"], "ops": list(PROFILES | RUNTIME_PROFILES), "requirements": ["AR-02", "AR-11", "AR-12"]},
}


def coverage():
    bundle = compile_all()
    counts = Counter(n["op"] for g in bundle["graphs"] for n in g["nodes"])
    if set(counts) != set(PROFILES):
        raise ValueError("New graph operator needs explicit implementation coverage")
    rows = []
    for op, values in PROFILES.items():
        kind, observation, inputs, effect, missing = values
        rows.append(dict(op=op, nodes=counts[op], implementation_kind=kind,
            observation_supplier=observation, input_path=inputs, effect_rule=effect,
            remaining=missing, listed=True, coordinates="partial" if inputs != "read only" else "not_applicable",
            input_connected="partial" if inputs != "read only" else "not_applicable",
            live_effect_verified=False, live_recovery_verified=False, live_test_ready=False))
    runtime_rows=[dict(op=op,nodes=0,definition_basis='current_typed_program_only',
        implementation_kind=v[0],observation_supplier=v[1],input_path=v[2],effect_rule=v[3],remaining=v[4],
        listed=True,coordinates='partial',input_connected='partial',live_effect_verified=False,
        live_recovery_verified=False,live_test_ready=False) for op,v in RUNTIME_PROFILES.items()]
    return dict(schema="masukja.arbeit-implementation-coverage.v1", denominator=bundle["denominator"],
        runtime_rows=runtime_rows,runtime_only_operator_types=len(runtime_rows),
        graphs=len(bundle["graphs"]), nodes=sum(counts.values()), operator_types=len(rows), rows=rows,
        groups={kind: dict(operator_types=sum(r["implementation_kind"] == kind for r in rows),
                           nodes=sum(r["nodes"] for r in rows if r["implementation_kind"] == kind))
                for kind in ("concrete_planner", "typed_state", "conditional_planner", "proposal_bridge")},
        whole_live_routes_ready=0, live_verified_routes=0,
        meaning="Code/source coverage only; no game execution or present server fact is claimed.")


def update_impact(changed_paths):
    normalized = [str(path).replace("\\", "/").removeprefix("./") for path in changed_paths]
    affected = {key: value for key, value in CAPABILITIES.items()
                if any(path == key or any(path.startswith(prefix) for prefix in value["prefixes"]) for path in normalized)}
    operations = {op for value in affected.values() for op in value["ops"]}
    graphs = compile_all()["graphs"]
    return dict(changed=normalized, capabilities=sorted(affected), operations=sorted(operations),
        runtime_operations=sorted(operations & set(RUNTIME_PROFILES)),
        runtime_quest_binding="Current typed occurrence join required; not inferred from static catalog hints",
        quest_ids=sorted(g["quest_id"] for g in graphs if any(n["op"] in operations for n in g["nodes"])),
        requirements=sorted({r for value in affected.values() for r in value["requirements"]}),
        unclassified=[path for path in normalized if not any(path == key or any(path.startswith(p) for p in v["prefixes"]) for key, v in CAPABILITIES.items())],
        required_action="Invalidate affected prepared coordinates and plans; reconcile saved occurrence and effects against the new source lock before resume.",
        modifies_files=False)
