// @ts-nocheck
import { authorizedFetch } from './auth/tokens';
import { ApiError, emitApiErrorEvent } from './lib/apiError';
import type { EventLimitSummary } from './lib/limitWarnings';
export type { EventLimitSummary };
import i18n from './i18n';

type JsonValue = Record<string, unknown>;

export type TenantAccountProfile = {
  id: number;
  name: string;
  email: string;
  preferred_locale: string | null;
  email_verified: boolean;
  email_verified_at: string | null;
};

export type UpdateTenantProfilePayload = {
  name: string;
  email: string;
  preferred_locale?: string | null;
  current_password?: string;
  password?: string;
  password_confirmation?: string;
};

export type EventQrInviteLayout = {
  id: string;
  name: string;
  description: string;
  subtitle: string;
  paper?: string | null;
  orientation?: string | null;
  panel_mode?: string | null;
  badge_label?: string | null;
  instructions_heading?: string | null;
  link_heading?: string | null;
  cta_label?: string | null;
  cta_caption?: string | null;
  instructions?: string[];
  preview: {
    background: string | null;
    background_gradient: { angle: number; stops: string[] } | null;
    accent: string | null;
    text: string | null;
    qr_size_px?: number | null;
  };
  formats: string[];
  download_urls?: Record<string, string>;
};

export type TenantEventType = {
  id: number;
  slug: string;
  name: string;
  name_translations: Record<string, string>;
  icon: string | null;
  settings: Record<string, unknown>;
  created_at?: string | null;
  updated_at?: string | null;
};

export type LiveShowSettings = {
  moderation_mode?: 'off' | 'manual' | 'trusted_only';
  retention_window_hours?: number;
  playback_mode?: 'newest_first' | 'balanced' | 'curated';
  pace_mode?: 'auto' | 'fixed';
  fixed_interval_seconds?: number;
  layout_mode?: 'single' | 'split' | 'grid_burst';
  effect_preset?: 'film_cut' | 'shutter_flash' | 'polaroid_toss' | 'parallax_glide' | 'light_effects';
  effect_intensity?: number;
  background_mode?: 'blur_last' | 'gradient' | 'solid' | 'brand';
};

export type LiveShowLink = {
  token: string;
  url: string;
  qr_code_data_url: string | null;
  rotated_at: string | null;
};

export type TenantEvent = {
  id: number;
  name: string | Record<string, string>;
  slug: string;
  event_date: string | null;
  event_type_id: number | null;
  event_type: TenantEventType | null;
  status: 'draft' | 'published' | 'archived';
  is_active?: boolean;
  description?: string | null;
  photo_count?: number;
  pending_photo_count?: number;
  like_count?: number;
  tasks_count?: number;
  active_invites_count?: number;
  total_invites_count?: number;
  engagement_mode?: 'tasks' | 'photo_only';
  settings?: Record<string, unknown> & {
    engagement_mode?: 'tasks' | 'photo_only';
    guest_upload_visibility?: 'review' | 'immediate';
    live_show?: LiveShowSettings;
    watermark?: WatermarkSettings;
    watermark_allowed?: boolean | null;
    watermark_removal_allowed?: boolean | null;
    watermark_serve_originals?: boolean | null;
  };
  package?: {
    id: number | string | null;
    name: string | null;
    price: number | null;
    purchased_at: string | null;
    expires_at: string | null;
    branding_allowed?: boolean | null;
    watermark_allowed?: boolean | null;
  } | null;
  limits?: EventLimitSummary | null;
  addons?: EventAddonSummary[];
  member_permissions?: string[] | null;
  [key: string]: unknown;
};

export type GuestNotificationSummary = {
  id: number;
  type: string;
  title: string;
  body: string | null;
  status: 'draft' | 'active' | 'archived';
  audience_scope: 'all' | 'guest';
  target_identifier?: string | null;
  payload?: Record<string, unknown> | null;
  priority: number;
  created_at: string | null;
  expires_at: string | null;
};

export type SendGuestNotificationPayload = {
  title: string;
  message: string;
  type?: string;
  audience?: 'all' | 'guest';
  guest_identifier?: string | null;
  cta?: { label: string; url: string } | null;
  expires_in_minutes?: number | null;
  priority?: number | null;
};

export type TenantPhoto = {
  id: number;
  filename: string | null;
  original_name: string | null;
  mime_type: string | null;
  size: number;
  url: string | null;
  thumbnail_url: string | null;
  status: string;
  live_status?: string | null;
  live_approved_at?: string | null;
  live_reviewed_at?: string | null;
  live_rejection_reason?: string | null;
  live_priority?: number | null;
  is_featured: boolean;
  likes_count: number;
  uploaded_at: string;
  uploader_name: string | null;
  ingest_source?: string | null;
  caption?: string | null;
};

export type EventStats = {
  total: number;
  featured: number;
  likes: number;
  recent_uploads: number;
  status: string;
  is_active: boolean;
  uploads_total?: number;
  uploads_24h?: number;
  likes_total?: number;
  pending_photos?: number;
};

export type EventEngagementSummary = {
  totalPhotos: number;
  uniqueGuests: number;
  tasksSolved: number;
  likesTotal: number;
};

export type EventEngagementLeaderboardEntry = {
  guest: string;
  photos: number;
  likes: number;
};

export type EventEngagementTopPhoto = {
  photoId: number;
  guest: string;
  likes: number;
  task?: string | null;
  createdAt: string;
  thumbnail: string | null;
};

export type EventEngagementTrendingEmotion = {
  emotionId: number;
  name: string;
  count: number;
};

export type EventEngagementTimelinePoint = {
  date: string;
  photos: number;
  guests: number;
};

export type EventEngagementFeedEntry = {
  photoId: number;
  guest: string;
  task?: string | null;
  likes: number;
  createdAt: string;
  thumbnail: string | null;
};

export type EventEngagement = {
  summary: EventEngagementSummary;
  leaderboards: {
    uploads: EventEngagementLeaderboardEntry[];
    likes: EventEngagementLeaderboardEntry[];
  };
  highlights: {
    topPhoto: EventEngagementTopPhoto | null;
    trendingEmotion: EventEngagementTrendingEmotion | null;
    timeline: EventEngagementTimelinePoint[];
  };
  feed: EventEngagementFeedEntry[];
};

export type PhotoboothStatusMetrics = {
  uploads_last_hour?: number | null;
  uploads_today?: number | null;
  uploads_total?: number | null;
  uploads_24h?: number | null;
  last_upload_at?: string | null;
};

export type SparkboothStatus = {
  enabled: boolean;
  status: string | null;
  username: string | null;
  password: string | null;
  expires_at: string | null;
  upload_url: string | null;
  response_format: 'json' | 'xml';
  metrics?: PhotoboothStatusMetrics | null;
};

export type PhotoboothStatus = {
  mode: 'ftp' | 'sparkbooth';
  enabled: boolean;
  status: string | null;
  username: string | null;
  password: string | null;
  path: string | null;
  ftp_url: string | null;
  upload_url: string | null;
  expires_at: string | null;
  rate_limit_per_minute: number;
  ftp: {
    host: string | null;
    port: number;
    require_ftps: boolean;
  };
  sparkbooth?: SparkboothStatus | null;
  metrics?: PhotoboothStatusMetrics | null;
};

export type PhotoboothConnectCode = {
  code: string;
  expires_at: string | null;
};

export type EventAddonCheckout = {
  addon_key: string;
  quantity?: number;
  checkout_url: string | null;
  checkout_id: string | null;
  expires_at: string | null;
};

export type EventAddonCatalogItem = {
  key: string;
  label: string;
  price_id: string | null;
  increments?: Record<string, number>;
};

export type TenantFontVariant = {
  variant: string | null;
  weight: number;
  style: string;
  url: string;
};

export type TenantFont = {
  family: string;
  category?: string | null;
  variants: TenantFontVariant[];
};

export type WatermarkSettings = {
  mode?: 'base' | 'custom' | 'off';
  asset?: string | null;
  asset_url?: string | null;
  asset_data_url?: string | null;
  position?:
    | 'top-left'
    | 'top-center'
    | 'top-right'
    | 'middle-left'
    | 'center'
    | 'middle-right'
    | 'bottom-left'
    | 'bottom-center'
    | 'bottom-right';
  opacity?: number;
  scale?: number;
  padding?: number;
  offset_x?: number;
  offset_y?: number;
};

export type EventAddonSummary = {
  id: number;
  key: string;
  label?: string | null;
  status: 'pending' | 'completed' | 'failed';
  extra_photos: number;
  extra_guests: number;
  extra_gallery_days: number;
  purchased_at: string | null;
};

export type HelpCenterArticleSummary = {
  slug: string;
  title: string;
  summary: string;
  updated_at?: string;
  status?: string;
  translation_state?: string;
  related?: Array<{ slug: string }>;
};

export type HelpCenterArticle = HelpCenterArticleSummary & {
  body_html?: string;
  body_markdown?: string;
  owner?: string;
  requires_app_version?: string | null;
  version_introduced?: string;
};

export type PaginationMeta = {
  current_page: number;
  last_page: number;
  per_page: number;
  total: number;
};

export type PaginatedResult<T> = {
  data: T[];
  meta: PaginationMeta;
};

export type DashboardSummary = {
    active_events: number;
    new_photos: number;
    task_progress: number;
    credit_balance?: number | null;
    upcoming_events?: number | null;
    active_package?: {
        name: string;
        expires_at?: string | null;
        remaining_events?: number | null;
    } | null;
    engagement_totals?: {
        tasks?: number;
        collections?: number;
        emotions?: number;
    };
};

export type TenantOnboardingStatus = {
  steps: {
    admin_app_opened_at?: string | null;
    primary_event_id?: number | string | null;
    selected_packages?: unknown;
    summary_seen_package_id?: number | null;
    summary_seen_at?: string | null;
    dismissed_at?: string | null;
    completed_at?: string | null;
    branding_completed?: boolean;
    tasks_configured?: boolean;
    event_created?: boolean;
    invite_created?: boolean;
  };
};

export async function trackOnboarding(step: string, meta?: Record<string, unknown>): Promise<void> {
  try {
    await authorizedFetch('/api/v1/tenant/onboarding', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ step, meta }),
    });
  } catch (error) {
    const message = i18n.t('common.errors.generic', 'Etwas ist schiefgelaufen.');
    emitApiErrorEvent({ message, code: 'onboarding.track_failed' });
    console.error('[Onboarding] Failed to track tenant onboarding step', error);
  }
}

export async function fetchOnboardingStatus(): Promise<TenantOnboardingStatus | null> {
  try {
    const response = await authorizedFetch('/api/v1/tenant/onboarding');
    return (await response.json()) as TenantOnboardingStatus;
  } catch (error) {
    const message = i18n.t('common.errors.generic', 'Etwas ist schiefgelaufen.');
    emitApiErrorEvent({ message, code: 'onboarding.fetch_failed' });
    console.error('[Onboarding] Failed to fetch tenant onboarding status', error);
    return null;
  }
}

function resolveHelpLocale(locale?: string): 'de' | 'en' {
  if (!locale) {
    return 'de';
  }
  const normalized = locale.toLowerCase().split('-')[0];
  return normalized === 'en' ? 'en' : 'de';
}

export async function fetchHelpCenterArticles(locale?: string): Promise<HelpCenterArticleSummary[]> {
  const resolvedLocale = resolveHelpLocale(locale);

  try {
    const params = new URLSearchParams({ audience: 'admin', locale: resolvedLocale });
    const response = await authorizedFetch(`/api/v1/help?${params.toString()}`);

    if (!response.ok) {
      throw new Error('Failed to fetch help articles');
    }

    const payload = (await response.json()) as { data?: HelpCenterArticleSummary[] };
    return Array.isArray(payload?.data) ? payload.data : [];
  } catch (error) {
    const message = i18n.t('common.errors.generic', 'Etwas ist schiefgelaufen.');
    emitApiErrorEvent({ message, code: 'help.fetch_list_failed' });
    console.error('[HelpApi] Failed to fetch help articles', error);
    throw error;
  }
}

export async function fetchHelpCenterArticle(slug: string, locale?: string): Promise<HelpCenterArticle> {
  const resolvedLocale = resolveHelpLocale(locale);

  try {
    const params = new URLSearchParams({ audience: 'admin', locale: resolvedLocale });
    const response = await authorizedFetch(`/api/v1/help/${encodeURIComponent(slug)}?${params.toString()}`);

    if (!response.ok) {
      throw new Error('Failed to fetch help article');
    }

    const payload = (await response.json()) as { data?: HelpCenterArticle };
    if (!payload?.data) {
      throw new Error('Empty help article response');
    }

    return payload.data;
  } catch (error) {
    const message = i18n.t('common.errors.generic', 'Etwas ist schiefgelaufen.');
    emitApiErrorEvent({ message, code: 'help.fetch_detail_failed' });
    console.error('[HelpApi] Failed to fetch help article', error);
    throw error;
  }
}

export type TenantPackageSummary = {
  id: number;
  package_id: number;
  package_name: string;
  package_type: string | null;
  included_package_slug: string | null;
  active: boolean;
  used_events: number;
  remaining_events: number | null;
  price: number | null;
  currency: string | null;
  purchased_at: string | null;
  expires_at: string | null;
  package_limits: Record<string, unknown> | null;
  branding_allowed?: boolean | null;
  watermark_allowed?: boolean | null;
  features?: string[] | null;
};

export type NotificationPreferences = Record<string, boolean>;

export type NotificationPreferencesMeta = Record<string, never>;

