/**
 * 🌳 AI-POWERED TREE-BASED REMINDER SEQUENCE ENGINE
 * 
 * This is the heart of the intelligent reminder system that creates
 * dynamic, conversational journeys for each patient based on their
 * behavior, preferences, and appointment context.
 * 
 * Features:
 * ✨ Dynamic tree traversal with smart routing
 * 🧠 AI-powered personalization engine
 * 📊 Real-time behavioral scoring
 * 🎯 Predictive no-show intervention
 * 🌍 Multi-language support with cultural awareness
 * 🔄 A/B testing infrastructure
 * 📈 Advanced analytics and conversion tracking
 */

import { prisma } from "./db";
import { sendMessage, sendButtons, type WaButton } from "./waha";
import { detectIntent, applyAppointmentIntent } from "./appointment-intent";
import type { 
  ReminderTree, TreeNode, UserJourney, NodeExecution, 
  PatientProfile, Appointment, Lead, Clinic 
} from "@prisma/client";

// ═══════════════════════════════════════════════════════════════════
// 🧠 AI PERSONALITY ENGINE
// ═══════════════════════════════════════════════════════════════════

interface PersonalityTraits {
  warmth: number;        // 0-100: Cold/Professional → Warm/Personal
  urgency: number;       // 0-100: Relaxed → Urgent
  formality: number;     // 0-100: Casual → Formal
  helpfulness: number;   // 0-100: Brief → Detailed
  culturalContext: 'western' | 'arabic' | 'mixed';
}

interface ConversationContext {
  appointmentType: string;
  daysTillAppointment: number;
  previousInteractions: number;
  responseHistory: string[];
  timeOfDay: number;
  isFirstTime: boolean;
  riskLevel: 'low' | 'medium' | 'high';
}

class AIPersonalityEngine {
  static generatePersonality(
    profile: PatientProfile,
    context: ConversationContext
  ): PersonalityTraits {
    // Base personality from patient profile
    let warmth = 70; // Start friendly
    let urgency = 30;
    let formality = 50;
    let helpfulness = 60;

    // Adjust based on engagement score
    if (profile.engagementScore < 30) {
      warmth += 20; // Be warmer with low-engagement patients
      helpfulness += 15;
    }

    // Adjust based on no-show risk
    if (profile.noShowRisk > 70) {
      urgency += 25;
      helpfulness += 20;
      warmth += 10;
    }

    // Adjust based on conversation tone preference
    switch (profile.conversationTone) {
      case 'formal':
        formality += 30;
        warmth -= 10;
        break;
      case 'casual':
        formality -= 25;
        warmth += 15;
        break;
      case 'friendly':
        warmth += 20;
        helpfulness += 10;
        break;
    }

    // Adjust based on response style
    switch (profile.responseStyle) {
      case 'minimal':
        helpfulness -= 20;
        urgency += 10;
        break;
      case 'detailed':
        helpfulness += 25;
        formality += 10;
        break;
      case 'emoji_heavy':
        warmth += 15;
        formality -= 20;
        break;
    }

    // Context adjustments
    if (context.isFirstTime) {
      helpfulness += 20;
      warmth += 10;
    }

    if (context.daysTillAppointment <= 1) {
      urgency += 15;
    }

    // Cultural context
    const culturalContext = profile.languagePreference.includes('ar') ? 'arabic' : 'western';

    return {
      warmth: Math.max(0, Math.min(100, warmth)),
      urgency: Math.max(0, Math.min(100, urgency)),
      formality: Math.max(0, Math.min(100, formality)),
      helpfulness: Math.max(0, Math.min(100, helpfulness)),
      culturalContext
    };
  }

  static personalizeMessage(
    template: string,
    personality: PersonalityTraits,
    context: ConversationContext
  ): string {
    let message = template;

    // Warmth adjustments
    if (personality.warmth > 70) {
      message = message.replace(/Hi /g, "Hi there ");
      message = message.replace(/Thank you/g, "Thank you so much");
      if (personality.culturalContext === 'arabic') {
        message = message.replace(/مرحبا/g, "أهلاً وسهلاً");
      }
    }

    // Urgency adjustments
    if (personality.urgency > 70) {
      message = message.replace(/Please /g, "Please urgently ");
      message = message.replace(/\./g, "!");
      if (context.daysTillAppointment <= 1) {
        message = "⚡ IMPORTANT: " + message;
      }
    }

    // Formality adjustments
    if (personality.formality > 70) {
      message = message.replace(/Hi /g, "Dear ");
      message = message.replace(/Thanks/g, "Thank you");
      message = message.replace(/awesome/gi, "excellent");
    } else if (personality.formality < 30) {
      message = message.replace(/Dear /g, "Hey ");
      message = message.replace(/Thank you/g, "Thanks");
    }

    // Helpfulness adjustments
    if (personality.helpfulness > 70) {
      if (!message.includes("Questions?")) {
        message += "\n\nAny questions? Just reply and we'll help! 😊";
      }
    }

    // Emoji adjustments based on culture and style
    if (personality.culturalContext === 'arabic') {
      // Arabic users often prefer more expressive emojis
      message = message.replace(/😊/g, "😊✨");
      message = message.replace(/✅/g, "✅🎉");
    }

    return message;
  }
}

