const CONFIG = {
  productId: "med-tools-print-ua",
  licenseApiUrl: "",
  remotePdfCatalogUrl: "",
  updateManifestUrl: "",
  licenseRecheckHours: 6,
  offlineGraceHours: 24,
  consultationCompanionUrl: "http://127.0.0.1:8765",
  consultationChunkMs: 30000,
  allowLocalTestKey: false,
  localTestKey: "",
  ...(window.MEDTOOLS_CONFIG || {})
};
const POPUP_URL_PARAMS = new URLSearchParams(window.location.search);
const POPUP_EMBEDDED = POPUP_URL_PARAMS.get("embedded") === "1";

const STORAGE_KEYS = {
  deviceId: "medtools.deviceId",
  license: "medtools.license"
};

const DB_NAME = "medtools-custom-pdfs";
const DB_VERSION = 1;
const CUSTOM_STORE = "pdfs";
const DAY_MS = 24 * 60 * 60 * 1000;

const els = {};
const appState = {
  license: null,
  licenseWarning: "",
  gateMessage: "",
  deviceId: "",
  packagedPdfs: [],
  remotePdfs: [],
  customPdfs: []
};

document.addEventListener("DOMContentLoaded", init);

async function init() {
  cacheElements();
  bindEvents();
  if (POPUP_URL_PARAMS.has("forceRuntimeReload")) {
    showGate("Оновлюю runtime розширення...");
    renderDiagnostics();
    window.setTimeout(() => chrome.runtime.reload(), 200);
    return;
  }
  setDefaultDates();
  appState.deviceId = await ensureDeviceId();
  renderDiagnostics();
  appState.license = await resolveLicense();
  renderLicenseState();
  renderDiagnostics();

  if (!appState.license?.active) {
    showGate(appState.gateMessage || "Введіть ліцензійний ключ для доступу.");
    return;
  }

  await loadPdfData();
  showApp();
  showPanel("date");
  updateBirthModeControls();
  calculateDateDiff();
  calculateChildAge();
}

function cacheElements() {
  els.licenseGate = document.getElementById("licenseGate");
  els.appShell = document.getElementById("appShell");
  els.licenseForm = document.getElementById("licenseForm");
  els.licenseEmail = document.getElementById("licenseEmail");
  els.licenseKey = document.getElementById("licenseKey");
  els.licenseMessage = document.getElementById("licenseMessage");
  els.licenseDiagnostics = document.getElementById("licenseDiagnostics");
  els.licenseStatus = document.getElementById("licenseStatus");
  els.runtimeDiagnostics = document.getElementById("runtimeDiagnostics");
  els.updateStatus = document.getElementById("updateStatus");
  els.copyDiagnosticsButton = document.getElementById("copyDiagnosticsButton");
  els.copyRuntimeDiagnosticsButton = document.getElementById("copyRuntimeDiagnosticsButton");
  els.checkUpdateButton = document.getElementById("checkUpdateButton");
  els.reloadExtensionButton = document.getElementById("reloadExtensionButton");
  els.reloadRuntimeButton = document.getElementById("reloadRuntimeButton");
  els.deactivateButton = document.getElementById("deactivateButton");
  els.navButtons = Array.from(document.querySelectorAll("[data-nav]"));
  els.panels = Array.from(document.querySelectorAll("[data-panel]"));
  els.dateStart = document.getElementById("dateStart");
  els.dateEnd = document.getElementById("dateEnd");
  els.dateIncludeStart = document.getElementById("dateIncludeStart");
  els.dateResults = document.getElementById("dateResults");
  els.birthDate = document.getElementById("birthDate");
  els.assessmentDate = document.getElementById("assessmentDate");
  els.gestWeeks = document.getElementById("gestWeeks");
  els.gestDays = document.getElementById("gestDays");
  els.childResults = document.getElementById("childResults");
  els.birthModeInputs = Array.from(document.querySelectorAll("input[name='birthMode']"));
  els.openRecorderButton = document.getElementById("openRecorderButton");
  els.checkCompanionButton = document.getElementById("checkCompanionButton");
  els.consultationStatus = document.getElementById("consultationStatus");
  els.addPdfButton = document.getElementById("addPdfButton");
  els.addPdfInput = document.getElementById("addPdfInput");
  els.pdfList = document.getElementById("pdfList");
  els.pdfStatus = document.getElementById("pdfStatus");
}

