<!DOCTYPE html>
<html lang="bg">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="theme-color" content="#080808">
  <title>THE BIGGEST DAN</title>

  <style>
    * { box-sizing: border-box; }

    body {
      margin: 0;
      min-height: 100vh;
      background: radial-gradient(ellipse at center, #211b0e 0%, #10100e 35%, #080808 75%);
      color: #f5f0e5;
      font-family: Arial, sans-serif;
      display: flex;
      align-items: center;
      justify-content: center;
      overflow: hidden;
    }

    .app {
      width: 100%;
      min-height: 100vh;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: space-between;
      padding: 45px 20px 28px;
    }

    .brand { text-align: center; }

    .brand h1 {
      color: #d6b35c;
      font-size: clamp(23px, 5vw, 34px);
      letter-spacing: 5px;
      margin: 0;
      font-weight: 800;
    }

    .brand p {
      color: #888;
      font-size: 10px;
      letter-spacing: 5px;
      margin-top: 12px;
    }

    .center {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 32px;
    }

    .orb-wrap {
      width: 230px;
      height: 230px;
      display: flex;
      justify-content: center;
      align-items: center;
      position: relative;
    }

    .ring {
      position: absolute;
      inset: 0;
      width: 100%;
      height: 100%;
      border: 1px solid rgba(214,179,92,.25);
      border-radius: 50%;
      transition: .3s;
      pointer-events: none;
    }

    .orb {
      position: relative;
      z-index: 1;
      width: 160px;
      height: 160px;
      border-radius: 50%;
      border: 2px solid #d6b35c;
      background: radial-gradient(circle at 35% 30%, #51401d, #1c180e 65%, #0b0b0b);
      box-shadow: 0 0 30px rgba(214,179,92,.15), inset 0 0 30px rgba(214,179,92,.15);
      display: flex;
      align-items: center;
      justify-content: center;
      cursor: pointer;
      transition: .3s;
      color: #e1c477;
    }

    .orb:hover {
      transform: scale(1.04);
      box-shadow: 0 0 55px rgba(214,179,92,.35);
    }

    .orb:disabled {
      opacity: .5;
      cursor: wait;
    }

    .orb svg {
      pointer-events: none;
      width: 48px;
      height: 48px;
    }

    body.listening .ring {
      animation: pulse 1.5s infinite;
      border-color: #d6b35c;
    }

    body.listening .orb {
      box-shadow: 0 0 65px rgba(214,179,92,.5);
    }

    body.speaking .ring {
      animation: pulse .8s infinite;
      border-color: #f1d78f;
    }

    @keyframes pulse {
      0% { transform: scale(1); opacity: .8; }
      100% { transform: scale(1.35); opacity: 0; }
    }

    .status { text-align: center; }

    #state {
      color: #d6b35c;
      font-size: 15px;
      letter-spacing: 2px;
      font-weight: bold;
    }

    #hint {
      color: #888;
      font-size: 12px;
      margin-top: 10px;
      min-height: 18px;
      text-align: center;
      max-width: 300px;
    }

    #stop {
      background: transparent;
      border: 1px solid #514326;
      color: #c5a85d;
      padding: 12px 24px;
      border-radius: 30px;
      cursor: pointer;
      letter-spacing: 1px;
      font-size: 11px;
    }

    #stop:hover { background: #211b0e; }

    .bottom {
      color: #555;
      font-size: 10px;
      letter-spacing: 2px;
      text-align: center;
    }

    .permission-screen {
      position: fixed;
      inset: 0;
      z-index: 1000;
      display: flex;
      align-items: center;
      justify-content: center;
      padding: 24px;
      background: rgba(5,5,5,.96);
    }

    .permission-screen[hidden] { display: none; }

    .permission-card {
      width: min(100%, 420px);
      padding: 32px 24px;
      border: 1px solid rgba(214,179,92,.45);
      border-radius: 22px;
      background: radial-gradient(ellipse at top, #211b0e, #10100e 68%);
      text-align: center;
      box-shadow: 0 0 50px rgba(214,179,92,.12);
    }

    .permission-card h2 {
      margin: 0 0 14px;
      color: #d6b35c;
      font-size: 20px;
      letter-spacing: 2px;
    }

    .permission-card p {
      margin: 0 0 22px;
      color: #c7c0b1;
      font-size: 14px;
      line-height: 1.6;
    }

    #permissionButton {
      width: 100%;
      min-height: 52px;
      padding: 14px 18px;
      border: 1px solid #d6b35c;
      border-radius: 30px;
      background: #d6b35c;
      color: #111;
      font-size: 12px;
      font-weight: 800;
      letter-spacing: 1.5px;
      cursor: pointer;
    }

    #permissionButton:disabled {
      opacity: .65;
      cursor: wait;
    }

    #permissionMessage {
      min-height: 20px;
      margin-top: 16px;
      color: #c5a85d;
      font-size: 12px;
      line-height: 1.5;
    }

    .progress-track {
      width: 100%;
      height: 8px;
      margin-top: 20px;
      overflow: hidden;
      border-radius: 20px;
      background: #302819;
    }

    #downloadBar {
      width: 0%;
      height: 100%;
      border-radius: 20px;
      background: linear-gradient(90deg, #9a7835, #f1d78f);
      transition: width .2s ease;
    }

    @media (max-height: 650px) {
      .app { padding: 25px 15px; }
      .orb-wrap { width: 190px; height: 190px; }
      .orb { width: 135px; height: 135px; }
    }