export type NotificationLogEntry = {
  id: number;
  type: string;
  channel: string;
  recipient: string | null;
  status: string;
  context: Record<string, unknown> | null;
  sent_at: string | null;
  failed_at: string | null;
  failure_reason: string | null;
  is_read?: boolean;
};

export type DataExportSummary = {
  id: number;
  scope: 'tenant' | 'event';
  status: 'pending' | 'processing' | 'ready' | 'failed';
  include_media: boolean;
  size_bytes: number | null;
  created_at: string | null;
  expires_at: string | null;
  download_url: string | null;
  error_message?: string | null;
  event?: {
    id: number;
    slug: string;
    name: string | Record<string, string>;
  } | null;
};

export type PaddleTransactionSummary = {
  id: string | null;
  status: string | null;
  amount: number | null;
  currency: string | null;
  origin: string | null;
  checkout_id: string | null;
  created_at: string | null;
  updated_at: string | null;
  receipt_url?: string | null;
  grand_total?: number | null;
  tax?: number | null;
};

export type TenantAddonEventSummary = {
  id: number;
  slug: string;
  name: string | Record<string, string> | null;
};

export type TenantAddonHistoryEntry = {
  id: number;
  addon_key: string;
  label?: string | null;
  event: TenantAddonEventSummary | null;
  amount: number | null;
  currency: string | null;
  status: 'pending' | 'completed' | 'failed';
  purchased_at: string | null;
  extra_photos: number;
  extra_guests: number;
  extra_gallery_days: number;
  quantity: number;
  receipt_url?: string | null;
};

export type CreditLedgerEntry = {
  id: number;
  delta: number;
  reason: string;
  note: string | null;
  related_purchase_id: number | null;
  created_at: string;
};

export type TenantTask = {
  id: number;
  slug: string;
  title: string;
  title_translations: Record<string, string>;
  description: string | null;
  description_translations: Record<string, string | null>;
  example_text: string | null;
  example_text_translations: Record<string, string>;
  priority: 'low' | 'medium' | 'high' | 'urgent' | null;
  difficulty: 'easy' | 'medium' | 'hard' | null;
  due_date: string | null;
  is_completed: boolean;
  event_type_id: number | null;
  event_type?: TenantEventType | null;
  emotion_id?: number | null;
  emotion?: {
    id: number;
    name: string;
    name_translations: Record<string, string>;
    icon: string | null;
    color: string | null;
  } | null;
  tenant_id: number | null;
  collection_id: number | null;
  source_task_id: number | null;
  source_collection_id: number | null;
  sort_order?: number | null;
  assigned_events_count: number;
  assigned_events?: TenantEvent[];
  created_at: string | null;
  updated_at: string | null;
};

export type TenantTaskCollection = {
  id: number;
  slug: string;
  name: string;
  name_translations: Record<string, string>;
  description: string | null;
  description_translations: Record<string, string | null>;
  tenant_id: number | null;
  is_global: boolean;
  is_mine?: boolean;
  event_type?: {
    id: number;
    slug: string;
    name: string;
    name_translations: Record<string, string>;
    icon: string | null;
  } | null;
  tasks_count: number;
  events_count?: number;
  imports_count?: number;
  position: number | null;
  source_collection_id: number | null;
  created_at: string | null;
  updated_at: string | null;
};

export type TenantEmotion = {
  id: number;
  name: string;
  name_translations: Record<string, string>;
  description: string | null;
  description_translations: Record<string, string | null>;
  icon: string;
  color: string;
  sort_order: number;
  is_active: boolean;
  is_global: boolean;
  tenant_id: number | null;
  event_types: Array<{
    id: number;
    slug: string;
    name: string;
    name_translations: Record<string, string>;
  }>;
  created_at: string | null;
  updated_at: string | null;
};

export type TaskPayload = Partial<{
  title: string;
  title_translations: Record<string, string>;
  description: string | null;
  description_translations: Record<string, string | null>;
  example_text: string | null;
  example_text_translations: Record<string, string | null>;
  collection_id: number | null;
  priority: 'low' | 'medium' | 'high' | 'urgent';
  due_date: string | null;
  is_completed: boolean;
  difficulty: 'easy' | 'medium' | 'hard';
}>; 

export type EmotionPayload = Partial<{
  name: string;
  description: string | null;
  icon: string;
  color: string;
  sort_order: number;
  is_active: boolean;
  event_type_ids: number[];
}>;

export type EventMember = {
  id: number;
  name: string;
  email: string | null;
  role: 'tenant_admin' | 'member';
  status?: 'pending' | 'active' | 'invited' | string;
  joined_at?: string | null;
  avatar_url?: string | null;
  permissions?: string[] | null;
  user_id?: number | null;
};

type EventListResponse = { data?: JsonValue[] };
type EventResponse = { data: JsonValue };

export type EventQrInvite = {
  id: number;
  token: string;
  url: string;
  label: string | null;
  qr_code_data_url: string | null;
  usage_limit: number | null;
  usage_count: number;
  expires_at: string | null;
  revoked_at: string | null;
  is_active: boolean;
  created_at: string | null;
  metadata: Record<string, unknown>;
  layouts: EventQrInviteLayout[];
  layouts_url: string | null;
};

export type EventToolkitTask = {
  id: number;
  title: string;
  description: string | null;
  is_completed: boolean;
  priority?: string | null;
};

export type EventToolkit = {
  event: TenantEvent;
  metrics: {
    uploads_total: number;
    uploads_24h: number;
    pending_photos: number;
    active_invites: number;
    engagement_mode: 'tasks' | 'photo_only';
  };
  tasks: {
    summary: {
      total: number;
      completed: number;
      pending: number;
    };
    items: EventToolkitTask[];
  };
  photos: {
    pending: TenantPhoto[];
    recent: TenantPhoto[];
  };
  invites: {
    summary: {
      total: number;
      active: number;
    };
    items: EventQrInvite[];
  };
  notifications?: {
    summary: {
      total: number;
      last_sent_at: string | null;
      by_type: Record<string, number>;
      broadcasts: {
        total: number;
        last_title: string | null;
        last_sent_at: string | null;
      };
    };
    recent: EventToolkitNotification[];
  };
  alerts: string[];
};

export type EventToolkitNotification = {
  id: number;
  title: string;
  type: string;
  status: string;
  audience_scope: string;
  created_at: string | null;
};
type CreatedEventResponse = { message: string; data: JsonValue; balance: number };
type PhotoResponse = { message: string; data: TenantPhoto };
type AdminPushSubscriptionPayload = {
  endpoint: string;
  keys: {
    p256dh: string;
    auth: string;
  };
  expirationTime?: number | null;
  contentEncoding?: string | null;
};

type EventSavePayload = {
  name: string;
  slug: string;
  event_type_id: number;
  event_date?: string;
  status?: 'draft' | 'published' | 'archived';
  is_active?: boolean;
  package_id?: number;
  service_package_slug?: string;
  accepted_waiver?: boolean;
  settings?: Record<string, unknown> & {
    live_show?: LiveShowSettings;
    watermark?: WatermarkSettings;
    watermark_serve_originals?: boolean | null;
  };
};

type JsonOrThrowOptions = {
  suppressToast?: boolean;
};

async function jsonOrThrow<T>(response: Response, message: string, options: JsonOrThrowOptions = {}): Promise<T> {
  if (!response.ok) {
    const body = await safeJson(response);
    const status = response.status;
    const errorPayload = body && typeof body === 'object' ? (body as Record<string, unknown>).error : null;
    const errorRecord = errorPayload && typeof errorPayload === 'object'
      ? (errorPayload as Record<string, unknown>)
      : null;
    const errorMessage = errorRecord && typeof errorRecord.message === 'string'
      ? errorRecord.message
      : message;
    const errorCode = errorRecord && typeof errorRecord.code === 'string'
      ? errorRecord.code
      : undefined;
    let errorMeta = errorRecord && typeof errorRecord.meta === 'object'
      ? (errorRecord.meta ?? null) as Record<string, unknown>
      : undefined;

    if (!errorMeta && body && typeof body === 'object' && 'errors' in body && typeof body.errors === 'object') {
      errorMeta = {
        errors: body.errors as Record<string, unknown>,
      };
    }

    if (!options.suppressToast) {
      emitApiErrorEvent({ message: errorMessage, status, code: errorCode, meta: errorMeta });
    }

    console.error('[API]', errorMessage, status, body);
    throw new ApiError(errorMessage, status, errorCode, errorMeta);
  }

  return (await response.json()) as T;
}

async function safeJson(response: Response): Promise<JsonValue | null> {
  try {
    return (await response.clone().json()) as JsonValue;
  } catch {
    return null;
  }
}

function buildPagination(payload: JsonValue | null, defaultCount: number): PaginationMeta {
  const meta = (payload?.meta as Partial<PaginationMeta>) ?? {};
  return {
    current_page: Number(meta.current_page ?? payload?.current_page ?? 1),
    last_page: Number(meta.last_page ?? payload?.last_page ?? 1),
    per_page: Number(meta.per_page ?? payload?.per_page ?? defaultCount ?? 0) || defaultCount || 0,
    total: Number(meta.total ?? payload?.total ?? defaultCount ?? 0) || defaultCount || 0,
  };
}

function translationLocales(): string[] {
  const locale = i18n.language;
  const base = locale?.includes('-') ? locale.split('-')[0] : locale;
  const fallback = ['de', 'en'];
  return [locale, base, ...fallback].filter(
    (value, index, self): value is string => Boolean(value) && self.indexOf(value) === index
  );
}

function normalizeTranslationMap(value: unknown, fallback?: string, allowEmpty = false): Record<string, string> {
  if (typeof value === 'string') {
    const map: Record<string, string> = {};
    for (const locale of translationLocales()) {
      map[locale] = value;
    }
    return map;
  }

  if (value && typeof value === 'object' && !Array.isArray(value)) {
    const entries: Record<string, string> = {};
    for (const [key, entry] of Object.entries(value as Record<string, unknown>)) {
      if (typeof entry === 'string') {
        entries[key] = entry;
      }
    }

    if (Object.keys(entries).length > 0) {
      return entries;
    }
  }

  if (fallback) {
    const locales = translationLocales();
    return locales.reduce<Record<string, string>>((acc, locale) => {
      acc[locale] = fallback;
      return acc;
    }, {});
  }

  return allowEmpty ? {} : {};
}

function pickTranslatedText(translations: Record<string, string>, fallback: string): string {
  const locales = translationLocales();
  for (const locale of locales) {
    if (translations[locale]) {
      return translations[locale]!;
    }
  }
  const first = Object.values(translations)[0];
  if (first) {
    return first;
  }
  return fallback;
}

function normalizeEventType(raw: JsonValue | TenantEventType | null): TenantEventType | null {
  if (!raw) {
    return null;
  }

  const translations = normalizeTranslationMap((raw as JsonValue).name ?? {}, undefined, true);
  const fallback = typeof (raw as JsonValue).name === 'string' ? (raw as JsonValue).name : 'Event';

  return {
    id: Number((raw as JsonValue).id ?? 0),
    slug: String((raw as JsonValue).slug ?? ''),
    name: pickTranslatedText(translations, fallback ?? 'Event'),
    name_translations: translations,
    icon: ((raw as JsonValue).icon ?? null) as string | null,
    settings: ((raw as JsonValue).settings ?? {}) as Record<string, unknown>,
    created_at: (raw as JsonValue).created_at ?? null,
    updated_at: (raw as JsonValue).updated_at ?? null,
  };
}

function normalizeEvent(event: JsonValue): TenantEvent {
  const normalizedType = normalizeEventType(event.event_type ?? event.eventType ?? null);
  const settings = ((event.settings ?? {}) as Record<string, unknown>) ?? {};
  const engagementMode = (settings.engagement_mode ?? event.engagement_mode ?? 'tasks') as
    | 'tasks'
    | 'photo_only';
  const normalized: TenantEvent = {
    ...(event as Record<string, unknown>),
    id: Number(event.id ?? 0),
    name: event.name ?? '',
    slug: String(event.slug ?? ''),
    event_date: typeof event.event_date === 'string'
      ? event.event_date
      : (typeof event.date === 'string' ? event.date : null),
    event_type_id: event.event_type_id !== undefined && event.event_type_id !== null
      ? Number(event.event_type_id)
      : null,
    event_type: normalizedType,
    status: (event.status ?? 'draft') as TenantEvent['status'],
    is_active: typeof event.is_active === 'boolean' ? event.is_active : undefined,
    description: event.description ?? null,
    photo_count: event.photo_count !== undefined ? Number(event.photo_count ?? 0) : undefined,
    pending_photo_count: event.pending_photo_count !== undefined ? Number(event.pending_photo_count ?? 0) : undefined,
    like_count:
      event.like_count !== undefined
        ? Number(event.like_count ?? 0)
        : event.likes_sum !== undefined
          ? Number(event.likes_sum ?? 0)
          : undefined,
    tasks_count: event.tasks_count !== undefined ? Number(event.tasks_count ?? 0) : undefined,
    active_invites_count:
      event.active_invites_count !== undefined
        ? Number(event.active_invites_count ?? 0)
        : event.active_join_tokens_count !== undefined
          ? Number(event.active_join_tokens_count ?? 0)
          : undefined,
    total_invites_count:
      event.total_invites_count !== undefined
        ? Number(event.total_invites_count ?? 0)
        : event.total_join_tokens_count !== undefined
          ? Number(event.total_join_tokens_count ?? 0)
          : undefined,
    engagement_mode: engagementMode,
    settings,
    package: event.package ?? null,
    limits: (event.limits ?? null) as EventLimitSummary | null,
    member_permissions: Array.isArray(event.member_permissions)
      ? (event.member_permissions as string[])
      : event.member_permissions
        ? String(event.member_permissions).split(',').map((entry) => entry.trim())
        : null,
  };

  return normalized;
}

