(function attachOrbitOneVaultExtension(globalScope) {
  'use strict';

  const browserApi = globalScope.browser || globalScope.chrome;
  const usesPromiseApi = Boolean(globalScope.browser);
  const RELEASE_FEED_URL = 'https://orbitone.io/downloads/orbitone-vault-release.json';
  const SETTINGS_STORAGE_KEY = 'orbitoneVaultSettingsV2';
  const LEGACY_SETTING_KEYS = [
    'baseUrl',
    'apiToken',
    'autofillEnabled',
    'savePromptEnabled',
    'matchSubdomains',
    'defaultScope',
    'defaultWorkspaceId',
    'defaultVaultId',
  ];
  const DEFAULT_SETTINGS = Object.freeze({
    connections: [],
    activeConnectionId: '',
    activeTenantId: 0,
    autofillEnabled: true,
    savePromptEnabled: true,
    matchSubdomains: false,
    ignoredCredentialHosts: [],
  });

  function callbackCall(target, method, args = []) {
    return new Promise((resolve, reject) => {
      target[method](...args, (result) => {
        const lastError = globalScope.chrome?.runtime?.lastError;
        if (lastError) {
          reject(new Error(lastError.message || 'Browser-API fehlgeschlagen.'));
          return;
        }
        resolve(result);
      });
    });
  }

  function apiCall(target, method, args = []) {
    if (!target || typeof target[method] !== 'function') {
      return Promise.reject(new Error(`Browser-API ${method} ist nicht verfügbar.`));
    }
    if (usesPromiseApi) {
      return Promise.resolve(target[method](...args));
    }
    return callbackCall(target, method, args);
  }

  const extension = {
    browserApi,
    storage: {
      get(keys) {
        return apiCall(browserApi?.storage?.local, 'get', [keys]);
      },
      set(values) {
        return apiCall(browserApi?.storage?.local, 'set', [values]);
      },
      remove(keys) {
        return apiCall(browserApi?.storage?.local, 'remove', [keys]);
      },
      session: {
        get(keys) {
          if (!browserApi?.storage?.session) {
            return Promise.resolve({});
          }
          return apiCall(browserApi.storage.session, 'get', [keys]);
        },
        set(values) {
          if (!browserApi?.storage?.session) {
            return Promise.resolve();
          }
          return apiCall(browserApi.storage.session, 'set', [values]);
        },
        remove(keys) {
          if (!browserApi?.storage?.session) {
            return Promise.resolve();
          }
          return apiCall(browserApi.storage.session, 'remove', [keys]);
        },
        available: Boolean(browserApi?.storage?.session),
      },
    },
    tabs: {
      query(queryInfo) {
        return apiCall(browserApi?.tabs, 'query', [queryInfo]);
      },
      sendMessage(tabId, message) {
        return apiCall(browserApi?.tabs, 'sendMessage', [tabId, message]);
      },
      create(createProperties) {
        return apiCall(browserApi?.tabs, 'create', [createProperties]);
      },
    },
    runtime: {
      sendMessage(message) {
        return apiCall(browserApi?.runtime, 'sendMessage', [message]);
      },
      openOptionsPage() {
        return apiCall(browserApi?.runtime, 'openOptionsPage');
      },
      getURL(path) {
        return browserApi?.runtime?.getURL(String(path || '')) || '';
      },
    },
  };

  function normalizeBaseUrl(rawValue) {
    const value = String(rawValue || '').trim();
    if (!value) {
      return '';
    }
    const withProtocol = /^[a-z][a-z0-9+.-]*:\/\//i.test(value) ? value : `https://${value}`;
    const normalized = new URL(withProtocol);
    if (!['http:', 'https:'].includes(normalized.protocol)) {
      throw new Error('Die OrbitOne-Adresse muss HTTP oder HTTPS verwenden.');
    }
    normalized.hash = '';
    normalized.search = '';
    normalized.pathname = normalized.pathname.replace(/\/+$/, '');
    return normalized.toString().replace(/\/$/, '');
  }

  function buildApiUrl(baseUrl, path, params = {}) {
    const root = normalizeBaseUrl(baseUrl);
    if (!root) {
      throw new Error('Bitte zuerst die OrbitOne-Adresse einrichten.');
    }
    const url = new URL(`api/${String(path || '').replace(/^\/+/, '')}`, `${root}/`);
    Object.entries(params || {}).forEach(([key, value]) => {
      if (value === undefined || value === null || value === '') {
        return;
      }
      url.searchParams.set(key, String(value));
    });
    return url.toString();
  }

  async function apiRequest(baseUrl, token, path, options = {}) {
    const apiToken = String(token || '').trim();
    if (!apiToken) {
      throw new Error('Bitte zuerst einen OrbitOne-Zugriffstoken hinterlegen.');
    }

    const controller = new AbortController();
    const timeout = globalScope.setTimeout(() => controller.abort(), Number(options.timeout || 20000));
    try {
      const response = await fetch(buildApiUrl(baseUrl, path, options.params), {
        method: String(options.method || 'GET').toUpperCase(),
        headers: {
          Accept: 'application/json',
          'X-API-Key': apiToken,
          'X-Auth-Token': apiToken,
          Authorization: `Bearer ${apiToken}`,
          ...(options.data ? { 'Content-Type': 'application/json' } : {}),
        },
        body: options.data ? JSON.stringify(options.data) : undefined,
        cache: 'no-store',
        credentials: 'omit',
        signal: controller.signal,
      });

      const payload = await response.json().catch(() => null);
      if (!response.ok || !payload || payload.success === false) {
        const error = new Error(payload?.error?.message || `OrbitOne-Anfrage fehlgeschlagen (${response.status}).`);
        error.code = payload?.error?.code || 'request_failed';
        error.status = response.status;
        throw error;
      }
      return payload.data;
    } catch (error) {
      if (error?.name === 'AbortError') {
        throw new Error('Die OrbitOne-Anfrage hat zu lange gedauert.');
      }
      throw error;
    } finally {
      globalScope.clearTimeout(timeout);
    }
  }

  function connectionId(rawValue = '') {
    const supplied = String(rawValue || '').trim();
    if (supplied) {
      const sanitized = supplied.replace(/[^a-z0-9_-]/gi, '').slice(0, 80);
      if (sanitized) {
        return sanitized;
      }
    }
    if (globalScope.crypto?.randomUUID) {
      return `connection-${globalScope.crypto.randomUUID()}`;
    }
    return `connection-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
  }

  function normalizeConnection(connection = {}, index = 0) {
    const baseUrl = normalizeBaseUrl(connection?.baseUrl || '');
    const fallbackName = baseUrl
      ? (parseWebUrl(baseUrl)?.hostname || `OrbitOne ${index + 1}`)
      : `OrbitOne ${index + 1}`;
    const defaultsByTenant = Object.fromEntries(Object.entries(
      connection?.defaultsByTenant && typeof connection.defaultsByTenant === 'object'
        ? connection.defaultsByTenant
        : {}
    ).map(([tenantId, defaults]) => [
      String(Number(tenantId || 0)),
      {
        scope: defaults?.scope === 'workspace' ? 'workspace' : 'personal',
        workspaceId: Number(defaults?.workspaceId || 0),
        vaultId: Number(defaults?.vaultId || 0),
      },
    ]).filter(([tenantId]) => tenantId !== '0'));
    return {
      id: connectionId(connection?.id),
      name: String(connection?.name || connection?.tenantName || fallbackName).trim() || fallbackName,
      baseUrl,
      apiToken: String(connection?.apiToken || '').trim(),
      tenantName: String(connection?.tenantName || '').trim(),
      userName: String(connection?.userName || '').trim(),
      defaultScope: connection?.defaultScope === 'workspace' ? 'workspace' : 'personal',
      defaultWorkspaceId: Number(connection?.defaultWorkspaceId || 0),
      defaultVaultId: Number(connection?.defaultVaultId || 0),
      defaultsByTenant,
    };
  }

  function normalizeSettings(settings = {}) {
    let rawConnections = Array.isArray(settings?.connections) ? settings.connections : [];
    if (!rawConnections.length && (settings?.baseUrl || settings?.apiToken)) {
      rawConnections = [{
        id: 'connection-migrated',
        name: settings?.tenantName || '',
        baseUrl: settings?.baseUrl || '',
        apiToken: settings?.apiToken || '',
        defaultScope: settings?.defaultScope,
        defaultWorkspaceId: settings?.defaultWorkspaceId,
        defaultVaultId: settings?.defaultVaultId,
      }];
    }
    const seen = new Set();
    const normalizedConnections = rawConnections.map((connection, index) => {
      const normalized = normalizeConnection(connection, index);
      while (seen.has(normalized.id)) {
        normalized.id = connectionId();
      }
      seen.add(normalized.id);
      return normalized;
    });
    const requestedActiveId = String(settings?.activeConnectionId || '').trim();
    const primaryConnection = normalizedConnections.find((connection) => connection.id === requestedActiveId)
      || normalizedConnections.find((connection) => connection.baseUrl && connection.apiToken)
      || normalizedConnections[0]
      || null;
    const connections = primaryConnection ? [primaryConnection] : [];
    const activeConnectionId = primaryConnection?.id || '';
    return {
      ...DEFAULT_SETTINGS,
      connections,
      activeConnectionId,
      activeTenantId: Number(settings?.activeTenantId || 0),
      autofillEnabled: settings?.autofillEnabled !== false,
      savePromptEnabled: settings?.savePromptEnabled !== false,
      matchSubdomains: settings?.matchSubdomains === true,
      ignoredCredentialHosts: Array.from(new Set(
        (Array.isArray(settings?.ignoredCredentialHosts) ? settings.ignoredCredentialHosts : [])
          .map(normalizeHostname)
          .filter(Boolean)
      )).slice(0, 500),
    };
  }

  function configuredConnections(settings) {
    return (Array.isArray(settings?.connections) ? settings.connections : [])
      .filter((connection) => Boolean(connection?.baseUrl && connection?.apiToken));
  }

  function activeConnection(settings, preferredId = '') {
    const connections = Array.isArray(settings?.connections) ? settings.connections : [];
    const requestedId = String(preferredId || settings?.activeConnectionId || '');
    return connections.find((connection) => connection.id === requestedId)
      || connections[0]
      || null;
  }

  function tenantConnectionId(rawConnectionId, rawTenantId) {
    const base = String(rawConnectionId || '').trim();
    const tenantId = Number(rawTenantId || 0);
    return base && tenantId > 0 ? `${base}--tenant-${tenantId}` : base;
  }

  function splitTenantConnectionId(rawValue) {
    const value = String(rawValue || '').trim();
    const match = value.match(/^(.*)--tenant-(\d+)$/);
    return {
      connectionId: match ? match[1] : value,
      tenantId: match ? Number(match[2]) : 0,
    };
  }

  function tenantDefaults(connection, rawTenantId) {
    const tenantId = String(Number(rawTenantId || 0));
    const stored = connection?.defaultsByTenant?.[tenantId];
    return {
      scope: stored?.scope === 'workspace'
        ? 'workspace'
        : (stored?.scope === 'personal' ? 'personal' : (connection?.defaultScope === 'workspace' ? 'workspace' : 'personal')),
      workspaceId: Number(stored?.workspaceId ?? connection?.defaultWorkspaceId ?? 0),
      vaultId: Number(stored?.vaultId ?? connection?.defaultVaultId ?? 0),
    };
  }

  async function getSettings() {
    const stored = await extension.storage.get({
      [SETTINGS_STORAGE_KEY]: null,
      baseUrl: '',
      apiToken: '',
      autofillEnabled: true,
      savePromptEnabled: true,
      matchSubdomains: false,
      defaultScope: 'personal',
      defaultWorkspaceId: 0,
      defaultVaultId: 0,
    });
    const hasCurrentSettings = stored?.[SETTINGS_STORAGE_KEY] && typeof stored[SETTINGS_STORAGE_KEY] === 'object';
    const normalized = normalizeSettings(hasCurrentSettings ? stored[SETTINGS_STORAGE_KEY] : stored);
    if (!hasCurrentSettings && (stored?.baseUrl || stored?.apiToken)) {
      await extension.storage.set({ [SETTINGS_STORAGE_KEY]: normalized });
      await extension.storage.remove(LEGACY_SETTING_KEYS);
    }
    return normalized;
  }

  async function saveSettings(settings) {
    const normalized = normalizeSettings(settings);
    await extension.storage.set({ [SETTINGS_STORAGE_KEY]: normalized });
    await extension.storage.remove(LEGACY_SETTING_KEYS);
    return normalized;
  }

  function isConfigured(settings) {
    return configuredConnections(settings).length > 0;
  }

  function parseWebUrl(rawValue) {
    try {
      const url = new URL(String(rawValue || ''));
      return ['http:', 'https:'].includes(url.protocol) ? url : null;
    } catch (_error) {
      return null;
    }
  }

  function normalizeEntryUrl(rawValue) {
    const value = String(rawValue || '').trim();
    if (!value) {
      return null;
    }
    return parseWebUrl(/^[a-z][a-z0-9+.-]*:\/\//i.test(value) ? value : `https://${value.replace(/^\/+/, '')}`);
  }

  function normalizeHostname(value) {
    return String(value || '').trim().toLowerCase().replace(/\.$/, '');
  }

  function isExactDomainMatch(entry, pageUrl) {
    const current = parseWebUrl(pageUrl);
    const saved = normalizeEntryUrl(entry?.url);
    return Boolean(current && saved && normalizeHostname(current.hostname) === normalizeHostname(saved.hostname));
  }

  function isDomainMatch(entry, pageUrl, allowSubdomains = false) {
    const current = parseWebUrl(pageUrl);
    const saved = normalizeEntryUrl(entry?.url);
    if (!current || !saved) {
      return false;
    }
    const currentHost = normalizeHostname(current.hostname);
    const savedHost = normalizeHostname(saved.hostname);
    if (currentHost === savedHost) {
      return true;
    }
    return allowSubdomains && (
      currentHost.endsWith(`.${savedHost}`)
      || savedHost.endsWith(`.${currentHost}`)
    );
  }

  function scoreEntryForUrl(entry, pageUrl, allowSubdomains = false) {
    if (isExactDomainMatch(entry, pageUrl)) {
      return 100;
    }
    return isDomainMatch(entry, pageUrl, allowSubdomains) ? 70 : 0;
  }

  function pageHost(pageUrl) {
    return normalizeHostname(parseWebUrl(pageUrl)?.hostname || '');
  }

  function trimTo(value, length = 120) {
    const text = String(value || '').trim();
    return text.length <= length ? text : `${text.slice(0, Math.max(1, length - 3))}...`;
  }

  function t(key, substitutions = undefined, fallback = '') {
    try {
      const translated = browserApi?.i18n?.getMessage?.(String(key || ''), substitutions);
      return translated || fallback || String(key || '');
    } catch (_error) {
      return fallback || String(key || '');
    }
  }

  function translateDocument(root = globalScope.document) {
    if (!root?.querySelectorAll) {
      return;
    }
    root.querySelectorAll('[data-i18n]').forEach((node) => {
      node.textContent = t(node.dataset.i18n, undefined, node.textContent);
    });
    root.querySelectorAll('[data-i18n-placeholder]').forEach((node) => {
      node.setAttribute('placeholder', t(node.dataset.i18nPlaceholder, undefined, node.getAttribute('placeholder') || ''));
    });
    root.querySelectorAll('[data-i18n-title]').forEach((node) => {
      const value = t(node.dataset.i18nTitle, undefined, node.getAttribute('title') || '');
      node.setAttribute('title', value);
      node.setAttribute('aria-label', value);
    });
    const language = String(browserApi?.i18n?.getUILanguage?.() || '').split('-')[0];
    if (language && root.documentElement) {
      root.documentElement.lang = language;
    }
  }

  function secureRandomIndex(maximum) {
    if (maximum <= 1) return 0;
    const limit = Math.floor(0x100000000 / maximum) * maximum;
    const buffer = new Uint32Array(1);
    do {
      globalScope.crypto.getRandomValues(buffer);
    } while (buffer[0] >= limit);
    return buffer[0] % maximum;
  }

  function generatePassword(options = {}) {
    const length = Math.max(12, Math.min(128, Number(options.length || 20)));
    const groups = [
      options.uppercase === false ? '' : 'ABCDEFGHJKLMNPQRSTUVWXYZ',
      options.lowercase === false ? '' : 'abcdefghijkmnopqrstuvwxyz',
      options.numbers === false ? '' : '23456789',
      options.symbols === false ? '' : '!@#$%*-_=+',
    ].filter(Boolean);
    if (!groups.length) {
      throw new Error(t('passwordGeneratorNoCharacters', undefined, 'Mindestens eine Zeichengruppe muss aktiv sein.'));
    }
    const characters = groups.flatMap((group) => group[secureRandomIndex(group.length)]);
    const alphabet = groups.join('');
    while (characters.length < length) {
      characters.push(alphabet[secureRandomIndex(alphabet.length)]);
    }
    for (let index = characters.length - 1; index > 0; index -= 1) {
      const target = secureRandomIndex(index + 1);
      [characters[index], characters[target]] = [characters[target], characters[index]];
    }
    return characters.join('');
  }

  function evaluatePassword(value) {
    const secret = String(value || '');
    const lower = secret.toLowerCase();
    const classes = [
      /[a-z]/.test(secret),
      /[A-Z]/.test(secret),
      /\d/.test(secret),
      /[^A-Za-z0-9]/.test(secret),
    ].filter(Boolean).length;
    let points = 0;
    if (secret.length >= 8) points += 1;
    if (secret.length >= 12) points += 1;
    if (secret.length >= 16) points += 1;
    if (classes >= 3) points += 1;
    if (classes === 4 && secret.length >= 14) points += 1;
    if (/^(.)\1+$/.test(secret) || /(1234|abcd|qwert|password|passwort|admin|letmein)/i.test(lower)) {
      points = Math.min(points, 1);
    }
    if (/(.{2,})\1{2,}/.test(secret)) {
      points = Math.max(0, points - 1);
    }
    const score = Math.max(0, Math.min(4, points - (secret.length ? 1 : 0)));
    const labels = [
      t('strengthVeryWeak', undefined, 'Sehr schwach'),
      t('strengthWeak', undefined, 'Schwach'),
      t('strengthFair', undefined, 'Ausreichend'),
      t('strengthGood', undefined, 'Gut'),
      t('strengthStrong', undefined, 'Stark'),
    ];
    const hints = [];
    if (secret.length < 12) hints.push(t('strengthHintLength', undefined, 'Mindestens 12 Zeichen verwenden.'));
    if (classes < 3) hints.push(t('strengthHintVariety', undefined, 'Mehr Zeichenarten kombinieren.'));
    if (score < 2 && secret.length >= 12) hints.push(t('strengthHintUnique', undefined, 'Vorhersehbare Folgen und Wiederholungen vermeiden.'));
    return {
      score,
      percent: secret ? Math.max(8, (score + 1) * 20) : 0,
      label: labels[score],
      hints,
      length: secret.length,
      classes,
    };
  }

  function passwordAge(changedAt, fallbackAt = '') {
    const date = new Date(changedAt || fallbackAt || '');
    if (Number.isNaN(date.getTime())) {
      return {
        days: null,
        level: 'unknown',
        label: t('passwordAgeUnknown', undefined, 'Alter unbekannt'),
      };
    }
    const days = Math.max(0, Math.floor((Date.now() - date.getTime()) / 86400000));
    if (days <= 90) {
      return {
        days,
        level: 'current',
        label: t('passwordAgeCurrent', [String(days)], `${days} Tage alt`),
      };
    }
    if (days <= 180) {
      return {
        days,
        level: 'aging',
        label: t('passwordAgeAging', [String(days)], `${days} Tage alt, Wechsel empfohlen`),
      };
    }
    return {
      days,
      level: 'old',
      label: t('passwordAgeOld', [String(days)], `${days} Tage alt, bitte erneuern`),
    };
  }

  async function searchVaultEntries(baseUrl, token, query, workspaceId = '', tenantId = 0) {
    const response = await apiRequest(baseUrl, token, 'search.php', {
      params: {
        q: String(query || '').trim(),
        areas: 'vaults',
        workspace_id: workspaceId || '',
        org_id: Number(tenantId || 0) || '',
      },
    });
    return Array.isArray(response?.results?.vaults) ? response.results.vaults : [];
  }

  async function loadVaultEntryDetail(baseUrl, token, entry) {
    const personal = entry?.scope === 'personal' || entry?.personal === true;
    const response = await apiRequest(baseUrl, token, 'vault.php', {
      params: {
        scope: personal ? 'personal' : 'workspace',
        workspace_id: personal ? '' : (entry?.workspaceId || entry?.workspace_id || 0),
        vault_id: entry?.vaultId || entry?.vault_id || 0,
        entry_id: entry?.id || 0,
        org_id: Number(entry?.tenantId || entry?.tenant_id || 0) || '',
      },
    });
    return response?.selectedEntry || null;
  }

  async function loadPasswordHistory(baseUrl, token, entry) {
    const personal = entry?.scope === 'personal' || entry?.personal === true;
    const response = await apiRequest(baseUrl, token, 'vault.php', {
      params: {
        action: 'password-history',
        scope: personal ? 'personal' : 'workspace',
        workspace_id: personal ? '' : (entry?.workspaceId || entry?.workspace_id || 0),
        vault_id: entry?.vaultId || entry?.vault_id || 0,
        entry_id: entry?.id || 0,
        org_id: Number(entry?.tenantId || entry?.tenant_id || 0) || '',
      },
    });
    return {
      entry: response?.entry || null,
      history: Array.isArray(response?.history) ? response.history : [],
    };
  }

  function loadExtensionContext(baseUrl, token, tenantId = 0) {
    return apiRequest(baseUrl, token, 'vault.php', {
      params: {
        action: 'extension-context',
        org_id: Number(tenantId || 0) || '',
      },
    });
  }

  async function saveVaultCredential(baseUrl, token, credential) {
    const entryId = Number(credential?.entryId || credential?.id || 0);
    const action = entryId > 0 ? 'update-entry' : 'create-entry';
    const personal = credential?.scope === 'personal' || credential?.personal === true;
    const response = await apiRequest(baseUrl, token, 'vault.php', {
      method: 'POST',
      params: {
        action,
        org_id: Number(credential?.tenantId || credential?.tenant_id || 0) || '',
      },
      data: {
        scope: personal ? 'personal' : 'workspace',
        workspace_id: personal ? undefined : Number(credential?.workspaceId || 0),
        vault_id: Number(credential?.vaultId || 0),
        entry_id: entryId || undefined,
        kind: 'credential',
        title: String(credential?.title || '').trim(),
        username: String(credential?.username || '').trim(),
        secret: String(credential?.secret || ''),
        url: String(credential?.url || '').trim(),
        notes: String(credential?.notes || '').trim(),
      },
    });
    return response?.selectedEntry || null;
  }

  function buildAppVaultUrl(baseUrl, entry) {
    const url = new URL('index.html', `${normalizeBaseUrl(baseUrl)}/`);
    const personal = entry?.scope === 'personal' || entry?.personal === true;
    const params = new URLSearchParams({
      scope: personal ? 'personal' : 'workspace',
      vault_id: String(entry?.vaultId || entry?.vault_id || 0),
      entry_id: String(entry?.id || 0),
      open: '1',
    });
    if (!personal) {
      params.set('workspace_id', String(entry?.workspaceId || entry?.workspace_id || 0));
    }
    if (Number(entry?.tenantId || entry?.tenant_id || 0) > 0) {
      params.set('org_id', String(entry?.tenantId || entry?.tenant_id));
    }
    url.hash = `/vault?${params.toString()}`;
    return url.toString();
  }

  globalScope.OrbitOneVaultExtension = {
    DEFAULT_SETTINGS,
    SETTINGS_STORAGE_KEY,
    RELEASE_FEED_URL,
    extension,
    normalizeBaseUrl,
    buildApiUrl,
    apiRequest,
    getSettings,
    saveSettings,
    normalizeConnection,
    normalizeSettings,
    configuredConnections,
    activeConnection,
    connectionId,
    tenantConnectionId,
    splitTenantConnectionId,
    tenantDefaults,
    isConfigured,
    parseWebUrl,
    normalizeEntryUrl,
    normalizeHostname,
    isExactDomainMatch,
    isDomainMatch,
    scoreEntryForUrl,
    pageHost,
    trimTo,
    t,
    translateDocument,
    generatePassword,
    evaluatePassword,
    passwordAge,
    searchVaultEntries,
    loadVaultEntryDetail,
    loadPasswordHistory,
    loadExtensionContext,
    saveVaultCredential,
    buildAppVaultUrl,
  };
}(globalThis));
