/**
 * Flow Engine — executes saved Workflow flows for incoming chat messages.
 *
 * Execution model:
 * 1. Load all active flows from the DB
 * 2. For each flow, find its intent node and check if user message matches
 * 3. If matched, traverse the flow graph from intent → response nodes
 * 4. Return the response content (or null if no flow matched)
 */

import { getDb } from "./db";
import { workflowNodes, workflowEdges } from "../drizzle/schema";
import { eq } from "drizzle-orm";

export interface FlowResult {
  content: string;
  shouldEscalate?: boolean;
  flowId: string;
  flowName?: string;
}

// Intent patterns for built-in Support Flows
const FLOW_INTENT_PATTERNS: Record<string, RegExp[]> = {
  "check-order-status": [
    /\border\s*status\b/i,
    /\bcheck.*order\b/i,
    /\bwhere.*my order\b/i,
    /\border.*update\b/i,
    /\bmy order\b/i,
  ],
  "track-shipment": [
    /\btrack.*shipment\b/i,
    /\btracking\b/i,
    /\bshipment.*status\b/i,
    /\bwhere.*package\b/i,
    /\bdelivery.*status\b/i,
    /\btrack.*package\b/i,
  ],
  "submit-return": [
    /\breturn\b/i,
    /\bRMA\b/i,
    /\bsend.*back\b/i,
    /\breturn.*request\b/i,
    /\brefund\b/i,
  ],
  "cancel-order": [
    /\bcancel.*order\b/i,
    /\bcancel.*my order\b/i,
    /\bstop.*order\b/i,
  ],
  "faq-deflection": [
    /\bpayment\b/i,
    /\bwarranty\b/i,
    /\bhow.*long.*delivery\b/i,
    /\bshipping.*time\b/i,
    /\bminimum.*order\b/i,
  ],
  // ── New flows ─────────────────────────────────────────────────────────────
  "leads-capture": [
    /\bi('d| would) like to (buy|order|purchase)\b/i,
    /\bget a quote\b/i,
    /\bbecome a (dealer|retailer|reseller)\b/i,
    /\bopen an? account\b/i,
    /\binterested in (buying|carrying|stocking)\b/i,
    /\bnew (customer|client)\b/i,
    /\bhow (do i|can i) (become|sign up|register|apply)\b/i,
  ],
  "sales-inquiry": [
    /\bbulk (order|pricing|discount)\b/i,
    /\bwholesale\b/i,
    /\bminimum order quantity\b/i,
    /\bMOQ\b/i,
    /\bprice list\b/i,
    /\bcontact.*sales\b/i,
    /\bsales rep(resentative)?\b/i,
    /\btalk to sales\b/i,
  ],
};

// Fallback static responses for flows that don't have DB nodes yet
const FLOW_STATIC_RESPONSES: Record<string, string | null> = {
  "check-order-status": "To check your order status, please provide your **Sales Order number** (e.g., SO-12345) and I'll look it up for you right away.",
  "track-shipment": "I can help you track your shipment! Please share your **order number** or **tracking number** and I'll pull up the latest status.",
  "submit-return": "I can guide you through the return process. Please provide:\n1. Your **order number**\n2. The **item(s)** you want to return\n3. The **reason** for the return\n\nOur return policy allows returns within 30 days of delivery. You can also email **returns@homelegance.com** for assistance.",
  "cancel-order": "I can help with order cancellation. Please provide your **order number** and I'll check if it's still within the cancellation window. Note: orders that have already shipped cannot be cancelled.",
  "faq-deflection": null, // handled by knowledge base
  "leads-capture":
    "We'd love to have you as a Homelegance dealer! 🎉\n\nPlease share the following so our sales team can follow up within **1 business day**:\n1. **Company name**\n2. **Your name & title**\n3. **Email address**\n4. **Phone number**\n5. **City / State** you operate in\n\nAlternatively, email us directly at **sales@homelegance.com**.",
  "sales-inquiry":
    "Our sales team handles bulk orders and wholesale pricing directly.\n\n📞 **Call us:** Contact your assigned sales rep\n📧 **Email:** sales@homelegance.com\n\nOr I can connect you with an agent right now — just say **'Talk to agent'**.",
};

/**
 * Detect if the user message matches a flow intent.
 * Returns the flowId or null.
 */
export function detectFlowIntent(message: string): string | null {
  for (const [flowId, patterns] of Object.entries(FLOW_INTENT_PATTERNS)) {
    if (patterns.some(p => p.test(message))) {
      return flowId;
    }
  }
  return null;
}

/**
 * Execute a flow by ID using full graph traversal.
 * Tries DB-saved nodes first, falls back to static responses.
 */
export async function executeFlow(flowId: string, userMessage: string): Promise<FlowResult | null> {
  // Try to load and traverse DB-saved flow graph
  try {
    const db = await getDb();
    if (db) {
      const [nodes, edges] = await Promise.all([
        db.select().from(workflowNodes).where(eq(workflowNodes.workflowId, flowId)),
        db.select().from(workflowEdges).where(eq(workflowEdges.workflowId, flowId)),
      ]);

      if (nodes.length > 0) {
        const result = traverseGraph(nodes, edges, userMessage);
        if (result) return { ...result, flowId };
      }
    }
  } catch (err) {
    console.error("[FlowEngine] DB error:", err);
  }

  return fallbackToStatic(flowId);
}

// ── Graph traversal ───────────────────────────────────────────────────────────

type DbNode = { nodeId: string; type: string; label: string; config: unknown };
type DbEdge = { sourceNodeId: string; targetNodeId: string; label: string | null; condition: unknown };

function traverseGraph(
  nodes: DbNode[],
  edges: DbEdge[],
  userMessage: string
): Omit<FlowResult, "flowId"> | null {
  // Build maps for O(1) lookup
  const nodeMap = new Map(nodes.map(n => [n.nodeId, n]));
  const adjMap = new Map<string, DbEdge[]>();
  for (const e of edges) {
    if (!adjMap.has(e.sourceNodeId)) adjMap.set(e.sourceNodeId, []);
    adjMap.get(e.sourceNodeId)!.push(e);
  }

  const intentNode = nodes.find(n => n.type === "intent");
  if (!intentNode) return null;

  const responses: string[] = [];
  let shouldEscalate = false;
  const visited = new Set<string>();

  function traverse(nodeId: string): void {
    if (visited.has(nodeId)) return;
    visited.add(nodeId);

    const node = nodeMap.get(nodeId);
    if (!node) return;

    const cfg = node.config as Record<string, unknown> | null ?? {};

    switch (node.type) {
      case "response":
        if (cfg.message) responses.push(String(cfg.message));
        break;

      case "condition": {
        // Route based on whether pattern matches the user message
        const pattern = cfg.pattern ? new RegExp(String(cfg.pattern), "i") : null;
        const conditionMet = pattern ? pattern.test(userMessage) : true;
        const outEdges = adjMap.get(nodeId) ?? [];
        // Edge label "true"/"false" controls routing; if no labels, follow all
        const labeled = outEdges.filter(e => e.label === "true" || e.label === "false");
        if (labeled.length > 0) {
          const target = outEdges.find(e => e.label === (conditionMet ? "true" : "false"));
          if (target) traverse(target.targetNodeId);
        } else {
          outEdges.forEach(e => traverse(e.targetNodeId));
        }
        return; // condition handles its own routing
      }

      case "escalation":
        shouldEscalate = true;
        if (cfg.message) responses.push(String(cfg.message));
        break;

      case "tag":
        // future: tag conversation; no output
        break;

      case "delay":
        // future: async delay; skip for now
        break;
    }

    // Follow outgoing edges to next nodes
    for (const edge of adjMap.get(nodeId) ?? []) {
      traverse(edge.targetNodeId);
    }
  }

  traverse(intentNode.nodeId);

  if (!responses.length && !shouldEscalate) return null;
  return { content: responses.join("\n\n"), shouldEscalate };
}

function fallbackToStatic(flowId: string): FlowResult | null {
  const staticResponse = FLOW_STATIC_RESPONSES[flowId];
  if (staticResponse) return { content: staticResponse, flowId };
  return null;
}
