export type MailMessageDirection = "inbound" | "outbound" | "draft";

const MIN_VALID_MESSAGE_DATE = Date.UTC(1970, 0, 1);
const MAX_FUTURE_SKEW_MS = 2 * 24 * 60 * 60 * 1000;

function toValidMessageDate(
  value: Date | string | null | undefined,
  now: Date,
) {
  if (!value) {
    return null;
  }

  const date = value instanceof Date ? new Date(value.getTime()) : new Date(value);
  const timestamp = date.getTime();

  if (
    !Number.isFinite(timestamp) ||
    timestamp < MIN_VALID_MESSAGE_DATE ||
    timestamp > now.getTime() + MAX_FUTURE_SKEW_MS
  ) {
    return null;
  }

  return date;
}

function parseRawHeaders(rawSource: string) {
  const headerEnd = rawSource.search(/\r?\n\r?\n/);
  const headerBlock = headerEnd >= 0 ? rawSource.slice(0, headerEnd) : rawSource;
  const unfolded = headerBlock.replace(/\r?\n[ \t]+/g, " ");
  const headers = new Map<string, string[]>();

  for (const line of unfolded.split(/\r?\n/)) {
    const separator = line.indexOf(":");

    if (separator <= 0) {
      continue;
    }

    const name = line.slice(0, separator).trim().toLowerCase();
    const value = line.slice(separator + 1).trim();
    const values = headers.get(name) ?? [];
    values.push(value);
    headers.set(name, values);
  }

  return headers;
}

function parseReceivedHeaderDate(values: string[], now: Date) {
  for (const value of values) {
    const separator = value.lastIndexOf(";");

    if (separator < 0) {
      continue;
    }

    const date = toValidMessageDate(value.slice(separator + 1).trim(), now);

    if (date) {
      return date;
    }
  }

  return null;
}

export function extractOriginalMessageDate(input: {
  direction: MailMessageDirection;
  messageDate?: Date | string | null;
  now?: Date;
  rawSource?: string | null;
}) {
  const now = input.now ?? new Date();
  const headers = input.rawSource ? parseRawHeaders(input.rawSource) : null;
  const receivedDate = headers
    ? parseReceivedHeaderDate(headers.get("received") ?? [], now)
    : null;
  const messageDate =
    toValidMessageDate(input.messageDate, now) ??
    toValidMessageDate(headers?.get("date")?.[0], now);

  return input.direction === "inbound"
    ? receivedDate ?? messageDate
    : messageDate ?? receivedDate;
}

export function resolveMailboxMessageReceivedAt(input: {
  direction: MailMessageDirection;
  internalDate: Date;
  messageDate?: Date | string | null;
  now?: Date;
  rawSource?: string | null;
}) {
  return (
    extractOriginalMessageDate(input) ??
    toValidMessageDate(input.internalDate, input.now ?? new Date()) ??
    new Date()
  );
}