function bindEvents() {
  els.licenseForm.addEventListener("submit", handleLicenseSubmit);
  els.deactivateButton.addEventListener("click", deactivateLicense);
  els.copyDiagnosticsButton.addEventListener("click", copySupportDiagnostics);
  els.copyRuntimeDiagnosticsButton.addEventListener("click", copySupportDiagnostics);
  els.checkUpdateButton.addEventListener("click", checkExtensionUpdate);
  els.reloadExtensionButton.addEventListener("click", reloadExtensionRuntime);
  els.reloadRuntimeButton.addEventListener("click", reloadExtensionRuntime);

  for (const button of els.navButtons) {
    button.addEventListener("click", () => showPanel(button.dataset.nav));
  }

  for (const input of [els.dateStart, els.dateEnd, els.dateIncludeStart]) {
    input.addEventListener("input", calculateDateDiff);
    input.addEventListener("change", calculateDateDiff);
  }

  for (const input of [els.birthDate, els.assessmentDate, els.gestWeeks, els.gestDays]) {
    input.addEventListener("input", calculateChildAge);
    input.addEventListener("change", calculateChildAge);
  }

  for (const input of els.birthModeInputs) {
    input.addEventListener("change", () => {
      updateBirthModeControls();
      calculateChildAge();
    });
  }

  els.openRecorderButton.addEventListener("click", openConsultationRecorder);
  els.checkCompanionButton.addEventListener("click", checkConsultationCompanion);
  els.addPdfButton.addEventListener("click", () => els.addPdfInput.click());
  els.addPdfInput.addEventListener("change", handlePdfUpload);
}

function setDefaultDates() {
  const today = toInputDate(new Date());
  els.dateStart.value = today;
  els.dateEnd.value = today;
  els.birthDate.value = today;
  els.assessmentDate.value = today;
}

function showGate(message = "") {
  els.licenseGate.hidden = false;
  els.appShell.hidden = true;
  els.licenseMessage.textContent = message;
  els.licenseMessage.style.color = "";
  renderDiagnostics();
}

function showApp() {
  els.licenseGate.hidden = true;
  els.appShell.hidden = false;
  renderDiagnostics();
}

function showPanel(name) {
  for (const button of els.navButtons) {
    button.classList.toggle("active", button.dataset.nav === name);
  }
  for (const panel of els.panels) {
    panel.classList.toggle("active", panel.dataset.panel === name);
  }
}

async function ensureDeviceId() {
  const current = await chrome.storage.local.get(STORAGE_KEYS.deviceId);
  if (current[STORAGE_KEYS.deviceId]) {
    return current[STORAGE_KEYS.deviceId];
  }

  const deviceId = crypto.randomUUID();
  await chrome.storage.local.set({ [STORAGE_KEYS.deviceId]: deviceId });
  return deviceId;
}

async function resolveLicense() {
  const stored = await chrome.storage.local.get(STORAGE_KEYS.license);
  const license = stored[STORAGE_KEYS.license];
  if (!license?.active) {
    return null;
  }

  if (license.expiresAt && new Date(license.expiresAt).getTime() < Date.now()) {
    await chrome.storage.local.remove(STORAGE_KEYS.license);
    appState.gateMessage = "Термін дії ліцензії завершився. Введіть продовжений ключ або зверніться до адміністратора.";
    return null;
  }

  const shouldRecheck = CONFIG.licenseApiUrl && (!license.nextCheckAt || Date.now() > new Date(license.nextCheckAt).getTime());
  if (!shouldRecheck) {
    return license;
  }

  try {
    const response = await callLicenseApi("validate", license.email, license.licenseKey);
    if (!response.ok || response.status !== "active") {
      await chrome.storage.local.remove(STORAGE_KEYS.license);
      appState.gateMessage = licenseErrorMessage(response);
      return null;
    }
    return saveEntitlement(license.email, license.licenseKey, response, "remote");
  } catch (error) {
    const graceUntil = new Date(new Date(license.lastCheckedAt || license.activatedAt).getTime() + CONFIG.offlineGraceHours * 60 * 60 * 1000);
    if (Date.now() <= graceUntil.getTime()) {
      appState.licenseWarning = "Сервер ліцензій недоступний, працює кешований доступ.";
      return license;
    }
    await chrome.storage.local.remove(STORAGE_KEYS.license);
    appState.gateMessage = "Не вдалося перевірити ліцензію на сервері. Потрібне підключення до інтернету або продовження доступу.";
    return null;
  }
}

