// components/apps/overview/migrate/js/migrate-page.js — cross-host Restore.
//
// Moved out of BackupPage (was its "Migrate" tab) into a standalone controller
// so it can live as Overview › Migrate › Restore. Pulls a backup taken on another
// LibrePortal out of a shared backup location and lays it down here: the
// destination's existing copy is snapshotted first (rollback safety), then
// replaced. Self-contained — own data fetch, modal, and task dispatch
// (libreportal restore migrate …). Renders into the container id it's given.
class MigratePage {
  constructor(rootId) {
    this.rootId = rootId || 'ov-migrate-restore';
    this.migrate = null;
    this.locations = null;
    this.hostnameToPeerName = {};
    this.taskManager = (typeof TaskManager !== 'undefined') ? new TaskManager() : null;
    this._ac = new AbortController();
    this._bound = false;
  }

  root() { return document.getElementById(this.rootId); }

  async init() {
    this.bindEvents();
    await this.refreshAll();
    this.render();
  }

  async refreshAll() {
    const ts = Date.now();
    const get = (u) => fetch(u, { cache: 'no-store' }).then((r) => (r.ok ? r.json() : null)).catch(() => null);
    const [migrate, locations, peersData] = await Promise.all([
      get(`/data/backup/generated/migrate.json?t=${ts}`),
      get(`/data/backup/generated/locations.json?t=${ts}`),
      get(`/data/peers/generated/peers.json?t=${ts}`),
    ]);
    this.migrate = migrate;
    this.locations = locations;
    this.hostnameToPeerName = {};
    for (const p of (peersData?.peers || [])) {
      if (p?.config?.hostname) this.hostnameToPeerName[p.config.hostname] = p.name;
    }
  }

  bindEvents() {
    if (this._bound) return;
    this._bound = true;
    // Repaint when a restore/migrate task finishes (no live feed for these).
    if (window.taskRefresh?.register) {
      window.taskRefresh.register({
        id: 'ov-migrate',
        match: (d) => d.action === 'restore' || /^libreportal\s+restore\b/.test((d.task && d.task.command) || ''),
        run: () => { if (window.migratePage === this && this.root()) return this.refreshAll().then(() => this.render()); },
        debounceMs: 600,
      });
    }
    // One AbortController-scoped document listener (the pane is replaced on full
    // navigation; dispose() aborts it on teardown).
    document.addEventListener('click', (e) => {
      const appBtn = e.target.closest('[data-action="migrate-app"]');
      if (appBtn && this.root()?.contains(appBtn)) {
        this.openMigrateModal({ mode: 'app', locIdx: parseInt(appBtn.dataset.loc, 10), host: appBtn.dataset.host, app: appBtn.dataset.app });
        return;
      }
      const hostBtn = e.target.closest('[data-action="migrate-host"]');
      if (hostBtn && this.root()?.contains(hostBtn)) {
        this.openMigrateModal({ mode: 'host', locIdx: parseInt(hostBtn.dataset.loc, 10), host: hostBtn.dataset.host });
        return;
      }
      const locBtn = e.target.closest('[data-action="go-to-locations"]');
      if (locBtn && this.root()?.contains(locBtn)) {
        if (window.navigateToRoute) window.navigateToRoute('/apps/overview/backups/locations');
        return;
      }
      if (e.target.closest('#ov-migrate-confirm')) { this.confirmMigrate(); return; }
      if (e.target.closest('[data-ov-migrate-close]')) { this.closeModal(); return; }
    }, { signal: this._ac.signal });
  }

  dispose() {
    try { this._ac.abort(); } catch (_) {}
    try { window.taskRefresh?.unregister('ov-migrate'); } catch (_) {}
  }

  // ---- helpers (self-contained copies) -------------------------------------

  escape(s) {
    return String(s == null ? '' : s).replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
  }

  formatRelativeTime(iso) {
    if (!iso) return 'never';
    const t = Date.parse(iso);
    if (!t) return iso;
    const diff = Date.now() - t;
    const minute = 60000, hour = 60 * minute, day = 24 * hour;
    if (diff < hour) return `${Math.max(1, Math.round(diff / minute))} min ago`;
    if (diff < day) return `${Math.round(diff / hour)} h ago`;
    if (diff < 7 * day) return `${Math.round(diff / day)} d ago`;
    return new Date(t).toISOString().slice(0, 10);
  }