// ═══════════════════════════════════════════════════════════════════
// 🎯 SMART ROUTING ENGINE
// ═══════════════════════════════════════════════════════════════════

interface RoutingCondition {
  type: 'engagement_score' | 'no_show_risk' | 'response_time' | 'appointment_day' | 'previous_response' | 'time_of_day' | 'custom';
  operator: 'gt' | 'lt' | 'eq' | 'contains' | 'in' | 'not_in';
  value: any;
  weight?: number; // For scoring multiple conditions
}

class SmartRoutingEngine {
  static async evaluateConditions(
    conditions: RoutingCondition[],
    journey: UserJourney & { appointment: Appointment & { lead: Lead, clinic: Clinic } },
    profile: PatientProfile,
    context: ConversationContext
  ): Promise<boolean> {
    let score = 0;
    let totalWeight = 0;

    for (const condition of conditions) {
      const weight = condition.weight || 1;
      totalWeight += weight;

      const passes = await this.evaluateSingleCondition(condition, journey, profile, context);
      if (passes) score += weight;
    }

    return score / totalWeight >= 0.5; // Majority must pass
  }

  private static async evaluateSingleCondition(
    condition: RoutingCondition,
    journey: UserJourney & { appointment: Appointment & { lead: Lead, clinic: Clinic } },
    profile: PatientProfile,
    context: ConversationContext
  ): Promise<boolean> {
    let actualValue: any;

    switch (condition.type) {
      case 'engagement_score':
        actualValue = profile.engagementScore;
        break;
      case 'no_show_risk':
        actualValue = profile.noShowRisk;
        break;
      case 'response_time':
        // Calculate average response time from message logs
        const responseData = await this.getAverageResponseTime(journey.appointment.leadId);
        actualValue = responseData.averageMinutes;
        break;
      case 'appointment_day':
        actualValue = new Date(journey.appointment.dateTime).getDay();
        break;
      case 'time_of_day':
        actualValue = new Date().getHours();
        break;
      case 'previous_response':
        const lastExecution = await prisma.nodeExecution.findFirst({
          where: { journeyId: journey.id },
          orderBy: { executedAt: 'desc' }
        });
        actualValue = lastExecution?.response || '';
        break;
      default:
        return true;
    }

    switch (condition.operator) {
      case 'gt':
        return actualValue > condition.value;
      case 'lt':
        return actualValue < condition.value;
      case 'eq':
        return actualValue === condition.value;
      case 'contains':
        return String(actualValue).toLowerCase().includes(String(condition.value).toLowerCase());
      case 'in':
        return Array.isArray(condition.value) && condition.value.includes(actualValue);
      case 'not_in':
        return Array.isArray(condition.value) && !condition.value.includes(actualValue);
      default:
        return true;
    }
  }

  private static async getAverageResponseTime(leadId: string): Promise<{ averageMinutes: number, responseCount: number }> {
    // This would analyze message logs to find response patterns
    // For now, return default
    return { averageMinutes: 30, responseCount: 0 };
  }
}

// ═══════════════════════════════════════════════════════════════════
// 🌳 TREE EXECUTION ENGINE
// ═══════════════════════════════════════════════════════════════════

export class ReminderTreeEngine {
  // ─── Start a new journey ─────────────────────────────────────────
  static async startJourney(
    appointmentId: string,
    treeId: string,
    triggerType: 'booking_created' | 'no_show' | 'completed' | 'custom' = 'booking_created',
    options: { testMode?: boolean } = {}
  ): Promise<UserJourney> {
    const appointment = await prisma.appointment.findUnique({
      where: { id: appointmentId },
      include: { lead: true, clinic: true, treatment: true }
    });

    if (!appointment) {
      throw new Error(`Appointment ${appointmentId} not found`);
    }

    // Check for existing active journey
    const existingJourney = await prisma.userJourney.findFirst({
      where: {
        appointmentId,
        treeId,
        status: 'active'
      }
    });

    if (existingJourney) {
      console.log(`🔄 Resume existing journey for appointment ${appointmentId}`);
      return existingJourney;
    }

    // Find or create patient profile
    let profile = await prisma.patientProfile.findFirst({
      where: {
        leadId: appointment.leadId,
        clinicId: appointment.clinicId
      }
    });

    if (!profile) {
      profile = await this.createPatientProfile(appointment.lead, appointment.clinic);
    }

    // Find root node of the tree
    const rootNode = await prisma.treeNode.findFirst({
      where: {
        treeId,
        parentId: null
      },
      orderBy: { sortOrder: 'asc' }
    });

    if (!rootNode) {
      throw new Error(`No root node found for tree ${treeId}`);
    }

    // Create the journey
    const journey = await prisma.userJourney.create({
      data: {
        appointmentId,
        treeId,
        currentNodeId: rootNode.id,
        status: 'active',
        journeyData: JSON.stringify({
          triggerType,
          testMode: options.testMode ?? false,
          startContext: await this.buildConversationContext(appointment, profile)
        })
      }
    });

    console.log(`🌱 Started journey ${journey.id} for appointment ${appointmentId}`);
    
    // Execute the first node
    await this.executeNextNode(journey.id);
    
    return journey;
  }

