/**
 * kF - keys From
 * kT - keys To
 * kC - extra context data available to process functions
 */
export type TranslationEntry<kF, kT, kC = unknown> = {
  from: keyof kF;
  to: keyof kT;
} & {
  [kFrom in keyof kF]: {
    [kTo in keyof kT]: {
      from: kFrom;
      to: kTo;
    } & (kF[kFrom] extends kT[kTo]
      ? {
          process?: (value: kF[kFrom], context: kC, row: kF) => kT[kTo];
        }
      : {
          process: (value: kF[kFrom], context: kC, row: kF) => kT[kTo];
        });
  }[keyof kT];
}[keyof kF];

export interface Translation<kF, kT, kC = unknown> {
  values: TranslationEntry<kF, kT, kC>[];
}

/**
 * Represents the outcome of translating a single row.
 * - ok: true  → translation succeeded, `value` contains the translated row
 * - ok: false → row should be skipped, `reason` explains why
 */
export type TranslationResult<T> =
  | { ok: true; value: T }
  | { ok: false; reason: string };

/**
 * Sentinel class thrown by `skipRow()` inside process functions.
 * Caught by `translateRow()` and converted to a `TranslationResult`.
 * This avoids string-prefix matching on generic Error messages.
 */
export class SkipRowError extends Error {
  readonly __brand = "SkipRowError" as const;
  constructor(reason: string) {
    super(reason);
    this.name = "SkipRowError";
  }
}

/**
 * Convenience helper for process functions to signal that a row
 * should be skipped.  Preferred over `throw new Error("SKIP_ROW: …")`.
 */
export function skipRow(reason: string): never {
  throw new SkipRowError(reason);
}
