package com.android.commands.monkey.ape.jev;

import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

/** Wire contract for the page-local concrete-action semantic projection. */
public final class JevSemanticProtocol {
    static final Charset UTF8=Charset.forName("UTF-8");
    public static final String PROMOTE="PROMOTE";
    public static final String INSTRUCTIONS="Concrete Action Semantic Review\n\nFor each candidate concrete action, determine whether\nits operation is represented by any existing ModelAction\non the current page.\n\nCompare the candidate with the concrete controls of\nall available ModelActions. Infer operation semantics\nfrom action type, control attributes, and UI context,\nrather than labels or control types alone.\n\nDifferent languages, card names, list items, or content\nvalues may simply be different parameters of the same\noperation. Such data variations alone do not justify\nPROMOTE.\n\nChoose an existing model_action_id when the evidence\nsupports the same operation, even if the data instances\ndiffer. Do not merge genuinely different operations\nmerely because their UI structures are similar.\n\nChoose PROMOTE only when no existing ModelAction can\nreliably represent the candidate's operation.\n\nEach candidate must be judged independently.\nDo not use candidate order as evidence.";

    public static final class Request {
        public final String requestId,bodyJson,inputSignature,inputFingerprint;
        public final List<String> candidateIds,modelActionIds;
        public final int requestBytes;
        private Request(String id,String body,String signature,List<String> candidates,List<String> actions) {
            requestId=id;bodyJson=body;inputSignature=signature;
            try { inputFingerprint=JevSharedEvidence.fingerprint(new JSONObject(body)); }
            catch (JSONException error) { throw new IllegalStateException(error); }
            candidateIds=Collections.unmodifiableList(new ArrayList<String>(candidates));
            modelActionIds=Collections.unmodifiableList(new ArrayList<String>(actions));
            requestBytes=body.getBytes(UTF8).length;
        }
    }
    public static final class Decision {
        public final String candidateId,representativeModelActionId;
        public final double confidence;
        Decision(String id,String representative,double score){candidateId=id;representativeModelActionId=representative;confidence=score;}
        public boolean promote(){return PROMOTE.equals(representativeModelActionId);}
    }
    public static final class Result {
        public final Map<String,Decision> decisions;
        Result(Map<String,Decision> values){decisions=Collections.unmodifiableMap(new LinkedHashMap<String,Decision>(values));}
    }
    private JevSemanticProtocol(){}

    public static Request request(String requestId,String model,JSONObject page,int maxBytes)throws JSONException {
        if(requestId==null||requestId.length()==0||model==null||model.length()==0||page==null||maxBytes<=0)
            throw new JSONException("invalid semantic request");
        JSONObject copy=new JSONObject(page.toString());
        JSONArray groups=copy.getJSONArray("model_actions");
        Set<String> actionIds=new LinkedHashSet<String>();
        List<String> candidates=new ArrayList<String>();Set<String> seen=new LinkedHashSet<String>();
        for(int i=0;i<groups.length();i++) {
            JSONObject group=groups.getJSONObject(i);String actionId=group.getString("model_action_id");
            if(actionId.length()==0||!actionIds.add(actionId))throw new JSONException("duplicate model action id");
            group.getString("action_type");
            JSONArray unexecuted=group.getJSONArray("unexecuted_concrete_actions");
            for(int j=0;j<unexecuted.length();j++) {
                JSONObject control=unexecuted.getJSONObject(j);if(!control.has("candidate_id"))continue;
                String candidate=control.getString("candidate_id");
                if(!candidate.matches("c[1-9][0-9]*")||!seen.add(candidate))throw new JSONException("invalid semantic candidate");
                candidates.add(candidate);
            }
        }
        if(copy.has("candidate_ids")) {
            JSONArray selected=copy.getJSONArray("candidate_ids");candidates.clear();seen.clear();
            for(int i=0;i<selected.length();i++) {
                String candidate=selected.getString(i);
                if(!candidate.matches("c[1-9][0-9]*")||!seen.add(candidate))throw new JSONException("invalid selected candidate");
                candidates.add(candidate);
            }
        }
        if(candidates.isEmpty())throw new JSONException("empty semantic request");
        JSONObject questions=new JSONObject(); JSONObject criteria=new JSONObject();
        for(String actionId:actionIds)criteria.put(actionId,"Existing ModelAction representing this operation");
        for(String id:candidates)questions.put(id,new JSONObject().put("type","choice")
            .put("instructions","Choose the representative_model_action_id for candidate_id "+id+".")
            .put("criteria",new JSONObject(criteria.toString())
                .put(PROMOTE,"No existing ModelAction reliably represents this operation.")));
        JSONObject body=new JSONObject().put("model",model).put("instructions",INSTRUCTIONS)
            .put("page",copy).put("questions",questions);
        Request result=new Request(requestId,body.toString(),copy.optString("input_signature",""),candidates,new ArrayList<String>(actionIds));
        if(result.requestBytes>maxBytes)throw new JSONException("SEMANTIC_REQUEST_BUDGET_EXCEEDED");
        return result;
    }

    public static Result fromProvider(String raw,Request request)throws JSONException {
        JSONObject provider=new JSONObject(raw),answers=provider.getJSONObject("answers");
        if(answers.length()!=request.candidateIds.size())throw new JSONException("incomplete semantic answers");
        for(String id:request.candidateIds)if(!answers.has(id))throw new JSONException("missing semantic answer");
        for(java.util.Iterator<?> it=answers.keys();it.hasNext();)if(!request.candidateIds.contains((String)it.next()))throw new JSONException("unknown semantic answer");
        Map<String,Decision> out=new LinkedHashMap<String,Decision>();
        for(String id:request.candidateIds) {
            JSONObject answer=answers.getJSONObject(id);
            String representative=answer.has("representative_model_action_id")
                ?answer.getString("representative_model_action_id"):answer.optString("choice","");
            if(!PROMOTE.equals(representative)&&!request.modelActionIds.contains(representative))
                throw new JSONException("invalid semantic representative");
            double confidence=answer.optDouble("confidence",1.0);
            if(Double.isNaN(confidence)||confidence<0||confidence>1)throw new JSONException("invalid semantic confidence");
            if(out.put(id,new Decision(id,representative,confidence))!=null)throw new JSONException("duplicate semantic answer");
        }
        return new Result(out);
    }
}