  // ─── Execute the next node in a journey ──────────────────────────
  static async executeNextNode(journeyId: string): Promise<void> {
    const journey = await prisma.userJourney.findUnique({
      where: { id: journeyId },
      include: {
        appointment: {
          include: { lead: true, clinic: true, treatment: true }
        },
        currentNode: true
      }
    });

    if (!journey || !journey.currentNode) {
      console.log(`⚠️ Journey ${journeyId} has no current node`);
      return;
    }

    if (journey.status !== 'active') {
      console.log(`⚠️ Journey ${journeyId} is not active (status: ${journey.status})`);
      return;
    }

    // Get patient profile
    const profile = await prisma.patientProfile.findFirst({
      where: {
        leadId: journey.appointment.leadId,
        clinicId: journey.appointment.clinicId
      }
    });

    if (!profile) {
      console.log(`⚠️ No patient profile found for journey ${journeyId}`);
      return;
    }

    const currentNode = journey.currentNode;
    const context = await this.buildConversationContext(journey.appointment, profile);
    
    try {
      await this.executeNode(journey as any, currentNode, profile, context);
    } catch (error) {
      console.error(`❌ Failed to execute node ${journey.currentNode.id}:`, error);
      
      // Mark execution as failed
      await prisma.nodeExecution.create({
        data: {
          journeyId,
          nodeId: journey.currentNode.id,
          status: 'failed',
          metadata: JSON.stringify({ error: error instanceof Error ? error.message : 'Unknown error' })
        }
      });
    }
  }

  // ─── Execute a specific node ─────────────────────────────────────
  private static async executeNode(
    journey: UserJourney & { appointment: Appointment & { lead: Lead, clinic: Clinic, treatment: any }, currentNode: TreeNode },
    node: TreeNode,
    profile: PatientProfile,
    context: ConversationContext
  ): Promise<void> {
    console.log(`🎯 Executing ${node.nodeType} node: ${node.name}`);

    // ── Idempotency guard: skip if this exact node was executed in the last 10 seconds ──
    const recentExec = await prisma.nodeExecution.findFirst({
      where: {
        journeyId: journey.id,
        nodeId: node.id,
        status: 'executed',
        executedAt: { gte: new Date(Date.now() - 10_000) },
      },
    });
    if (recentExec) {
      console.log(`⏭️  Skipping duplicate execution of node "${node.name}" (ran ${Date.now() - recentExec.executedAt!.getTime()}ms ago)`);
      return;
    }

    // Create execution record
    const execution = await prisma.nodeExecution.create({
      data: {
        journeyId: journey.id,
        nodeId: node.id,
        status: 'executed',
        executedAt: new Date(),
        metadata: JSON.stringify({ nodeType: node.nodeType, context })
      }
    });

    switch (node.nodeType) {
      // ── standard tree engine types ──
      case 'wait':
        await this.executeWaitNode(journey, node, execution);
        break;
        
      case 'send_message':
      case 'message':         // canvas alias
        await this.executeSendMessageNode(journey, node, profile, context, execution);
        break;
        
      case 'send_buttons':
      case 'buttons':         // canvas alias
        await this.executeSendButtonsNode(journey, node, profile, context, execution);
        break;
        
      case 'condition':
        await this.executeConditionNode(journey, node, profile, context, execution);
        break;
        
      case 'action':
      case 'save_var':        // canvas alias
        await this.executeActionNode(journey, node, execution);
        break;
        
      case 'collect_input':
        await this.executeCollectInputNode(journey, node, execution);
        break;

      // ── ai_response — AI removed, skip silently and advance ──
      case 'ai_response':
        console.log(`⏭️  ai_response node "${node.name}" skipped (AI disabled) — advancing`);
        await this.advanceFromNode(journey, node, null, execution);
        break;

      case 'sentiment':
      case 'retry':
        // Evaluation/control nodes — advance to next node immediately
        await this.advanceFromNode(journey, node, null, execution);
        break;

      case 'start':
        // Entry marker — just advance to first real node
        console.log(`▶️  Start node reached — advancing to first content node`);
        await this.advanceFromNode(journey, node, null, execution);
        break;

      case 'end':
        // Terminal node — complete the journey
        console.log(`🏁 End node reached — completing journey ${journey.id}`);
        await prisma.userJourney.update({
          where: { id: journey.id },
          data: { status: 'completed', completedAt: new Date() }
        });
        break;
        
      default:
        console.log(`⚠️ Unknown node type: "${node.nodeType}" — advancing`);
        await this.advanceFromNode(journey, node, null, execution);
    }

    // Update journey last active time
    await prisma.userJourney.update({
      where: { id: journey.id },
      data: { lastActiveAt: new Date() }
    });
  }