async function handleLicenseSubmit(event) {
  event.preventDefault();
  els.licenseMessage.textContent = "Перевіряю ліцензію...";
  const email = els.licenseEmail.value.trim().toLowerCase();
  const licenseKey = normalizeLicenseKey(els.licenseKey.value);

  if (!licenseKey) {
    els.licenseMessage.textContent = "Введіть ліцензійний ключ.";
    return;
  }
  if (CONFIG.licenseApiUrl && !email) {
    els.licenseMessage.textContent = "Введіть email, на який видана ліцензія.";
    return;
  }

  try {
    let entitlement;
    if (!CONFIG.licenseApiUrl && CONFIG.allowLocalTestKey && licenseKey === normalizeLicenseKey(CONFIG.localTestKey)) {
      entitlement = await saveEntitlement(email, licenseKey, { ok: true, status: "active" }, "local-test");
    } else {
      const response = await callLicenseApi("activate", email, licenseKey);
      if (!response.ok || response.status !== "active") {
        throw new Error(licenseErrorMessage(response));
      }
      entitlement = await saveEntitlement(email, licenseKey, response, "remote");
    }

    appState.license = entitlement;
    appState.licenseWarning = "";
    await loadPdfData();
    renderLicenseState();
    renderDiagnostics();
    showApp();
    showPanel("date");
    calculateDateDiff();
    calculateChildAge();
  } catch (error) {
    els.licenseMessage.textContent = error.message || "Не вдалося активувати ліцензію.";
  }
}

async function callLicenseApi(action, email, licenseKey) {
  if (!CONFIG.licenseApiUrl) {
    throw new Error("Сервер ліцензій ще не налаштований.");
  }

  const deviceId = await ensureDeviceId();
  const response = await fetch(CONFIG.licenseApiUrl, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      action,
      productId: CONFIG.productId,
      email,
      licenseKey,
      deviceId,
      extensionVersion: chrome.runtime.getManifest().version
    })
  });

  if (!response.ok) {
    throw new Error("Сервер ліцензій повернув помилку.");
  }
  return response.json();
}

async function saveEntitlement(email, licenseKey, response, source) {
  const now = new Date();
  const entitlement = {
    active: true,
    source,
    email,
    licenseKey,
    status: response.status || "active",
    expiresAt: response.expiresAt || null,
    maxDevices: response.maxDevices || null,
    activeDevices: response.activeDevices || null,
    serverTime: response.serverTime || null,
    activatedAt: response.activatedAt || now.toISOString(),
    lastCheckedAt: now.toISOString(),
    nextCheckAt: new Date(now.getTime() + CONFIG.licenseRecheckHours * 60 * 60 * 1000).toISOString()
  };
  await chrome.storage.local.set({ [STORAGE_KEYS.license]: entitlement });
  return entitlement;
}

async function deactivateLicense() {
  await chrome.storage.local.remove(STORAGE_KEYS.license);
  appState.license = null;
  showGate("Ліцензію видалено з цього браузера.");
}

function renderLicenseState() {
  if (!appState.license?.active) {
    return;
  }
  const source = appState.license.source === "local-test" ? "тестова" : "активна";
  const expires = appState.license.expiresAt ? `, до ${formatDateHuman(appState.license.expiresAt)}` : "";
  els.licenseStatus.textContent = `Ліцензія ${source}${expires}. ${appState.licenseWarning}`;
  els.licenseStatus.style.color = "";
}

function renderDiagnostics() {
  const manifest = chrome.runtime.getManifest();
  const version = manifest.version ? `v${manifest.version}` : "version unknown";
  const devicePrefix = appState.deviceId ? appState.deviceId.slice(0, 8) : "device pending";
  const licenseLabel = appState.license?.active
    ? appState.license.email || "без email"
    : "не активовано";
  const text = `${version} · device ${devicePrefix} · ${licenseLabel}`;
  if (els.licenseDiagnostics) {
    els.licenseDiagnostics.textContent = text;
  }
  if (els.runtimeDiagnostics) {
    els.runtimeDiagnostics.textContent = text;
  }
}

async function copySupportDiagnostics() {
  const text = buildSupportDiagnostics();
  try {
    await copyTextToClipboard(text);
    setSupportCopyStatus("Діагностику скопійовано.");
  } catch (error) {
    setSupportCopyStatus("Не вдалося скопіювати діагностику.", true);
  }
}

async function copyTextToClipboard(text) {
  const value = String(text || "");
  if (navigator.clipboard?.writeText) {
    try {
      await navigator.clipboard.writeText(value);
      return;
    } catch (error) {
      // Keep diagnostics copyable when Chrome blocks navigator.clipboard in extension views.
    }
  }
  const textarea = document.createElement("textarea");
  textarea.value = value;
  textarea.setAttribute("readonly", "");
  Object.assign(textarea.style, {
    position: "fixed",
    top: "-1000px",
    left: "-1000px",
    width: "1px",
    height: "1px",
    opacity: "0"
  });
  document.body.append(textarea);
  textarea.focus();
  textarea.select();
  const copied = document.execCommand("copy");
  textarea.remove();
  if (!copied) {
    throw new Error("Clipboard copy failed");
  }
}

function reloadExtensionRuntime() {
  setSupportCopyStatus("Відкриваю сторінку оновлення...");
  const url = chrome.runtime.getURL(`reload.html?token=${Date.now()}`);
  window.location.assign(url);
}

