// visions.jsx - symbolic image cards for demonology and dream readings

const DEMON_REGISTRY = [
  ["Bael", "Баель", "King", 1, ["bael", "baal", "баал", "баель"], ["жаба", "кіт", "людина"], "Схід"],
  ["Agares", "Агарес", "Duke", 2, ["agares", "агарес"], ["старець", "яструб", "ящір"], "Схід"],
  ["Vassago", "Вассаго", "Prince", 3, ["vassago", "вассаго"], ["ключ", "дзеркало", "пам'ять"], "Захід"],
  ["Samigina", "Самігіна", "Marquis", 4, ["samigina", "gamigin", "самігіна", "гамигин", "гамігін"], ["кінь", "голос", "мертві"], "Північ"],
  ["Marbas", "Марбас", "President", 5, ["marbas", "марбас"], ["лев", "механіка", "лікування"], "Південь"],
  ["Valefor", "Валефор", "Duke", 6, ["valefor", "malaphar", "валефор"], ["лев", "рука", "крадіж"], "Захід"],
  ["Amon", "Амон", "Marquis", 7, ["amon", "amonas", "амон"], ["вовк", "ворон", "полум'я"], "Південь"],
  ["Barbatos", "Барбатос", "Duke", 8, ["barbatos", "барбатос"], ["ліс", "звірина", "сурма"], "Схід"],
  ["Paimon", "Паймон", "King", 9, ["paimon", "паймон", "паимон"], ["верблюд", "корона", "сурми"], "Захід"],
  ["Buer", "Буер", "President", 10, ["buer", "буер"], ["колесо", "трави", "філософія"], "Південь"],
  ["Gusion", "Гусіон", "Duke", 11, ["gusion", "gusoin", "гусион", "гусіон"], ["ксенофіл", "пам'ять", "відповідь"], "Захід"],
  ["Sitri", "Сітрі", "Prince", 12, ["sitri", "ситри", "сітрі"], ["леопард", "крила", "пристрасть"], "Південь"],
  ["Beleth", "Белет", "King", 13, ["beleth", "bileth", "белет", "білеф"], ["кінь", "музика", "вогонь"], "Схід"],
  ["Leraje", "Лерайє", "Marquis", 14, ["leraje", "leraie", "leraye", "лерайе", "лерайє"], ["лучник", "стріла", "рана"], "Південь"],
  ["Eligos", "Елігос", "Duke", 15, ["eligos", "abigor", "элигос", "елігос", "абигор"], ["вершник", "спис", "прапор"], "Схід"],
  ["Zepar", "Зепар", "Duke", 16, ["zepar", "зепар"], ["червона броня", "любов", "плідність"], "Захід"],
  ["Botis", "Ботіс", "President / Earl", 17, ["botis", "ботис", "ботіс"], ["змія", "меч", "перемир'я"], "Північ"],
  ["Bathin", "Батін", "Duke", 18, ["bathin", "батин", "батін"], ["змія", "кінь", "мандри"], "Захід"],
  ["Sallos", "Саллос", "Duke", 19, ["sallos", "saleos", "саллос", "салеос"], ["крокодил", "корона", "кохання"], "Захід"],
  ["Purson", "Пурсон", "King", 20, ["purson", "пурсон"], ["лев", "ведмідь", "сурма"], "Південь"],
  ["Marax", "Маракс", "Earl / President", 21, ["marax", "morax", "маракс", "моракс"], ["бик", "зорі", "каміння"], "Північ"],
  ["Ipos", "Іпос", "Earl / Prince", 22, ["ipos", "ipos", "іпос", "ипос"], ["янгол", "лев", "гуска"], "Захід"],
  ["Aim", "Айм", "Duke", 23, ["aim", "aym", "haborym", "айм", "аим", "хаборим"], ["змія", "вогонь", "три голови"], "Південь"],
  ["Naberius", "Наберіус", "Marquis", 24, ["naberius", "cerberus", "наберіус", "наберий", "цербер"], ["ворон", "голоси", "риторика"], "Північ"],
  ["Glasya-Labolas", "Глася-Лаболас", "Earl / President", 25, ["glasya", "labolas", "глася", "лаболас"], ["пес", "крила", "кров"], "Південь"],
  ["Bune", "Буне", "Duke", 26, ["bune", "bime", "буне", "біме"], ["дракон", "скарби", "могили"], "Захід"],
  ["Ronove", "Ронове", "Marquis / Earl", 27, ["ronove", "ронове"], ["язики", "служителі", "мова"], "Схід"],
  ["Berith", "Беріт", "Duke", 28, ["berith", "baalberith", "берит", "беріт"], ["кінь", "червона броня", "перстень"], "Південь"],
  ["Astaroth", "Астарот", "Duke", 29, ["astaroth", "астарт", "астараф", "астарот"], ["дракон", "гадюка", "архів"], "Захід"],
  ["Forneus", "Форнеус", "Marquis", 30, ["forneus", "форнеус"], ["морське чудо", "мови", "репутація"], "Захід"],
  ["Foras", "Форас", "President", 31, ["foras", "forras", "форас"], ["трави", "каміння", "невидимість"], "Північ"],
  ["Asmoday", "Асмодей", "King", 32, ["asmoday", "asmodeus", "асмодей", "асмодеус"], ["три голови", "змій", "вежа"], "Південь"],
  ["Gaap", "Гаап", "President / Prince", 33, ["gaap", "goap", "гаап"], ["крила", "провідник", "філософія"], "Захід"],
  ["Furfur", "Фурфур", "Earl", 34, ["furfur", "фурфур"], ["олень", "грім", "блискавка"], "Північ"],
  ["Marchosias", "Мархоціас", "Marquis", 35, ["marchosias", "мархосиас", "мархоціас"], ["вовк", "крила", "змій"], "Південь"],
  ["Stolas", "Столас", "Prince", 36, ["stolas", "столас"], ["сова", "зорі", "трави"], "Захід"],
  ["Phenex", "Фенекс", "Marquis", 37, ["phenex", "phoenix", "фенекс", "фенікс"], ["фенікс", "пісня", "попіл"], "Південь"],
  ["Halphas", "Халфас", "Earl", 38, ["halphas", "malthus", "халфас", "мальтус"], ["голуб", "вежа", "зброя"], "Схід"],
  ["Malphas", "Малфас", "President", 39, ["malphas", "малфас"], ["ворон", "будівля", "вежа"], "Схід"],
  ["Raum", "Раум", "Earl", 40, ["raum", "раум"], ["ворон", "руїна", "скарб"], "Північ"],
  ["Focalor", "Фокалор", "Duke", 41, ["focalor", "форкалор", "фокалор"], ["крила", "море", "вітер"], "Захід"],
  ["Vepar", "Вепар", "Duke", 42, ["vepar", "separ", "вепар"], ["русалка", "море", "корабель"], "Захід"],
  ["Sabnock", "Сабнок", "Marquis", 43, ["sabnock", "savnok", "сабнок"], ["лев", "вершник", "вежа"], "Північ"],
  ["Shax", "Шакс", "Marquis", 44, ["shax", "chax", "шакс"], ["лелека", "голос", "обман"], "Захід"],
  ["Vine", "Віне", "King / Earl", 45, ["vine", "вин", "віне", "вайн"], ["лев", "змій", "вежа"], "Північ"],
  ["Bifrons", "Біфронс", "Earl", 46, ["bifrons", "біфронс", "бифронс"], ["свічка", "каміння", "могили"], "Північ"],
  ["Vual", "Вуал", "Duke", 47, ["vual", "ual", "вул", "вуал"], ["дромадер", "кохання", "приязнь"], "Захід"],
  ["Haagenti", "Хаагенті", "President", 48, ["haagenti", "хаагенти", "хаагенті"], ["бик", "крила", "трансмутація"], "Південь"],
  ["Crocell", "Кроцелл", "Duke", 49, ["crocell", "procell", "кроцелл", "кроцель"], ["янгол", "води", "геометрія"], "Захід"],
  ["Furcas", "Фуркас", "Knight", 50, ["furcas", "фуркас"], ["старець", "кінь", "спис"], "Північ"],
  ["Balam", "Балам", "King", 51, ["balam", "балам"], ["бик", "людина", "змія"], "Південь"],
  ["Alloces", "Аллоцес", "Duke", 52, ["alloces", "allocer", "аллоцес", "аллоцер"], ["лев", "кінь", "зорі"], "Схід"],
  ["Caim", "Каїм", "President", 53, ["caim", "camio", "каим", "каїм"], ["птах", "меч", "спір"], "Схід"],
  ["Murmur", "Мурмур", "Duke / Earl", 54, ["murmur", "мурмур"], ["стерв'ятник", "труби", "мертві"], "Північ"],
  ["Orobas", "Оробас", "Prince", 55, ["orobas", "оробас"], ["кінь", "правда", "захист"], "Захід"],
  ["Gremory", "Греморі", "Duke", 56, ["gremory", "gamori", "гремори", "греморі"], ["верблюд", "корона", "скарб"], "Захід"],
  ["Ose", "Осе", "President", 57, ["ose", "oso", "voso", "осе"], ["леопард", "форма", "омана"], "Південь"],
  ["Amy", "Амі", "President", 58, ["amy", "avnas", "амі", "ами", "авнас"], ["полум'я", "зорі", "скарб"], "Південь"],
  ["Orias", "Оріас", "Marquis", 59, ["orias", "оріас", "ориас"], ["лев", "змій", "зорі"], "Схід"],
  ["Vapula", "Вапула", "Duke", 60, ["vapula", "нафула", "вапула"], ["лев", "ремесло", "книга"], "Схід"],
  ["Zagan", "Заган", "King / President", 61, ["zagan", "заган"], ["бик", "грифон", "вино"], "Південь"],
  ["Valac", "Валак", "President", 62, ["valac", "volac", "uvalac", "валак", "волак"], ["дитя", "дракон", "змії"], "Північ"],
  ["Andras", "Андрас", "Marquis", 63, ["andras", "андрас"], ["сова", "вовк", "меч"], "Південь"],
  ["Haures", "Хаурес", "Duke", 64, ["haures", "flauros", "хаурес", "флаурос"], ["леопард", "вогонь", "правда"], "Південь"],
  ["Andrealphus", "Андреалфус", "Marquis", 65, ["andrealphus", "андреалфус"], ["павич", "геометрія", "міра"], "Схід"],
  ["Cimejes", "Кімейєс", "Marquis", 66, ["cimejes", "kimaris", "cimeries", "кимарис", "кімейєс"], ["воїн", "кінь", "скарб"], "Північ"],
  ["Amdusias", "Амдуціас", "Duke", 67, ["amdusias", "amdukias", "амдуциас", "амдуціас"], ["єдиноріг", "музика", "дерева"], "Схід"],
  ["Belial", "Беліал", "King", 68, ["belial", "белиал", "беліал"], ["корона", "вогонь", "договір"], "Південь"],
  ["Decarabia", "Декарабія", "Marquis", 69, ["decarabia", "декарабия", "декарабія"], ["пентаграма", "каміння", "птахи"], "Захід"],
  ["Seere", "Сеере", "Prince", 70, ["seere", "seer", "sear", "сеере", "сеар"], ["кінь", "швидкість", "знахідка"], "Схід"],
  ["Dantalion", "Данталіон", "Duke", 71, ["dantalion", "данталион", "данталіон"], ["книга", "обличчя", "думки"], "Захід"],
  ["Andromalius", "Андромаліус", "Earl", 72, ["andromalius", "андромалиус", "андромаліус"], ["змія", "злодій", "повернення"], "Північ"],
  ["Lilith", "Ліліт", "Nocturnal archetype", 0, ["lilith", "ліліт", "лилит"], ["ніч", "птах", "чорний місяць"], "Захід"],
  ["Lucifer", "Люцифер", "Light-bearer archetype", 0, ["lucifer", "люцифер"], ["ранкова зоря", "факел", "падіння"], "Схід"],
  ["Baphomet", "Бафомет", "Hermetic icon", 0, ["baphomet", "бафомет"], ["козел", "факел", "solve et coagula"], "Центр"],
];