  // ─── Generic advance helper (for pass-through nodes) ────────────
  private static async advanceFromNode(
    journey: UserJourney,
    node: TreeNode,
    userResponse: string | null,
    execution: NodeExecution
  ): Promise<void> {
    const nextNode = await this.findNextNode(journey as any, node, userResponse);
    if (nextNode) {
      await this.moveToNextNode(journey.id, nextNode.id, execution.id);
      await this.executeNextNode(journey.id);
    }
  }

  // ─── Node Type Implementations ───────────────────────────────────

  private static async executeWaitNode(
    journey: UserJourney,
    node: TreeNode,
    execution: NodeExecution
  ): Promise<void> {
    const delayMinutes = node.offsetMinutes || 0;
    const journeyMeta  = JSON.parse(journey.journeyData || '{}');
    const testMode     = journeyMeta.testMode === true;

    const nextNode = await this.findNextNode(journey, node, null);
    if (!nextNode) return;

    await this.moveToNextNode(journey.id, nextNode.id, execution.id);

    if (delayMinutes > 0 && !testMode) {
      // Real mode — schedule with setTimeout (job queue in production)
      console.log(`⏱️  Wait node: scheduling next execution in ${delayMinutes} min`);
      setTimeout(async () => {
        await this.executeNextNode(journey.id);
      }, delayMinutes * 60_000);
    } else {
      // Test mode OR zero-delay — execute immediately
      if (testMode && delayMinutes > 0) {
        const label = delayMinutes >= 1440
          ? `${Math.round(delayMinutes / 1440)}d`
          : delayMinutes >= 60
          ? `${Math.round(delayMinutes / 60)}h`
          : `${delayMinutes}m`;
        console.log(`⚡ TEST MODE: skipping ${label} wait → executing immediately`);
      }
      await this.executeNextNode(journey.id);
    }
  }

  private static async executeSendMessageNode(
    journey: UserJourney & { appointment: Appointment & { lead: Lead, clinic: Clinic, treatment: any } },
    node: TreeNode,
    profile: PatientProfile,
    context: ConversationContext,
    execution: NodeExecution
  ): Promise<void> {
    if (!node.content) {
      console.log(`⚠️ Send message node ${node.id} has no content`);
      return;
    }

    // Send the message exactly as written in the template — only fill {{variables}}
    const finalMessage = this.renderTemplate(node.content, journey.appointment, profile, journey);

    try {
      // Send via WhatsApp — capture the WAHA message ID
      const wahaResult = await sendMessage(journey.appointment.lead.phone, finalMessage) as any;
      const wahaMessageId = wahaResult?.key?.id ?? wahaResult?.id ?? null;

      // Log the interaction
      await prisma.messageLog.create({
        data: {
          clinicId: journey.appointment.clinicId,
          appointmentId: journey.appointment.id,
          phone: journey.appointment.lead.phone,
          direction: 'outgoing',
          message: finalMessage,
          status: 'sent',
          wahaMessageId,
          sentAt: new Date()
        }
      });

      console.log(`📤 [Tree] Message sent to ${journey.appointment.lead.phone} | wahaId: ${wahaMessageId}`);

      // Update execution
      await prisma.nodeExecution.update({
        where: { id: execution.id },
        data: {
          metadata: JSON.stringify({
            ...JSON.parse(execution.metadata || '{}'),
            messageSent: finalMessage,
          })
        }
      });

      // Move to next node
      const nextNode = await this.findNextNode(journey, node, null);
      if (nextNode) {
        await this.moveToNextNode(journey.id, nextNode.id, execution.id);
        
        // If next node is not a wait node, execute immediately
        if (nextNode.nodeType !== 'wait') {
          await this.executeNextNode(journey.id);
        } else {
          // Handle wait node delay
          const delayMs = (nextNode.offsetMinutes || 0) * 60000;
          if (delayMs > 0) {
            setTimeout(() => this.executeNextNode(journey.id), delayMs);
          }
        }
      }

    } catch (error: any) {
      const errMsg = error?.message ?? String(error);
      console.error(`❌ Failed to send message to ${journey.appointment.lead.phone}:`, errMsg);

      // Mark execution as failed with error details
      await prisma.nodeExecution.update({
        where: { id: execution.id },
        data: {
          status: 'failed',
          metadata: JSON.stringify({
            ...JSON.parse(execution.metadata || '{}'),
            error: errMsg,
            failedAt: new Date().toISOString(),
          }),
        },
      });

      // Don't leave the journey stuck — still advance to next node
      // so that later nodes (wait, reminders) still fire
      const nextNode = await this.findNextNode(journey, node, null);
      if (nextNode) {
        console.log(`⏩ Advancing past failed send node → "${nextNode.name}"`);
        await this.moveToNextNode(journey.id, nextNode.id, execution.id);
        if (nextNode.nodeType !== 'wait') {
          await this.executeNextNode(journey.id);
        } else {
          const delayMs = (nextNode.offsetMinutes || 0) * 60000;
          if (delayMs > 0) setTimeout(() => this.executeNextNode(journey.id), delayMs);
        }
      }
    }
  }

