// Detect `output/...` workspace paths in plain text or markdown.
//
// Used by the LinkifiedText component (for plain-text contexts like the
// Output tab + Subtask instruction blocks) and by the MarkdownText
// renderer (for comment / timeline / agent-result bodies). Both turn
// matches into clickable anchors that the OutputPreviewProvider opens.
//
// Why only `output/`: users overwhelmingly hit this on agent-produced
// artifacts; `input/` / `logs/` paths showing up in agent text are
// usually citations the user doesn't need to open from inline. Adding
// more roots is a one-line change here if that ever flips.

/**
 * Matches `output/<anything>.<ext>` boundary-aware:
 *   - Must not be preceded by `/` (rules out URL-embedded paths like
 *     `https://example.com/output/foo.md`).
 *   - Path body excludes whitespace and common trailing punctuation
 *     (`)`, `]`, `}`, quotes, Japanese 、。 etc).
 *   - Extension: 1-8 word chars (covers `md` / `csv` / `jsonl` /
 *     `pdf` / `xlsx` / `html` etc; excludes long suffixes that are
 *     almost always false positives).
 *
 * Global flag so we can `String.matchAll` over a long buffer.
 */
export const OUTPUT_PATH_REGEX = /(?<![/\w])output\/[^\s)>\]\}"'`、。]+?\.[A-Za-z0-9]{1,8}\b/g;

export interface TextSegment {
  type: 'text';
  text: string;
}
export interface PathSegment {
  type: 'path';
  /** Full matched path, e.g. `output/foo.md`. Always starts with `output/`. */
  path: string;
}
export type Segment = TextSegment | PathSegment;

/**
 * Split a plain string into alternating text + path segments. The
 * concatenation of all segment texts equals the original input.
 *
 * Returns a single text segment when no paths match — callers don't
 * need to special-case empty results.
 */
export function splitTextByOutputPaths(text: string): Segment[] {
  if (!text) return [{ type: 'text', text: '' }];
  const segments: Segment[] = [];
  let lastIndex = 0;
  // matchAll keeps the iterator stateless (vs. exec + lastIndex which
  // would break if the regex above is ever rebuilt without /g).
  for (const m of text.matchAll(OUTPUT_PATH_REGEX)) {
    const start = m.index ?? 0;
    if (start > lastIndex) {
      segments.push({ type: 'text', text: text.slice(lastIndex, start) });
    }
    segments.push({ type: 'path', path: m[0] });
    lastIndex = start + m[0].length;
  }
  if (lastIndex < text.length) {
    segments.push({ type: 'text', text: text.slice(lastIndex) });
  }
  return segments.length > 0 ? segments : [{ type: 'text', text }];
}

/**
 * Strip the leading `output/` from a matched path so it can be passed
 * to `previewLocalFile` with section='output' (the FilePreview API
 * expects the path relative to the section root).
 */
export function stripOutputPrefix(matched: string): string {
  return matched.startsWith('output/') ? matched.slice('output/'.length) : matched;
}

/** Escape a single character for embedding in HTML attribute / text. */
function escapeHtml(s: string): string {
  return s
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

/**
 * HTML-safe linkification of `output/...` paths in a string. Used by
 * the Marked renderer overrides in markdown-text.tsx — the `text` /
 * `codespan` token values are already HTML-escaped by Marked, so we
 * pre-decode by treating them as already-safe text and only re-escape
 * the path text itself before embedding in the anchor.
 *
 * The returned anchor has `data-output-path="..."` for delegated click
 * handling by OutputPreviewProvider; no `href` (would briefly flash
 * `#` in the status bar). `role="button"` + `tabindex="0"` so keyboard
 * users can reach it.
 */
export function linkifyOutputPathsInEscapedHtml(escapedHtml: string): string {
  // The regex is built around literal ASCII chars that survive HTML
  // escaping unchanged (`/`, `.`, word chars). So we can run it directly
  // on the already-escaped HTML string.
  return escapedHtml.replace(OUTPUT_PATH_REGEX, (match) => {
    const safePath = escapeHtml(match);
    return `<a class="output-path-link" data-output-path="${safePath}" role="button" tabindex="0">${safePath}</a>`;
  });
}