const DEMON_RANKS = {
  King: { ua: "Король", frame: "crown", element: "Вогонь", stroke: "#e3bd52" },
  Duke: { ua: "Герцог", frame: "wings", element: "Земля / Венера", stroke: "#c9a227" },
  Prince: { ua: "Князь", frame: "star", element: "Повітря", stroke: "#d9c178" },
  Marquis: { ua: "Маркіз", frame: "horns", element: "Марс / Місяць", stroke: "#bfa04b" },
  President: { ua: "Президент", frame: "book", element: "Меркурій", stroke: "#d7b95b" },
  Earl: { ua: "Граф", frame: "moon", element: "Сатурн", stroke: "#b59b62" },
  Knight: { ua: "Лицар", frame: "spear", element: "Марс", stroke: "#d6a84c" },
  Spirit: { ua: "Дух", frame: "veil", element: "Ефір", stroke: "#c9a227" },
};

function visionNormalize(text) {
  return String(text || "")
    .normalize("NFKD")
    .replace(/[\u0300-\u036f]/g, "")
    .replace(/ё/g, "е")
    .toLowerCase();
}

function visionHash(input) {
  const text = String(input || "azoth");
  let h = 2166136261;
  for (let i = 0; i < text.length; i += 1) {
    h ^= text.charCodeAt(i);
    h = Math.imul(h, 16777619);
  }
  return h >>> 0;
}