  locName(idx) {
    const l = (this.locations?.locations || []).find((x) => String(x.idx) === String(idx));
    return l ? l.name : ('location ' + idx);
  }

  closeModal() { document.getElementById('ov-migrate-modal')?.classList.remove('open'); }

  notify(msg, type) {
    if (window.notificationSystem && window.notificationSystem.show) window.notificationSystem.show(msg, type || 'info');
    else if (type === 'error') console.error(msg);
    else console.log(msg);
  }

  async runTask(command, type, app) {
    if (!this.taskManager) { this.notify('Task system unavailable', 'error'); return; }
    try {
      await this.taskManager.createTask(command, type, app);
      setTimeout(() => this.refreshAll().then(() => this.render()), 1500);
    } catch (err) {
      this.notify(`Failed to queue task: ${err.message || err}`, 'error');
    }
  }

  // ---- rendering -----------------------------------------------------------

  render() {
    const body = document.getElementById('ov-migrate-body');
    const empty = document.getElementById('ov-migrate-empty');
    if (!body || !empty) return;

    const data = this.migrate || {};
    const locations = (data.locations || []).filter((l) => (l.hosts || []).length > 0);
    if (!locations.length) {
      body.innerHTML = '';
      empty.hidden = false;
      return;
    }
    empty.hidden = true;

    const installed = new Set(data.destination?.installed_apps || []);
    const html = locations.map((loc) => `
      <div class="backup-migrate-location">
        <div class="backup-card-header" style="margin-bottom:8px">
          <h3 style="margin:0">${this.escape(loc.name || 'Location')}</h3>
          <span class="backup-card-hint">${(loc.hosts || []).length} other host${loc.hosts.length === 1 ? '' : 's'} backing up here</span>
        </div>
        ${loc.hosts.map((host) => {
          const peerName = (this.hostnameToPeerName || {})[host.hostname];
          const headerLabel = peerName
            ? `<strong style="font-size:1.05em">${this.escape(peerName)}</strong><span class="backup-card-hint" style="margin-left:6px; font-size:.85em">host: <code>${this.escape(host.hostname)}</code></span>`
            : `<strong style="font-size:1.05em">${this.escape(host.hostname)}</strong>`;
          return `
          <div class="backup-migrate-host" style="border:1px solid var(--border-color, #2a2a2a); border-radius:8px; padding:14px; margin-bottom:12px">
            <div style="display:flex; justify-content:space-between; align-items:center; gap:12px; margin-bottom:10px">
              <div>
                ${headerLabel}
                <span class="backup-card-hint" style="margin-left:10px">${(host.apps || []).length} app${host.apps.length === 1 ? '' : 's'} available</span>
              </div>
              <button class="backup-primary-btn" data-action="migrate-host" data-loc="${loc.idx}" data-host="${this.escape(host.hostname)}">
                Restore every app from this host
              </button>
            </div>
            <div class="backup-migrate-apps" style="display:grid; grid-template-columns:repeat(auto-fill, minmax(280px, 1fr)); gap:8px">
              ${(host.apps || []).map((app) => {
                const collide = installed.has(app.slug);
                return `
                <div class="backup-migrate-app" style="display:flex; justify-content:space-between; align-items:center; padding:8px 12px; background:var(--surface-2, #1a1a1a); border-radius:6px">
                  <div style="display:flex; flex-direction:column; min-width:0">
                    <span style="display:flex; align-items:center; gap:8px">
                      <strong>${this.escape(app.slug)}</strong>
                      ${collide ? `<span class="backup-status-dot warn" title="Already installed here"></span>` : ''}
                    </span>
                    <span class="backup-card-hint" style="font-size:.82em">
                      ${app.snapshots} snapshot${app.snapshots === 1 ? '' : 's'} · latest ${this.escape(this.formatRelativeTime(app.latest_date))}
                    </span>
                  </div>
                  <button class="backup-secondary-btn" data-action="migrate-app" data-loc="${loc.idx}" data-host="${this.escape(host.hostname)}" data-app="${this.escape(app.slug)}">
                    Restore
                  </button>
                </div>`;
              }).join('')}
            </div>
          </div>`;
        }).join('')}
      </div>`).join('');
    body.innerHTML = html;
  }

  // ---- modal ---------------------------------------------------------------