function normalizePhoto(photo: TenantPhoto): TenantPhoto {
  return {
    id: photo.id,
    filename: photo.filename,
    original_name: photo.original_name ?? null,
    mime_type: photo.mime_type ?? null,
    size: Number(photo.size ?? 0),
    url: photo.url,
    thumbnail_url: photo.thumbnail_url ?? photo.url,
    status: photo.status ?? 'approved',
    live_status: photo.live_status ?? null,
    live_approved_at: photo.live_approved_at ?? null,
    live_reviewed_at: photo.live_reviewed_at ?? null,
    live_rejection_reason: photo.live_rejection_reason ?? null,
    live_priority: typeof photo.live_priority === 'number' ? photo.live_priority : null,
    is_featured: Boolean(photo.is_featured),
    likes_count: Number(photo.likes_count ?? 0),
    uploaded_at: photo.uploaded_at,
    uploader_name: photo.uploader_name ?? null,
    caption: photo.caption ?? null,
  };
}

function normalizeDashboard(payload: JsonValue | null): DashboardSummary | null {
  if (!payload) {
    return null;
  }

  return {
    active_events: Number(payload.active_events ?? payload.activeEvents ?? 0),
    new_photos: Number(payload.new_photos ?? payload.newPhotos ?? 0),
    task_progress: Number(payload.task_progress ?? payload.taskProgress ?? 0),
    credit_balance: payload.credit_balance ?? payload.creditBalance ?? null,
    upcoming_events: payload.upcoming_events ?? payload.upcomingEvents ?? null,
    active_package: payload.active_package
      ? {
          name: String(payload.active_package.name ?? 'Aktives Package'),
          expires_at: payload.active_package.expires_at ?? null,
          remaining_events: payload.active_package.remaining_events ?? payload.active_package.remainingEvents ?? null,
      }
      : null,
    engagement_totals: {
      tasks:
        Number(
          payload.tasks?.summary?.total ??
            payload.tasks_total ??
            payload.engagement?.tasks ??
            0,
        ),
      collections:
        Number(
          payload.task_collections?.summary?.total ??
            payload.collections_total ??
            payload.engagement?.collections ??
            0,
        ),
      emotions:
        Number(
          payload.emotions?.summary?.total ??
            payload.emotions_total ??
            payload.engagement?.emotions ??
            0,
        ),
    },
  };
}

export function normalizeTenantPackage(pkg: JsonValue): TenantPackageSummary {
  const packageData = pkg.package ?? {};
  return {
    id: Number(pkg.id ?? 0),
    package_id: Number(pkg.package_id ?? packageData.id ?? 0),
    package_name: String(packageData.name ?? pkg.package_name ?? 'Unbekanntes Package'),
    package_type:
      typeof (packageData as any).type === 'string'
        ? String((packageData as any).type)
        : typeof (pkg as any).package_type === 'string'
          ? String((pkg as any).package_type)
          : null,
    included_package_slug:
      typeof (packageData as any).included_package_slug === 'string'
        ? String((packageData as any).included_package_slug)
        : typeof (pkg as any).included_package_slug === 'string'
          ? String((pkg as any).included_package_slug)
          : null,
    active: Boolean(pkg.active ?? false),
    used_events: Number(pkg.used_events ?? 0),
    remaining_events:
      pkg.remaining_events === undefined || pkg.remaining_events === null ? null : Number(pkg.remaining_events),
    price: packageData.price !== undefined ? Number(packageData.price) : pkg.price ?? null,
    currency: packageData.currency ?? pkg.currency ?? 'EUR',
    purchased_at: pkg.purchased_at ?? pkg.created_at ?? null,
    expires_at: pkg.expires_at ?? pkg.valid_until ?? null,
    package_limits: pkg.package_limits ?? packageData.limits ?? null,
    branding_allowed: pkg.branding_allowed ?? packageData.branding_allowed ?? null,
    watermark_allowed: pkg.watermark_allowed ?? packageData.watermark_allowed ?? null,
    features: pkg.features ?? packageData.features ?? null,
  };
}

function normalizePaddleTransaction(entry: JsonValue): PaddleTransactionSummary {
  const amountValue = entry.amount ?? entry.grand_total ?? (entry.totals && entry.totals.grand_total);
  const taxValue = entry.tax ?? (entry.totals && entry.totals.tax_total);

  return {
    id: typeof entry.id === 'string' ? entry.id : entry.id ? String(entry.id) : null,
    status: entry.status ?? null,
    amount: amountValue !== undefined && amountValue !== null ? Number(amountValue) : null,
    currency: entry.currency ?? entry.currency_code ?? 'EUR',
    origin: entry.origin ?? null,
    checkout_id: entry.checkout_id ?? (entry.details?.checkout_id ?? null),
    created_at: entry.created_at ?? null,
    updated_at: entry.updated_at ?? null,
    receipt_url: entry.receipt_url ?? entry.invoice_url ?? null,
    grand_total: entry.grand_total !== undefined && entry.grand_total !== null ? Number(entry.grand_total) : null,
    tax: taxValue !== undefined && taxValue !== null ? Number(taxValue) : null,
  };
}

function normalizeTenantAddonHistoryEntry(entry: JsonValue): TenantAddonHistoryEntry {
  let event: TenantAddonEventSummary | null = null;

  if (entry.event && typeof entry.event === 'object') {
    const rawEvent = entry.event as JsonValue;
    const id = Number((rawEvent as { id?: unknown }).id ?? 0);
    const slugValue = (rawEvent as { slug?: unknown }).slug;
    const rawName = (rawEvent as { name?: unknown }).name ?? null;
    let name: TenantAddonEventSummary['name'] = null;

    if (typeof rawName === 'string') {
      name = rawName;
    } else if (rawName && typeof rawName === 'object') {
      name = normalizeTranslationMap(rawName, undefined, true);
    }

    event = {
      id,
      slug: typeof slugValue === 'string' ? slugValue : '',
      name,
    };
  }

  const amountValue = entry.amount;

  return {
    id: Number(entry.id ?? 0),
    addon_key: String(entry.addon_key ?? ''),
    label: typeof entry.label === 'string' ? entry.label : null,
    event,
    amount: amountValue !== undefined && amountValue !== null ? Number(amountValue) : null,
    currency: typeof entry.currency === 'string' ? entry.currency : null,
    status: (entry.status as TenantAddonHistoryEntry['status']) ?? 'pending',
    purchased_at: typeof entry.purchased_at === 'string' ? entry.purchased_at : null,
    extra_photos: Number(entry.extra_photos ?? 0),
    extra_guests: Number(entry.extra_guests ?? 0),
    extra_gallery_days: Number(entry.extra_gallery_days ?? 0),
    quantity: Number(entry.quantity ?? 1),
    receipt_url: typeof entry.receipt_url === 'string' ? entry.receipt_url : null,
  };
}

function normalizeTask(task: JsonValue): TenantTask {
  const titleTranslations = normalizeTranslationMap(task.title_translations ?? task.title ?? {});
  const descriptionTranslations = normalizeTranslationMap(task.description_translations ?? task.description ?? {});
  const exampleTranslations = normalizeTranslationMap(task.example_text ?? {});
  const eventType = normalizeEventType(task.event_type ?? task.eventType ?? null);
  const eventTypeId =
    typeof task.event_type_id === 'number'
      ? Number(task.event_type_id)
      : eventType?.id ?? null;
  const emotionRaw = task.emotion ?? null;
  const rawId =
    task.id ??
    (task as { task_id?: unknown }).task_id ??
    ((task as { pivot?: { task_id?: unknown } }).pivot?.task_id ?? null);

  return {
    id: Number(rawId ?? 0),
    slug: String(task.slug ?? `task-${rawId ?? task.id ?? ''}`),
    title: pickTranslatedText(titleTranslations, 'Ohne Titel'),
    title_translations: titleTranslations,
    description: Object.keys(descriptionTranslations).length
      ? pickTranslatedText(descriptionTranslations, '')
      : null,
    description_translations: Object.keys(descriptionTranslations).length ? descriptionTranslations : {},
    example_text: Object.keys(exampleTranslations).length ? pickTranslatedText(exampleTranslations, '') : null,
    example_text_translations: Object.keys(exampleTranslations).length ? exampleTranslations : {},
    priority: (task.priority ?? null) as TenantTask['priority'],
    difficulty: (task.difficulty ?? null) as TenantTask['difficulty'],
    due_date: task.due_date ?? null,
    is_completed: Boolean(task.is_completed ?? false),
    event_type_id: eventTypeId,
    event_type: eventType,
    sort_order:
      typeof task.sort_order === 'number'
        ? Number(task.sort_order)
        : task.pivot && typeof (task.pivot as { sort_order?: unknown }).sort_order === 'number'
          ? Number((task.pivot as { sort_order?: number }).sort_order)
          : null,
    emotion_id: typeof task.emotion_id === 'number' ? Number(task.emotion_id) : null,
    emotion: emotionRaw
      ? {
          id: Number(emotionRaw.id ?? 0),
          name: pickTranslatedText(
            normalizeTranslationMap(emotionRaw.name_translations ?? emotionRaw.name ?? {}),
            String(emotionRaw.name ?? '')
          ),
          name_translations: normalizeTranslationMap(emotionRaw.name_translations ?? emotionRaw.name ?? {}),
          icon: emotionRaw.icon ?? null,
          color: emotionRaw.color ?? null,
        }
      : null,
    tenant_id: task.tenant_id ?? null,
    collection_id: task.collection_id ?? null,
    source_task_id: task.source_task_id ?? null,
    source_collection_id: task.source_collection_id ?? null,
    assigned_events_count: Number(task.assigned_events_count ?? 0),
    assigned_events: Array.isArray(task.assigned_events) ? task.assigned_events.map(normalizeEvent) : undefined,
    created_at: task.created_at ?? null,
    updated_at: task.updated_at ?? null,
  };
}

function normalizeTaskCollection(raw: JsonValue): TenantTaskCollection {
  const nameTranslations = normalizeTranslationMap(raw.name_translations ?? raw.name ?? {});
  const descriptionTranslations = normalizeTranslationMap(
    raw.description_translations ?? raw.description ?? {},
    undefined,
    true
  );

  const eventTypeRaw = raw.event_type ?? raw.eventType ?? null;
  let eventType: TenantTaskCollection['event_type'] = null;
  if (eventTypeRaw && typeof eventTypeRaw === 'object') {
    const eventNameTranslations = normalizeTranslationMap(eventTypeRaw.name ?? {});
    eventType = {
      id: Number(eventTypeRaw.id ?? 0),
      slug: String(eventTypeRaw.slug ?? ''),
      name: pickTranslatedText(eventNameTranslations, String(eventTypeRaw.slug ?? '')),
      name_translations: eventNameTranslations,
      icon: eventTypeRaw.icon ?? null,
    };
  }

  return {
    id: Number(raw.id ?? 0),
    slug: String(raw.slug ?? `collection-${raw.id ?? ''}`),
    name: pickTranslatedText(nameTranslations, 'Unbenannte Sammlung'),
    name_translations: nameTranslations,
    description: descriptionTranslations ? pickTranslatedText(descriptionTranslations, '') : null,
    description_translations: descriptionTranslations ?? {},
    tenant_id: raw.tenant_id ?? null,
    is_global: !raw.tenant_id,
    is_mine: Boolean(raw.tenant_id),
    event_type: eventType,
    tasks_count: Number(raw.tasks_count ?? raw.tasksCount ?? 0),
    events_count: raw.events_count !== undefined ? Number(raw.events_count) : undefined,
    imports_count: raw.imports_count !== undefined ? Number(raw.imports_count) : undefined,
    position: raw.position !== undefined ? Number(raw.position) : null,
    source_collection_id: raw.source_collection_id ?? null,
    created_at: raw.created_at ?? null,
    updated_at: raw.updated_at ?? null,
  };
}

function normalizeEmotion(raw: JsonValue): TenantEmotion {
  const nameTranslations = normalizeTranslationMap(raw.name_translations ?? raw.name ?? {}, undefined, true);
  const descriptionTranslations = normalizeTranslationMap(
    raw.description_translations ?? raw.description ?? {},
    undefined,
    true
  );

  const eventTypes = Array.isArray(raw.event_types ?? raw.eventTypes)
    ? (raw.event_types ?? raw.eventTypes)
    : [];

  return {
    id: Number(raw.id ?? 0),
    name: pickTranslatedText(nameTranslations, 'Emotion'),
    name_translations: nameTranslations,
    description: descriptionTranslations ? pickTranslatedText(descriptionTranslations, '') : null,
    description_translations: descriptionTranslations ?? {},
    icon: typeof raw.icon === 'string' ? raw.icon : 'lucide-smile',
    color: String(raw.color ?? '#6366f1'),
    sort_order: Number(raw.sort_order ?? 0),
    is_active: Boolean(raw.is_active ?? true),
    is_global: raw.tenant_id === null || raw.tenant_id === undefined,
    tenant_id: raw.tenant_id ?? null,
    event_types: (eventTypes as JsonValue[]).map((eventType) => {
      const translations = normalizeTranslationMap(eventType.name ?? {});
      return {
        id: Number(eventType.id ?? 0),
        slug: String(eventType.slug ?? ''),
        name: pickTranslatedText(translations, String(eventType.slug ?? '')),
        name_translations: translations,
      };
    }),
    created_at: raw.created_at ?? null,
    updated_at: raw.updated_at ?? null,
  };
}