function buildSupportDiagnostics() {
  const manifest = chrome.runtime.getManifest();
  const license = appState.license || {};
  return [
    "Parallax Health support diagnostics",
    `time: ${new Date().toISOString()}`,
    `version: ${manifest.version || ""}`,
    `extensionId: ${chrome.runtime.id}`,
    `devicePrefix: ${appState.deviceId ? appState.deviceId.slice(0, 8) : ""}`,
    `licenseEmail: ${license.email || ""}`,
    `licenseSource: ${license.source || ""}`,
    `licenseStatus: ${license.status || (license.active ? "active" : "not_active")}`,
    `expiresAt: ${license.expiresAt || ""}`,
    `nextCheckAt: ${license.nextCheckAt || ""}`,
    `productId: ${CONFIG.productId || ""}`,
    `licenseApi: ${CONFIG.licenseApiUrl ? "configured" : "missing"}`,
    `updateManifestUrl: ${CONFIG.updateManifestUrl || ""}`,
    `companionUrl: ${CONFIG.consultationCompanionUrl || ""}`,
    `embedded: ${POPUP_EMBEDDED ? "yes" : "no"}`
  ].join("\n");
}

function setSupportCopyStatus(message, isError = false) {
  const target = els.appShell.hidden ? els.licenseMessage : els.licenseStatus;
  target.textContent = message;
  target.style.color = isError ? "var(--danger)" : "var(--success)";
}

async function checkExtensionUpdate() {
  if (!CONFIG.updateManifestUrl) {
    setUpdateStatus("Update-канал ще не налаштований.", true);
    return;
  }

  setUpdateStatus("Перевіряю оновлення...");
  try {
    const currentVersion = chrome.runtime.getManifest().version || "0";
    const url = new URL(CONFIG.updateManifestUrl);
    url.searchParams.set("version", currentVersion);
    url.searchParams.set("device", appState.deviceId ? appState.deviceId.slice(0, 8) : "");
    const response = await fetch(url.toString(), {
      method: "GET",
      headers: { "Accept": "application/json" },
      cache: "no-store"
    });
    if (!response.ok) {
      throw new Error(`Update endpoint returned ${response.status}.`);
    }
    const result = await response.json();
    if (!result?.ok) {
      throw new Error(result?.error || "Не вдалося перевірити оновлення.");
    }
    const latestVersion = String(result.version || "0");
    const updateAvailable = Boolean(result.updateAvailable) || compareVersions(currentVersion, latestVersion) < 0;
    if (!updateAvailable) {
      setUpdateStatus(`Актуальна версія ${currentVersion}.`);
      return;
    }
    const downloadUrl = String(result.downloadUrl || "").trim();
    if (!downloadUrl) {
      throw new Error("Update-канал не повернув посилання на ZIP.");
    }
    const safeVersion = latestVersion.replace(/[^0-9A-Za-z._-]+/g, "_") || "latest";
    const safeFileName = String(result.fileName || `Parallax_Health_${safeVersion}.zip`).replace(/[^0-9A-Za-z._-]+/g, "_");
    const filename = `Parallax_Health_updates/${safeFileName}`;
    const downloadId = await chrome.downloads.download({
      url: downloadUrl,
      filename,
      saveAs: false,
      conflictAction: "uniquify"
    });
    setUpdateStatus(`Оновлення ${latestVersion} завантажується в Downloads/${filename}. Після завантаження розпакуйте ZIP і оновіть розширення в Chrome.`, false, true);
    console.info("Parallax Health update download started", { downloadId, latestVersion, sha256: result.sha256 || "" });
  } catch (error) {
    setUpdateStatus(error.message || "Не вдалося перевірити оновлення.", true);
  }
}

function setUpdateStatus(message, isError = false, isReady = false) {
  if (!els.updateStatus) {
    setSupportCopyStatus(message, isError);
    return;
  }
  els.updateStatus.textContent = message;
  els.updateStatus.style.color = isError ? "var(--danger)" : "";
  els.updateStatus.classList.toggle("update-ready", Boolean(isReady));
}

function compareVersions(left, right) {
  const leftParts = String(left || "0").split(".").map((item) => Number.parseInt(item, 10) || 0);
  const rightParts = String(right || "0").split(".").map((item) => Number.parseInt(item, 10) || 0);
  const length = Math.max(leftParts.length, rightParts.length);
  for (let index = 0; index < length; index += 1) {
    const delta = (leftParts[index] || 0) - (rightParts[index] || 0);
    if (delta !== 0) {
      return delta;
    }
  }
  return 0;
}

function normalizeLicenseKey(value) {
  return String(value || "").trim().toUpperCase().replace(/\s+/g, "");
}

