// Офлайн lite-режим: анализ документа БЕЗ внешних ИИ-вызовов.
// Извлечение по регуляркам/ключевым словам + поиск по тексту с цитатами [с. N].
// Работает сразу, без ключа, не отправляет данные наружу (импортозамещение-friendly).
//
// Подписи ответов локализуются под язык интерфейса (ru/en); извлекаемые данные и
// системные промпты РФ-моделей не затрагиваются.

import { getLang } from "../common/i18n.js";

const L = {
  ru: {
    note: "Офлайн-режим (без ИИ-ключа): извлечение и поиск по тексту. Для развёрнутых ответов и перевода подключите ИИ-модель в ⚙ настройках.",
    found: "найдено", none: "не найдено в тексте документа.", nomatch: "Совпадений в тексте не найдено.",
    refine: "Сформулируйте вопрос конкретнее (минимум одно слово от 4 букв).", foundFor: "Найдено по запросу",
    amounts: "Денежные суммы", penalties: "Штрафы, пени, санкции", dates: "Даты", terms: "Сроки",
    requisites: "Реквизиты", accounts: "Расчётные счета", contacts: "Контакты", email: "E-mail",
    phones: "Телефоны", fio: "ФИО", risks: "Потенциальные риски и проблемные условия",
    about: "О документе", pages: "Страниц", docStart: "Начало документа",
    summary: "Краткое содержание (по страницам)", conclusions: "Основные выводы (по маркерам в тексте)",
    noConcl: "Явных формулировок выводов не найдено — см. краткое содержание.",
    tables: "**Извлечение таблиц** недоступно в офлайн-режиме (нужна ИИ-модель для разбора структуры). Подключите провайдера в ⚙ настройках.",
    compareNeed: "**Сравнение версий** требует ИИ-модель и второй документ. Подключите провайдера в ⚙ настройках.",
    cantDo: (x) => `Офлайн-режим не выполняет «${x}». Подключите ИИ-модель в ⚙ настройках, чтобы переводить/переписывать/объяснять текст.`,
    bmSums: "💰 Суммы", bmPenalties: "⚠ Штрафы/пени", bmReq: "🏦 Реквизиты/счёт",
    cmpTitle: (a, b) => `**Сравнение версий:** A = ${a}, B = ${b}`,
    cmpAdded: "➕ Добавлено в B", cmpRemoved: "➖ Удалено из A", cmpNone: "— нет —",
    cmpIdentical: "Документы текстуально идентичны.",
    cmpNote: "_Офлайн-сравнение по строкам. Для смыслового сравнения подключите ИИ-модель._",
  },
  en: {
    note: "Offline mode (no AI key): text extraction and search. Connect an AI model in ⚙ settings for detailed answers and translation.",
    found: "found", none: "not found in the document text.", nomatch: "No matches found in the text.",
    refine: "Please refine your query (at least one word of 4+ letters).", foundFor: "Found for query",
    amounts: "Amounts", penalties: "Penalties & fines", dates: "Dates", terms: "Terms",
    requisites: "Requisites", accounts: "Bank accounts", contacts: "Contacts", email: "Email",
    phones: "Phones", fio: "Names", risks: "Potential risks and problematic clauses",
    about: "About the document", pages: "Pages", docStart: "Document start",
    summary: "Summary (by pages)", conclusions: "Key takeaways (by text markers)",
    noConcl: "No explicit conclusions found — see the summary.",
    tables: "**Table extraction** is unavailable in offline mode (an AI model is needed to parse structure). Connect a provider in ⚙ settings.",
    compareNeed: "**Version comparison** requires an AI model and a second document. Connect a provider in ⚙ settings.",
    cantDo: (x) => `Offline mode does not perform “${x}”. Connect an AI model in ⚙ settings to translate/rewrite/explain text.`,
    bmSums: "💰 Amounts", bmPenalties: "⚠ Penalties", bmReq: "🏦 Requisites/account",
    cmpTitle: (a, b) => `**Version comparison:** A = ${a}, B = ${b}`,
    cmpAdded: "➕ Added in B", cmpRemoved: "➖ Removed from A", cmpNone: "— none —",
    cmpIdentical: "The documents are textually identical.",
    cmpNote: "_Offline line-by-line comparison. Connect an AI model for semantic comparison._",
  },
};
const tr = (k) => (L[getLang()] || L.ru)[k] ?? L.ru[k];

