/**
 * ACC Dev Portal client
 */
(function () {
  const cfg = window.ACC_DEV_CFG || {};
  const root = document.getElementById('dev-root');
  if (!root) return;

  const state = {
    tab: cfg.initialTab || 'docs',
    sections: [],
    activeSectionId: null,
    assets: [],
    catalog: { items: [], selected: null, detail: null },
    codeCatalog: {
      items: [],
      selected: null,
      detail: null,
      stats: null,
      critical: [],
      portals: [],
      kinds: [],
      excludeTiles: false,
    },
    backups: { code: [], db: [] },
    doc: cfg.doc || null,
  };

  function esc(s) {
    return String(s ?? '')
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;');
  }

  function bytes(n) {
    n = Number(n) || 0;
    if (n < 1024) return n + ' B';
    if (n < 1048576) return (n / 1024).toFixed(1) + ' KB';
    if (n < 1073741824) return (n / 1048576).toFixed(2) + ' MB';
    return (n / 1073741824).toFixed(2) + ' GB';
  }

  async function api(action, body = {}) {
    const fd = new FormData();
    fd.append('action', action);
    fd.append('csrf', cfg.csrf || '');
    Object.entries(body).forEach(([k, v]) => {
      if (v === undefined || v === null) return;
      fd.append(k, typeof v === 'boolean' ? (v ? '1' : '0') : String(v));
    });
    const res = await fetch(cfg.endpoint || location.pathname, {
      method: 'POST',
      body: fd,
      credentials: 'same-origin',
      headers: { Accept: 'application/json', 'X-Requested-With': 'XMLHttpRequest' },
    });
    const data = await res.json().catch(() => ({ ok: false, message: 'پاسخ نامعتبر' }));
    if (!res.ok && !data.message) data.message = 'خطای سرور (' + res.status + ')';
    return data;
  }

  function setStatus(el, msg, kind) {
    if (!el) return;
    el.hidden = !msg;
    el.textContent = msg || '';
    el.classList.remove('is-error', 'is-ok');
    if (kind === 'error') el.classList.add('is-error');
    if (kind === 'ok') el.classList.add('is-ok');
  }

  function tabsHtml() {
    const tabs = [
      ['docs', 'مرکز مستندات تیم'],
      ['code_catalog', 'کاتالوگ کد'],
      ['code', 'پشتیبان‌گیری کد'],
      ['db', 'پشتیبان‌گیری دیتابیس'],
      ['catalog', 'کاتالوگ دیتابیس'],
      ['guide', 'راهنمای توسعه‌دهنده'],
    ];
    return tabs
      .map(
        ([id, label]) =>
          `<button type="button" class="dev-tab ${state.tab === id ? 'is-active' : ''}" data-tab="${id}">${label}</button>`
      )
      .join('');
  }

  function renderShell(inner) {
    const user = cfg.user || {};
    root.innerHTML = `
      <div class="dev-shell">
        <header class="dev-header">
          <a class="dev-brand" href="${esc(cfg.base || '/acc-engine/')}dev.php">
            <span class="dev-brand__mark">DV</span>
            <span>
              <div class="dev-brand__name">ACC <em>Dev Hub</em></div>
              <div class="dev-brand__sub">مرکز مستندات و پشتیبان‌گیری تیم</div>
            </span>
          </a>
          <div class="dev-header__meta">
            <span>${esc(user.display_name || user.user_login || '')}</span>
            <button type="button" class="dev-btn dev-btn--ghost dev-btn--sm" data-act="logout" style="color:#0B1220">خروج</button>
          </div>
        </header>
        <nav class="dev-tabs">${tabsHtml()}</nav>
        <main class="dev-main">${inner}</main>
      </div>`;
    bindCommon();
  }

  function bindCommon() {
    root.querySelectorAll('[data-tab]').forEach((btn) => {
      btn.addEventListener('click', () => {
        state.tab = btn.getAttribute('data-tab');
        route();
      });
    });
    const logout = root.querySelector('[data-act="logout"]');
    if (logout) {
      logout.addEventListener('click', async () => {
        await api('logout');
        location.reload();
      });
    }
  }

  async function renderDocs() {
    renderShell(`
      <section class="dev-panel">
        <h2>مرکز مستندات تیم</h2>
        <p class="dev-lead">محتوای «PORTALS-DEV-HANDOFF» از دیتابیس — قابل جستجو و فیلتر بر اساس پورتال.</p>
        <div class="dev-cards" id="doc-stats"></div>
        <div class="dev-toolbar">
          <input class="dev-input" id="doc-q" placeholder="جستجو در عناوین و متن…" style="flex:1;min-width:200px">
          <select class="dev-select" id="doc-portal">
            <option value="all">همه پورتال‌ها</option>
            <option value="erp">ERP</option>
            <option value="melk">Melk</option>
            <option value="wholesale">Wholesale</option>
            <option value="election">Election</option>
            <option value="service">Service</option>
            <option value="education">Education</option>
            <option value="shop">Shop</option>
            <option value="growth">Growth / سفیران</option>
            <option value="offline">آفلاین</option>
            <option value="images">تصاویر</option>
            <option value="shared">هسته مشترک</option>
            <option value="profile">پروفایل</option>
            <option value="risk">ریسک first-load</option>
            <option value="general">عمومی</option>
          </select>
          <button type="button" class="dev-btn" id="btn-sync">همگام‌سازی از فایل</button>
        </div>
        <div class="dev-status" id="doc-status" hidden></div>
        <div class="dev-grid">
          <aside class="dev-side" id="sec-list"></aside>
          <article class="dev-article" id="sec-view"><p class="dev-lead">یک بخش را انتخاب کنید.</p></article>
        </div>
      </section>`);

    const status = root.querySelector('#doc-status');
    const list = root.querySelector('#sec-list');
    const view = root.querySelector('#sec-view');
    const stats = root.querySelector('#doc-stats');

    async function load() {
      const portal = root.querySelector('#doc-portal').value;
      const q = root.querySelector('#doc-q').value.trim();
      const data = await api('docs_list', { portal, q });
      if (!data.ok) {
        setStatus(status, data.message || 'خطا', 'error');
        return;
      }
      state.doc = data.doc;
      state.sections = data.sections || [];
      stats.innerHTML = `
        <div class="dev-card-stat"><div class="dev-card-stat__n">${esc(data.doc?.version ?? '—')}</div><div class="dev-card-stat__l">نسخه سند</div></div>
        <div class="dev-card-stat"><div class="dev-card-stat__n">${state.sections.length}</div><div class="dev-card-stat__l">بخش‌های نمایش‌داده‌شده</div></div>
        <div class="dev-card-stat"><div class="dev-card-stat__n">${esc(data.doc?.synced_at || '—')}</div><div class="dev-card-stat__l">آخرین همگام‌سازی</div></div>`;

      list.innerHTML = state.sections
        .map(
          (s) =>
            `<button type="button" data-sid="${s.id}" class="${String(state.activeSectionId) === String(s.id) ? 'is-active' : ''}">
              <strong>${esc(s.heading)}</strong><br><span style="opacity:.7;font-size:.75rem">${esc(s.portal_key)}</span>
            </button>`
        )
        .join('') || '<p style="padding:12px;color:#64748B">نتیجه‌ای نیست</p>';

      list.querySelectorAll('[data-sid]').forEach((btn) => {
        btn.addEventListener('click', () => {
          state.activeSectionId = btn.getAttribute('data-sid');
          const sec = state.sections.find((x) => String(x.id) === String(state.activeSectionId));
          list.querySelectorAll('button').forEach((b) => b.classList.toggle('is-active', b === btn));
          view.innerHTML = sec
            ? `<h2>${esc(sec.heading)}</h2><div>${sec.content_html || ''}</div>`
            : '';
        });
      });

      if (state.sections.length && !state.activeSectionId) {
        list.querySelector('[data-sid]')?.click();
      } else if (state.activeSectionId) {
        const still = state.sections.find((x) => String(x.id) === String(state.activeSectionId));
        if (still) {
          view.innerHTML = `<h2>${esc(still.heading)}</h2><div>${still.content_html || ''}</div>`;
        }
      }
    }

    root.querySelector('#btn-sync').addEventListener('click', async () => {
      const btn = root.querySelector('#btn-sync');
      btn.disabled = true;
      setStatus(status, 'در حال همگام‌سازی از فایل…');
      const data = await api('docs_sync');
      btn.disabled = false;
      setStatus(status, data.message || '', data.ok ? 'ok' : 'error');
      if (data.ok) await load();
    });

    let t;
    root.querySelector('#doc-q').addEventListener('input', () => {
      clearTimeout(t);
      t = setTimeout(load, 280);
    });
    root.querySelector('#doc-portal').addEventListener('change', load);
    await load();
  }

  function backupTable(rows, type) {
    if (!rows.length) return '<p class="dev-lead">هنوز پشتیبانی ساخته نشده.</p>';
    return `<table class="dev-table-list"><thead><tr>
      <th>نام فایل</th><th>حجم</th><th>تاریخ</th><th></th>
    </tr></thead><tbody>${rows
      .map(
        (b) => `<tr>
        <td><code>${esc(b.filename)}</code></td>
        <td>${esc(bytes(b.size_bytes))}</td>
        <td>${esc(b.created_at)}</td>
        <td style="white-space:nowrap">
          <a class="dev-btn dev-btn--ghost dev-btn--sm" href="?action=download&id=${esc(b.id)}">دانلود</a>
          <button type="button" class="dev-btn dev-btn--danger dev-btn--sm" data-del="${esc(b.id)}" data-type="${type}">حذف</button>
        </td>
      </tr>`
      )
      .join('')}</tbody></table>`;
  }

  async function renderCode() {
    renderShell(`
      <section class="dev-panel">
        <h2>پشتیبان‌گیری کد (بدون عکس)</h2>
        <p class="dev-lead">آرشیو <code>tar.gz</code> از سورس — تصاویر، کاشی نقشه، node_modules و <code>.env</code> به‌صورت پیش‌فرض حذف می‌شوند.</p>
        <div class="dev-toolbar">
          <label class="dev-check"><input type="checkbox" id="inc-fonts" checked> شامل فونت‌ها (assets/fonts)</label>
          <label class="dev-check"><input type="checkbox" id="exc-env" checked> حذف .env / اسرار</label>
          <button type="button" class="dev-btn" id="btn-code">ساخت پشتیبان کد</button>
        </div>
        <div class="dev-status" id="code-status" hidden></div>
        <div id="code-list"></div>
        <details class="dev-guide" style="margin-top:16px" open>
          <summary>موارد حذف‌شدهٔ پیش‌فرض</summary>
          <ul>
            <li><code>assets/img/**</code> (~۱۶MB تصاویر پورتال)</li>
            <li><code>assets/vendor/leaflet/tiles*</code> (~۱۹۰MB کاشی نقشه)</li>
            <li><code>node_modules</code> ، <code>vite-src/node_modules</code></li>
            <li><code>uploads</code> ، <code>logs</code> ، <code>storage/backups</code> ، <code>.git</code></li>
            <li>فایل‌های <code>.env*</code> و <code>*.log</code></li>
          </ul>
        </details>
      </section>`);

    const status = root.querySelector('#code-status');
    const list = root.querySelector('#code-list');

    async function refresh() {
      const data = await api('backups_list', { type: 'code' });
      state.backups.code = data.items || [];
      list.innerHTML = backupTable(state.backups.code, 'code');
      list.querySelectorAll('[data-del]').forEach((btn) => {
        btn.addEventListener('click', async () => {
          if (!confirm('حذف این پشتیبان؟')) return;
          const r = await api('backup_delete', { id: btn.getAttribute('data-del') });
          setStatus(status, r.message || '', r.ok ? 'ok' : 'error');
          await refresh();
        });
      });
    }

    root.querySelector('#btn-code').addEventListener('click', async () => {
      const btn = root.querySelector('#btn-code');
      btn.disabled = true;
      setStatus(status, 'در حال ساخت آرشیو کد… ممکن است چند دقیقه طول بکشد.');
      const data = await api('backup_code', {
        include_fonts: root.querySelector('#inc-fonts').checked,
        exclude_env: root.querySelector('#exc-env').checked,
      });
      btn.disabled = false;
      setStatus(status, data.message || '', data.ok ? 'ok' : 'error');
      await refresh();
    });

    await refresh();
  }

  async function renderDb() {
    renderShell(`
      <section class="dev-panel">
        <h2>پشتیبان‌گیری دیتابیس</h2>
        <p class="dev-lead">اولویت با <code>mysqldump</code> از XAMPP؛ در صورت نیاز، dump مبتنی بر PHP. خروجی <code>.sql.gz</code>. به‌صورت پیش‌فرض جداول حجیم <code>wp_gis_*</code> حذف می‌شوند.</p>
        <div class="dev-toolbar dev-toolbar--wrap">
          <label class="dev-check"><input type="checkbox" id="exc-gis" checked> حذف جداول حجیم GIS (پیشنهادی)</label>
          <label class="dev-check"><input type="checkbox" id="inc-gis"> شامل جداول <code>wp_gis_*</code></label>
          <button type="button" class="dev-btn" id="btn-db">ساخت پشتیبان دیتابیس</button>
        </div>
        <details class="dev-guide" id="gis-tables-box" style="margin-top:12px">
          <summary>جداول GIS و حجم تقریبی <span id="gis-total" class="dev-muted"></span></summary>
          <p class="dev-lead" style="margin-top:8px">برای پشتیبان سبک، تیک «حذف…» را بگذارید. برای dump کامل، «شامل جداول wp_gis_*» را بزنید. یا فقط چند جدول را از لیست زیر نگه دارید.</p>
          <div id="gis-tables" class="dev-gis-list">در حال بارگذاری…</div>
        </details>
        <div class="dev-status" id="db-status" hidden></div>
        <div id="db-list"></div>
      </section>`);

    const status = root.querySelector('#db-status');
    const list = root.querySelector('#db-list');
    const excGis = root.querySelector('#exc-gis');
    const incGis = root.querySelector('#inc-gis');
    const gisBox = root.querySelector('#gis-tables');
    const gisTotal = root.querySelector('#gis-total');

    function syncGisMode(from) {
      if (from === 'inc' && incGis.checked) {
        excGis.checked = false;
      } else if (from === 'exc' && excGis.checked) {
        incGis.checked = false;
      } else if (from === 'exc' && !excGis.checked) {
        incGis.checked = true;
      } else if (from === 'inc' && !incGis.checked) {
        excGis.checked = true;
      }
      gisBox.querySelectorAll('[data-gis-inc]').forEach((cb) => {
        cb.disabled = incGis.checked || !excGis.checked;
        if (incGis.checked) cb.checked = true;
        if (excGis.checked && from === 'exc') cb.checked = false;
      });
    }

    excGis.addEventListener('change', () => syncGisMode('exc'));
    incGis.addEventListener('change', () => syncGisMode('inc'));

    async function loadGisTables() {
      const data = await api('db_gis_tables');
      const items = data.items || [];
      if (!data.ok || !items.length) {
        gisBox.innerHTML = '<p class="dev-lead">جدول GIS یافت نشد.</p>';
        gisTotal.textContent = '';
        return;
      }
      gisTotal.textContent = `(${items.length} جدول، حدود ${bytes(data.total_bytes || 0)})`;
      gisBox.innerHTML = `<table class="dev-table-list"><thead><tr>
        <th>نگه داشتن</th><th>جدول</th><th>حجم تقریبی</th>
      </tr></thead><tbody>${items
        .map(
          (t) => `<tr>
          <td><input type="checkbox" data-gis-inc value="${esc(t.name)}" title="شامل این جدول حتی در حالت حذف GIS"></td>
          <td><code>${esc(t.name)}</code>${t.known_heavy ? ' <span class="dev-pill">حجیم</span>' : ''}</td>
          <td>${esc(bytes(t.size_bytes))}</td>
        </tr>`
        )
        .join('')}</tbody></table>`;
      syncGisMode('exc');
    }

    async function refresh() {
      const data = await api('backups_list', { type: 'db' });
      state.backups.db = data.items || [];
      list.innerHTML = backupTable(state.backups.db, 'db');
      list.querySelectorAll('[data-del]').forEach((btn) => {
        btn.addEventListener('click', async () => {
          if (!confirm('حذف این پشتیبان؟')) return;
          const r = await api('backup_delete', { id: btn.getAttribute('data-del') });
          setStatus(status, r.message || '', r.ok ? 'ok' : 'error');
          await refresh();
        });
      });
    }

    root.querySelector('#btn-db').addEventListener('click', async () => {
      const btn = root.querySelector('#btn-db');
      btn.disabled = true;
      setStatus(status, 'در حال dump دیتابیس…');
      const includeTables = [...gisBox.querySelectorAll('[data-gis-inc]:checked')]
        .map((cb) => cb.value)
        .filter(Boolean)
        .join(',');
      const data = await api('backup_db', {
        exclude_gis: excGis.checked,
        include_gis: incGis.checked,
        include_tables: includeTables,
      });
      btn.disabled = false;
      setStatus(status, data.message || '', data.ok ? 'ok' : 'error');
      await refresh();
    });

    await Promise.all([loadGisTables(), refresh()]);
  }

  async function renderCatalog() {
    renderShell(`
      <section class="dev-panel">
        <h2>کاتالوگ دیتابیس</h2>
        <p class="dev-lead">مرور زندهٔ جداول MySQL از INFORMATION_SCHEMA — فیلتر پیشوند/ماژول، جزئیات ستون و ایندکس، FK واقعی و روابط استنباطی.</p>
        <div class="dev-toolbar dev-toolbar--wrap">
          <input class="dev-input" id="cat-q" placeholder="جستجوی نام جدول…" style="flex:1;min-width:160px">
          <select class="dev-select" id="cat-prefix">
            <option value="all">همه پیشوندها</option>
            <option value="wp_acc_">wp_acc_</option>
            <option value="wp_gis_">wp_gis_</option>
            <option value="wp_election_">wp_election_</option>
            <option value="wp_melk_">wp_melk_</option>
            <option value="wp_svc_">wp_svc_</option>
            <option value="wp_edu_">wp_edu_</option>
            <option value="wp_arag_">wp_arag_</option>
            <option value="wp_wholesale_">wp_wholesale_</option>
            <option value="wp_erp_">wp_erp_</option>
            <option value="wp_ai_">wp_ai_</option>
            <option value="wp_inv_">wp_inv_</option>
            <option value="wp_chat_">wp_chat_</option>
            <option value="wp_acl_">wp_acl_</option>
          </select>
          <select class="dev-select" id="cat-module">
            <option value="all">همه ماژول‌ها</option>
          </select>
          <select class="dev-select" id="cat-size">
            <option value="all">هر حجمی</option>
            <option value="1m">بیش از ۱MB</option>
            <option value="10m">بیش از ۱۰MB</option>
            <option value="100m">بیش از ۱۰۰MB</option>
          </select>
          <select class="dev-select" id="cat-sort">
            <option value="name">مرتب: نام</option>
            <option value="size">مرتب: حجم</option>
            <option value="rows">مرتب: ردیف</option>
            <option value="module">مرتب: ماژول</option>
          </select>
          <button type="button" class="dev-btn" id="cat-refresh">بارگذاری</button>
          <button type="button" class="dev-btn dev-btn--ghost" id="cat-meta">غنی‌سازی متا</button>
          <button type="button" class="dev-btn dev-btn--ghost" id="cat-export">خروجی JSON</button>
        </div>
        <div class="dev-status" id="cat-status" hidden></div>
        <div class="dev-cards" id="cat-stats"></div>
        <div class="dev-catalog">
          <div class="dev-catalog__list" id="cat-list"><p class="dev-lead">در حال بارگذاری…</p></div>
          <div class="dev-catalog__detail" id="cat-detail"><p class="dev-lead">یک جدول را انتخاب کنید.</p></div>
        </div>
      </section>`);

    const status = root.querySelector('#cat-status');
    const listEl = root.querySelector('#cat-list');
    const detailEl = root.querySelector('#cat-detail');
    const statsEl = root.querySelector('#cat-stats');
    const modSel = root.querySelector('#cat-module');

    function sizeFilter() {
      const v = root.querySelector('#cat-size').value;
      if (v === '1m') return 1048576;
      if (v === '10m') return 10485760;
      if (v === '100m') return 104857600;
      return 0;
    }

    async function loadList() {
      setStatus(status, 'در حال خواندن INFORMATION_SCHEMA…');
      const data = await api('db_catalog_list', {
        q: root.querySelector('#cat-q').value.trim(),
        prefix: root.querySelector('#cat-prefix').value,
        module: root.querySelector('#cat-module').value,
        min_size: sizeFilter(),
        sort: root.querySelector('#cat-sort').value,
      });
      if (!data.ok) {
        setStatus(status, data.message || 'خطا', 'error');
        return;
      }
      state.catalog.items = data.items || [];
      if (modSel.options.length <= 1 && data.modules) {
        data.modules.forEach((m) => {
          const o = document.createElement('option');
          o.value = m.key;
          o.textContent = m.label;
          modSel.appendChild(o);
        });
      }
      const totalBytes = state.catalog.items.reduce((s, t) => s + (t.total_size || 0), 0);
      statsEl.innerHTML = `
        <div class="dev-card-stat"><div class="dev-card-stat__n">${esc(data.db || '')}</div><div class="dev-card-stat__l">دیتابیس</div></div>
        <div class="dev-card-stat"><div class="dev-card-stat__n">${state.catalog.items.length}</div><div class="dev-card-stat__l">جدول (فیلترشده)</div></div>
        <div class="dev-card-stat"><div class="dev-card-stat__n">${esc(bytes(totalBytes))}</div><div class="dev-card-stat__l">حجم تقریبی مجموع</div></div>`;

      listEl.innerHTML = state.catalog.items.length
        ? `<table class="dev-table-list"><thead><tr>
            <th>جدول</th><th>ماژول</th><th>ردیف≈</th><th>حجم</th>
          </tr></thead><tbody>${state.catalog.items
            .map(
              (t) => `<tr data-table="${esc(t.name)}" class="${state.catalog.selected === t.name ? 'is-active' : ''}">
              <td><code>${esc(t.name)}</code>${t.title_fa ? `<div class="dev-muted" style="font-size:.75rem">${esc(t.title_fa)}</div>` : ''}</td>
              <td><span class="dev-pill dev-pill--mod">${esc(t.module_label || t.module)}</span></td>
              <td>${esc(t.rows)}</td>
              <td>${esc(bytes(t.total_size))}</td>
            </tr>`
            )
            .join('')}</tbody></table>`
        : '<p class="dev-lead">جدولی با این فیلتر نیست.</p>';

      listEl.querySelectorAll('[data-table]').forEach((tr) => {
        tr.addEventListener('click', () => openTable(tr.getAttribute('data-table')));
      });
      setStatus(status, `${state.catalog.items.length} جدول بارگذاری شد.`, 'ok');
    }

    function relBlock(title, rows, kind) {
      if (!rows || !rows.length) return `<p class="dev-muted">${title}: ندارد</p>`;
      return `<h4>${esc(title)}</h4><table class="dev-table"><thead><tr>
        <th>جزئیات</th><th>نوع</th>
      </tr></thead><tbody>${rows
        .map((r) => {
          if (kind === 'fk-from') {
            return `<tr><td><code>${esc(r.column_name)}</code> → <code>${esc(r.referenced_table)}.${esc(r.referenced_column)}</code>
              <div class="dev-muted">${esc(r.constraint_name)} · ON DELETE ${esc(r.on_delete)} / UPDATE ${esc(r.on_update)}</div></td>
              <td><span class="dev-pill">FK</span></td></tr>`;
          }
          if (kind === 'fk-to') {
            return `<tr><td><code>${esc(r.from_table)}.${esc(r.column_name)}</code> → این جدول.<code>${esc(r.referenced_column)}</code>
              <div class="dev-muted">${esc(r.constraint_name)}</div></td>
              <td><span class="dev-pill">FK</span></td></tr>`;
          }
          if (kind === 'inferred') {
            const tgt = r.referenced_table
              ? `<code>${esc(r.column)}</code> → <code>${esc(r.referenced_table)}.${esc(r.referenced_column || 'id')}</code>`
              : `<code>${esc(r.column)}</code> (هدف نامشخص)`;
            return `<tr><td>${tgt}<div class="dev-muted">${esc(r.note || '')} · اطمینان: ${esc(r.confidence || '')}</div></td>
              <td><span class="dev-pill dev-pill--inf">استنباطی</span></td></tr>`;
          }
          return `<tr><td><code>${esc(r.table)}.${esc(r.column)}</code></td>
            <td><span class="dev-pill ${r.kind === 'fk' ? '' : 'dev-pill--inf'}">${r.kind === 'fk' ? 'FK' : 'استنباطی'}</span></td></tr>`;
        })
        .join('')}</tbody></table>`;
    }

    async function openTable(name) {
      state.catalog.selected = name;
      listEl.querySelectorAll('[data-table]').forEach((tr) => {
        tr.classList.toggle('is-active', tr.getAttribute('data-table') === name);
      });
      detailEl.innerHTML = '<p class="dev-lead">بارگذاری جزئیات…</p>';
      const data = await api('db_catalog_table', { table: name, with_create: true });
      if (!data.ok) {
        detailEl.innerHTML = `<p class="dev-lead" style="color:var(--dev-danger)">${esc(data.message || 'خطا')}</p>`;
        return;
      }
      const t = data.table;
      state.catalog.detail = t;
      const cols = (t.columns || [])
        .map(
          (c) => `<tr>
          <td><code>${esc(c.name)}</code></td>
          <td style="direction:ltr;text-align:left">${esc(c.type)}</td>
          <td>${c.nullable ? 'NULL' : 'NOT NULL'}</td>
          <td>${c.default === null || c.default === undefined ? '—' : `<code>${esc(c.default)}</code>`}</td>
          <td>${esc(c.extra || '—')}</td>
          <td>${esc(c.key || '—')}</td>
          <td>${esc(c.description || c.comment || '—')}</td>
          <td>${esc(c.charset || '—')}</td>
        </tr>`
        )
        .join('');
      const idxs = (t.indexes || [])
        .map(
          (ix) => `<tr>
          <td><code>${esc(ix.name)}</code></td>
          <td>${esc((ix.columns || []).join(', '))}</td>
          <td>${ix.unique ? 'UNIQUE' : 'غیریکتا'}</td>
          <td>${esc(ix.type)}</td>
          <td>${ix.cardinality == null ? '—' : esc(ix.cardinality)}</td>
        </tr>`
        )
        .join('');

      detailEl.innerHTML = `
        <div class="dev-catalog__head">
          <h3><code>${esc(t.name)}</code></h3>
          <div class="dev-muted">${esc(t.module_label)} · ${esc(t.engine)} · ${esc(bytes(t.total_size))} · ردیف≈ ${esc(t.rows_approx)}</div>
          ${t.description ? `<p>${esc(t.description)}</p>` : ''}
          <div class="dev-toolbar" style="margin-top:8px">
            <button type="button" class="dev-btn dev-btn--sm" id="cat-sample">نمونه ۲۰ ردیف</button>
            <button type="button" class="dev-btn dev-btn--ghost dev-btn--sm" id="cat-count">شمارش دقیق</button>
            <button type="button" class="dev-btn dev-btn--ghost dev-btn--sm" id="cat-md">خروجی Markdown</button>
          </div>
        </div>
        <div id="cat-sample-box"></div>
        <details open><summary>ستون‌ها (${(t.columns || []).length})</summary>
          <div class="dev-scroll"><table class="dev-table"><thead><tr>
            <th>نام</th><th>نوع</th><th>Null</th><th>پیش‌فرض</th><th>Extra</th><th>Key</th><th>توضیح</th><th>Charset</th>
          </tr></thead><tbody>${cols}</tbody></table></div>
        </details>
        <details open><summary>ایندکس‌ها (${(t.indexes || []).length})</summary>
          <table class="dev-table"><thead><tr>
            <th>نام</th><th>ستون‌ها</th><th>یکتایی</th><th>نوع</th><th>Cardinality</th>
          </tr></thead><tbody>${idxs || '<tr><td colspan="5">—</td></tr>'}</tbody></table>
        </details>
        <details open><summary>روابط</summary>
          ${relBlock('FK از این جدول', (t.foreign_keys && t.foreign_keys.from) || [], 'fk-from')}
          ${relBlock('FK به این جدول', (t.foreign_keys && t.foreign_keys.to) || [], 'fk-to')}
          ${relBlock('روابط استنباطی (*_id)', t.inferred || [], 'inferred')}
          ${relBlock('جداول ارجاع‌دهنده (راهنما)', t.referenced_by || [], 'refs')}
          <p class="dev-muted" style="margin-top:8px">FK = محدودیت واقعی MySQL · استنباطی = حدس از نام ستون، اجباری نیست.</p>
        </details>
        <details><summary>SHOW CREATE TABLE</summary>
          <pre class="dev-code">${esc(t.create_sql || '')}</pre>
        </details>`;

      detailEl.querySelector('#cat-sample')?.addEventListener('click', async () => {
        const box = detailEl.querySelector('#cat-sample-box');
        box.innerHTML = '<p class="dev-lead">در حال خواندن نمونه…</p>';
        const s = await api('db_catalog_sample', { table: name, limit: 20, exact_count: false });
        if (!s.ok) {
          box.innerHTML = `<p class="dev-lead" style="color:var(--dev-danger)">${esc(s.message)}</p>`;
          return;
        }
        const headers = (s.columns || []).map((c) => `<th>${esc(c)}</th>`).join('');
        const body = (s.rows || [])
          .map(
            (row) =>
              `<tr>${(s.columns || []).map((c) => `<td>${row[c] === null || row[c] === undefined ? '<em>NULL</em>' : esc(row[c])}</td>`).join('')}</tr>`
          )
          .join('');
        box.innerHTML = `<details open><summary>نمونه (${(s.rows || []).length} ردیف)</summary>
          <div class="dev-scroll"><table class="dev-table"><thead><tr>${headers}</tr></thead><tbody>${body || '<tr><td>خالی</td></tr>'}</tbody></table></div>
        </details>`;
      });

      detailEl.querySelector('#cat-count')?.addEventListener('click', async () => {
        const s = await api('db_catalog_sample', { table: name, limit: 1, exact_count: true });
        if (s.ok) alert('تعداد دقیق ردیف: ' + s.count);
        else alert(s.message || 'خطا');
      });

      detailEl.querySelector('#cat-md')?.addEventListener('click', () => {
        const md = [
          `# ${t.name}`,
          '',
          t.description || '',
          '',
          `| Column | Type | Null | Key | Comment |`,
          `|---|---|---|---|---|`,
          ...(t.columns || []).map(
            (c) =>
              `| ${c.name} | ${c.type} | ${c.nullable ? 'YES' : 'NO'} | ${c.key || ''} | ${(c.description || c.comment || '').replace(/\|/g, '/')} |`
          ),
          '',
          '```sql',
          t.create_sql || '',
          '```',
        ].join('\n');
        const blob = new Blob([md], { type: 'text/markdown;charset=utf-8' });
        const a = document.createElement('a');
        a.href = URL.createObjectURL(blob);
        a.download = t.name + '.md';
        a.click();
      });
    }

    root.querySelector('#cat-refresh').addEventListener('click', loadList);
    root.querySelector('#cat-meta').addEventListener('click', async () => {
      setStatus(status, 'در حال غنی‌سازی متا…');
      const r = await api('db_catalog_refresh_meta');
      setStatus(status, r.message || '', r.ok ? 'ok' : 'error');
      if (r.ok) await loadList();
    });
    root.querySelector('#cat-export').addEventListener('click', () => {
      const blob = new Blob([JSON.stringify({ items: state.catalog.items, detail: state.catalog.detail }, null, 2)], {
        type: 'application/json',
      });
      const a = document.createElement('a');
      a.href = URL.createObjectURL(blob);
      a.download = 'db-catalog.json';
      a.click();
    });
    let tmr;
    root.querySelector('#cat-q').addEventListener('input', () => {
      clearTimeout(tmr);
      tmr = setTimeout(loadList, 280);
    });
    ['cat-prefix', 'cat-module', 'cat-size', 'cat-sort'].forEach((id) => {
      root.querySelector('#' + id).addEventListener('change', loadList);
    });

    await loadList();
    if (state.catalog.selected) await openTable(state.catalog.selected);
  }

  async function renderCodeCatalog() {
    const cc = state.codeCatalog;
    renderShell(`
      <section class="dev-panel">
        <h2>کاتالوگ کد</h2>
        <p class="dev-lead">فهرست زندهٔ فایل‌های پروژه از فایل‌سیستم — پورتال‌ها، قالب‌ها، CSS/JS، تصاویر، فونت، vendor، helpers و docs. بسته‌های کاشی Leaflet به‌صورت خلاصه ثبت می‌شوند.</p>
        <div class="dev-toolbar dev-toolbar--wrap">
          <input class="dev-input" id="cc-q" placeholder="جستجوی مسیر یا توضیح…" style="flex:1;min-width:160px">
          <select class="dev-select" id="cc-portal">
            <option value="all">همه پورتال‌ها</option>
          </select>
          <select class="dev-select" id="cc-kind">
            <option value="all">همه انواع</option>
          </select>
          <select class="dev-select" id="cc-sort">
            <option value="path">مرتب: مسیر</option>
            <option value="size">مرتب: حجم</option>
            <option value="mtime">مرتب: زمان</option>
            <option value="kind">مرتب: نوع</option>
            <option value="portal">مرتب: پورتال</option>
          </select>
          <label class="dev-check"><input type="checkbox" id="cc-crit"> فقط critical path</label>
          <label class="dev-check"><input type="checkbox" id="cc-ex-tiles" ${cc.excludeTiles ? 'checked' : ''}> بدون کاشی‌ها</label>
          <button type="button" class="dev-btn" id="cc-load">بارگذاری</button>
          <button type="button" class="dev-btn" id="cc-scan">بازخوانی کاتالوگ کد</button>
        </div>
        <div class="dev-status" id="cc-status" hidden></div>
        <div class="dev-cards" id="cc-stats"></div>
        <details class="dev-guide" id="cc-crit-box" style="margin-bottom:12px">
          <summary>مسیر critical first-load (از قالب‌ها)</summary>
          <div id="cc-crit-list" class="dev-muted">—</div>
        </details>
        <div class="dev-catalog">
          <div class="dev-catalog__list" id="cc-list"><p class="dev-lead">در حال بارگذاری…</p></div>
          <div class="dev-catalog__detail" id="cc-detail"><p class="dev-lead">یک فایل را انتخاب کنید.</p></div>
        </div>
      </section>`);

    const status = root.querySelector('#cc-status');
    const listEl = root.querySelector('#cc-list');
    const detailEl = root.querySelector('#cc-detail');
    const statsEl = root.querySelector('#cc-stats');
    const portalSel = root.querySelector('#cc-portal');
    const kindSel = root.querySelector('#cc-kind');
    const critList = root.querySelector('#cc-crit-list');

    function fillSelect(sel, items, valueKey, labelKey, current) {
      const cur = current || sel.value;
      const keep = sel.querySelector('option[value="all"]');
      sel.innerHTML = '';
      if (keep) sel.appendChild(keep);
      else {
        const o = document.createElement('option');
        o.value = 'all';
        o.textContent = sel.id === 'cc-portal' ? 'همه پورتال‌ها' : 'همه انواع';
        sel.appendChild(o);
      }
      (items || []).forEach((it) => {
        const o = document.createElement('option');
        if (typeof it === 'string') {
          o.value = it;
          o.textContent = it;
        } else {
          o.value = it[valueKey];
          o.textContent = it[labelKey] || it[valueKey];
        }
        sel.appendChild(o);
      });
      if ([...sel.options].some((o) => o.value === cur)) sel.value = cur;
    }

    async function loadList() {
      setStatus(status, 'در حال خواندن کاتالوگ کد…');
      cc.excludeTiles = root.querySelector('#cc-ex-tiles').checked;
      const data = await api('code_catalog_list', {
        q: root.querySelector('#cc-q').value.trim(),
        portal: portalSel.value,
        kind: kindSel.value,
        sort: root.querySelector('#cc-sort').value,
        critical_only: root.querySelector('#cc-crit').checked,
        exclude_tiles: cc.excludeTiles,
      });
      if (!data.ok) {
        setStatus(status, data.message || 'خطا', 'error');
        return;
      }
      cc.items = data.items || [];
      cc.stats = data.stats || null;
      cc.critical = data.critical || [];
      cc.portals = data.portals || [];
      cc.kinds = data.kinds || [];

      fillSelect(portalSel, cc.portals, null, null, portalSel.value);
      fillSelect(kindSel, cc.kinds, 'key', 'label', kindSel.value);

      const st = cc.stats || {};
      const byKind = st.by_kind || {};
      const kindChips = Object.keys(byKind)
        .map((k) => {
          const row = byKind[k];
          return `<span class="dev-pill" title="${esc(bytes(row.size))}">${esc(row.label || k)}: ${esc(row.count)}</span>`;
        })
        .join(' ');
      statsEl.innerHTML = `
        <div class="dev-card-stat"><div class="dev-card-stat__n">${esc(st.total_count ?? cc.items.length)}</div><div class="dev-card-stat__l">کل آیتم‌ها</div></div>
        <div class="dev-card-stat"><div class="dev-card-stat__n">${esc(bytes(st.total_size_ex_tiles ?? 0))}</div><div class="dev-card-stat__l">حجم بدون کاشی</div></div>
        <div class="dev-card-stat"><div class="dev-card-stat__n">${esc(bytes(st.total_size ?? 0))}</div><div class="dev-card-stat__l">حجم با کاشی</div></div>
        <div class="dev-card-stat"><div class="dev-card-stat__n">${esc(st.critical_count ?? 0)}</div><div class="dev-card-stat__l">critical path</div></div>
        <div class="dev-card-stat"><div class="dev-card-stat__n" style="font-size:.85rem">${esc(st.synced_at || '—')}</div><div class="dev-card-stat__l">آخرین اسکن</div></div>`;
      if (kindChips) {
        statsEl.insertAdjacentHTML('beforeend', `<div style="grid-column:1/-1;display:flex;flex-wrap:wrap;gap:6px;margin-top:4px">${kindChips}</div>`);
      }

      const critByPortal = {};
      cc.critical.forEach((c) => {
        const p = c.portal_key || 'shared';
        if (!critByPortal[p]) critByPortal[p] = [];
        critByPortal[p].push(c);
      });
      critList.innerHTML = Object.keys(critByPortal).length
        ? Object.keys(critByPortal)
            .map((p) => {
              const lines = critByPortal[p]
                .map(
                  (c) =>
                    `<li><code data-cc-path="${esc(c.path)}" class="dev-cc-link">${esc(c.path)}</code> <span class="dev-muted">(${esc(bytes(c.size_bytes))})</span></li>`
                )
                .join('');
              return `<div style="margin:8px 0"><strong>${esc(p)}</strong><ul style="margin:4px 0;padding-inline-start:1.2rem">${lines}</ul></div>`;
            })
            .join('')
        : '<p>هنوز مسیر critical ثبت نشده — بازخوانی را بزنید.</p>';

      critList.querySelectorAll('[data-cc-path]').forEach((el) => {
        el.addEventListener('click', () => openFile(el.getAttribute('data-cc-path')));
      });

      listEl.innerHTML = cc.items.length
        ? `<table class="dev-table-list"><thead><tr>
            <th>مسیر</th><th>نوع</th><th>پورتال</th><th>حجم</th>
          </tr></thead><tbody>${cc.items
            .map((f) => {
              const flags =
                (f.critical_path ? '<span class="dev-pill dev-pill--crit">critical</span> ' : '') +
                (f.is_summary ? '<span class="dev-pill dev-pill--inf">خلاصه</span> ' : '');
              return `<tr data-cc="${esc(f.path)}" class="${cc.selected === f.path ? 'is-active' : ''}">
                <td>${flags}<code>${esc(f.path)}</code>${f.notes ? `<div class="dev-muted" style="font-size:.72rem">${esc(f.notes)}</div>` : ''}</td>
                <td><span class="dev-pill">${esc(f.kind_label || f.kind)}</span></td>
                <td>${esc(f.portal_key)}</td>
                <td>${esc(bytes(f.size_bytes))}${f.is_summary && f.file_count ? ` <span class="dev-muted">(${esc(f.file_count)} فایل)</span>` : ''}</td>
              </tr>`;
            })
            .join('')}</tbody></table>`
        : '<p class="dev-lead">موردی با این فیلتر نیست.</p>';

      listEl.querySelectorAll('[data-cc]').forEach((tr) => {
        tr.addEventListener('click', () => openFile(tr.getAttribute('data-cc')));
      });
      setStatus(status, `${cc.items.length} مورد نمایش داده شد.`, 'ok');
    }

    async function openFile(path) {
      cc.selected = path;
      listEl.querySelectorAll('[data-cc]').forEach((tr) => {
        tr.classList.toggle('is-active', tr.getAttribute('data-cc') === path);
      });
      detailEl.innerHTML = '<p class="dev-lead">بارگذاری جزئیات…</p>';
      const data = await api('code_catalog_file', { path });
      if (!data.ok) {
        detailEl.innerHTML = `<p class="dev-lead" style="color:var(--dev-danger)">${esc(data.message || 'خطا')}</p>`;
        return;
      }
      const f = data.file || {};
      cc.detail = f;
      const handoff = data.handoff || [];
      detailEl.innerHTML = `
        <div class="dev-catalog__head">
          <h3><code>${esc(f.path)}</code></h3>
          <div class="dev-muted">${esc(f.kind_label || f.kind)} · پورتال ${esc(f.portal_key)} · ${esc(bytes(f.size_bytes))}
            ${f.mtime ? ' · ' + esc(f.mtime) : ''}
            ${f.critical_path ? ' · <span class="dev-pill dev-pill--crit">critical</span>' : ''}
            ${f.is_summary ? ' · <span class="dev-pill dev-pill--inf">خلاصه tiles</span>' : ''}
          </div>
          ${f.notes ? `<p>${esc(f.notes)}</p>` : ''}
          <div class="dev-toolbar" style="margin-top:8px">
            <button type="button" class="dev-btn dev-btn--sm" id="cc-copy">کپی مسیر</button>
            ${data.handoff_doc ? `<a class="dev-btn dev-btn--ghost dev-btn--sm" href="#docs" id="cc-handoff-link">سند handoff</a>` : ''}
          </div>
        </div>
        ${
          handoff.length
            ? `<details open><summary>ارجاع handoff (${handoff.length})</summary>
            <ul>${handoff
              .map(
                (h) =>
                  `<li><strong>${esc(h.section || h.portal || '')}</strong> — ${esc(h.notes || h.kind || '')}</li>`
              )
              .join('')}</ul></details>`
            : ''
        }
        <details open><summary>پیش‌نمایش متن</summary>
          ${data.snippet_note ? `<p class="dev-muted">${esc(data.snippet_note)}</p>` : ''}
          ${data.snippet != null ? `<pre class="dev-code">${esc(data.snippet)}</pre>` : '<p class="dev-muted">محتوای متنی در دسترس نیست.</p>'}
        </details>`;

      detailEl.querySelector('#cc-copy')?.addEventListener('click', async () => {
        try {
          await navigator.clipboard.writeText(f.path || path);
          setStatus(status, 'مسیر کپی شد.', 'ok');
        } catch (e) {
          setStatus(status, 'کپی ناموفق — مسیر: ' + (f.path || path), 'error');
        }
      });
      detailEl.querySelector('#cc-handoff-link')?.addEventListener('click', (e) => {
        e.preventDefault();
        state.tab = 'docs';
        route();
      });
    }

    root.querySelector('#cc-load').addEventListener('click', loadList);
    root.querySelector('#cc-scan').addEventListener('click', async () => {
      const btn = root.querySelector('#cc-scan');
      btn.disabled = true;
      setStatus(status, 'در حال اسکن فایل‌سیستم… (کاشی‌ها خلاصه می‌شوند)');
      const r = await api('code_catalog_scan');
      btn.disabled = false;
      setStatus(status, r.message || '', r.ok ? 'ok' : 'error');
      if (r.ok) await loadList();
    });
    let tmr;
    root.querySelector('#cc-q').addEventListener('input', () => {
      clearTimeout(tmr);
      tmr = setTimeout(loadList, 280);
    });
    ['cc-portal', 'cc-kind', 'cc-sort', 'cc-crit', 'cc-ex-tiles'].forEach((id) => {
      root.querySelector('#' + id).addEventListener('change', loadList);
    });

    await loadList();
    if (cc.selected) await openFile(cc.selected);
  }

  function renderGuide() {
    renderShell(`
      <section class="dev-panel">
        <h2>راهنمای توسعه‌دهنده</h2>
        <p class="dev-lead">چگونه از این پورتال برای بک‌آپ و دانش تیمی استفاده کنید.</p>
        <div class="dev-guide">
          <details open>
            <summary>فهرست پورتال‌ها</summary>
            <ul>
              <li><a href="${esc(cfg.base || '/acc-engine/')}">ERP / Holding</a> — <code>index.php</code></li>
              <li><a href="${esc(cfg.base || '/acc-engine/')}melk.php">Melk — اقتصاد ملک</a></li>
              <li><a href="${esc(cfg.base || '/acc-engine/')}wholesale.php">Wholesale — عمده‌فروشی</a></li>
              <li><a href="${esc(cfg.base || '/acc-engine/')}election.php">Election — هوادران</a></li>
              <li><a href="${esc(cfg.base || '/acc-engine/')}service.php">Service / HVAC</a></li>
              <li><a href="${esc(cfg.base || '/acc-engine/')}education.php">Education — EduBrain</a></li>
              <li><a href="${esc(cfg.base || '/acc-engine/')}shop.php">Shop — عرقیات</a></li>
              <li><a href="${esc(cfg.base || '/acc-engine/')}growth.php">Growth / سفیران — رشدگاه</a></li>
              <li><a href="${esc(cfg.base || '/acc-engine/')}dev.php">Dev Hub (همین صفحه)</a></li>
            </ul>
            <p class="dev-lead" style="margin-top:8px">جزئیات critical path و جداول: تب «مرکز مستندات تیم» → فیلتر پورتال، یا سند <code>docs/PORTALS-DEV-HANDOFF.md</code>.</p>
          </details>
          <details open>
            <summary>کاتالوگ کد و کاتالوگ دیتابیس</summary>
            <ul>
              <li>تب «کاتالوگ کد»: اسکن فایل‌سیستم (ورود پورتال، قالب، CSS/JS، تصویر، فونت، vendor، helper، docs، run-*)</li>
              <li>بسته‌های <code>tiles*</code> Leaflet فقط به‌صورت خلاصه (تعداد/حجم) ثبت می‌شوند</li>
              <li>تب «کاتالوگ دیتابیس»: جداول MySQL از INFORMATION_SCHEMA — جدا نگه داشته شده</li>
            </ul>
          </details>
          <details open>
            <summary>پشتیبان کد — چه چیزی داخل است؟</summary>
            <ul>
              <li>PHP، JS، CSS، قالب‌ها، includes، docs، configs</li>
              <li>فونت‌ها به‌صورت پیش‌فرض (اختیاری)</li>
              <li>بدون تصاویر پورتال و بدون کاشی Leaflet</li>
              <li><code>.env</code> حذف می‌شود مگر تیک را بردارید (توصیه: حذف بماند)</li>
            </ul>
          </details>
          <details open>
            <summary>بازیابی کد</summary>
            <ul>
              <li>آرشیو را در ریشهٔ پروژه باز کنید: <code>tar -xzf code-….tar.gz</code></li>
              <li>تصاویر و کاشی‌ها را جداگانه از بک‌آپ media یا سرور اصلی کپی کنید</li>
              <li>فایل‌های اسرار (<code>.env</code> / رمز DB) را دستی بازسازی کنید</li>
            </ul>
          </details>
          <details open>
            <summary>بازیابی دیتابیس</summary>
            <ul>
              <li><code>gunzip -c db-….sql.gz | mysql -u root DBNAME</code></li>
              <li>یا از phpMyAdmin → Import</li>
              <li>پس از restore، در صورت نیاز <code>run-*-install.php</code>های مهاجرت را دوباره نزنید مگر schema ناقص باشد</li>
            </ul>
          </details>
          <details open>
            <summary>چک‌لیست پروفایل critical path (از handoff)</summary>
            <ul>
              <li>Network → Doc: TTFB HTML</li>
              <li>CSS blocking / فونت لوکال فقط از <code>fonts-local.css</code></li>
              <li>JS: eager vs dynamic</li>
              <li>Img LCP فقط مسیرهای <code>/acc-engine/assets/img/…</code></li>
              <li>Fetch قبل از First Paint؟ Chat sync یا idle؟</li>
              <li>بدون unsplash / fonts.googleapis / tile.openstreetmap در first-load</li>
            </ul>
          </details>
          <details>
            <summary>محدودیت‌ها</summary>
            <ul>
              <li>روی Windows حجم آرشیو و زمان ساخت به تعداد فایل بستگی دارد</li>
              <li>ZipArchive در PHP این محیط فعال نیست — خروجی کد <code>tar.gz</code> است</li>
              <li>mysqldump ترجیحی: <code>C:\\xampp\\mysql\\bin\\mysqldump.exe</code></li>
              <li>دانلود فقط از طریق این پورتال (پوشه storage با htaccess قفل است)</li>
            </ul>
          </details>
        </div>
      </section>`);
  }

  async function route() {
    if (state.tab === 'docs') return renderDocs();
    if (state.tab === 'code_catalog') return renderCodeCatalog();
    if (state.tab === 'code') return renderCode();
    if (state.tab === 'db') return renderDb();
    if (state.tab === 'catalog') return renderCatalog();
    return renderGuide();
  }

  route();
})();