function licenseErrorMessage(response) {
  const code = response?.code || "";
  const fallback = response?.message || "Ліцензія не активна.";
  const messages = {
    not_found: "Ключ ліцензії не знайдено. Перевірте код або зверніться до адміністратора.",
    revoked: "Цю ліцензію відкликано адміністратором.",
    expired: "Термін дії ліцензії завершився.",
    email_required: "Введіть email, на який видана ліцензія.",
    email_mismatch: "Email не збігається з email, на який видана ліцензія.",
    device_limit: "Цей ключ уже активований на дозволеній кількості комп'ютерів.",
    device_not_activated: "Цей комп'ютер не прив'язаний до ліцензії. Зверніться до адміністратора.",
    wrong_product: "Цей ключ виданий для іншого продукту.",
    missing_fields: "Не вистачає даних для перевірки ліцензії.",
    unknown_action: "Сервер ліцензій не підтримує цю дію."
  };
  return messages[code] || fallback;
}

async function loadPdfData() {
  try {
    const catalogResponse = await fetch(chrome.runtime.getURL("assets/pdf-catalog.json"));
    appState.packagedPdfs = await catalogResponse.json();
  } catch (error) {
    appState.packagedPdfs = [];
    els.pdfStatus.textContent = "Не вдалося прочитати локальний каталог PDF.";
  }

  appState.remotePdfs = [];
  if (CONFIG.remotePdfCatalogUrl) {
    try {
      const remoteResponse = await fetch(CONFIG.remotePdfCatalogUrl, { cache: "no-store" });
      const remoteItems = await remoteResponse.json();
      appState.remotePdfs = Array.isArray(remoteItems) ? remoteItems : [];
    } catch (error) {
      els.pdfStatus.textContent = "Віддалений каталог PDF зараз недоступний.";
    }
  }

  appState.customPdfs = await listCustomPdfs();
  renderPdfList();
}

function renderPdfList() {
  const items = [
    ...appState.packagedPdfs.map((item) => ({ ...item, source: "packaged" })),
    ...appState.remotePdfs.map((item) => ({ ...item, source: "remote" })),
    ...appState.customPdfs.map((item) => ({ ...item, source: "custom", category: "Додані на цьому комп'ютері" }))
  ];

  els.pdfList.textContent = "";
  if (!items.length) {
    els.pdfList.append(emptyState("Поки немає PDF для друку."));
    return;
  }

  const groups = groupBy(items, (item) => item.category || "Матеріали");
  for (const [category, groupItems] of groups) {
    const heading = document.createElement("div");
    heading.className = "pdf-group-title";
    heading.textContent = category;
    els.pdfList.append(heading);

    for (const item of groupItems) {
      els.pdfList.append(renderPdfItem(item));
    }
  }
}

function renderPdfItem(item) {
  const row = document.createElement("article");
  row.className = "pdf-item";

  const text = document.createElement("div");
  const title = document.createElement("span");
  title.className = "pdf-title";
  title.textContent = item.title || item.name || "PDF";
  const meta = document.createElement("span");
  meta.className = "pdf-meta";
  meta.textContent = item.source === "custom" ? "Локально доданий PDF" : "PDF відкриється в новій вкладці";
  text.append(title, meta);

  const actions = document.createElement("div");
  actions.className = "pdf-actions";
  const openButton = document.createElement("button");
  openButton.type = "button";
  openButton.className = "small-button";
  openButton.textContent = "Відкрити";
  openButton.addEventListener("click", () => openPdf(item));
  actions.append(openButton);

  if (item.source === "custom") {
    const deleteButton = document.createElement("button");
    deleteButton.type = "button";
    deleteButton.className = "small-button danger";
    deleteButton.textContent = "Видалити";
    deleteButton.addEventListener("click", () => deleteCustomPdfAndRender(item.id));
    actions.append(deleteButton);
  }

  row.append(text, actions);
  return row;
}

function emptyState(text) {
  const node = document.createElement("p");
  node.className = "muted";
  node.textContent = text;
  return node;
}

function groupBy(items, getKey) {
  const groups = new Map();
  for (const item of items) {
    const key = getKey(item);
    if (!groups.has(key)) {
      groups.set(key, []);
    }
    groups.get(key).push(item);
  }
  return groups;
}

function openPdf(item) {
  if (item.source === "custom") {
    chrome.tabs.create({ url: chrome.runtime.getURL(`pdf-viewer.html?id=${encodeURIComponent(item.id)}`) });
    return;
  }

  const url = item.url || chrome.runtime.getURL(item.file);
  chrome.tabs.create({ url });
}

async function handlePdfUpload(event) {
  const files = Array.from(event.target.files || []);
  if (!files.length) {
    return;
  }

  let added = 0;
  for (const file of files) {
    if (!file.name.toLowerCase().endsWith(".pdf")) {
      continue;
    }
    await saveCustomPdf(file);
    added += 1;
  }

  els.addPdfInput.value = "";
  els.pdfStatus.textContent = added ? `Додано PDF: ${added}.` : "Оберіть файл у форматі PDF.";
  appState.customPdfs = await listCustomPdfs();
  renderPdfList();
}

