/**
 * Vega expression catalog — the function and constant names the expression
 * language exposes, for editor completion / signature help inside `calculate` /
 * `filter` / `expr` / `test` strings (docs/architecture/08 → editor augmentation).
 *
 * Portable core: no browser APIs, no React, no Monaco. The **name set is derived**,
 * not hand-listed — `vega-expression` (already imported by core/expr-validate, no
 * added bundle weight) ships the parser's own function/constant tables, so the
 * catalog never drifts from what the parser actually accepts. We add only the one
 * thing the registry lacks: a **curated signature** (parameter names + a one-line
 * doc) for the commonly-typed functions, used by hover and signature help. The
 * curated set is a subset of the derived names — a doc for a function the parser
 * doesn't know would be a lie; `vega-expr-catalog.test.ts` asserts the inclusion.
 *
 * Out of scope (deliberate): the *fuller* runtime function set Vega-Lite registers
 * via `vega-functions` (`toNumber`, `format`, `indexof`, …). That package exports
 * implementations, not a clean name registry, so listing it cleanly is more work
 * than it earns here; the base parser set below covers the overwhelming majority of
 * what users write (math, dates, strings, `if`, `datetime`, `test`).
 */

// TODO: extend the name set with vega-functions' runtime registry (toNumber,
// format, indexof, isValid, inrange, …) if completion coverage proves too thin.
import { functions, constants } from 'vega-expression';

/** A curated call signature for one function — what the registry can't tell us. */
export interface FunctionSignature {
  /** The function name (always one the parser knows — see the inclusion test). */
  name: string;
  /** Ordered parameter names, e.g. `['test', 'thenValue', 'elseValue']`. */
  params: readonly string[];
  /** True when the last parameter repeats, e.g. `min(...values)`. */
  variadic?: boolean;
  /** A one-line description for hover and the signature-help label. */
  doc: string;
}

/**
 * Every function name the expression parser exposes, sorted — derived from the
 * parser's own table. `functions(codegen)` maps each name to codegen output; we
 * pass an empty codegen because only the **keys** (the names) are needed, and they
 * are independent of the generator.
 */
export const EXPR_FUNCTION_NAMES: readonly string[] = Object.keys(functions({})).sort();

/** Every constant name the parser exposes (`PI`, `E`, `NaN`, …), sorted. */
export const EXPR_CONSTANT_NAMES: readonly string[] = Object.keys(constants).sort();

/**
 * Curated signatures for the commonly-typed functions, keyed by name. A subset of
 * `EXPR_FUNCTION_NAMES`; functions absent here still complete as bare names, just
 * without parameter hints.
 */
export const EXPR_SIGNATURES: Readonly<Record<string, FunctionSignature>> = Object.freeze(
  Object.fromEntries(
    (
      [
        {
          name: 'if',
          params: ['test', 'thenValue', 'elseValue'],
          doc: 'Conditional: thenValue when test is truthy, else elseValue.',
        },
        { name: 'isNaN', params: ['value'], doc: 'True when value is NaN.' },
        { name: 'isFinite', params: ['value'], doc: 'True when value is a finite number.' },
        { name: 'abs', params: ['value'], doc: 'Absolute value.' },
        { name: 'ceil', params: ['value'], doc: 'Round up to the nearest integer.' },
        { name: 'floor', params: ['value'], doc: 'Round down to the nearest integer.' },
        { name: 'round', params: ['value'], doc: 'Round to the nearest integer.' },
        { name: 'sqrt', params: ['value'], doc: 'Square root.' },
        { name: 'pow', params: ['base', 'exponent'], doc: 'base raised to exponent.' },
        { name: 'min', params: ['values'], variadic: true, doc: 'Smallest of the arguments.' },
        { name: 'max', params: ['values'], variadic: true, doc: 'Largest of the arguments.' },
        {
          name: 'clamp',
          params: ['value', 'min', 'max'],
          doc: 'Restrict value to the [min, max] range.',
        },
        { name: 'random', params: [], doc: 'A pseudo-random number in [0, 1).' },
        {
          name: 'parseFloat',
          params: ['string'],
          doc: 'Parse a floating-point number from a string.',
        },
        {
          name: 'parseInt',
          params: ['string', 'base'],
          doc: 'Parse an integer from a string (optional radix).',
        },
        { name: 'length', params: ['value'], doc: 'Length of an array or string.' },
        { name: 'lower', params: ['string'], doc: 'Lower-cased string.' },
        { name: 'upper', params: ['string'], doc: 'Upper-cased string.' },
        {
          name: 'substring',
          params: ['string', 'start', 'end'],
          doc: 'Substring from start up to (optional) end.',
        },
        {
          name: 'split',
          params: ['string', 'separator', 'limit'],
          doc: 'Split a string into an array on separator.',
        },
        {
          name: 'trim',
          params: ['string'],
          doc: 'String with leading/trailing whitespace removed.',
        },
        {
          name: 'regexp',
          params: ['pattern', 'flags'],
          doc: 'Build a RegExp from a pattern (optional flags).',
        },
        {
          name: 'test',
          params: ['regexp', 'string'],
          doc: 'True when the RegExp matches the string.',
        },
        { name: 'now', params: [], doc: 'Current timestamp (ms since the Unix epoch).' },
        {
          name: 'datetime',
          params: ['year', 'month', 'day', 'hours', 'minutes', 'seconds', 'milliseconds'],
          doc: 'A Date from local-time components (month is 0-based).',
        },
        { name: 'time', params: ['datetime'], doc: 'Timestamp (ms) of a date value.' },
        { name: 'year', params: ['datetime'], doc: 'Year of a date value (local time).' },
        {
          name: 'month',
          params: ['datetime'],
          doc: 'Month of a date value, 0-based (local time).',
        },
        {
          name: 'date',
          params: ['datetime'],
          doc: 'Day of the month of a date value (local time).',
        },
        {
          name: 'day',
          params: ['datetime'],
          doc: 'Day of the week of a date value, 0 = Sunday (local time).',
        },
        { name: 'hours', params: ['datetime'], doc: 'Hours of a date value (local time).' },
        { name: 'minutes', params: ['datetime'], doc: 'Minutes of a date value (local time).' },
        { name: 'seconds', params: ['datetime'], doc: 'Seconds of a date value (local time).' },
      ] satisfies FunctionSignature[]
    ).map((sig) => [sig.name, Object.freeze(sig)]),
  ),
);

/**
 * The human-readable call signature, e.g. `if(test, thenValue, elseValue)` or
 * `min(...values)`. Used as the completion detail and the signature-help label.
 */
export function signatureLabel(sig: FunctionSignature): string {
  const params = sig.params.map((p, i) =>
    sig.variadic && i === sig.params.length - 1 ? `...${p}` : p,
  );
  return `${sig.name}(${params.join(', ')})`;
}