  private static async executeSendButtonsNode(
    journey: UserJourney & { appointment: Appointment & { lead: Lead, clinic: Clinic, treatment: any } },
    node: TreeNode,
    profile: PatientProfile,
    context: ConversationContext,
    execution: NodeExecution
  ): Promise<void> {
    if (!node.content) return;

    const nodeConfig = JSON.parse(node.content);
    const message = nodeConfig.message || '';
    const buttons: WaButton[] = nodeConfig.buttons || [];

    // Send the message exactly as written in the template — only fill {{variables}}
    const finalMessage = this.renderTemplate(message, journey.appointment, profile, journey);

    try {
      // Build WaButton array — use label (short + emoji) for poll display
      const waButtons: WaButton[] = buttons.map((b: any) => ({
        id: b.id ?? b.label ?? b.text,
        text: b.text ?? b.label ?? '',
        label: b.label ?? b.text ?? '',
      }));

      // Send as poll / numbered menu
      const wahaResult = await sendButtons(
        journey.appointment.lead.phone,
        finalMessage,
        waButtons,
        journey.appointment.clinic.name
      ) as any;
      const wahaMessageId = wahaResult?.id || wahaResult?._data?.key?.id || null;

      // Log the interaction
      await prisma.messageLog.create({
        data: {
          clinicId: journey.appointment.clinicId,
          appointmentId: journey.appointment.id,
          phone: journey.appointment.lead.phone,
          direction: 'outgoing',
          message: `${finalMessage}\n[Options: ${waButtons.map((b,i)=>`${i+1}. ${b.label||b.text}`).join(' | ')}]`,
          status: 'sent',
          wahaMessageId,
          sentAt: new Date()
        }
      });

      console.log(`📊 [Tree] Poll/menu sent to ${journey.appointment.lead.phone} | wahaId: ${wahaMessageId}`);

      // Store buttons in metadata so routing can match by index/label
      await prisma.nodeExecution.update({
        where: { id: execution.id },
        data: {
          status: 'executed',
          metadata: JSON.stringify({
            ...JSON.parse(execution.metadata || '{}'),
            messageSent: finalMessage,
            buttons: waButtons,
            awaitingResponse: true,
          }),
        },
      });

      // Journey waits here — next node determined by user's reply in handleUserResponse()

    } catch (error: any) {
      const errMsg = error?.message ?? String(error);
      console.error(`❌ Failed to send poll/buttons to ${journey.appointment.lead.phone}:`, errMsg);

      await prisma.nodeExecution.update({
        where: { id: execution.id },
        data: {
          status: 'failed',
          metadata: JSON.stringify({
            ...JSON.parse(execution.metadata || '{}'),
            error: errMsg,
            failedAt: new Date().toISOString(),
          }),
        },
      });

      // Advance past the failed buttons node to next node
      const nextNode = await this.findNextNode(journey, node, null);
      if (nextNode) {
        console.log(`⏩ Advancing past failed buttons node → "${nextNode.name}"`);
        await this.moveToNextNode(journey.id, nextNode.id, execution.id);
        if (nextNode.nodeType !== 'wait') {
          await this.executeNextNode(journey.id);
        } else {
          const delayMs = (nextNode.offsetMinutes || 0) * 60000;
          if (delayMs > 0) setTimeout(() => this.executeNextNode(journey.id), delayMs);
        }
      }
    }
  }



  private static async executeConditionNode(
    journey: UserJourney & { appointment: Appointment & { lead: Lead, clinic: Clinic, treatment: any } },
    node: TreeNode,
    profile: PatientProfile,
    context: ConversationContext,
    execution: NodeExecution
  ): Promise<void> {
    if (!node.conditions) return;

    const conditions: RoutingCondition[] = JSON.parse(node.conditions);
    const passes = await SmartRoutingEngine.evaluateConditions(conditions, journey, profile, context);

    console.log(`🎯 Condition node ${node.name}: ${passes ? 'PASS' : 'FAIL'}`);

    // Update execution with result
    await prisma.nodeExecution.update({
      where: { id: execution.id },
      data: {
        metadata: JSON.stringify({
          ...JSON.parse(execution.metadata || '{}'),
          conditionResult: passes,
          evaluatedConditions: conditions
        })
      }
    });

    // Find next node based on condition result
    const nextNode = await this.findNextNode(journey, node, passes ? 'true' : 'false');
    if (nextNode) {
      await this.moveToNextNode(journey.id, nextNode.id, execution.id);
      await this.executeNextNode(journey.id);
    }
  }

  private static async executeActionNode(
    journey: UserJourney,
    node: TreeNode,
    execution: NodeExecution
  ): Promise<void> {
    if (!node.content) return;

    const action = JSON.parse(node.content);
    
    switch (action.type) {
      case 'update_profile':
        await this.updatePatientProfile(journey, action.updates);
        break;
        
      case 'schedule_appointment':
        // Logic to schedule follow-up appointment
        console.log(`📅 Scheduling follow-up appointment`);
        break;
        
      case 'send_notification':
        // Send notification to clinic staff
        console.log(`🔔 Sending staff notification`);
        break;
        
      case 'complete_journey':
        await prisma.userJourney.update({
          where: { id: journey.id },
          data: { status: 'completed', completedAt: new Date() }
        });
        console.log(`✅ Journey completed`);
        return;
    }

    // Move to next node
    const nextNode = await this.findNextNode(journey, node, null);
    if (nextNode) {
      await this.moveToNextNode(journey.id, nextNode.id, execution.id);
      await this.executeNextNode(journey.id);
    }
  }

