/**
 * registerWatch — turn a just-run `async` tool into a background watch.
 *
 * Called by the tool-dispatcher right after a tool with an `async` block
 * runs (and detaches). Resolves the spec's templates against the
 * trigger's args/result, enforces the global active cap and chain-depth
 * guard, and persists a watch row. The watch-runner (chat-standalone)
 * picks it up on the next tick. No dependency on the runner or
 * tool-dispatcher → no import cycle.
 */

import type { AsyncWatchSpec } from '../connectors/types';
import { resolveDeep, resolveTemplate } from './template';
import { createWatch, countActive, type WatchAction } from './watch-store';

export const MAX_ACTIVE_WATCHES = 50;
export const DEFAULT_CHAIN_DEPTH = 3;
const MIN_INTERVAL_SEC = 30;
const DEFAULT_INTERVAL_SEC = 60;
const DEFAULT_TIMEOUT_SEC = 1200;
const DEFAULT_MAX_POLLS = 40;

export interface RegisterWatchCtx {
  spec: AsyncWatchSpec;
  connectorId: string;
  toolName: string;
  args: Record<string, unknown>;          // trigger tool input
  result: unknown;                          // trigger tool result (parsed)
  settings: Record<string, unknown>;
  sessionId: string | null;
  chainDepth: number;                       // remaining chain budget
}

export type RegisterResult =
  | { ok: true; watch_id: string; label: string }
  | { ok: false; reason: string };

function num(v: unknown, dflt: number): number {
  const n = Number(v);
  return Number.isFinite(n) && n > 0 ? n : dflt;
}

export function registerWatch(ctx: RegisterWatchCtx): RegisterResult {
  const { spec } = ctx;
  if (!spec || !spec.poll) return { ok: false, reason: 'no async.poll declared' };
  if (ctx.chainDepth <= 0) return { ok: false, reason: 'chain depth exhausted' };
  if (countActive() >= MAX_ACTIVE_WATCHES) {
    return { ok: false, reason: `active watch limit reached (${MAX_ACTIVE_WATCHES})` };
  }

  // Templating context for register-time resolution.
  const regCtx = { args: ctx.args, result: ctx.result, settings: ctx.settings };

  const pollArgs = (resolveDeep(spec.poll_args || {}, regCtx) as Record<string, unknown>) || {};

  let doneMatch: { path: string; equals?: string; contains?: string } | null = null;
  if (spec.done_match && spec.done_match.path) {
    doneMatch = {
      path: spec.done_match.path,
      ...(spec.done_match.equals != null ? { equals: resolveTemplate(String(spec.done_match.equals), regCtx) } : {}),
      ...(spec.done_match.contains != null ? { contains: resolveTemplate(String(spec.done_match.contains), regCtx) } : {}),
    };
  }

  const interval = Math.max(MIN_INTERVAL_SEC, num(spec.interval_sec, DEFAULT_INTERVAL_SEC));
  const timeout = num(spec.timeout_sec, DEFAULT_TIMEOUT_SEC);
  const maxPolls = num(spec.max_polls, DEFAULT_MAX_POLLS);

  // Label: connector.tool + a hint (host/lab/ip) pulled from args if present.
  const hint = ['host', 'lab', 'ip', 'name'].map((k) => ctx.args[k]).find((v) => typeof v === 'string' && v);
  const label = `${ctx.connectorId}.${ctx.toolName}${hint ? ` ${hint}` : ''}`;

  // Pre-resolve {args.*}/{result.*}/{settings.*} in messages/action-args NOW
  // (they're fixed at register time). {poll.*}/{poll_count}/{max_polls} are
  // left literal for the runner to fill per poll — resolveTemplate keeps
  // tokens whose namespace isn't in the context, so the two passes compose.
  const preAction = (a: WatchAction | undefined): WatchAction | null => {
    if (!a) return null;
    return {
      ...a,
      ...(a.message ? { message: resolveTemplate(a.message, regCtx) } : {}),
      ...(a.args ? { args: resolveDeep(a.args, regCtx) as Record<string, unknown> } : {}),
    };
  };
  const preProgress = spec.progress
    ? { ...spec.progress, ...(spec.progress.message ? { message: resolveTemplate(spec.progress.message, regCtx) } : {}) }
    : null;

  const w = createWatch({
    session_id: ctx.sessionId,
    label,
    connector_id: ctx.connectorId,
    poll_tool: spec.poll,
    poll_args: pollArgs,
    done_path: spec.done_path ?? null,
    done_match: doneMatch,
    fail_path: spec.fail_path ?? null,
    on_done: preAction(spec.on_done as WatchAction),
    on_fail: preAction(spec.on_fail as WatchAction),
    progress: preProgress,
    interval_sec: interval,
    timeout_sec: timeout,
    max_polls: maxPolls,
    chain_depth: ctx.chainDepth,
    now: Date.now(),
  });
  return { ok: true, watch_id: w.id, label };
}