/* ПОПРАВКА: интерфейсът остава видим при зареждане */

.permission-screen {
  position: fixed;
  inset: 105px 12px auto 12px;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: transparent;
  pointer-events: none;
}

.permission-screen[hidden] {
  display: none;
}

.permission-card {
  width: min(100%, 420px);
  padding: 14px 18px;
  border: 1px solid rgba(214,179,92,.45);
  border-radius: 18px;
  background: rgba(16,16,14,.97);
  text-align: center;
  box-shadow: 0 5px 30px rgba(0,0,0,.5);
  pointer-events: auto;
}

.permission-card h2 {
  font-size: 16px;
  margin-bottom: 7px;
}

.permission-card p {
  font-size: 12px;
  margin-bottom: 8px;
}

.progress-track {
  margin-top: 8px;
}

  </style>
</head>

<body>

  <div class="permission-screen" id="permissionScreen" role="dialog" aria-modal="true">
    <div class="permission-card">
      <h2 id="permissionTitle">THE BIGGEST DAN</h2>

      <p id="permissionDescription">
        Подготвям българския глас Димитър.
      </p>

      <div class="progress-track">
        <div id="downloadBar"></div>
      </div>

      <button id="permissionButton" type="button" hidden>
        ОПИТАЙ ОТНОВО
      </button>

      <div id="permissionMessage" role="status" aria-live="polite">
        Подготвям гласа...
      </div>
    </div>
  </div>

  <div class="app">

    <header class="brand">
      <h1>THE BIGGEST DAN</h1>
      <p>VOICE INTELLIGENCE</p>
    </header>

    <section class="center">

      <div class="orb-wrap">
        <div class="ring" aria-hidden="true"></div>

        <button class="orb" id="mic" aria-label="Започни разговор" type="button" disabled>
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor"
               stroke-width="1.5" stroke-linecap="round"
               stroke-linejoin="round" aria-hidden="true">
            <rect x="9" y="2" width="6" height="13" rx="3"/>
            <path d="M5 10v2a7 7 0 0 0 14 0v-2"/>
            <path d="M12 19v3m-4 0h8"/>
          </svg>
        </button>
      </div>

      <div class="status">
        <div id="state">ЗАРЕЖДАМ ГЛАСА</div>
        <div id="hint">Подготвям Димитър...</div>
      </div>

      <button id="stop" type="button">ПРИКЛЮЧИ РАЗГОВОРА</button>

    </section>

    <footer class="bottom">PRIVATE VOICE EXPERIENCE</footer>

  </div>

<script type="module">
"use strict";

// ========================================
// НАСТРОЙКИ
// ========================================

const API =
  "https://growshop30.albaniangoats.workers.dev/chat";

const VOICE_ID = "bg_BG-dimitar-medium";

const MODEL_PATH =
  "bg/bg_BG/dimitar/medium/bg_BG-dimitar-medium.onnx";

const MODEL_BASE =
  "https://huggingface.co/rhasspy/piper-voices/resolve/main/";

const mic = document.getElementById("mic");
const stopButton = document.getElementById("stop");
const state = document.getElementById("state");
const hint = document.getElementById("hint");

const permissionScreen = document.getElementById("permissionScreen");
const permissionTitle = document.getElementById("permissionTitle");
const permissionDescription = document.getElementById("permissionDescription");
const permissionButton = document.getElementById("permissionButton");
const permissionMessage = document.getElementById("permissionMessage");
const downloadBar = document.getElementById("downloadBar");

