/// Vault tab entry point — full "desktop-like" sidebar + pane layout.
///
/// Registers as the shared state host so popup components (item-detail,
/// item-form, trash, devices, settings, etc.) render natively in the
/// vault tab's pane area.

import type { Request, Response } from '../shared/messages';
import type {
  ItemId, ItemType, ManifestEntry, Item, VaultSettings, GeneratorRequest,
} from '../shared/types';
import { registerHost } from '../shared/state';
import { lookupErrorCopy, type ErrorCta } from '../shared/error-copy';
import { relativeTime } from '../shared/relative-time';
import {
  GLYPH_TRASH, GLYPH_DEVICES, GLYPH_SETTINGS, GLYPH_LOCK, GLYPH_HISTORY,
  GLYPH_TYPE_LOGIN, GLYPH_TYPE_SECURE_NOTE, GLYPH_TYPE_TOTP,
  GLYPH_TYPE_CARD, GLYPH_TYPE_IDENTITY, GLYPH_TYPE_KEY, GLYPH_TYPE_DOCUMENT,
} from '../shared/glyphs';
import { renderItemDetail } from '../popup/components/item-detail';
import { renderItemForm } from '../popup/components/item-form';
import { renderTrash, teardown as teardownTrash } from '../popup/components/trash';
import { renderDevices, teardown as teardownDevices } from '../popup/components/devices';
import { renderSettings, teardownSettings } from '../popup/components/settings';
import { renderVaultSettings as renderVaultSettingsView } from '../popup/components/settings-vault';
import { renderFieldHistory, teardown as teardownFieldHistory } from '../popup/components/field-history';
import { renderItemHistoryIndex, teardown as teardownHistoryIndex } from '../popup/components/item-history-index';
import { renderBackupPanel, teardown as teardownBackup } from './components/backup-panel';
import { renderImportPanel, teardown as teardownImport } from './components/import-panel';
import { applyColorScheme } from '../shared/color-scheme';

// ---------------------------------------------------------------------------
// Type picker (right side panel)
// ---------------------------------------------------------------------------

const PICKER_TYPES: Array<{ type: ItemType; label: string }> = [
  { type: 'login',       label: 'Login' },
  { type: 'secure_note', label: 'Secure Note' },
  { type: 'totp',        label: 'TOTP' },
  { type: 'card',        label: 'Card' },
  { type: 'identity',    label: 'Identity' },
  { type: 'key',         label: 'SSH / API Key' },
  { type: 'document',    label: 'Document' },
];

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

function sendMessage(request: Request): Promise<Response> {
  return new Promise((resolve) => {
    chrome.runtime.sendMessage(request, (response: Response) => {
      // MV3 service workers are evicted after ~30s idle, which wipes the
      // in-memory session/manifest/gitHost. The fullscreen tab stays open
      // and has no signal that the SW restarted — the next RPC just comes
      // back `vault_locked`. Treat that as "session lost" and force the
      // lock screen so the user can re-enter their passphrase. Skip for
      // is_unlocked / unlock themselves to avoid loops on cold start.
      if (
        response &&
        !response.ok &&
        response.error === 'vault_locked' &&
        request.type !== 'is_unlocked' &&
        request.type !== 'unlock' &&
        state.unlocked
      ) {
        state.unlocked = false;
        state.selectedId = null;
        state.selectedItem = null;
        state.entries = [];
        state.error = 'Session expired — please unlock again.';
        render();
      }
      resolve(response);
    });
  });
}

