const {
  normalizeLink,
  absoluteInternalUrl,
  routeLabel,
  stripTags
} = require("./link_normalizer");

const METHODS = /\b(?:GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)\s+(\/[^\s<>"')\]]+)/gi;
const MARKDOWN_LINK = /\[([^\]]+)\]\(([^)]+)\)/g;
const HTML_LINK = /<a\b[^>]*\bhref\s*=\s*(["'])(.*?)\1[^>]*>(.*?)<\/a>/gi;
const REPO_PATH = /\b(?:src|plugins|scripts|data)\/[a-z0-9_./-]+\.(?:js|json|ejs|md|css|txt)\b/gi;
const SOURCE_DETAIL = /\s*\((?:src|plugins|scripts)\/[^)]+\)/gi;

function formatAssistantResponse({
  text,
  links = [],
  baseUrl = "",
  verifiedRoutes = [],
  role = "user",
  allowModeratorCodeHelp = false,
  maxLength = null
}) {
  const routeSet = new Set(verifiedRoutes);
  const collected = [];
  let output = normalizeCodeFences(String(text || ""));

  output = output.replace(HTML_LINK, (_match, _quote, href, label) => {
    const normalized = collectLink(collected, { href, label: stripTags(label), external: true }, baseUrl, routeSet);
    return normalized ? markdownLink(normalized) : stripTags(label);
  });
  output = output.replace(MARKDOWN_LINK, (_match, label, href) => {
    const normalized = collectLink(collected, { href, label, external: true }, baseUrl, routeSet);
    return normalized ? markdownLink(normalized) : label;
  });
  output = output.replace(METHODS, (_match, route) => {
    const punctuation = route.match(/[.,;:!?]+$/)?.[0] || "";
    const cleanRoute = punctuation ? route.slice(0, -punctuation.length) : route;
    const normalized = collectLink(collected, { href: cleanRoute, label: routeLabel(cleanRoute) }, baseUrl, routeSet);
    return normalized ? `${markdownLink(normalized)}${punctuation}` : route;
  });
  for (const link of Array.isArray(links) ? links : []) {
    collectLink(collected, link, baseUrl, routeSet);
  }

  output = output
    .replace(/<br\s*\/?>/gi, "\n")
    .replace(/<\/p\s*>/gi, "\n\n")
    .replace(/<[^>]*>/g, "");
  if (role !== "admin") {
    output = output.replace(REPO_PATH, "internal Lumi component").replace(SOURCE_DETAIL, "");
    if (role === "user" || !allowModeratorCodeHelp) {
      output = output.replace(/```[\s\S]*?```/g, "Technical implementation details are available to administrators.");
    }
  }
  output = output.replace(/[ \t]+\n/g, "\n").replace(/\n{4,}/g, "\n\n\n").trim();

  const originalLength = output.length;
  const constrained = constrainFinal(output, maxLength);
  const delivered = constrained.text;
  return {
    text: delivered,
    links: uniqueLinks(collected),
    original_final_length: originalLength,
    delivered_length: delivered.length,
    truncated: delivered.length < originalLength,
    delivery_action: constrained.action
  };
}

function collectLink(output, link, baseUrl, routeSet) {
  const normalized = normalizeLink(link, baseUrl, routeSet);
  if (normalized) output.push(normalized);
  return normalized;
}

function truncateFinal(value, maxLength) {
  return constrainFinal(value, maxLength).text;
}

function constrainFinal(value, maxLength) {
  const output = String(value || "");
  const requestedLimit = Number(maxLength);
  if (!Number.isFinite(requestedLimit) || requestedLimit <= 0 || output.length <= requestedLimit) {
    return { text: output, action: "none" };
  }
  const limit = Math.max(100, Number.parseInt(requestedLimit, 10) || 4000);
  const suffix = " [Reply shortened for platform limits.]";
  const available = Math.max(1, limit - suffix.length);
  const candidate = output.slice(0, available);
  const minimumBoundary = Math.floor(available * 0.55);
  const sentenceBoundary = lastBoundary(candidate, /(?:[.!?][\])}"']?|\n\n)(?=\s|$)/g, minimumBoundary);
  const lineBoundary = lastBoundary(candidate, /\n/g, minimumBoundary);
  const wordBoundary = candidate.lastIndexOf(" ");
  const boundary = sentenceBoundary >= minimumBoundary
    ? sentenceBoundary
    : lineBoundary >= minimumBoundary
      ? lineBoundary
      : wordBoundary >= minimumBoundary
        ? wordBoundary
        : available;
  return {
    text: `${candidate.slice(0, boundary).trimEnd()}${suffix}`,
    action: "compacted"
  };
}

function lastBoundary(value, pattern, minimum) {
  let boundary = -1;
  for (const match of value.matchAll(pattern)) {
    const end = (match.index || 0) + match[0].length;
    if (end >= minimum) boundary = end;
  }
  return boundary;
}

function markdownLink(link) {
  const label = String(link.label || "").replace(/[[\]]/g, "");
  const href = String(link.href || "").replace(/[<>\s]/g, (character) => encodeURIComponent(character));
  return `[${label}](${href})`;
}

function uniqueLinks(links) {
  return [...new Map(links.map((link) => [link.href, link])).values()];
}

function normalizeCodeFences(value) {
  return value.replace(/```(javascript|python)\s+([^\n][\s\S]*?)```/gi, (_match, language, code) =>
    `\`\`\`${language.toLowerCase()}\n${code.trim()}\n\`\`\``);
}

module.exports = {
  formatAssistantResponse,
  normalizeLink,
  truncateFinal,
  constrainFinal,
  absoluteInternalUrl,
  normalizeCodeFences
};