function openDb() {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open(DB_NAME, DB_VERSION);
    request.onupgradeneeded = () => {
      const db = request.result;
      if (!db.objectStoreNames.contains(CUSTOM_STORE)) {
        db.createObjectStore(CUSTOM_STORE, { keyPath: "id" });
      }
    };
    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
  });
}

async function saveCustomPdf(file) {
  const db = await openDb();
  const record = {
    id: crypto.randomUUID(),
    title: file.name.replace(/\.pdf$/i, ""),
    name: file.name,
    size: file.size,
    createdAt: new Date().toISOString(),
    blob: file
  };
  await idbRequest(db.transaction(CUSTOM_STORE, "readwrite").objectStore(CUSTOM_STORE).put(record));
  db.close();
}

async function listCustomPdfs() {
  const db = await openDb();
  const records = await idbRequest(db.transaction(CUSTOM_STORE, "readonly").objectStore(CUSTOM_STORE).getAll());
  db.close();
  return records
    .map(({ blob, ...metadata }) => metadata)
    .sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));
}

async function deleteCustomPdfAndRender(id) {
  if (!confirm("Видалити цей PDF зі списку?")) {
    return;
  }
  const db = await openDb();
  await idbRequest(db.transaction(CUSTOM_STORE, "readwrite").objectStore(CUSTOM_STORE).delete(id));
  db.close();
  appState.customPdfs = await listCustomPdfs();
  renderPdfList();
}

function idbRequest(request) {
  return new Promise((resolve, reject) => {
    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
  });
}

function calculateDateDiff() {
  const start = parseInputDate(els.dateStart.value);
  const end = parseInputDate(els.dateEnd.value);
  if (!start || !end) {
    els.dateResults.textContent = "";
    return;
  }

  const includeStart = els.dateIncludeStart.checked;
  let totalDays = dayDiff(start, end);
  const isReverse = totalDays < 0;
  const from = isReverse ? end : start;
  const to = isReverse ? start : end;
  totalDays = Math.abs(totalDays);
  if (includeStart) {
    totalDays += 1;
  }

  const weeks = Math.floor(totalDays / 7);
  const days = totalDays % 7;
  const calendar = calendarDiff(from, to, includeStart);

  els.dateResults.textContent = "";
  els.dateResults.append(
    resultCard("Усього днів", String(totalDays), isReverse ? "Кінцева дата раніше початкової" : ""),
    resultCard("Тижні і дні", `${weeks} тиж. ${days} дн.`, ""),
    resultCard("Календарно", formatCalendarDiff(calendar), ""),
    resultCard("Приблизно місяців", (totalDays / 30.4375).toFixed(1), "")
  );
}

function updateBirthModeControls() {
  const mode = getBirthMode();
  const isTerm = mode === "term";
  els.gestWeeks.disabled = isTerm;
  els.gestDays.disabled = isTerm;
  if (isTerm) {
    els.gestWeeks.value = "40";
    els.gestDays.value = "0";
  }
}

function calculateChildAge() {
  const birthDate = parseInputDate(els.birthDate.value);
  const assessmentDate = parseInputDate(els.assessmentDate.value);
  if (!birthDate || !assessmentDate) {
    els.childResults.textContent = "";
    return;
  }

  const chronologicalDays = dayDiff(birthDate, assessmentDate);
  if (chronologicalDays < 0) {
    els.childResults.textContent = "";
    els.childResults.append(resultCard("Помилка", "Дата оцінки раніше народження", "", true));
    return;
  }

  const gestWeeks = clampNumber(Number(els.gestWeeks.value), 22, 42);
  const gestDays = clampNumber(Number(els.gestDays.value), 0, 6);
  const gestationalDays = gestWeeks * 7 + gestDays;
  const prematurityDays = Math.max(0, 280 - gestationalDays);
  const mode = getBirthMode();
  const correctionDays = mode === "preterm" ? prematurityDays : 0;
  const correctedDays = Math.max(0, chronologicalDays - correctionDays);
  const correctedStartDate = addDays(birthDate, correctionDays);
  const correctedLabel = correctionDays > 0 ? "Скоригований вік" : "Вік дитини";

  els.childResults.textContent = "";
  els.childResults.append(
    resultCard("Хронологічний вік", formatAgeFromDates(birthDate, assessmentDate), formatAgeBreakdown(chronologicalDays), true),
    resultCard(correctedLabel, formatAgeFromDates(correctedStartDate, assessmentDate), formatCorrectedAgeDetail(correctedDays, correctionDays), true)
  );
}

function getBirthMode() {
  return document.querySelector("input[name='birthMode']:checked")?.value || "term";
}

