import type { AdventureLogBattleGroup, AdventureLogEntry, AdventureLogPlainEntry } from './types';
import type { LogEntry } from '../network/api';
import type { Translations } from '../i18n/types';
import type { Locale } from '../i18n/index';
import {
  isAdventureLogCombatCode,
  isAdventureLogEncounterCode,
  parseAdventureLogLine,
} from './adventureLogMarkers';
import type { AdventureLogEventWire } from './adventureLogFormat';
import { formatAdventureLogEvent, formatAdventureLogPayload } from './adventureLogFormat';

export interface AdventureLogRawRow {
  id: number;
  message: string;
  timestamp: number;
  event?: AdventureLogEventWire;
}

function isEncounterStart(row: AdventureLogRawRow): boolean {
  if (isAdventureLogEncounterCode(row.event?.code)) return true;
  return parseAdventureLogLine(row.message).type === 'encounter';
}

function isBattleLine(row: AdventureLogRawRow): boolean {
  if (isAdventureLogCombatCode(row.event?.code)) return true;
  return parseAdventureLogLine(row.message).type === 'battle';
}

function encounterTitle(locale: Locale, tr: Translations, row: AdventureLogRawRow): string {
  const c = row.event?.code;
  if (isAdventureLogEncounterCode(c)) {
    return formatAdventureLogEvent(locale, tr, c!, row.event?.args, row.message);
  }
  const p = parseAdventureLogLine(row.message);
  return p.type === 'encounter' ? p.title : row.message;
}

function battleLineText(locale: Locale, tr: Translations, row: AdventureLogRawRow): string {
  const c = row.event?.code;
  if (isAdventureLogCombatCode(c)) {
    const formatted = formatAdventureLogEvent(locale, tr, c!, row.event?.args, row.message);
    if (formatted) return formatted;
  }
  const p = parseAdventureLogLine(row.message);
  if (p.type === 'battle') return p.text;
  return row.message;
}

function plainLineText(locale: Locale, tr: Translations, row: AdventureLogRawRow): string {
  const parsed = parseAdventureLogLine(row.message);
  if (parsed.type === 'battle') return parsed.text;
  if (parsed.type === 'encounter') {
    return encounterTitle(locale, tr, row);
  }
  const formatted = formatAdventureLogPayload(locale, tr, { message: row.message, event: row.event });
  return formatted || (parsed.type === 'plain' ? parsed.text : row.message);
}

/** Group raw rows (oldest first) into plain lines + battle groups. */
export function buildAdventureLogEntriesFromRaw(
  sortedOldestFirst: AdventureLogRawRow[],
  locale: Locale,
  tr: Translations,
): AdventureLogEntry[] {
  const out: AdventureLogEntry[] = [];
  let i = 0;
  while (i < sortedOldestFirst.length) {
    const row = sortedOldestFirst[i]!;
    if (isEncounterStart(row)) {
      const lines: { id: number; message: string }[] = [];
      i++;
      while (i < sortedOldestFirst.length) {
        const inner = sortedOldestFirst[i]!;
        if (isBattleLine(inner)) {
          lines.push({ id: inner.id, message: battleLineText(locale, tr, inner) });
          i++;
        } else {
          break;
        }
      }
      const group: AdventureLogBattleGroup = {
        kind: 'battle_group',
        id: row.id,
        title: encounterTitle(locale, tr, row),
        timestamp: row.timestamp,
        lines,
      };
      out.push(group);
    } else {
      const plain: AdventureLogPlainEntry = {
        kind: 'line',
        id: row.id,
        message: plainLineText(locale, tr, row),
        timestamp: row.timestamp,
      };
      out.push(plain);
      i++;
    }
  }
  return out;
}

/** Append a server/client log row immutably. */
export function appendAdventureLogRawRow(
  rows: AdventureLogRawRow[],
  row: Omit<AdventureLogRawRow, 'id'> & { id?: number },
  nextId: () => number,
): AdventureLogRawRow[] {
  const id = row.id ?? nextId();
  const full: AdventureLogRawRow = {
    id,
    message: row.message,
    timestamp: row.timestamp,
    event: row.event,
  };
  return [...rows, full];
}

/** Legacy: append by raw message string only (client or old server). */
export function appendAdventureLogMessageToRows(
  rows: AdventureLogRawRow[],
  rawMessage: string,
  nextId: () => number,
): AdventureLogRawRow[] {
  const parsed = parseAdventureLogLine(rawMessage);
  const ts = Date.now();

  if (parsed.type === 'encounter') {
    return appendAdventureLogRawRow(rows, { message: rawMessage, timestamp: ts }, nextId);
  }
  if (parsed.type === 'battle') {
    const last = rows[rows.length - 1];
    if (last && isEncounterStart(last)) {
      return appendAdventureLogRawRow(rows, { message: rawMessage, timestamp: ts }, nextId);
    }
    return appendAdventureLogRawRow(rows, { message: rawMessage, timestamp: ts }, nextId);
  }
  return appendAdventureLogRawRow(rows, { message: rawMessage, timestamp: ts }, nextId);
}

export function adventureRowsFromServerLog(serverLog: LogEntry[]): { rows: AdventureLogRawRow[]; maxId: number } {
  const sorted = [...serverLog].sort(
    (a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(),
  );
  const rows: AdventureLogRawRow[] = sorted.map((entry) => ({
    id: Number(entry.id),
    message: entry.message ?? '',
    timestamp: new Date(entry.createdAt).getTime(),
    event: entry.event,
  }));
  const maxId = rows.reduce((m, e) => Math.max(m, e.id), 0);
  return { rows, maxId };
}

export function adventureEntriesFromServerLog(
  serverLog: LogEntry[],
  locale: Locale,
  tr: Translations,
): { entries: AdventureLogEntry[]; rawRows: AdventureLogRawRow[]; maxId: number } {
  const { rows, maxId } = adventureRowsFromServerLog(serverLog);
  const entries = buildAdventureLogEntriesFromRaw(rows, locale, tr);
  return { entries, rawRows: rows, maxId };
}
