/**
 * Gemeinsame Sprach-Regeln: Auto-Korrekturen und Smileys per Sprachbefehl.
 */
(function initChillicollectivoVoiceTextRules(global) {
  const root = global || (typeof globalThis !== 'undefined' ? globalThis : self);

  function parseCorrectionRules(raw) {
    if (!raw) return [];
    const rules = [];
    for (const line of String(raw).split(/\r?\n/)) {
      const trimmed = line.trim();
      if (!trimmed || trimmed.startsWith('#')) continue;
      const parts = trimmed.split('=>');
      if (parts.length < 2) continue;
      const wrong = parts[0].trim();
      const correct = parts.slice(1).join('=>').trim();
      if (wrong && correct) rules.push({ wrong, correct });
    }
    return rules;
  }

  function splitSmileyTriggers(raw) {
    return String(raw || '')
      .split(/[,;|/]+/)
      .map((part) => part.trim())
      .filter(Boolean);
  }

  function parseSmileyRules(raw) {
    if (!raw) return [];
    const rules = [];
    for (const line of String(raw).split(/\r?\n/)) {
      const trimmed = line.trim();
      if (!trimmed || trimmed.startsWith('#')) continue;
      const match = trimmed.match(/^(.+?)\s*(?:=>|=)\s*(.+)$/);
      if (!match) continue;
      const triggers = splitSmileyTriggers(match[1]);
      const emoji = match[2].trim();
      if (!triggers.length || !emoji) continue;
      rules.push({ triggers, emoji });
    }
    return rules;
  }

  function collapseDuplicatePunctuation(text) {
    let t = String(text || '');
    if (!t) return '';
    t = t.replace(/!\s+!+/g, '!');
    t = t.replace(/\?\s+\?+/g, '?');
    t = t.replace(/([!?.…])\s+([!?.…])/g, '$1');
    t = t.replace(/!{2,}/g, '!');
    t = t.replace(/\?{2,}/g, '?');
    return t;
  }

  function isPunktNounContextBefore(before) {
    return /\b(?:an|am|vom|zum|zu|beim|in)\s+(?:dem\s+|einem\s+)?$/i.test(before)
      || /\b(?:diesem|jenem|einem|keinem|dem)\s+$/i.test(before);
  }

  function applySpokenPunctuation(text) {
    let t = String(text || '');
    if (!t) return '';

    t = t.replace(/\b(auslassungspunkte|ellipsis|drei\s*(?:mal\s*)?punkte|punkt\s+punkt\s+punkt)\b/gi, '...');
    t = t.replace(/\b(?:punkt\s+){2,}punkt\b/gi, (match) => '.'.repeat((match.match(/punkt/gi) || []).length));

    t = t.replace(/\b(?:frage\s*-?\s*zeichen|fragezeichen|question\s*mark)(?:\s*[!?.])*/gi, '?');
    t = t.replace(/\b(?:ausruf\s*-?\s*zeichen|ausrufezeichen|exclamation\s*(?:mark|point))(?:\s*[!?.])*/gi, '!');
    t = t.replace(/\b(anführungs\s*-?\s*zeichen\s+auf|gänsefüsschen\s+auf|gänsefüßchen\s+auf|zitatzeichen\s+auf|quote\s+open)\b/gi, '"');
    t = t.replace(/\b(anführungs\s*-?\s*zeichen\s+zu|gänsefüsschen\s+zu|gänsefüßchen\s+zu|zitatzeichen\s+zu|quote\s+close)\b/gi, '"');
    t = t.replace(/\b(anführungs\s*-?\s*zeichen|gänsefüsschen|gänsefüßchen|zitatzeichen|quotes?)\b/gi, '"');

    // «von dir Punkt wäre …» – gesprochenes Satzende, danach weiterdiktieren
    t = t.replace(/\bpunkt\b(?=\s+\S)/gi, (match, offset) => {
      const before = t.slice(Math.max(0, offset - 48), offset);
      if (isPunktNounContextBefore(before)) return match;
      return '.';
    });

    t = t.replace(/\bpunkt\b(?=\s*(?:$|[,;.!?…]|\s+(?:komma|koma|comma|frage|ausruf|auslassung|drei|punkt)\b))/gi, '.');
    t = t.replace(/\bperiod\b(?=\s*(?:$|[,;.!?…]|\s+(?:komma|comma|frage|question|ausruf|exclamation)\b))/gi, '.');

    t = t.replace(/\b(komma|koma|comma)\b/gi, ',');
    t = t.replace(/\b(doppelpunkt|colon)\b/gi, ':');
    t = t.replace(/\b(strichpunkt|semikolon|semicolon)\b/gi, ';');
    t = t.replace(/\b(bindestrich|gedankenstrich|minus|hyphen|dash)\b/gi, '-');
    t = t.replace(/\b(apostroph|apostrophe)\b/gi, "'");

    t = t.replace(/\.(?:\s*\.){1,}/g, (match) => '.'.repeat((match.match(/\./g) || []).length));
    t = t.replace(/[ \t]+([,.!?…:;"'\-])/g, '$1');
    t = collapseDuplicatePunctuation(t);
    t = t.replace(/[ \t]{2,}/g, ' ').trim();
    return t;
  }

  function applyMicMikePhoneticFixes(text) {
    let t = String(text || '');
    if (!t) return '';

    // Kurz ausgesprochenes «mic» (STT: mik/mick) → mic
    t = t.replace(/\b(mik|mick)\b/gi, (match) => (match[0] === match[0].toUpperCase() ? 'Mic' : 'mic'));

    // Mikrofon-Kontext: STT schreibt oft «mike» statt «mic»
    t = t.replace(/\b(in\s+das|ins|im|vom|am|das|dem|in\s+dem)\s+mike\b/gi, (full, prep) => `${prep} mic`);
    t = t.replace(/\bmike\s+(an|aus|ein|rein|zu|ein-?\s*und)\b/gi, 'mic $1');
    t = t.replace(/\b(red(?:e|en)?|sprich(?:e|st)?|sprech(?:e|t)?)\s+(?:in\s+)?(?:das\s+)?mike\b/gi, (full) => full.replace(/\bmike\b/gi, 'mic'));
    t = t.replace(/\b(mikro(?:fon)?|micro(?:phone)?)\s*[-]?\s*mike\b/gi, (full) => full.replace(/\bmike\b/gi, 'mic'));

    // Name / Anrede: «mike» → Mike
    t = t.replace(/\b(hey|hallo|hi|servus|grüezi|gruezi|lieber|liebe|ok|okay)\s+mike\b/gi, (_, greet) => `${greet} Mike`);
    t = t.replace(/(^|[.!?…]\s+)mike\b(?=[,!.?…\s]|$)/g, (_, prefix) => `${prefix}Mike`);

    return t;
  }

  function applyBuiltinSpeechFixes(text) {
    let t = String(text || '');
    t = applyMicMikePhoneticFixes(t);
    // Chrome-STT: «Hat dir …» nur am Satzanfang – nicht «ich hatte dir …» zerstören.
    t = t.replace(/(^|[.!?…]\s+)(Hatte dir das jetzt geholfen)\s*\??/g, '$1Hat dir das jetzt geholfen');
    t = t.replace(/(^|[.!?…]\s+)(hatte dir das jetzt geholfen)\s*\??/g, '$1hat dir das jetzt geholfen');
    t = t.replace(/(^|[.!?…]\s+)(Hatte dir)\b/g, '$1Hat dir');
    t = t.replace(/(^|[.!?…]\s+)(hatte dir)\b/g, '$1hat dir');
    // Häufige STT-Verhörer (de-CH)
    t = t.replace(/\birgendwie\s+Packer\b/gi, 'im Gepäck');
    t = t.replace(/\bzeit\s+für\s+mich\s+irgendwie\b/gi, 'Zeit für mich im Gepäck');
    t = t.replace(/\bfür\s+michim\b/gi, 'für mich im');
    t = t.replace(/\bmichim\s+Gepäck\b/gi, 'mich im Gepäck');
    t = t.replace(/\bweist\s+du\b/gi, 'weisst du');
    t = t.replace(/\bWeist\s+du\b/g, 'Weisst du');
    // Chrome-STT verwechselt «Schwanzbild» oft mit «Schwarzbild»
    t = t.replace(/\bSchwarzbild\b/g, 'Schwanzbild');
    t = t.replace(/\bschwarzbild\b/g, 'schwanzbild');
    return t;
  }

  const CHILLICOLLECTIVO_BUILTIN_SPOKEN_NAME_HINTS = [
    {
      name: 'Fötzchen',
      hints: ['fotzchen', 'fötzli', 'fetzchen', 'fötzchen', 'foetzchen'],
    },
  ];

  function parseSpokenNameHints(raw, extras = []) {
    const hints = [];
    for (const entry of [...CHILLICOLLECTIVO_BUILTIN_SPOKEN_NAME_HINTS, ...(extras || [])]) {
      if (!entry?.name) continue;
      const list = Array.isArray(entry.hints) ? entry.hints : [];
      hints.push({ name: String(entry.name).trim(), hints: list.map((h) => String(h || '').trim()).filter(Boolean) });
    }
    for (const line of String(raw || '').split(/\r?\n/)) {
      const trimmed = line.trim();
      if (!trimmed || trimmed.startsWith('#')) continue;
      const match = trimmed.match(/^([^:|=]+)\s*[:|=]\s*(.+)$/);
      if (!match) continue;
      const name = match[1].trim();
      const variants = match[2].split(/[,;|/]+/).map((part) => part.trim()).filter(Boolean);
      if (name && variants.length) hints.push({ name, hints: variants });
    }
    return hints;
  }

  function isNumericSpeechToken(token) {
    const norm = String(token || '').trim().toLowerCase();
    if (!norm) return false;
    if (/^\d+$/.test(norm)) return true;
    return new Set([
      'null', 'eins', 'zwei', 'drei', 'vier', 'fuenf', 'fünf', 'sechs', 'sieben', 'acht', 'neun', 'zehn',
      'elf', 'zwoelf', 'zwölf', 'dreizehn', 'vierzehn', 'vierzen', 'fuenfzehn', 'fünfzehn', 'sechzehn',
      'siebzehn', 'achtzehn', 'neunzehn', 'zwanzig', 'dreissig', 'dreißig', 'hundert', 'tausend',
    ]).has(norm);
  }

  function applySpokenNameHints(text, hints) {
    let t = String(text || '');
    if (!t || !Array.isArray(hints) || !hints.length) return t;
    for (const entry of hints) {
      const name = String(entry?.name || '').trim();
      if (!name) continue;
      for (const hint of entry.hints || []) {
        if (!hint || isNumericSpeechToken(hint)) continue;
        const escaped = hint.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
        const pattern = new RegExp('(^|[\\s,.;!?])(' + escaped + ')(?=$|[\\s,.;!?])', 'gi');
        t = t.replace(pattern, (match, prefix, word) => {
          if (word[0] === word[0].toUpperCase()) {
            return prefix + (name[0].toUpperCase() + name.slice(1));
          }
          return prefix + name;
        });
      }
    }
    return t;
  }

  function applyCorrectionRules(text, rules) {
    let t = String(text || '');
    const sorted = [...(rules || [])].sort((a, b) => String(b.wrong || '').length - String(a.wrong || '').length);
    for (const rule of sorted) {
      if (!rule.wrong || rule.wrong === 'ß') continue;
      const wrongIsNumeric = isNumericSpeechToken(rule.wrong);
      const correctIsNumeric = isNumericSpeechToken(rule.correct);
      // Keine pauschale Zahl→Name-Regel (z. B. «vierzehn => Fötzchen») – «14» soll möglich bleiben.
      if (wrongIsNumeric && !correctIsNumeric) continue;
      const escapedWrong = rule.wrong.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
      const pattern = new RegExp('(^|[\\s,.;!?])(' + escapedWrong + ')(?=$|[\\s,.;!?])', 'gi');
      t = t.replace(pattern, (match, prefix) => prefix + rule.correct);
    }
    return t;
  }

  function normalizeSmileyTriggerWord(word) {
    return String(word || '')
      .trim()
      .toLowerCase()
      .replace(/[.,!?;:]+$/g, '')
      .trim();
  }

  function collectSmileyTriggerWords(rules) {
    const out = new Set();
    for (const rule of rules || []) {
      for (const trigger of rule.triggers || []) {
        for (const part of splitSmileyTriggers(trigger)) {
          const norm = normalizeSmileyTriggerWord(part);
          if (norm) out.add(norm);
        }
      }
    }
    return out;
  }

  function isSmileyTriggerWord(word, rules) {
    const norm = normalizeSmileyTriggerWord(word);
    if (!norm) return false;
    const triggers = collectSmileyTriggerWords(rules || activeRules.smileyRules);
    if (triggers.has(norm)) return true;
    for (const trigger of triggers) {
      if (trigger.startsWith(norm) || norm.startsWith(trigger)) return true;
    }
    return false;
  }

  function applySmileyRules(text, rules) {
    let t = String(text || '');
    for (const rule of rules || []) {
      for (const trigger of rule.triggers || []) {
        const escaped = trigger.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
        const hasSpace = /\s/.test(trigger);
        const pattern = hasSpace
          ? new RegExp(escaped, 'gi')
          : new RegExp('(^|[\\s,.;!?])(' + escaped + ')(?=$|[\\s,.;!?])', 'gi');
        t = t.replace(pattern, (match, prefix) => (prefix || '') + rule.emoji);
      }
    }
    return t;
  }

  function applySmileyRulesPartialTrailing(text, rules) {
    let t = String(text || '');
    const match = t.match(/(^|[\s,.;!?])(\S+)\s*$/);
    if (!match) return t;
    const trailing = match[2];
    const norm = normalizeSmileyTriggerWord(trailing);
    if (!norm || norm.length < 2) return t;

    for (const rule of rules || []) {
      for (const trigger of rule.triggers || []) {
        for (const part of splitSmileyTriggers(trigger)) {
          const triggerNorm = normalizeSmileyTriggerWord(part);
          if (!triggerNorm || !triggerNorm.startsWith(norm)) continue;
          const minPartial = Math.max(2, triggerNorm.length - 1);
          if (norm.length >= minPartial) {
            return t.slice(0, t.length - trailing.length) + rule.emoji;
          }
        }
      }
    }
    return t;
  }

  function applyVoiceTextRules(text, options) {
    let t = String(text || '');
    if (!t) return '';
    t = t.replace(/ß/g, 'ss');
    t = applyBuiltinSpeechFixes(t);
    t = applySpokenNameHints(t, parseSpokenNameHints(options?.spokenNamesRaw || ''));
    const builtin = [
      { wrong: 'irgendwie Packer', correct: 'im Gepäck' },
      { wrong: 'Irgendwie Packer', correct: 'im Gepäck' },
      { wrong: 'weist du', correct: 'weisst du' },
      { wrong: 'Weist du', correct: 'Weisst du' },
      { wrong: 'für michim', correct: 'für mich im' },
      { wrong: 'michim Gepäck', correct: 'mich im Gepäck' },
      { wrong: 'Kegelfick', correct: 'Kehlenfick' },
      { wrong: 'kegelfick', correct: 'kehlenfick' },
    ];
    t = applyCorrectionRules(t, builtin);
    t = applyCorrectionRules(t, options?.correctionRules || []);
    const smileyRules = options?.smileyRules || [];
    t = applySmileyRules(t, smileyRules);
    t = applySmileyRulesPartialTrailing(t, smileyRules);
    return t;
  }

  let activeRules = { correctionRules: [], smileyRules: [], spokenNamesRaw: '' };

  root.chillicollectivoApplySpokenPunctuation = applySpokenPunctuation;
  root.chillicollectivoCollapseDuplicatePunctuation = collapseDuplicatePunctuation;
  root.chillicollectivoParseCorrectionRules = parseCorrectionRules;
  root.chillicollectivoParseSmileyRules = parseSmileyRules;
  root.chillicollectivoApplyCorrectionRules = applyCorrectionRules;
  root.chillicollectivoApplySmileyRules = applySmileyRules;
  root.chillicollectivoApplySmileyRulesPartialTrailing = applySmileyRulesPartialTrailing;
  root.chillicollectivoCollectSmileyTriggerWords = function chillicollectivoCollectSmileyTriggerWords(rules) {
    return Array.from(collectSmileyTriggerWords(rules || activeRules.smileyRules));
  };
  root.chillicollectivoIsVoiceSmileyTrigger = function chillicollectivoIsVoiceSmileyTrigger(word) {
    return isSmileyTriggerWord(word);
  };
  root.chillicollectivoApplyVoiceTextRules = applyVoiceTextRules;
  root.chillicollectivoSetVoiceTextRules = function chillicollectivoSetVoiceTextRules(rules) {
    activeRules = {
      correctionRules: rules?.correctionRules || [],
      smileyRules: rules?.smileyRules || [],
      spokenNamesRaw: rules?.spokenNamesRaw || '',
    };
  };
  root.chillicollectivoGetVoiceTextRules = function chillicollectivoGetVoiceTextRules() {
    return activeRules;
  };
  root.chillicollectivoRefreshVoiceTextRulesFromSettings = function chillicollectivoRefreshVoiceTextRulesFromSettings(settings) {
    const s = settings || {};
    root.chillicollectivoSetVoiceTextRules({
      correctionRules: parseCorrectionRules(s.autoCorrectionsRaw || ''),
      smileyRules: parseSmileyRules(s.voiceSmileysRaw || ''),
      spokenNamesRaw: s.spokenNamesRaw || '',
    });
  };

  root.CHILLICOLLECTIVO_LOVE_DIRTY_EMOJIS = [
    '❤️', '💋', '😘', '🥵', '🔥', '😈', '💦', '😍', '🤤', '😏',
    '🥰', '💕', '💗', '💖', '💘', '💝', '😻', '🫦', '👄', '🌹',
    '✨', '❤️‍🔥', '👅', '🍑', '🫶', '😽', '💌', '🥀', '🌶️', '💞',
  ];

  root.CHILLICOLLECTIVO_EMOTION_EMOJIS = [
    '😢', '😭', '😞', '😔', '🥺', '☹️', '😿',
    '😊', '☺️', '😳', '🥹', '😌',
    '😄', '😁', '😃', '🤗', '😆', '🥳',
    '🤔', '💭', '🧐', '😶',
    '🤫', '🤐', '🫢', '🤭',
    '🙈', '🙉', '🙊', '🐵',
  ];

  root.chillicollectivoSmileyRulesToRows = function chillicollectivoSmileyRulesToRows(raw) {
    return parseSmileyRules(raw).map((rule) => ({
      trigger: rule.triggers.join(', '),
      emoji: rule.emoji,
    }));
  };

  root.chillicollectivoSerializeSmileyRows = function chillicollectivoSerializeSmileyRows(rows) {
    const lines = [];
    for (const row of rows || []) {
      const trigger = String(row?.trigger || '').trim();
      const emoji = String(row?.emoji || '').trim();
      if (!trigger || !emoji) continue;
      lines.push(`${trigger} => ${emoji}`);
    }
    return lines.join('\n');
  };

  root.chillicollectivoCorrectionRulesToRows = function chillicollectivoCorrectionRulesToRows(raw) {
    return parseCorrectionRules(raw).map((rule) => ({
      wrong: rule.wrong,
      correct: rule.correct,
    }));
  };

  root.chillicollectivoSerializeCorrectionRows = function chillicollectivoSerializeCorrectionRows(rows) {
    const lines = [];
    for (const row of rows || []) {
      const wrong = String(row?.wrong || '').trim();
      const correct = String(row?.correct || '').trim();
      if (!wrong || !correct) continue;
      lines.push(`${wrong} => ${correct}`);
    }
    return lines.join('\n');
  };
})(typeof globalThis !== 'undefined' ? globalThis : self);