function demonRankKey(rank) {
  const value = String(rank || "");
  if (/king/i.test(value)) return "King";
  if (/duke/i.test(value)) return "Duke";
  if (/prince/i.test(value)) return "Prince";
  if (/marquis/i.test(value)) return "Marquis";
  if (/president/i.test(value)) return "President";
  if (/earl/i.test(value)) return "Earl";
  if (/knight/i.test(value)) return "Knight";
  if (/spirit|archetype|icon/i.test(value)) return "Spirit";
  return "President";
}

function demonEntries() {
  return DEMON_REGISTRY.map(([name, ua, rank, order, aliases, symbols, direction]) => ({
    name, ua, rank, order, aliases, symbols, direction, known: true,
  }));
}

function hasDemonNameContext(text) {
  const q = visionNormalize(text);
  return /(демон(?:а|у|ом|ів|ов|и)?|дух(?:а|у|ом|ів|ов)?|goetia|goetic|гоет(?:ія|ии|ич)|гр[іи]муар|grimoire|печать\s+духа|печат[ьи]\s+духа|сиг[іи]л\s+духа)/i.test(q);
}

function invalidDemonCandidate(name) {
  const q = visionNormalize(name).trim();
  if (!q) return true;
  if (/^(фото|photo|image|img|картин|зображ|браслет|камін|камень|камни|кристал|кристалл|клієнт|клиент|людина|человек)$/.test(q)) return true;
  if (/^(хто|кто|який|яка|яке|якого|какой|какая|какое|какого|чи|или|або|ли|його|его|мій|мой|твій|твой)(?:\s|$)/.test(q)) return true;
  if (/(^|\s)(чи|или|або)\s+(біс|бес|демон|дух)(\s|$)/.test(q)) return true;
  if (/^(біс|бес|демон|дух)(?:\s|$)/.test(q)) return true;
  return false;
}

