/**
 * Shared protocol for Mephonecast (desktop) and Phonecap (Nolimits Evolution — features/phonecap).
 * JSON over WebSocket. LAN only.
 *
 * Versioning:
 * - PROTOCOL_VERSION: highest protocol this desktop build fully supports (phone should send this in register).
 * - MIN_PROTOCOL_VERSION: oldest client protocol still accepted. Raise only when you intentionally drop old apps.
 *
 * Release discipline (keep desktop + Phonecap shippable on different days):
 * 1. Prefer additive JSON (optional fields) so you do not need a version bump.
 * 2. Bump PROTOCOL_VERSION only when both sides must agree on new message shapes; keep MIN_PROTOCOL_VERSION
 *    unchanged until App Store has caught up, so old phones still connect.
 * 3. For new optional commands, add capability flags on register; desktop skips commands when capability is false.
 *    Omitted capability = assumed supported (legacy clients that only sent { platform }).
 * 4. Run: npm test (includes verify-protocol-sync) before merge.
 * 5. When you raise MIN_PROTOCOL_VERSION, plan a comms / store note: “Update Phonecap to connect.”
 * 6. Ship order: widen desktop range first, then raise phone PROTOCOL_VERSION, then later raise MIN on desktop
 *    if you remove old paths.
 *
 * Keep NolimitsEvolution/features/phonecap/src/lib/protocol.js in sync (run npm run verify:protocol).
 */
const PROTOCOL_VERSION = 1;
/** Desktop accepts register messages where MIN_PROTOCOL_VERSION <= protocolVersion <= PROTOCOL_VERSION. */
const MIN_PROTOCOL_VERSION = 1;

/** register.capabilities keys — optional; if missing, desktop assumes the feature is supported. */
const CAPABILITY_CAMERA_ZOOM = 'cameraZoom';
const CAPABILITY_HUD_LAYOUT = 'hudLayout';

/**
 * Whether a client protocol integer is acceptable for this desktop build.
 * @param {unknown} v - protocolVersion from register
 * @returns {boolean}
 */
function isSupportedClientProtocolVersion(v) {
  return (
    typeof v === 'number' &&
    Number.isInteger(v) &&
    v >= MIN_PROTOCOL_VERSION &&
    v <= PROTOCOL_VERSION
  );
}

// Ports — desktop reads MEPHONECAST_WS_PORT from env (loaded before this module in Electron main).
function resolveDesktopWsPort() {
  const raw = typeof process !== 'undefined' && process.env && process.env.MEPHONECAST_WS_PORT;
  if (raw != null && String(raw).trim() !== '') {
    const n = parseInt(String(raw).trim(), 10);
    if (Number.isFinite(n) && n >= 1024 && n <= 65535) return n;
    console.warn('[mephonecast/protocol] Invalid MEPHONECAST_WS_PORT, using default 8765');
  }
  return 8765;
}

const WS_PORT = resolveDesktopWsPort(); // Desktop WebSocket server (phones connect here; may differ at runtime in dev if port busy)
const PHONE_HTTP_PORT = 8080; // Reserved for future phone HTTP file server

// Commands (desktop -> phone)
const CMD_START_RECORDING = 'START_RECORDING';
const CMD_STOP_RECORDING = 'STOP_RECORDING';
const CMD_PAUSE_RECORDING = 'PAUSE_RECORDING';
const CMD_STATUS = 'STATUS';
const CMD_MARKER = 'MARKER';
const CMD_SET_MIC_SOURCE = 'SET_MIC_SOURCE';
const CMD_SET_MIC_LEVEL = 'SET_MIC_LEVEL';
const CMD_SET_MIC_AGC = 'SET_MIC_AGC';
const CMD_SET_RECORD_EXCLUDED = 'SET_RECORD_EXCLUDED';
const CMD_REQUEST_RECORDING_VIA_WS = 'REQUEST_RECORDING_VIA_WS';
/** Phonecap full-screen HUD: flat portrait vs cinematic strip — driven from Mephonecast orientation button. */
const CMD_SET_HUD_LAYOUT = 'SET_HUD_LAYOUT';
/** Phone camera zoom factor (≥1). Applied on the WebRTC capture track so the OS can use optical multi-cam when available (not CSS stretch). */
const CMD_SET_CAMERA_ZOOM = 'SET_CAMERA_ZOOM';
/** Desktop → phone: show “transfer in progress” on camera until pull finishes (markerPayload.pullUiActive). */
const CMD_SET_DESKTOP_PULL_UI = 'SET_DESKTOP_PULL_UI';