  private static async executeCollectInputNode(
    journey: UserJourney,
    node: TreeNode,
    execution: NodeExecution
  ): Promise<void> {
    // This node type waits for user input before proceeding
    // The actual collection happens in handleUserResponse()
    
    console.log(`📝 Collection node ${node.name} activated, waiting for user input`);
    
    await prisma.nodeExecution.update({
      where: { id: execution.id },
      data: {
        metadata: JSON.stringify({
          ...JSON.parse(execution.metadata || '{}'),
          awaitingInput: true,
          inputType: node.content ? JSON.parse(node.content).inputType : 'text'
        })
      }
    });
  }

  // ─── User Response Handling ──────────────────────────────────────

  static async handleUserResponse(
    appointmentId: string,
    userResponse: string
  ): Promise<void> {
    // Find active journey for this appointment
    const journey = await prisma.userJourney.findFirst({
      where: {
        appointmentId,
        status: 'active'
      },
      include: {
        appointment: {
          include: { lead: true, clinic: true, treatment: true }
        },
        currentNode: true
      }
    });

    if (!journey || !journey.currentNode) return;

    // Get the current node execution
    const execution = await prisma.nodeExecution.findFirst({
      where: {
        journeyId: journey.id,
        nodeId: journey.currentNode.id,
        status: 'executed'
      },
      orderBy: { executedAt: 'desc' }
    });

    if (!execution) return;

    const metadata = JSON.parse(execution.metadata || '{}');

    // Update execution with user response
    await prisma.nodeExecution.update({
      where: { id: execution.id },
      data: {
        response: userResponse,
        metadata: JSON.stringify({
          ...metadata,
          userResponse,
          responseReceivedAt: new Date()
        })
      }
    });

    // Get patient profile for updating engagement score
    const profile = await prisma.patientProfile.findFirst({
      where: {
        leadId: journey.appointment.leadId,
        clinicId: journey.appointment.clinicId
      }
    });

    if (profile) {
      // Update engagement score based on response quality
      const engagementDelta = this.calculateEngagementDelta(userResponse, metadata.awaitingResponse);
      await this.updatePatientProfile(journey, {
        engagementScore: Math.max(0, Math.min(100, profile.engagementScore + engagementDelta)),
        lastInteractionAt: new Date()
      });
    }

    // Determine next node based on response
    const nextNode = await this.findNextNode(journey, journey.currentNode, userResponse);

    // ── Auto-update appointment status from patient reply ──────────
    const intent = detectIntent(userResponse, nextNode?.name ?? null);
    if (intent) {
      await applyAppointmentIntent(journey.appointmentId, intent);
    }

    if (nextNode) {
      await this.moveToNextNode(journey.id, nextNode.id, execution.id);
      await this.executeNextNode(journey.id);
    }

    console.log(`📨 User response processed: "${userResponse}" -> Next: ${nextNode?.name || 'none'}${intent ? ` | Status → ${intent}` : ''}`);
  }

  // ─── Helper Methods ──────────────────────────────────────────────

  private static async createPatientProfile(lead: Lead, clinic: Clinic): Promise<PatientProfile> {
    return await prisma.patientProfile.create({
      data: {
        leadId: lead.id,
        clinicId: clinic.id,
        responseStyle: 'standard',
        engagementScore: 50.0,
        noShowRisk: 50.0,
        conversationTone: 'neutral',
        languagePreference: 'auto'
      }
    });
  }

  private static async buildConversationContext(
    appointment: Appointment & { lead: Lead, clinic: Clinic, treatment: any },
    profile: PatientProfile
  ): Promise<ConversationContext> {
    const daysTillAppointment = Math.ceil(
      (new Date(appointment.dateTime).getTime() - new Date().getTime()) / (1000 * 60 * 60 * 24)
    );

    const previousInteractions = await prisma.messageLog.count({
      where: {
        phone: appointment.lead.phone,
        clinicId: appointment.clinicId
      }
    });

    const responseHistory = await prisma.nodeExecution.findMany({
      where: {
        journey: { appointmentId: appointment.id }
      },
      select: { response: true },
      orderBy: { executedAt: 'desc' },
      take: 5
    }).then(executions => executions.map(e => e.response).filter((r): r is string => r !== null));

    // Determine risk level based on profile scores
    let riskLevel: 'low' | 'medium' | 'high' = 'low';
    if (profile.noShowRisk > 70) riskLevel = 'high';
    else if (profile.noShowRisk > 40) riskLevel = 'medium';

    return {
      appointmentType: appointment.treatment.name,
      daysTillAppointment,
      previousInteractions,
      responseHistory,
      timeOfDay: new Date().getHours(),
      isFirstTime: previousInteractions === 0,
      riskLevel
    };
  }