function normalizeMember(member: JsonValue): EventMember {
  return {
    id: Number(member.id ?? 0),
    name: String(member.name ?? member.email ?? 'Unbekannt'),
    email: member.email ?? null,
    role: (member.role ?? 'member') as EventMember['role'],
    status: member.status ?? 'active',
    joined_at: member.joined_at ?? member.created_at ?? null,
    avatar_url: member.avatar_url ?? member.avatar ?? null,
    permissions: Array.isArray(member.permissions)
      ? (member.permissions as string[])
      : member.permissions
        ? String(member.permissions).split(',').map((entry) => entry.trim())
        : null,
    user_id: member.user_id ?? null,
  };
}

function normalizeQrInvite(raw: JsonValue): EventQrInvite {
  const rawLayouts = Array.isArray(raw.layouts) ? (raw.layouts as JsonValue[]) : [];
  const layouts: EventQrInviteLayout[] = rawLayouts
    .map((layout: JsonValue) => {
      const formats = Array.isArray(layout.formats)
        ? layout.formats.map((format: unknown) => String(format ?? '')).filter((format: string) => format.length > 0)
        : [];

      return {
        id: String(layout.id ?? ''),
        name: String(layout.name ?? ''),
        description: String(layout.description ?? ''),
        subtitle: String(layout.subtitle ?? ''),
        paper: layout.paper ?? null,
        orientation: layout.orientation ?? null,
        panel_mode: layout.panel_mode ?? null,
        badge_label: layout.badge_label ?? null,
        instructions_heading: layout.instructions_heading ?? null,
        link_heading: layout.link_heading ?? null,
        cta_label: layout.cta_label ?? null,
        cta_caption: layout.cta_caption ?? null,
        instructions: Array.isArray(layout.instructions) ? (layout.instructions as string[]) : [],
        preview: {
          background: layout.preview?.background ?? null,
          background_gradient: layout.preview?.background_gradient ?? null,
          accent: layout.preview?.accent ?? null,
          text: layout.preview?.text ?? null,
          qr_size_px: layout.preview?.qr_size_px ?? layout.qr?.size_px ?? null,
        },
        formats,
        download_urls: (layout.download_urls ?? {}) as Record<string, string>,
      };
    })
    .filter((layout: EventQrInviteLayout) => layout.id.length > 0);

  return {
    id: Number(raw.id ?? 0),
    token: String(raw.token ?? ''),
    url: String(raw.url ?? ''),
    label: raw.label ?? null,
    usage_limit: raw.usage_limit ?? null,
    usage_count: Number(raw.usage_count ?? 0),
    expires_at: raw.expires_at ?? null,
    revoked_at: raw.revoked_at ?? null,
    is_active: Boolean(raw.is_active),
    created_at: raw.created_at ?? null,
    metadata: (raw.metadata ?? {}) as Record<string, unknown>,
    layouts,
    layouts_url: typeof raw.layouts_url === 'string' ? raw.layouts_url : null,
    qr_code_data_url:
      typeof raw.qr_code_data_url === 'string' && raw.qr_code_data_url.length > 0
        ? String(raw.qr_code_data_url)
        : null,
  };
}

function toEngagementNumber(value: unknown, fallback = 0): number {
  if (typeof value === 'number' && Number.isFinite(value)) {
    return value;
  }
  if (typeof value === 'string' && value !== '') {
    const parsed = Number(value);
    return Number.isFinite(parsed) ? parsed : fallback;
  }
  return fallback;
}

function toEngagementString(value: unknown): string {
  return typeof value === 'string' ? value : '';
}

function normalizeEventEngagement(payload: JsonValue): EventEngagement {
  const record = payload && typeof payload === 'object' ? (payload as Record<string, unknown>) : {};
  const summary = record.summary && typeof record.summary === 'object' ? (record.summary as Record<string, unknown>) : {};
  const leaderboards = record.leaderboards && typeof record.leaderboards === 'object'
    ? (record.leaderboards as Record<string, unknown>)
    : {};
  const highlights = record.highlights && typeof record.highlights === 'object'
    ? (record.highlights as Record<string, unknown>)
    : {};
  const feedRaw = Array.isArray(record.feed) ? record.feed : [];

  const uploadsBoard = Array.isArray(leaderboards.uploads)
    ? (leaderboards.uploads as Record<string, unknown>[]).map((row) => ({
        guest: toEngagementString(row.guest),
        photos: toEngagementNumber(row.photos),
        likes: toEngagementNumber(row.likes),
      }))
    : [];

  const likesBoard = Array.isArray(leaderboards.likes)
    ? (leaderboards.likes as Record<string, unknown>[]).map((row) => ({
        guest: toEngagementString(row.guest),
        photos: toEngagementNumber(row.photos),
        likes: toEngagementNumber(row.likes),
      }))
    : [];

  const topPhotoRaw = highlights.top_photo && typeof highlights.top_photo === 'object'
    ? (highlights.top_photo as Record<string, unknown>)
    : null;
  const topPhoto = topPhotoRaw
    ? {
        photoId: toEngagementNumber(topPhotoRaw.photo_id),
        guest: toEngagementString(topPhotoRaw.guest),
        likes: toEngagementNumber(topPhotoRaw.likes),
        task: (topPhotoRaw as { task?: string | null }).task ?? null,
        createdAt: toEngagementString(topPhotoRaw.created_at),
        thumbnail: topPhotoRaw.thumbnail ? toEngagementString(topPhotoRaw.thumbnail) : null,
      }
    : null;

  const trendingRaw = highlights.trending_emotion && typeof highlights.trending_emotion === 'object'
    ? (highlights.trending_emotion as Record<string, unknown>)
    : null;
  const trendingEmotion = trendingRaw
    ? {
        emotionId: toEngagementNumber(trendingRaw.emotion_id),
        name: toEngagementString(trendingRaw.name),
        count: toEngagementNumber(trendingRaw.count),
      }
    : null;

  const timeline = Array.isArray(highlights.timeline)
    ? (highlights.timeline as Record<string, unknown>[]).map((row) => ({
        date: toEngagementString(row.date),
        photos: toEngagementNumber(row.photos),
        guests: toEngagementNumber(row.guests),
      }))
    : [];

  const feed = feedRaw.map((row) => {
    const entry = row as Record<string, unknown>;
    return {
      photoId: toEngagementNumber(entry.photo_id),
      guest: toEngagementString(entry.guest),
      task: (entry as { task?: string | null }).task ?? null,
      likes: toEngagementNumber(entry.likes),
      createdAt: toEngagementString(entry.created_at),
      thumbnail: entry.thumbnail ? toEngagementString(entry.thumbnail) : null,
    };
  });

  return {
    summary: {
      totalPhotos: toEngagementNumber(summary.total_photos),
      uniqueGuests: toEngagementNumber(summary.unique_guests),
      tasksSolved: toEngagementNumber(summary.tasks_solved),
      likesTotal: toEngagementNumber(summary.likes_total),
    },
    leaderboards: {
      uploads: uploadsBoard,
      likes: likesBoard,
    },
    highlights: {
      topPhoto,
      trendingEmotion,
      timeline,
    },
    feed,
  };
}

function normalizeGuestNotification(raw: JsonValue): GuestNotificationSummary | null {
  if (!raw || typeof raw !== 'object') {
    return null;
  }

  const record = raw as Record<string, JsonValue>;
  const status = typeof record.status === 'string' ? record.status : null;
  const audience = typeof record.audience_scope === 'string' ? record.audience_scope : null;

  return {
    id: Number(record.id ?? 0),
    type: typeof record.type === 'string' ? record.type : 'broadcast',
    title: typeof record.title === 'string' ? record.title : '',
    body: typeof record.body === 'string' ? record.body : null,
    status: status === 'draft' || status === 'archived' || status === 'active' ? status : 'active',
    audience_scope: audience === 'guest' || audience === 'all' ? audience : 'all',
    target_identifier: typeof record.target_identifier === 'string' ? record.target_identifier : null,
    payload: (record.payload as Record<string, unknown>) ?? null,
    priority: Number(record.priority ?? 0),
    created_at: typeof record.created_at === 'string' ? record.created_at : null,
    expires_at: typeof record.expires_at === 'string' ? record.expires_at : null,
  };
}

function eventEndpoint(slug: string): string {
  return `/api/v1/tenant/events/${encodeURIComponent(slug)}`;
}

function guestNotificationsEndpoint(slug: string): string {
  return `${eventEndpoint(slug)}/guest-notifications`;
}

function photoboothEndpoint(slug: string): string {
  return `${eventEndpoint(slug)}/photobooth`;
}

function normalizePhotoboothStatus(payload: JsonValue): PhotoboothStatus {
  const ftp = (payload.ftp ?? {}) as JsonValue;
  const metricsPayload = ((payload.metrics ?? payload.stats) ?? null) as JsonValue | null;
  let metrics: PhotoboothStatusMetrics | null = null;

  if (metricsPayload && typeof metricsPayload === 'object') {
    const record = metricsPayload as Record<string, JsonValue>;
    const readNumber = (key: string): number | null => {
      const value = record[key];
      if (value === null || value === undefined) {
        return null;
      }
      const parsed = Number(value);
      return Number.isFinite(parsed) ? parsed : null;
    };

    metrics = {
      uploads_last_hour: readNumber('uploads_last_hour') ?? readNumber('last_hour') ?? readNumber('hour'),
      uploads_today: readNumber('uploads_today') ?? readNumber('today'),
      uploads_total: readNumber('uploads_total') ?? readNumber('total'),
      uploads_24h: readNumber('uploads_24h') ?? readNumber('uploads_today') ?? readNumber('today'),
      last_upload_at: typeof record.last_upload_at === 'string' ? record.last_upload_at : null,
    };
  }

  const sparkRaw = (payload.sparkbooth ?? null) as JsonValue | null;
  let sparkbooth: SparkboothStatus | null = null;

  if (sparkRaw && typeof sparkRaw === 'object') {
    sparkbooth = {
      enabled: Boolean((sparkRaw as JsonValue).enabled),
      status: typeof (sparkRaw as JsonValue).status === 'string' ? (sparkRaw as JsonValue).status : null,
      username: typeof (sparkRaw as JsonValue).username === 'string' ? (sparkRaw as JsonValue).username : null,
      password: typeof (sparkRaw as JsonValue).password === 'string' ? (sparkRaw as JsonValue).password : null,
      expires_at: typeof (sparkRaw as JsonValue).expires_at === 'string' ? (sparkRaw as JsonValue).expires_at : null,
      upload_url: typeof (sparkRaw as JsonValue).upload_url === 'string' ? (sparkRaw as JsonValue).upload_url : null,
      response_format:
        (sparkRaw as JsonValue).response_format === 'xml' ? 'xml' : 'json',
      metrics: normalizePhotoboothMetrics((sparkRaw as JsonValue).metrics),
    };
  }

  const modeValue = typeof payload.mode === 'string' && payload.mode === 'sparkbooth' ? 'sparkbooth' : 'ftp';

  return {
    mode: modeValue,
    enabled: Boolean(payload.enabled),
    status: typeof payload.status === 'string' ? payload.status : null,
    username: typeof payload.username === 'string' ? payload.username : null,
    password: typeof payload.password === 'string' ? payload.password : null,
    path: typeof payload.path === 'string' ? payload.path : null,
    ftp_url: typeof payload.ftp_url === 'string' ? payload.ftp_url : null,
    upload_url: typeof payload.upload_url === 'string' ? payload.upload_url : null,
    expires_at: typeof payload.expires_at === 'string' ? payload.expires_at : null,
    rate_limit_per_minute: Number(payload.rate_limit_per_minute ?? ftp.rate_limit_per_minute ?? 0),
    ftp: {
      host: typeof ftp.host === 'string' ? ftp.host : null,
      port: Number(ftp.port ?? payload.ftp_port ?? 0) || 0,
      require_ftps: Boolean(ftp.require_ftps ?? payload.require_ftps),
    },
    sparkbooth,
    metrics,
  };
}

function normalizePhotoboothMetrics(raw: JsonValue | null | undefined): PhotoboothStatusMetrics | null {
  if (!raw || typeof raw !== 'object') {
    return null;
  }

  const record = raw as Record<string, JsonValue>;
  const readNumber = (key: string): number | null => {
    const value = record[key];
    if (value === null || value === undefined) {
      return null;
    }
    const parsed = Number(value);
    return Number.isFinite(parsed) ? parsed : null;
  };

  return {
    uploads_last_hour: readNumber('uploads_last_hour') ?? readNumber('last_hour') ?? readNumber('hour'),
    uploads_today: readNumber('uploads_today') ?? readNumber('today'),
    uploads_total: readNumber('uploads_total') ?? readNumber('total'),
    last_upload_at: typeof record.last_upload_at === 'string' ? record.last_upload_at : null,
  };
}

async function requestPhotoboothStatus(slug: string, path = '', init: RequestInit = {}, errorMessage = 'Failed to fetch photobooth status'): Promise<PhotoboothStatus> {
  const response = await authorizedFetch(`${photoboothEndpoint(slug)}${path}`, init);
  const payload = await jsonOrThrow<JsonValue | { data: JsonValue }>(response, errorMessage);
  const body = (payload as { data?: JsonValue }).data ?? (payload as JsonValue);

  return normalizePhotoboothStatus(body ?? {});
}

export async function getEvents(options?: { force?: boolean }): Promise<TenantEvent[]> {
  return cachedFetch(
    CacheKeys.events,
    async () => {
      const response = await authorizedFetch('/api/v1/tenant/events');
      const data = await jsonOrThrow<EventListResponse>(response, 'Failed to load events');
      return (data.data ?? []).map(normalizeEvent);
    },
    DEFAULT_CACHE_TTL,
    options?.force === true,
  );
}