/**
 * Whether to send an optional command, given register capabilities.
 * Legacy clients (no/slim capabilities): always true for commands they always supported.
 * @param {unknown} capabilities - msg.capabilities from register
 * @param {string} cmd - CMD_* constant
 * @returns {boolean}
 */
function deviceSupportsCommand(capabilities, cmd) {
  if (capabilities == null || typeof capabilities !== 'object' || Array.isArray(capabilities)) return true;
  if (cmd === CMD_SET_CAMERA_ZOOM) return capabilities[CAPABILITY_CAMERA_ZOOM] !== false;
  if (cmd === CMD_SET_HUD_LAYOUT) return capabilities[CAPABILITY_HUD_LAYOUT] !== false;
  return true;
}

// Message type strings
const TYPE_REGISTER = 'register';
const TYPE_STATUS = 'status';
const TYPE_COMMAND = 'command';
const TYPE_WEBRTC_SIGNAL = 'webrtc-signal';
/** Phone → desktop: join RPC failed before register (e.g. session full). One-shot; not part of PROTOCOL_VERSION handshake. */
const TYPE_JOIN_FAILURE_NOTICE = 'join-failure-notice';

// Device state (in Status)
const STATE_IDLE = 'idle';
const STATE_RECORDING = 'recording';
const STATE_PAUSED = 'paused';
/** Phone is finalizing files after STOP_RECORDING (before STATE_STOPPED). */
const STATE_STOPPING = 'stopping';
const STATE_STOPPED = 'stopped';

/**
 * Phone -> Desktop: Register device after connect. Optional pin when desktop requires auth.
 * @typedef {{ type: 'register', deviceId: string, deviceName: string, capabilities?: { platform?: 'android'|'ios' }, pin?: string }} RegisterMessage
 */

/**
 * Phone -> Desktop: Status update.
 * micSource: current mic label or id. audioInputs: list of available mics (phone sends; desktop uses for picker).
 * @typedef {{ type: 'status', deviceId: string, state: 'idle'|'recording'|'paused'|'stopping'|'stopped', durationSec?: number, currentFile?: string, battery?: number, micSource?: string, audioInputs?: Array<{ id?: string, label: string }|string>, storage?: object, error?: string }} StatusMessage
 */

/**
 * Desktop -> Phone: Command.
 * @typedef {{ type: 'command', cmd: 'START_RECORDING'|'STOP_RECORDING'|'PAUSE_RECORDING'|'STATUS'|'MARKER', markerPayload?: any }} CommandMessage
 */

/**
 * WebRTC signaling (bidirectional). Payload: { sdp?: RTCSessionDescriptionInit, candidate?: RTCIceCandidateInit }.
 * Phone -> Desktop: offer or ICE candidate. Desktop -> Phone: answer or ICE candidate.
 * @typedef {{ type: 'webrtc-signal', sdp?: object, candidate?: object }} WebRTCSignalMessage
 */

// Phone HTTP API (desktop pulls from phone)
// GET http://<phone-ip>:8080/recordings -> { recordings: [{ id, name, size, createdAt }] }
// GET http://<phone-ip>:8080/recordings/:id -> file stream

/**
 * Build a register message (phone -> desktop on connect).
 * @param {string} deviceId - Device identifier
 * @param {string} deviceName - Display name
 * @param {object} [capabilities] - Optional capabilities object
 * @param {string} [pin] - Optional PIN when desktop requires auth
 * @param {string} [joinToken] - Server-issued token from Supabase mephonecast_join_cast_session (production enforcement)
 * @returns {RegisterMessage} Register message for WebSocket
 */
function createRegister(deviceId, deviceName, capabilities = undefined, pin = undefined, joinToken = undefined) {
  const msg = { type: TYPE_REGISTER, protocolVersion: PROTOCOL_VERSION, deviceId, deviceName };
  if (capabilities != null) msg.capabilities = capabilities;
  if (pin != null && String(pin).trim() !== '') msg.pin = String(pin).trim();
  if (joinToken != null && String(joinToken).trim() !== '') msg.joinToken = String(joinToken).trim();
  return msg;
}