  openMigrateModal({ mode, locIdx, host, app }) {
    const modal = document.getElementById('ov-migrate-modal');
    const body = document.getElementById('ov-migrate-modal-body');
    if (!modal || !body) return;

    const dest = this.migrate?.destination || {};
    const installed = new Set(dest.installed_apps || []);
    const running = new Set(dest.running_apps || []);
    const locName = this.locName(locIdx);

    let targetApps = [];
    if (mode === 'app') {
      targetApps = [app];
    } else {
      const loc = (this.migrate?.locations || []).find((l) => l.idx === locIdx);
      const h = (loc?.hosts || []).find((x) => x.hostname === host);
      targetApps = (h?.apps || []).map((a) => a.slug);
    }
    const collisions = targetApps.filter((a) => installed.has(a));
    const collisionsRunning = collisions.filter((a) => running.has(a));

    const intro = mode === 'app'
      ? `<p>Restore <strong>${this.escape(app)}</strong> from <strong>${this.escape(host)}</strong> via <strong>${this.escape(locName)}</strong> onto this host.</p>`
      : `<p>Restore <strong>every app</strong> (${targetApps.length}) from <strong>${this.escape(host)}</strong> via <strong>${this.escape(locName)}</strong> onto this host.</p>`;

    let collisionNote = '';
    if (collisions.length) {
      collisionNote = `
        <p class="backup-card-hint" style="color:var(--warning, #d97706); margin-top:8px">
          ⚠ Already installed here: ${collisions.map((c) => `<code>${this.escape(c)}</code>`).join(', ')}.
          These will be <strong>replaced</strong>.
          ${collisionsRunning.length ? `Currently running: ${collisionsRunning.map((c) => `<code>${this.escape(c)}</code>`).join(', ')} — will be stopped first.` : ''}
        </p>`;
    }

    body.innerHTML = `
      ${intro}
      ${collisionNote}
      <div style="margin-top:14px; display:flex; flex-direction:column; gap:8px">
        <label style="display:flex; align-items:flex-start; gap:8px; cursor:pointer">
          <input type="checkbox" id="ov-migrate-opt-pre-backup" ${collisions.length ? 'checked' : 'disabled'}>
          <span>
            Back up the destination's existing copy first
            <span class="backup-card-hint" style="display:block; font-size:.85em">
              Safety net: snapshot the current ${mode === 'app' ? this.escape(app) : 'app'} into your first
              enabled backup location (tagged <code>pre-migrate</code>) before wipe.
              ${collisions.length ? '' : 'No collision — nothing to back up.'}
            </span>
          </span>
        </label>
        <label style="display:flex; align-items:flex-start; gap:8px; cursor:pointer">
          <input type="checkbox" id="ov-migrate-opt-rewrite-urls" checked>
          <span>
            Rewrite host-bound URLs to this host
            <span class="backup-card-hint" style="display:block; font-size:.85em">
              Replaces <code>CFG_*_URL</code>, <code>*_DOMAIN</code>, <code>*_HOSTNAME</code> with this
              host's values. Uncheck only if you want the moved app to keep claiming the source's hostname.
            </span>
          </span>
        </label>
      </div>`;
    modal.dataset.mode = mode;
    modal.dataset.locIdx = String(locIdx);
    modal.dataset.host = host;
    modal.dataset.app = app || '';
    modal.classList.add('open');
  }

  async confirmMigrate() {
    const modal = document.getElementById('ov-migrate-modal');
    if (!modal) return;
    const { mode, locIdx, host, app } = modal.dataset;
    const preBackup = document.getElementById('ov-migrate-opt-pre-backup')?.checked;
    const rewrite = document.getElementById('ov-migrate-opt-rewrite-urls')?.checked;

    const opts = [];
    if (preBackup === false) opts.push('--no-pre-backup');
    if (rewrite === false) opts.push('--keep-urls');
    const optStr = opts.length ? ' ' + opts.join(' ') : '';

    this.closeModal();
    if (mode === 'app') {
      await this.runTask(`libreportal restore migrate app ${app} ${host} ${locIdx}${optStr}`, 'restore', app);
    } else {
      await this.runTask(`libreportal restore migrate system ${host} ${locIdx}${optStr}`, 'restore', null);
    }
  }
}

window.MigratePage = MigratePage;
