/**
 * Conversation Types for BaoLife
 * Ported from Python conversationEvents.py
 */

import { v4 as uuidv4 } from 'uuid';
import type { Person } from '../../models/Person.js';

/**
 * Conversation message - individual message in a conversation
 */
export interface ConversationMessageData {
  id: string;
  message: string;
  answerOptions?: string[];
  sender?: string;
  datetime: string;
  date?: string;
  time?: string;
  sentiment?: 'positive' | 'negative' | 'neutral' | 'unknown';
  affinityDelta?: number;
  tempId?: string;
  data?: unknown;
}

/**
 * Conversation message class
 */
export class ConversationMessage implements ConversationMessageData {
  id: string;
  message: string;
  answerOptions?: string[];
  sender?: string;
  datetime: string;
  date?: string;
  time?: string;
  sentiment?: 'positive' | 'negative' | 'neutral' | 'unknown';
  affinityDelta?: number;
  tempId?: string;
  data?: unknown;

  constructor(
    message: string,
    options: {
      answerOptions?: string[];
      sender?: string;
      sentiment?: 'positive' | 'negative' | 'neutral' | 'unknown';
      affinityDelta?: number;
      date?: string;
      time?: string;
      tempId?: string;
    } = {}
  ) {
    this.id = uuidv4();
    this.message = message;
    this.answerOptions = options.answerOptions;
    this.sender = options.sender;
    this.datetime = new Date().toISOString();
    this.date = options.date;
    this.time = options.time;
    this.sentiment = options.sentiment;
    this.affinityDelta = options.affinityDelta;
    this.tempId = options.tempId;
  }

  addAnswerOption(option: string): void {
    if (!this.answerOptions) {
      this.answerOptions = [];
    }
    this.answerOptions.push(option);
  }
}

/**
 * Conversation object - represents a conversation thread
 */
export interface ConversationObjData {
  id: string;
  type: 'conversationEvent';
  cType?: string;
  character?: string | Person;
  conversation: ConversationMessage[];
  question: number;
  unread: boolean;
  summary?: string;
  summary_message_count?: number;
  /** Index of last message included in fact extraction (for full-batch extraction) */
  last_fact_extraction_index?: number;
}

/**
 * Conversation object class
 */
export class ConversationObj implements ConversationObjData {
  id: string;
  type: 'conversationEvent' = 'conversationEvent';
  cType?: string;
  character?: string | Person;
  conversation: ConversationMessage[];
  question: number;
  unread: boolean;
  summary?: string;
  summary_message_count?: number;
  last_fact_extraction_index?: number;

  constructor(character?: string | Person, cType?: string) {
    this.id = uuidv4();
    this.cType = cType;
    // Store character ID like Python does, not the full object
    this.character = typeof character === 'string' ? character : character?.id;
    this.conversation = [];
    this.question = 0;
    this.unread = true;
  }

  /**
   * Convert to JSON matching Python's __dict__ serialization
   */
  toJSON(): Record<string, unknown> {
    return {
      id: this.id,
      type: this.type,
      cType: this.cType,
      character: typeof this.character === 'string' ? this.character : this.character?.id,
      conversation: this.conversation,
      question: this.question,
      unread: this.unread,
      summary: this.summary,
      summary_message_count: this.summary_message_count,
      last_fact_extraction_index: this.last_fact_extraction_index,
    };
  }

  addMessage(
    message: string,
    sender?: string,
    options: {
      data?: unknown;
      sentiment?: 'positive' | 'negative' | 'neutral' | 'unknown';
      affinityDelta?: number;
      date?: string;
      time?: string;
      tempId?: string;
    } = {}
  ): void {
    if (message.length <= 1) {
      console.log('Rejecting empty or single-character message');
      return;
    }

    const characterId = typeof this.character === 'string'
      ? this.character
      : this.character?.id;
    const messageSender = sender ?? characterId;

    // Check for duplicate: only reject if identical to the very last message
    // (prevents double-sends from network retries)
    const lastMessage = this.conversation.length > 0
      ? this.conversation[this.conversation.length - 1]
      : null;
    const isDuplicate = lastMessage !== null
      && lastMessage.message === message
      && lastMessage.sender === messageSender;
    if (isDuplicate) {
      console.log(`Skipping duplicate message: "${message.substring(0, 50)}..."`);
      return;
    }

    const msg = new ConversationMessage(message, {
      sender: messageSender,
      sentiment: options.sentiment,
      affinityDelta: options.affinityDelta,
      date: options.date,
      time: options.time,
      tempId: options.tempId,
    });

    if (options.data) {
      msg.data = options.data;
    }

    this.conversation.push(msg);
  }

  getConversation(): ConversationMessage[] {
    return this.conversation;
  }

  addAnswerOption(option: string): void {
    if (this.conversation.length > 0) {
      this.conversation[this.conversation.length - 1].addAnswerOption(option);
    }
  }

  setAnswerOptions(options: string[]): void {
    if (this.conversation.length > 0) {
      this.conversation[this.conversation.length - 1].answerOptions = options;
    }
  }

  getAnswerOptions(): string[] | undefined {
    if (this.conversation.length > 0) {
      return this.conversation[this.conversation.length - 1].answerOptions;
    }
    return undefined;
  }

  /**
   * Create a ConversationObj from plain data (e.g., loaded from database)
   */
  static fromData(data: Record<string, unknown>, character?: { id: string }): ConversationObj {
    const characterId = typeof data.character === 'string'
      ? data.character
      : (data.character as { id?: string })?.id ?? character?.id ?? '';

    const convo = new ConversationObj(characterId, data.cType as string | undefined);
    convo.id = (data.id as string) ?? convo.id;

    // Restore conversation messages
    if (Array.isArray(data.conversation)) {
      for (const msg of data.conversation) {
        if (typeof msg === 'object' && msg !== null) {
          const msgData = msg as Record<string, unknown>;
          convo.addMessage(
            (msgData.message as string) ?? '',
            msgData.sender as string | undefined,
            {
              sentiment: msgData.sentiment as 'positive' | 'negative' | 'neutral' | 'unknown' | undefined,
              date: msgData.date as string | undefined,
              time: msgData.time as string | undefined,
              data: msgData.data,
            }
          );
          // Restore answer options if present
          if (Array.isArray(msgData.answerOptions) && convo.conversation.length > 0) {
            convo.conversation[convo.conversation.length - 1].answerOptions = msgData.answerOptions as string[];
          }
        }
      }
    }

    convo.question = (data.question as number) ?? 0;
    convo.unread = (data.unread as boolean) ?? true;
    convo.summary = data.summary as string | undefined;
    convo.summary_message_count = data.summary_message_count as number | undefined;
    convo.last_fact_extraction_index = data.last_fact_extraction_index as number | undefined;

    return convo;
  }
}

/**
 * Conversation event check result
 */
export interface ConversationCheckResult {
  button: string;
  fname: string;
}

/**
 * Conversation event function type
 */
export type ConversationEventFunction = (
  player: unknown,
  character: Person,
  response?: string | false,
  check?: boolean
) => Promise<ConversationObj | ConversationCheckResult | false>;
