/**
 * Tiny namespaced template resolver for watch specs. Replaces tokens
 * like {args.x}, {result.fired_at}, {poll.build}, {settings.host} from a
 * context of namespaces. Dot paths supported. Unknown tokens are left
 * literal (so a typo is visible rather than silently empty). No eval.
 */

export function getPath(obj: unknown, path: string): unknown {
  let v: any = obj;
  for (const p of path.split('.')) {
    if (v == null || typeof v !== 'object') return undefined;
    v = v[p];
  }
  return v;
}

export function resolveTemplate(str: string, ctx: Record<string, unknown>): string {
  return str.replace(/\{([^{}]+)\}/g, (full, raw) => {
    const key = String(raw).trim();
    const dot = key.indexOf('.');
    const ns = dot < 0 ? key : key.slice(0, dot);
    const rest = dot < 0 ? '' : key.slice(dot + 1);
    if (!(ns in ctx)) return full;
    const v = rest ? getPath(ctx[ns], rest) : ctx[ns];
    if (v == null) return full;
    return typeof v === 'object' ? JSON.stringify(v) : String(v);
  });
}

/** Resolve templates throughout a value (strings, arrays, plain objects). */
export function resolveDeep(val: unknown, ctx: Record<string, unknown>): unknown {
  if (typeof val === 'string') return resolveTemplate(val, ctx);
  if (Array.isArray(val)) return val.map((v) => resolveDeep(v, ctx));
  if (val && typeof val === 'object') {
    const out: Record<string, unknown> = {};
    for (const [k, v] of Object.entries(val)) out[k] = resolveDeep(v, ctx);
    return out;
  }
  return val;
}
