"""Native user input stays distinct from tool approvals and ordinary messages."""
import asyncio
import re
from collections import OrderedDict
from uuid import uuid4
from .models import ControlError

def secret_question(question):
    return bool(question.get('isSecret')) or bool(re.search(r'password|secret|token|credential|密码|密钥|令牌',question.get('question',''),re.I))

class UserInputs:
    def __init__(self): self.entries=OrderedDict();self.presentations={};self.on_change=None
    def changed(self):
        if self.on_change: self.on_change()
    def create(self,agent,session_id,project_id,identity,questions,owner):
        if not isinstance(questions,list) or not 1<=len(questions)<=16: raise ControlError('Unsupported native questions',400)
        for q in questions:
            if not isinstance(q,dict) or not isinstance(q.get('id'),str) or len(q['id'])>160 or not isinstance(q.get('question'),str): raise ControlError('Malformed native question',400)
            if len(q.get('options') or [])>32: raise ControlError('Too many native choices',400)
        if sum(e['state']=='PENDING' for e in self.entries.values())>=128: raise ControlError('Interaction queue full',429)
        rid=uuid4().hex
        entry=dict(request_id=rid,agent=agent,session_id=session_id,project_id=project_id,identity=identity,owner=owner,questions=questions,answers={},index=0,state='PENDING',future=asyncio.get_running_loop().create_future())
        self.entries[rid]=entry
        while len(self.entries)>384:
            key=next((k for k,e in self.entries.items() if e['state']!='PENDING'),None)
            if key is None: break
            self.entries.pop(key)
        self.changed();return entry
    def pending(self): return [e for e in self.entries.values() if e['state']=='PENDING']
    def wire(self,e):
        q=e['questions'][e['index']];secret=secret_question(q)
        return dict(type='user_input_request',request_id=e['request_id'],agent=e['agent'],session_id=e['session_id'],question_id=q['id'],question='Answer on desktop' if secret else q['question'].encode()[:768].decode(errors='ignore'),options=[] if secret else [o['label'].encode()[:64].decode(errors='ignore') for o in (q.get('options') or [])],free_text=not secret and (bool(q.get('isOther')) or not q.get('options')),secret=secret,question_index=e['index'],question_count=len(e['questions']))
    def valid(self,connection,rid,qid):
        if not isinstance(rid,str) or not isinstance(qid,str):return False
        e=self.entries.get(rid)
        return bool(e and e['state']=='PENDING' and e['questions'][e['index']]['id']==qid and self.presentations.get(connection)==(rid,qid))
    def resolve(self,connection,rid,qid,answer,choice_index=None):
        if not self.valid(connection,rid,qid): return False
        e=self.entries[rid];q=e['questions'][e['index']]
        if choice_index is not None:
            options=q.get('options') or []
            if type(choice_index) is not int or not 0<=choice_index<len(options):return False
            answer=options[choice_index]['label']
        if secret_question(q) or not isinstance(answer,str) or not answer.strip() or len(answer.encode())>4000: return False
        labels=[o['label'] for o in q.get('options') or []]
        if labels and answer not in labels and not q.get('isOther'): return False
        e['answers'][qid]={'answers':[answer]};e['index']+=1
        if e['index']==len(e['questions']):
            e['state']='ANSWERED';e['future'].set_result({'answers':e['answers']})
        self.changed();return True
    def cancel(self,e):
        if e['state']=='PENDING':
            e['state']='CANCELLED'
            if not e['future'].done(): e['future'].cancel()
            self.changed()
    def cancel_owner(self,owner):
        for e in self.pending():
            if e['owner']==owner:self.cancel(e)
    def cancel_session(self,agent,sid):
        for e in self.pending():
            if e['agent']==agent and e['session_id']==sid:self.cancel(e)
    def overlay(self,session):
        entry=next((e for e in self.pending() if e["agent"]==session.agent and e["session_id"]==session.session_id),None)
        if entry:
            session.state="WAITING_INPUT";session.waiting_reason=self.wire(entry)["question"]
        return session
    async def wait(self,e): return await asyncio.shield(e['future'])
