self.__CHILLICOLLECTIVO_AGENTEN_MIC = true;
importScripts('diagnostic-log.js', 'voice-text-rules.js', 'license.js', 'mic-web-sync.js');

const CHILLICOLLECTIVO_DEFAULT_VOICE_HOTKEY = {
  key: 'J',
  altKey: true,
  ctrlKey: false,
  shiftKey: false,
  metaKey: false,
};

const CHILLICOLLECTIVO_ACTION_ICON_PATHS = {
  on: {
    16: 'icons/action-mic-on-16.png',
    32: 'icons/action-mic-on-32.png',
    48: 'icons/action-mic-on-48.png',
    128: 'icons/action-mic-on-128.png',
  },
  off: {
    16: 'icons/action-mic-off-16.png',
    32: 'icons/action-mic-off-32.png',
    48: 'icons/action-mic-off-48.png',
    128: 'icons/action-mic-off-128.png',
  },
};

function chillicollectivoVoiceActionIconIsActive(state) {
  return state === 'recording' || state === 'loading';
}

function chillicollectivoIsTorchwoodUrl(url) {
  if (!url) return false;
  try {
    return /(^|\.)torchwood\.app$/i.test(new URL(url).hostname);
  } catch (_) {
    return false;
  }
}

function syncToolbarActionIcon(state) {
  if (!chrome.action?.setIcon) return;
  const active = chillicollectivoVoiceActionIconIsActive(state);
  const path = active ? CHILLICOLLECTIVO_ACTION_ICON_PATHS.on : CHILLICOLLECTIVO_ACTION_ICON_PATHS.off;
  chrome.action.setIcon({ path });
  if (chrome.action.setTitle) {
    chrome.action.setTitle({
      title: active ? 'ChiliColectivo: AN (Aufnahme)' : 'ChiliColectivo: AUS',
    });
  }
}

function syncToolbarVisibilityForTab(tabId, url) {
  if (!chrome.action?.disable || !chrome.action?.enable || tabId == null) return;
  const onTorchwood = chillicollectivoIsTorchwoodUrl(url);
  const action = onTorchwood ? chrome.action.enable : chrome.action.disable;
  action.call(chrome.action, tabId, () => chrome.runtime.lastError);
}

function setupTorchwoodOnlyToolbar() {
  const refreshTab = (tab) => {
    if (tab?.id != null) syncToolbarVisibilityForTab(tab.id, tab.url);
  };

  chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
    if (changeInfo.url || changeInfo.status === 'complete') {
      refreshTab(tab || { id: tabId, url: changeInfo.url });
    }
  });

  chrome.tabs.onActivated.addListener(({ tabId }) => {
    chrome.tabs.get(tabId, (tab) => {
      if (chrome.runtime.lastError) return;
      refreshTab(tab);
    });
  });

  chrome.tabs.query({}, (tabs) => {
    (tabs || []).forEach(refreshTab);
  });
}

function hotkeyToChromeShortcut(hotkey) {
  const hk = (hotkey?.key || hotkey?.code) ? hotkey : CHILLICOLLECTIVO_DEFAULT_VOICE_HOTKEY;
  const parts = [];
  if (hk.ctrlKey) parts.push('Ctrl');
  if (hk.altKey) parts.push('Alt');
  if (hk.shiftKey) parts.push('Shift');
  if (hk.metaKey) parts.push('Command');
  parts.push(hk.key.length === 1 ? hk.key.toUpperCase() : hk.key);
  return parts.join('+');
}

async function syncChromeVoiceCommandHotkey(hotkey) {
  if (!chrome.commands?.update) return;
  const hk = (hotkey?.key || hotkey?.code) ? hotkey : null;
  if (hk && !hk.ctrlKey && !hk.altKey && !hk.shiftKey && !hk.metaKey) {
    try {
      await new Promise((resolve, reject) => {
        chrome.commands.update({ name: 'toggle-voice', shortcut: '' }, () => {
          if (chrome.runtime.lastError) reject(new Error(chrome.runtime.lastError.message));
          else resolve();
        });
      });
    } catch (err) {
      console.warn('[CHILLICOLLECTIVO] Einzeltaste – globales Chrome-Kürzel deaktiviert:', err?.message || err);
    }
    return;
  }
  const shortcut = hk ? hotkeyToChromeShortcut(hk) : hotkeyToChromeShortcut(null);
  try {
    await new Promise((resolve, reject) => {
      chrome.commands.update({ name: 'toggle-voice', shortcut }, () => {
        if (chrome.runtime.lastError) reject(new Error(chrome.runtime.lastError.message));
        else resolve();
      });
    });
  } catch (err) {
    console.warn('[CHILLICOLLECTIVO] Tastenkürzel-Sync:', err?.message || err);
  }
}

const CHILLICOLLECTIVO_DEFAULT_ACCENT_COLOR = '#D46E6E';

const CHILLICOLLECTIVO_DEFAULT_SETTINGS = {
  speechLang: 'de-CH',
  sharpSAsDoubleS: true,
  previewEnabled: true,
  previewMovable: false,
  previewPinned: false,
  previewOpacity: 100,
  previewColor: CHILLICOLLECTIVO_DEFAULT_ACCENT_COLOR,
  previewFontWeight: 'light',
  previewTextCase: 'sentence',
  previewFontSize: 22,
  toolColor: CHILLICOLLECTIVO_DEFAULT_ACCENT_COLOR,
  toolOpacity: 100,
  fabGearIcon: 'leaf',
  previewEnterToInsert: false,
  previewClickToInsert: false,
  previewClickFlushToInsert: false,
  ppAutoInsert: false,
  previewInsertHotkey: null,
  tabEnterHotkey: null,
  micTipsEnabled: false,
  replyToLastCustomer: true,
  sendCrumbleIntensity: 100,
  hotkey: null,
  voiceSmileysRaw: '',
  autoCorrectionsRaw: '',
};