export async function createEvent(payload: EventSavePayload): Promise<{ event: TenantEvent; balance: number }> {
  const response = await authorizedFetch('/api/v1/tenant/events', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  });
  const data = await jsonOrThrow<CreatedEventResponse>(response, 'Failed to create event');
  const result = { event: normalizeEvent(data.data), balance: data.balance };
  invalidateTenantApiCache([CacheKeys.events, CacheKeys.dashboard]);
  return result;
}

export async function updateEvent(slug: string, payload: Partial<EventSavePayload>): Promise<TenantEvent> {
  const response = await authorizedFetch(eventEndpoint(slug), {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  });
  const data = await jsonOrThrow<EventResponse>(response, 'Failed to update event');
  const event = normalizeEvent(data.data);
  invalidateTenantApiCache([CacheKeys.events, CacheKeys.dashboard]);
  return event;
}

export async function getEvent(slug: string): Promise<TenantEvent> {
  const response = await authorizedFetch(eventEndpoint(slug));
  const data = await jsonOrThrow<EventResponse>(response, 'Failed to load event');
  return normalizeEvent(data.data);
}

export async function createEventAddonCheckout(
  eventSlug: string,
  params: { addon_key: string; quantity?: number; success_url?: string; cancel_url?: string }
): Promise<{ checkout_url: string | null; checkout_id: string | null; expires_at: string | null }> {
  const response = await authorizedFetch(`${eventEndpoint(eventSlug)}/addons/checkout`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(params),
  });

  return await jsonOrThrow<{ checkout_url: string | null; checkout_id: string | null; expires_at: string | null }>(
    response,
    'Failed to create addon checkout'
  );
}

export async function getAddonCatalog(): Promise<EventAddonCatalogItem[]> {
  const response = await authorizedFetch('/api/v1/tenant/addons/catalog');
  const data = await jsonOrThrow<{ data?: EventAddonCatalogItem[] }>(response, 'Failed to load add-ons');
  return data.data ?? [];
}

export async function getTenantFonts(): Promise<TenantFont[]> {
  return cachedFetch(
    CacheKeys.fonts,
    async () => {
      const response = await authorizedFetch('/api/v1/tenant/fonts');
      const data = await jsonOrThrow<{ data?: TenantFont[] }>(response, 'Failed to load fonts');
      return data.data ?? [];
    },
    6 * 60 * 60 * 1000,
  );
}

export async function getEventTypes(): Promise<TenantEventType[]> {
  const response = await authorizedFetch('/api/v1/tenant/event-types');
  const data = await jsonOrThrow<{ data?: JsonValue[] }>(response, 'Failed to load event types');
  const rows = Array.isArray(data.data) ? data.data : [];
  return rows
    .map((row) => normalizeEventType(row))
    .filter((row): row is TenantEventType => Boolean(row));
}

export type GetEventPhotosOptions = {
  page?: number;
  perPage?: number;
  sort?: 'asc' | 'desc';
  search?: string;
  status?: string;
  featured?: boolean;
  ingestSource?: string;
  visibility?: 'visible' | 'hidden' | 'all';
};

export type LiveShowQueueStatus = 'pending' | 'approved' | 'rejected' | 'none' | 'expired' | 'all';

export type GetLiveShowQueueOptions = {
  page?: number;
  perPage?: number;
  liveStatus?: LiveShowQueueStatus;
};

function normalizeLiveShowLink(payload: JsonValue | LiveShowLink | null | undefined): LiveShowLink {
  if (!payload || typeof payload !== 'object') {
    return {
      token: '',
      url: '',
      qr_code_data_url: null,
      rotated_at: null,
    };
  }

  const record = payload as Record<string, JsonValue>;

  return {
    token: typeof record.token === 'string' ? record.token : '',
    url: typeof record.url === 'string' ? record.url : '',
    qr_code_data_url: typeof record.qr_code_data_url === 'string' ? record.qr_code_data_url : null,
    rotated_at: typeof record.rotated_at === 'string' ? record.rotated_at : null,
  };
}

export async function getLiveShowLink(slug: string): Promise<LiveShowLink> {
  const response = await authorizedFetch(`${eventEndpoint(slug)}/live-show/link`);
  const data = await jsonOrThrow<{ data?: JsonValue }>(response, 'Failed to load live show link');

  return normalizeLiveShowLink(data.data);
}

export async function rotateLiveShowLink(slug: string): Promise<LiveShowLink> {
  const response = await authorizedFetch(`${eventEndpoint(slug)}/live-show/link/rotate`, {
    method: 'POST',
  });
  const data = await jsonOrThrow<{ data?: JsonValue }>(response, 'Failed to rotate live show link');

  return normalizeLiveShowLink(data.data);
}

export async function getEventPhotos(
  slug: string,
  options: GetEventPhotosOptions = {}
): Promise<{ photos: TenantPhoto[]; limits: EventLimitSummary | null; meta: PaginationMeta }> {
  const params = new URLSearchParams();
  if (options.page) params.set('page', String(options.page));
  if (options.perPage) params.set('per_page', String(options.perPage));
  if (options.sort) params.set('sort', options.sort);
  if (options.search) params.set('search', options.search);
  if (options.status) params.set('status', options.status);
  if (options.featured) params.set('featured', '1');
  if (options.ingestSource) params.set('ingest_source', options.ingestSource);
  if (options.visibility) params.set('visibility', options.visibility);

  const response = await authorizedFetch(`${eventEndpoint(slug)}/photos${params.toString() ? `?${params.toString()}` : ''}`);
  const data = await jsonOrThrow<{
    data?: TenantPhoto[];
    limits?: EventLimitSummary | null;
    meta?: Partial<PaginationMeta>;
    current_page?: number;
    last_page?: number;
    per_page?: number;
    total?: number;
  }>(
    response,
    'Failed to load photos'
  );

  const meta = buildPagination(data as unknown as JsonValue, options.perPage ?? 40);

  return {
    photos: (data.data ?? []).map(normalizePhoto),
    limits: (data.limits ?? null) as EventLimitSummary | null,
    meta,
  };
}

export async function getLiveShowQueue(
  slug: string,
  options: GetLiveShowQueueOptions = {}
): Promise<{ photos: TenantPhoto[]; meta: PaginationMeta }> {
  const params = new URLSearchParams();
  if (options.page) params.set('page', String(options.page));
  if (options.perPage) params.set('per_page', String(options.perPage));
  if (options.liveStatus) params.set('live_status', options.liveStatus);

  const response = await authorizedFetch(
    `${eventEndpoint(slug)}/live-show/photos${params.toString() ? `?${params.toString()}` : ''}`
  );
  const data = await jsonOrThrow<{
    data?: TenantPhoto[];
    meta?: Partial<PaginationMeta>;
    current_page?: number;
    last_page?: number;
    per_page?: number;
    total?: number;
  }>(response, 'Failed to load live show queue');

  const meta = buildPagination(data as unknown as JsonValue, options.perPage ?? 20);

  return {
    photos: (data.data ?? []).map(normalizePhoto),
    meta,
  };
}

export async function approveLiveShowPhoto(
  slug: string,
  id: number,
  payload: { priority?: number } = {}
): Promise<TenantPhoto> {
  const response = await authorizedFetch(`${eventEndpoint(slug)}/live-show/photos/${id}/approve`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  });
  const data = await jsonOrThrow<PhotoResponse>(response, 'Failed to approve live show photo');
  return normalizePhoto(data.data);
}

export async function approveAndLiveShowPhoto(
  slug: string,
  id: number,
  payload: { priority?: number } = {}
): Promise<TenantPhoto> {
  const response = await authorizedFetch(`${eventEndpoint(slug)}/live-show/photos/${id}/approve-and-live`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  });
  const data = await jsonOrThrow<PhotoResponse>(response, 'Failed to approve and add live show photo');
  return normalizePhoto(data.data);
}

export async function rejectLiveShowPhoto(slug: string, id: number, reason?: string): Promise<TenantPhoto> {
  const response = await authorizedFetch(`${eventEndpoint(slug)}/live-show/photos/${id}/reject`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ reason }),
  });
  const data = await jsonOrThrow<PhotoResponse>(response, 'Failed to reject live show photo');
  return normalizePhoto(data.data);
}

export async function clearLiveShowPhoto(slug: string, id: number): Promise<TenantPhoto> {
  const response = await authorizedFetch(`${eventEndpoint(slug)}/live-show/photos/${id}/clear`, {
    method: 'POST',
  });
  const data = await jsonOrThrow<PhotoResponse>(response, 'Failed to clear live show photo');
  return normalizePhoto(data.data);
}

export async function getEventPhoto(slug: string, id: number): Promise<TenantPhoto> {
  const response = await authorizedFetch(`${eventEndpoint(slug)}/photos/${id}`);
  const data = await jsonOrThrow<PhotoResponse>(response, 'Failed to load photo');
  return normalizePhoto(data.data);
}

export async function registerAdminPushSubscription(subscription: PushSubscription, deviceId?: string): Promise<void> {
  const json = subscription.toJSON() as AdminPushSubscriptionPayload;
  const response = await authorizedFetch('/api/v1/tenant/notifications/push-subscriptions', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      endpoint: json.endpoint,
      keys: json.keys,
      expiration_time: json.expirationTime ?? null,
      content_encoding: json.contentEncoding ?? null,
      device_id: deviceId ?? null,
    }),
  });

  await jsonOrThrow<{ id: number; status: string }>(response, 'Failed to register push subscription', { suppressToast: true });
}

export async function unregisterAdminPushSubscription(endpoint: string): Promise<void> {
  const response = await authorizedFetch('/api/v1/tenant/notifications/push-subscriptions', {
    method: 'DELETE',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ endpoint }),
  });

  await jsonOrThrow<{ status: string }>(response, 'Failed to unregister push subscription', { suppressToast: true });
}

export async function featurePhoto(slug: string, id: number): Promise<TenantPhoto> {
  const response = await authorizedFetch(`${eventEndpoint(slug)}/photos/${id}/feature`, { method: 'POST' });
  const data = await jsonOrThrow<PhotoResponse>(response, 'Failed to feature photo');
  return normalizePhoto(data.data);
}

export async function unfeaturePhoto(slug: string, id: number): Promise<TenantPhoto> {
  const response = await authorizedFetch(`${eventEndpoint(slug)}/photos/${id}/unfeature`, { method: 'POST' });
  const data = await jsonOrThrow<PhotoResponse>(response, 'Failed to unfeature photo');
  return normalizePhoto(data.data);
}

export async function deletePhoto(slug: string, id: number): Promise<void> {
  const response = await authorizedFetch(`${eventEndpoint(slug)}/photos/${id}`, { method: 'DELETE' });
  if (!response.ok) {
    const payload = await safeJson(response);
    if (response.status === 404) {
      // Treat missing files as idempotent deletes to keep the UI in sync.
      return;
    }
    throw new Error(typeof payload?.message === 'string' ? payload.message : 'Failed to delete photo');
  }
}

export async function updatePhotoVisibility(slug: string, id: number, visible: boolean): Promise<TenantPhoto> {
  const response = await authorizedFetch(`${eventEndpoint(slug)}/photos/${id}/visibility`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ visible }),
  });
  const data = await jsonOrThrow<PhotoResponse>(response, 'Failed to update photo visibility');
  return normalizePhoto(data.data);
}

export async function updatePhotoStatus(
  slug: string,
  id: number,
  status: 'pending' | 'approved' | 'rejected'
): Promise<TenantPhoto> {
  const response = await authorizedFetch(`${eventEndpoint(slug)}/photos/${id}`, {
    method: 'PATCH',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ status }),
  });
  const data = await jsonOrThrow<PhotoResponse>(response, 'Failed to update photo status');
  return normalizePhoto(data.data);
}

export async function toggleEvent(slug: string): Promise<TenantEvent> {
  const response = await authorizedFetch(`${eventEndpoint(slug)}/toggle`, { method: 'POST' });
  const data = await jsonOrThrow<{ message: string; data: JsonValue }>(response, 'Failed to toggle event');
  return normalizeEvent(data.data);
}

export async function getEventStats(slug: string): Promise<EventStats> {
  const response = await authorizedFetch(`${eventEndpoint(slug)}/stats`);
  const data = await jsonOrThrow<EventStats>(response, 'Failed to load stats');
  return {
    total: Number(data.total ?? 0),
    featured: Number(data.featured ?? 0),
    likes: Number(data.likes ?? 0),
    recent_uploads: Number(data.recent_uploads ?? 0),
    status: data.status ?? 'draft',
    is_active: Boolean(data.is_active),
    uploads_total: Number((data as JsonValue).uploads_total ?? data.total ?? 0),
    uploads_24h: Number((data as JsonValue).uploads_24h ?? data.recent_uploads ?? 0),
    likes_total: Number((data as JsonValue).likes_total ?? data.likes ?? 0),
    pending_photos: Number((data as JsonValue).pending_photos ?? 0),
  };
}

export async function getEventEngagement(
  token: string,
  options: { locale?: string; guestName?: string; signal?: AbortSignal } = {}
): Promise<EventEngagement> {
  const params = new URLSearchParams();
  if (options.guestName) {
    params.set('guest_name', options.guestName);
  }
  if (options.locale) {
    params.set('locale', options.locale);
  }

  const query = params.toString();
  const response = await fetch(
    `/api/v1/events/${encodeURIComponent(token)}/achievements${query ? `?${query}` : ''}`,
    {
      method: 'GET',
      headers: {
        Accept: 'application/json',
        ...(options.locale ? { 'X-Locale': options.locale } : {}),
      },
      credentials: 'same-origin',
      cache: 'no-store',
      signal: options.signal,
    }
  );

  const data = await jsonOrThrow<JsonValue>(response, 'Failed to load engagement data', { suppressToast: true });
  return normalizeEventEngagement(data);
}

