/**
 * Error Recovery Rules — highest priority
 * Maps error patterns to actionable recovery hints.
 */

import type { HintRule } from '../hint-engine';

/**
 * Errors prefixed with `uid_evicted:` come from the backend-node uid contract
 * (#844) and already carry a structured payload pointing at the new
 * loaderId. Suppress the generic stale-ref hint so we do not double-report
 * the same condition.
 */
const UID_EVICTED_PREFIX = /^\s*uid_evicted\b/i;

const patterns: Array<{ test: RegExp; hint: string }> = [
  {
    test: /ref\b.+not found|invalid ref|stale ref/i,
    hint: 'Hint: Refs expire after page changes. Use read_page or find for fresh refs.',
  },
  {
    test: /tab\b.+not found|target\b.+not found|invalid tab|no such tab/i,
    hint: 'Hint: Use tabs_context to list valid tabIds, or navigate to open a new page.',
  },
  {
    test: /selector\b.*(failed|not found|no match)|querySelectorAll.*returned 0|no elements? (found )?match/i,
    hint: 'Hint: Try find(query) with natural language instead.',
  },
  {
    test: /no clickable elements found|no good match found|click element error/i,
    hint: 'Hint: Element may not be loaded. Try interact with waitForMs parameter, or read_page mode="dom" to verify.',
  },
  {
    test: /captureScreenshot.*timed?\s*out|screenshot.*timed?\s*out|screenshot failed/i,
    hint: 'Hint: Screenshot timed out on a slow page. Use read_page mode="dom" for instant page state without rendering. For heavy pages (Next.js dev, SPAs), always prefer read_page over screenshot.',
  },
  {
    test: /timeout|timed?\s*out|navigation timeout/i,
    hint: 'Hint: Page timed out. Try wait_for with type "selector" to wait for specific content, or navigate to a simpler URL first.',
  },
  {
    test: /cannot read propert|null is not|undefined is not|is null|is undefined/i,
    hint: 'Hint: Element is null. Use find or read_page to verify.',
  },
  {
    test: /coordinate|click at position|x,?\s*y/i,
    hint: 'Hint: Use interact(action:"click", query:"...") instead — finds and clicks in one step.',
  },
  {
    test: /^\s*\{\s*\}\s*$|"result":\s*\{\s*\}/,
    hint: 'Hint: Empty object may indicate an async result. Wrap code in async IIFE: (async () => { return await ... })()',
  },
  {
    test: /await is only valid in async/i,
    hint: 'Hint: Top-level await is not supported in javascript_tool. Use Promise chaining instead: new Promise(r => setTimeout(r, ms)).then(() => yourCode). Or wrap in async IIFE: (async () => { await ...; return result; })()',
  },
  {
    test: /not editable|is not editable/i,
    hint: 'Hint: This element is not a form input (e.g., SVG, div, span). Use find to locate the correct input field, or javascript_tool to set the value directly.',
  },
  {
    test: /layout object|could not get position/i,
    hint: 'Hint: Element is hidden (display:none or detached). Use find to locate a visible equivalent, or javascript_tool to interact programmatically.',
  },
];

export const errorRecoveryRules: HintRule[] = patterns.map((p, i) => ({
  name: `error-recovery-${i}`,
  priority: 100 + i,
  match(ctx) {
    if (!ctx.isError) return null;
    // The first pattern is the stale-ref rule (i === 0). The structured
    // `uid_evicted:` error from the backend-node uid contract (#844)
    // already informs the caller about navigation epoch rotation, so we
    // suppress the generic stale-ref hint when that prefix is present.
    if (i === 0 && UID_EVICTED_PREFIX.test(ctx.resultText)) return null;
    return p.test.test(ctx.resultText) ? p.hint : null;
  },
}));
