#pragma once
// Step 326: RoutingEngine — Annotation-to-Dispatch Logic
//
// Given a WorkItem with routing annotations, produces a RoutingDecision
// that says what kind of worker should handle it and how much context
// to provide. Rules follow a priority cascade from explicit annotations
// through complexity and context width heuristics to pattern defaults.

#include "WorkItem.h"
#include <string>
#include <vector>
#include <algorithm>

// --- RoutingDecision ---

struct RoutingDecision {
    std::string workerType;          // "deterministic" | "template" | "slm" | "llm" | "human"
    std::string contextWidth;        // "local" | "file" | "project" | "cross-project"
    int contextBudgetTokens = 0;     // estimated tokens for context window
    bool reviewRequired = false;     // whether result needs human review
    float confidence = 0.0f;         // engine's confidence in this routing (0.0-1.0)
    std::string reasoning;           // human-readable explanation
    std::string agentRole;           // "linter" | "refactor" | "generator"

    json toJson() const {
        return json{
            {"workerType", workerType},
            {"contextWidth", contextWidth},
            {"contextBudgetTokens", contextBudgetTokens},
            {"reviewRequired", reviewRequired},
            {"confidence", confidence},
            {"reasoning", reasoning},
            {"agentRole", agentRole}
        };
    }

    static RoutingDecision fromJson(const json& j) {
        RoutingDecision d;
        if (j.contains("workerType")) d.workerType = j["workerType"].get<std::string>();
        if (j.contains("contextWidth")) d.contextWidth = j["contextWidth"].get<std::string>();
        if (j.contains("contextBudgetTokens")) d.contextBudgetTokens = j["contextBudgetTokens"].get<int>();
        if (j.contains("reviewRequired")) d.reviewRequired = j["reviewRequired"].get<bool>();
        if (j.contains("confidence")) d.confidence = j["confidence"].get<float>();
        if (j.contains("reasoning")) d.reasoning = j["reasoning"].get<std::string>();
        if (j.contains("agentRole")) d.agentRole = j["agentRole"].get<std::string>();
        return d;
    }
};

// --- Context budget estimation ---

inline int estimateContextBudget(const std::string& contextWidth) {
    if (contextWidth == "local") return 500;
    if (contextWidth == "file") return 2000;
    if (contextWidth == "project") return 8000;
    if (contextWidth == "cross-project") return 16000;
    return 500; // default to local
}

// --- Agent role from worker type ---

inline std::string agentRoleForWorker(const std::string& workerType) {
    if (workerType == "deterministic" || workerType == "template") return "refactor";
    if (workerType == "human") return "generator";
    return "generator"; // slm, llm
}

// --- Pattern detection ---

inline bool isGetterSetterPattern(const std::string& nodeName) {
    if (nodeName.size() < 4) return false;
    std::string lower = nodeName;
    std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
    return lower.substr(0, 3) == "get" || lower.substr(0, 3) == "set" ||
           lower.substr(0, 2) == "is";
}

// --- RoutingEngine ---