const SpeechRecognition =
  window.SpeechRecognition || window.webkitSpeechRecognition;

// Български преобразувател на числа; зарежда се паралелно с Piper.
let bgNumberWords = null;
const bgNumberWordsReady = import("https://esm.sh/nums2words-bg@1.5.9")
  .then((mod) => {
    const candidate = mod.default || mod.nums2wordsBG;
    if (typeof candidate !== "function") {
      throw new Error("Не открих функцията nums2wordsBG.");
    }
    bgNumberWords = candidate;
    console.log("[DAN] Bulgarian number converter ready");
    return candidate;
  })
  .catch((error) => {
    console.warn("[DAN] Number converter unavailable; digits will be kept:", error);
    return null;
  });

// ========================================
// СЪСТОЯНИЕ
// ========================================

let Piper = null;
let piperSession = null;

let voiceReady = false;
let microphoneReady = false;

let voiceError = null;
let microphoneError = null;

let preloadPromise = null;

let recognition = null;
let listening = false;
let speaking = false;
let waiting = false;
let sessionActive = false;

let currentAudio = null;
let finishAudio = null;
let activeController = null;

let history = [];

// ========================================
// ИСТОРИЯ
// ========================================

try {
  const saved = JSON.parse(
    sessionStorage.getItem("biggest_dan_voice_history") || "[]"
  );

  history = Array.isArray(saved) ? saved.slice(-20) : [];
} catch (_) {
  history = [];
}

function saveHistory() {
  try {
    sessionStorage.setItem(
      "biggest_dan_voice_history",
      JSON.stringify(history.slice(-20))
    );
  } catch (_) {}
}

// ========================================
// СТАТУС
// ========================================

function setState(name, message) {
  document.body.classList.remove("listening", "speaking");

  if (name === "СЛУШАМ") {
    document.body.classList.add("listening");
  }

  if (name === "ГОВОРЯ") {
    document.body.classList.add("speaking");
  }

  state.textContent = name;
  hint.textContent = message;
}

function refreshScreen() {
  mic.disabled = true;
  permissionScreen.hidden = false;

  if (!voiceReady) {
    permissionTitle.textContent =
      voiceError ? "ГРЕШКА ПРИ ГЛАСА" : "ПОДГОТВЯМ DAN";

    permissionDescription.textContent =
      "Подготвям българския глас Димитър. Моделът е около 63 MB.";

    permissionMessage.textContent =
      voiceError
        ? voiceError.message
        : "Изтеглям и запазвам модела на устройството...";

    permissionButton.hidden = !voiceError;
    permissionButton.textContent = "ПОВТОРИ ИЗТЕГЛЯНЕТО";

    setState(
      voiceError ? "ГРЕШКА С ГЛАСА" : "ИЗТЕГЛЯМ ГЛАСА",
      voiceError ? "Не успях да заредя Piper." : "Димитър се подготвя..."
    );

    return;
  }

  if (!microphoneReady) {
    permissionTitle.textContent = "МИКРОФОН";

    permissionDescription.textContent =
      "Гласът Димитър е готов. Сега разреши достъп до микрофона.";

    permissionMessage.textContent =
      microphoneError || "Избери „Разрешаване“ в Chrome.";

    permissionButton.hidden = false;
    permissionButton.textContent = "РАЗРЕШИ МИКРОФОНА";

    setState("РАЗРЕШИ МИКРОФОНА", "Моделът е готов.");
    return;
  }

  permissionScreen.hidden = true;
  permissionButton.hidden = true;

  if (!SpeechRecognition) {
    setState(
      "НЕ СЕ ПОДДЪРЖА",
      "Използвай актуален Google Chrome."
    );
    return;
  }

  mic.disabled = false;

  setState("ГОТОВ", "Димитър е зареден. Натисни големия бутон.");
}

// ========================================
// PIPER БИБЛИОТЕКА
// ========================================

async function loadPiper() {
  const module = await import(
    "https://esm.sh/@jtsage/piper-tts-web@1.2.0"
  );

  console.log("[DAN] Piper exports:", Object.keys(module));

  if (
    typeof module.TtsSession !== "function" ||
    typeof module.CachedFileReader !== "function" ||
    !module.PATH_MAP || typeof module.PATH_MAP !== "object"
  ) {
    throw new Error(
      "Piper пакетът няма очакваните експорти. Налични: " +
      Object.keys(module).join(", ")
    );
  }

  // Регистрираме българския модел в глобалния PATH_MAP.
  module.PATH_MAP[VOICE_ID] = MODEL_PATH;

  if (module.PATH_MAP[VOICE_ID] !== MODEL_PATH) {
    throw new Error(
      "Piper PATH_MAP не прие пътя за " + VOICE_ID
    );
  }

  Piper = module;

  console.log("[DAN] Registered voice path:", Piper.PATH_MAP[VOICE_ID]);
}