function findDemon(text) {
  const q = visionNormalize(text);
  const entries = demonEntries();
  for (const demon of entries) {
    const aliases = [demon.name, demon.ua, ...(demon.aliases || [])].map(visionNormalize);
    for (const alias of aliases) {
      if (!alias) continue;
      const tail = /[а-яіїєґ]/i.test(alias) ? "(?:а|у|ом|е|і|ою|ой|ів|ов)?" : "";
      const pattern = new RegExp(`(^|[^\\p{L}\\p{N}])${alias.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}${tail}([^\\p{L}\\p{N}]|$)`, "u");
      if (pattern.test(q)) return demon;
    }
  }
  if (!hasDemonNameContext(text)) return null;
  const m = String(text || "").match(/(?:демон(?:а|у|ом|ів|ов)?|дух(?:а|у|ом|ів|ов)?|goetia|goetic|гоет(?:ія|ии|ич)?|гр[іи]муар|grimoire|печать\s+духа|печат[ьи]\s+духа|сиг[іи]л\s+духа)\s*(?:на ім['ʼ]я|по імені|по имени|імені|имени|про|о|about)?\s+([A-Za-zА-Яа-яІіЇїЄєҐґ'ʼ -]{3,34})/i);
  if (m) {
    const name = m[1].split(/\s+(?:і|и|та|для|что|що|как|як|покажи|намалюй|нарисуй)\s+/i)[0].trim();
    if (!invalidDemonCandidate(name)) {
      return { name, ua: name, rank: "Spirit", order: 0, aliases: [], symbols: ["тінь", "знак", "поріг"], direction: "Центр", known: false };
    }
  }
  return null;
}

function shouldBuildDemonVision(active, text) {
  return Boolean(findDemon(text));
}

function buildDemonVision(text) {
  const demon = findDemon(text) || { name: "Unknown Spirit", ua: "Невідомий дух", rank: "Spirit", order: 0, symbols: ["тінь", "поріг", "знак"], direction: "Центр" };
  const rankKey = demonRankKey(demon.rank);
  const rank = DEMON_RANKS[rankKey] || { ua: "Дух", frame: "veil", element: "Ефір", stroke: "#c9a227" };
  const hash = visionHash(`${demon.name}|${demon.rank}|${text}`);
  const seal = Array.from({ length: 9 }).map((_, i) => (hash >> (i * 3)) % 12);
  return {
    type: "demon_vision",
    version: 1,
    name: demon.ua || demon.name,
    latinName: demon.name,
    rank: rank.ua,
    rankRaw: demon.rank,
    order: demon.order || null,
    direction: demon.direction || "Центр",
    element: rank.element,
    symbols: demon.symbols || ["тінь", "поріг", "знак"],
    frame: rank.frame,
    tone: rank.stroke,
    seed: hash.toString(16).toUpperCase(),
    seal,
    query: String(text || "").slice(0, 260),
  };
}

function demonVisionContext(vision) {
  if (!vision) return "";
  const order = vision.order ? `#${vision.order} ` : "";
  return [
    "",
    `[Візуальна демонологічна картка вже показана в інтерфейсі: ${order}${vision.name} (${vision.latinName}), ранг ${vision.rank}, напрям ${vision.direction}, ключі: ${(vision.symbols || []).join(", ")}. Тлумач саме цього духа і не пиши, що зображення відсутнє.]`,
  ].join("\n");
}