function applyAgentenMicSettingsOverrides(settings) {
  settings.previewEnterToInsert = false;
  settings.ppAutoInsert = false;
  settings.tabEnterHotkey = null;
  settings.previewPinned = false;
  settings.previewTextCase = 'spoken';
  settings.speechLang = 'de-CH';
  settings.sharpSAsDoubleS = true;
  if (settings.previewClickToInsert === undefined) {
    settings.previewClickToInsert = false;
  }
  if (settings.previewClickFlushToInsert === undefined) {
    settings.previewClickFlushToInsert = false;
  }
  return settings;
}

function resolveEffectIntensity(storedIntensity, storedEnabled, defaultValue = 100, min = 0) {
  if (storedIntensity != null && Number.isFinite(Number(storedIntensity))) {
    return Math.min(100, Math.max(min, Math.round(Number(storedIntensity) / 5) * 5));
  }
  if (storedEnabled === false) return 0;
  return defaultValue;
}

function normalizePreviewColorValue(value) {
  const raw = String(value || '').trim();
  if (/^#[0-9a-f]{6}$/i.test(raw)) return raw.toUpperCase();
  if (/^#[0-9a-f]{3}$/i.test(raw)) {
    const r = raw[1];
    const g = raw[2];
    const b = raw[3];
    return `#${r}${r}${g}${g}${b}${b}`.toUpperCase();
  }
  return CHILLICOLLECTIVO_DEFAULT_ACCENT_COLOR;
}

function isLegacyDefaultGreenColor(value) {
  const c = String(value || '').trim().toUpperCase();
  return !c || c === '#4ADE80';
}

function isLegacyDefaultYellowColor(value) {
  const c = String(value || '').trim().toUpperCase();
  return c === '#FACC15';
}

function buildVoiceEffectsPayload(settings) {
  const sendCrumbleIntensity = resolveEffectIntensity(settings.sendCrumbleIntensity, settings.sendCrumbleEnabled);
  return {
    sendCrumbleIntensity,
    sendCrumbleEnabled: sendCrumbleIntensity > 0,
    previewEnabled: settings.previewEnabled !== false,
    previewOpacity: settings.previewOpacity,
    previewColor: settings.previewColor || CHILLICOLLECTIVO_DEFAULT_ACCENT_COLOR,
    previewFontWeight: settings.previewFontWeight || CHILLICOLLECTIVO_DEFAULT_SETTINGS.previewFontWeight,
    previewTextCase: settings.previewTextCase || CHILLICOLLECTIVO_DEFAULT_SETTINGS.previewTextCase,
    previewFontSize: settings.previewFontSize ?? CHILLICOLLECTIVO_DEFAULT_SETTINGS.previewFontSize,
    toolColor: settings.toolColor || settings.previewColor || CHILLICOLLECTIVO_DEFAULT_ACCENT_COLOR,
    toolOpacity: settings.toolOpacity,
    fabGearIcon: settings.fabGearIcon === 'mic' ? 'mic' : 'leaf',
  };
}

class CHILLICOLLECTIVOBackgroundService {
  constructor() {
    this.isRecording = false;
    this.currentTabId = null;
    this.localSettings = { ...CHILLICOLLECTIVO_DEFAULT_SETTINGS };
    this.voiceConfig = {
      speechLang: 'de-CH',
      sharpSAsDoubleS: true,
    };
    this.correctionRules = [];
    this.smileyRules = [];
    this._localBuffer = [];
    this._localConfidences = [];
    this._localDebounceTimer = null;
    this._localRecordingStartTime = null;
    this._lastVoiceToggleAt = 0;
    this._recordingStartId = 0;

    this.init();
  }

  async init() {
    console.log('CHILLICOLLECTIVO Voice Background Service initialized (lokal)');

    this.setupMessageHandlers();
    this.setupExtensionAction();
    this.setupCommandHandlers();
    setupTorchwoodOnlyToolbar();
    syncToolbarActionIcon('ready');

    await this.loadLocalSettings().catch(e => console.warn('Could not load local settings:', e));
    globalThis.ChillicollectivoDiagnosticLog?.initBackground?.();
    chillicollectivoLoadOfflineLicenses?.().catch(() => {});

    chillicollectivoSyncOutCounterTotal()
      .then((total) => chillicollectivoTryGrantOutMilestoneReward(total))
      .then((result) => {
        if (result?.granted) chillicollectivoBroadcastLicenseState();
      })
      .catch(() => {});

    chillicollectivoScheduleWebMicSync(this);
    chillicollectivoIsLicenseForceLocked?.()
      .then((locked) => {
        if (!locked) chillicollectivoSyncFromWebMicPortal(this).catch(() => {});
      })
      .catch(() => {});
    chillicollectivoRegisterTrialExpiredHandler(async (state) => {
      if (state?.access === 'granted') return;
      if (this.isRecording && this.currentTabId) {
        await this.stopRecording(this.currentTabId);
      }
      if (this.currentTabId) {
        this.updateState(this.currentTabId, 'locked', state?.message || 'Lizenz abgelaufen');
      }
    });

    chillicollectivoBootstrapLicense({ revalidate: true }).catch(() => {});
    chillicollectivoFetchMicUpdateFromServer?.().catch(() => {});

    chrome.storage.onChanged.addListener((changes, area) => {
      if (area !== 'local') return;
      if (changes.chillicollectivoVoiceSettings) {
        this.applyLocalSettings(changes.chillicollectivoVoiceSettings.newValue || { ...CHILLICOLLECTIVO_DEFAULT_SETTINGS });
      }
      if (changes.chillicollectivoTrialStartedAt || changes.chillicollectivoLicense) {
        chillicollectivoScheduleTrialExpiryWatch().catch(() => {});
      }
      if (changes.chillicollectivoLicense) {
        if (typeof chillicollectivoIsLicenseActivating === 'function') {
          chillicollectivoIsLicenseActivating()
            .then((activating) => {
              if (activating) return;
              return this.handleLicenseStorageChange();
            })
            .catch(() => {});
        } else {
          this.handleLicenseStorageChange().catch(() => {});
        }
      }
    });

    chrome.runtime.onStartup.addListener(() => {
      chillicollectivoBootstrapLicense({ revalidate: true }).catch(() => {});
    });

    chrome.runtime.onInstalled.addListener((details) => {
      chillicollectivoBootstrapLicense({ revalidate: true }).catch(() => {});
      chillicollectivoBroadcastLicenseState().catch(() => {});
      if (details.reason === 'install' || details.reason === 'update') {
        chrome.storage.local.remove('chillicollectivoDiagnosticLog').catch?.(() => {});
      }
    });
  }

  setupMessageHandlers() {
    chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
      if (sender.id !== chrome.runtime.id) return;

      switch (message.type) {
        case 'app_ping':
          sendResponse({ installed: true });
          break;

        case 'ICON_CLICKED':
          (async () => {
            const tabId = sender.tab?.id;
            let currentState = message.currentState;
            if (tabId) {
              try {
                const response = await chrome.tabs.sendMessage(tabId, { type: 'GET_VOICE_STATE' });
                const liveState = response?.state;
                const requestedState = message.currentState;
                if (liveState) {
                  const startIntent = requestedState === 'ready' || requestedState === 'error' || requestedState === 'locked';
                  // Content springt beim Einschalten sofort auf loading – das ist kein Stopp-Wunsch.
                  if (liveState === 'loading' && startIntent) {
                    currentState = requestedState;
                  } else {
                    currentState = liveState;
                  }
                }
              } catch (_) {}
            }
            await this.handleIconClick(tabId, currentState);
          })();
          sendResponse({ success: true });
          break;

        case 'CONTENT_VOICE_STATE': {
          const tabId = sender.tab?.id || message.tabId;
          const state = String(message.state || '').trim();
          if (tabId && state === 'recording') {
            this.isRecording = true;
            this.currentTabId = tabId;
          } else if (tabId && state && state !== 'recording') {
            const wasRecording = !!(this.isRecording && this.currentTabId === tabId);
            if (this.currentTabId === tabId) {
              this.isRecording = false;
              this.currentTabId = null;
              this._recordingStartId = (this._recordingStartId || 0) + 1;
              if (wasRecording) {
                this._flushLocalBuffer().catch((e) => console.warn('local analytics flush error:', e));
              }
            }
          }
          if (state) syncToolbarActionIcon(state);
          sendResponse({ success: true });
          break;
        }

        case 'TRANSCRIPTION':
          if (!message.localPreviewHandled) {
            this.handleTranscription(message.text, message.tabId || sender.tab?.id, message.isFinal, message.confidence ?? null);
          } else if (message.isFinal && String(message.text || '').trim()) {
            this._localBuffer.push(String(message.text).trim());
            if (message.confidence !== null && message.confidence !== undefined) {
              this._localConfidences.push(message.confidence);
            }
            if (this._localDebounceTimer) clearTimeout(this._localDebounceTimer);
            this._localDebounceTimer = setTimeout(() => {
              this._flushLocalBuffer().catch(e => console.warn('local analytics flush error:', e));
            }, 3000);
          }
          sendResponse({ success: true });
          break;

        case 'SUBMIT_FORM':
          sendResponse({ success: true });
          break;

        case 'GET_VOICE_SETTINGS':
          this.loadLocalSettings()
            .then(settings => sendResponse({ success: true, settings }))
            .catch(e => sendResponse({ success: false, error: e.message }));
          return true;

        case 'CHECK_RECORDING_STATE':
          if (this.isRecording && this.currentTabId === sender.tab?.id) {
            sendResponse({
              isRecording: true,
              tabId: sender.tab?.id,
              config: {
                voiceConfig: this.voiceConfig,
                correctionRules: this.correctionRules,
                smileyRules: this.smileyRules,
              }
            });
          } else {
            sendResponse({ isRecording: false });
          }
          break;

        case 'RECORDING_ERROR':
          this.handleRecordingError(message.error, sender.tab?.id);
          sendResponse({ success: true });
          break;

        case 'SAVE_VOICE_SETTINGS':
          this.saveLocalSettings(message.settings || {})
            .then((settings) => sendResponse({ success: true, settings }))
            .catch(e => sendResponse({ success: false, error: e.message }));
          return true;

        case 'OPEN_OPTIONS':
          chillicollectivoOpenWebMicOptions()
            .then((ok) => sendResponse({ success: !!ok }))
            .catch(() => sendResponse({ success: false }));
          return true;

        case 'GET_WEB_PANEL_STATE':
          (async () => {
            try {
              await this.loadLocalSettings();
              const [licenseState, deviceId] = await Promise.all([
                chillicollectivoGetLicenseState({ revalidate: true }),
                chillicollectivoGetOrCreateDeviceId(),
              ]);
              const portalOnline = !!(await chillicollectivoPingWebMicPortal?.().catch(() => false));
              const micUpdate = await chillicollectivoGetMicUpdateInfo?.().catch(() => null);
              sendResponse({
                success: true,
                settings: this.localSettings,
                licenseState,
                deviceId,
                version: chrome.runtime.getManifest?.()?.version || '2.1.41',
                portalOnline,
                portalUrl: CHILLICOLLECTIVO_WEB_MIC?.OPTIONS_BASE || '',
                micUpdate,
              });
            } catch (e) {
              sendResponse({ success: false, error: e.message });
            }
          })();
          return true;

        case 'MIC_WEB_SYNC_NOW':
          chillicollectivoSyncFromWebMicPortal(this)
            .then((result) => sendResponse({ success: !!result?.ok, ...result }))
            .catch((e) => sendResponse({ success: false, error: e.message }));
          return true;

        case 'GET_LICENSE_STATE':
          chillicollectivoGetLicenseState({ revalidate: !!message.revalidate })
            .then((state) => sendResponse({ success: true, state }))
            .catch((e) => sendResponse({ success: false, error: e.message }));
          return true;

        case 'LICENSE_SERVER_POST':
          (async () => {
            try {
              const body = message.body || {};
              const timeoutMs = Number(message.timeoutMs || 12000);
              const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
              const timeoutId = controller ? setTimeout(() => controller.abort(), timeoutMs) : null;
              const url = `${CHILLICOLLECTIVO_LICENSE.API_BASE}/api/license.php`;
              const response = await fetch(url, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(body),
                cache: 'no-store',
                signal: controller?.signal,
              });
              if (timeoutId) clearTimeout(timeoutId);
              let payload = {};
              try {
                payload = await response.json();
              } catch (_) {
                payload = {};
              }
              sendResponse({
                success: true,
                ok: response.ok,
                status: response.status,
                payload,
                networkError: false,
              });
            } catch (e) {
              sendResponse({
                success: false,
                networkError: true,
                error: e?.message || 'Lizenzserver nicht erreichbar',
              });
            }
          })();
          return true;

        case 'CHECK_MIC_UPDATE':
          chillicollectivoFetchMicUpdateFromServer()
            .then((micUpdate) => sendResponse({ success: true, micUpdate }))
            .catch((e) => sendResponse({ success: false, error: e.message }));
          return true;

        case 'OPEN_MIC_UPDATE_DOWNLOAD':
          (async () => {
            try {
              let info = await chillicollectivoGetMicUpdateInfo();
              if (!info?.downloadUrl && !(info?.downloads?.length)) {
                info = await chillicollectivoFetchMicUpdateFromServer();
              }
              const url = String(message.url || '').trim();
              if (!chillicollectivoOpenMicUpdateDownload(info, url)) {
                sendResponse({ success: false, error: 'Kein Download-Link auf dem Server hinterlegt.' });
                return;
              }
              sendResponse({ success: true, micUpdate: info });
            } catch (e) {
              sendResponse({ success: false, error: e.message });
            }
          })();
          return true;

        case 'ACTIVATE_LICENSE':
          chillicollectivoActivateLicense(message.key, { email: message.email })
            .then(async (license) => {
              if (!license?.valid) {
                throw new Error(license?.lastError || 'Aktivierung fehlgeschlagen.');
              }
              await chillicollectivoScheduleAccessExpiryWatch();
              const state = chillicollectivoBuildLicenseState(license, null);
              await chillicollectivoBroadcastLicenseState(state);
              await this.unlockTorchwoodTabsAfterLicense(state);
              chillicollectivoPushLicenseToWebMicPortal?.(message.key, message.email).catch(() => {});
              sendResponse({ success: true, license, state });
            })
            .catch((e) => sendResponse({ success: false, error: e.message }));
          return true;

        case 'CLEAR_LICENSE':
          chillicollectivoClearLicense()
            .then(async () => {
              await chillicollectivoClearWebMicPortalLicense?.().catch(() => {});
              const state = await chillicollectivoGetLicenseState({
                revalidate: false,
                startTrial: false,
                scheduleWatch: false,
              });
              await chillicollectivoBroadcastLicenseState(state);
              if (this.isRecording && this.currentTabId) {
                await this.stopRecording(this.currentTabId).catch(() => {});
              }
              if (typeof chrome?.tabs?.query === 'function') {
                const tabs = await new Promise((resolve) => {
                  chrome.tabs.query({ url: ['*://torchwood.app/*', '*://*.torchwood.app/*'] }, (result) => resolve(result || []));
                });
                for (const tab of tabs) {
                  if (!tab.id) continue;
                  if (state?.access === 'granted') {
                    this.updateState(tab.id, 'ready');
                  } else {
                    this.updateState(tab.id, 'locked', state?.message || 'Keine gültige Lizenz');
                  }
                }
              }
              sendResponse({ success: true, state });
            })
            .catch((e) => sendResponse({ success: false, error: e.message }));
          return true;

        case 'OPEN_SALES_PAGE':
          chillicollectivoOpenSalesPage()
            .then(() => sendResponse({ success: true }))
            .catch((e) => sendResponse({ success: false, error: e.message }));
          return true;

        case 'LICENSE_REQUIRED':
          chillicollectivoOpenLicenseOptions()
            .then(() => sendResponse({ success: true }))
            .catch((e) => sendResponse({ success: false, error: e.message }));
          return true;

        case 'CHECK_OUT_REWARD':
          chillicollectivoTryGrantOutMilestoneReward(message.totalOuts)
            .then(async (result) => {
              if (result?.granted) await chillicollectivoBroadcastLicenseState();
              sendResponse({ success: true, ...result });
            })
            .catch((e) => sendResponse({ success: false, error: e.message }));
          return true;

        case 'open-options-page':
        case 'tfx_open_options':
        case 'tfx_open_options_page':
        case 'tfx_open_sidebar_options':
          chillicollectivoOpenWebMicOptions()
            .then((ok) => sendResponse({ ok: !!ok }))
            .catch(() => sendResponse({ ok: false }));
          return true;
      }

      return false;
    });

    chrome.tabs.onRemoved.addListener((tabId) => {
      if (this.isRecording && this.currentTabId === tabId) {
        console.log('Recording tab closed, stopping recording');
        this.stopRecording(tabId);
      }
    });
  }

  setupCommandHandlers() {
    if (!chrome.commands?.onCommand) return;
    chrome.commands.onCommand.addListener((command) => {
      if (command !== 'toggle-voice') return;
      this.handleVoiceToggleCommand().catch((err) => {
        console.warn('toggle-voice command failed:', err);
      });
    });
  }

  async handleVoiceToggleCommand() {
    const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
    const tabId = tab?.id;
    if (!tabId || !chillicollectivoIsTorchwoodUrl(tab.url)) return;

    let currentState = (this.isRecording && this.currentTabId === tabId) ? 'recording' : 'ready';
    try {
      const response = await chrome.tabs.sendMessage(tabId, { type: 'GET_VOICE_STATE' });
      if (response?.state) currentState = response.state;
    } catch (_) {}

    await this.handleIconClick(tabId, currentState);
  }

  async handleIconClick(tabId, currentState) {
    const now = Date.now();
    if (now - (this._lastVoiceToggleAt || 0) < 120) return;
    this._lastVoiceToggleAt = now;

    if (!tabId) {
      console.error('No tab ID provided');
      return;
    }

    const bgRecording = !!(this.isRecording && this.currentTabId === tabId);
    const shouldStop = currentState === 'recording' || (currentState === 'loading' && bgRecording);
    const shouldStart = currentState === 'ready' || currentState === 'error' || currentState === 'locked'
      || (currentState === 'loading' && !bgRecording);

    if (shouldStop) {
      await this.stopRecording(tabId);
      return;
    }

    if (shouldStart) {
      if (bgRecording) {
        if (currentState === 'recording' || currentState === 'loading') {
          await this.stopRecording(tabId);
        } else {
          this.isRecording = false;
          this.currentTabId = null;
          this._recordingStartId = (this._recordingStartId || 0) + 1;
        }
      }
      await this.startRecording(tabId);
      return;
    }

    if (bgRecording) {
      await this.stopRecording(tabId);
    }
  }

  async startRecording(tabId) {
    try {
      const startId = ++this._recordingStartId;
      const licenseState = await chillicollectivoGetLicenseState({ revalidate: true });
      if (licenseState.access !== 'granted') {
        console.warn('Recording blocked – no valid license:', licenseState.reason);
        await chillicollectivoOpenLicenseOptions();
        this.updateState(tabId, 'locked', licenseState.message);
        return;
      }

      console.log('Starting local browser recording for tab:', tabId);

      await this.loadLocalSettings().catch(e => console.warn('local settings load failed:', e));

      const response = await chrome.tabs.sendMessage(tabId, {
        type: 'START_BROWSER_RECORDING',
        tabId: tabId,
        config: {
          voiceConfig: this.voiceConfig,
          correctionRules: this.correctionRules,
          smileyRules: this.smileyRules,
        }
      });

      if (!response?.ready) {
        throw new Error(response?.error || 'Mikrofon konnte nicht gestartet werden');
      }

      if (startId !== this._recordingStartId) {
        console.log('Recording start cancelled while starting');
        await chrome.tabs.sendMessage(tabId, { type: 'STOP_BROWSER_RECORDING', keepMic: false }).catch(() => {});
        return;
      }

      this._localBuffer = [];
      this._localConfidences = [];
      this._localRecordingStartTime = Date.now();
      if (this._localDebounceTimer) { clearTimeout(this._localDebounceTimer); this._localDebounceTimer = null; }
      this.isRecording = true;
      this.currentTabId = tabId;
      this.updateState(tabId, 'recording');
      console.log('Browser recording started in content script');
    } catch (error) {
      console.error('Error starting recording:', error);
      this.isRecording = false;
      this.currentTabId = null;
      this.updateState(tabId, 'error', error.message);
    }
  }

  async stopRecording(tabId) {
    try {
      console.log('Stopping recording for tab:', tabId);

      this._recordingStartId = (this._recordingStartId || 0) + 1;
      this.isRecording = false;
      this.currentTabId = null;
      this.updateState(tabId, 'ready');
      await chrome.tabs.sendMessage(tabId, { type: 'STOP_BROWSER_RECORDING', keepMic: false }).catch(() => {});
      this._flushLocalBuffer().catch(e => console.warn('local analytics flush error:', e));

      console.log('Recording stopped');
    } catch (error) {
      console.error('Error stopping recording:', error);
      this.updateState(tabId, 'error', error.message);
    }
  }

  processTranscribedText(text) {
    if (!text) return '';
    let t = typeof chillicollectivoApplyVoiceTextRules === 'function'
      ? chillicollectivoApplyVoiceTextRules(text, {
        correctionRules: this.correctionRules,
        smileyRules: this.smileyRules,
      })
      : String(text).replace(/ß/g, 'ss');

    if (typeof chillicollectivoApplySpokenPunctuation === 'function') {
      t = chillicollectivoApplySpokenPunctuation(t);
    }

    return t;
  }

  handleTranscription(text, tabId, isFinal = false, confidence = null) {
    if (!tabId) return;

    let processedText = this.processTranscribedText(text);

    if (!processedText.trim()) return;

    chrome.tabs.sendMessage(tabId, {
      type: 'INSERT_TEXT',
      text: processedText,
      isFinal: isFinal,
    }).catch(err => {
      console.error('Failed to send text to content script:', err);
    });

    if (isFinal && processedText.trim()) {
      this._localBuffer.push(processedText.trim());
      if (confidence !== null) this._localConfidences.push(confidence);

      if (this._localDebounceTimer) clearTimeout(this._localDebounceTimer);
      this._localDebounceTimer = setTimeout(() => {
        this._flushLocalBuffer().catch(e => console.warn('local analytics flush error:', e));
      }, 3000);
    }
  }

  async _flushLocalBuffer() {
    if (this._localDebounceTimer) {
      clearTimeout(this._localDebounceTimer);
      this._localDebounceTimer = null;
    }
    this._localBuffer = [];
    this._localConfidences = [];
    this._localRecordingStartTime = null;
  }

  handleRecordingError(error, tabId) {
    console.error('Recording error:', error);

    const errorMessage = typeof error === 'string' ? error : (error.message || String(error));

    const isPermissionError = errorMessage.includes('Mikrofon-Zugriff verweigert') ||
        errorMessage.includes('Permission denied') ||
        errorMessage.includes('NotAllowedError');

    const isServiceError = errorMessage.includes('Sprachdienst nicht verfügbar');

    const displayError = isPermissionError
      ? 'Mikrofon-Zugriff verweigert. Bitte in den Seiteneinstellungen (Schloss-Symbol) erlauben.'
      : isServiceError
        ? 'Sprachdienst nicht verfügbar. Bitte Seite neu laden oder Chrome neu starten.'
        : errorMessage;

    if (tabId) {
      this.updateState(tabId, 'error', displayError);
    }

    globalThis.ChillicollectivoDiagnosticLog?.error?.(
      'background.recording',
      displayError,
      errorMessage
    );

    this.isRecording = false;
    this.currentTabId = null;
  }

  async handleLicenseStorageChange() {
    const state = await chillicollectivoGetLicenseState({ revalidate: false, startTrial: false });
    await chillicollectivoBroadcastLicenseState(state);
    if (state?.access === 'granted') {
      await this.unlockTorchwoodTabsAfterLicense(state);
      return;
    }
    if (typeof chrome?.tabs?.query !== 'function') return;
    const tabs = await new Promise((resolve) => {
      chrome.tabs.query({ url: ['*://torchwood.app/*', '*://*.torchwood.app/*'] }, (result) => resolve(result || []));
    });
    for (const tab of tabs) {
      if (tab.id) this.updateState(tab.id, 'locked', state?.message || 'Keine gültige Lizenz');
    }
  }

  async unlockTorchwoodTabsAfterLicense(state) {
    if (state?.access !== 'granted' || typeof chrome?.tabs?.query !== 'function') return;
    const tabs = await new Promise((resolve) => {
      chrome.tabs.query({ url: ['*://torchwood.app/*', '*://*.torchwood.app/*'] }, (result) => resolve(result || []));
    });
    for (const tab of tabs) {
      if (!tab.id) continue;
      try {
        const response = await chrome.tabs.sendMessage(tab.id, { type: 'GET_VOICE_STATE' });
        const voiceState = String(response?.state || '').trim();
        if (voiceState === 'locked') {
          this.updateState(tab.id, 'ready');
        }
      } catch (_) {}
    }
  }

  updateState(tabId, state, errorMessage = '') {
    syncToolbarActionIcon(state);
    chrome.tabs.sendMessage(tabId, {
      type: 'STATE_CHANGE',
      state: state,
      errorMessage: errorMessage
    }).catch(err => {
      console.warn('Could not update state in content script:', err);
    });
  }

  setupExtensionAction() {
    chrome.action.onClicked.addListener(() => {
      chillicollectivoOpenWebMicOptions().catch(() => {});
    });
  }

  async loadLocalSettings() {
    const data = await chrome.storage.local.get([
      'chillicollectivoVoiceSettings',
      'chillicollectivoVoiceEffects',
      'chillicollectivoVoiceHotkey',
    ]);

    const stored = data.chillicollectivoVoiceSettings || {};
    const settings = { ...CHILLICOLLECTIVO_DEFAULT_SETTINGS, ...stored };

    if (data.chillicollectivoVoiceEffects) {
      settings.previewEnabled = data.chillicollectivoVoiceEffects.previewEnabled !== false;
      if (data.chillicollectivoVoiceEffects.previewOpacity != null) {
        settings.previewOpacity = data.chillicollectivoVoiceEffects.previewOpacity;
      }
      if (data.chillicollectivoVoiceEffects.previewColor) {
        settings.previewColor = data.chillicollectivoVoiceEffects.previewColor;
      }
      if (data.chillicollectivoVoiceEffects.toolColor) {
        settings.toolColor = data.chillicollectivoVoiceEffects.toolColor;
      }
      if (data.chillicollectivoVoiceEffects.toolOpacity != null) {
        settings.toolOpacity = data.chillicollectivoVoiceEffects.toolOpacity;
      }
      if (data.chillicollectivoVoiceEffects.fabGearIcon) {
        settings.fabGearIcon = data.chillicollectivoVoiceEffects.fabGearIcon === 'mic' ? 'mic' : 'leaf';
      }
      settings.sendCrumbleIntensity = resolveEffectIntensity(
        data.chillicollectivoVoiceEffects.sendCrumbleIntensity,
        data.chillicollectivoVoiceEffects.sendCrumbleEnabled
      );
    }
    if (data.chillicollectivoVoiceHotkey !== undefined) {
      settings.hotkey = data.chillicollectivoVoiceHotkey || null;
    } else if (stored.hotkey !== undefined) {
      settings.hotkey = stored.hotkey || null;
    }

    let persistDefaults = false;

    if (settings.sharpSAsDoubleS !== true) {
      settings.sharpSAsDoubleS = true;
      persistDefaults = true;
    }

    if (settings.speechLang !== 'de-CH') {
      settings.speechLang = 'de-CH';
      persistDefaults = true;
    }

    const legacySmileys = await chrome.storage.local.get('chillicollectivoVoiceSmileysRaw');
    if (legacySmileys.chillicollectivoVoiceSmileysRaw && !stored.voiceSmileysRaw) {
      settings.voiceSmileysRaw = String(legacySmileys.chillicollectivoVoiceSmileysRaw || '');
      persistDefaults = true;
    }

    const agentenV2Migration = await chrome.storage.local.get('chillicollectivoAgentenMicV2Migrated');
    if (!agentenV2Migration.chillicollectivoAgentenMicV2Migrated) {
      settings.previewEnterToInsert = false;
      settings.previewClickToInsert = false;
      settings.ppAutoInsert = false;
      settings.previewInsertHotkey = null;
      settings.tabEnterHotkey = null;
      settings.previewPinned = false;
      settings.previewTextCase = 'sentence';
      settings.voiceSmileysRaw = '';
      if (isLegacyDefaultGreenColor(settings.previewColor)) {
        settings.previewColor = CHILLICOLLECTIVO_DEFAULT_ACCENT_COLOR;
      }
      if (isLegacyDefaultGreenColor(settings.toolColor)) {
        settings.toolColor = CHILLICOLLECTIVO_DEFAULT_ACCENT_COLOR;
      }
      persistDefaults = true;
      await chrome.storage.local.set({ chillicollectivoAgentenMicV2Migrated: true });
    }

    const agentenYellowDefaults = await chrome.storage.local.get('chillicollectivoAgentenMicYellowDefaults');
    if (!agentenYellowDefaults.chillicollectivoAgentenMicYellowDefaults) {
      if (isLegacyDefaultGreenColor(settings.previewColor)) {
        settings.previewColor = CHILLICOLLECTIVO_DEFAULT_ACCENT_COLOR;
      }
      if (isLegacyDefaultGreenColor(settings.toolColor)) {
        settings.toolColor = CHILLICOLLECTIVO_DEFAULT_ACCENT_COLOR;
      }
      persistDefaults = true;
      await chrome.storage.local.set({ chillicollectivoAgentenMicYellowDefaults: true });
    }

    const previewFontMigration = await chrome.storage.local.get('chillicollectivoPreviewFontSemiboldMigrated');
    if (!previewFontMigration.chillicollectivoPreviewFontSemiboldMigrated) {
      if (settings.previewFontWeight === 'extrabold') {
        settings.previewFontWeight = 'semibold';
      }
      persistDefaults = true;
      await chrome.storage.local.set({ chillicollectivoPreviewFontSemiboldMigrated: true });
    }

    const previewCaseMigration = await chrome.storage.local.get('chillicollectivoPreviewTextCaseLowerMigrated');
    if (!previewCaseMigration.chillicollectivoPreviewTextCaseLowerMigrated) {
      settings.previewTextCase = 'sentence';
      persistDefaults = true;
      await chrome.storage.local.set({ chillicollectivoPreviewTextCaseLowerMigrated: true });
    }

    const previewTypographyFix = await chrome.storage.local.get('chillicollectivoPreviewTypographyFixV2');
    if (!previewTypographyFix.chillicollectivoPreviewTypographyFixV2) {
      settings.previewTextCase = 'sentence';
      settings.previewFontWeight = 'light';
      persistDefaults = true;
      await chrome.storage.local.set({ chillicollectivoPreviewTypographyFixV2: true });
    }

    const agentenDirectModeMigration = await chrome.storage.local.get('chillicollectivoAgentenMicDirectModeV210');
    if (!agentenDirectModeMigration.chillicollectivoAgentenMicDirectModeV210) {
      settings.previewClickToInsert = false;
      persistDefaults = true;
      await chrome.storage.local.set({ chillicollectivoAgentenMicDirectModeV210: true });
    }

    const agentenCoralDefaults = await chrome.storage.local.get('chillicollectivoAgentenMicCoralDefaultsV214');
    if (!agentenCoralDefaults.chillicollectivoAgentenMicCoralDefaultsV214) {
      if (isLegacyDefaultGreenColor(settings.previewColor) || isLegacyDefaultYellowColor(settings.previewColor)) {
        settings.previewColor = CHILLICOLLECTIVO_DEFAULT_ACCENT_COLOR;
      }
      if (isLegacyDefaultGreenColor(settings.toolColor) || isLegacyDefaultYellowColor(settings.toolColor)) {
        settings.toolColor = CHILLICOLLECTIVO_DEFAULT_ACCENT_COLOR;
      }
      persistDefaults = true;
      await chrome.storage.local.set({ chillicollectivoAgentenMicCoralDefaultsV214: true });
    }

    if (settings.previewTextCase !== 'spoken') {
      settings.previewTextCase = 'spoken';
      persistDefaults = true;
    }

    applyAgentenMicSettingsOverrides(settings);
    this.applyLocalSettings(settings);
    await syncChromeVoiceCommandHotkey(this.localSettings.hotkey);

    const storagePayload = {
      chillicollectivoVoiceSettings: this.localSettings,
      chillicollectivoVoiceEffects: buildVoiceEffectsPayload(this.localSettings),
      chillicollectivoVoiceHotkey: this.localSettings.hotkey || null,
    };
    if (persistDefaults) {
      await chrome.storage.local.set(storagePayload);
    }

    return this.localSettings;
  }

  async saveLocalSettings(partial) {
    const incoming = partial || {};
    const settings = applyAgentenMicSettingsOverrides({
      ...this.localSettings,
      ...incoming,
      speechLang: 'de-CH',
      sharpSAsDoubleS: true,
    });

    const storagePayload = {
      chillicollectivoVoiceSettings: settings,
      chillicollectivoVoiceEffects: buildVoiceEffectsPayload(settings),
      chillicollectivoVoiceHotkey: settings.hotkey || null,
    };

    await chrome.storage.local.set(storagePayload);
    this.applyLocalSettings(settings);
    await syncChromeVoiceCommandHotkey(settings.hotkey || null);
    return this.localSettings;
  }

  applyLocalSettings(settings) {
    const merged = { ...CHILLICOLLECTIVO_DEFAULT_SETTINGS, ...settings };
    merged.sendCrumbleIntensity = resolveEffectIntensity(merged.sendCrumbleIntensity, merged.sendCrumbleEnabled);
    const opacity = Number(merged.previewOpacity);
    merged.previewOpacity = Number.isFinite(opacity)
      ? Math.min(100, Math.max(0, Math.round(opacity / 5) * 5))
      : CHILLICOLLECTIVO_DEFAULT_SETTINGS.previewOpacity;
    const toolOpacity = Number(merged.toolOpacity);
    merged.toolOpacity = Number.isFinite(toolOpacity)
      ? Math.min(100, Math.max(20, Math.round(toolOpacity / 5) * 5))
      : CHILLICOLLECTIVO_DEFAULT_SETTINGS.toolOpacity;
    merged.previewColor = normalizePreviewColorValue(merged.previewColor);
    merged.toolColor = normalizePreviewColorValue(merged.toolColor || merged.previewColor);
    const previewFontWeight = String(merged.previewFontWeight || '').trim().toLowerCase();
    merged.previewFontWeight = ['light', 'semibold', 'extrabold'].includes(previewFontWeight)
      ? previewFontWeight
      : CHILLICOLLECTIVO_DEFAULT_SETTINGS.previewFontWeight;
    const previewTextCase = String(merged.previewTextCase || '').trim().toLowerCase();
    merged.previewTextCase = ['sentence', 'spoken', 'lowercase', 'uppercase'].includes(previewTextCase)
      ? previewTextCase
      : CHILLICOLLECTIVO_DEFAULT_SETTINGS.previewTextCase;
    const previewFontSize = Number(merged.previewFontSize);
    merged.previewFontSize = Number.isFinite(previewFontSize)
      ? Math.min(32, Math.max(16, Math.round(previewFontSize)))
      : CHILLICOLLECTIVO_DEFAULT_SETTINGS.previewFontSize;
    merged.fabGearIcon = merged.fabGearIcon === 'mic' ? 'mic' : 'leaf';
    if (merged.previewInsertHotkey && typeof merged.previewInsertHotkey === 'object' && merged.previewInsertHotkey.key) {
      merged.previewInsertHotkey = {
        key: String(merged.previewInsertHotkey.key),
        ctrlKey: !!merged.previewInsertHotkey.ctrlKey,
        shiftKey: !!merged.previewInsertHotkey.shiftKey,
        altKey: !!merged.previewInsertHotkey.altKey,
        metaKey: !!merged.previewInsertHotkey.metaKey,
      };
    } else {
      merged.previewInsertHotkey = null;
    }
    if (merged.tabEnterHotkey && typeof merged.tabEnterHotkey === 'object' && merged.tabEnterHotkey.key) {
      merged.tabEnterHotkey = {
        key: String(merged.tabEnterHotkey.key),
        ctrlKey: !!merged.tabEnterHotkey.ctrlKey,
        shiftKey: !!merged.tabEnterHotkey.shiftKey,
        altKey: !!merged.tabEnterHotkey.altKey,
        metaKey: !!merged.tabEnterHotkey.metaKey,
      };
    } else {
      merged.tabEnterHotkey = null;
    }
    if (merged.previewEnabled === false) {
      merged.previewMovable = false;
      merged.previewPinned = false;
    }
    merged.speechLang = 'de-CH';
    merged.sharpSAsDoubleS = true;
    delete merged.voiceModel;
    delete merged.authToken;
    delete merged.pulseIntensity;
    delete merged.glowIntensity;
    delete merged.pulseEnabled;
    delete merged.glowEnabled;
    delete merged.sendCrumbleEnabled;
    applyAgentenMicSettingsOverrides(merged);
    this.localSettings = merged;
    this.voiceConfig = {
      speechLang: 'de-CH',
      sharpSAsDoubleS: true,
    };
    this.correctionRules = typeof chillicollectivoParseCorrectionRules === 'function'
      ? chillicollectivoParseCorrectionRules(this.localSettings.autoCorrectionsRaw || '')
      : [];
    this.smileyRules = typeof chillicollectivoParseSmileyRules === 'function'
      ? chillicollectivoParseSmileyRules(this.localSettings.voiceSmileysRaw || '')
      : [];
  }
}

new CHILLICOLLECTIVOBackgroundService();