function escapeHtml(str: string): string {
  return str
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

function renderErrorBlock(code: string | null | undefined): string {
  if (!code) return '';
  const copy = lookupErrorCopy(code);
  const ctaHtml = copy.cta
    ? `<button class="btn btn-primary error-cta" data-cta="${escapeHtml(copy.cta.action ?? '')}">${escapeHtml(copy.cta.label)}</button>`
    : '';
  return `
    <div class="error error-block">
      <div class="error-title">${escapeHtml(copy.title)}</div>
      <div class="error-body">${escapeHtml(copy.body)}</div>
      ${ctaHtml}
    </div>
  `;
}

function typeIcon(t: ItemType): string {
  switch (t) {
    case 'login':       return GLYPH_TYPE_LOGIN;
    case 'secure_note': return GLYPH_TYPE_SECURE_NOTE;
    case 'identity':    return GLYPH_TYPE_IDENTITY;
    case 'card':        return GLYPH_TYPE_CARD;
    case 'key':         return GLYPH_TYPE_KEY;
    case 'document':    return GLYPH_TYPE_DOCUMENT;
    case 'totp':        return GLYPH_TYPE_TOTP;
  }
}

function typeLabel(t: ItemType): string {
  const labels: Record<ItemType, string> = {
    login: 'Login',
    secure_note: 'Secure Note',
    identity: 'Identity',
    card: 'Card',
    key: 'SSH / API Key',
    document: 'Document',
    totp: 'TOTP',
  };
  return labels[t];
}

// ---------------------------------------------------------------------------
// Hash routing
// ---------------------------------------------------------------------------

type VaultView = 'list' | 'detail' | 'add' | 'edit' | 'trash' | 'devices' | 'settings' | 'settings-vault' | 'field-history' | 'history' | 'backup' | 'import';

interface HashRoute {
  view: VaultView;
  id?: string;
  type?: string;
}

function parseHash(): HashRoute {
  let raw = window.location.hash.replace(/^#\/?/, '');
  if (!raw) return { view: 'list' };

  // Normalize legacy bookmarks: #field-history/<id> → #history/<id>
  if (raw.startsWith('field-history/')) {
    raw = 'history/' + raw.slice('field-history/'.length);
    window.location.hash = raw;
  }

  const parts = raw.split('/');
  const view = parts[0] as VaultView;

  switch (view) {
    case 'detail':
    case 'edit':
      return { view, id: parts[1] };
    case 'add':
      return { view, type: parts[1] };
    case 'history':
      return parts[1]
        ? { view: 'field-history', id: parts[1] }
        : { view: 'history' };
    case 'trash':
    case 'devices':
    case 'settings':
    case 'settings-vault':
    case 'field-history':
    case 'backup':
    case 'import':
      return { view };
    default:
      return { view: 'list' };
  }
}

function setHash(view: VaultView, param?: string): void {
  const fragment = param ? `${view}/${param}` : view;
  window.location.hash = fragment === 'list' ? '' : fragment;
}

// ---------------------------------------------------------------------------
// State
// ---------------------------------------------------------------------------

interface VaultState {
  unlocked: boolean;
  view: VaultView;
  entries: Array<[ItemId, ManifestEntry]>;
  selectedId: ItemId | null;
  selectedItem: Item | null;
  selectedIndex: number;
  searchQuery: string;
  activeGroup: string | null;
  drawerOpen: boolean;
  typePanelOpen: boolean;
  vaultSettings: VaultSettings | null;
  generatorDefaults: GeneratorRequest | null;
  error: string | null;
  loading: boolean;
  newType: ItemType | null;
  capturedTabId: number | null;
  capturedUrl: string;
  historyItemId: ItemId | null;
}

const state: VaultState = {
  unlocked: false,
  view: 'list',
  entries: [],
  selectedId: null,
  selectedItem: null,
  selectedIndex: 0,
  searchQuery: '',
  activeGroup: null,
  drawerOpen: false,
  typePanelOpen: false,
  vaultSettings: null,
  generatorDefaults: null,
  error: null,
  loading: false,
  newType: null,
  capturedTabId: null,
  capturedUrl: '',
  historyItemId: null,
};

// ---------------------------------------------------------------------------
// Register as shared state host
// ---------------------------------------------------------------------------

registerHost({
  getState: () => state,
  setState: (partial: any) => {
    Object.assign(state, partial);
    renderPane();
  },
  navigate: (view: string, extras?: any) => {
    Object.assign(state, { view, error: null, loading: false, ...extras });
    setHash(view as VaultView);
    applyShellViewClass();
    renderSidebarCategories();
    if (state.view === 'list') renderListPane();
    renderPane();
  },
  sendMessage,
  escapeHtml,
  popOutToTab: () => {},
  isInTab: () => true,
  openVaultTab: () => {},
});

// ---------------------------------------------------------------------------
// Render entry point
// ---------------------------------------------------------------------------

function render(): void {
  const app = document.getElementById('vault-app');
  if (!app) return;

  if (!state.unlocked) {
    renderLockScreen(app);
  } else {
    renderShell(app);
  }
}

// ---------------------------------------------------------------------------
// Lock screen
// ---------------------------------------------------------------------------

function renderLockScreen(app: HTMLElement): void {
  app.innerHTML = `
    <div class="vault-lock-screen">
      <img class="brand-logo" src="icons/relicario-logo.svg" alt="">
      <span class="brand">Relicario</span>
      <div class="vault-lock-screen__form">
        <input type="password" id="vault-passphrase" placeholder="passphrase" autocomplete="off" />
        <button class="btn btn-primary" id="vault-unlock-btn" style="width:100%;">unlock</button>
        ${renderErrorBlock(state.error)}
      </div>
    </div>
  `;

  const input = document.getElementById('vault-passphrase') as HTMLInputElement;
  const btn = document.getElementById('vault-unlock-btn')!;

  const doUnlock = async () => {
    const passphrase = input.value;
    if (!passphrase) return;
    btn.textContent = 'unlocking...';
    btn.setAttribute('disabled', 'true');
    const resp = await sendMessage({ type: 'unlock', passphrase });
    if (resp.ok) {
      state.unlocked = true;
      state.error = null;
      await loadManifest();
      render();
    } else {
      state.error = resp.error ?? 'unlock failed';
      render();
    }
  };

  btn.addEventListener('click', doUnlock);
  input.addEventListener('keydown', (e) => {
    if (e.key === 'Enter') doUnlock();
  });
  input.focus();
}

// ---------------------------------------------------------------------------
// Shell (3-column: sidebar + list pane + drawer)
// ---------------------------------------------------------------------------

function renderShell(app: HTMLElement): void {
  if (!app.querySelector('.vault-shell')) {
    app.innerHTML = `
      <div class="vault-shell">
        <div class="vault-sidebar">
          <div class="vault-sidebar__header">
            <img class="brand-logo" src="icons/relicario-logo.svg" alt="">
            <span class="brand">Relicario</span>
          </div>
          <div class="vault-sidebar__search">
            <input type="text" id="vault-search" placeholder="/ search…" />
          </div>
          <nav class="vault-sidebar__categories" id="vault-categories" aria-label="Item types"></nav>
          <div class="vault-sidebar__nav">
            <button class="vault-sidebar__nav-item vault-sidebar__nav-item--primary" data-nav="add" title="New item">+ new item</button>
            <button class="vault-sidebar__nav-item" data-nav="trash" title="Trash">${GLYPH_TRASH} <span class="vault-sidebar__nav-label">trash</span></button>
            <button class="vault-sidebar__nav-item" data-nav="devices" title="Devices">${GLYPH_DEVICES} <span class="vault-sidebar__nav-label">devices</span></button>
            <button class="vault-sidebar__nav-item" data-nav="settings" title="Settings">${GLYPH_SETTINGS} <span class="vault-sidebar__nav-label">settings</span></button>
            <button class="vault-sidebar__nav-item" data-nav="history" title="History">${GLYPH_HISTORY} <span class="vault-sidebar__nav-label">history</span></button>
            <button class="vault-sidebar__nav-item" data-nav="lock" title="Lock">${GLYPH_LOCK} <span class="vault-sidebar__nav-label">lock</span></button>
          </div>
        </div>
        <div class="vault-list-pane" id="vault-list-pane"></div>
        <div class="vault-pane" id="vault-pane"></div>
        <div class="vault-drawer" id="vault-drawer"></div>
        <div class="vault-type-panel-scrim" id="vault-type-scrim"></div>
        <aside class="vault-type-panel" id="vault-type-panel" aria-label="Choose item type"></aside>
      </div>
    `;
    wireSidebar();
    wireTypePanel();
  }

  applyShellViewClass();
  renderSidebarCategories();
  if (state.view === 'list') {
    renderListPane();
    if (state.drawerOpen && state.selectedItem) {
      renderDrawer(state.selectedItem);
    }
  } else {
    renderPane();
  }
}

// Toggle which middle column is visible based on the current view.
// list view → list-pane (+ optional drawer); other views → vault-pane.
function applyShellViewClass(): void {
  const shell = document.querySelector('.vault-shell');
  if (!shell) return;
  shell.classList.toggle('vault-shell--list', state.view === 'list');
  shell.classList.toggle('vault-shell--pane', state.view !== 'list');
}

// ---------------------------------------------------------------------------
// Right-side type picker panel
// ---------------------------------------------------------------------------

function wireTypePanel(): void {
  document.getElementById('vault-type-scrim')?.addEventListener('click', closeTypePanel);
  document.addEventListener('keydown', (e) => {
    if (e.key === 'Escape' && state.typePanelOpen) closeTypePanel();
  });
}

function openTypePanel(): void {
  const panel = document.getElementById('vault-type-panel');
  const scrim = document.getElementById('vault-type-scrim');
  if (!panel || !scrim) return;

  panel.innerHTML = `
    <div class="vault-type-panel__head">
      <div class="vault-type-panel__title">New item</div>
      <button class="vault-type-panel__close" id="vault-type-close" title="Close (Esc)" aria-label="Close">✕</button>
    </div>
    <div class="vault-type-panel__hint">Choose a type</div>
    <div class="vault-type-list" role="menu">
      ${PICKER_TYPES.map((t) => `
        <button class="vault-type-item" data-type="${t.type}" role="menuitem">
          <span class="vault-type-item__icon" aria-hidden="true">${typeIcon(t.type)}</span>
          <span class="vault-type-item__name">${escapeHtml(t.label)}</span>
        </button>
      `).join('')}
    </div>
  `;

  panel.classList.add('vault-type-panel--open');
  scrim.classList.add('vault-type-panel-scrim--visible');
  state.typePanelOpen = true;

  panel.querySelector('#vault-type-close')?.addEventListener('click', closeTypePanel);

  panel.querySelectorAll<HTMLButtonElement>('[data-type]').forEach((btn) => {
    btn.addEventListener('click', () => {
      const type = btn.dataset.type as ItemType;
      closeTypePanel();
      // Use the host's navigate hook so view + hash + visibility all update
      // together. This was the bug: bare setHash + renderPane left the
      // shell stuck in list view with #vault-pane hidden.
      state.newType = type;
      state.selectedId = null;
      state.selectedItem = null;
      state.drawerOpen = false;
      state.view = 'add';
      setHash('add', type);
      applyShellViewClass();
      renderSidebarCategories();
      renderPane();
    });
  });

  // Focus first item for keyboard users
  (panel.querySelector('.vault-type-item') as HTMLElement | null)?.focus();
}

function closeTypePanel(): void {
  document.getElementById('vault-type-panel')?.classList.remove('vault-type-panel--open');
  document.getElementById('vault-type-scrim')?.classList.remove('vault-type-panel-scrim--visible');
  state.typePanelOpen = false;
}

// ---------------------------------------------------------------------------
// Drawer (implemented in Task 10)
// ---------------------------------------------------------------------------

function openDrawer(): void {
  document.getElementById('vault-drawer')?.classList.add('vault-drawer--open');
}

function closeDrawer(): void {
  state.drawerOpen = false;
  state.selectedId = null;
  state.selectedItem = null;
  document.getElementById('vault-drawer')?.classList.remove('vault-drawer--open');
}

function getDrawerCoreFields(item: Item): Array<[string, string, boolean]> {
  const core = item.core as unknown as Record<string, unknown>;
  if (!core) return [];
  const fields: Array<[string, string, boolean]> = [];

  switch (item.type) {
    case 'login':
      if ('username' in core) fields.push(['username', String(core.username ?? ''), false]);
      if ('password' in core) fields.push(['password', '••••••••', false]);
      if ('url' in core)      fields.push(['url', String(core.url ?? ''), true]);
      break;
    case 'card': {
      if ('number' in core)  fields.push(['number', String(core.number ?? ''), false]);
      if ('holder' in core)  fields.push(['holder', String(core.holder ?? ''), false]);
      if ('expiry' in core && core.expiry) {
        const exp = core.expiry as { month: number; year: number };
        fields.push(['expiry', `${String(exp.month).padStart(2, '0')}/${exp.year}`, false]);
      }
      if ('cvv' in core)     fields.push(['cvv', '•••', false]);
      if ('pin' in core)     fields.push(['pin', '••••', false]);
      break;
    }
    case 'identity':
      if ('full_name' in core)     fields.push(['full name', String(core.full_name ?? ''), true]);
      if ('email' in core)         fields.push(['email', String(core.email ?? ''), true]);
      if ('phone' in core)         fields.push(['phone', String(core.phone ?? ''), false]);
      if ('address' in core)       fields.push(['address', String(core.address ?? ''), true]);
      if ('date_of_birth' in core) fields.push(['date of birth', String(core.date_of_birth ?? ''), false]);
      break;
    case 'key':
      if ('label' in core)      fields.push(['label', String(core.label ?? ''), true]);
      if ('algorithm' in core)  fields.push(['algorithm', String(core.algorithm ?? ''), false]);
      if ('public_key' in core) fields.push(['public key', String(core.public_key ?? ''), true]);
      break;
    case 'secure_note':
      if ('body' in core) fields.push(['body', String(core.body ?? ''), true]);
      break;
    case 'totp':
      if ('issuer' in core) fields.push(['issuer', String(core.issuer ?? ''), false]);
      if ('label' in core)  fields.push(['label', String(core.label ?? ''), false]);
      break;
    case 'document':
      if ('filename' in core)  fields.push(['filename', String(core.filename ?? ''), true]);
      if ('mime_type' in core) fields.push(['type', String(core.mime_type ?? ''), false]);
      break;
  }

  if (item.notes) fields.push(['notes', item.notes, true]);
  return fields;
}

function renderDrawer(item: Item): void {
  const drawer = document.getElementById('vault-drawer');
  if (!drawer) return;

  const coreFields = getDrawerCoreFields(item);

  drawer.innerHTML = `
    <div class="vault-drawer__header">
      <span class="vault-drawer__type-pill">${item.type.replace('_', ' ').toUpperCase()}</span>
      <div class="vault-drawer__actions">
        <button class="btn" id="drawer-edit-btn" style="font-size:11px;">edit</button>
        <button class="vault-drawer__close" id="drawer-close-btn" title="Close (Esc)">✕</button>
      </div>
    </div>
    <div class="vault-drawer__body">
      <div class="vault-drawer__title">${escapeHtml(item.title)}</div>
      ${item.type === 'login' && (item.core as { url?: string }).url
        ? `<div class="vault-drawer__subtitle">${escapeHtml((item.core as { url?: string }).url ?? '')}</div>`
        : ''}
      <div class="vault-drawer__field-grid">
        ${coreFields.map(([label, value, full]) => `
          <div class="vault-drawer__field${full ? ' vault-drawer__field--full' : ''}">
            <div class="vault-drawer__field-label">${escapeHtml(label)}</div>
            <div class="vault-drawer__field-value">${escapeHtml(value)}</div>
          </div>
        `).join('')}
      </div>
    </div>
  `;

  document.getElementById('drawer-close-btn')?.addEventListener('click', () => {
    closeDrawer();
    renderListPane();
  });

  document.getElementById('drawer-edit-btn')?.addEventListener('click', () => {
    if (state.selectedId) {
      setHash('edit', state.selectedId);
      renderPane();
    }
  });
}

// ---------------------------------------------------------------------------
// Item selection (implemented in Task 10)
// ---------------------------------------------------------------------------

async function selectItemForDrawer(id: string): Promise<void> {
  const resp = await sendMessage({ type: 'get_item', id });
  if (!resp.ok) return;
  const data = resp.data as { item: Item };
  state.selectedId = id;
  state.selectedItem = data.item;
  state.drawerOpen = true;
  renderSidebarCategories();
  renderListPane();
  renderDrawer(data.item);
  openDrawer();
}

// ---------------------------------------------------------------------------
// Sidebar wiring
// ---------------------------------------------------------------------------

function wireSidebar(): void {
  // Search
  const searchInput = document.getElementById('vault-search') as HTMLInputElement | null;
  searchInput?.addEventListener('input', () => {
    state.searchQuery = searchInput.value;
    renderSidebarCategories();
    renderListPane();
  });

  // Nav buttons
  document.querySelectorAll('.vault-sidebar__nav-item').forEach((btn) => {
    btn.addEventListener('click', async () => {
      const nav = (btn as HTMLElement).dataset.nav;
      if (nav === 'lock') {
        await sendMessage({ type: 'lock' });
        state.unlocked = false;
        state.selectedId = null;
        state.selectedItem = null;
        state.entries = [];
        render();
        return;
      }
      if (nav === 'add') {
        state.selectedId = null;
        state.selectedItem = null;
        state.newType = null;
        state.drawerOpen = false;
        closeDrawer();
        openTypePanel();
        return;
      }
      if (nav === 'trash' || nav === 'devices' || nav === 'settings' || nav === 'history') {
        state.selectedId = null;
        state.selectedItem = null;
        state.newType = null;
        state.drawerOpen = false;
        state.view = nav;
        setHash(nav);
        applyShellViewClass();
        renderPane();
        return;
      }
    });
  });

  // Global "/" shortcut to focus search; Esc to close drawer
  document.addEventListener('keydown', (e) => {
    if (e.key === '/' && !isEditableTarget(e.target)) {
      e.preventDefault();
      searchInput?.focus();
      return;
    }
    if (e.key === 'Escape' && state.drawerOpen) {
      closeDrawer();
      renderListPane();
    }
  });
}

function isEditableTarget(target: EventTarget | null): boolean {
  if (!(target instanceof HTMLElement)) return false;
  const tag = target.tagName;
  if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return true;
  if (target.isContentEditable) return true;
  return false;
}

// ---------------------------------------------------------------------------
// Sidebar category nav
// ---------------------------------------------------------------------------

function getFilteredEntries(): Array<[ItemId, ManifestEntry]> {
  let filtered = state.entries.filter(
    ([, e]) => e.trashed_at === undefined || e.trashed_at === null,
  );
  if (state.searchQuery) {
    const q = state.searchQuery.toLowerCase();
    filtered = filtered.filter(([, e]) => {
      if (e.title.toLowerCase().includes(q)) return true;
      if (e.icon_hint?.toLowerCase().includes(q)) return true;
      if (e.group?.toLowerCase().includes(q)) return true;
      if (e.tags.some((t) => t.toLowerCase().includes(q))) return true;
      return false;
    });
  }
  filtered.sort((a, b) => a[1].title.localeCompare(b[1].title));
  return filtered;
}

function renderSidebarCategories(): void {
  const container = document.getElementById('vault-categories');
  if (!container) return;

  const filtered = getFilteredEntries();
  const typeOrder: ItemType[] = ['login', 'secure_note', 'identity', 'card', 'key', 'document', 'totp'];

  const allCount = filtered.length;
  const isAllActive = !state.activeGroup && state.view === 'list';

  let html = `
    <button class="vault-category-row ${isAllActive ? 'vault-category-row--active' : ''}" data-group="">
      <span class="vault-category-row__icon">◈</span>
      <span class="vault-category-row__label vault-sidebar__category-label">All items</span>
      <span class="vault-category-row__count vault-sidebar__category-count">${allCount}</span>
    </button>
  `;

  for (const t of typeOrder) {
    const count = filtered.filter(([, e]) => e.type === t).length;
    // Always show Login (staple type); hide other types when empty.
    if (count === 0 && t !== 'login') continue;
    const isActive = state.activeGroup === t;
    html += `
      <button class="vault-category-row ${isActive ? 'vault-category-row--active' : ''}" data-group="${t}">
        <span class="vault-category-row__icon">${typeIcon(t)}</span>
        <span class="vault-category-row__label vault-sidebar__category-label">${typeLabel(t)}</span>
        <span class="vault-category-row__count vault-sidebar__category-count">${count}</span>
      </button>
    `;
  }

  container.innerHTML = html;

  container.querySelectorAll<HTMLButtonElement>('.vault-category-row').forEach((btn) => {
    btn.addEventListener('click', () => {
      state.activeGroup = btn.dataset.group || null;
      state.drawerOpen = false;
      state.selectedId = null;
      state.selectedItem = null;
      state.view = 'list';
      setHash('list');
      applyShellViewClass();
      renderSidebarCategories();
      renderListPane();
      closeDrawer();
    });
  });
}

// ---------------------------------------------------------------------------
// List pane
// ---------------------------------------------------------------------------

function renderListPane(): void {
  const pane = document.getElementById('vault-list-pane');
  if (!pane) return;

  const group = state.activeGroup as ItemType | null;
  let items = getFilteredEntries();
  if (group) items = items.filter(([, e]) => e.type === group);

  if (items.length === 0) {
    pane.innerHTML = `
      <div class="empty-state">
        <span class="empty-state__icon" aria-hidden="true">${state.searchQuery ? '⊘' : '◈'}</span>
        <div class="empty-state__title">${state.searchQuery ? `No results for "${escapeHtml(state.searchQuery)}"` : 'No items yet'}</div>
        <div class="empty-state__hint">${state.searchQuery ? 'Try a shorter search term.' : 'Click + new item to get started.'}</div>
      </div>
    `;
    return;
  }

  pane.innerHTML = items.map(([id, e]) => {
    const sel = id === state.selectedId ? ' vault-list-row--selected' : '';
    const subtitle = (e as any).icon_hint ?? (e.tags?.length > 0 ? e.tags.join(', ') : '');
    const modifiedAgo = e.modified ? relativeTime(e.modified) : '';
    return `
      <div class="vault-list-row${sel}" data-id="${escapeHtml(id)}">
        <div class="vault-list-row__icon" aria-hidden="true">${typeIcon(e.type)}</div>
        <div class="vault-list-row__text">
          <div class="vault-list-row__title">${escapeHtml(e.title)}</div>
          ${subtitle ? `<div class="vault-list-row__subtitle">${escapeHtml(subtitle)}</div>` : ''}
        </div>
        ${modifiedAgo ? `<div class="vault-list-row__age">${escapeHtml(modifiedAgo)}</div>` : ''}
      </div>
    `;
  }).join('');

  pane.querySelectorAll<HTMLElement>('.vault-list-row').forEach((row) => {
    row.addEventListener('click', async () => {
      await selectItemForDrawer(row.dataset.id!);
    });
  });
}

// ---------------------------------------------------------------------------
// Platform-aware save hint
// ---------------------------------------------------------------------------

const isMac = navigator.platform.toLowerCase().includes('mac');
const SAVE_HINT = isMac ? '⌘+S to save' : 'Ctrl+S to save';

// ---------------------------------------------------------------------------
// Fullscreen form wrapper — sticky save bar + scrollable content + header
// ---------------------------------------------------------------------------

function renderFormWrapped(app: HTMLElement, mode: 'add' | 'edit'): void {
  const itemType = state.selectedItem?.type ?? state.newType ?? 'login';
  const typeLabelText = itemType.replace('_', ' ');
  const titleText = mode === 'add' ? `new ${typeLabelText}` : `edit ${typeLabelText}`;
  const wrapper = document.createElement('div');
  wrapper.className = 'form-pane';
  wrapper.innerHTML = `
    <div class="fullscreen-form-header">
      <div>
        <div class="title">${titleText}</div>
        <div class="sub" id="form-dirty-sub">no changes</div>
      </div>
      <div class="hint">${SAVE_HINT}</div>
    </div>
    <div class="form-scroll" id="form-scroll"></div>
    <div class="sticky-save-bar">
      <button class="btn-secondary" id="form-cancel">cancel</button>
      <button class="btn-primary" id="form-save">save</button>
    </div>
  `;
  // Remove pane padding so form-pane can fill height cleanly
  app.style.padding = '0';
  app.style.overflow = 'hidden';
  app.replaceChildren(wrapper);

  const scrollEl = wrapper.querySelector('#form-scroll') as HTMLElement;
  renderItemForm(scrollEl, mode);

  const subEl = wrapper.querySelector('#form-dirty-sub') as HTMLElement;
  let isDirty = false;
  const markDirty = () => {
    if (isDirty) return;
    isDirty = true;
    subEl.textContent = 'unsaved · esc to cancel';
  };
  const markClean = () => {
    isDirty = false;
    subEl.textContent = 'no changes';
  };
  scrollEl.addEventListener('input', markDirty, true);
  scrollEl.addEventListener('change', markDirty, true);

  wrapper.querySelector('#form-cancel')?.addEventListener('click', () => {
    markClean();
    (scrollEl.querySelector('#cancel-btn') as HTMLButtonElement | null)?.click();
  });
  wrapper.querySelector('#form-save')?.addEventListener('click', () => {
    markClean();
    (scrollEl.querySelector('#save-btn') as HTMLButtonElement | null)?.click();
  });
}

export const __test__ = { renderFormWrapped };

// ---------------------------------------------------------------------------
// Pane rendering — delegates to shared popup components
// ---------------------------------------------------------------------------

function teardownPaneComponents(): void {
  teardownTrash();
  teardownDevices();
  teardownSettings();
  teardownFieldHistory();
  teardownHistoryIndex();
  teardownBackup();
  teardownImport();
}

function renderPane(): void {
  const pane = document.getElementById('vault-pane');
  if (!pane) return;

  teardownPaneComponents();

  const route = parseHash();
  // Keep state.view in sync with hash for components that read it
  state.view = route.view;
  applyShellViewClass();

  pane.className = 'vault-pane';

  switch (route.view) {
    case 'detail':
      if (state.selectedItem) {
        renderItemDetail(pane);
      } else {
        pane.className = 'vault-pane vault-pane--empty';
        pane.innerHTML = 'select an item';
      }
      break;
    case 'add':
      // Prefer hash type for deep-links; otherwise keep the in-memory value
      // set by the type-selection click handler (which calls setState →
      // renderPane before the URL hash has been updated to include the type).
      state.newType = (route.type as ItemType) ?? state.newType ?? null;
      // Use the form wrapper (sticky bar + header) when a type is already chosen.
      // Without a type the type-selection screen renders — no sticky bar needed.
      if (state.newType) {
        renderFormWrapped(pane, 'add');
      } else {
        renderItemForm(pane, 'add');
      }
      break;
    case 'edit':
      renderFormWrapped(pane, 'edit');
      break;
    case 'trash':
      renderTrash(pane);
      break;
    case 'devices':
      renderDevices(pane);
      break;
    case 'settings':
      void renderSettings(pane);
      break;
    case 'settings-vault':
      renderVaultSettingsView(pane);
      break;
    case 'field-history':
      renderFieldHistory(pane);
      break;
    case 'history':
      renderItemHistoryIndex(pane);
      break;
    case 'backup':
      renderBackupPanel(pane);
      break;
    case 'import':
      renderImportPanel(pane);
      break;
    default:
      pane.className = 'vault-pane vault-pane--empty';
      pane.innerHTML = 'select an item';
      break;
  }
}

// ---------------------------------------------------------------------------
// Data loading
// ---------------------------------------------------------------------------

async function loadManifest(): Promise<void> {
  const listResp = await sendMessage({ type: 'list_items' });
  if (listResp.ok) {
    const data = listResp.data as { items: Array<[ItemId, ManifestEntry]> };
    state.entries = data.items;
  }

  const vsResp = await sendMessage({ type: 'get_vault_settings' });
  if (vsResp.ok) {
    const data = vsResp.data as { settings: VaultSettings };
    state.vaultSettings = data.settings;
    state.generatorDefaults = data.settings.generator_defaults;
  }

  // Handle deep link from hash
  const route = parseHash();
  if (route.view === 'detail' && route.id) {
    const itemResp = await sendMessage({ type: 'get_item', id: route.id });
    if (itemResp.ok) {
      const data = itemResp.data as { item: Item };
      state.selectedId = route.id;
      state.selectedItem = data.item;
    }
  }
}

// ---------------------------------------------------------------------------
// Init
// ---------------------------------------------------------------------------

document.addEventListener('DOMContentLoaded', async () => {
  await applyColorScheme();

  chrome.storage.onChanged.addListener((changes, area) => {
    if (area === 'sync' && 'password_display_scheme' in changes) {
      void applyColorScheme();
    }
  });

  // Delegated handler for .error-cta buttons — set up once on the stable root.
  const app = document.getElementById('vault-app')!;
  app.addEventListener('click', (e) => {
    const btn = (e.target as HTMLElement).closest<HTMLButtonElement>('.error-cta');
    if (!btn) return;
    const cta = btn.dataset.cta as ErrorCta['action'];
    switch (cta) {
      case 'unlock': {
        document.getElementById('vault-passphrase')?.focus();
        break;
      }
      case 'open_setup': {
        void chrome.tabs.create({ url: chrome.runtime.getURL('setup.html') });
        break;
      }
      case 'reload_extension': {
        chrome.runtime.reload();
        break;
      }
    }
  });

  // Check if already unlocked
  const resp = await sendMessage({ type: 'is_unlocked' });
  if (resp.ok) {
    const data = resp.data as { unlocked: boolean };
    if (data.unlocked) {
      state.unlocked = true;
      await loadManifest();
    }
  }

  render();

  // Session expired listener
  chrome.runtime.onMessage.addListener((msg) => {
    if (msg.type === 'session_expired') {
      state.unlocked = false;
      state.selectedId = null;
      state.selectedItem = null;
      state.entries = [];
      state.error = null;
      render();
    }
  });

  // Hash change listener
  window.addEventListener('hashchange', () => {
    if (!state.unlocked) return;

    const route = parseHash();
    state.view = route.view;
    applyShellViewClass();

    // If navigating to a detail/edit view for an item we already have loaded
    if ((route.view === 'detail' || route.view === 'edit') && route.id) {
      if (state.selectedId === route.id && state.selectedItem) {
        renderPane();
        renderSidebarCategories();
        if (state.view === 'list') renderListPane();
        return;
      }
      // Need to fetch the item
      selectItem(route.id);
      return;
    }

    // For non-item views, just re-render the pane
    state.selectedId = null;
    state.selectedItem = null;
    renderSidebarCategories();
    if (state.view === 'list') renderListPane();
    renderPane();
  });
});

// ---------------------------------------------------------------------------
// Legacy selectItem — used by hash-change deep linking
// ---------------------------------------------------------------------------

async function selectItem(id: ItemId): Promise<void> {
  state.loading = true;
  const resp = await sendMessage({ type: 'get_item', id });
  if (resp.ok) {
    const data = resp.data as { item: Item };
    state.selectedId = id;
    state.selectedItem = data.item;
    state.loading = false;
    setHash('detail', id);
    renderSidebarCategories();
    renderListPane();
    renderPane();
  } else {
    state.loading = false;
    state.error = (resp as { error: string }).error;
  }
}