function DemonSeal({ vision }) {
  const seal = vision.seal || [];
  const stroke = vision.tone || "#c9a227";
  const cx = 250;
  const cy = 250;
  const r = 156;
  const points = seal.map((n, i) => {
    const a = -Math.PI / 2 + ((n + i * 2) % 12) * Math.PI / 6;
    return `${(cx + Math.cos(a) * (r - (i % 3) * 22)).toFixed(1)},${(cy + Math.sin(a) * (r - (i % 3) * 22)).toFixed(1)}`;
  }).join(" ");
  const hasCrown = vision.frame === "crown";
  const hasBook = vision.frame === "book";
  const hasSpear = vision.frame === "spear";
  const hasMoon = vision.frame === "moon";
  const hasStar = vision.frame === "star";
  const hasWings = vision.frame === "wings";
  return (
    <svg className="ovision__svg" viewBox="0 0 500 500" role="img" aria-label={`Образ ${vision.name}`}>
      <defs>
        <radialGradient id={`dvCore-${vision.seed}`} cx="50%" cy="42%" r="58%">
          <stop offset="0%" stopColor="#fff2c8" stopOpacity="0.9" />
          <stop offset="36%" stopColor={stroke} stopOpacity="0.34" />
          <stop offset="100%" stopColor="#080706" stopOpacity="0" />
        </radialGradient>
        <filter id={`dvGlow-${vision.seed}`} x="-45%" y="-45%" width="190%" height="190%">
          <feGaussianBlur stdDeviation="4" result="b" />
          <feMerge><feMergeNode in="b" /><feMergeNode in="SourceGraphic" /></feMerge>
        </filter>
      </defs>
      <rect width="500" height="500" rx="18" fill="#0b0908" />
      <circle cx="250" cy="226" r="210" fill={`url(#dvCore-${vision.seed})`} opacity="0.78" />
      <g fill="none" stroke={stroke} strokeWidth="1.2" opacity="0.42">
        <circle cx="250" cy="250" r="190" />
        <circle cx="250" cy="250" r="136" strokeDasharray="2 9" />
        <polygon points="250,54 420,348 80,348" opacity="0.55" />
        <polygon points="250,418 96,150 404,150" opacity="0.32" />
      </g>
      {hasWings && (
        <g fill="none" stroke={stroke} strokeWidth="2" opacity="0.45">
          <path d="M218 232 C116 112, 70 216, 124 310 C158 258, 188 244, 224 270" />
          <path d="M282 232 C384 112, 430 216, 376 310 C342 258, 312 244, 276 270" />
        </g>
      )}
      <g filter={`url(#dvGlow-${vision.seed})`}>
        <path d="M250 128 C226 156, 212 184, 218 222 C188 244, 178 284, 184 338 C206 374, 294 374, 316 338 C322 284, 312 244, 282 222 C288 184, 274 156, 250 128 Z" fill="#14100c" stroke={stroke} strokeWidth="2" />
        <path d="M222 164 C188 112, 164 102, 150 94 C170 136, 178 164, 216 194" fill="none" stroke={stroke} strokeWidth="2.3" />
        <path d="M278 164 C312 112, 336 102, 350 94 C330 136, 322 164, 284 194" fill="none" stroke={stroke} strokeWidth="2.3" />
        <circle cx="232" cy="216" r="4.5" fill="#f3d778" />
        <circle cx="268" cy="216" r="4.5" fill="#f3d778" />
        <path d="M232 258 C246 268, 260 268, 274 258" fill="none" stroke="#dac78e" strokeWidth="1.2" opacity="0.72" />
      </g>
      {hasCrown && (
        <path d="M198 126 L224 92 L250 126 L276 92 L302 126 Z" fill="rgba(201,162,39,0.18)" stroke={stroke} strokeWidth="2" />
      )}
      {hasBook && (
        <g transform="translate(168 330)" fill="rgba(0,0,0,0.22)" stroke={stroke} opacity="0.78">
          <path d="M0 0 C42 -16, 70 -6, 82 10 L82 66 C58 48, 28 46, 0 60 Z" />
          <path d="M164 0 C122 -16, 94 -6, 82 10 L82 66 C106 48, 136 46, 164 60 Z" />
          <line x1="82" y1="10" x2="82" y2="66" />
        </g>
      )}
      {hasSpear && <path d="M362 106 L402 78 L386 122 L306 322" stroke={stroke} strokeWidth="3" fill="none" opacity="0.7" />}
      {hasMoon && <path d="M358 128 C326 142, 318 178, 336 202 C304 190, 296 136, 342 112 Z" fill={stroke} opacity="0.28" />}
      {hasStar && (
        <polygon points="250,76 264,116 306,116 272,140 284,180 250,156 216,180 228,140 194,116 236,116" fill="rgba(201,162,39,0.2)" stroke={stroke} />
      )}
      <polyline points={points} fill="none" stroke={stroke} strokeWidth="2" opacity="0.82" />
      <g fill={stroke} opacity="0.74">
        {seal.slice(0, 7).map((n, i) => {
          const a = -Math.PI / 2 + ((n + i * 2) % 12) * Math.PI / 6;
          const rr = 156 - (i % 3) * 22;
          return <circle key={i} cx={cx + Math.cos(a) * rr} cy={cy + Math.sin(a) * rr} r={4.2} />;
        })}
      </g>
      <text x="250" y="444" textAnchor="middle" className="ovision__svg-title">{vision.latinName}</text>
    </svg>
  );
}