class RoutingEngine {
public:
    // Main entry point: route a single work item
    RoutingDecision route(const WorkItem& item) const {
        RoutingDecision decision;
        decision.contextWidth = item.contextWidth.empty() ? "local" : item.contextWidth;
        decision.reviewRequired = item.reviewRequired;
        decision.contextBudgetTokens = estimateContextBudget(decision.contextWidth);

        // Track how many annotations contributed to the decision
        int annotationSignals = 0;

        // Rule 1: Explicit @Automatability override
        if (!item.workerType.empty() && item.workerType != "llm") {
            // Non-default workerType means architect explicitly set it
            decision.workerType = item.workerType;
            decision.reasoning = "Explicit @Automatability(" + item.workerType + ")";
            decision.confidence = 0.95f;
            annotationSignals++;
            // Human routing always requires review
            if (item.workerType == "human") {
                decision.reviewRequired = true;
                decision.confidence = 0.9f;
                decision.reasoning = "Routed to human (high ambiguity or explicit)";
            }
            decision.agentRole = agentRoleForWorker(decision.workerType);
            return decision;
        }

        // Rule 3: Review gate
        if (item.reviewRequired) {
            annotationSignals++;
        }

        // Rule 5: Context width escalation (evaluated before complexity
        //          since we may not have complexity annotations)
        if (decision.contextWidth == "cross-project") {
            decision.workerType = "llm";
            decision.reasoning = "Cross-project context requires LLM";
            decision.confidence = 0.8f;
            annotationSignals++;
            decision.agentRole = "generator";
            return decision;
        }

        if (decision.contextWidth == "project") {
            decision.workerType = "llm";
            decision.reasoning = "Project-wide context favors LLM";
            decision.confidence = 0.75f;
            annotationSignals++;
            decision.agentRole = "generator";
            return decision;
        }

        // Rule 6: Pattern defaults (no complexity annotations available
        //          through WorkItem, so use heuristics)

        // Getter/setter/accessor → template
        if (isGetterSetterPattern(item.nodeName)) {
            decision.workerType = "template";
            decision.reasoning = "Getter/setter pattern → template";
            decision.confidence = 0.9f;
            decision.agentRole = "refactor";
            return decision;
        }

        // Context-based defaults
        if (decision.contextWidth == "local") {
            // Local context: simple enough for template or SLM
            decision.workerType = "slm";
            decision.reasoning = "Local context, default to SLM";
            decision.confidence = 0.6f;
            decision.agentRole = "generator";
        } else if (decision.contextWidth == "file") {
            decision.workerType = "slm";
            decision.reasoning = "File context, default to SLM";
            decision.confidence = 0.55f;
            decision.agentRole = "generator";
        } else {
            // Fallback
            decision.workerType = "llm";
            decision.reasoning = "Default routing to LLM";
            decision.confidence = 0.5f;
            decision.agentRole = "generator";
        }

        // Boost confidence if we have annotation signals
        if (annotationSignals > 0) {
            decision.confidence = std::min(0.95f,
                decision.confidence + 0.1f * annotationSignals);
        }

        return decision;
    }

    // Batch routing
    std::vector<RoutingDecision> routeBatch(const std::vector<WorkItem>& items) const {
        std::vector<RoutingDecision> decisions;
        decisions.reserve(items.size());
        for (const auto& item : items) {
            decisions.push_back(route(item));
        }
        return decisions;
    }

    RoutingDecision routeWithHistory(const WorkItem& item, int rejectionCount) const {
        RoutingDecision base = route(item);
        if (rejectionCount <= 0) return base;

        // Escalate one level per rejection attempt, based on the last rejected
        // worker. This prevents skipping stages (e.g. slm -> human).
        std::string priorWorker = base.workerType;
        if (!item.rejectionHistory.empty() &&
            !item.rejectionHistory.back().workerType.empty()) {
            priorWorker = item.rejectionHistory.back().workerType;
        }

        if (priorWorker == "human") {
            base.workerType = "human";
            base.reviewRequired = true;
            base.reasoning += " | remains human after rejection";
            base.agentRole = agentRoleForWorker(base.workerType);
            return base;
        }

        base.workerType = escalateWorker(priorWorker);
        base.contextWidth = widenedContext(base.contextWidth, 1);
        base.contextBudgetTokens = estimateContextBudget(base.contextWidth);
        base.reviewRequired = true;
        base.confidence = std::max(0.4f, base.confidence - 0.10f);
        base.reasoning += " | escalated after rejection history";
        base.agentRole = agentRoleForWorker(base.workerType);
        return base;
    }

    // Route and apply: updates the WorkItem's workerType + reviewRequired
    void routeAndApply(WorkItem& item) const {
        auto decision = route(item);
        item.workerType = decision.workerType;
        item.reviewRequired = decision.reviewRequired;
        item.contextWidth = decision.contextWidth;
    }

private:
    static std::string escalateWorker(const std::string& workerType) {
        if (workerType == "deterministic" || workerType == "template") return "slm";
        if (workerType == "slm") return "llm";
        return "human";
    }

    static std::string widenedContext(const std::string& ctx, int rejectionCount) {
        std::vector<std::string> order = {"local", "file", "project", "cross-project"};
        int idx = 0;
        for (size_t i = 0; i < order.size(); ++i) {
            if (order[i] == ctx) {
                idx = static_cast<int>(i);
                break;
            }
        }
        idx = std::min(3, idx + rejectionCount);
        return order[idx];
    }
};