export async function getEventQrInvites(slug: string): Promise<EventQrInvite[]> {
  const response = await authorizedFetch(`${eventEndpoint(slug)}/join-tokens`);
  const payload = await jsonOrThrow<{ data: JsonValue[] }>(response, 'Failed to load invitations');
  const list = Array.isArray(payload.data) ? payload.data : [];
  return list.map(normalizeQrInvite);
}

export async function createQrInvite(
  slug: string,
  payload?: { label?: string; usage_limit?: number; expires_at?: string }
): Promise<EventQrInvite> {
  const body = JSON.stringify(payload ?? {});
  const response = await authorizedFetch(`${eventEndpoint(slug)}/join-tokens`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body,
  });
  const data = await jsonOrThrow<{ data: JsonValue }>(response, 'Failed to create invitation');
  return normalizeQrInvite(data.data ?? {});
}

export async function revokeEventQrInvite(
  slug: string,
  tokenId: number,
  reason?: string
): Promise<EventQrInvite> {
  const options: RequestInit = { method: 'DELETE' };
  if (reason) {
    options.headers = { 'Content-Type': 'application/json' };
    options.body = JSON.stringify({ reason });
  }
  const response = await authorizedFetch(`${eventEndpoint(slug)}/join-tokens/${tokenId}`, options);
  const data = await jsonOrThrow<{ data: JsonValue }>(response, 'Failed to revoke invitation');
  return normalizeQrInvite(data.data ?? {});
}

export async function updateEventQrInvite(
  slug: string,
  tokenId: number,
  payload: {
    label?: string | null;
    expires_at?: string | null;
    usage_limit?: number | null;
    metadata?: Record<string, unknown> | null;
  }
): Promise<EventQrInvite> {
  const response = await authorizedFetch(`${eventEndpoint(slug)}/join-tokens/${tokenId}`, {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  });
  const data = await jsonOrThrow<{ data: JsonValue }>(response, 'Failed to update invitation');
  return normalizeQrInvite(data.data ?? {});
}

export async function getEventToolkit(slug: string): Promise<EventToolkit> {
  const response = await authorizedFetch(`${eventEndpoint(slug)}/toolkit`);
  const json = await jsonOrThrow<Record<string, JsonValue>>(response, 'Failed to load toolkit');

  const metrics = json.metrics ?? {};
  const tasks = json.tasks ?? {};
  const photos = json.photos ?? {};
  const invites = json.invites ?? {};
  const notifications = normalizeToolkitNotifications(json.notifications ?? null);

  const pendingPhotosRaw = Array.isArray((photos as Record<string, JsonValue>).pending)
    ? (photos as Record<string, JsonValue>).pending
    : [];
  const recentPhotosRaw = Array.isArray((photos as Record<string, JsonValue>).recent)
    ? (photos as Record<string, JsonValue>).recent
    : [];

  const toolkit: EventToolkit = {
    event: normalizeEvent(json.event ?? {}),
    metrics: {
      uploads_total: Number((metrics as JsonValue).uploads_total ?? 0),
      uploads_24h: Number((metrics as JsonValue).uploads_24h ?? 0),
      pending_photos: Number((metrics as JsonValue).pending_photos ?? 0),
      active_invites: Number((metrics as JsonValue).active_invites ?? 0),
      engagement_mode: ((metrics as JsonValue).engagement_mode as 'tasks' | 'photo_only') ?? 'tasks',
    },
  tasks: {
    summary: {
      total: Number((tasks as JsonValue)?.summary?.total ?? 0),
      completed: Number((tasks as JsonValue)?.summary?.completed ?? 0),
      pending: Number((tasks as JsonValue)?.summary?.pending ?? 0),
    },
    items: Array.isArray((tasks as JsonValue)?.items)
      ? ((tasks as JsonValue).items as JsonValue[]).map((item) => ({
          id: Number(item?.id ?? 0),
          title: String(item?.title ?? ''),
          description: item?.description !== undefined && item?.description !== null ? String(item.description) : null,
          is_completed: Boolean(item?.is_completed ?? false),
          priority: item?.priority !== undefined ? String(item.priority) : null,
        }))
      : [],
  },
    photos: {
      pending: pendingPhotosRaw.map((photo: JsonValue) => normalizePhoto(photo as TenantPhoto)),
      recent: recentPhotosRaw.map((photo: JsonValue) => normalizePhoto(photo as TenantPhoto)),
    },
    invites: {
      summary: {
        total: Number((invites as JsonValue)?.summary?.total ?? 0),
        active: Number((invites as JsonValue)?.summary?.active ?? 0),
      },
      items: Array.isArray((invites as JsonValue)?.items)
        ? ((invites as JsonValue).items as JsonValue[]).map((item) => normalizeQrInvite(item))
        : [],
    },
    notifications,
    alerts: Array.isArray(json.alerts) ? (json.alerts as string[]) : [],
  };

  return toolkit;
}

function normalizeToolkitNotifications(payload: JsonValue | null | undefined): EventToolkit['notifications'] | undefined {
  if (!payload || typeof payload !== 'object') {
    return undefined;
  }

  const record = payload as Record<string, JsonValue>;
  const summaryRaw = record.summary ?? {};
  const broadcastsRaw = summaryRaw?.broadcasts ?? {};

  return {
    summary: {
      total: Number(summaryRaw?.total ?? 0),
      last_sent_at: typeof summaryRaw?.last_sent_at === 'string' ? summaryRaw.last_sent_at : null,
      by_type: (summaryRaw?.by_type ?? {}) as Record<string, number>,
      broadcasts: {
        total: Number(broadcastsRaw?.total ?? 0),
        last_title: typeof broadcastsRaw?.last_title === 'string' ? broadcastsRaw.last_title : null,
        last_sent_at: typeof broadcastsRaw?.last_sent_at === 'string' ? broadcastsRaw.last_sent_at : null,
      },
    },
    recent: Array.isArray(record.recent)
      ? (record.recent as JsonValue[]).map((row) => normalizeToolkitNotification(row))
      : [],
  };
}

function normalizeToolkitNotification(row: JsonValue): EventToolkitNotification {
  if (!row || typeof row !== 'object') {
    return {
      id: 0,
      title: '',
      type: 'broadcast',
      status: 'active',
      audience_scope: 'all',
      created_at: null,
    };
  }

  const record = row as Record<string, JsonValue>;

  return {
    id: Number(record.id ?? 0),
    title: typeof record.title === 'string' ? record.title : '',
    type: typeof record.type === 'string' ? record.type : 'broadcast',
    status: typeof record.status === 'string' ? record.status : 'active',
    audience_scope: typeof record.audience_scope === 'string' ? record.audience_scope : 'all',
    created_at: typeof record.created_at === 'string' ? record.created_at : null,
  };
}

export async function listGuestNotifications(slug: string): Promise<GuestNotificationSummary[]> {
  const response = await authorizedFetch(guestNotificationsEndpoint(slug));
  const data = await jsonOrThrow<{ data?: JsonValue[] }>(response, 'Failed to load guest notifications');
  const rows = Array.isArray(data.data) ? data.data : [];

  return rows
    .map((row) => normalizeGuestNotification(row))
    .filter((row): row is GuestNotificationSummary => Boolean(row));
}

export async function sendGuestNotification(
  slug: string,
  payload: SendGuestNotificationPayload
): Promise<GuestNotificationSummary> {
  const response = await authorizedFetch(guestNotificationsEndpoint(slug), {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  });
  const data = await jsonOrThrow<{ data?: JsonValue }>(response, 'Failed to send guest notification');
  const fallback = normalizeGuestNotification({
    id: 0,
    type: payload.type ?? 'broadcast',
    title: payload.title,
    body: payload.message,
    status: 'active',
    audience_scope: payload.audience ?? 'all',
    target_identifier: payload.guest_identifier ?? null,
    payload: payload.cta ? { cta: payload.cta } : null,
    priority: payload.priority ?? 0,
    created_at: new Date().toISOString(),
    expires_at: null,
  });

  const normalized = normalizeGuestNotification(data.data ?? {}) ?? fallback;

  if (!normalized) {
    throw new Error('Failed to normalize guest notification');
  }

  return normalized;
}

export async function getEventPhotoboothStatus(slug: string): Promise<PhotoboothStatus> {
  return requestPhotoboothStatus(slug, '', {}, 'Failed to load photobooth status');
}

export async function enableEventPhotobooth(slug: string, options?: { mode?: 'ftp' | 'sparkbooth' }): Promise<PhotoboothStatus> {
  const body = options?.mode ? JSON.stringify({ mode: options.mode }) : undefined;
  const headers = body ? { 'Content-Type': 'application/json' } : undefined;

  return requestPhotoboothStatus(
    slug,
    '/enable',
    { method: 'POST', body, headers },
    'Failed to enable photobooth access'
  );
}

export async function rotateEventPhotobooth(slug: string, options?: { mode?: 'ftp' | 'sparkbooth' }): Promise<PhotoboothStatus> {
  const body = options?.mode ? JSON.stringify({ mode: options.mode }) : undefined;
  const headers = body ? { 'Content-Type': 'application/json' } : undefined;

  return requestPhotoboothStatus(
    slug,
    '/rotate',
    { method: 'POST', body, headers },
    'Failed to rotate credentials'
  );
}

export async function disableEventPhotobooth(slug: string, options?: { mode?: 'ftp' | 'sparkbooth' }): Promise<PhotoboothStatus> {
  const body = options?.mode ? JSON.stringify({ mode: options.mode }) : undefined;
  const headers = body ? { 'Content-Type': 'application/json' } : undefined;

  return requestPhotoboothStatus(
    slug,
    '/disable',
    { method: 'POST', body, headers },
    'Failed to disable photobooth access'
  );
}

export async function createEventPhotoboothConnectCode(
  slug: string,
  options?: { expires_in_minutes?: number }
): Promise<PhotoboothConnectCode> {
  const body = options ? JSON.stringify(options) : undefined;
  const headers = body ? { 'Content-Type': 'application/json' } : undefined;

  const response = await authorizedFetch(`${photoboothEndpoint(slug)}/connect-codes`, {
    method: 'POST',
    body,
    headers,
  });

  const data = await jsonOrThrow<{ data?: JsonValue }>(response, 'Failed to create photobooth connect code');
  const record = (data.data ?? {}) as Record<string, JsonValue>;

  return {
    code: typeof record.code === 'string' ? record.code : '',
    expires_at: typeof record.expires_at === 'string' ? record.expires_at : null,
  };
}

export async function sendEventPhotoboothUploaderEmail(slug: string): Promise<void> {
  const response = await authorizedFetch(`${photoboothEndpoint(slug)}/uploader-email`, {
    method: 'POST',
  });
  await jsonOrThrow<{ message?: string }>(response, 'Failed to send photobooth uploader email');
}

export async function submitTenantFeedback(payload: {
  category: string;
  sentiment?: 'positive' | 'neutral' | 'negative';
  rating?: number | null;
  title?: string | null;
  message?: string | null;
  event_slug?: string | null;
  metadata?: Record<string, unknown> | null;
}): Promise<void> {
  const response = await authorizedFetch('/api/v1/tenant/feedback', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  });
  if (!response.ok) {
    const body = await safeJson(response);
    console.error('[API] Failed to submit feedback', response.status, body);
    throw new Error('Failed to submit feedback');
  }
}

export type Package = {
  id: number;
  name: string;
  slug?: string;
  type?: 'endcustomer' | 'reseller';
  price: number;
  max_photos: number | null;
  max_guests: number | null;
  gallery_days: number | null;
  max_events_per_year?: number | null;
  included_package_slug?: string | null;
  paddle_price_id?: string | null;
  paddle_product_id?: string | null;
  branding_allowed?: boolean | null;
  watermark_allowed?: boolean | null;
  features: string[] | Record<string, boolean> | null;
};

export async function getPackages(type: 'endcustomer' | 'reseller' = 'endcustomer'): Promise<Package[]> {
  const response = await authorizedFetch(`/api/v1/tenant/packages?type=${type}`);
  const data = await jsonOrThrow<{ data: Package[] }>(response, 'Failed to load packages');
  return data.data ?? [];
}

type TenantPackagesResponse = {
  data?: JsonValue[];
  active_package?: JsonValue | null;
  message?: string;
};

type LedgerResponse = {
  data?: JsonValue[];
  meta?: Partial<PaginationMeta>;
  current_page?: number;
  last_page?: number;
  per_page?: number;
  total?: number;
};

type TaskCollectionResponse = {
  data?: JsonValue[];
  collection?: JsonValue;
  message?: string;
  meta?: Partial<PaginationMeta>;
  current_page?: number;
  last_page?: number;
  per_page?: number;
  total?: number;
};

type MemberResponse = {
  data?: JsonValue[];
  meta?: Partial<PaginationMeta>;
  current_page?: number;
  last_page?: number;
  per_page?: number;
  total?: number;
};

async function fetchTenantPackagesEndpoint(): Promise<Response> {
  const first = await authorizedFetch('/api/v1/tenant/tenant/packages');
  if (first.status === 404) {
    return authorizedFetch('/api/v1/tenant/packages');
  }
  return first;
}