/**
 * Build a status message (phone -> desktop).
 * @param {string} deviceId - Device identifier
 * @param {string} state - One of 'idle'|'recording'|'paused'|'stopping'|'stopped'
 * @param {object} [opts] - Optional fields (durationSec, currentFile, battery, micSource, audioInputs, etc.)
 * @returns {StatusMessage} Status message for WebSocket
 */
function createStatus(deviceId, state, opts = {}) {
  return {
    type: TYPE_STATUS,
    deviceId,
    state,
    ...(opts.durationSec != null && { durationSec: opts.durationSec }),
    ...(opts.currentFile != null && { currentFile: opts.currentFile }),
    ...(opts.sessionId != null && { sessionId: opts.sessionId }),
    ...(opts.startTimeIso != null && { startTimeIso: opts.startTimeIso }),
    ...(opts.videoFile != null && { videoFile: opts.videoFile }),
    ...(opts.audioFile != null && { audioFile: opts.audioFile }),
    ...(opts.battery != null && { battery: opts.battery }),
    ...(opts.metering != null && { metering: opts.metering }),
    ...(opts.micSource != null && opts.micSource !== '' && { micSource: opts.micSource }),
    ...(opts.audioInputs != null && { audioInputs: opts.audioInputs }),
    ...(opts.micLevel != null && { micLevel: opts.micLevel }),
    ...(opts.micAgc != null && { micAgc: opts.micAgc }),
    ...(opts.storage != null && { storage: opts.storage }),
    ...(opts.error != null && { error: opts.error }),
    ...(opts.videoRecordingAvailable != null && { videoRecordingAvailable: opts.videoRecordingAvailable }),
    ...(opts.videoRecordingFailed != null && { videoRecordingFailed: opts.videoRecordingFailed }),
    ...(opts.videoRecordingConfirmed != null && { videoRecordingConfirmed: opts.videoRecordingConfirmed }),
    ...(opts.recordingsSaved != null && { recordingsSaved: opts.recordingsSaved }),
    ...(opts.recordExcluded != null && { recordExcluded: opts.recordExcluded }),
    ...(opts.recordingQuality != null &&
      opts.recordingQuality !== '' && { recordingQuality: String(opts.recordingQuality) }),
    ...(opts.cameraZoom != null &&
      Number.isFinite(Number(opts.cameraZoom)) && { cameraZoom: Number(opts.cameraZoom) }),
  };
}

/**
 * Build a command message (desktop -> phone).
 * @param {string} cmd - Command string (e.g. START_RECORDING, STOP_RECORDING, STATUS, MARKER)
 * @param {*} [markerPayload] - Optional payload for MARKER command
 * @returns {CommandMessage} Command message for WebSocket
 */
function createCommand(cmd, markerPayload = undefined) {
  const msg = { type: TYPE_COMMAND, cmd };
  if (markerPayload !== undefined) msg.markerPayload = markerPayload;
  return msg;
}

module.exports = {
  MIN_PROTOCOL_VERSION,
  PROTOCOL_VERSION,
  isSupportedClientProtocolVersion,
  CAPABILITY_CAMERA_ZOOM,
  CAPABILITY_HUD_LAYOUT,
  deviceSupportsCommand,
  WS_PORT,
  PHONE_HTTP_PORT,
  CMD_START_RECORDING,
  CMD_STOP_RECORDING,
  CMD_PAUSE_RECORDING,
  CMD_STATUS,
  CMD_MARKER,
  CMD_SET_MIC_SOURCE,
  CMD_SET_MIC_LEVEL,
  CMD_SET_MIC_AGC,
  CMD_SET_RECORD_EXCLUDED,
  CMD_REQUEST_RECORDING_VIA_WS,
  CMD_SET_HUD_LAYOUT,
  CMD_SET_CAMERA_ZOOM,
  CMD_SET_DESKTOP_PULL_UI,
  TYPE_REGISTER,
  TYPE_STATUS,
  TYPE_COMMAND,
  TYPE_WEBRTC_SIGNAL,
  TYPE_JOIN_FAILURE_NOTICE,
  STATE_IDLE,
  STATE_RECORDING,
  STATE_PAUSED,
  STATE_STOPPING,
  STATE_STOPPED,
  createRegister,
  createStatus,
  createCommand,
};
