// Input parsing/validation for admin write endpoints. Each parser returns the
// Prisma-ready data object or throws a ValidationError (mapped to HTTP 400).

import { ProjectSize } from "@/generated/prisma/client";

export class ValidationError extends Error {}

function str(value: unknown, field: string): string {
  if (typeof value !== "string" || value.trim() === "") {
    throw new ValidationError(`"${field}" is required`);
  }
  return value.trim();
}

function optStr(value: unknown): string | null {
  if (value === undefined || value === null || value === "") return null;
  if (typeof value !== "string") throw new ValidationError("Expected a string");
  const trimmed = value.trim();
  return trimmed === "" ? null : trimmed;
}

function bool(value: unknown): boolean {
  return value === true;
}

function int(value: unknown): number {
  const n = typeof value === "number" ? value : Number.parseInt(String(value ?? 0), 10);
  return Number.isFinite(n) ? Math.trunc(n) : 0;
}

function optInt(value: unknown): number | null {
  if (value === undefined || value === null || value === "") return null;
  const n = typeof value === "number" ? value : Number.parseInt(String(value), 10);
  return Number.isFinite(n) ? Math.trunc(n) : null;
}

function tags(value: unknown, max: number): string[] {
  if (!Array.isArray(value)) return [];
  const cleaned = value
    .map((t) => (typeof t === "string" ? t.trim() : ""))
    .filter(Boolean);
  if (cleaned.length > max) {
    throw new ValidationError(`Too many tags (max ${max})`);
  }
  return cleaned;
}

function strList(value: unknown): string[] {
  if (!Array.isArray(value)) return [];
  return value.map((t) => (typeof t === "string" ? t.trim() : "")).filter(Boolean);
}

function date(value: unknown, field: string): Date {
  const d = new Date(String(value));
  if (Number.isNaN(d.getTime())) throw new ValidationError(`"${field}" is not a valid date`);
  return d;
}

function optDate(value: unknown): Date | null {
  if (value === undefined || value === null || value === "") return null;
  const d = new Date(String(value));
  return Number.isNaN(d.getTime()) ? null : d;
}

export function parseProject(body: Record<string, unknown>) {
  const size = String(body.size);
  const validSize = (Object.values(ProjectSize) as string[]).includes(size)
    ? (size as ProjectSize)
    : ProjectSize.MediumSized;

  return {
    name: str(body.name, "name"),
    label: str(body.label, "label"),
    size: validSize,
    imageUrl: optStr(body.imageUrl),
    link: str(body.link, "link"),
    linkIsDemo: bool(body.linkIsDemo),
    sourceCode: optStr(body.sourceCode),
    description: str(body.description, "description"),
    why: str(body.why, "why"),
    note: optStr(body.note),
    tags: tags(body.tags, 3),
    loc: optInt(body.loc),
    locEndpoint: optStr(body.locEndpoint),
  };
}

export function parseExperience(body: Record<string, unknown>) {
  return {
    company: str(body.company, "company"),
    role: str(body.role, "role"),
    fromDate: date(body.fromDate, "fromDate"),
    toDate: optDate(body.toDate),
    url: optStr(body.url),
    iconUrl: optStr(body.iconUrl),
    summary: str(body.summary, "summary"),
    tags: tags(body.tags, 6),
    sortIndex: int(body.sortIndex),
  };
}

export function parseAffiliate(body: Record<string, unknown>) {
  return {
    name: str(body.name, "name"),
    link: str(body.link, "link"),
    icon: str(body.icon, "icon"),
    location: str(body.location, "location"),
    provides: strList(body.provides),
    good: strList(body.good),
    bad: strList(body.bad),
    sortIndex: int(body.sortIndex),
  };
}