// ========================================
// СПЕЦИАЛНА КАРТА ЗА БЪЛГАРСКИЯ МОДЕЛ
// ========================================

function createPiperFileReader() {
  const pathMap = {
    [VOICE_ID]: MODEL_PATH
  };

  const expectedFiles = new Set([
    MODEL_PATH,
    MODEL_PATH + ".json"
  ]);

  return new Piper.CachedFileReader({
    pathMap,

    customLoader: async (url) => {
      const incoming = String(url);
      let pathname = incoming;

      try {
        pathname = new URL(incoming, MODEL_BASE).pathname;
      } catch (_) {}

      pathname = decodeURIComponent(pathname);

      const marker = "/resolve/main/";
      const markerIndex = pathname.indexOf(marker);

      let relativePath = markerIndex >= 0
        ? pathname.slice(markerIndex + marker.length)
        : pathname.replace(/^\/+/, "");

      if (!expectedFiles.has(relativePath)) {
        if (relativePath.endsWith("bg_BG-dimitar-medium.onnx.json")) {
          relativePath = MODEL_PATH + ".json";
        } else if (relativePath.endsWith("bg_BG-dimitar-medium.onnx")) {
          relativePath = MODEL_PATH;
        } else {
          throw new Error(
            "Piper поиска непознат файл: " + incoming
          );
        }
      }

      const finalUrl = MODEL_BASE + relativePath;
      console.log("[DAN] Correct Piper URL:", finalUrl);

      const response = await fetch(finalUrl, {
        method: "GET",
        mode: "cors",
        cache: "force-cache"
      });

      if (!response.ok) {
        throw new Error(
          "Piper download HTTP " + response.status + ": " + finalUrl
        );
      }

      if (!response.body || !response.body.getReader) {
        const blob = await response.blob();
        if (!blob.size) {
          throw new Error("Piper върна празен файл: " + relativePath);
        }
        return blob;
      }

      const reader = response.body.getReader();
      const chunks = [];
      let loaded = 0;

      const headerTotal =
        Number(response.headers.get("content-length")) || 0;

      const expectedTotal = relativePath.endsWith(".onnx.json")
        ? 5123
        : 63221984;

      const total = headerTotal || expectedTotal;

      while (true) {
        const result = await reader.read();
        if (result.done) break;

        chunks.push(result.value);
        loaded += result.value.byteLength;

        if (relativePath.endsWith(".onnx")) {
          const percent = Math.min(
            99,
            Math.round(loaded / total * 100)
          );

          downloadBar.style.width = percent + "%";

          const message =
            "Изтеглям Димитър: " + percent + "% (" +
            (loaded / 1048576).toFixed(1) + " MB)";

          permissionMessage.textContent = message;
          setState("ИЗТЕГЛЯМ " + percent + "%", message);
        } else {
          permissionMessage.textContent =
            "Изтеглям конфигурацията на гласа...";
        }
      }

      const blob = new Blob(chunks, {
        type: response.headers.get("content-type") ||
          "application/octet-stream"
      });

      if (!blob.size) {
        throw new Error("Piper върна празен файл: " + relativePath);
      }

      console.log(
        "[DAN] Piper file downloaded:",
        relativePath,
        blob.size,
        "bytes"
      );

      return blob;
    }
  });
}

// ========================================
// ПРЕДВАРИТЕЛНО ИЗТЕГЛЯНЕ И ЗАРЕЖДАНЕ
// ========================================

