/**
 * Страница «Маркет» на Vue 3 без сборки.
 */
(function () {
  'use strict';

  if (typeof Vue === 'undefined' || typeof VueI18n === 'undefined') {
    return;
  }

  var h = Vue.h;

  function request(path, options) {
    var opts = options || {};
    var url = path.startsWith('http') ? path : (window.location.origin || '') + path;
    return fetch(url, {
      method: opts.method || 'GET',
      credentials: 'same-origin',
      headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
      body: opts.body ? JSON.stringify(opts.body) : undefined
    })
      .then(function (resp) { return resp.text().then(function (text) { return { resp: resp, text: text }; }); })
      .then(function (ctx) {
        var data = null;
        try { data = ctx.text ? JSON.parse(ctx.text) : null; } catch (e) { data = null; }
        if (!ctx.resp.ok || (data && data.success === false)) {
          return { success: false, error: (data && data.error) || ('HTTP ' + ctx.resp.status), data: data, status: ctx.resp.status };
        }
        return { success: true, data: data || {}, status: ctx.resp.status };
      })
      .catch(function (err) {
        return { success: false, error: err.message || 'Network error' };
      });
  }

  function apiGet(path) { return request(path, { method: 'GET' }); }
  function apiPost(path, body) { return request(path, { method: 'POST', body: body || {} }); }
  function apiPut(path, body) { return request(path, { method: 'PUT', body: body || {} }); }

  var locale = (function () {
    try { return localStorage.getItem('cascad360_admin_locale') || 'ru'; } catch (e) { return 'ru'; }
  })();

  var messages = {
    ru: {
      market: {
        title: 'Маркет', check: 'Обновить каталог', search: 'Поиск',
        all_types: 'Все типы', plugins: 'Плагины', modules: 'Модули',
        all_statuses: 'Все статусы', all_pricing: 'Все модели',
        pricing_free: 'Бесплатно', pricing_paid: 'Платно', pricing_subscription: 'Подписка',
        status_not_installed: 'Не установлено', status_installed: 'Установлено',
        status_update_available: 'Доступно обновление', status_blocked: 'Заблокировано',
        status_in_progress: 'В процессе', install: 'Установить', update: 'Обновить',
        rollback: 'Откатить', blocked: 'Заблокировано', details: 'Подробнее',
        jobs: 'Задачи', history: 'История',         access_denied: 'Маркет недоступен: требуется активная подписка.',
        access_browse_hint: 'Просмотр каталога доступен. Установка и обновление требуют активной подписки.',
        dry_run_title: 'Dry-run отчёт', dry_run_confirm: 'Подтвердить установку',
        dry_run_cancel: 'Отмена', trust: 'Доверие', source: 'Источник',
        changelog: 'Changelog', pricing: 'Модель', loading: 'Загрузка...',
        empty_catalog: 'Каталог пуст', retry: 'Повторить',
        rollback_title: 'Откат версии', rollback_reason: 'Причина отката',
        rollback_confirm: 'Подтверждаю откат', rollback_target: 'Целевая версия',
        rollback_history: 'История откатов', rollback_select: 'Выберите версию',
        screenshots: 'Скриншоты', no_screenshots: 'Скриншоты отсутствуют',
        progress: 'Прогресс', job_details: 'Детали задачи', job_steps: 'Шаги'
      }
    },
    en: {
      market: {
        title: 'Market', check: 'Refresh catalog', search: 'Search',
        all_types: 'All types', plugins: 'Plugins', modules: 'Modules',
        all_statuses: 'All statuses', all_pricing: 'All pricing models',
        pricing_free: 'Free', pricing_paid: 'Paid', pricing_subscription: 'Subscription',
        status_not_installed: 'Not installed', status_installed: 'Installed',
        status_update_available: 'Update available', status_blocked: 'Blocked',
        status_in_progress: 'In progress', install: 'Install', update: 'Update',
        rollback: 'Rollback', blocked: 'Blocked', details: 'Details',
        jobs: 'Jobs', history: 'History',         access_denied: 'Market is unavailable without active subscription.',
        access_browse_hint: 'Catalog browsing is available. Install and update require an active subscription.',
        dry_run_title: 'Dry-run report', dry_run_confirm: 'Confirm install',
        dry_run_cancel: 'Cancel', trust: 'Trust', source: 'Source',
        changelog: 'Changelog', pricing: 'Pricing', loading: 'Loading...',
        empty_catalog: 'Catalog is empty', retry: 'Retry',
        rollback_title: 'Rollback version', rollback_reason: 'Rollback reason',
        rollback_confirm: 'Confirm rollback', rollback_target: 'Target version',
        rollback_history: 'Rollback history', rollback_select: 'Select version',
        screenshots: 'Screenshots', no_screenshots: 'No screenshots',
        progress: 'Progress', job_details: 'Job details', job_steps: 'Steps'
      }
    }
  };

  var i18n = VueI18n.createI18n({ legacy: false, locale: locale, fallbackLocale: 'ru', messages: messages });
  var mountEl = document.getElementById('app-market');
  if (!mountEl) return;

  function toQuery(params) {
    var pairs = [];
    Object.keys(params || {}).forEach(function (key) {
      var value = params[key];
      if (value === null || value === undefined || value === '') return;
      pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
    });
    return pairs.length ? ('?' + pairs.join('&')) : '';
  }

  var app = Vue.createApp({
    data: function () {
      return {
        loading: false,
        error: null,
        channel: 'stable',
        q: '',
        artifactType: '',
        installStatus: '',
        pricingModel: '',
        page: 1,
        perPage: 20,
        items: [],
        pagination: null,
        jobs: [],
        rollbacks: [],
        access: null,
        selectedItem: null,
        dryRunReport: null,
        dryRunItem: null,
        rollbackItem: null,
        rollbackForm: { target_version: '', reason: '', confirm_rollback: false },
        rollbackPoints: [],
        selectedJob: null,
        pollTimer: null,
        readOnly: false
      };
    },
    methods: {
      t: function (key) { return this.$t(key); },
      statusBadgeClass: function (status) {
        if (status === 'blocked') return 'bg-danger';
        if (status === 'in_progress') return 'bg-info';
        if (status === 'update_available') return 'bg-warning text-dark';
        if (status === 'installed') return 'bg-success';
        return 'bg-secondary';
      },
      loadAccess: function () {
        var self = this;
        return apiGet('/api/v1/admin/market/access-status').then(function (res) {
          if (!res.success) { self.error = res.error || 'Access status error'; return false; }
          self.access = (res.data || {}).access || { allowed: false };
          return !!self.access.allowed;
        });
      },
      loadCatalog: function () {
        var self = this;
        self.loading = true;
        self.error = null;
        var params = {
          channel: self.channel, q: self.q, artifact_type: self.artifactType,
          install_status: self.installStatus, pricing_model: self.pricingModel,
          page: self.page, per_page: self.perPage
        };
        return apiGet('/api/v1/admin/market/catalog' + toQuery(params)).then(function (res) {
          self.loading = false;
          if (!res.success) {
            self.error = res.error || 'Catalog load error';
            self.items = [];
            return;
          }
          var data = res.data || {};
          self.items = data.items || [];
          self.pagination = data.pagination || null;
        });
      },
      loadJobs: function () {
        var self = this;
        return apiGet('/api/v1/admin/market/jobs').then(function (res) {
          if (!res.success) return;
          self.jobs = (res.data && res.data.jobs) || [];
          self.syncJobPolling();
        });
      },
      loadHistory: function () {
        var self = this;
        return apiGet('/api/v1/admin/market/history?limit=50').then(function (res) {
          if (!res.success) return;
          self.jobs = (res.data && res.data.jobs) || self.jobs;
          self.rollbacks = (res.data && res.data.rollbacks) || [];
          self.syncJobPolling();
        });
      },
      isActiveJobStatus: function (status) {
        var s = String(status || '').toLowerCase();
        return s === 'pending' || s === 'running' || s === 'in_progress' || s === 'queued';
      },
      isTerminalJobStatus: function (status) {
        return !this.isActiveJobStatus(status);
      },
      syncJobPolling: function () {
        var self = this;
        var hasActive = (self.jobs || []).some(function (job) {
          return self.isActiveJobStatus(job.status);
        });
        if (hasActive && !self.pollTimer) {
          self.pollTimer = setInterval(function () { self.pollActiveJobs(); }, 4000);
        } else if (!hasActive && self.pollTimer) {
          clearInterval(self.pollTimer);
          self.pollTimer = null;
        }
      },
      pollActiveJobs: function () {
        var self = this;
        var active = (self.jobs || []).filter(function (job) {
          return self.isActiveJobStatus(job.status) && job.id;
        });
        if (!active.length) {
          self.syncJobPolling();
          return;
        }
        Promise.all(active.map(function (job) {
          return apiGet('/api/v1/admin/market/jobs/' + encodeURIComponent(job.id)).then(function (res) {
            if (!res.success || !(res.data && res.data.job)) return null;
            return res.data.job;
          });
        })).then(function (updates) {
          updates.forEach(function (updated) {
            if (!updated) return;
            var idx = self.jobs.findIndex(function (j) { return j.id === updated.id; });
            if (idx >= 0) {
              self.jobs.splice(idx, 1, updated);
            }
            if (self.selectedJob && self.selectedJob.id === updated.id) {
              self.selectedJob = updated;
            }
          });
          self.syncJobPolling();
        });
      },
      openJobDetail: function (job) {
        var self = this;
        if (!job || !job.id) return;
        apiGet('/api/v1/admin/market/jobs/' + encodeURIComponent(job.id)).then(function (res) {
          if (res.success && res.data && res.data.job) {
            self.selectedJob = res.data.job;
          } else {
            self.selectedJob = job;
          }
        });
      },
      closeJobDetail: function () { this.selectedJob = null; },
      loadRollbackPoints: function (item) {
        var self = this;
        var params = {
          limit: 50,
          artifact_id: item.artifact_id || '',
          artifact_type: item.artifact_type || ''
        };
        return apiGet('/api/v1/admin/market/history' + toQuery(params)).then(function (res) {
          if (!res.success) {
            self.rollbackPoints = [];
            return;
          }
          self.rollbackPoints = (res.data && res.data.rollbacks) || [];
        });
      },
      refreshAll: function () {
        var self = this;
        self.loadAccess().then(function () {
          self.loadCatalog();
          self.loadHistory();
        });
      },
      openDetails: function (item) { this.selectedItem = item; },
      closeDetails: function () { this.selectedItem = null; },
      closeDryRun: function () { this.dryRunReport = null; this.dryRunItem = null; },
      closeRollback: function () {
        this.rollbackItem = null;
        this.rollbackPoints = [];
      },
      openRollback: function (item) {
        var self = this;
        this.rollbackItem = item;
        this.rollbackForm = {
          target_version: item.installed_version || item.latest_version || '',
          reason: '',
          confirm_rollback: false
        };
        this.loadRollbackPoints(item).then(function () {
          var versions = (self.rollbackPoints || []).map(function (rp) {
            return String(rp.to_version || '').trim();
          }).filter(Boolean);
          if (versions.length && versions.indexOf(self.rollbackForm.target_version) < 0) {
            self.rollbackForm.target_version = versions[0];
          }
        });
      },
      submitRollback: function () {
        var self = this;
        var item = this.rollbackItem;
        if (!item) return;
        return apiPost('/api/v1/admin/market/rollback', {
          artifact_id: item.artifact_id,
          artifact_type: item.artifact_type,
          target_version: self.rollbackForm.target_version,
          reason: self.rollbackForm.reason,
          confirm_rollback: !!self.rollbackForm.confirm_rollback
        }).then(function (res) {
          if (!res.success) { self.error = res.error || 'Rollback failed'; return; }
          self.error = null;
          self.closeRollback();
          self.refreshAll();
        });
      },
      runInstallOrUpdate: function (item, confirmThirdParty) {
        var self = this;
        var endpoint = (item.install_status === 'installed' || item.install_status === 'update_available')
          ? '/api/v1/admin/market/update' : '/api/v1/admin/market/install';
        var body = {
          artifact_id: item.artifact_id,
          artifact_type: item.artifact_type,
          version: item.target_version || item.latest_version,
          confirm_third_party: !!confirmThirdParty
        };
        return apiPost(endpoint, body).then(function (res) {
          if (!res.success) {
            var blockReason = (((res.data || {}).block_reason) || '');
            var dryReport = ((res.data || {}).dry_run_report) || null;
            if (blockReason === 'dry_run_confirmation_required' || (res.status === 409 && dryReport)) {
              self.dryRunReport = dryReport || {};
              self.dryRunItem = item;
              return;
            }
            self.error = res.error || 'Install/update failed';
            return;
          }
          self.error = null;
          self.closeDryRun();
          self.refreshAll();
        });
      },
      confirmDryRun: function () {
        if (!this.dryRunItem) return;
        this.runInstallOrUpdate(this.dryRunItem, true);
      },
      runDryRunPreview: function (item) {
        var self = this;
        return apiPost('/api/v1/admin/market/dry-run', {
          artifact_id: item.artifact_id,
          artifact_type: item.artifact_type,
          version: item.target_version || item.latest_version
        }).then(function (res) {
          if (!res.success) { self.error = res.error || 'Dry-run failed'; return; }
          self.dryRunReport = (res.data || {}).dry_run_report || {};
          self.dryRunItem = item;
        });
      },
      markPublisherTrust: function (item, trustProfile) {
        var self = this;
        var publisherId = (item.publisher || {}).id;
        if (!publisherId) return;
        apiPut('/api/v1/admin/market/publishers/' + encodeURIComponent(publisherId) + '/trust-profile', {
          trust_profile: trustProfile
        }).then(function () { self.refreshAll(); });
      },
      canManage: function () {
        return !!(this.access && this.access.allowed) && !(this.readOnly);
      },
    },
    mounted: function () {
      this.readOnly = !!(window.__MARKET_READ_ONLY__);
      this.refreshAll();
    },
    beforeUnmount: function () {
      if (this.pollTimer) {
        clearInterval(this.pollTimer);
        this.pollTimer = null;
      }
    },
    render: function () {
      var self = this;
      var t = this.t;

      var accessBanner = null;
      if (this.access && !this.access.allowed) {
        var accessMsg = this.items && this.items.length
          ? t('market.access_browse_hint')
          : t('market.access_denied');
        accessBanner = h('div', { class: 'alert alert-warning mb-3' }, [
          h('strong', accessMsg),
          this.access.reason ? h('div', { class: 'small mt-1 text-muted' }, 'reason: ' + String(this.access.reason)) : null
        ]);
      }

      var tableRows = (this.items || []).map(function (item) {
        var status = item.install_status || 'not_installed';
        var canRun = status !== 'blocked' && status !== 'in_progress' && self.canManage();
        var canRollback = (status === 'installed' || status === 'update_available') && self.canManage();
        return h('tr', { key: (item.artifact_id || '') + ':' + (item.artifact_type || '') }, [
          h('td', [
            h('div', { class: 'fw-semibold' }, String(item.title || item.artifact_id || '')),
            h('div', { class: 'text-muted small' }, String(item.description || ''))
          ]),
          h('td', String(item.artifact_type || '')),
          h('td', String(item.pricing_model || 'free')),
          h('td', String(item.latest_version || '')),
          h('td', String(item.installed_version || '—')),
          h('td', [h('span', { class: 'badge ' + self.statusBadgeClass(status) }, status)]),
          h('td', String(((item.publisher || {}).trust_profile) || 'unverified')),
          h('td', { class: 'text-end' }, [
            h('button', { type: 'button', class: 'btn btn-outline-secondary btn-sm me-1', onClick: function () { self.openDetails(item); } }, t('market.details')),
            canRun ? h('button', { type: 'button', class: 'btn btn-outline-primary btn-sm me-1', onClick: function () { self.runInstallOrUpdate(item, false); } },
              status === 'update_available' || status === 'installed' ? t('market.update') : t('market.install')) : null,
            canRollback ? h('button', { type: 'button', class: 'btn btn-outline-warning btn-sm', onClick: function () { self.openRollback(item); } }, t('market.rollback')) : null
          ])
        ]);
      });

      if (self.loading && !tableRows.length) {
        tableRows.push(h('tr', [h('td', { colspan: 8, class: 'text-center text-muted py-4' }, t('market.loading'))]));
      } else if (!tableRows.length) {
        tableRows.push(h('tr', [h('td', { colspan: 8, class: 'text-center text-muted py-4' }, t('market.empty_catalog'))]));
      }

      var detailsModal = null;
      if (this.selectedItem) {
        var item = this.selectedItem;
        detailsModal = h('div', { class: 'card mt-3 border-info' }, [
          h('div', { class: 'card-header d-flex justify-content-between' }, [
            h('span', String(item.title || item.artifact_id || '')),
            h('button', { type: 'button', class: 'btn-close', onClick: this.closeDetails })
          ]),
          h('div', { class: 'card-body' }, [
            h('p', String(item.description || '')),
            h('div', [h('strong', t('market.pricing') + ': '), String(item.pricing_model || 'free')]),
            h('div', [h('strong', t('market.trust') + ': '), String(((item.publisher || {}).trust_profile) || 'unverified')]),
            h('div', [h('strong', t('market.source') + ': '), String(((item.source || {}).host) || '—')]),
            item.changelog ? h('pre', { class: 'mt-2 small bg-light p-2' }, String(item.changelog)) : null,
            h('div', { class: 'mt-3' }, [
              h('strong', t('market.screenshots') + ': '),
              (item.screenshots || []).length
                ? h('div', { class: 'row g-2 mt-2' }, (item.screenshots || []).map(function (url, idx) {
                    return h('div', { class: 'col-6 col-md-4', key: 'ss-' + idx }, [
                      h('img', {
                        src: String(url),
                        alt: String(item.title || item.artifact_id || ''),
                        loading: 'lazy',
                        class: 'img-fluid rounded border',
                        style: 'max-height:160px;object-fit:cover;width:100%;'
                      })
                    ]);
                  }))
                : h('div', { class: 'text-muted small mt-1' }, t('market.no_screenshots'))
            ]),
            self.canManage() ? h('div', { class: 'mt-3 d-flex gap-2' }, [
              h('button', { type: 'button', class: 'btn btn-sm btn-outline-primary', onClick: function () { self.runDryRunPreview(item); } }, t('market.dry_run_title')),
              h('button', { type: 'button', class: 'btn btn-sm btn-outline-success', onClick: function () { self.markPublisherTrust(item, 'trusted'); } }, 'Trust'),
              h('button', { type: 'button', class: 'btn btn-sm btn-outline-danger', onClick: function () { self.markPublisherTrust(item, 'blocked'); } }, 'Block')
            ]) : null
          ])
        ]);
      }

      var dryRunModal = null;
      if (this.dryRunReport && this.dryRunItem) {
        var report = this.dryRunReport;
        dryRunModal = h('div', { class: 'card mt-3 border-warning' }, [
          h('div', { class: 'card-header d-flex justify-content-between' }, [
            h('span', t('market.dry_run_title')),
            h('button', { type: 'button', class: 'btn-close', onClick: this.closeDryRun })
          ]),
          h('div', { class: 'card-body' }, [
            h('pre', { class: 'small bg-light p-2' }, JSON.stringify(report, null, 2)),
            self.canManage() ? h('div', { class: 'mt-2 d-flex gap-2' }, [
              h('button', { type: 'button', class: 'btn btn-warning btn-sm', onClick: this.confirmDryRun }, t('market.dry_run_confirm')),
              h('button', { type: 'button', class: 'btn btn-secondary btn-sm', onClick: this.closeDryRun }, t('market.dry_run_cancel'))
            ]) : null
          ])
        ]);
      }

      var rollbackModal = null;
      if (this.rollbackItem) {
        rollbackModal = h('div', { class: 'card mt-3 border-warning' }, [
          h('div', { class: 'card-header d-flex justify-content-between' }, [
            h('span', t('market.rollback_title')),
            h('button', { type: 'button', class: 'btn-close', onClick: this.closeRollback })
          ]),
          h('div', { class: 'card-body' }, [
            h('label', { class: 'form-label' }, t('market.rollback_target')),
            (self.rollbackPoints || []).length
              ? h('select', {
                  class: 'form-select form-select-sm mb-2',
                  value: self.rollbackForm.target_version,
                  onChange: function (e) { self.rollbackForm.target_version = e.target.value || ''; }
                }, (self.rollbackPoints || []).map(function (rp, idx) {
                  var ver = String(rp.to_version || '');
                  return h('option', { key: 'rbv-' + idx, value: ver }, ver + (rp.from_version ? (' ← ' + rp.from_version) : ''));
                }))
              : h('input', { type: 'text', class: 'form-control form-control-sm mb-2', value: self.rollbackForm.target_version,
                  onInput: function (e) { self.rollbackForm.target_version = e.target.value; } }),
            h('label', { class: 'form-label' }, t('market.rollback_reason')),
            h('textarea', { class: 'form-control form-control-sm mb-2', rows: 3, value: self.rollbackForm.reason,
              onInput: function (e) { self.rollbackForm.reason = e.target.value; } }),
            h('div', { class: 'form-check mb-2' }, [
              h('input', { type: 'checkbox', class: 'form-check-input', id: 'rb-confirm', checked: self.rollbackForm.confirm_rollback,
                onChange: function (e) { self.rollbackForm.confirm_rollback = e.target.checked; } }),
              h('label', { class: 'form-check-label', for: 'rb-confirm' }, t('market.rollback_confirm'))
            ]),
            h('button', { type: 'button', class: 'btn btn-warning btn-sm', onClick: this.submitRollback }, t('market.rollback'))
          ])
        ]);
      }

      return h('div', { class: 'admin-page' }, [
        h('h1', { class: 'mb-3' }, [h('i', { class: 'bi bi-shop me-2', 'aria-hidden': 'true' }), t('market.title')]),
        accessBanner,
        this.error ? h('div', { class: 'alert alert-danger d-flex justify-content-between' }, [
          h('span', this.error),
          h('button', { type: 'button', class: 'btn btn-sm btn-outline-danger', onClick: this.refreshAll }, t('market.retry'))
        ]) : null,
        h('div', { class: 'card mb-3' }, [
          h('div', { class: 'card-body d-flex gap-2 align-items-center flex-wrap' }, [
            h('input', { type: 'text', class: 'form-control form-control-sm', placeholder: t('market.search'), value: this.q,
              onInput: function (e) { self.q = e.target.value || ''; } }),
            h('select', { class: 'form-select form-select-sm', value: this.artifactType,
              onChange: function (e) { self.artifactType = e.target.value || ''; } }, [
              h('option', { value: '' }, t('market.all_types')),
              h('option', { value: 'plugin' }, t('market.plugins')),
              h('option', { value: 'module' }, t('market.modules'))
            ]),
            h('select', { class: 'form-select form-select-sm', value: this.pricingModel,
              onChange: function (e) { self.pricingModel = e.target.value || ''; } }, [
              h('option', { value: '' }, t('market.all_pricing')),
              h('option', { value: 'free' }, t('market.pricing_free')),
              h('option', { value: 'paid' }, t('market.pricing_paid')),
              h('option', { value: 'subscription' }, t('market.pricing_subscription'))
            ]),
            h('select', { class: 'form-select form-select-sm', value: this.installStatus,
              onChange: function (e) { self.installStatus = e.target.value || ''; } }, [
              h('option', { value: '' }, t('market.all_statuses')),
              h('option', { value: 'not_installed' }, t('market.status_not_installed')),
              h('option', { value: 'installed' }, t('market.status_installed')),
              h('option', { value: 'update_available' }, t('market.status_update_available')),
              h('option', { value: 'in_progress' }, t('market.status_in_progress')),
              h('option', { value: 'blocked' }, t('market.status_blocked'))
            ]),
            h('button', { type: 'button', class: 'btn btn-primary btn-sm', disabled: this.loading, onClick: this.refreshAll }, t('market.check'))
          ])
        ]),
        h('div', { class: 'card' }, [
          h('div', { class: 'card-header d-flex justify-content-between' }, [
            h('span', 'Каталог'),
            h('span', { class: 'text-muted small' }, this.pagination ? ('Всего: ' + this.pagination.total) : '')
          ]),
          h('div', { class: 'card-body p-0' }, [
            h('div', { class: 'table-responsive' }, [
              h('table', { class: 'table table-sm mb-0' }, [
                h('thead', [h('tr', [
                  h('th', 'Продукт'), h('th', 'Тип'), h('th', t('market.pricing')),
                  h('th', 'Latest'), h('th', 'Installed'), h('th', 'Статус'), h('th', t('market.trust')),
                  h('th', { class: 'text-end' }, 'Действия')
                ])]),
                h('tbody', tableRows)
              ])
            ])
          ])
        ]),
        h('div', { class: 'card mt-3' }, [
          h('div', { class: 'card-header' }, t('market.history')),
          h('div', { class: 'card-body p-0' }, [
            h('div', { class: 'table-responsive' }, [
              h('table', { class: 'table table-sm mb-0' }, [
                h('thead', [h('tr', [
                  h('th', 'ID'), h('th', 'Операция'), h('th', 'Артефакт'), h('th', 'Версия'),
                  h('th', t('market.progress')), h('th', 'Статус'), h('th', 'Ошибка')
                ])]),
                h('tbody', (this.jobs || []).length ? this.jobs.slice(0, 20).map(function (job) {
                  var pct = Math.max(0, Math.min(100, parseInt(job.progress_percent, 10) || 0));
                  return h('tr', {
                    key: job.id,
                    style: 'cursor:pointer',
                    onClick: function () { self.openJobDetail(job); }
                  }, [
                    h('td', String(job.id || '')),
                    h('td', String(job.operation || '')),
                    h('td', String(job.artifact_id || '')),
                    h('td', String(job.target_version || '')),
                    h('td', [
                      h('div', { class: 'progress', style: 'height:18px;min-width:80px;' }, [
                        h('div', {
                          class: 'progress-bar' + (self.isActiveJobStatus(job.status) ? ' progress-bar-striped progress-bar-animated' : ''),
                          role: 'progressbar',
                          style: 'width:' + pct + '%',
                          'aria-valuenow': pct,
                          'aria-valuemin': 0,
                          'aria-valuemax': 100
                        }, pct + '%')
                      ])
                    ]),
                    h('td', String(job.status || '')),
                    h('td', { class: 'small text-danger' }, String((job.error || (job.details && job.details.error) || job.message) || ''))
                  ]);
                }) : [h('tr', [h('td', { colspan: 7, class: 'text-center text-muted py-3' }, 'Нет задач')])])
              ])
            ])
          ])
        ]),
        h('div', { class: 'card mt-3' }, [
          h('div', { class: 'card-header' }, t('market.rollback_history')),
          h('div', { class: 'card-body p-0' }, [
            h('div', { class: 'table-responsive' }, [
              h('table', { class: 'table table-sm mb-0' }, [
                h('thead', [h('tr', [
                  h('th', 'Артефакт'), h('th', 'Тип'), h('th', 'From'), h('th', 'To'),
                  h('th', 'Причина'), h('th', 'Дата')
                ])]),
                h('tbody', (this.rollbacks || []).length ? this.rollbacks.slice(0, 20).map(function (rb, idx) {
                  return h('tr', { key: 'rb-' + idx }, [
                    h('td', String(rb.artifact_id || '')),
                    h('td', String(rb.artifact_type || '')),
                    h('td', String(rb.from_version || '')),
                    h('td', String(rb.to_version || '')),
                    h('td', { class: 'small' }, String(rb.reason || '')),
                    h('td', { class: 'small text-muted' }, String(rb.created_at || ''))
                  ]);
                }) : [h('tr', [h('td', { colspan: 6, class: 'text-center text-muted py-3' }, 'Нет записей')])])
              ])
            ])
          ])
        ]),
        detailsModal, dryRunModal, rollbackModal,
        self.selectedJob ? h('div', { class: 'card mt-3 border-primary' }, [
          h('div', { class: 'card-header d-flex justify-content-between' }, [
            h('span', t('market.job_details') + ': ' + String(self.selectedJob.id || '')),
            h('button', { type: 'button', class: 'btn-close', onClick: self.closeJobDetail })
          ]),
          h('div', { class: 'card-body' }, [
            h('div', [h('strong', 'Status: '), String(self.selectedJob.status || '')]),
            h('div', [h('strong', t('market.progress') + ': '), String(self.selectedJob.progress_percent || 0) + '%']),
            self.selectedJob.message ? h('div', { class: 'mt-2' }, [h('strong', 'Message: '), String(self.selectedJob.message)]) : null,
            self.selectedJob.details ? h('div', { class: 'mt-2' }, [
              h('strong', t('market.job_steps') + ':'),
              h('pre', { class: 'small bg-light p-2 mt-1' }, JSON.stringify(self.selectedJob.details, null, 2))
            ]) : null
          ])
        ]) : null
      ]);
    }
  });

  app.use(i18n);
  app.mount(mountEl);

  window.addEventListener('admin-locale-changed', function (e) {
    var nextLocale = e.detail && e.detail.locale;
    if (nextLocale && (nextLocale === 'ru' || nextLocale === 'en')) {
      i18n.global.locale.value = nextLocale;
    }
  });
})();