export async function getDashboardSummary(options?: { force?: boolean }): Promise<DashboardSummary | null> {
  return cachedFetch(
    CacheKeys.dashboard,
    async () => {
      const response = await authorizedFetch('/api/v1/tenant/dashboard');
      if (response.status === 404) {
        return null;
      }
      if (!response.ok) {
        const payload = await safeJson(response);
        const fallbackMessage = i18n.t('dashboard:errors.loadFailed', 'Dashboard konnte nicht geladen werden.');
        emitApiErrorEvent({ message: fallbackMessage, status: response.status });
        console.error('[API] Failed to load dashboard', response.status, payload);
        throw new Error(fallbackMessage);
      }
      const json = (await response.json()) as JsonValue;
      return normalizeDashboard(json);
    },
    DEFAULT_CACHE_TTL,
    options?.force === true,
  );
}

export type TenantSettingsPayload = {
  id: number;
  settings: Record<string, unknown>;
  updated_at: string | null;
};

export async function getTenantSettings(): Promise<TenantSettingsPayload> {
  const response = await authorizedFetch('/api/v1/tenant/settings');
  const data = await jsonOrThrow<{ data?: { id?: number; settings?: Record<string, unknown>; updated_at?: string | null } }>(
    response,
    'Failed to load tenant settings',
  );
  const payload = (data.data ?? {}) as Record<string, unknown>;

  return {
    id: Number(payload.id ?? 0),
    settings: (payload.settings ?? {}) as Record<string, unknown>,
    updated_at: (payload.updated_at ?? null) as string | null,
  };
}

export async function getTenantPackagesOverview(options?: { force?: boolean }): Promise<{
  packages: TenantPackageSummary[];
  activePackage: TenantPackageSummary | null;
}> {
  return cachedFetch(
    CacheKeys.packages,
    async () => {
      const response = await fetchTenantPackagesEndpoint();
      if (!response.ok) {
        const payload = await safeJson(response);
        const fallbackMessage = i18n.t('common:errors.generic', 'Etwas ist schiefgelaufen. Bitte versuche es erneut.');
        emitApiErrorEvent({ message: fallbackMessage, status: response.status });
        console.error('[API] Failed to load tenant packages', response.status, payload);
        throw new Error(fallbackMessage);
      }
      const data = (await response.json()) as TenantPackagesResponse;
      const packages = Array.isArray(data.data) ? data.data.map(normalizeTenantPackage) : [];
      const activePackage = data.active_package ? normalizeTenantPackage(data.active_package) : null;
      return { packages, activePackage };
    },
    DEFAULT_CACHE_TTL * 5,
    options?.force === true,
  );
}

export type NotificationPreferenceResponse = {
  defaults: NotificationPreferences;
  preferences: NotificationPreferences;
  overrides: NotificationPreferences | null;
  meta: NotificationPreferencesMeta | null;
};

export async function getNotificationPreferences(): Promise<NotificationPreferenceResponse> {
  const response = await authorizedFetch('/api/v1/tenant/settings/notifications');
  const payload = await jsonOrThrow<{ data?: { defaults?: NotificationPreferences; preferences?: NotificationPreferences; overrides?: NotificationPreferences; meta?: NotificationPreferencesMeta } }>(
    response,
    'Failed to load notification preferences'
  );

  const data = payload.data ?? {};

  return {
    defaults: data.defaults ?? {},
    preferences: data.preferences ?? {},
    overrides: data.overrides ?? null,
    meta: data.meta ?? null,
  };
}

type ProfileResponse = {
  data?: TenantAccountProfile;
  message?: string;
};

export async function fetchTenantProfile(): Promise<TenantAccountProfile> {
  const response = await authorizedFetch('/api/v1/tenant/profile');
  const payload = await jsonOrThrow<ProfileResponse>(
    response,
    i18n.t('settings.profile.errors.load', 'Profil konnte nicht geladen werden.'),
    { suppressToast: true }
  );

  if (!payload.data) {
    throw new Error('Profilantwort war leer.');
  }

  return payload.data;
}

export async function updateTenantProfile(payload: UpdateTenantProfilePayload): Promise<TenantAccountProfile> {
  const response = await authorizedFetch('/api/v1/tenant/profile', {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json',
      Accept: 'application/json',
    },
    body: JSON.stringify(payload),
  });

  const json = await jsonOrThrow<ProfileResponse>(
    response,
    i18n.t('settings.profile.errors.update', 'Profil konnte nicht aktualisiert werden.'),
    { suppressToast: true }
  );

  if (!json.data) {
    throw new Error('Profilantwort war leer.');
  }

  return json.data;
}

export async function updateNotificationPreferences(
  preferences: NotificationPreferences
): Promise<NotificationPreferenceResponse> {
  const response = await authorizedFetch('/api/v1/tenant/settings/notifications', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ preferences }),
  });

  const payload = await jsonOrThrow<{ data?: { preferences?: NotificationPreferences; overrides?: NotificationPreferences; meta?: NotificationPreferencesMeta } }>(
    response,
    'Failed to update notification preferences'
  );

  const data = payload.data ?? {};

  return {
    defaults: {},
    preferences: data.preferences ?? preferences,
    overrides: data.overrides ?? null,
    meta: data.meta ?? null,
  };
}

function normalizeNotificationLog(entry: JsonValue): NotificationLogEntry | null {
  if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
    return null;
  }

  const row = entry as Record<string, JsonValue>;

  return {
    id: Number(row.id ?? 0),
    type: typeof row.type === 'string' ? row.type : '',
    channel: typeof row.channel === 'string' ? row.channel : '',
    recipient: typeof row.recipient === 'string' ? row.recipient : null,
    status: typeof row.status === 'string' ? row.status : '',
    context: (row.context && typeof row.context === 'object' && !Array.isArray(row.context)) ? (row.context as Record<string, unknown>) : null,
    sent_at: typeof row.sent_at === 'string' ? row.sent_at : null,
    failed_at: typeof row.failed_at === 'string' ? row.failed_at : null,
    failure_reason: typeof row.failure_reason === 'string' ? row.failure_reason : null,
  };
}

function normalizeDataExport(entry: JsonValue): DataExportSummary | null {
  if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
    return null;
  }

  const row = entry as Record<string, JsonValue>;
  const event = row.event;
  const eventRecord = event && typeof event === 'object' && !Array.isArray(event)
    ? (event as Record<string, JsonValue>)
    : null;

  return {
    id: Number(row.id ?? 0),
    scope: row.scope === 'event' ? 'event' : 'tenant',
    status: typeof row.status === 'string' ? (row.status as DataExportSummary['status']) : 'pending',
    include_media: Boolean(row.include_media),
    size_bytes: typeof row.size_bytes === 'number' ? row.size_bytes : null,
    created_at: typeof row.created_at === 'string' ? row.created_at : null,
    expires_at: typeof row.expires_at === 'string' ? row.expires_at : null,
    download_url: typeof row.download_url === 'string' ? row.download_url : null,
    error_message: typeof row.error_message === 'string' ? row.error_message : null,
    event: eventRecord
      ? {
          id: Number(eventRecord.id ?? 0),
          slug: typeof eventRecord.slug === 'string' ? eventRecord.slug : '',
          name: typeof eventRecord.name === 'string' || typeof eventRecord.name === 'object'
            ? (eventRecord.name as DataExportSummary['event']['name'])
            : '',
        }
      : null,
  };
}

export async function listNotificationLogs(options?: {
  page?: number;
  perPage?: number;
  type?: string;
  status?: string;
  scope?: string;
  eventId?: number;
}): Promise<{
  data: NotificationLogEntry[];
  meta: PaginationMeta & { unread_count?: number };
}> {
  const params = new URLSearchParams();
  if (options?.page) params.set('page', String(options.page));
  if (options?.perPage) params.set('per_page', String(options.perPage));
  if (options?.type) params.set('type', options.type);
  if (options?.status) params.set('status', options.status);
  if (options?.scope) params.set('scope', options.scope);
  if (options?.eventId) params.set('event_id', String(options.eventId));

  const response = await authorizedFetch(`/api/v1/tenant/notifications/logs${params.toString() ? `?${params.toString()}` : ''}`);
  const payload = await jsonOrThrow<{ data?: JsonValue[]; meta?: Partial<PaginationMeta> }>(
    response,
    'Failed to load notification logs'
  );

  const rows = Array.isArray(payload.data) ? payload.data : [];
  const meta = buildPagination((payload.meta ?? {}) as JsonValue, 0) as PaginationMeta & { unread_count?: number };
  if (payload.meta && typeof (payload.meta as any).unread_count === 'number') {
    meta.unread_count = (payload.meta as any).unread_count as number;
  }

  return {
    data: rows.map((row) => normalizeNotificationLog(row)).filter((row): row is NotificationLogEntry => Boolean(row)),
    meta,
  };
}

export async function markNotificationLogs(ids: number[], status: 'read' | 'dismissed'): Promise<void> {
  await authorizedFetch('/api/v1/tenant/notifications/logs/mark', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ ids, status }),
  });
}

export async function listTenantDataExports(): Promise<DataExportSummary[]> {
  const response = await authorizedFetch('/api/v1/tenant/exports');
  const payload = await jsonOrThrow<{ data?: JsonValue[] }>(response, 'Failed to load data exports');
  const rows = Array.isArray(payload.data) ? payload.data : [];

  return rows
    .map((row) => normalizeDataExport(row))
    .filter((row): row is DataExportSummary => Boolean(row));
}

export async function requestTenantDataExport(payload: {
  scope: 'tenant' | 'event';
  eventId?: number;
  includeMedia?: boolean;
}): Promise<DataExportSummary | null> {
  const response = await authorizedFetch('/api/v1/tenant/exports', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      scope: payload.scope,
      event_id: payload.eventId,
      include_media: payload.includeMedia,
    }),
  });

  const body = await jsonOrThrow<{ data?: JsonValue }>(response, 'Failed to request export');
  const record = body.data ? normalizeDataExport(body.data) : null;

  return record ?? null;
}

export async function downloadTenantDataExport(downloadUrl: string): Promise<Blob> {
  const response = await authorizedFetch(downloadUrl, {
    headers: { 'Accept': 'application/octet-stream' },
  });

  if (!response.ok) {
    const payload = await safeJson(response);
    console.error('[API] Failed to download data export', response.status, payload);
    throw new Error('Failed to download data export');
  }

  return response.blob();
}

export async function getTenantPaddleTransactions(cursor?: string): Promise<{
  data: PaddleTransactionSummary[];
  nextCursor: string | null;
  hasMore: boolean;
}> {
  const query = cursor ? `?cursor=${encodeURIComponent(cursor)}` : '';
  const response = await authorizedFetch(`/api/v1/tenant/billing/transactions${query}`);

  if (response.status === 404) {
    return { data: [], nextCursor: null, hasMore: false };
  }

  if (!response.ok) {
    const payload = await safeJson(response);
    console.error('[API] Failed to load Paddle transactions', response.status, payload);
    throw new Error('Failed to load Paddle transactions');
  }

  const payload = await safeJson(response) ?? {};
  const entries = Array.isArray(payload.data) ? payload.data : [];
  const meta = payload.meta ?? {};

  return {
    data: entries.map(normalizePaddleTransaction),
    nextCursor: typeof meta.next === 'string' ? meta.next : null,
    hasMore: Boolean(meta.has_more),
  };
}

export async function createTenantPaddleCheckout(
  packageId: number,
  urls?: { success_url?: string; return_url?: string }
): Promise<{ checkout_url: string; id: string; expires_at?: string; checkout_session_id?: string }> {
  const response = await authorizedFetch('/api/v1/tenant/packages/paddle-checkout', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      package_id: packageId,
      success_url: urls?.success_url,
      return_url: urls?.return_url,
    }),
  });
  return await jsonOrThrow<{ checkout_url: string; id: string; expires_at?: string; checkout_session_id?: string }>(
    response,
    'Failed to create checkout'
  );
}

export async function getTenantPackageCheckoutStatus(
  checkoutSessionId: string,
): Promise<{ status: string; completed_at?: string | null; reason?: string | null; checkout_url?: string | null }> {
  const response = await authorizedFetch(`/api/v1/tenant/packages/checkout-session/${checkoutSessionId}/status`);
  return await jsonOrThrow<{ status: string; completed_at?: string | null; reason?: string | null; checkout_url?: string | null }>(
    response,
    'Failed to load checkout status'
  );
}

export async function createTenantBillingPortalSession(): Promise<{ url: string }> {
  const response = await authorizedFetch('/api/v1/tenant/billing/portal', {
    method: 'POST',
  });

  if (!response.ok) {
    const payload = await safeJson(response);
    console.error('[API] Failed to create Paddle portal session', response.status, payload);
    throw new Error('Failed to create Paddle portal session');
  }

  const payload = await safeJson(response);
  const url = payload?.url;

  if (typeof url !== 'string' || url.length === 0) {
    throw new Error('Paddle portal session missing URL');
  }

  return { url };
}

export async function getTenantAddonHistory(page = 1, perPage = 25): Promise<{
  data: TenantAddonHistoryEntry[];
  meta: PaginationMeta;
}> {
  const params = new URLSearchParams({
    page: String(Math.max(1, page)),
    per_page: String(Math.max(1, Math.min(perPage, 100))),
  });

  const response = await authorizedFetch(`/api/v1/tenant/billing/addons?${params.toString()}`);

  if (response.status === 404) {
    return {
      data: [],
      meta: { current_page: 1, last_page: 1, per_page: perPage, total: 0 },
    };
  }

  const payload = await jsonOrThrow<{ data?: JsonValue[]; meta?: Partial<PaginationMeta>; current_page?: number; last_page?: number; per_page?: number; total?: number }>(
    response,
    'Failed to load add-on history'
  );

  const rows = Array.isArray(payload.data) ? payload.data.map((row) => normalizeTenantAddonHistoryEntry(row)) : [];
  const metaSource = payload.meta ?? payload;

  const meta: PaginationMeta = {
    current_page: Number(metaSource.current_page ?? 1),
    last_page: Number(metaSource.last_page ?? 1),
    per_page: Number(metaSource.per_page ?? perPage),
    total: Number(metaSource.total ?? rows.length),
  };

  return { data: rows, meta };
}

