/// Popup entry point — state machine with view routing.
///
/// Views: setup | locked | list | detail | add | edit | settings | settings-vault
/// Navigation works by updating `currentState` and calling `render()`.

import type { Request, Response } from '../shared/messages';
import { lookupErrorCopy } from '../shared/error-copy';
import { encodeBinary, decodeBinary } from '../shared/message-binary';
import type { ItemId, ManifestEntry, Item } from '../shared/types';
import { registerHost } from '../shared/state';
import { renderUnlock } from './components/unlock';
import { renderItemList } from './components/item-list';
import { renderItemDetail } from './components/item-detail';
import { renderItemForm } from './components/item-form';
import { renderSettings, teardownSettings } from './components/settings';
import { renderVaultSettings } from './components/settings-vault';
import { renderTrash } from './components/trash';
import { renderDevices } from './components/devices';
import { renderFieldHistory } from './components/field-history';
import { teardown as teardownTrash } from './components/trash';
import { teardown as teardownDevices } from './components/devices';
import { teardown as teardownFieldHistory } from './components/field-history';
import { applyColorScheme } from '../shared/color-scheme';

// --- Escape HTML to prevent XSS ---
export function escapeHtml(str: string): string {
  return str
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

// --- Pop out to tab ---

export function isInTab(): boolean {
  return window.location.search.length > 0;
}

export function openVaultTab(hash?: string): void {
  const url = chrome.runtime.getURL('vault.html') + (hash ? `#${hash}` : '');
  chrome.tabs.create({ url });
}

export function popOutToTab(): void {
  const state = getState();
  if (state.newType) {
    openVaultTab(`add/${state.newType}`);
  } else if (state.selectedId) {
    openVaultTab(`${state.view}/${state.selectedId}`);
  } else {
    openVaultTab();
  }
  window.close();
}

function parseUrlParams(): { view?: View; type?: string; id?: string } | null {
  const params = new URLSearchParams(window.location.search);
  const view = params.get('view');
  if (!view) return null;
  return {
    view: view as View,
    type: params.get('type') ?? undefined,
    id: params.get('id') ?? undefined,
  };
}

// --- State ---

import type { View, PopupState } from '../shared/popup-state';

let currentState: PopupState = {
  view: 'locked',
  entries: [],
  selectedId: null,
  selectedItem: null,
  selectedIndex: 0,
  searchQuery: '',
  activeGroup: null,
  error: null,
  loading: false,
  capturedTabId: null,
  capturedUrl: '',
  newType: null,
  vaultSettings: null,
  generatorDefaults: null,
  historyItemId: null,
};

export function getState(): PopupState {
  return currentState;
}

export function setState(partial: Partial<PopupState>): void {
  currentState = { ...currentState, ...partial };
  render();
}

// --- Messaging ---

export function sendMessage(request: Request): Promise<Response> {
  return new Promise((resolve) => {
    // Binary payloads (attachments, backups) are base64-enveloped for the
    // channel; decode the response before handing it back. See message-binary.ts.
    chrome.runtime.sendMessage(encodeBinary(request), (raw: Response) => {
      let response = decodeBinary(raw);
      if (response && !response.ok && response.error) {
        // Replace cryptic low-level errors with user-readable messages.
        response = { ok: false, error: humanizeError(response.error) };
      }
      resolve(response);
    });
  });
}

/// Translate cryptic Rust/serde/WASM error strings into messages a user
/// can act on. Unknown errors pass through unchanged.
export function humanizeError(err: string): string {
  // URL parse failures (Rust `url::Url::parse`) bubble up through serde
  // as `item json: ...`. Match the core phrasing.
  if (/relative URL without a base/i.test(err)) {
    return 'URL must start with https:// or http:// (e.g. https://example.com)';
  }
  if (/item json:/i.test(err)) {
    return 'Could not save item — one of the fields is in an invalid format.';
  }
  if (/settings json:/i.test(err)) {
    return 'Settings are in an invalid format — try reloading the extension.';
  }
  const copy = lookupErrorCopy(err);
  return copy.body;
}

// --- Navigation ---

export function navigate(view: View, extras?: Partial<PopupState>): void {
  setState({ view, error: null, loading: false, ...extras });
}

// --- Register as state host so shared components can call back ---

registerHost({
  getState: () => currentState,
  setState,
  navigate,
  sendMessage,
  escapeHtml,
  popOutToTab,
  isInTab,
  openVaultTab,
});

// --- Render ---

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

  teardownTrash();
  teardownDevices();
  teardownFieldHistory();
  teardownSettings();

  switch (currentState.view) {
    case 'locked':
      renderUnlock(app);
      break;
    case 'list':
      renderItemList(app);
      break;
    case 'detail':
      renderItemDetail(app);
      break;
    case 'add':
      renderItemForm(app, 'add');
      break;
    case 'edit':
      renderItemForm(app, 'edit');
      break;
    case 'settings':
      renderSettings(app);
      break;
    case 'settings-vault':
      renderVaultSettings(app);
      break;
    case 'trash':
      renderTrash(app);
      break;
    case 'devices':
      renderDevices(app);
      break;
    case 'field-history':
      renderFieldHistory(app);
      break;
  }
}