function DemonVisionView({ vision }) {
  if (!vision) return null;
  return (
    <section className="ovision ovision--demon">
      <div className="ovision__top">
        <div>
          <div className="ovision__eyebrow">Demonologia · Imago</div>
          <div className="ovision__title">{vision.name}</div>
        </div>
        <div className="ovision__meta">
          {vision.order ? `Goetia ${vision.order}` : "Архетип"}
          <small>{vision.rank}</small>
        </div>
      </div>
      <div className="ovision__grid">
        <DemonSeal vision={vision} />
        <div className="ovision__side">
          <div className="ovision__statement">Стилізований образ духа у палітрі Azoth для демонологічної консультації.</div>
          <div className="ovision__formula">
            <div><span>Латинське ім'я</span><b>{vision.latinName}</b></div>
            <div><span>Ранг</span><b>{vision.rank}</b></div>
            <div><span>Напрям</span><b>{vision.direction}</b></div>
            <div><span>Стихійний ключ</span><b>{vision.element}</b></div>
            <div><span>Печать</span><b>{vision.seed}</b></div>
          </div>
          <div className="ovision__chips">
            {(vision.symbols || []).map(symbol => <span key={symbol}>{symbol}</span>)}
          </div>
        </div>
      </div>
    </section>
  );
}

const DREAM_MOTIFS = [
  { id: "water", rx: /(вода|ріка|река|море|океан|дощ|дожд|плав|тону|утоп|озеро|river|sea|ocean)/i, name: "Вода", element: "Вода", operation: "розчинення", symbol: "хвиля" },
  { id: "fire", rx: /(вогонь|огонь|пожеж|плам|полум|свеч|свіч|fire|flame)/i, name: "Вогонь", element: "Вогонь", operation: "кальцинація", symbol: "полум'я" },
  { id: "teeth", rx: /(зуб|зуби|teeth|tooth)/i, name: "Зуби", element: "Земля", operation: "сепарація", symbol: "кістка" },
  { id: "snake", rx: /(змія|змея|гадюк|уж|snake|serpent)/i, name: "Змія", element: "Земля / Вода", operation: "трансмутація", symbol: "серпент" },
  { id: "bird", rx: /(птах|птиц|ворон|сова|орел|bird|raven|owl)/i, name: "Птах", element: "Повітря", operation: "сублімація", symbol: "крило" },
  { id: "dead", rx: /(мертв|помер|умер|кладов|могил|предок|dead|grave|ancestor)/i, name: "Предки", element: "Сатурн", operation: "нігредо", symbol: "пам'ять" },
  { id: "door", rx: /(двер|ворот|брама|ключ|замок|door|gate|key)/i, name: "Поріг", element: "Меркурій", operation: "ініціація", symbol: "ключ" },
  { id: "mirror", rx: /(дзеркал|зеркал|відображ|отраж|mirror)/i, name: "Дзеркало", element: "Місяць", operation: "рефлексія", symbol: "відбиток" },
  { id: "road", rx: /(дорог|шлях|путь|машин|авто|поїзд|поезд|літак|самолет|road|car|train|plane)/i, name: "Шлях", element: "Повітря / Земля", operation: "циркуляція", symbol: "дорога" },
  { id: "falling", rx: /(падал|падав|падіння|падение|falling|летів вниз|летел вниз)/i, name: "Падіння", element: "Повітря", operation: "коагуляція тіла", symbol: "спуск" },
  { id: "flying", rx: /(летів|летал|літав|політ|полет|flying|fly)/i, name: "Політ", element: "Повітря", operation: "сублімація", symbol: "висота" },
  { id: "house", rx: /(дім|дом|квартира|кімнат|комнат|будинок|house|room)/i, name: "Дім", element: "Земля", operation: "вмістилище", symbol: "внутрішній храм" },
  { id: "money", rx: /(грош|деньг|монет|золото|гаманець|кошелек|money|coin|gold)/i, name: "Монети", element: "Земля / Сонце", operation: "коагуляція цінності", symbol: "золото" },
  { id: "blood", rx: /(кров|кровь|blood)/i, name: "Кров", element: "Марс / Вода", operation: "рубедо", symbol: "життєва сила" },
];

function dreamMotif(text) {
  const query = String(text || "");
  return DREAM_MOTIFS.find(motif => motif.rx.test(query)) || { id: "veil", name: "Завіса сну", element: "Місяць / Ефір", operation: "дистиляція образу", symbol: "нічний знак" };
}

function shouldBuildDreamVision(active, text) {
  const q = visionNormalize(text);
  if (/(сонце|сонячн|сонячный|солнечн|соняшник)/i.test(q)) return false;
  return /(сонник|тлумаченн(?:я)?\s+сн|толковани[ея]\s+сн|сновид|приснив|наснив|снило|снився|снилась|уві\s+сні|во\s+сне|кошмар|нічн(?:ий|ого)\s+жах|dream|nightmare|oneiromancy|oneirocritica)/i.test(q);
}