const MONTHS = "янв(?:ар[ья])?|фев(?:рал[ья])?|март?[а]?|апр(?:ел[ья])?|ма[йя]|июн[ья]?|июл[ья]?|авг(?:уст[а]?)?|сен(?:тябр[ья])?|окт(?:ябр[ья])?|ноя(?:бр[ья])?|дек(?:абр[ья])?";

const RE = {
  money: new RegExp(
    String.raw`\d[\d  .,]*\d\s?(?:руб(?:\.|лей|ля)?|₽|млн|млрд|тыс\.?|коп(?:\.|еек)?|USD|\$|EUR|€|у\.?\s?е\.?)|(?:руб(?:\.|лей)?|₽|\$|€)\s?\d[\d  .,]*\d`,
    "gi",
  ),
  percent: /\d+(?:[.,]\d+)?\s?%/g,
  date: new RegExp(String.raw`\b\d{1,2}[.\/-]\d{1,2}[.\/-]\d{2,4}\b|\b\d{1,2}\s+(?:${MONTHS})\s+\d{4}\s?(?:г\.?|года)?`, "gi"),
  term: /(?:в течение|не позднее|не ранее|в срок(?:\sдо)?|сроком?(?:\sна)?|до)\s+[^.;:\n]{2,70}/gi,
  inn: /\b\d{12}\b|\b\d{10}\b/g,
  kpp: /\b\d{9}\b/g,
  ogrn: /\b\d{15}\b|\b\d{13}\b/g,
  account: /\b\d{20}\b/g,
  bik: /\bБИК\W*\d{9}\b/gi,
  email: /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g,
  phone: /(?:\+7|8)[\s(-]*\d{3}[\s)-]*\d{3}[\s-]*\d{2}[\s-]*\d{2}/g,
  fio: /\b[А-ЯЁ][а-яё]+\s+[А-ЯЁ]\.\s?[А-ЯЁ]\.|\b[А-ЯЁ][а-яё]+\s+[А-ЯЁ][а-яё]+(?:ович|евич|инична|овна|евна)\b/g,
};

const KW = {
  penalties: ["штраф", "пеня", "пени", "неустойк", "санкци", "просрочк", "ответственн"],
  risks: ["в одностороннем порядке", "вправе расторгнуть", "вправе отказаться", "ответственн", "неустойк",
    "штраф", "пеня", "риск", "форс-мажор", "обеспечени", "залог", "удержани", "не возвращается", "не подлежит возврату"],
  conclusions: ["таким образом", "следовательно", "в результате", "вывод", "итог", "резюмиру", "в заключени"],
};

const STOP = new Set("и в во не на я с со как а то все она так его но да ты к у же вы за бы по только ее мне было вот от меня еще нет о из ему теперь когда даже ну вдруг ли если или быть был него до вас нибудь опять уж вам ведь там потом себя ничего ей может они тут где есть надо ней для мы тебя их чем была сам чтоб без будто чего раз тоже себе под будет ж тогда кто этот того потому этого какой совсем ним здесь этом один почти мой тем чтобы нее сейчас были куда зачем всех никогда можно при наконец два об другой хоть после над больше тот через эти нас про всего них какая много разве сказал три эту моя впрочем хорошо свою этой перед иногда лучше чуть том нельзя такой им более всегда конечно всю между что это так его как она для они".split(" "));

// --- помощники ------------------------------------------------------------

function* matchPages(pages, regex) {
  for (let i = 0; i < pages.length; i++) {
    const text = pages[i] || "";
    regex.lastIndex = 0;
    let m;
    while ((m = regex.exec(text))) {
      yield { page: i + 1, value: m[0].trim(), index: m.index, text };
      if (m.index === regex.lastIndex) regex.lastIndex++;
    }
  }
}

function context(text, index, len, span = 50) {
  const a = Math.max(0, index - span);
  const b = Math.min(text.length, index + len + span);
  return (a > 0 ? "…" : "") + text.slice(a, b).replace(/\s+/g, " ").trim() + (b < text.length ? "…" : "");
}

function collect(pages, regex, { withContext = true, max = 60 } = {}) {
  const seen = new Set();
  const out = [];
  for (const m of matchPages(pages, regex)) {
    const key = `${m.page}:${m.value.toLowerCase()}`;
    if (seen.has(key)) continue;
    seen.add(key);
    out.push({ page: m.page, value: m.value, ctx: withContext ? context(m.text, m.index, m.value.length) : "" });
    if (out.length >= max) break;
  }
  return out;
}

