/**
 * Row shape returned by PostgreSQL (snake_case).
 * pg returns DATE as string, NUMERIC as string, TIMESTAMPTZ as Date.
 */
export interface RaceRow {
  id: string;
  race_date: string | Date;
  title: string;
  distance_km: string;
  status: string | null;
  official_url: string | null;
  start_time: string | null;
  cluster_schedule: string | null;
  bib_pickup: string | null;
  bib_number: string | null;
  finish_time: string | null;
  finish_place: string | null;
  notes: string | null;
  created_at: Date;
  updated_at: Date | null;
}

/** API shape (camelCase). */
export interface RaceDto {
  id: string;
  date: string;
  title: string;
  distanceKm: number;
  status: string | null;
  officialUrl: string | null;
  startTime: string | null;
  clusterSchedule: string | null;
  bibPickup: string | null;
  bibNumber: string | null;
  finishTime: string | null;
  finishPlace: string | null;
  notes: string | null;
  createdAt: string;
  updatedAt: string | null;
}

function toISOString(value: Date | string): string {
  return value instanceof Date ? value.toISOString() : String(value);
}

/** DATE column may arrive as string or Date; API always exposes YYYY-MM-DD for the calendar day. */
function raceDateToApiValue(value: string | Date): string {
  if (typeof value === "string") {
    const m = value.match(/^(\d{4}-\d{2}-\d{2})/);
    return m ? m[1]! : value;
  }
  const y = value.getFullYear();
  const mo = String(value.getMonth() + 1).padStart(2, "0");
  const day = String(value.getDate()).padStart(2, "0");
  return `${y}-${mo}-${day}`;
}

/** Convert a DB row to the API DTO (camelCase). */
export function rowToDto(row: RaceRow): RaceDto {
  return {
    id: row.id,
    date: raceDateToApiValue(row.race_date),
    title: row.title,
    distanceKm: parseFloat(row.distance_km),
    status: row.status,
    officialUrl: row.official_url,
    startTime: row.start_time,
    clusterSchedule: row.cluster_schedule,
    bibPickup: row.bib_pickup,
    bibNumber: row.bib_number,
    finishTime: row.finish_time,
    finishPlace: row.finish_place,
    notes: row.notes,
    createdAt: toISOString(row.created_at),
    updatedAt: row.updated_at ? toISOString(row.updated_at) : null,
  };
}

/** Map incoming camelCase body fields to snake_case column names. */
const FIELD_MAP: Record<string, string> = {
  date: "race_date",
  title: "title",
  distanceKm: "distance_km",
  status: "status",
  officialUrl: "official_url",
  startTime: "start_time",
  clusterSchedule: "cluster_schedule",
  bibPickup: "bib_pickup",
  bibNumber: "bib_number",
  finishTime: "finish_time",
  finishPlace: "finish_place",
  notes: "notes",
};

/**
 * Convert a partial camelCase body into { columns, values, placeholders }
 * suitable for SQL INSERT / UPDATE.
 */
export function bodyToColumns(body: Record<string, unknown>) {
  const columns: string[] = [];
  const values: unknown[] = [];

  for (const [camel, snake] of Object.entries(FIELD_MAP)) {
    if (camel in body) {
      columns.push(snake);
      values.push(body[camel]);
    }
  }

  return { columns, values };
}