// --- Init ---

async function init(): Promise<void> {
  await applyColorScheme();

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

  // Snapshot the active tab at popup-open — the fill path uses this
  // tabId/url pair so the SW can verify the tab hasn't navigated before
  // forwarding credentials (audit M5 + TOCTOU close via expectedHost).
  const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
  currentState.capturedTabId = tab?.id ?? null;
  currentState.capturedUrl = tab?.url ?? '';

  // Check if extension is configured.
  const setupResp = await sendMessage({ type: 'get_setup_state' });
  if (setupResp.ok) {
    const data = setupResp.data as { isConfigured: boolean };
    if (!data.isConfigured) {
      await chrome.tabs.create({ url: chrome.runtime.getURL('setup.html') });
      window.close();
      return;
    }
  }

  // Check if vault is unlocked.
  const unlockResp = await sendMessage({ type: 'is_unlocked' });
  if (unlockResp.ok) {
    const data = unlockResp.data as { unlocked: boolean };
    if (data.unlocked) {
      // Load entries and go to list.
      const listResp = await sendMessage({ type: 'list_items' });
      if (listResp.ok) {
        const listData = listResp.data as { items: Array<[ItemId, ManifestEntry]> };
        // Fetch vault settings so subsequent screens (generator popover,
        // settings-vault) can show current values without a round-trip.
        // Failures swallow silently — list view still renders; consumers
        // can show "settings not loaded" if needed.
        const vsResp = await sendMessage({ type: 'get_vault_settings' });
        if (vsResp.ok) {
          const vs = (vsResp.data as { settings: import('../shared/types').VaultSettings }).settings;
          currentState.vaultSettings = vs;
          currentState.generatorDefaults = vs.generator_defaults;
        }

        // Check URL params for deep linking (when opened in tab)
        const urlParams = parseUrlParams();
        if (urlParams) {
          currentState.entries = listData.items;
          if (urlParams.view === 'add' && urlParams.type) {
            currentState.newType = urlParams.type as import('../shared/types').ItemType;
            navigate('add');
            return;
          }
          if ((urlParams.view === 'edit' || urlParams.view === 'detail') && urlParams.id) {
            // Fetch the item
            const itemResp = await sendMessage({ type: 'get_item', id: urlParams.id });
            if (itemResp.ok) {
              currentState.selectedId = urlParams.id;
              currentState.selectedItem = (itemResp.data as { item: Item }).item;
              navigate(urlParams.view);
              return;
            }
          }
        }

        navigate('list', { entries: listData.items });
        return;
      }
    }
  }

  navigate('locked');
}

document.addEventListener('DOMContentLoaded', () => {
  init();

  chrome.runtime.onMessage.addListener((msg) => {
    if (msg.type === 'session_expired') {
      currentState.view = 'locked';
      currentState.error = null;
      currentState.selectedItem = null;
      currentState.selectedId = null;
      render();
    }
  });
});
