const MAX_CONDITIONAL_REPLIES = 100;

function normalizeConditionalKey(value) {
  return String(value || "")
    .trim()
    .toLowerCase()
    .replace(/[^\p{L}\p{N}]+/gu, " ")
    .replace(/\s+/g, " ")
    .trim();
}

function splitConditionalKeywords(value) {
  return String(value || "")
    .split(",")
    .map((keyword) => keyword.trim().slice(0, 100))
    .filter(Boolean);
}

function normalizeConditionalReplies(value) {
  return normalizeConditionalReplyRows(value).flatMap((entry) => splitConditionalKeywords(entry.keyword).map((keyword) => ({
    keyword,
    normalized: normalizeConditionalKey(keyword),
    response: entry.response
  })));
}

function normalizeConditionalReplyRows(value) {
  let input = value;
  if (typeof input === "string") {
    try { input = JSON.parse(input); } catch { input = []; }
  }
  if (!Array.isArray(input)) return [];
  const seen = new Set();
  const rows = [];
  let aliasCount = 0;
  for (const entry of input) {
    const response = String(entry?.response || "").trim().slice(0, 4000);
    if (!response) continue;
    const aliases = [];
    for (const keyword of splitConditionalKeywords(entry?.keyword)) {
      const normalized = normalizeConditionalKey(keyword);
      if (!normalized || seen.has(normalized)) continue;
      seen.add(normalized);
      aliases.push(keyword);
      aliasCount += 1;
      if (aliasCount >= MAX_CONDITIONAL_REPLIES) break;
    }
    if (aliases.length) rows.push({ keyword: aliases.join(", "), response });
    if (aliasCount >= MAX_CONDITIONAL_REPLIES) break;
  }
  return rows;
}

function conditionalRepliesFromBody(body = {}) {
  const keywords = asArray(body.conditional_keyword);
  const responses = asArray(body.conditional_response);
  const replies = [];
  const errors = [];
  const seen = new Set();
  let aliasCount = 0;
  const count = Math.max(keywords.length, responses.length);
  for (let index = 0; index < count; index += 1) {
    const keyword = String(keywords[index] || "").trim();
    const aliases = splitConditionalKeywords(keyword);
    const response = String(responses[index] || "").trim();
    if (!keyword && !response) continue;
    if (!aliases.length || !response) {
      errors.push("Each conditional reply needs both a keyword and a response.");
      continue;
    }
    for (const alias of aliases) {
      const normalized = normalizeConditionalKey(alias);
      if (seen.has(normalized)) errors.push(`The keyword “${alias}” is listed more than once.`);
      seen.add(normalized);
    }
    aliasCount += aliases.length;
    replies.push({ keyword: aliases.join(", "), response: response.slice(0, 4000) });
  }
  if (!replies.length) errors.push("Add at least one keyword reply.");
  if (aliasCount > MAX_CONDITIONAL_REPLIES) errors.push(`Use no more than ${MAX_CONDITIONAL_REPLIES} keywords.`);
  return { ok: errors.length === 0, errors, replies };
}

function findConditionalReply(replies, argument, { fuzzy = true } = {}) {
  const normalizedArgument = normalizeConditionalKey(argument);
  if (!normalizedArgument) return { match: null, fuzzy: false };
  const normalizedReplies = normalizeConditionalReplies(replies);
  const exact = normalizedReplies.find((entry) => entry.normalized === normalizedArgument);
  if (exact) return { match: exact, fuzzy: false };
  if (!fuzzy) return { match: null, fuzzy: false };

  const candidates = normalizedReplies
    .map((entry) => ({ entry, score: similarity(normalizedArgument, entry.normalized) }))
    .sort((left, right) => right.score - left.score);
  const best = candidates[0];
  const runnerUp = candidates[1];
  const minimum = normalizedArgument.length <= 4 ? 0.82 : 0.68;
  if (!best || best.score < minimum || (runnerUp && best.score - runnerUp.score < 0.08)) {
    return { match: null, fuzzy: false };
  }
  return { match: best.entry, fuzzy: true };
}

function similarity(left, right) {
  if (left === right) return 1;
  const distance = levenshtein(left, right);
  return 1 - distance / Math.max(left.length, right.length, 1);
}

function levenshtein(left, right) {
  const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
  for (let i = 1; i <= left.length; i += 1) {
    let diagonal = previous[0];
    previous[0] = i;
    for (let j = 1; j <= right.length; j += 1) {
      const above = previous[j];
      previous[j] = Math.min(
        previous[j] + 1,
        previous[j - 1] + 1,
        diagonal + (left[i - 1] === right[j - 1] ? 0 : 1)
      );
      diagonal = above;
    }
  }
  return previous[right.length];
}

function asArray(value) {
  if (Array.isArray(value)) return value;
  return value === undefined || value === null ? [] : [value];
}

module.exports = {
  conditionalRepliesFromBody,
  findConditionalReply,
  normalizeConditionalKey,
  normalizeConditionalReplies,
  normalizeConditionalReplyRows,
  splitConditionalKeywords
};