async function openConsultationRecorder() {
  if (POPUP_EMBEDDED && window.parent !== window) {
    window.parent.postMessage({
      source: "MEDTOOLS_APP",
      type: "SHOW_RECORDER"
    }, "*");
    return;
  }
  const overlayResult = await openRecorderOverlayViaBackground();
  if (overlayResult.ok) {
    return;
  }
  if (els.consultationStatus) {
    els.consultationStatus.textContent = overlayResult.message || "Відкрийте Лакмус або звичайну https-сторінку і натисніть Parallax Health ще раз.";
  }
}

async function openRecorderOverlayViaBackground() {
  try {
    return await chrome.runtime.sendMessage({ type: "MEDTOOLS_OPEN_RECORDER_OVERLAY" });
  } catch (error) {
    return showRecorderOverlayInLakmus();
  }
}

async function showRecorderOverlayInLakmus() {
  try {
    const targetTab = await findRecorderOverlayTargetTab();
    if (!targetTab?.id) {
      return {
        ok: false,
        message: "Не знайшов відкритої сторінки, поверх якої можна показати віджет. Відкрийте Лакмус або звичайну https-сторінку."
      };
    }
    await sendRecorderOverlayMessage(targetTab.id);
    if (targetTab.windowId) {
      await chrome.windows.update(targetTab.windowId, { focused: true });
    }
    await chrome.tabs.update(targetTab.id, { active: true });
    return { ok: true };
  } catch (error) {
    return {
      ok: false,
      message: "Не вдалося показати віджет поверх сторінки. Відкрийте Лакмус або звичайну https-сторінку."
    };
  }
}

async function findRecorderOverlayTargetTab() {
  const lakmusTabs = await chrome.tabs.query({ url: "https://baddi.lakmus.org/*" });
  const lakmusTab = (
    lakmusTabs.find((tab) => tab.active && tab.currentWindow) ||
    lakmusTabs.find((tab) => tab.active) ||
    lakmusTabs[0]
  );
  if (lakmusTab?.id) {
    return lakmusTab;
  }

  const [activeTab] = await chrome.tabs.query({ active: true, currentWindow: true });
  if (canHostRecorderOverlay(activeTab)) {
    return activeTab;
  }

  const currentWindowTabs = await chrome.tabs.query({ currentWindow: true });
  const currentWindowCandidate = currentWindowTabs.find(canHostRecorderOverlay);
  if (currentWindowCandidate) {
    return currentWindowCandidate;
  }

  const allTabs = await chrome.tabs.query({});
  return allTabs.find(canHostRecorderOverlay) || null;
}

