import type {ApiResponse, RangeKey} from './contracts';

/**
 * The backend-swap seam.
 *
 * Point NEXT_PUBLIC_API_BASE at a real host and every request bypasses the
 * local route handlers. Nothing in any feature file changes.
 */
const BASE = process.env.NEXT_PUBLIC_API_BASE ?? '';

export interface Endpoint<T> {
  path: string;
  params: Record<string, string>;
  /** Phantom field — carries the payload type through to useResource. */
  __type?: T;
}

export interface Scope {
  range: RangeKey;
  storeId: string;
}

function endpoint<T>(
  path: string,
  scope: Scope,
  extra: Record<string, string> = {},
): Endpoint<T> {
  return {path, params: {range: scope.range, storeId: scope.storeId, ...extra}};
}

export function endpointUrl(e: Endpoint<unknown>): string {
  const qs = new URLSearchParams(e.params).toString();
  return `${BASE}${e.path}${qs ? `?${qs}` : ''}`;
}

export async function fetchEndpoint<T>(
  e: Endpoint<T>,
  signal?: AbortSignal,
): Promise<ApiResponse<T>> {
  const res = await fetch(endpointUrl(e), {signal, cache: 'no-store'});
  // A non-JSON body (proxy error page, 502 HTML) must surface as a typed
  // failure rather than throwing a SyntaxError deep inside the hook.
  let body: unknown;
  try {
    body = await res.json();
  } catch {
    return {
      error: {code: 'internal', message: `Unexpected response (${res.status})`},
    };
  }
  return body as ApiResponse<T>;
}

// ---------------------------------------------------------------------------
// The endpoint registry. One entry per route handler; the return type is what
// useResource infers, so a handler and its caller cannot drift.
// ---------------------------------------------------------------------------

import type {
  ActivityEvent,
  AttendancePoint,
  DashboardBriefing,
  Granularity,
  HourCell,
  Kpi,
  PeriodPoint,
  Reward,
  RewardUsagePoint,
  StaffMember,
  StaffSummary,
  Store,
  StoreComparison,
  TimePoint,
} from './contracts';

export const endpoints = {
  dashboardKpis: (s: Scope) => endpoint<Kpi[]>('/api/dashboard/kpis', s),
  dashboardBriefing: (s: Scope) =>
    endpoint<DashboardBriefing>('/api/dashboard/briefing', s),
  dashboardTimeseries: (s: Scope) =>
    endpoint<TimePoint[]>('/api/dashboard/timeseries', s),
  dashboardPeakHours: (s: Scope) =>
    endpoint<HourCell[]>('/api/dashboard/peak-hours', s),
  dashboardActivity: (s: Scope) =>
    endpoint<ActivityEvent[]>('/api/dashboard/activity', s),
  dashboardStoreComparison: (s: Scope) =>
    endpoint<StoreComparison[]>('/api/dashboard/store-comparison', s),
  dashboardRewardUsage: (s: Scope) =>
    endpoint<RewardUsagePoint[]>('/api/dashboard/reward-usage', s),
  dashboardPerformance: (s: Scope, granularity: Granularity) =>
    endpoint<PeriodPoint[]>('/api/dashboard/performance', s, {granularity}),

  stores: (s: Scope) => endpoint<Store[]>('/api/stores', s),
  store: (s: Scope, storeId: string) =>
    endpoint<Store>(`/api/stores/${storeId}`, s),

  lytsRewards: (s: Scope) => endpoint<Reward[]>('/api/lyts/rewards', s),
  lytsRedemptions: (s: Scope) =>
    endpoint<TimePoint[]>('/api/lyts/redemptions', s),
  lytsActivity: (s: Scope) =>
    endpoint<ActivityEvent[]>('/api/lyts/activity', s),

  staff: (s: Scope) => endpoint<StaffMember[]>('/api/staff', s),
  staffSummary: (s: Scope) =>
    endpoint<StaffSummary>('/api/staff/summary', s),
  staffAttendance: (s: Scope) =>
    endpoint<AttendancePoint[]>('/api/staff/attendance', s),
} as const;
