/**
 * Message store helpers - delegates pure domain logic to `domains/chat/domain/`
 * and provides DB-dependent hydration/merge operations at the application level.
 */
import { Message } from '../../shared-kernel';
import { DatabaseService } from '../../infrastructure/persistence';
import { getMessageTimestamp, normaliseDeletedMessage } from '../../domains/chat/domain/rules/message.rules';
import type { InventoryItem } from '../../domains/chat/domain/rules/message-sync.rules';

// Re-export domain logic so existing callers keep working
export {
  getMessageTimestamp,
  getLatestTimestamp,
  normaliseDeletedMessage,
  canEditMessage
} from '../../domains/chat/domain/rules/message.rules';
export {
  INVENTORY_LIMIT,
  CHUNK_SIZE,
  SYNC_POLL_FAST_MS,
  SYNC_POLL_SLOW_MS,
  SYNC_TIMEOUT_MS,
  FULL_SYNC_LIMIT,
  chunkArray,
  findMissingIds
} from '../../domains/chat/domain/rules/message-sync.rules';
export type { InventoryItem } from '../../domains/chat/domain/rules/message-sync.rules';

/** Hydrates a single message with its reactions from the database. */
export async function hydrateMessage(
  msg: Message,
  db: DatabaseService
): Promise<Message> {
  if (msg.isDeleted)
    return normaliseDeletedMessage(msg);

  const reactions = await db.getReactionsForMessage(msg.id);

  return reactions.length > 0 ? { ...msg,
    reactions } : msg;
}

/** Hydrates an array of messages with their reactions. */
export async function hydrateMessages(
  messages: Message[],
  db: DatabaseService
): Promise<Message[]> {
  return Promise.all(messages.map((msg) => hydrateMessage(msg, db)));
}

/** Builds a sync inventory item from a message and its reaction count. */
export async function buildInventoryItem(
  msg: Message,
  db: DatabaseService,
  attachmentCountOverride?: number
): Promise<InventoryItem> {
  if (msg.isDeleted) {
    return {
      id: msg.id,
      ts: getMessageTimestamp(msg),
      rc: 0,
      ac: 0
    };
  }

  const reactions = await db.getReactionsForMessage(msg.id);
  const attachments =
    attachmentCountOverride === undefined
      ? await db.getAttachmentsForMessage(msg.id)
      : [];

  return { id: msg.id,
    ts: getMessageTimestamp(msg),
    rc: reactions.length,
    ac: attachmentCountOverride ?? attachments.length };
}

/** Builds a local map of `{timestamp, reactionCount, attachmentCount}` keyed by message ID. */
export async function buildLocalInventoryMap(
  messages: Message[],
  db: DatabaseService,
  attachmentCountOverrides?: ReadonlyMap<string, number>
): Promise<Map<string, { ts: number; rc: number; ac: number }>> {
  const map = new Map<string, { ts: number; rc: number; ac: number }>();

  await Promise.all(
    messages.map(async (msg) => {
      if (msg.isDeleted) {
        map.set(msg.id, {
          ts: getMessageTimestamp(msg),
          rc: 0,
          ac: 0
        });

        return;
      }

      const reactions = await db.getReactionsForMessage(msg.id);
      const attachmentCountOverride = attachmentCountOverrides?.get(msg.id);
      const attachments =
        attachmentCountOverride === undefined
          ? await db.getAttachmentsForMessage(msg.id)
          : [];

      map.set(msg.id, { ts: getMessageTimestamp(msg),
        rc: reactions.length,
        ac: attachmentCountOverride ?? attachments.length });
    })
  );

  return map;
}

/** Result of merging an incoming message into the local database. */
export interface MergeResult {
  message: Message;
  changed: boolean;
}

/**
 * Merges an incoming message into the local database.
 * Handles message upsert and reaction deduplication, then returns
 * the fully hydrated message alongside a `changed` flag.
 */
export async function mergeIncomingMessage(
  incoming: Message,
  db: DatabaseService
): Promise<MergeResult> {
  const existing = await db.getMessageById(incoming.id);
  const existingTs = existing ? getMessageTimestamp(existing) : -1;
  const incomingTs = getMessageTimestamp(incoming);
  const isDeletedStateNewer =
    !!existing &&
    incomingTs === existingTs &&
    incoming.isDeleted &&
    !existing.isDeleted;
  const isNewer = !existing || incomingTs > existingTs || isDeletedStateNewer;

  if (isNewer) {
    await db.saveMessage(incoming);
  }

  // Persist incoming reactions (deduped by the DB layer)
  const incomingReactions = incoming.isDeleted ? [] : incoming.reactions ?? [];

  for (const reaction of incomingReactions) {
    await db.saveReaction(reaction);
  }

  const changed = isNewer || incomingReactions.length > 0;

  if (changed) {
    const baseMessage = isNewer ? incoming : existing;

    if (!baseMessage) {
      return { message: normaliseDeletedMessage(incoming),
        changed };
    }

    if (baseMessage.isDeleted) {
      return {
        message: normaliseDeletedMessage(baseMessage),
        changed
      };
    }

    const reactions = await db.getReactionsForMessage(incoming.id);

    return {
      message: { ...baseMessage,
        reactions },
      changed
    };
  }

  if (!existing) {
    return { message: normaliseDeletedMessage(incoming),
      changed: false };
  }

  return { message: normaliseDeletedMessage(existing),
    changed: false };
}
