"""Audited CLI adapters; unrepresentable conditions keep the original click path."""
from ..game_connector import successful, paid_actions_enabled
from .contracts import Operation, require, exact, integer
from .food_adapters import SNACK
from .policy import processing_requirements
from .life_activity import activity_idle


def exact_row(view, name, available):
    require(isinstance(view,dict) and isinstance(view.get('items'),list), '커넥터 품목 목록 형식 변경')
    rows=[r for r in view['items'] if isinstance(r,dict) and r.get('DisplayName')==name]
    require(len(rows)==1, '커넥터의 정확한 단일 품목 확인 필요: '+name)
    require(rows[0].get(available) is True, '커넥터 품목 실행 불가: '+str(rows[0]))
    return rows[0]


class ConnectorPort:
    def __init__(self, fallback, client, *, event, journal=None, wait=None, now=None):
        self.fallback,self.client,self.event=fallback,client,event
        self.journal,self.wait,self.now=journal,wait,now
        self.tool_recovery=None

    def ensure_gather_tool(self,op,name):
        if self.tool_recovery:return self.tool_recovery.ensure_ready(op,name)
        exact_row(self.client.query('get_gatherable_items',name),name,'ToolOk')
        return dict(tool_ready=True,item=name,repaired=False)

    def note_user_resume(self):self.fallback.note_user_resume()

    def route(self,op,backend,reason):
        self.event('control_backend',dict(operation=op.name,key=op.key,backend=backend,message=reason))

    def click(self,op,reason):
        self.route(op,'click','클릭 보완 · '+reason)
        return self.fallback.perform(op)

    def idle_field(self,*,allow_fishing=False):
        activity=self.client.query('get_activity')
        # The button's Combat appearance is not the IsInCombat state. Reuse
        # the same official field predicate as direct life menu entry.
        require(activity_idle(activity) and
                (allow_fishing or activity.get('Mode',{}).get('MainButtonState')!='Fishing'),
                '커넥터 실행 전 필드 대기 상태가 필요합니다. 다른 행동을 덮어쓰지 않습니다.')

    def field_before_action(self,op,module,*,allow_fishing=False):
        # Live tests proved that a recipe/menu left open can block the official
        # command, even after wings were charged. Reuse the authored cleanup.
        proof=self.fallback.perform(Operation('prepare_task_start',op.key+'/api_field',
            dict(module=module,entered_combat=False,native_activity=False,previous_in_flight=False)))
        require(proof.get('native_idle') is True and
                (proof.get('outside_combat') is True or proof.get('baseline_ready') is True),
                '커넥터 소비 전 기본 화면 복귀 확인 필요')
        if module=='processing' or proof.get('basis')=='user_prepared_field_screen_only':
            return  # Initial HUD admission must not regain a CLI activity veto.
        if allow_fishing:self.idle_field(allow_fishing=True)
        else:self.idle_field()

    def perform(self,op):
        p=op.parameters;c=self.client
        direct=getattr(self,'direct',None)
        if direct:
            from .direct_processing import OPERATIONS
            from .direct_food import OPERATIONS as FOOD_OPERATIONS
            if op.name in FOOD_OPERATIONS:return direct.perform(op)
            if op.name=='collect_processing' and p.get('include_existing'):
                self.route(op,'click','현장 완료품 좌표 수령 · CLI는 건수 확인만')
                return direct.processing.collect_existing(op)
            if op.name in OPERATIONS and not (op.name=='collect_processing' and p.get('include_existing')):
                return direct.perform(op)
        if op.name=='repair_unavailable_tool':
            require(self.tool_recovery is not None,'공통 CLI 도구 수리 경로 연결 필요')
            return self.tool_recovery.ensure_ready(op)
        if op.name=='read_supplies' and self.tool_recovery and not p.get('combat') and p.get('skill'):
            return self.tool_recovery.read_supplies(op)
        if op.name=='native_fishing' and not p.get('travel_only'):
            self.event('screen_operation_started',dict(key=op.key,operation=op.name,
                input_protocol='prepared-before-send-v1',consuming=op.consuming))
            from .connector_fishing import run
            return run(self,op)
        from .inventory_port import InventoryPort
        if isinstance(self.fallback,InventoryPort) and op.name in InventoryPort.OPERATIONS:
            return self.fallback.perform(op)
        if op.name=='collect_processing' and p.get('include_existing') is True:
            return self.collect_existing(op)
        api = {
            'connector_gather':('execute_gathering','get_gatherable_items','get_activity'),
            'craft_snack_batch':('execute_crafting','get_craftable_items','get_items','get_activity'),
            'register_processing':('execute_altering','get_alterable_items','get_items'),
        }.get(op.name)
        if api is None or not c.supports(*api):return self.click(op,'미지원 행동/현재 명령 목록 · '+op.name)
        if not paid_actions_enabled(c.config):
            return self.click(op,'CLI 실행용 날개 OFF · 이동용 날개/T는 유지')
        if op.name=='register_processing':
            # Exact CLI DisplayName includes the material variant. Only remote
            # withdrawal lacks a command-side resource cap; retain its UI path.
            recipe=p['recipe']
            from .connector_works import altering_candidates
            candidates=altering_candidates(c.query('get_alterable_items'),recipe)
            if len(candidates)!=1:
                return self.click(op,'CLI 이름만으로 재료 변형을 단일 지정할 수 없는 가공품 · '+recipe['name'])
            if p['expected_view'].get('parts_cost')!=0:
                return self.click(op,'원격 부품 비용은 기존 가공 경로에서 확인')
            stock=c.inventory().materials([m['name'] for m in recipe['materials']])
            if any(stock[m['name']]<m['required'] for m in recipe['materials']):
                return self.click(op,'원격 재료 사용 조건은 기존 경로에서 확인')
        self.event('screen_operation_started',dict(key=op.key,operation=op.name,input_protocol='prepared-before-send-v1'))
        self.route(op,'connector','AI 커넥터 · '+op.name)
        if op.name=='connector_gather':
            require('One call gathers up to 100 of the named item' in c.commands['execute_gathering'].get('Note',''),
                    '채집 묶음 수량 명세가 변경되었습니다. 자동 실행하지 않습니다.')
            self.ensure_gather_tool(op,p['name'])
            self.field_before_action(op,'gather')
            return c.action('execute_gathering',{'displayName':p['name']},op)
        if op.name=='craft_snack_batch':
            count=integer(p['attempts'],'간식 제작 횟수',1)
            require(count<=10,'간식 한 묶음 최대 10회')
            exact_row(c.query('get_craftable_items',SNACK),SNACK,'Craftable')
            stock=c.inventory().materials(['달걀','고기'])
            for name in ('달걀','고기'):require(stock[name]>=count,'가방 간식 재료 부족/잠금: '+name)
            self.field_before_action(op,'gather')
            result=successful(c.action('execute_crafting',{'displayName':SNACK,'craftCount':count},op))
            require(result.get('result')=='completed','간식 제작 완료 미확인: '+str(result))
            exact(result.get('craftCount'),count,'커넥터 제작 횟수')
            return dict(batch_completed=True,attempts=count,basis='connector',receipt=result)
        recipe=p['recipe'];before=p['expected_view']
        # Refresh the existing policy proof immediately before the API commit.
        fresh=self.fallback.perform(Operation('visit_processing',op.key+'/cli_before',{'recipe':recipe}))
        for key in ('recipe_id','available_materials','parts_cost','parts_balance','slots'):
            exact(fresh.get(key),before.get(key),'가공 직전 '+key)
        processing_requirements(recipe,fresh,{'remote_materials':p['remote_materials']})
        candidates=altering_candidates(c.query('get_alterable_items'),recipe)
        require(len(candidates)==1,'가공 직전 CLI 재료 변형 목록 변경 · 등록하지 않습니다.')
        row=candidates[0]
        # Metadata identifies the exact material variant/output only. Current
        # stock and local work counts authorize processing, not this flag.
        name=row['DisplayName']
        exact(row.get('ProducedPerWork'),recipe['output_per_batch'],'가공 산출량')
        self.field_before_action(op,'processing')
        result=successful(c.action('execute_altering',{'displayName':name},op))
        require(result.get('result')=='started','가공 등록 미확인: '+str(result))
        after=self.fallback.perform(Operation('visit_processing',op.key+'/cli_after',{'recipe':recipe}))
        from .processing_adapters import same_layout
        same_layout(before['slots'],after['slots'])
        added=[new for old,new in zip(before['slots'],after['slots']) if old['state']=='empty' and new['state']!='empty']
        require(len(added)==1,'커넥터 등록 후 새 가공 슬롯 한 개 확인 필요 · 재전송 금지')
        for material in recipe['materials']:
            item=material['name']
            exact(after['available_materials'].get(item),before['available_materials'][item]-material['required'],'커넥터 가공 재료 감소')
        return dict(registered=True,slot_id=added[0]['id'],recipe_id=recipe['id'],basis='connector',receipt=result)

    def collect_existing(self,op):
        """Legacy adapter also keeps collection on its existing local UI path."""
        return self.click(op,'현장 완료품 좌표 수령 · 캐릭터 상태 조회/CLI 수령 명령 없음')
