/**
 * The reports module — plan step 10.
 *
 * Each "report" is one entry in `reports.registry.ts`. The entry declares
 * its slug (URL id), title, what filters it accepts, what columns it
 * returns, and a `run` function that produces the data from Prisma. The
 * service dispatches on slug; the controller exposes JSON + CSV + XLSX +
 * PDF + HTML print; the catalog endpoint exposes the registry itself so
 * the `/reportes` page can render the same data.
 *
 * Output philosophy: a report returns a uniform shape — `columns` (typed
 * schema) + `rows` (any[] of values matching the column types) + `totals`
 * (record of column key → summary value). All three output formats
 * (CSV/XLSX/PDF/print) derive from this same shape so adding a new
 * report is one entry, never a per-format template.
 */

import { Prisma } from "@jorgecuadros/database";
import { PrismaService } from "../prisma/prisma.service";

/** Top-level grouping for the catalog page; matches the existing nav. */
export type ReportDomain =
  | "clientes"
  | "polizas"
  | "servicios"
  | "estado-cuenta"
  | "chequera";

/** How the runner should render rows: a grid, a per-customer statement, or
 *  one printable letter per row (e.g. renewal notices — see `format:
 *  "letter"` reports for the `__kind: "letter"` row shape they emit). */
export type ReportFormat = "tabular" | "statement" | "letter";

/** Filter controls the report's UI should render. */
export type ParamDef =
  | {
      key: string;
      label: string;
      kind: "text" | "number";
      placeholder?: string;
      defaultValue?: string;
    }
  | {
      key: string;
      label: string;
      kind: "date";
      /** Inclusive bound, true for `to`, false for `from`. */
      endOfDay?: boolean;
      defaultValue?: string;
    }
  | {
      key: string;
      label: string;
      kind: "select";
      options: { value: string; label: string }[];
      defaultValue?: string;
    }
  | {
      key: string;
      label: string;
      kind: "customer-picker";
    };

/** One column of the output table. */
export interface ColumnDef {
  key: string;
  label: string;
  /** Render hint for the on-screen + print table. */
  type: "text" | "number" | "money" | "date";
  /** Right-align numbers/money; default false (left). */
  align?: "left" | "right";
  /** Used for column-width hints in the print/PDF layout. */
  width?: number;
}

/** Shape every report's `run` resolves to. Columns come from the def. */
export interface ReportResult {
  rows: Array<Record<string, unknown>>;
  totals?: Record<string, string | number>;
  /** Optional free-form subtitle for print/PDF (e.g. date range, scope). */
  subtitle?: string;
}

/** A report's static declaration. */
export interface ReportDef {
  slug: string;
  title: string;
  description: string;
  domain: ReportDomain;
  /** The original Access report name (per docs/LEGACY_DATABASES_OBJECTS.md)
   *  for traceability. Null when this is a new report with no legacy equiv. */
  legacyName: string | null;
  format: ReportFormat;
  params: ParamDef[];
  columns: ColumnDef[];
  /**
   * Run the report. Receives the Prisma client and the validated params
   * record (keys are the `key` from ParamDef, values are the strings the
   * runner collected; numeric/date params arrive as strings — the report
   * parses them). Must apply the same NOT_VOIDED filter on transactions as
   * the billing module so totals match.
   */
  run: (
    prisma: PrismaService,
    params: Record<string, string | undefined>,
  ) => Promise<ReportResult>;
}

/** A typed bag of helpers for the report functions. */
export interface ReportCtx {
  prisma: PrismaService;
  params: Record<string, string | undefined>;
}

/** Helper: a `YYYY-MM-DD` bound; unparseable is undefined. */
export function parseDate(
  v: string | undefined,
  endOfDay = false,
): Date | undefined {
  if (!v) return undefined;
  const d = new Date(endOfDay ? `${v}T23:59:59.999Z` : `${v}T00:00:00.000Z`);
  return Number.isNaN(d.getTime()) ? undefined : d;
}

/** Helper: integer param with default. */
export function intParam(
  p: Record<string, string | undefined>,
  key: string,
  def: number,
  min = 1,
  max = 1000,
): number {
  const n = Number(p[key]);
  if (!Number.isFinite(n)) return def;
  return Math.min(max, Math.max(min, Math.round(n)));
}

/** Helper: not-voided filter, shared with billing.service. */
export const NOT_VOIDED: Prisma.TransactionWhereInput = { voidedAt: null };
export const NOT_VOIDED_BANK: Prisma.BankTransactionWhereInput = {
  voidedAt: null,
};