function canHostRecorderOverlay(tab) {
  return Boolean(tab?.id && /^https?:\/\//.test(tab.url || ""));
}

async function sendRecorderOverlayMessage(tabId) {
  try {
    return await chrome.tabs.sendMessage(tabId, {
      type: "MEDTOOLS_SHOW_RECORDER_OVERLAY",
      options: { compact: false }
    });
  } catch (error) {
    if (!chrome.scripting?.executeScript) {
      throw error;
    }
    await chrome.scripting.executeScript({
      target: { tabId },
      files: ["lakmus-content.js"]
    });
    return chrome.tabs.sendMessage(tabId, {
      type: "MEDTOOLS_SHOW_RECORDER_OVERLAY",
      options: { compact: false }
    });
  }
}

async function checkConsultationCompanion() {
  els.consultationStatus.textContent = "Перевіряю сервер транскрипції...";
  try {
    const response = await fetch(`${consultationCompanionBaseUrl()}/v1/health`, { cache: "no-store" });
    const data = await response.json();
    if (!response.ok || !data.ok) {
      throw new Error(data.message || "Сервер відповів з помилкою.");
    }
    els.consultationStatus.textContent = data.whisper?.ready
      ? "Сервер готовий, Whisper знайдено."
      : "Сервер працює, але Whisper або модель не знайдені.";
  } catch (error) {
    els.consultationStatus.textContent = "Сервер транскрипції недоступний.";
  }
}

function consultationCompanionBaseUrl() {
  return String(CONFIG.consultationCompanionUrl || "http://127.0.0.1:8765").replace(/\/+$/, "");
}

function resultCard(label, value, detail = "", full = false) {
  const card = document.createElement("div");
  card.className = `result-card${full ? " full" : ""}`;
  const strong = document.createElement("strong");
  strong.textContent = label;
  const span = document.createElement("span");
  span.textContent = value;
  card.append(strong, span);
  if (detail) {
    const small = document.createElement("small");
    small.textContent = detail;
    card.append(small);
  }
  return card;
}

function parseInputDate(value) {
  if (!value) {
    return null;
  }
  const [year, month, day] = value.split("-").map(Number);
  if (!year || !month || !day) {
    return null;
  }
  return new Date(Date.UTC(year, month - 1, day));
}

function toInputDate(date) {
  return date.toISOString().slice(0, 10);
}

function dayDiff(start, end) {
  return Math.round((start.getTime() - end.getTime()) / -DAY_MS);
}

function addDays(date, days) {
  const next = new Date(date.getTime());
  next.setUTCDate(next.getUTCDate() + days);
  return next;
}

function calendarDiff(start, end, includeStart) {
  const adjustedEnd = includeStart ? addDays(end, 1) : new Date(end.getTime());
  let years = adjustedEnd.getUTCFullYear() - start.getUTCFullYear();
  let yearCursor = addCalendarYears(start, years);
  if (yearCursor.getTime() > adjustedEnd.getTime()) {
    years -= 1;
    yearCursor = addCalendarYears(start, years);
  }

  let months = (adjustedEnd.getUTCFullYear() - yearCursor.getUTCFullYear()) * 12
    + adjustedEnd.getUTCMonth()
    - yearCursor.getUTCMonth();
  let monthCursor = addCalendarMonths(yearCursor, months);
  if (monthCursor.getTime() > adjustedEnd.getTime()) {
    months -= 1;
    monthCursor = addCalendarMonths(yearCursor, months);
  }

  const days = dayDiff(monthCursor, adjustedEnd);
  return { years, months, days };
}

function addCalendarYears(date, years) {
  return addCalendarMonths(date, years * 12);
}

function addCalendarMonths(date, months) {
  const year = date.getUTCFullYear();
  const month = date.getUTCMonth();
  const day = date.getUTCDate();
  const targetMonthIndex = month + months;
  const targetYear = year + Math.floor(targetMonthIndex / 12);
  const targetMonth = ((targetMonthIndex % 12) + 12) % 12;
  const targetDay = Math.min(day, daysInUtcMonth(targetYear, targetMonth));
  return new Date(Date.UTC(targetYear, targetMonth, targetDay));
}

function daysInUtcMonth(year, monthIndex) {
  return new Date(Date.UTC(year, monthIndex + 1, 0)).getUTCDate();
}

function formatAgeFromDates(start, end) {
  if (dayDiff(start, end) <= 0) {
    return "0 днів";
  }
  return formatAgeParts(calendarDiff(start, end, false));
}

function formatCalendarDiff({ years, months, days }) {
  return `${years} р. ${months} міс. ${days} дн.`;
}

function formatAgeParts({ years, months, days }) {
  const parts = [];
  if (years) {
    parts.push(formatUnit(years, ["рік", "роки", "років"]));
  }
  if (months) {
    parts.push(formatUnit(months, ["місяць", "місяці", "місяців"]));
  }
  if (days || !parts.length) {
    parts.push(formatUnit(days, ["день", "дні", "днів"]));
  }
  return parts.join(" ");
}

function formatAgeBreakdown(totalDays) {
  return `${formatWeeksDaysLong(totalDays)} · приблизно ${formatApproxMonths(totalDays)} міс.`;
}

function formatCorrectedAgeDetail(totalDays, correctionDays) {
  const base = formatAgeBreakdown(totalDays);
  if (!correctionDays) {
    return base;
  }
  return `${base} · корекція: мінус ${formatWeeksDaysLong(correctionDays)}`;
}

function formatWeeksDays(totalDays) {
  const safeDays = Math.max(0, Math.round(totalDays));
  const weeks = Math.floor(safeDays / 7);
  const days = safeDays % 7;
  return `${weeks} тиж. ${days} дн.`;
}

function formatWeeksDaysLong(totalDays) {
  const safeDays = Math.max(0, Math.round(totalDays));
  const weeks = Math.floor(safeDays / 7);
  const days = safeDays % 7;
  return `${formatUnit(weeks, ["тиждень", "тижні", "тижнів"])} ${formatUnit(days, ["день", "дні", "днів"])}`;
}

function formatApproxMonths(totalDays) {
  const safeDays = Math.max(0, Math.round(totalDays));
  return (safeDays / 30.4375).toFixed(1);
}

function formatUnit(value, forms) {
  const abs = Math.abs(value);
  const lastTwo = abs % 100;
  const lastOne = abs % 10;
  if (lastTwo >= 11 && lastTwo <= 14) {
    return `${value} ${forms[2]}`;
  }
  if (lastOne === 1) {
    return `${value} ${forms[0]}`;
  }
  if (lastOne >= 2 && lastOne <= 4) {
    return `${value} ${forms[1]}`;
  }
  return `${value} ${forms[2]}`;
}

function formatDateHuman(value) {
  return new Date(value).toLocaleDateString("uk-UA");
}

function clampNumber(value, min, max) {
  if (!Number.isFinite(value)) {
    return min;
  }
  return Math.min(max, Math.max(min, value));
}