function collectByKeywords(pages, words, max = 40) {
  const out = [];
  for (let i = 0; i < pages.length; i++) {
    const text = pages[i] || "";
    const low = text.toLowerCase();
    for (const w of words) {
      let idx = low.indexOf(w);
      while (idx !== -1) {
        out.push({ page: i + 1, ctx: context(text, idx, w.length, 70) });
        if (out.length >= max) return dedup(out);
        idx = low.indexOf(w, idx + w.length);
      }
    }
  }
  return dedup(out);
}

function dedup(items) {
  const seen = new Set();
  return items.filter((x) => {
    const k = `${x.page}:${x.ctx.slice(0, 40)}`;
    if (seen.has(k)) return false;
    seen.add(k);
    return true;
  });
}

function listMatches(title, items, fmt = (x) => x.value) {
  if (!items.length) return `**${title}:** ${tr("none")}`;
  const lines = items.slice(0, 40).map((x) => `- ${fmt(x)} [с. ${x.page}]`);
  return `**${title}** (${tr("found")}: ${items.length}):\n${lines.join("\n")}`;
}

function passages(items) {
  if (!items.length) return tr("nomatch");
  return items.slice(0, 8).map((x) => `- «${x.ctx}» [с. ${x.page}]`).join("\n");
}

// --- свободный вопрос: поиск релевантных фрагментов -----------------------

function freeQuery(pages, query) {
  const terms = (query.toLowerCase().match(/[а-яёa-z0-9]+/gi) || [])
    .filter((w) => w.length >= 4 && !STOP.has(w));
  if (!terms.length) return null;

  const scored = [];
  for (let i = 0; i < pages.length; i++) {
    const low = (pages[i] || "").toLowerCase();
    let score = 0;
    for (const t of terms) {
      let idx = low.indexOf(t), c = 0;
      while (idx !== -1 && c < 10) { score++; c++; idx = low.indexOf(t, idx + t.length); }
    }
    if (score) scored.push({ page: i + 1, score, text: pages[i] });
  }
  scored.sort((a, b) => b.score - a.score);

  const top = scored.slice(0, 5).map((p) => {
    // вырезаем предложение с первым найденным термином
    const low = p.text.toLowerCase();
    let pos = -1;
    for (const t of terms) { const k = low.indexOf(t); if (k !== -1 && (pos === -1 || k < pos)) pos = k; }
    return { page: p.page, ctx: context(p.text, Math.max(0, pos), 0, 140) };
  });
  return { terms, top };
}

// --- основной вход --------------------------------------------------------

/**
 * Локальный ответ. intent — id быстрого действия / действия выделения, либо null.
 * Возвращает markdown-строку с цитатами [с. N].
 */
export function answer({ pages, query, intent }) {
  const note = `\n\n_${tr("note")}_`;

  switch (intent) {
    case "amounts":
      return listMatches(tr("amounts"), collect(pages, RE.money), (x) => `${x.value} — ${x.ctx}`) + note;
    case "penalties": {
      const m = collectByKeywords(pages, KW.penalties);
      return `**${tr("penalties")}:**\n${passages(m)}` + note;
    }
    case "deadlines": {
      const dates = collect(pages, RE.date);
      const terms = collect(pages, RE.term, { max: 30 });
      return [
        listMatches(tr("dates"), dates),
        listMatches(tr("terms"), terms, (x) => x.value),
      ].join("\n\n") + note;
    }
    case "requisites": {
      const parts = [
        listMatches("ИНН", collect(pages, RE.inn, { withContext: false })),
        listMatches("КПП", collect(pages, RE.kpp, { withContext: false })),
        listMatches("ОГРН/ОГРНИП", collect(pages, RE.ogrn, { withContext: false })),
        listMatches(tr("accounts"), collect(pages, RE.account, { withContext: false })),
        listMatches("БИК", collect(pages, RE.bik, { withContext: false })),
      ];
      return `**${tr("requisites")}:**\n\n` + parts.join("\n\n") + note;
    }
    case "contacts": {
      const parts = [
        listMatches(tr("email"), collect(pages, RE.email, { withContext: false })),
        listMatches(tr("phones"), collect(pages, RE.phone, { withContext: false })),
        listMatches(tr("fio"), collect(pages, RE.fio, { withContext: false })),
      ];
      return `**${tr("contacts")}:**\n\n` + parts.join("\n\n") + note;
    }
    case "risks": {
      const m = collectByKeywords(pages, KW.risks);
      return `**${tr("risks")}:**\n${passages(m)}` + note;
    }
    case "about": {
      const total = pages.length;
      const intro = (pages.find((p) => (p || "").trim().length > 40) || "").replace(/\s+/g, " ").slice(0, 500);
      return `**${tr("about")}.** ${tr("pages")}: ${total}.\n\n${tr("docStart")} [с. 1]:\n«${intro}…»` + note;
    }
    case "summary": {
      const lines = [];
      for (let i = 0; i < pages.length && lines.length < 20; i++) {
        const s = (pages[i] || "").replace(/\s+/g, " ").trim();
        if (s.length < 30) continue;
        const sentence = (s.match(/[^.!?]{20,180}[.!?]/) || [s.slice(0, 160)])[0].trim();
        lines.push(`- [с. ${i + 1}] ${sentence}`);
      }
      return `**${tr("summary")}:**\n${lines.join("\n")}` + note;
    }
    case "conclusions": {
      const m = collectByKeywords(pages, KW.conclusions);
      return `**${tr("conclusions")}:**\n${m.length ? passages(m) : tr("noConcl")}` + note;
    }
    case "tables":
      return tr("tables") + note;
    case "compare":
      return tr("compareNeed") + note;
    case "translate":
    case "rewrite":
    case "shorten":
    case "explain":
      return tr("cantDo")(intent) + note;
    default: {
      // Свободный вопрос или действие «Спросить AI» / «Найти риски» из выделения.
      const r = freeQuery(pages, query);
      if (!r) return tr("refine") + note;
      return `${tr("foundFor")} (${r.terms.join(", ")}):\n${passages(r.top)}` + note;
    }
  }
}