async function preloadPiper() {
  if (voiceReady) return;
  if (preloadPromise) return preloadPromise;

  preloadPromise = (async () => {
    voiceError = null;
    refreshScreen();

    try {
      if (!Piper) {
        await loadPiper();
      }

      setState(
        "ЗАРЕЖДАМ ДИМИТЪР",
        "Изтеглям българския модел..."
      );

      permissionMessage.textContent =
        "Подготвям гласа за първа употреба. Изчакай.";

      downloadBar.style.width = "0%";

      const fileReader = createPiperFileReader();

      piperSession = new Piper.TtsSession({
        voiceId: VOICE_ID,
        fileReader,
        allowLocalModels: true,
        fallbackStrategy: "cdn",

        progress: (progress) => {
          const loaded = Number(progress.loaded) || 0;
          const total = Number(progress.total) || 0;

          if (total > 0) {
            const percent = Math.min(
              100,
              Math.round(loaded * 100 / total)
            );

            downloadBar.style.width = percent + "%";

            const loadedMB = (loaded / 1048576).toFixed(1);
            const totalMB = (total / 1048576).toFixed(1);

            const message =
              "Изтеглям Димитър: " +
              percent + "% (" +
              loadedMB + " / " + totalMB + " MB)";

            permissionMessage.textContent = message;
            setState("ИЗТЕГЛЯМ " + percent + "%", message);
          } else {
            permissionMessage.textContent =
              "Зареждам и кеширам българския модел...";
          }
        },

        logger: (message) => {
          console.log("[PIPER]", message);
        }
      });

      // Първият predict зарежда модела в OPFS.
      setState(
        "ПОДГОТВЯМ DAN",
        "Зареждам модела в локалното хранилище..."
      );

      const testAudio = await piperSession.predict(
        "Здравей! Аз съм гласът на THE BIGGEST DAN."
      );

      if (!testAudio || testAudio.size === 0) {
        throw new Error("Piper не генерира тестово аудио.");
      }

      voiceReady = true;
      voiceError = null;

      downloadBar.style.width = "100%";

      console.log("[DAN] Piper ready:", VOICE_ID);

      refreshScreen();

    } catch (error) {
      console.error("[DAN] Piper initialization failed:", error);

      piperSession = null;
      voiceReady = false;
      voiceError = error;

      refreshScreen();
    }
  })();

  try {
    await preloadPromise;
  } finally {
    preloadPromise = null;
  }
}

// ========================================
// МИКРОФОН
// ========================================

async function requestMicrophonePermission() {
  if (!navigator.mediaDevices?.getUserMedia) {
    microphoneError =
      "Отвори сайта през HTTPS в актуален Chrome.";

    refreshScreen();
    return false;
  }

  try {
    const stream = await navigator.mediaDevices.getUserMedia({
      audio: true
    });

    stream.getTracks().forEach(track => track.stop());

    microphoneReady = true;
    microphoneError = null;

    refreshScreen();
    return true;

  } catch (error) {
    console.error("[DAN] Microphone error:", error);

    microphoneReady = false;

    microphoneError =
      error.name === "NotAllowedError"
        ? "Chrome отказа достъп. Провери разрешенията за микрофона."
        : error.message || "Не успях да получа достъп до микрофона.";

    refreshScreen();
    return false;
  }
}

async function initializeMicrophone() {
  try {
    if (navigator.permissions?.query) {
      const permission = await navigator.permissions.query({
        name: "microphone"
      });

      if (permission.state === "granted") {
        microphoneReady = true;
        microphoneError = null;
        refreshScreen();
        return;
      }

      if (permission.state === "denied") {
        microphoneError = "Микрофонът е блокиран за този сайт.";
        refreshScreen();
        return;
      }
    }
  } catch (_) {}

  await requestMicrophonePermission();
}

// ========================================
// ПОВТОРЕН ОПИТ
// ========================================

permissionButton.addEventListener("click", async () => {
  if (!voiceReady) {
    Piper = null;
    piperSession = null;
    await preloadPiper();
  } else {
    await requestMicrophonePermission();
  }
});

// ========================================
// ПОЧИСТВАНЕ НА ТЕКСТА ЗА ГЛАСА
// ========================================

