export const categories = ['outdoors', 'learning', 'creative'] as const;
export type Category = (typeof categories)[number];
export type CategoryFilter = Category | 'all';
export type CommunityEvent = {
  id: string;
  title: string;
  category: Category;
  date: string;
  location: string;
  places: number;
  description?: string;
};

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null && !Array.isArray(value);
}
export function isCategory(value: unknown): value is Category {
  return value === 'outdoors' || value === 'learning' || value === 'creative';
}
function isNonemptyText(value: unknown): value is string {
  return typeof value === 'string' && value.trim().length > 0;
}
function isCalendarDate(value: unknown): value is string {
  if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return false;
  const date = new Date(`${value}T00:00:00.000Z`);
  return Number.isFinite(date.getTime()) && date.toISOString().slice(0, 10) === value;
}

// A TypeScript annotation cannot validate JSON. Every unknown field is checked here.
export function parseEvents(value: unknown): CommunityEvent[] {
  if (!Array.isArray(value)) throw new Error('Expected an array of events.');
  const ids = new Set<string>();
  return value.map((item: unknown, index): CommunityEvent => {
    if (!isRecord(item) || !isNonemptyText(item.id) ||
        !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(item.id) ||
        !isNonemptyText(item.title) || !isCategory(item.category) ||
        !isCalendarDate(item.date) || !isNonemptyText(item.location) ||
        typeof item.places !== 'number' || !Number.isInteger(item.places) || item.places < 0 ||
        (item.description !== undefined && typeof item.description !== 'string')) {
      throw new Error(`Event ${index + 1} has invalid fields.`);
    }
    if (ids.has(item.id)) throw new Error(`Duplicate event ID: ${item.id}.`);
    ids.add(item.id);
    return {
      id: item.id, title: item.title, category: item.category, date: item.date,
      location: item.location, places: item.places,
      ...(item.description === undefined ? {} : { description: item.description }),
    };
  });
}

export function filterEvents(
  events: readonly CommunityEvent[], query: string, category: CategoryFilter,
): CommunityEvent[] {
  const normalized = query.trim().toLocaleLowerCase();
  return events.filter(event =>
    (category === 'all' || event.category === category) &&
    `${event.title} ${event.location}`.toLocaleLowerCase().includes(normalized),
  );
}
export function toggleSaved(ids: readonly string[], id: string): string[] {
  return ids.includes(id) ? ids.filter(value => value !== id) : [...ids, id];
}
