/**
 * Template substitution for connector manifest fields.
 *
 * Expands tokens that depend on user settings ({base_url}, {settings.*}) when
 * Forge serves the manifest to the extension. Tokens that depend on the LLM's
 * tool arguments ({args.*}) are left literal — the extension's runner expands
 * those at execution time.
 *
 * See docs/Connector-DeclarativeExtract-Spec.md §4.
 */

type SettingsMap = Record<string, any>;

/**
 * Expand {base_url} and {settings.<key>} tokens in a string. Other tokens
 * (including {args.*}) are passed through unchanged so the extension can
 * finish substitution at run time.
 */
export function expandSettingsTokens(template: string, settings: SettingsMap | undefined): string {
  if (!template || !settings) return template;
  return template.replace(/\{([^{}]+)\}/g, (full, raw) => {
    const key = String(raw).trim();
    if (key === 'base_url') {
      const v = settings.base_url;
      return typeof v === 'string' ? stripTrailingSlashes(v) : full;
    }
    if (key.startsWith('settings.')) {
      const path = key.slice('settings.'.length);
      const v = settings[path];
      return typeof v === 'string' ? stripTrailingSlashes(v) : full;
    }
    return full;
  });
}

function stripTrailingSlashes(s: string): string {
  return s.replace(/\/+$/, '');
}

/**
 * Expand all tokens — `{base_url}`, `{settings.*}`, `{args.*}` — in one
 * pass. Used by server-side protocols (http, shell) where Forge has both
 * the settings and the LLM's parsed arguments in hand.
 *
 * Unknown tokens are left literal.
 *
 * `{args.*}` supports dotted paths (e.g. `{args.repo.full_name}`).
 * Non-string values are JSON-encoded for object/array, coerced for
 * number/boolean. base_url and settings get trailing slashes stripped;
 * args do not (an args value is whatever the LLM passed).
 */
export function expandAllTokens(
  template: string,
  settings: SettingsMap | undefined,
  args: SettingsMap | undefined,
): string {
  if (!template) return template;
  return template.replace(/\{([^{}]+)\}/g, (full, raw) => {
    const key = String(raw).trim();
    if (key === 'base_url') {
      const v = settings?.base_url;
      return typeof v === 'string' ? stripTrailingSlashes(v) : full;
    }
    if (key.startsWith('settings.')) {
      const v = settings?.[key.slice('settings.'.length)];
      if (v == null) return full;
      return typeof v === 'string' ? v : String(v);
    }
    if (key.startsWith('args.')) {
      const path = key.slice('args.'.length).split('.');
      let v: any = args;
      for (const p of path) {
        if (v == null || typeof v !== 'object') { v = undefined; break; }
        v = v[p];
      }
      if (v == null) return full;
      if (typeof v === 'string') return v;
      if (typeof v === 'number' || typeof v === 'boolean') return String(v);
      try { return JSON.stringify(v); } catch { return full; }
    }
    return full;
  });
}