function cleanTextForSpeech(input) {
  let text = String(input ?? "");

  // Премахваме кодовите блокове, но пазим текста в inline code.
  text = text.replace(/```[\s\S]*?```/g, " ");
  text = text.replace(/`([^`]+)`/g, "$1");

  // При линкове произнасяме заглавието, не URL адреса.
  text = text.replace(/\[([^\]]+)\]\((?:[^)]+)\)/g, "$1");
  text = text.replace(/https?:\/\/\S+/gi, " ");

  // Премахваме Markdown заглавия, цитати, списъци и форматиране.
  text = text.replace(/^\s{0,3}#{1,6}\s*/gm, "");
  text = text.replace(/^\s{0,3}>\s?/gm, "");
  text = text.replace(/^\s*(?:[-+*]|\d+[.)])\s+/gm, "");
  text = text.replace(/\*\*|__|~~/g, "");
  text = text.replace(/[*_~]/g, "");

  // Премахваме HTML и разделителите на таблици.
  text = text.replace(/<[^>]*>/g, " ");
  text = text.replace(/\|/g, ", ");

  // Премахваме декоративни знаци.
  text = text.replace(/[★☆•▪◆▶►]/g, " ");
  text = text.replace(/&nbsp;/gi, " ");
  text = text.replace(/&amp;/gi, "и");

  return text.replace(/\s+/g, " ").trim();
}

// ========================================
// БЪЛГАРСКИ ЧИСЛА И ВАЛУТИ
// ========================================

function spellNumber(value, options = {}) {
  if (typeof bgNumberWords !== "function") return null;

  try {
    const result = bgNumberWords(String(value), options);

    return typeof result === "string" && result.trim()
      ? result.trim()
      : null;

  } catch (error) {
    console.warn("[DAN] Number conversion failed:", value, error);
    return null;
  }
}

function convertBulgarianNumbers(input) {
  if (typeof bgNumberWords !== "function") return input;

  let text = input;

  // Лева.
  if (typeof bgNumberWords.currency === "function") {
    text = text.replace(
      /(?<![\p{L}\d])([0-9]+(?:[.,][0-9]{1,2})?)\s*(лв\.?|лева|лев|BGN)(?![\p{L}])/giu,
      (match, amount) => {
        try {
          return bgNumberWords.currency(amount.replace(",", "."));
        } catch (_) {
          return match;
        }
      }
    );

    // Евро.
    text = text.replace(
      /(?<![\p{L}\d])([0-9]+(?:[.,][0-9]{1,2})?)\s*(EUR|€)(?![\p{L}])/giu,
      (match, amount) => {
        try {
          return bgNumberWords.currency(amount.replace(",", "."), {
            currency: "EUR"
          });
        } catch (_) {
          return match;
        }
      }
    );

    // Долари.
    text = text.replace(
      /(?<![\p{L}\d])([0-9]+(?:[.,][0-9]{1,2})?)\s*(USD|\$)(?![\p{L}])/giu,
      (match, amount) => {
        try {
          return bgNumberWords.currency(amount.replace(",", "."), {
            currency: "USD"
          });
        } catch (_) {
          return match;
        }
      }
    );
  }

  // Проценти.
  text = text.replace(
    /(?<![\p{L}\d])([0-9]{1,9})\s*%(?![\p{L}])/gu,
    (match, digits) => {
      const words = spellNumber(digits, { gender: "m" });
      if (!words) return match;

      return words + (digits === "1" ? " процент" : " процента");
    }
  );

  // Обикновени цели числа.
  // Не променяме дати, десетични числа, версии, телефони и дълги идентификатори.
  const integerPattern =
    /(?<![\p{L}\d.,/:-])((?:\d{1,3}(?:[ \u00A0]\d{3})+)|\d{1,9})(?![\p{L}\d.,/:-])/gu;

  text = text.replace(integerPattern, (original) => {
    const digits = original.replace(/[ \u00A0]/g, "");

    if (!/^\d{1,9}$/.test(digits)) return original;

    return spellNumber(digits) || original;
  });

  return text;
}

async function prepareSpeechText(input) {
  const cleaned = cleanTextForSpeech(input);

  await bgNumberWordsReady;

  return convertBulgarianNumbers(cleaned);
}

// ========================================
// РАЗДЕЛЯНЕ НА ДЪЛГИ ОТГОВОРИ
// ========================================

function splitText(text, maxLength = 420) {
  const sentences = text.match(/[^.!?…]+[.!?…]*/g) || [text];

  const chunks = [];
  let current = "";

  for (const sentence of sentences) {
    const clean = sentence.trim();
    if (!clean) continue;

    if (clean.length > maxLength) {
      if (current) {
        chunks.push(current);
        current = "";
      }

      const words = clean.split(/\s+/);
      let part = "";

      for (const word of words) {
        const candidate = part ? part + " " + word : word;

        if (candidate.length > maxLength && part) {
          chunks.push(part);
          part = word;
        } else {
          part = candidate;
        }
      }

      if (part) chunks.push(part);
      continue;
    }

    const candidate = current ? current + " " + clean : clean;

    if (candidate.length > maxLength && current) {
      chunks.push(current);
      current = clean;
    } else {
      current = candidate;
    }
  }

  if (current) chunks.push(current);

  return chunks;
}

// ========================================
// СПИРАНЕ НА АУДИО
// ========================================

function stopAudio() {
  if (currentAudio) {
    currentAudio.pause();
  }

  if (finishAudio) {
    const finish = finishAudio;
    finishAudio = null;
    finish();
  }
}

// ========================================
// ВЪЗПРОИЗВЕЖДАНЕ НА PIPER
// ========================================

function playPiperAudio(blob, onPlaying) {
  return new Promise((resolve, reject) => {
    try {
      stopAudio();

      const url = URL.createObjectURL(blob);
      const audio = new Audio(url);

      currentAudio = audio;
      speaking = true;

      // Малко по-бързо произнасяне, със запазване на височината на гласа.
      audio.playbackRate = 1.10;

      if ("preservesPitch" in audio) {
        audio.preservesPitch = true;
      }

      setState("ГОВОРЯ", "THE BIGGEST DAN ти отговаря...");

      let finished = false;

      function finish(error) {
        if (finished) return;
        finished = true;

        audio.onended = null;
        audio.onplaying = null;
        audio.onerror = null;

        audio.pause();
        audio.src = "";

        URL.revokeObjectURL(url);

        if (currentAudio === audio) {
          currentAudio = null;
        }

        if (finishAudio === finish) {
          finishAudio = null;
        }

        speaking = false;

        if (error) reject(error);
        else resolve();
      }

      finishAudio = finish;

      let playbackStarted = false;

      audio.onplaying = () => {
        if (playbackStarted) return;

        playbackStarted = true;

        if (typeof onPlaying === "function") {
          onPlaying();
        }
      };

      audio.onended = () => finish();

      audio.onerror = () => {
        finish(new Error("Проблем при възпроизвеждане на Piper аудиото."));
      };

      audio.play().catch(error => {
        finish(new Error("Браузърът отказа аудиото: " + error.message));
      });

    } catch (error) {
      speaking = false;
      reject(error);
    }
  });
}

async function speakWithPiper(text) {
  if (!voiceReady || !piperSession) {
    throw new Error("Гласът Димитър още не е готов.");
  }

  const chunks = splitText(text, 420);

  let pendingAudio = piperSession.predict(chunks[0]);

  for (let i = 0; i < chunks.length; i++) {
    if (!sessionActive) break;

    setState(
      "ГОВОРЯ",
      chunks.length > 1
        ? "Говоря... Част " + (i + 1) + " от " + chunks.length
        : "THE BIGGEST DAN ти отговаря..."
    );

    const synthesisStarted = performance.now();
    const wav = await pendingAudio;

    console.log(
      "[DAN] Piper synthesis chunk " + (i + 1) + "/" + chunks.length +
      " took " + Math.round(performance.now() - synthesisStarted) + " ms"
    );

    if (!sessionActive) break;

    let nextAudioPromise = null;

    // Подготвяме следващата част, докато текущата вече се възпроизвежда.
    await playPiperAudio(wav, () => {
      if (i + 1 < chunks.length && sessionActive && !nextAudioPromise) {
        nextAudioPromise = piperSession.predict(chunks[i + 1]);

        // Предотвратява необработено Promise rejection.
        nextAudioPromise.catch(() => {});
      }
    });

    if (i + 1 < chunks.length && sessionActive) {
      pendingAudio =
        nextAudioPromise || piperSession.predict(chunks[i + 1]);
    }
  }
}

// ========================================
// GROQ / QWEN
// ========================================

async function askAI(message) {
  waiting = true;

  setState("МИСЛЯ", "THE BIGGEST DAN подготвя отговор...");

  const controller = new AbortController();
  activeController = controller;

  try {
    const groqStarted = performance.now();

    const response = await fetch(API, {
      method: "POST",

      headers: {
        "Content-Type": "application/json"
      },

      signal: controller.signal,

      body: JSON.stringify({
        message,
        history: history.slice(-10)
      })
    });

    let data;

    try {
      data = await response.json();
    } catch (_) {
      throw new Error("Worker върна невалиден отговор.");
    }

    if (!response.ok) {
      throw new Error(
        data.details || data.error || "Грешка при връзката с Groq."
      );
    }

    if (typeof data.answer !== "string" || !data.answer.trim()) {
      throw new Error("Qwen не върна текстов отговор.");
    }

    const groqMs = Math.round(performance.now() - groqStarted);

    console.log(
      "[DAN] Groq request completed in " + groqMs + " ms"
    );

    history.push(
      { role: "user", text: message },
      { role: "assistant", text: data.answer }
    );

    saveHistory();

    waiting = false;

    if (activeController === controller) {
      activeController = null;
    }

    if (!sessionActive) return;

    // Премахваме Markdown и изписваме числата с български думи.
    const speechText = await prepareSpeechText(data.answer);

    console.log("[DAN] Clean speech text:", speechText);

    setState(
      "ПОДГОТВЯМ ГЛАСА",
      "Отговорът е готов. Подготвям озвучаването..."
    );

    await speakWithPiper(speechText);

    if (sessionActive) {
      startListening();
    }

  } catch (error) {
    waiting = false;

    if (activeController === controller) {
      activeController = null;
    }

    if (error.name === "AbortError") return;

    console.error("[DAN]", error);

    if (sessionActive) {
      setState("ГРЕШКА", error.message || "Възникна проблем.");
    }
  }
}

// ========================================
// ГЛАСОВО РАЗПОЗНАВАНЕ
// ========================================

function startListening() {
  if (!sessionActive || listening || speaking || waiting) return;

  if (!voiceReady || !microphoneReady) {
    sessionActive = false;
    refreshScreen();
    return;
  }

  if (!SpeechRecognition) {
    sessionActive = false;

    setState(
      "НЕ СЕ ПОДДЪРЖА",
      "Използвай актуален Google Chrome."
    );

    return;
  }

  const instance = new SpeechRecognition();
  recognition = instance;

  instance.lang = "bg-BG";
  instance.continuous = false;
  instance.interimResults = false;
  instance.maxAlternatives = 1;

  instance.onstart = () => {
    if (recognition !== instance) return;

    listening = true;
    setState("СЛУШАМ", "Говори спокойно...");
  };

  instance.onresult = async event => {
    if (recognition !== instance) return;

    const transcript =
      event.results?.[0]?.[0]?.transcript?.trim() || "";

    listening = false;

    if (transcript) {
      setState("МИСЛЯ", "Получих съобщението ти...");
      await askAI(transcript);
    } else {
      sessionActive = false;
      setState("НЕ ЧУХ", "Не успях да разбера. Опитай отново.");
    }
  };

  instance.onerror = event => {
    if (recognition !== instance) return;

    listening = false;
    sessionActive = false;

    console.error("[DAN] Recognition error:", event.error);

    if (event.error === "not-allowed") {
      microphoneReady = false;
      microphoneError = "Chrome отказа достъп до микрофона.";
      refreshScreen();

    } else if (event.error === "no-speech") {
      setState("НЕ ЧУХ", "Не чух глас. Опитай отново.");

    } else {
      setState("ГРЕШКА", "Проблем с микрофона или разпознаването.");
    }
  };

  instance.onend = () => {
    if (recognition !== instance) return;

    listening = false;

    if (sessionActive && !speaking && !waiting) {
      sessionActive = false;
      setState("ГОТОВ", "Натисни бутона, за да продължиш.");
    }
  };

  try {
    instance.start();

  } catch (error) {
    listening = false;
    sessionActive = false;

    console.error(error);

    setState("ГРЕШКА", "Не успях да стартирам микрофона.");
  }
}

// ========================================
// ОСНОВЕН БУТОН
// ========================================

mic.addEventListener("click", () => {
  if (!voiceReady || !microphoneReady || waiting) return;

  if (listening && recognition) {
    sessionActive = false;

    try {
      recognition.stop();
    } catch (_) {}

    listening = false;
    setState("ГОТОВ", "Слушането е спряно.");
    return;
  }

  if (speaking) {
    stopAudio();
  }

  sessionActive = true;
  startListening();
});

// ========================================
// ПРИКЛЮЧВАНЕ
// ========================================

stopButton.addEventListener("click", () => {
  sessionActive = false;

  if (recognition) {
    try {
      recognition.abort();
    } catch (_) {}

    recognition = null;
  }

  if (activeController) {
    activeController.abort();
    activeController = null;
  }

  stopAudio();

  listening = false;
  speaking = false;
  waiting = false;

  if (voiceReady && microphoneReady) {
    setState("ГОТОВ", "Разговорът приключи.");
  } else {
    refreshScreen();
  }
});

// ========================================
// СТАРТИРАНЕ
// ========================================

initializeMicrophone();
preloadPiper();

// Преобразувателят на числа се зарежда паралелно с Piper.
bgNumberWordsReady.catch(() => {});

</script>

</body>
</html>