// extension/src/shared/state.ts
//
// Single channel for popup and vault-tab UI to read/write app state and
// dispatch messages to the service worker. Two registered hosts (popup,
// vault tab) implement StateHost; each surface calls registerHost(this) at
// boot.
//
// The vault_locked intercept (lines 47-74 in vault.ts pre-Phase-4) lifts
// into sendMessage() here in Phase 4. Phase 1 lays the wrapper signature;
// the body is a thin pass-through until Phase 4.

import type { Request, Response } from './messages';
import type { PopupState, View } from './popup-state';

export interface StateHost {
  getState(): PopupState;
  setState(partial: Partial<PopupState>): void;
  navigate(view: View, extras?: Partial<PopupState>): void;
  sendMessage(request: Request): Promise<Response>;
  escapeHtml(s: string): string;
  popOutToTab(): void;
  isInTab(): boolean;
  openVaultTab(hash?: string): void;
}

let host: StateHost | null = null;

export function registerHost(h: StateHost): void {
  if (host) throw new Error('state host already registered');
  host = h;
}

/** Test-only — vitest beforeEach() calls this to break inter-test leakage. */
export function __resetHostForTests(): void {
  host = null;
}

export function getState(): PopupState {
  if (!host) throw new Error('No state host registered');
  return host.getState();
}

export function setState(partial: Partial<PopupState>): void {
  if (!host) throw new Error('No state host registered');
  host.setState(partial);
}

export function navigate(view: View, extras?: Partial<PopupState>): void {
  if (!host) throw new Error('No state host registered');
  host.navigate(view, extras);
}

// Requests that must NOT trigger the lock screen on a vault_locked response:
// they run during cold start / unlock, before a session exists, so a
// vault_locked here is expected rather than a lost session.
const VAULT_LOCKED_BYPASS: ReadonlySet<Request['type']> = new Set([
  'unlock', 'is_unlocked',
]);

/**
 * Dispatches a request to the service worker and intercepts the `vault_locked`
 * response. MV3 evicts the service worker after ~30s idle, wiping the in-memory
 * session/manifest; the next RPC comes back `vault_locked`. Any surface (popup
 * or vault tab) that gets that on a non-bypassed request treats it as "session
 * lost" and navigates to the lock screen so the user can re-enter their
 * passphrase. Lifted here from vault.ts's local sendMessage in Plan C Phase 4
 * so both surfaces share one channel.
 */
export async function sendMessage(request: Request): Promise<Response> {
  if (!host) throw new Error('No state host registered');
  const response = await host.sendMessage(request);
  if (
    !response.ok &&
    response.error === 'vault_locked' &&
    !VAULT_LOCKED_BYPASS.has(request.type)
  ) {
    host.navigate('locked', { error: 'Session expired — please unlock again.' });
  }
  return response;
}

export function escapeHtml(s: string): string {
  if (!host) throw new Error('No state host registered');
  return host.escapeHtml(s);
}

export function popOutToTab(): void {
  if (!host) throw new Error('No state host registered');
  host.popOutToTab();
}

export function isInTab(): boolean {
  if (!host) throw new Error('No state host registered');
  return host.isInTab();
}

export function openVaultTab(hash?: string): void {
  if (!host) throw new Error('No state host registered');
  host.openVaultTab(hash);
}