  private static renderTemplate(
    template: string,
    appointment: Appointment & { lead: Lead, clinic: Clinic, treatment: any },
    profile: PatientProfile,
    journey: UserJourney
  ): string {
    const vars = {
      name: appointment.lead.name.split(' ')[0],
      fullName: appointment.lead.name,
      treatment: appointment.treatment.name,
      clinic: appointment.clinic.name,
      date: new Date(appointment.dateTime).toLocaleDateString(),
      time: new Date(appointment.dateTime).toLocaleTimeString(),
      engagementScore: profile.engagementScore.toString(),
      riskScore: profile.noShowRisk.toString()
    };

    let result = template;
    for (const [key, value] of Object.entries(vars)) {
      result = result.replace(new RegExp(`{{${key}}}`, 'g'), value);
    }
    return result;
  }

  private static async findNextNode(
    journey: UserJourney,
    currentNode: TreeNode,
    userResponse: string | null
  ): Promise<TreeNode | null> {
    // Find child nodes
    const childNodes = await prisma.treeNode.findMany({
      where: { parentId: currentNode.id },
      orderBy: { sortOrder: 'asc' }
    });

    if (childNodes.length === 0) {
      console.log(`🏁 End of tree reached at node ${currentNode.name}`);
      await prisma.userJourney.update({
        where: { id: journey.id },
        data: { status: 'completed', completedAt: new Date() }
      });
      return null;
    }

    // If there's only one child, use it
    if (childNodes.length === 1) {
      return childNodes[0];
    }

    // Multiple children — try condition matching first
    for (const node of childNodes) {
      if (!node.conditions) continue;
      try {
        const conditions = JSON.parse(node.conditions);
        if (this.matchesCondition(conditions, userResponse)) {
          return node;
        }
      } catch (error) {
        console.error(`Error parsing conditions for node ${node.id}:`, error);
      }
    }

    // No conditions set — try to match by button label/text from execution metadata
    if (userResponse) {
      const lastExecution = await prisma.nodeExecution.findFirst({
        where: { journeyId: journey.id, nodeId: currentNode.id, status: 'executed' },
        orderBy: { executedAt: 'desc' },
      });
      const meta = JSON.parse(lastExecution?.metadata || '{}');
      const buttons: Array<{ id: string; text: string; label?: string }> = meta.buttons || [];

      if (buttons.length > 0) {
        // Match: "1" / "2" / "3" → index
        const numMatch = userResponse.trim().match(/^(\d+)$/);
        if (numMatch) {
          const idx = parseInt(numMatch[1]) - 1;
          if (idx >= 0 && idx < childNodes.length) return childNodes[idx];
        }

        // Match: button label or text (case-insensitive, partial)
        const resp = userResponse.toLowerCase();
        const btnIdx = buttons.findIndex(b =>
          resp.includes((b.label ?? '').toLowerCase()) ||
          resp.includes((b.text ?? '').toLowerCase()) ||
          (b.label ?? '').toLowerCase().includes(resp) ||
          (b.text ?? '').toLowerCase().includes(resp)
        );
        if (btnIdx >= 0 && btnIdx < childNodes.length) return childNodes[btnIdx];

        // Match keywords: yes/confirm → 0, reschedule/change → 1, cancel/no → 2
        if (/yes|confirm|attend|going|will/i.test(resp))    return childNodes[0];
        if (/reschedule|change|different|another/i.test(resp)) return childNodes[Math.min(1, childNodes.length-1)];
        if (/cancel|no|stop|quit/i.test(resp))              return childNodes[Math.min(2, childNodes.length-1)];
      }
    }

    // Default to first child
    return childNodes[0];
  }

  private static matchesCondition(conditions: any, userResponse: string | null): boolean {
    if (!userResponse) return false;
    
    // Simple keyword matching for now
    if (conditions.keywords) {
      const keywords = Array.isArray(conditions.keywords) ? conditions.keywords : [conditions.keywords];
      return keywords.some((keyword: string) => 
        userResponse.toLowerCase().includes(keyword.toLowerCase())
      );
    }
    
    return false;
  }

  private static async moveToNextNode(
    journeyId: string,
    nextNodeId: string,
    executionId: string
  ): Promise<void> {
    await prisma.userJourney.update({
      where: { id: journeyId },
      data: { 
        currentNodeId: nextNodeId,
        lastActiveAt: new Date()
      }
    });

    await prisma.nodeExecution.update({
      where: { id: executionId },
      data: { nextNodeId }
    });
  }

