"""Source-output policy; does not send input or turn stock into quest events."""
from copy import deepcopy
from functools import lru_cache
import json


@lru_cache(maxsize=1)
def requirement_scope():
    from .paths import root
    return json.loads((root()/'data/objective-inventory/requirement-scope.json').read_text('utf8'))


def equipment_crafting_policy(node,recipe,request,snapshot):
    """Explain the exact consuming boundary before an Equipment hold craft.

    Native optional constraints are observed facts. Preview rarity/group IDs
    are not a generative guarantee. Unproven constrained output therefore has
    zero new automatic attempts; existing receipts must still be reconciled.
    """
    from .arbeit_operations import _fresh,_scope
    result=dict(status='waiting',input_eligible=False,reason='current_equipment_hold_constraints_required',missing=[],
        future_attribute_guarantee=False,automatic_retry_allowed=False,max_new_attempts=0,
        already_sent_receipt_must_be_reconciled=True,native_goal_effect_supplied=False,
        required_result_checks=['actual CommonRewardResult NonStackableId -> current owned Equipment',
            'actual RuneSheetData/TranscendentState attributes; preview excluded',
            'same occurrence native objective effect, independent of instance existence',
            'owned result ACK and actual empty UI/HUD before any next cycle'])
    if (not isinstance(request,dict) or request.get('ready') is not True or request.get('confirmed') is not True
            or not _fresh(request,snapshot) or not _scope(request,node,objective=True)
            or request.get('semantics')!='inventory.hold'):return result
    item=request.get('item') or {}
    if item.get('table_domain')!='EquipmentSheet' or item.get('item_id')!=recipe.get('equipment_result_id'):
        return dict(result,reason='current_equipment_hold_recipe_output_mismatch')
    constraints=request.get('constraints') or {}
    if set(constraints)!={'Rarity','Season'} or any(not isinstance(v,dict) or v.get('interpreted') is not True
            or type(v.get('has_value')) is not bool for v in constraints.values()):
        return dict(result,reason='current_equipment_hold_constraints_unverified')
    from .objective_inventory import inventory_sources
    enums={r['name']:r['enum_constants'] for r in inventory_sources()['classes'] if r['name'] in ('Rarity','RuneSeasonType')}
    for name,row in constraints.items():
        if row['has_value'] and (type(row.get('value')) is not int or row['value'] not in enums['Rarity' if name=='Rarity' else 'RuneSeasonType'].values()):
            return dict(result,reason='current_equipment_hold_constraint_enum_unverified',missing=[name])
    requested=[key for key,row in constraints.items() if row['has_value']]
    result.update(recipe_id=recipe['id'],equipment_item_id=item['item_id'],constraints=deepcopy(constraints),
        source_refs=[deepcopy(recipe['source_ref'])],source_recipe_output_identity_verified=True,
        current_native_effect=deepcopy(request.get('native_effect')),
        source_generation_facts={k:deepcopy(recipe.get(k)) for k in ('carving_rune_group_id','item_override_sheet_id',
            'carving_rune_probability','critical_carving_rune_probability')})
    if requested:
        return dict(result,reason='current_equipment_craft_output_constraints_unbound',
            missing=['current recipe output '+key+' -> native '+str(constraints[key].get('optional_argument_type',{}).get('name',key))+' predicate' for key in requested],
            non_guaranteed_attempt_policy='not configured; do not invent a retry budget or consume materials to test a preview')
    return dict(result,status='eligible',input_eligible=True,reason='exact_equipment_output_without_optional_constraints',
        max_new_attempts=None,automatic_retry_allowed=False,
        continuation_policy='existing planner requires positive same-occurrence native progress and current materials after owned cleanup')


def bind_fake_acquisition(node,snapshot):
    """Retain exact output of an already-supported current world objective.

    This reuses the existing typed world binder. Static token symbols can only
    be a comparison, never an interaction target or a substituted Consumable.
    """
    from .world_target_observer import owned_instant_prop_program
    from .objective_inventory import inventory_definitions,current_fake_item_stock
    program,normalized=owned_instant_prop_program(node,snapshot)
    if not (program.get('data') or {}).get('type','').endswith('.GainFakeItemByInteractInstantPropQuestObjectiveData'):
        return None
    output=normalized['operands'].get('output_item') or {}
    if output.get('table_domain')!='QuestFakeItemSheet' or type(output.get('id')) is not int:
        raise ValueError('current_fake_acquisition_exact_output_domain_required')
    definitions=[v for v in inventory_definitions()['fake']['items'] if v['raw_id']==output['id']]
    if len(definitions)!=1:raise ValueError('current_fake_acquisition_source_output_ID_unresolved')
    definition=definitions[0];item=dict(table_domain='QuestFakeItemSheet',item_id=output['id'],
        name=definition['display_name'],symbol=definition['fake_item_id'])
    references=[deepcopy(r) for r in requirement_scope()['fake']['rows']
        if all(r['scope'][k]==node.get(k) for k in ('quest_id','step_id','objective_index'))]
    return dict(ready=True,output_item=item,instant_prop=deepcopy(normalized['operands']['instant_prop']),
        source=program['source'],source_version=deepcopy(program['source_version']),observed_at=program['observed_at'],
        session_id=program['session_id'],character_id=program['character_id'],scope=deepcopy(normalized['scope']),
        source_ref=deepcopy(definition['source_ref']),native_event_effect=deepcopy(program.get('typed_effect')),
        static_descriptor_references=references,
        static_descriptor_output_agrees=all(r['identity']['item_id']==item['item_id'] for r in references) if references else None,
        source_of_action='current typed Data; static description does not override it',
        current_stock=current_fake_item_stock(item,snapshot),stock_is_acquisition_effect=False,
        existing_dispatch='objective.inspect/current owned spawned InstantProp + HUD pointer guard',
        new_action_inferred=False)
