/**
 * # series/types — the value model of the series layer (RUNTIME §2–§3)
 *
 * Two small, load-bearing types and the units bridge shared by every file in this module.
 *
 * - {@link UNKNOWN} — the single sentinel for "insufficient data / unresolvable". It is a
 *   value, distinct from `false`: a series with too little history reads UNKNOWN, never a
 *   guess (RUNTIME §2), and a trigger over an UNKNOWN operand evaluates UNKNOWN, never
 *   silently false (RUNTIME §3). Only a definite `true` fires.
 * - {@link Resolved} — what a series resolves to: a `number` (a market/org scalar), a
 *   `string` (a symbolic state value like a plan-state or regime tag), or {@link UNKNOWN}.
 * - {@link TriState} — the result of evaluating a trigger: `true | false | UNKNOWN`.
 *
 * All time is injected (RUNTIME §0): {@link durationMs} is the one place a `Window` or
 * `Duration` value+unit is turned into milliseconds, so every window/held/within/esc span
 * measures identically.
 */

import type { TimeUnit } from "../lang/index.ts";

// ─────────────────────────────────────────────────────────────────────────────
// UNKNOWN — the third truth value / the missing scalar
// ─────────────────────────────────────────────────────────────────────────────

/** The sole "insufficient data / unresolvable" sentinel. A unique symbol so it can never
 * be confused with a real string value, a `0`, or a `false`. */
export const UNKNOWN: unique symbol = Symbol("kestrel.UNKNOWN");
export type Unknown = typeof UNKNOWN;

/** A concrete series value: a numeric market/org scalar, or a symbolic state string
 * (a plan-state literal like `armed`, a regime tag value). */
export type SeriesValue = number | string;

/** What {@link import("./provider.ts").SeriesProvider.resolve} returns. */
export type Resolved = SeriesValue | Unknown;

/** The tri-state result of a trigger evaluation (RUNTIME §3). Only `true` fires. */
export type TriState = boolean | Unknown;

/** Is `x` the UNKNOWN sentinel? */
export function isUnknown(x: unknown): x is Unknown {
  return x === UNKNOWN;
}

/** Is `x` a resolved *number* (not a string, not UNKNOWN)? */
export function isNumber(x: Resolved): x is number {
  return typeof x === "number";
}

// ─────────────────────────────────────────────────────────────────────────────
// Units bridge — the one time→ms conversion (RUNTIME §0: all time injected)
// ─────────────────────────────────────────────────────────────────────────────

/** Milliseconds per {@link TimeUnit}. Calendar units use fixed nominal spans (a month is
 * 30d, a quarter 90d, a year 365d) — windows are trailing measurement horizons, not
 * calendar arithmetic, so a fixed nominal span is the honest, deterministic choice. */
const MS: Readonly<Record<TimeUnit, number>> = {
  s: 1_000,
  m: 60_000,
  h: 3_600_000,
  d: 86_400_000,
  w: 604_800_000,
  mo: 2_592_000_000, // 30d
  q: 7_776_000_000, // 90d
  y: 31_536_000_000, // 365d
};

/** Convert a `{ value, unit }` window/duration to milliseconds. */
export function durationMs(value: number, unit: TimeUnit): number {
  return value * MS[unit];
}