function extractDreamTitle(text, motif) {
  const raw = String(text || "").replace(/\s+/g, " ").trim();
  const m = raw.match(/(?:приснив(?:ся|ся)?|наснив(?:ся)?|снило(?:ся)?|уві сні|во сне|сон:|сновидіння:)\s*([^.!?]{4,80})/i);
  const fragment = (m && m[1] ? m[1] : raw).replace(/^[:\-–—\s]+/, "").slice(0, 72).trim();
  return fragment || motif.name;
}

function buildDreamVision(text) {
  const motif = dreamMotif(text);
  const hash = visionHash(`${motif.id}|${text}`);
  return {
    type: "dream_vision",
    version: 1,
    title: extractDreamTitle(text, motif),
    motif: motif.name,
    motifId: motif.id,
    element: motif.element,
    operation: motif.operation,
    symbol: motif.symbol,
    seed: hash.toString(16).toUpperCase(),
    palette: ["#0b0908", "#c9a227", "#e8c75a", "#6e5a24"],
    query: String(text || "").slice(0, 260),
  };
}

function buildDreamVisionContext(vision) {
  if (!vision) return "";
  return [
    "",
    `[Візуальна картка сновидіння вже показана в інтерфейсі: мотив "${vision.motif}", стихія ${vision.element}, алхімічна операція ${vision.operation}, символ ${vision.symbol}. Тлумач сон через цей образ і не пиши, що зображення відсутнє.]`,
  ].join("\n");
}

function DreamSymbol({ vision }) {
  const id = vision.motifId;
  const seed = vision.seed;
  const drawWater = id === "water" || id === "veil";
  return (
    <svg className="ovision__svg" viewBox="0 0 500 500" role="img" aria-label={`Образ сну: ${vision.motif}`}>
      <defs>
        <radialGradient id={`drMoon-${seed}`} cx="54%" cy="24%" r="58%">
          <stop offset="0%" stopColor="#fff1bf" stopOpacity="0.86" />
          <stop offset="38%" stopColor="#c9a227" stopOpacity="0.22" />
          <stop offset="100%" stopColor="#0b0908" stopOpacity="0" />
        </radialGradient>
        <filter id={`drGlow-${seed}`} x="-45%" y="-45%" width="190%" height="190%">
          <feGaussianBlur stdDeviation="4" result="b" />
          <feMerge><feMergeNode in="b" /><feMergeNode in="SourceGraphic" /></feMerge>
        </filter>
      </defs>
      <rect width="500" height="500" rx="18" fill="#0b0908" />
      <circle cx="286" cy="126" r="184" fill={`url(#drMoon-${seed})`} />
      <circle cx="354" cy="92" r="44" fill="#e8d6a2" opacity="0.9" />
      <circle cx="371" cy="82" r="44" fill="#0b0908" opacity="0.92" />
      <g stroke="#c9a227" strokeWidth="1.15" fill="none" opacity="0.35">
        <circle cx="250" cy="250" r="188" />
        <path d="M72 334 C134 302, 190 306, 250 332 C316 362, 386 344, 432 320" />
        <path d="M96 372 C164 342, 242 360, 310 380 C356 394, 406 378, 448 356" opacity="0.55" />
      </g>
      {drawWater && (
        <g stroke="#d6b85b" fill="none" opacity="0.72">
          <path d="M88 332 C136 306, 178 306, 226 332 C280 362, 346 354, 420 316" strokeWidth="2.4" />
          <path d="M114 374 C174 350, 238 360, 300 382 C354 402, 398 382, 444 358" strokeWidth="1.4" />
        </g>
      )}
      {id === "fire" && (
        <g filter={`url(#drGlow-${seed})`}>
          <path d="M250 352 C216 306, 238 270, 260 242 C258 286, 298 294, 298 342 C298 386, 220 392, 250 352 Z" fill="#c7772d" opacity="0.52" />
          <path d="M250 376 C198 318, 220 268, 280 216 C266 280, 334 300, 318 366 C304 416, 216 420, 250 376 Z" fill="none" stroke="#e8c75a" strokeWidth="3" />
        </g>
      )}
      {id === "snake" && <path d="M122 336 C190 250, 278 384, 370 276 C404 238, 436 272, 404 304 C376 332, 330 330, 292 306" fill="none" stroke="#d7b95b" strokeWidth="8" strokeLinecap="round" opacity="0.84" />}
      {id === "bird" && <path d="M142 238 C204 164, 224 242, 250 248 C276 242, 296 164, 358 238 C304 218, 282 258, 250 266 C218 258, 196 218, 142 238 Z" fill="none" stroke="#e0c46d" strokeWidth="3" opacity="0.88" />}
      {id === "door" && <g stroke="#d7b95b" fill="rgba(201,162,39,0.08)"><path d="M190 382 L190 176 L316 176 L316 382" strokeWidth="3" /><path d="M220 382 L220 214 C250 188, 288 202, 288 234 L288 382" strokeWidth="2" /><circle cx="272" cy="294" r="4" fill="#e8c75a" /></g>}
      {id === "mirror" && <g stroke="#d7b95b" fill="rgba(232,199,90,0.08)"><ellipse cx="250" cy="254" rx="76" ry="116" strokeWidth="3" /><path d="M218 374 L202 420 M282 374 L298 420 M212 420 L304 420" /></g>}
      {id === "road" && <path d="M210 430 C222 356, 234 300, 246 240 C258 300, 286 356, 324 430 M246 240 L246 178" fill="none" stroke="#d7b95b" strokeWidth="3" opacity="0.78" />}
      {id === "house" && <g stroke="#d7b95b" fill="rgba(201,162,39,0.09)"><path d="M148 300 L250 198 L352 300" strokeWidth="3" /><path d="M178 288 L178 392 L322 392 L322 288" strokeWidth="2" /><path d="M232 392 L232 326 L272 326 L272 392" /></g>}
      {id === "teeth" && <g fill="#efe3bf" stroke="#c9a227" opacity="0.9">{[0,1,2,3].map(i => <path key={i} d={`M${188 + i * 38} 238 C${176 + i * 38} 282, ${184 + i * 38} 334, ${204 + i * 38} 352 C${224 + i * 38} 334, ${232 + i * 38} 282, ${220 + i * 38} 238 Z`} />)}</g>}
      {id === "dead" && <g stroke="#d7b95b" fill="none" opacity="0.78"><path d="M250 190 C220 222, 212 266, 224 306 C238 350, 294 350, 308 306 C320 266, 310 222, 250 190 Z" /><path d="M218 376 C244 342, 280 342, 306 376" /><path d="M206 392 L318 392" /></g>}
      {id === "money" && <g>{[0,1,2,3,4].map(i => <circle key={i} cx={178 + i * 34} cy={315 - (i % 2) * 20} r="22" fill="rgba(201,162,39,0.22)" stroke="#e8c75a" />)}<text x="246" y="324" textAnchor="middle" className="ovision__coin">AZ</text></g>}
      {id === "blood" && <path d="M250 192 C220 250, 190 296, 196 338 C202 390, 298 390, 304 338 C310 296, 280 250, 250 192 Z" fill="rgba(148,38,28,0.42)" stroke="#d7b95b" strokeWidth="2.5" />}
      {id === "falling" && <g stroke="#d7b95b" fill="none"><path d="M250 146 L250 340" strokeDasharray="8 10" /><path d="M218 306 L250 344 L282 306" strokeWidth="3" /></g>}
      {id === "flying" && <g stroke="#d7b95b" fill="none"><path d="M150 302 C196 214, 228 258, 250 274 C272 258, 304 214, 350 302" strokeWidth="3" /><path d="M184 196 C222 166, 278 166, 316 196" /></g>}
      <g fill="#e8c75a" opacity="0.9">
        {[0,1,2,3,4,5,6].map(i => {
          const h = parseInt(seed.slice(i, i + 2), 16) || i * 17;
          return <circle key={i} cx={86 + (h * 37) % 336} cy={78 + (h * 19) % 316} r={1.8 + (i % 3)} opacity={0.35 + (i % 4) * 0.12} />;
        })}
      </g>
      <text x="250" y="444" textAnchor="middle" className="ovision__svg-title">{vision.motif}</text>
    </svg>
  );
}

