/**
 * API Explorer admin page (1.0.5)
 */
(function () {
  'use strict';

  if (typeof SwaggerUIBundle === 'undefined') {
    return;
  }

  let ui = null;
  let loadSeq = 0;
  let initialLoadDone = false;
  let explorerContext = {
    is_admin: false,
    permissions: [],
    can_execute: false,
    public_mutating: false,
  };
  let cachedSpec = null;

  const state = {
    scope: 'admin',
    source: 'all',
  };

  function t(key, fallback) {
    if (typeof window.__t === 'function') {
      const value = window.__t(key);
      if (value && value !== key) {
        return value;
      }
    }
    return fallback != null ? fallback : key;
  }

  function sourceLabel(src) {
    const map = {
      core: t('explorer.source.core_label', 'Ядро'),
      module: t('explorer.source.module_label', 'Модуль'),
      plugin: t('explorer.source.plugin_label', 'Плагин'),
      service: t('explorer.source.service_label', 'Сервис'),
    };
    return map[src] || src;
  }

  function getCsrfToken() {
    const meta = document.querySelector('meta[name="csrf-token"]');
    return meta ? meta.getAttribute('content') : '';
  }

  function hasPermission(perm) {
    if (explorerContext.is_admin) {
      return true;
    }
    return explorerContext.permissions.indexOf(perm) !== -1;
  }

  function readUrlState() {
    const params = new URLSearchParams(window.location.search);
    if (params.get('scope')) {
      state.scope = params.get('scope');
    }
    if (params.get('source')) {
      state.source = params.get('source');
    }
  }

  function writeUrlState() {
    const params = new URLSearchParams();
    params.set('scope', state.scope);
    if (state.source && state.source !== 'all') {
      params.set('source', state.source);
    }
    const qs = params.toString();
    const url = qs ? `${window.location.pathname}?${qs}` : window.location.pathname;
    window.history.replaceState({}, '', url);
  }

  function syncControls() {
    document.querySelectorAll('.api-explorer-tab').forEach((btn) => {
      const active = btn.dataset.scope === state.scope;
      btn.classList.toggle('active', active);
      btn.setAttribute('aria-selected', active ? 'true' : 'false');
    });
    document.querySelectorAll('.api-explorer-chip').forEach((btn) => {
      btn.classList.toggle('active', btn.dataset.source === state.source);
    });
    const banner = document.getElementById('api-explorer-public-banner');
    if (banner) {
      banner.hidden = state.scope !== 'public';
    }
  }

  function specUrl() {
    const params = new URLSearchParams({
      scope: state.scope,
      source: state.source,
    });
    return `/api/v1/admin/openapi.json?${params.toString()}`;
  }

  function exportUrl(format) {
    const params = new URLSearchParams({
      scope: state.scope,
      source: state.source,
      format: format,
    });
    return `/api/v1/admin/openapi/export?${params.toString()}`;
  }

  function enrichSpecForDisplay(spec) {
    const paths = spec.paths || {};
    Object.keys(paths).forEach((pathKey) => {
      const methods = paths[pathKey];
      Object.keys(methods).forEach((method) => {
        const op = methods[method];
        if (!op || typeof op !== 'object') {
          return;
        }
        const badges = [];
        const src = op['x-api-source'];
        if (src) {
          let label = sourceLabel(src);
          if (src === 'plugin' && op['x-plugin-name']) {
            label += ': ' + op['x-plugin-name'];
          }
          if (src === 'service' && op['x-target-service']) {
            label += ' → ' + op['x-target-service'];
          }
          badges.push(label);
        }
        const perms = op['x-permissions'];
        if (perms && perms.length) {
          badges.push(t('explorer.badge.requires', 'Требует') + ': ' + perms.join(', '));
        }
        if (op['x-access'] === 'denied') {
          badges.push(t('explorer.badge.no_access', 'Нет прав'));
        }
        if (badges.length) {
          const prefix = '[' + badges.join(' | ') + '] ';
          const summaryText = typeof op.summary === 'string' ? op.summary : '';
          if (!summaryText || summaryText.indexOf(prefix) !== 0) {
            op.summary = prefix + (summaryText || pathKey);
          }
        }
      });
    });
    return spec;
  }

  function findOperationForRequest(request) {
    if (!cachedSpec || !request.url) {
      return null;
    }
    try {
      const url = new URL(request.url, window.location.origin);
      let apiPath = url.pathname;
      if (apiPath.startsWith('/api/')) {
        apiPath = apiPath.slice(4);
      }
      const method = (request.method || 'GET').toLowerCase();
      const paths = cachedSpec.paths || {};
      if (paths[apiPath] && paths[apiPath][method]) {
        return paths[apiPath][method];
      }
      for (const pathKey of Object.keys(paths)) {
        const regex = new RegExp(
          '^' + pathKey.replace(/\{[^}]+\}/g, '[^/]+') + '$'
        );
        if (regex.test(apiPath) && paths[pathKey][method]) {
          return paths[pathKey][method];
        }
      }
    } catch (e) {
      return null;
    }
    return null;
  }

  function showToast(message, isError) {
    let el = document.getElementById('api-explorer-toast');
    if (!el) {
      el = document.createElement('div');
      el.id = 'api-explorer-toast';
      el.setAttribute('role', 'status');
      el.className = 'api-explorer-toast';
      document.querySelector('.api-explorer-wrap').appendChild(el);
    }
    el.textContent = message;
    el.classList.toggle('api-explorer-toast--error', isError);
    el.hidden = false;
    clearTimeout(el._hideTimer);
    el._hideTimer = setTimeout(() => {
      el.hidden = true;
    }, 4000);
  }

  function requestInterceptor(request) {
    request.headers = request.headers || {};
    request.headers['X-API-Explorer'] = '1';

    const method = (request.method || 'GET').toUpperCase();
    const mutating = ['POST', 'PUT', 'PATCH', 'DELETE'].indexOf(method) !== -1;

    if (mutating) {
      const token = getCsrfToken();
      if (token) {
        request.headers['X-CSRF-Token'] = token;
      }
    }

    if (mutating && state.scope === 'public') {
      if (!explorerContext.public_mutating || !explorerContext.can_execute) {
        showToast(
          t(
            'explorer.toast.public_mutating',
            'Public API: mutating запросы отключены. Нужны api_docs_execute и API Key.'
          ),
          true
        );
        throw new Error('public_mutating_blocked');
      }
    }

    const operation = findOperationForRequest(request);
    if (operation && mutating) {
      const required = operation['x-permissions'] || [];
      for (let i = 0; i < required.length; i += 1) {
        if (!hasPermission(required[i])) {
          showToast(
            t('explorer.toast.no_permission', 'Недостаточно прав') + ': ' + required[i],
            true
          );
          throw new Error('permission_denied');
        }
      }
    }

    return request;
  }

  function showLoading(show) {
    const el = document.getElementById('api-explorer-loading');
    if (el) {
      el.hidden = !show;
    }
  }

  async function loadExplorerContext() {
    const response = await fetch('/api/v1/admin/openapi/context', {
      credentials: 'same-origin',
      cache: 'no-store',
    });
    if (!response.ok) {
      throw new Error('context HTTP ' + response.status);
    }
    const data = await response.json();
    explorerContext = {
      is_admin: data.is_admin,
      permissions: data.permissions || [],
      can_execute: data.can_execute,
      public_mutating: data.public_mutating,
    };
  }

  function recreateMountNode() {
    const wrap = document.querySelector('.api-explorer-wrap');
    if (!wrap) {
      return null;
    }
    const existing = document.getElementById('swagger-ui');
    if (existing) {
      existing.remove();
    }
    const node = document.createElement('div');
    node.id = 'swagger-ui';
    node.className = 'api-explorer-swagger';
    wrap.appendChild(node);
    return node;
  }

  function destroySwaggerUi() {
    ui = null;
    const existing = document.getElementById('swagger-ui');
    if (existing) {
      existing.remove();
    }
  }

  function resetEmptyState() {
    const emptyEl = document.getElementById('api-explorer-empty');
    if (!emptyEl) {
      return;
    }
    emptyEl.hidden = true;
    emptyEl.textContent = t(
      'explorer.empty',
      "Нет доступных endpoint'ов для вашей роли или фильтра."
    );
  }

  function showEmptyState(show, message) {
    const emptyEl = document.getElementById('api-explorer-empty');
    if (!emptyEl) {
      return;
    }
    emptyEl.hidden = !show;
    if (show && message) {
      emptyEl.textContent = message;
    }
  }

  function waitForPaint() {
    return new Promise((resolve) => {
      requestAnimationFrame(() => {
        requestAnimationFrame(resolve);
      });
    });
  }

  async function initSwaggerUi(spec) {
    await waitForPaint();
    const mountNode = recreateMountNode();
    if (!mountNode) {
      throw new Error('swagger_mount_missing');
    }
    ui = SwaggerUIBundle({
      spec: JSON.parse(JSON.stringify(spec)),
      domNode: mountNode,
      deepLinking: false,
      persistAuthorization: true,
      presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],
      plugins: [SwaggerUIBundle.plugins.DownloadUrl],
      layout: 'StandaloneLayout',
      requestInterceptor,
    });
  }

  async function loadSpec() {
    const seq = ++loadSeq;
    showLoading(true);
    resetEmptyState();
    writeUrlState();
    syncControls();
    destroySwaggerUi();

    try {
      const response = await fetch(specUrl(), {
        credentials: 'same-origin',
        cache: 'no-store',
      });
      if (seq !== loadSeq) {
        return;
      }
      if (!response.ok) {
        throw new Error('HTTP ' + response.status);
      }
      const spec = await response.json();
      if (seq !== loadSeq) {
        return;
      }
      if (!spec || typeof spec !== 'object' || !spec.openapi) {
        throw new Error('invalid_openapi_spec');
      }

      cachedSpec = enrichSpecForDisplay(JSON.parse(JSON.stringify(spec)));
      const pathCount = Object.keys(cachedSpec.paths || {}).length;

      if (pathCount === 0) {
        showEmptyState(true);
        return;
      }

      showEmptyState(false);
      showLoading(false);
      await initSwaggerUi(cachedSpec);
    } catch (err) {
      if (seq !== loadSeq) {
        return;
      }
      if (err.message !== 'permission_denied' && err.message !== 'public_mutating_blocked') {
        console.error('API Explorer spec load failed', err);
        showEmptyState(
          true,
          t('explorer.load_error', 'Не удалось загрузить спецификацию API.')
        );
      }
    } finally {
      if (seq === loadSeq) {
        showLoading(false);
      }
    }
  }

  function bindControls() {
    document.querySelectorAll('.api-explorer-tab').forEach((btn) => {
      btn.addEventListener('click', () => {
        initialLoadDone = true;
        state.scope = btn.dataset.scope || 'admin';
        loadSpec();
      });
    });
    document.querySelectorAll('.api-explorer-chip').forEach((btn) => {
      btn.addEventListener('click', () => {
        initialLoadDone = true;
        state.source = btn.dataset.source || 'all';
        loadSpec();
      });
    });

    const jsonBtn = document.getElementById('api-explorer-download-json');
    const yamlBtn = document.getElementById('api-explorer-download-yaml');
    if (jsonBtn) {
      jsonBtn.addEventListener('click', () => {
        window.location.href = exportUrl('json');
      });
    }
    if (yamlBtn) {
      yamlBtn.addEventListener('click', () => {
        window.location.href = exportUrl('yaml');
      });
    }
  }

  readUrlState();
  syncControls();
  bindControls();
  loadExplorerContext()
    .then(() => {
      if (!initialLoadDone) {
        initialLoadDone = true;
        return loadSpec();
      }
      return null;
    })
    .catch((err) => {
      console.error('API Explorer context failed', err);
      if (!initialLoadDone) {
        initialLoadDone = true;
        return loadSpec();
      }
      return null;
    });
})();