export async function completeTenantPackagePurchase(params: {
  packageId: number;
  paddleTransactionId: string;
}): Promise<void> {
  const { packageId, paddleTransactionId } = params;
  const payload: Record<string, unknown> = { package_id: packageId };

  if (paddleTransactionId) {
    payload.paddle_transaction_id = paddleTransactionId;
  }

  const response = await authorizedFetch('/api/v1/tenant/packages/complete', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(payload),
  });

  await jsonOrThrow(response, 'Failed to complete package purchase');
}

export async function assignFreeTenantPackage(packageId: number): Promise<void> {
  const response = await authorizedFetch('/api/v1/tenant/packages/free', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ package_id: packageId }),
  });

  await jsonOrThrow(response, 'Failed to assign free package');
}



export async function recordCreditPurchase(payload: {
  package_id: string;
  credits_added: number;
  platform?: string;
  transaction_id?: string;
  subscription_active?: boolean;
}): Promise<CreditBalance> {
  const response = await authorizedFetch('/api/v1/tenant/credits/purchase', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  });
  const data = await jsonOrThrow<{ message: string; balance: number; subscription_active?: boolean }>(
    response,
    'Failed to record credit purchase'
  );
  return { balance: Number(data.balance ?? 0) };
}

export async function getTaskCollections(params: {
  page?: number;
  per_page?: number;
  search?: string;
  event_type?: string;
  scope?: 'global' | 'tenant';
  top_picks?: boolean;
  limit?: number;
} = {}): Promise<PaginatedResult<TenantTaskCollection>> {
  const searchParams = new URLSearchParams();
  if (params.page) searchParams.set('page', String(params.page));
  if (params.per_page) searchParams.set('per_page', String(params.per_page));
  if (params.search) searchParams.set('search', params.search);
  if (params.event_type) searchParams.set('event_type', params.event_type);
  if (params.scope) searchParams.set('scope', params.scope);
   if (params.top_picks) searchParams.set('top_picks', '1');
   if (params.limit) searchParams.set('limit', String(params.limit));

  const queryString = searchParams.toString();
  const response = await authorizedFetch(
    `/api/v1/tenant/task-collections${queryString ? `?${queryString}` : ''}`
  );

  if (!response.ok) {
    const payload = await safeJson(response);
    console.error('[API] Failed to load task collections', response.status, payload);
    throw new Error('Failed to load task collections');
  }

  const json = (await response.json()) as TaskCollectionResponse;
  const collections = Array.isArray(json.data) ? json.data.map(normalizeTaskCollection) : [];

  return {
    data: collections,
    meta: buildPagination(json as JsonValue, collections.length),
  };
}

export async function getTaskCollection(collectionId: number): Promise<TenantTaskCollection> {
  const response = await authorizedFetch(`/api/v1/tenant/task-collections/${collectionId}`);
  const json = await jsonOrThrow<{ data: JsonValue }>(response, 'Failed to load task collection');
  return normalizeTaskCollection(json.data);
}

export async function importTaskCollection(
  collectionId: number,
  eventSlug: string
): Promise<TenantTaskCollection> {
  const response = await authorizedFetch(`/api/v1/tenant/task-collections/${collectionId}/activate`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ event_slug: eventSlug }),
  });

  const json = await jsonOrThrow<TaskCollectionResponse>(response, 'Failed to import task collection');
  if (json.collection) {
    return normalizeTaskCollection(json.collection);
  }

  if (json.data && json.data.length === 1) {
    return normalizeTaskCollection(json.data[0]!);
  }

  throw new Error('Missing collection payload');
}

export async function detachTasksFromEvent(eventId: number, taskIds: number[]): Promise<void> {
  const response = await authorizedFetch(`/api/v1/tenant/tasks/bulk-detach-event/${eventId}`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ task_ids: taskIds }),
  });

  if (!response.ok) {
    const payload = await safeJson(response);
    console.error('[API] Failed to detach tasks', response.status, payload);
    throw new Error('Failed to detach tasks');
  }
}

export async function reorderEventTasks(eventId: number, taskIds: number[]): Promise<void> {
  const response = await authorizedFetch(`/api/v1/tenant/tasks/event/${eventId}/reorder`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ task_ids: taskIds }),
  });

  if (!response.ok) {
    const payload = await safeJson(response);
    console.error('[API] Failed to reorder tasks', response.status, payload);
    throw new Error('Failed to reorder tasks');
  }
}

export async function getEmotions(): Promise<TenantEmotion[]> {
  const response = await authorizedFetch('/api/v1/tenant/emotions');
  if (!response.ok) {
    const payload = await safeJson(response);
    console.error('[API] Failed to load emotions', response.status, payload);
    throw new Error('Failed to load emotions');
  }

  const json = (await response.json()) as { data?: JsonValue[] };
  return Array.isArray(json.data) ? json.data.map(normalizeEmotion) : [];
}

export async function createEmotion(payload: EmotionPayload): Promise<TenantEmotion> {
  const response = await authorizedFetch('/api/v1/tenant/emotions', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  });

  const json = await jsonOrThrow<{ data: JsonValue }>(response, 'Failed to create emotion');
  return normalizeEmotion(json.data);
}

export async function updateEmotion(emotionId: number, payload: EmotionPayload): Promise<TenantEmotion> {
  const response = await authorizedFetch(`/api/v1/tenant/emotions/${emotionId}`, {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  });

  const json = await jsonOrThrow<{ data: JsonValue }>(response, 'Failed to update emotion');
  return normalizeEmotion(json.data);
}

export async function deleteEmotion(emotionId: number): Promise<void> {
  const response = await authorizedFetch(`/api/v1/tenant/emotions/${emotionId}`, {
    method: 'DELETE',
  });
  if (!response.ok) {
    const payload = await safeJson(response);
    console.error('[API] Failed to delete emotion', response.status, payload);
    throw new Error('Failed to delete emotion');
  }
}

export async function getTasks(params: { page?: number; per_page?: number; search?: string } = {}): Promise<PaginatedResult<TenantTask>> {
  const searchParams = new URLSearchParams();
  if (params.page) searchParams.set('page', String(params.page));
  if (params.per_page) searchParams.set('per_page', String(params.per_page));
  if (params.search) searchParams.set('search', params.search);

  const response = await authorizedFetch(`/api/v1/tenant/tasks?${searchParams.toString()}`);
  if (!response.ok) {
    const payload = await safeJson(response);
    console.error('[API] Failed to load tasks', response.status, payload);
    throw new Error('Failed to load tasks');
  }
  const json = (await response.json()) as TaskCollectionResponse;
  const tasks = Array.isArray(json.data) ? json.data.map(normalizeTask) : [];
  return {
    data: tasks,
    meta: buildPagination(json as JsonValue, tasks.length),
  };
}

export async function createTask(payload: TaskPayload): Promise<TenantTask> {
  const response = await authorizedFetch(`/api/v1/tenant/tasks`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  });
  const data = await jsonOrThrow<{ data: JsonValue }>(response, 'Failed to create task');
  return normalizeTask(data.data);
}

export async function updateTask(taskId: number, payload: TaskPayload): Promise<TenantTask> {
  const response = await authorizedFetch(`/api/v1/tenant/tasks/${taskId}`, {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  });
  const data = await jsonOrThrow<{ data: JsonValue }>(response, 'Failed to update task');
  return normalizeTask(data.data);
}

export async function deleteTask(taskId: number): Promise<void> {
  const response = await authorizedFetch(`/api/v1/tenant/tasks/${taskId}`, {
    method: 'DELETE',
  });
  if (!response.ok) {
    const payload = await safeJson(response);
    console.error('[API] Failed to delete task', response.status, payload);
    throw new Error('Failed to delete task');
  }
}

export async function assignTasksToEvent(eventId: number, taskIds: number[]): Promise<void> {
  const response = await authorizedFetch(`/api/v1/tenant/tasks/bulk-assign-event/${eventId}`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ task_ids: taskIds }),
  });
  if (!response.ok) {
    const payload = await safeJson(response);
    console.error('[API] Failed to assign tasks', response.status, payload);
    throw new Error('Failed to assign tasks');
  }
}

export async function getEventTasks(
  eventId: number,
  page = 1,
  perPage = 500,
): Promise<PaginatedResult<TenantTask>> {
  const response = await authorizedFetch(`/api/v1/tenant/tasks/event/${eventId}?page=${page}&per_page=${perPage}`);
  if (!response.ok) {
    const payload = await safeJson(response);
    console.error('[API] Failed to load event tasks', response.status, payload);
    throw new Error('Failed to load event tasks');
  }
  const json = (await response.json()) as TaskCollectionResponse;
  const tasks = Array.isArray(json.data) ? json.data.map(normalizeTask) : [];
  return {
    data: tasks,
    meta: buildPagination(json as JsonValue, tasks.length),
  };
}

export async function getEventMembers(eventIdentifier: number | string, page = 1): Promise<PaginatedResult<EventMember>> {
  const response = await authorizedFetch(
    `/api/v1/tenant/events/${encodeURIComponent(String(eventIdentifier))}/members?page=${page}`
  );
  if (response.status === 404) {
    return { data: [], meta: { current_page: 1, last_page: 1, per_page: 0, total: 0 } };
  }
  if (!response.ok) {
    const payload = await safeJson(response);
    console.error('[API] Failed to load event members', response.status, payload);
    throw new Error('Failed to load event members');
  }
  const json = (await response.json()) as MemberResponse;
  const members = Array.isArray(json.data) ? json.data.map(normalizeMember) : [];
  return {
    data: members,
    meta: buildPagination(json as JsonValue, members.length),
  };
}

export async function inviteEventMember(
  eventIdentifier: number | string,
  payload: { email: string; role: EventMember['role']; name?: string }
): Promise<EventMember> {
  const response = await authorizedFetch(
    `/api/v1/tenant/events/${encodeURIComponent(String(eventIdentifier))}/members`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    }
  );
  if (response.status === 404) {
    throw new Error('Mitgliederverwaltung ist fuer dieses Event noch nicht verfuegbar.');
  }
  const data = await jsonOrThrow<{ data: JsonValue }>(response, 'Failed to invite member');
  return normalizeMember(data.data);
}

export async function removeEventMember(eventIdentifier: number | string, memberId: number): Promise<void> {
  const response = await authorizedFetch(
    `/api/v1/tenant/events/${encodeURIComponent(String(eventIdentifier))}/members/${memberId}`,
    { method: 'DELETE' }
  );
  if (response.status === 404) {
    throw new Error('Mitglied konnte nicht gefunden werden.');
  }
  if (!response.ok) {
    const payload = await safeJson(response);
    console.error('[API] Failed to remove member', response.status, payload);
    throw new Error('Failed to remove member');
  }
}
export type AnalyticsTimelinePoint = {
  timestamp: string;
  count: number;
};

export type AnalyticsContributor = {
  name: string;
  count: number;
  likes: number;
};

export type AnalyticsTaskStat = {
  task_id: number;
  task_name: string;
  count: number;
};

export type EventAnalytics = {
  timeline: AnalyticsTimelinePoint[];
  contributors: AnalyticsContributor[];
  tasks: AnalyticsTaskStat[];
};

export async function getEventAnalytics(slug: string): Promise<EventAnalytics> {
  const response = await authorizedFetch(`${eventEndpoint(slug)}/analytics`);
  const data = await jsonOrThrow<EventAnalytics>(response, 'Failed to load analytics');
  return data;
}

type CacheEntry<T> = {
  value?: T;
  expiresAt: number;
  promise?: Promise<T>;
};

const tenantApiCache = new Map<string, CacheEntry<unknown>>();
const DEFAULT_CACHE_TTL = 60_000;

const CacheKeys = {
  dashboard: 'tenant:dashboard',
  events: 'tenant:events',
  packages: 'tenant:packages',
  fonts: 'tenant:fonts',
} as const;

function cachedFetch<T>(
  key: string,
  fetcher: () => Promise<T>,
  ttl: number = DEFAULT_CACHE_TTL,
  force = false,
): Promise<T> {
  if (force) {
    tenantApiCache.delete(key);
  }

  const now = Date.now();
  const existing = tenantApiCache.get(key) as CacheEntry<T> | undefined;

  if (!force && existing) {
    if (existing.promise) {
      return existing.promise;
    }

    if (existing.value !== undefined && existing.expiresAt > now) {
      return Promise.resolve(existing.value);
    }
  }

  const promise = fetcher()
    .then((value) => {
      tenantApiCache.set(key, { value, expiresAt: Date.now() + ttl });
      return value;
    })
    .catch((error) => {
      tenantApiCache.delete(key);
      throw error;
    });

  tenantApiCache.set(key, { value: existing?.value, expiresAt: existing?.expiresAt ?? 0, promise });

  return promise;
}

export function invalidateTenantApiCache(keys?: string | string[]): void {
  if (!keys) {
    tenantApiCache.clear();
    return;
  }

  const entries = Array.isArray(keys) ? keys : [keys];
  for (const key of entries) {
    tenantApiCache.delete(key);
  }
}
