/**
 * Application state — receives live updates from the proxy via WebSocket.
 */

import type { IContact } from '../../ts/config.ts';
import type { ICallHistoryEntry, ICallStatus, IDeviceStatus, IProviderStatus } from '../../ts/shared/status.ts';

export type { IContact };
export type { ICallHistoryEntry, ICallStatus, IDeviceStatus, IProviderStatus };
export type { ILegStatus } from '../../ts/shared/status.ts';

export interface IAppState {
  connected: boolean;
  browserDeviceId: string | null;
  uptime: number;
  providers: IProviderStatus[];
  devices: IDeviceStatus[];
  calls: ICallStatus[];
  callHistory: ICallHistoryEntry[];
  contacts: IContact[];
  selectedContact: IContact | null;
  logLines: string[];
  /** Unheard voicemail count per voicebox ID. */
  voicemailCounts: Record<string, number>;
}

const MAX_LOG = 200;
let knownInstanceId: string | null = null;

class AppStateManager {
  private state: IAppState = {
    connected: false,
    browserDeviceId: null,
    uptime: 0,
    providers: [],
    devices: [],
    calls: [],
    callHistory: [],
    contacts: [],
    selectedContact: null,
    logLines: [],
    voicemailCounts: {},
  };

  private listeners = new Set<(state: IAppState) => void>();
  private ws: WebSocket | null = null;

  getState(): IAppState {
    return this.state;
  }

  subscribe(listener: (state: IAppState) => void): () => void {
    this.listeners.add(listener);
    return () => this.listeners.delete(listener);
  }

  private update(partial: Partial<IAppState>): void {
    this.state = { ...this.state, ...partial };
    for (const fn of this.listeners) fn(this.state);
  }

  addLog(msg: string): void {
    const lines = [...this.state.logLines, msg];
    if (lines.length > MAX_LOG) lines.splice(0, lines.length - MAX_LOG);
    this.update({ logLines: lines });
  }

  connect(): void {
    // Guard against duplicate connections.
    if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) {
      return;
    }
    const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
    const ws = new WebSocket(`${proto}//${location.host}/ws`);

    ws.onopen = () => {
      this.ws = ws;
      (window as any).__sipRouterWs = ws;
      this.update({ connected: true });
    };

    ws.onclose = () => {
      this.ws = null;
      this.update({ connected: false });
      setTimeout(() => this.connect(), 2000);
    };

    ws.onerror = () => ws.close();

    ws.onmessage = (ev) => {
      try {
        const m = JSON.parse(ev.data);
        if (m.type === 'status') {
          // Auto-reload if backend restarted (different instance).
          if (m.data.instanceId) {
            if (knownInstanceId && knownInstanceId !== m.data.instanceId) {
              location.reload();
              return;
            }
            knownInstanceId = m.data.instanceId;
          }
          this.update({
            uptime: m.data.uptime,
            providers: m.data.providers || [],
            devices: m.data.devices || [],
            calls: m.data.calls || [],
            callHistory: m.data.callHistory || [],
            contacts: m.data.contacts || [],
            voicemailCounts: m.data.voicemailCounts || {},
          });
        } else if (m.type === 'log') {
          this.addLog(`${m.ts} ${m.data.message}`);
        } else if (m.type === 'call-update') {
          // Real-time call state update — will be picked up by next status snapshot.
        } else if (m.type?.startsWith('webrtc-')) {
          // Route to any registered WebRTC signaling handler.
          const handler = (window as any).__sipRouterWebRtcHandler;
          if (handler) handler(m);
        }
      } catch { /* ignore */ }
    };
  }

  setBrowserDeviceId(deviceId: string): void {
    this.update({ browserDeviceId: deviceId });
  }

  selectContact(contact: IContact | null): void {
    this.update({ selectedContact: contact });
  }

  clearSelectedContact(): void {
    this.update({ selectedContact: null });
  }

  async apiCall(number: string, deviceId?: string, providerId?: string): Promise<{ ok: boolean; callId?: string; error?: string }> {
    const res = await fetch('/api/call', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ number, deviceId, providerId }),
    });
    return res.json();
  }

  async apiHangup(callId: string): Promise<{ ok: boolean }> {
    const res = await fetch('/api/hangup', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ callId }),
    });
    return res.json();
  }

  async apiAddLeg(callId: string, deviceId: string): Promise<{ ok: boolean }> {
    const res = await fetch(`/api/call/${callId}/addleg`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ deviceId }),
    });
    return res.json();
  }

  async apiAddExternal(callId: string, number: string, providerId?: string): Promise<{ ok: boolean }> {
    const res = await fetch(`/api/call/${callId}/addexternal`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ number, providerId }),
    });
    return res.json();
  }

  async apiRemoveLeg(callId: string, legId: string): Promise<{ ok: boolean }> {
    const res = await fetch(`/api/call/${callId}/removeleg`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ legId }),
    });
    return res.json();
  }

  async apiTransfer(sourceCallId: string, legId: string, targetCallId: string): Promise<{ ok: boolean }> {
    const res = await fetch('/api/transfer', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ sourceCallId, legId, targetCallId }),
    });
    return res.json();
  }

  async apiGetConfig(): Promise<any> {
    const res = await fetch('/api/config');
    return res.json();
  }

  async apiSaveConfig(updates: any): Promise<{ ok: boolean }> {
    const res = await fetch('/api/config', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(updates),
    });
    return res.json();
  }
}

export const appState = new AppStateManager();
