// Feedback client-side limits and the attachment pre-upload gate. The gate is by
// file extension only (the UI does not list the allowed types and never uploads a
// rejected file); the server re-checks the same limits as the trust boundary.

/** MAX_BODY caps the feedback message length, in characters (matches the backend). */
export const MAX_BODY = 1024;

/** MAX_ATTACHMENT_BYTES caps a single attachment's size (matches the backend). */
export const MAX_ATTACHMENT_BYTES = 1_000_000;

// Allowed attachment extensions, mirrored from the backend allow-list.
const ALLOWED_EXT = new Set([
  'png', 'jpg', 'jpeg', 'webp', 'gif', // images
  'pdf', 'txt', 'log', 'doc', 'docx', 'rtf', 'zip', 'gz', '7z',
]);

/**
 * attachmentError validates a picked file by name and size, returning 'type' for a
 * disallowed (or missing) extension, 'size' for a too-large file, or '' when it is
 * acceptable. The caller shows a single generic "cannot attach" message regardless
 * of which non-empty reason it is, so the allowed types are never revealed.
 */
export function attachmentError(name: string, size: number): '' | 'type' | 'size' {
  const dot = name.lastIndexOf('.');
  const ext = dot >= 0 ? name.slice(dot + 1).toLowerCase() : '';
  if (!ext || !ALLOWED_EXT.has(ext)) return 'type';
  if (size > MAX_ATTACHMENT_BYTES) return 'size';
  return '';
}

/** ReplySegment is one run of the operator reply: plain text, or a link when href is set. */
export interface ReplySegment {
  text: string;
  href?: string;
}

// Links are recognised only for an explicit, whitelisted scheme, so dangerous ones
// (javascript:, data:, vbscript:) are never turned into an href. http/https/ftp use
// "scheme://host…"; mailto/tel use "scheme:rest". Matching stops at whitespace or '<'.
// The reply is operator-authored, so explicit schemes (no bare-domain guessing) are
// enough — which keeps this a few lines with no dependency.
const URL_RE = /\b(?:(?:https?|ftp):\/\/|(?:mailto|tel):)[^\s<]+/gi;
// Trailing punctuation that should not be part of the URL (kept as following text).
const TRAILING_RE = /[.,!?;:)\]}'"»]+$/;

/**
 * linkify splits the operator reply into segments, marking http(s) URLs as links so
 * the UI renders them as anchors (opened in a new tab) while everything else stays
 * plain, escaped text. It never emits markup — the caller renders each segment with
 * the framework's escaping — so it cannot introduce XSS.
 */
export function linkify(text: string): ReplySegment[] {
  const out: ReplySegment[] = [];
  let last = 0;
  for (const m of text.matchAll(URL_RE)) {
    const start = m.index;
    let url = m[0];
    const trail = TRAILING_RE.exec(url)?.[0] ?? '';
    if (trail) url = url.slice(0, url.length - trail.length);
    if (start > last) out.push({ text: text.slice(last, start) });
    out.push({ text: url, href: url });
    if (trail) out.push({ text: trail });
    last = start + m[0].length;
  }
  if (last < text.length) out.push({ text: text.slice(last) });
  if (out.length === 0) out.push({ text });
  return out;
}
