import type { PlantEvent } from '../../types/garden';
import type { SavedPlantProfile } from '../../types/plantScan';

export const DAY_MS = 24 * 60 * 60 * 1000;

const MAX_CONTEXT_TEXT_LENGTH = 600;

type UnknownRecord = Record<string, unknown>;

function asRecord(value: unknown): UnknownRecord | null {
  return value !== null && typeof value === 'object' ? value as UnknownRecord : null;
}

export function cleanContextText(value: unknown, maxLength = MAX_CONTEXT_TEXT_LENGTH): string {
  if (typeof value !== 'string') return '';
  return value
    .replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, ' ')
    .replace(/[<>]/g, character => character === '<' ? '‹' : '›')
    .replace(/\s+/g, ' ')
    .trim()
    .slice(0, maxLength);
}

export function parseTimestamp(value: unknown): number | null {
  if (typeof value !== 'string' && typeof value !== 'number' && !(value instanceof Date)) {
    return null;
  }
  const timestamp = value instanceof Date ? value.getTime() : new Date(value).getTime();
  return Number.isFinite(timestamp) ? timestamp : null;
}

/** Timestamp used to place an event on the plant's chronological timeline. */
export function eventTimestamp(event: PlantEvent): number | null {
  const record = event as unknown as UnknownRecord;
  return parseTimestamp(record.eventDate)
    ?? parseTimestamp(record.createdAt)
    ?? parseTimestamp(record.updatedAt)
    ?? parseTimestamp(record.deletedAt);
}

/** Newest metadata timestamp on an event, used for version and freshness checks. */
export function eventLatestTimestamp(event: PlantEvent): number | null {
  const record = event as unknown as UnknownRecord;
  const timestamps = [
    parseTimestamp(record.deletedAt),
    parseTimestamp(record.updatedAt),
    parseTimestamp(record.eventDate),
    parseTimestamp(record.createdAt),
  ].filter((value): value is number => value !== null);
  return timestamps.length ? Math.max(...timestamps) : null;
}

function isDeletedEvent(event: PlantEvent): boolean {
  const record = event as unknown as UnknownRecord;
  return record.isDeleted === true || Boolean(record.deletedAt);
}

function isIdentifiableEvent(value: unknown): value is PlantEvent {
  const record = asRecord(value);
  if (!record || !cleanContextText(record.id, 200)) return false;
  // A deletion tombstone may legitimately omit display fields. Keep it in the
  // merge so it can suppress a stale live copy from the secondary collection.
  return Boolean(cleanContextText(record.title) || record.isDeleted === true || record.deletedAt);
}

/**
 * Merge event collections by id, preferring the newest version of each record.
 * Tombstones participate in the merge so a stale secondary collection cannot
 * accidentally resurrect a deleted event.
 */
export function mergeLivePlantEvents(
  ...collections: Array<readonly PlantEvent[] | null | undefined>
): PlantEvent[] {
  const byId = new Map<string, PlantEvent>();

  for (const collection of collections) {
    if (!Array.isArray(collection)) continue;
    for (const candidate of collection) {
      if (!isIdentifiableEvent(candidate)) continue;
      const id = cleanContextText((candidate as unknown as UnknownRecord).id, 200);
      const existing = byId.get(id);
      if (
        !existing
        || (eventLatestTimestamp(candidate) ?? 0) >= (eventLatestTimestamp(existing) ?? 0)
      ) {
        byId.set(id, candidate);
      }
    }
  }

  return [...byId.values()].filter(event => !isDeletedEvent(event) && Boolean(cleanContextText(event.title)));
}

/** Read both supported event fields and merge them without mutating the profile. */
export function getLivePlantEvents(plant: SavedPlantProfile): PlantEvent[] {
  const extended = plant as SavedPlantProfile & {
    events?: PlantEvent[];
    plantEvents?: PlantEvent[];
  };
  return mergeLivePlantEvents(extended.events, extended.plantEvents);
}

export function formatEventDate(event: PlantEvent): string {
  const timestamp = eventTimestamp(event);
  return timestamp === null ? 'date unknown' : new Date(timestamp).toISOString().slice(0, 10);
}

export function profileAgeDays(createdAt: unknown, now = Date.now()): number {
  const createdTimestamp = parseTimestamp(createdAt);
  if (createdTimestamp === null || !Number.isFinite(now)) return 0;
  return Math.max(0, (now - createdTimestamp) / DAY_MS);
}

export function uniqueNonEmptyStrings(values: readonly string[]): string[] {
  const seen = new Set<string>();
  const result: string[] = [];
  for (const value of values) {
    const cleaned = cleanContextText(value);
    if (!cleaned || seen.has(cleaned)) continue;
    seen.add(cleaned);
    result.push(cleaned);
  }
  return result;
}