function DreamVisionView({ vision }) {
  if (!vision) return null;
  return (
    <section className="ovision ovision--dream">
      <div className="ovision__top">
        <div>
          <div className="ovision__eyebrow">Somnium · Imago</div>
          <div className="ovision__title">{vision.title}</div>
        </div>
        <div className="ovision__meta">
          {vision.motif}
          <small>{vision.operation}</small>
        </div>
      </div>
      <div className="ovision__grid">
        <DreamSymbol vision={vision} />
        <div className="ovision__side">
          <div className="ovision__statement">Образ сну для алхімічного сонника: не пророцтво, а карта символів консультації.</div>
          <div className="ovision__formula">
            <div><span>Мотив</span><b>{vision.motif}</b></div>
            <div><span>Стихія</span><b>{vision.element}</b></div>
            <div><span>Операція</span><b>{vision.operation}</b></div>
            <div><span>Ключ</span><b>{vision.symbol}</b></div>
            <div><span>Нічний seed</span><b>{vision.seed}</b></div>
          </div>
          <div className="ovision__chips">
            <span>маніфестний образ</span>
            <span>контекст клієнта</span>
            <span>алхімічна стадія</span>
          </div>
        </div>
      </div>
    </section>
  );
}

Object.assign(window, {
  shouldBuildDemonVision,
  buildDemonVision,
  buildDemonVisionContext: demonVisionContext,
  DemonVisionView,
  shouldBuildDreamVision,
  buildDreamVision,
  buildDreamVisionContext,
  DreamVisionView,
});
