/**
 * Chat auto-link patterns — turn natural prose like `Mantis #1226625`,
 * `MR !14860`, `bug 1234567`, `CVE-2024-0001` into clickable links.
 *
 * Disambiguation: every pattern requires a prose anchor word — `Mantis`,
 * `bug`, `MR`, `merge request`, `issue`, `BDSA-`, `CVE-`. We intentionally
 * do NOT match bare `#N` (ambiguous: GitHub issue / GitLab / Mantis).
 *
 * Rules live in this single file (not in connector manifests) so adding
 * a new pattern doesn't require bumping any connector version. Patterns
 * referencing a connector's base_url (`baseUrlFrom: 'mantis'`) are
 * auto-skipped when that connector is not installed.
 *
 * URL template syntax:
 *   {0}            → the entire regex match
 *   {1}, {2}, ...  → capture groups
 *   {base_url}     → connector's installed base_url, trailing slash stripped
 *
 * The chat system prompt also injects a "Reference Format" guide built
 * from the same connector base_urls (see lib/chat/reference-prompt.ts)
 * so the LLM is nudged to emit markdown links directly — these regexes
 * are a safety net for plain-text mentions.
 */

import { getInstalledConnector } from '../connectors/registry';

export interface LinkPattern {
  id: string;
  /** Regex with the `g` flag — required so we can iterate all matches. */
  regex: RegExp;
  /** Connector id whose `config.base_url` populates `{base_url}` in `url`.
   *  Omit when the pattern is self-contained (e.g. CVE → nvd.nist.gov). */
  baseUrlFrom?: string;
  /** URL template — see syntax above. */
  url: string;
  /** Tooltip template (same syntax). Defaults to the matched text. */
  label?: string;
}

/** Raw patterns. `baseUrlFrom` is resolved by getActiveLinkPatterns. */
export const LINK_PATTERNS: LinkPattern[] = [
  {
    // Matches "Mantis #1226625", "mantis bug 1226625", "bug #1234567",
    // "bug 1234567". 4–8 digits caps false positives on small numbers
    // like list indices ("bug 3" etc.).
    id: 'mantis-bug',
    regex: /\b(?:mantis(?:\s+bug)?\s*#?|bug\s*#?)(\d{4,8})\b/gi,
    baseUrlFrom: 'mantis',
    url: '{base_url}/bug_view_page.php?bug_id={1}',
    label: 'Mantis #{1}',
  },
  {
    // Matches "MR !14860", "MR 14860", "merge request !14860", "!14860".
    // The lookbehind on the bare-`!N` form keeps `not!42` / `1!42` /
    // `foo!42bar` out, and allows CJK-adjacent (`测试!14860`).
    id: 'gitlab-mr',
    regex: /(?:\b(?:MR|merge\s+request)\s+!?|(?<![A-Za-z0-9])!)(\d+)\b/gi,
    baseUrlFrom: 'gitlab',
    url: '{base_url}/-/merge_requests/{1}',
    label: 'MR !{1}',
  },
  {
    // Matches "issue #789", "issue 789", "gitlab issue #789". 3+ digits
    // minimum keeps "issue 1" / "issue 12" out (too noisy).
    id: 'gitlab-issue',
    regex: /\b(?:gitlab\s+issue\s*#?|gl\s*issue\s*#?|issue\s*#?)(\d{3,})\b/gi,
    baseUrlFrom: 'gitlab',
    url: '{base_url}/-/issues/{1}',
    label: 'Issue #{1}',
  },
  {
    id: 'blackduck-bdsa',
    regex: /\b(BDSA-\d{4}-\d+)\b/g,
    baseUrlFrom: 'blackduck',
    url: '{base_url}/api/vulnerabilities/{1}',
    label: '{1}',
  },
  {
    id: 'cve',
    regex: /\b(CVE-\d{4}-\d+)\b/g,
    url: 'https://nvd.nist.gov/vuln/detail/{1}',
    label: '{1}',
  },
  // Forge-managed files inside <dataDir>/. LLMs emit:
  //   • `scratch/foo.md`  — legacy task-workspace writes (kept under
  //                         <dataDir>/scratch/, served by /scratch viewer)
  //   • `tmp/foo.md`      — chat's save_tmp_file output (<dataDir>/tmp/)
  //   • `flows/x.yaml`    — pipeline workflow definitions
  //   • `prompts/y.yaml`  — schedule prompt bodies
  // All four resolve through the /files/<dataDir-path> viewer (rendered
  // markdown + download button), which fetches /api/files for raw bytes.
  // Sensitive top-level items (encrypt key, sqlite DBs, log, token
  // caches) are blocked at the API layer, so safe to be liberal here.
  {
    id: 'scratch-file',
    regex: /\bscratch\/([\w\-./]+?\.(?:md|txt|json|yaml|yml|csv|log|html|pdf|png|jpg|jpeg|gif|svg))\b/gi,
    url: '/scratch/{1}',
    label: 'scratch/{1}',
  },
  {
    id: 'tmp-file',
    regex: /\btmp\/([\w\-./]+?\.(?:md|txt|json|yaml|yml|csv|log|html|pdf|png|jpg|jpeg|gif|svg))\b/gi,
    url: '/files/tmp/{1}',
    label: 'tmp/{1}',
  },
  {
    id: 'flows-file',
    regex: /\bflows\/([\w\-./]+?\.(?:yaml|yml|json))\b/gi,
    url: '/files/flows/{1}',
    label: 'flows/{1}',
  },
  {
    id: 'prompts-file',
    regex: /\bprompts\/([\w\-./]+?\.(?:yaml|yml|md|txt))\b/gi,
    url: '/files/prompts/{1}',
    label: 'prompts/{1}',
  },
];

export interface CompiledPattern {
  id: string;
  regex: RegExp;
  /** url template after {base_url} expansion */
  urlTemplate: string;
  labelTemplate: string;
}

/**
 * Resolve `baseUrlFrom` against installed connector configs and drop
 * patterns whose connector isn't configured. Returns the safe-to-render
 * set; UI fetches this once and reuses it per chat session.
 */
export function getActiveLinkPatterns(): CompiledPattern[] {
  const out: CompiledPattern[] = [];
  for (const p of LINK_PATTERNS) {
    let urlTemplate = p.url;
    if (p.baseUrlFrom) {
      const cfg = getInstalledConnector(p.baseUrlFrom)?.config as
        | { base_url?: string }
        | undefined;
      const base = (cfg?.base_url || '').trim().replace(/\/+$/, '');
      if (!base) continue;          // skip — connector unconfigured
      urlTemplate = urlTemplate.replace('{base_url}', base);
    }
    out.push({
      id: p.id,
      regex: p.regex,
      urlTemplate,
      labelTemplate: p.label ?? '{0}',
    });
  }
  return out;
}

/** Serializable form for sending to the browser. */
export interface SerializablePattern {
  id: string;
  source: string;       // regex.source — re-hydrated client-side
  flags: string;        // regex.flags
  url: string;
  label: string;
}

export function serializePatterns(patterns: CompiledPattern[]): SerializablePattern[] {
  return patterns.map(p => ({
    id: p.id,
    source: p.regex.source,
    flags: p.regex.flags.includes('g') ? p.regex.flags : p.regex.flags + 'g',
    url: p.urlTemplate,
    label: p.labelTemplate,
  }));
}
