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

import java.nio.charset.StandardCharsets;
import java.util.*;
import org.json.*;

/** Frozen history-pairwise-offline-v1-20261031; only code aggregates into Probe eligibility. */
final class JevHistoryPairwiseProtocol {
    static final java.nio.charset.Charset UTF8=StandardCharsets.UTF_8;
    static final String VERSION="history-pairwise-offline-v1-20261031";
    static final String POLICY="exploratory-top3-v1";
    /** P1 view marker; the frozen SYSTEM prompt text remains unchanged. */
    static final String EVIDENCE_VIEW_VERSION="candidate-closure-v1";
    static final String PROJECTED_SCHEMA=VERSION+":"+EVIDENCE_VIEW_VERSION;
    static final String INCREMENTAL_VIEW_VERSION="incremental-candidate-closure-v2";
    static final int MAX_HISTORY=3;
    static final String RULES="Judge whether two concrete GUI actions represent the same application-function operation, using only the supplied GUI evidence and page context.\nSAME_OPERATION: the same application function, differing only in parameter, data object, or entry point.\nDIFFERENT_OPERATION: different application functions.\nINSUFFICIENT_EVIDENCE: the visible evidence is insufficient to judge reliably.\nThe same handler, class, resource ID, menu container, or switch is not sufficient evidence of operation equivalence. Judge application functions, not implementation form.";
    /** Exact suffix and insertion used by the archived StartLux P1 comparison. */
    static final String P1_DECISION_STANDARD="\n\nDecision standard:\n"
        +"SAME_OPERATION means the candidate and the executed history are the same GUI operation pattern already represented; differences limited to data instances, entered values, or equivalent entry points are SAME.\n"
        +"DIFFERENT_OPERATION means the candidate is a distinct command, feature entry, or interaction branch that the executed history does not represent and is reasonably worth one additional concrete-action attempt. Do not require proof of new method coverage, a new state, or successful completion.\n"
        +"INSUFFICIENT_EVIDENCE means the supplied GUI and execution history do not support a reliable relation.\n"
        +"Treat a visible menu-command label as evidence of the command. Do not equate a menu command with ordinary text entered into an EditText. Different Checklist rows with the same edit behavior are normally SAME. For a long-pressed EditText, do not infer a different block operation from the block name alone; it may be ordinary text selection. Similar class, resource ID, outer container, or ModelAction does not establish equivalence. Compare operation behavior, not wording alone.";
    static final String PROMOTE="PROMOTE",SAME="SAME_OPERATION",DIFFERENT="DIFFERENT_OPERATION",INSUFFICIENT="INSUFFICIENT_EVIDENCE";
    static final class Request {
        final String requestId,bodyJson,inputSignature,inputFingerprint,sourceInputSignature,evidenceViewVersion;
        final List<String> candidateIds;final int requestBytes,questionCount;
        final JSONObject plan;
        Request(String id,Prepared prepared,JSONObject payload)throws JSONException {
            requestId=id;bodyJson=prepared.bodyJson;plan=new JSONObject(payload.toString());
            inputSignature=prepared.inputSignature;inputFingerprint=prepared.inputFingerprint;requestBytes=prepared.requestBytes;
            sourceInputSignature=payload.optString("source_input_signature",inputSignature);
            evidenceViewVersion=payload.optString("evidence_view_version","");
            questionCount=prepared.questionCount;candidateIds=new ArrayList<String>(prepared.candidateIds);
        }
    }
    static final class Decision {
        final String candidateId,representativeModelActionId;
        final double confidence;final boolean resolved;
        JSONObject audit=new JSONObject();
        Decision(String id,String choice,double score){this(id,choice,score,!"UNRESOLVED".equals(choice));}
        Decision(String id,String choice,double score,boolean certain){candidateId=id;representativeModelActionId=choice;confidence=score;resolved=certain;}
        boolean promote(){return resolved && PROMOTE.equals(representativeModelActionId);}
        JSONObject audit(JSONObject target)throws JSONException {
            for(Iterator<?> it=audit.keys();it.hasNext();){String key=(String)it.next();target.put(key,audit.get(key));}
            return target;
        }
    }
    static final class Result {
        final Map<String,Decision> decisions;
        final Map<String,JSONObject> pairAnswers;
        Result(Map<String,Decision> out,Map<String,JSONObject> pairs){decisions=out;pairAnswers=pairs;}
    }
    static final class BudgetExceeded extends JSONException {
        final int requestBytes,stateBytes,questionsBytes,candidateCount,pairCount,limitBytes;
        BudgetExceeded(int requestBytes,int candidateCount,int pairCount,int stateBytes,int questionsBytes,int limit)throws JSONException {
            super("SEMANTIC_REQUEST_BUDGET_EXCEEDED");
            this.requestBytes=requestBytes;this.candidateCount=candidateCount;this.pairCount=pairCount;this.limitBytes=limit;
            this.stateBytes=stateBytes;this.questionsBytes=questionsBytes;
        }
    }
    static final class Prepared {
        final JSONObject body;
        final String bodyJson,inputSignature,inputFingerprint;
        final int requestBytes,questionCount;
        final List<String> candidateIds;
        Prepared(JSONObject body,JSONObject payload,String bodyJson,String fingerprint,int bytes,int count)throws JSONException {
            this.body=body;this.bodyJson=bodyJson;inputSignature=payload.getString("input_signature");inputFingerprint=fingerprint;
            requestBytes=bytes;questionCount=count;
            candidateIds=new ArrayList<String>();JSONArray ids=payload.getJSONArray("candidate_ids");
            for(int i=0;i<ids.length();i++)candidateIds.add(ids.getString(i));
        }
        Request request(String id,JSONObject payload)throws JSONException{return new Request(id,this,payload);}
    }
    static Request request(String id,String model,JSONObject payload,int maxBytes)throws JSONException {
        Prepared prepared=prepare(model,payload,maxBytes);
        return prepared.request(id,payload);
    }
    static Prepared prepare(String model,JSONObject payload,int maxBytes)throws JSONException {
        String view=payload.optString("evidence_view_version","");
        if(!view.isEmpty() && (!(EVIDENCE_VIEW_VERSION.equals(view)||INCREMENTAL_VIEW_VERSION.equals(view))
                || !(VERSION+":"+view).equals(payload.getJSONObject("state").optString("evidence_schema"))
                || payload.optString("source_input_signature").isEmpty()))throw new JSONException("UNKNOWN_EVIDENCE_VIEW");
        if(view.isEmpty() && (PROJECTED_SCHEMA.equals(payload.getJSONObject("state").optString("evidence_schema"))
                || (VERSION+":"+INCREMENTAL_VIEW_VERSION).equals(payload.getJSONObject("state").optString("evidence_schema"))))
            throw new JSONException("MISSING_EVIDENCE_VIEW");
        long questionStart=JevReviewProfile.start();
        JSONObject questions=new JSONObject(),criteria=new JSONObject()
            .put(SAME,"Same application-function operation; only parameter, data object, or entry point differs.")
            .put(DIFFERENT,"Different application-function operations.")
            .put(INSUFFICIENT,"The current visible GUI evidence is insufficient to determine the operation relation reliably.");
        JSONArray pairs=payload.getJSONArray("pairs");
        Set<String> bindings=new HashSet<String>();
        for(int i=0;i<pairs.length();i++) {
            JevDeadline.check();JSONObject pair=pairs.getJSONObject(i);String c=pair.getString("candidate"),h=pair.getString("history");
            String q=pair.getString("id");
            if(questions.has(q) || !bindings.add(c+"|"+h)
                    || !payload.getJSONObject("state").getJSONObject("candidates").has(c)
                    || !payload.getJSONObject("state").getJSONObject("executed_actions").has(h))
                throw new JSONException("invalid or duplicate pair binding");
            questions.put(q,new JSONObject().put("type","choice").put("criteria",criteria)
                .put("instructions",RULES+"\n\nIs candidate at `candidates."+c+"` the same application-function operation as executed action at `executed_actions."+h+"`? Interpret each action using its own source_page_id in pages."+P1_DECISION_STANDARD));
        }
        JevReviewProfile.end("questions",questionStart);
        if(questions.length()==0)throw new JSONException("NO_RECOVERABLE_PAIRS");
        JSONObject body=new JSONObject().put("model",model).put("state",payload.getJSONObject("state")).put("questions",questions);
        long serializeStart=JevReviewProfile.start();
        String bodyJson=body.toString();
        JevReviewProfile.end("wire_serialize",serializeStart);
        long sizeStart=JevReviewProfile.start();
        int bytes=bodyJson.getBytes(UTF8).length;
        JevReviewProfile.end("wire_size",sizeStart);
        JevReviewProfile.add("serialized_wire_bytes",0,bytes);
        int limit=Math.min(maxBytes,32768);
        if(bytes>limit)throw new BudgetExceeded(bytes,payload.getJSONArray("candidate_ids").length(),questions.length(),
            body.getJSONObject("state").toString().getBytes(UTF8).length,questions.toString().getBytes(UTF8).length,limit);
        return new Prepared(body,payload,bodyJson,JevEvidence.digest(bodyJson),bytes,questions.length());
    }
    static Result fromProvider(String raw,Request request)throws JSONException {
        JSONObject answers=new JSONObject(raw).getJSONObject("answers"),questions=new JSONObject(request.bodyJson).getJSONObject("questions");
        if(answers.length()!=questions.length())throw new JSONException("incomplete pair answers");
        for(Iterator<?> it=answers.keys();it.hasNext();)if(!questions.has((String)it.next()))throw new JSONException("unknown pair answer");
        JSONArray pairs=request.plan.getJSONArray("pairs");
        Map<String,List<JSONObject>> byCandidate=new LinkedHashMap<String,List<JSONObject>>();
        Map<String,JSONObject> pairAnswers=new LinkedHashMap<String,JSONObject>();
        for(int i=0;i<pairs.length();i++) {
            JevDeadline.check();JSONObject pair=pairs.getJSONObject(i),answer=answers.getJSONObject(pair.getString("id"));
            String choice=answer.getString("choice");
            if(!SAME.equals(choice)&&!DIFFERENT.equals(choice)&&!INSUFFICIENT.equals(choice))throw new JSONException("invalid pair choice");
            double confidence=answer.getDouble("confidence");
            if(!Double.isFinite(confidence)||confidence<0||confidence>1)throw new JSONException("invalid pair confidence");
            if(answer.has("probabilities")) {
                JSONObject probabilities=answer.getJSONObject("probabilities");
                if(probabilities.length()!=3)throw new JSONException("invalid pair probabilities");
                for(String option:new String[]{SAME,DIFFERENT,INSUFFICIENT}) {
                    double value=probabilities.getDouble(option);
                    if(!Double.isFinite(value)||value<0||value>1)throw new JSONException("invalid pair probability");
                }
            }
            String c=pair.getString("candidate");List<JSONObject> values=byCandidate.get(c);
            if(values==null){values=new ArrayList<JSONObject>();byCandidate.put(c,values);}values.add(answer);
            pairAnswers.put(pairFingerprint(request,pair),new JSONObject(answer.toString()));
        }
        Map<String,Decision> decisions=new LinkedHashMap<String,Decision>();
        JSONObject coverage=request.plan.getJSONObject("coverage");
        for(String c:request.candidateIds) {
            JSONObject expected=coverage.getJSONObject(c);List<JSONObject> values=byCandidate.get(c);
            double same=0;int different=0,rawSame=0,rawDifferent=0,insufficient=0,low=0,trustedSame=0;
            if(values!=null)for(JSONObject value:values) {
                double confidence=value.getDouble("confidence");String choice=value.getString("choice");
                if(SAME.equals(choice))rawSame++;
                if(DIFFERENT.equals(choice))rawDifferent++;
                if(INSUFFICIENT.equals(choice))insufficient++;
                if(confidence<=0.5)low++;
                if(confidence>0.5 && SAME.equals(choice)){same=Math.max(same,confidence);trustedSame++;}
                if(confidence>0.5 && DIFFERENT.equals(choice))different++;
            }
            int count=expected.getInt("selected_history"),comparisons=values==null?0:values.size();
            if(count<0 || count>MAX_HISTORY || comparisons!=count)throw new JSONException("invalid selected history coverage");
            boolean full=expected.getBoolean("complete") && count==expected.getInt("legal_history_count");
            Decision decision;
            if(same>0)decision=new Decision(c,"REPRESENTED",same);
            else if(different>0)decision=new Decision(c,PROMOTE,same==0?maxTrustedDifferent(values):0);
            else decision=new Decision(c,"UNRESOLVED",comparisons==1?values.get(0).getDouble("confidence"):0,false);
            decision.audit=new JSONObject().put("aggregation_policy",POLICY)
                .put("total_history",expected.getInt("legal_history_count")).put("history_complete",expected.optBoolean("history_collection_complete",expected.getBoolean("complete")))
                .put("unknown_mapping_count",expected.optInt("unknown_mapping_count"))
                .put("recoverable_history_count",expected.getInt("recoverable_history_count"))
                .put("selected_history",count).put("comparison_count",comparisons)
                .put("same_count",rawSame).put("different_count",rawDifferent).put("insufficient_count",insufficient)
                .put("trusted_same_count",trustedSame).put("trusted_different_count",different).put("low_confidence_count",low)
                .put("full_history_compared",full).put("exploratory",decision.promote() && !full);
            decisions.put(c,decision);
        }
        return new Result(decisions,pairAnswers);
    }
    private static double maxTrustedDifferent(List<JSONObject> values)throws JSONException {
        double max=0;for(JSONObject value:values)if(DIFFERENT.equals(value.getString("choice")) && value.getDouble("confidence")>0.5)max=Math.max(max,value.getDouble("confidence"));return max;
    }
    /** Stable identity for one Pair; excludes unrelated candidates in the same request. */
    static String pairFingerprint(Request request,JSONObject pair)throws JSONException {
        JSONObject wire=new JSONObject(request.bodyJson);
        JSONObject state=wire.getJSONObject("state"),candidate=state.getJSONObject("candidates").getJSONObject(pair.getString("candidate"));
        JSONObject history=state.getJSONObject("executed_actions").getJSONObject(pair.getString("history"));
        JSONArray pages=new JSONArray();
        for(JSONObject action:new JSONObject[]{candidate,history}) {
            String page=action.optString("source_page_id","");
            if(!page.isEmpty() && state.getJSONObject("pages").has(page))pages.put(state.getJSONObject("pages").get(page));
        }
        JSONObject candidateKey=new JSONObject(candidate.toString());candidateKey.remove("source_page_id");
        JSONObject historyKey=new JSONObject(history.toString());historyKey.remove("source_page_id");
        JSONObject value=new JSONObject().put("protocol",VERSION).put("model",wire.getString("model"))
            .put("contract",request.plan.optString("review_contract","")).put("provider_endpoint",request.plan.optString("provider_endpoint","")).put("state_id",request.plan.optString("state_id","")).put("model_version",request.plan.optLong("model_version",-1)).put("evidence_schema",state.optString("evidence_schema","")).put("pair_role","candidate-vs-history")
            .put("criteria",new JSONObject().put(SAME,"Same application-function operation; only parameter, data object, or entry point differs.").put(DIFFERENT,"Different application-function operations.").put(INSUFFICIENT,"The current visible GUI evidence is insufficient to determine the operation relation reliably."))
            .put("rules",RULES+P1_DECISION_STANDARD).put("candidate",candidateKey).put("history",historyKey)
            .put("history_provenance",request.plan.getJSONObject("history_provenance").optJSONObject(pair.getString("history")))
            .put("pages",pages);
        return JevEvidence.digest(JevSharedEvidence.canonical(value));
    }
    /** Attribute ranking only; equal signatures are NOT asserted to be semantic equivalents. */
    static List<String> selectHistory(JSONObject candidate,Map<String,JSONObject> history)throws JSONException {
        final Map<String,Set<String>> features=new LinkedHashMap<String,Set<String>>();
        for(String id:history.keySet()){JevDeadline.check();features.put(id,selectionFeatures(history.get(id)));}
        return selectHistory(candidate,history.keySet(),features);
    }
    static List<String> selectHistory(JSONObject candidate,Collection<String> historyIds,Map<String,Set<String>> features)throws JSONException {
        return selectHistoryFeatures(selectionFeatures(candidate),historyIds,features);
    }
    static List<String> selectHistoryFeatures(Set<String> wanted,Collection<String> historyIds,Map<String,Set<String>> features)throws JSONException {
        final Map<String,Integer> scores=new HashMap<String,Integer>();
        List<String> ranked=new ArrayList<String>(historyIds);
        for(String id:ranked){JevDeadline.check();Set<String> f=features.get(id);
            if(f==null)throw new JSONException("missing cached history selection features");
            int score=0;for(String value:f)if(wanted.contains(value))score+=value.startsWith("label:")?100:value.startsWith("word:")?10:value.startsWith("resource_id:")?8:2;
            scores.put(id,score);}
        // Stable sort preserves the Registry's chronological / identity tie order.
        Collections.sort(ranked,new Comparator<String>(){public int compare(String a,String b){return Integer.compare(scores.get(b),scores.get(a));}});
        List<String> selected=new ArrayList<String>();Set<Set<String>> seen=new HashSet<Set<String>>();
        while(!ranked.isEmpty() && selected.size()<MAX_HISTORY){
            JevDeadline.check();String next=ranked.get(0);
            if(!selected.isEmpty())for(String id:ranked)if(!seen.contains(features.get(id))){next=id;break;}
            selected.add(next);seen.add(features.get(next));ranked.remove(next);
        }
        return selected;
    }
    static Set<String> selectionFeatures(JSONObject control)throws JSONException {
        Set<String> out=new TreeSet<String>();if(control==null)return out;
        addFeatures(control,out);JSONArray subtree=control.optJSONArray("target_subtree");
        if(subtree!=null)for(int i=0;i<subtree.length();i++)addFeatures(subtree.getJSONObject(i),out);
        return out;
    }
    private static void addFeatures(JSONObject node,Set<String> out)throws JSONException {
        for(String field:new String[]{"text","content_desc","resource_id","class","action_type"}) {
            addSelectionFeature(field,node.optString(field,""),out);
        }
    }
    static void addSelectionFeature(String field,String raw,Set<String> out) {
        String value=raw.trim().toLowerCase(Locale.ROOT);if(value.isEmpty())return;
        if(field.equals("text")||field.equals("content_desc")){
            out.add("label:"+value);for(String word:value.split("[^\\p{L}\\p{N}]+"))if(!word.isEmpty())out.add("word:"+word);
        }else out.add(field+":"+value);
    }
    private JevHistoryPairwiseProtocol(){}
}