// --- AI-закладки (офлайн): ключевые места документа -----------------------

const HEADING_RE = /^(?:(?:Статья|Раздел|Глава|Приложение|Пункт)\s+[\dIVXЛ]|(?:\d+(?:\.\d+)*\.?\s+)[А-ЯA-Z])/;

/** Возвращает [{title, page, ai:true}] — заголовки + страницы с важными данными. */
export function suggestBookmarks(pages) {
  const out = [];
  const pushed = new Set();
  const add = (title, page) => {
    const key = `${page}:${title}`;
    if (pushed.has(key) || out.length >= 40) return;
    pushed.add(key);
    out.push({ title: title.slice(0, 70), page, ai: true });
  };

  // 1. Заголовки/разделы по началу строк.
  for (let i = 0; i < pages.length; i++) {
    const lines = (pages[i] || "").split(/\n|(?<=[.!?])\s{2,}/).map((l) => l.trim());
    for (const line of lines) {
      if (line.length >= 6 && line.length <= 70 && HEADING_RE.test(line)) add(line, i + 1);
    }
  }
  // 2. Страницы с ключевыми данными.
  const flag = (regexOrKw, label) => {
    const seen = new Set();
    const items = typeof regexOrKw === "string"
      ? collectByKeywords(pages, [regexOrKw])
      : collect(pages, regexOrKw, { withContext: false });
    for (const m of items) if (!seen.has(m.page)) { seen.add(m.page); add(`${label}`, m.page); }
  };
  flag(RE.money, tr("bmSums"));
  flag("штраф", tr("bmPenalties"));
  flag(RE.account, tr("bmReq"));

  out.sort((a, b) => a.page - b.page);
  return out;
}

// --- Сравнение двух документов (офлайн, построчный дифф) -------------------

function normLines(pages) {
  const map = new Map(); // нормализованная строка -> номер страницы
  pages.forEach((t, i) => {
    for (const raw of (t || "").split(/\n|(?<=[.!?])\s+/)) {
      const line = raw.trim().replace(/\s+/g, " ");
      if (line.length >= 12 && !map.has(line)) map.set(line, i + 1);
    }
  });
  return map;
}

/** Возвращает markdown-отчёт о различиях между документами A и B. */
export function compareDocs(pagesA, pagesB, nameA = "A", nameB = "B") {
  const a = normLines(pagesA);
  const b = normLines(pagesB);
  const added = [];
  const removed = [];
  for (const [line, page] of b) if (!a.has(line)) added.push({ line, page });
  for (const [line, page] of a) if (!b.has(line)) removed.push({ line, page });

  const fmt = (items, doc) =>
    items.length
      ? items.slice(0, 25).map((x) => `- «${x.line.slice(0, 120)}» [${doc} с. ${x.page}]`).join("\n")
      : tr("cmpNone");

  return [
    tr("cmpTitle")(nameA, nameB),
    `\n**${tr("cmpAdded")}** (${added.length}):\n${fmt(added, "B")}`,
    `\n**${tr("cmpRemoved")}** (${removed.length}):\n${fmt(removed, "A")}`,
    added.length + removed.length === 0 ? `\n${tr("cmpIdentical")}` : "",
    `\n${tr("cmpNote")}`,
  ].join("\n");
}