  private static calculateEngagementDelta(response: string, wasAwaitingResponse: boolean): number {
    if (!wasAwaitingResponse) return 0;
    
    let delta = 0;
    
    // Quick response bonus
    if (response.length > 0) delta += 2;
    
    // Length quality
    if (response.length > 10) delta += 1;
    if (response.length > 50) delta += 2;
    
    // Emoji usage (shows engagement)
    const emojiCount = (response.match(/[\u{1F600}-\u{1F64F}]|[\u{1F300}-\u{1F5FF}]|[\u{1F680}-\u{1F6FF}]|[\u{1F1E0}-\u{1F1FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]/gu) || []).length;
    delta += Math.min(emojiCount, 3);
    
    return delta;
  }

  private static async updatePatientProfile(
    journey: UserJourney,
    updates: Partial<PatientProfile>
  ): Promise<void> {
    await prisma.patientProfile.updateMany({
      where: {
        leadId: journey.appointmentId,
        clinicId: journey.appointmentId
      },
      data: {
        ...updates,
        updatedAt: new Date()
      }
    });
  }
}

// ═══════════════════════════════════════════════════════════════════
// 🎬 TREE BUILDER HELPERS - For creating pre-built trees
// ═══════════════════════════════════════════════════════════════════

export class TreeBuilder {
  static async createStandardBookingTree(clinicId: string): Promise<ReminderTree> {
    const tree = await prisma.reminderTree.create({
      data: {
        clinicId,
        name: 'Standard Booking Journey',
        description: 'Intelligent booking confirmation and follow-up sequence',
        triggerType: 'booking_created',
        version: 1
      }
    });

    // Build the tree structure
    await this.createBookingTreeStructure(tree.id);
    
    return tree;
  }

  private static async createBookingTreeStructure(treeId: string): Promise<void> {
    // Root: Immediate confirmation
    const root = await prisma.treeNode.create({
      data: {
        treeId,
        name: 'Booking Confirmation',
        nodeType: 'send_buttons',
        content: JSON.stringify({
          message: 'Hi {{name}}! ✅ Your {{treatment}} appointment is confirmed for {{date}} at {{time}}.\n\nHow would you like to proceed?',
          buttons: [
            { id: 'confirmed', text: '✅ All Good!' },
            { id: 'reschedule', text: '📅 Reschedule' },
            { id: 'questions', text: '❓ I have questions' }
          ]
        }),
        offsetMinutes: 0,
        sortOrder: 1
      }
    });

    // Branch 1: Confirmed path
    const confirmedPath = await prisma.treeNode.create({
      data: {
        treeId,
        parentId: root.id,
        name: 'Confirmation Received',
        nodeType: 'send_message',
        content: 'Perfect! {{name}} 🎉 We\'re looking forward to seeing you on {{date}}. Please arrive 10 minutes early.',
        conditions: JSON.stringify({ keywords: ['confirmed', 'good', 'yes', 'ok'] }),
        sortOrder: 1
      }
    });

    // Branch 2: Reschedule path
    const reschedulePath = await prisma.treeNode.create({
      data: {
        treeId,
        parentId: root.id,
        name: 'Reschedule Request',
        nodeType: 'send_message',
        content: 'No problem, {{name}}! 📅 Our team will contact you within 30 minutes to find a better time.',
        conditions: JSON.stringify({ keywords: ['reschedule', 'change', 'different'] }),
        sortOrder: 2
      }
    });

    // Branch 3: Questions path
    const questionsPath = await prisma.treeNode.create({
      data: {
        treeId,
        parentId: root.id,
        name: 'Questions Handler',
        nodeType: 'collect_input',
        content: JSON.stringify({
          inputType: 'text',
          prompt: 'Please ask your question and our team will respond shortly!'
        }),
        conditions: JSON.stringify({ keywords: ['question', 'ask', 'help', 'info'] }),
        sortOrder: 3
      }
    });

    // Add reminder sequences...
    await this.addReminderSequence(treeId, confirmedPath.id);
  }

  private static async addReminderSequence(treeId: string, parentNodeId: string): Promise<void> {
    // 3 days before reminder
    const threeDayReminder = await prisma.treeNode.create({
      data: {
        treeId,
        parentId: parentNodeId,
        name: '3 Days Before',
        nodeType: 'condition',
        conditions: JSON.stringify([{
          type: 'appointment_day',
          operator: 'gt',
          value: 3
        }]),
        offsetMinutes: 4320, // 3 days
        sortOrder: 1
      }
    });

    // High engagement path
    await prisma.treeNode.create({
      data: {
        treeId,
        parentId: threeDayReminder.id,
        name: 'High Engagement Reminder',
        nodeType: 'send_message',
        content: 'Hi {{name}}! Just a friendly reminder about your {{treatment}} appointment in 3 days. Looking forward to it! ✨',
        conditions: JSON.stringify([{
          type: 'engagement_score',
          operator: 'gt',
          value: 60
        }]),
        sortOrder: 1
      }
    });

    // Low engagement path
    await prisma.treeNode.create({
      data: {
        treeId,
        parentId: threeDayReminder.id,
        name: 'Low Engagement Reminder',
        nodeType: 'send_buttons',
        content: JSON.stringify({
          message: 'Hi {{name}}! Your {{treatment}} appointment is coming up in 3 days. We want to make sure you\'re all set!',
          buttons: [
            { id: 'ready', text: '✅ I\'m ready' },
            { id: 'prep_help', text: '❓ Need prep info' },
            { id: 'reschedule', text: '📅 Reschedule' }
          ]
        }),
        conditions: JSON.stringify([{
          type: 'engagement_score',
          operator: 'lt',
          value: 60
        }]),
        sortOrder: 2
      }
    });
  }
}