/**
 * WebSocket command contract manifest — single source of truth for
 * client ↔ server message shapes, envelope rules, and expected responses.
 */
import type { CommandManifest } from './types.js';

const idObject = (field: string) =>
  ({
    kind: 'object' as const,
    fields: { [field]: { type: 'string' as const } },
    required: [field],
  });

const legacyStringId = (description: string) =>
  ({ kind: 'string' as const, description });

export const WEBSOCKET_COMMANDS: CommandManifest = {
  // ── Init (handled before COMMAND_REGISTRY) ──────────────────────────────
  init: {
    direction: 'client-to-server',
    envelope: 'init-only',
    payloads: [
      {
        kind: 'object',
        fields: { userID: { type: 'string' } },
        required: ['userID'],
      },
    ],
    clients: ['ios', 'android'],
    flags: ['init-only'],
  },

  // ── Game control ────────────────────────────────────────────────────────
  start: {
    direction: 'client-to-server',
    envelope: 'command-alias',
    payloads: [{ kind: 'empty' }],
    clients: ['ios', 'android'],
  },
  stop: {
    direction: 'client-to-server',
    envelope: 'command-alias',
    payloads: [{ kind: 'empty' }],
    clients: ['ios', 'android'],
  },
  restart: {
    direction: 'client-to-server',
    envelope: 'command-alias',
    payloads: [{ kind: 'empty' }],
    clients: ['ios', 'android'],
  },
  startNewLife: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      { kind: 'empty' },
      {
        kind: 'object',
        fields: { mode: { type: 'string', optional: true } },
      },
    ],
    responses: [{ type: 'playerObject' }],
    clients: ['ios', 'android'],
    semanticNote:
      "Sent from the death screen. Optional message.mode selects the legacy path: 'fresh' (default, type-only) does a clean reset and clears the generational layer (familyPrestige/familyTree); 'heir' continues the lineage as the eldest living child, preserving familyPrestige + familyTree and applying inheritance + a prestige money seed. 'heir' falls back to 'fresh' when no living child exists.",
  },
  speed: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      { kind: 'number', description: 'Game speed value' },
      legacyStringId('Speed delta (+/-) or numeric string'),
    ],
    clients: ['ios', 'android'],
  },
  resetSpeed: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [{ kind: 'empty' }],
    clients: ['ios', 'android'],
  },

  // ── Events ──────────────────────────────────────────────────────────────
  questionEvent: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      {
        kind: 'object',
        fields: {
          id: { type: 'string' },
          response: { type: 'string' },
        },
        required: ['id', 'response'],
      },
    ],
    clients: ['ios', 'android'],
  },
  eventResponse: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      {
        kind: 'object',
        fields: {
          eventId: { type: 'string' },
          choiceId: { type: 'string' },
        },
        required: ['eventId', 'choiceId'],
      },
    ],
    responses: [{ type: 'playerObject' }, { type: 'error' }],
    clients: ['ios', 'android'],
  },

  // ── Character ───────────────────────────────────────────────────────────
  characterSetup: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      {
        kind: 'object',
        fields: {
          name: { type: 'string' },
          age: { type: 'number' },
          sex: { type: 'string' },
        },
        required: ['name', 'age', 'sex'],
      },
    ],
    responses: [{ type: 'playerObject' }, { type: 'error' }],
    clients: ['ios', 'android'],
  },
  deviceToken: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      legacyStringId('Legacy push token string'),
      {
        kind: 'object',
        fields: { token: { type: 'string' } },
        required: ['token'],
      },
    ],
    clients: ['ios', 'android'],
  },
  liveActivityToken: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      {
        kind: 'object',
        fields: {
          token: { type: 'string' },
          activityId: { type: 'string', optional: true },
        },
        required: ['token'],
      },
    ],
    clients: ['ios'],
  },
  liveActivityEnded: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      {
        kind: 'object',
        fields: { activityId: { type: 'string', optional: true } },
      },
    ],
    clients: ['ios'],
  },

  // ── Activities & jobs ───────────────────────────────────────────────────
  getExtraCurriculars: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [{ kind: 'empty' }, legacyStringId('Legacy empty string')],
    responses: [{ type: 'extraCurriculars' }],
    clients: ['ios', 'android'],
  },
  applyForExtracurricular: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      legacyStringId('Legacy activity ID string'),
      idObject('activityId'),
    ],
    idKeys: ['activityId'],
    responses: [
      { type: 'extracurricularApplied', fields: { success: 'boolean' } },
      { type: 'playerObject' },
      { type: 'error' },
    ],
    clients: ['ios', 'android'],
  },
  quitExtracurricular: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      legacyStringId('Legacy activity ID string'),
      idObject('activityId'),
    ],
    idKeys: ['activityId'],
    responses: [
      { type: 'extracurricularQuit', fields: { success: 'boolean' } },
      { type: 'playerObject' },
      { type: 'error' },
    ],
    clients: ['ios', 'android'],
  },
  focusUpdate: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      {
        kind: 'object',
        fields: {
          activityId: { type: 'string' },
          newFocus: { type: 'string' },
        },
        required: ['activityId', 'newFocus'],
      },
    ],
    idKeys: ['activityId'],
    responses: [
      { type: 'focusUpdated', fields: { success: 'boolean' } },
      { type: 'playerObject' },
      { type: 'error' },
    ],
    clients: ['ios', 'android'],
  },
  performActivity: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      legacyStringId('Legacy activity ID string'),
      {
        kind: 'object',
        fields: {
          activityId: { type: 'string' },
          override: { type: 'boolean' },
        },
        required: ['activityId'],
      },
    ],
    idKeys: ['activityId'],
    responses: [
      { type: 'activityPerformed', fields: { success: 'boolean' } },
      { type: 'activityPlanned', fields: { success: 'boolean' } },
      { type: 'playerObject' },
      { type: 'error' },
    ],
    clients: ['ios', 'android'],
  },
  applyForJob: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      legacyStringId('Legacy job ID string'),
      idObject('jobId'),
    ],
    idKeys: ['jobId'],
    responses: [
      { type: 'jobApplied', fields: { success: 'boolean' } },
      { type: 'playerObject' },
      { type: 'error' },
    ],
    clients: ['ios', 'android'],
  },
  quitJob: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      legacyStringId('Legacy job ID string'),
      idObject('jobId'),
    ],
    idKeys: ['jobId'],
    responses: [
      { type: 'jobQuit', fields: { success: 'boolean' } },
      { type: 'playerObject' },
      { type: 'error' },
    ],
    clients: ['ios', 'android'],
  },
  setSpendingHabits: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      {
        kind: 'object',
        fields: {
          habit: { type: 'string', optional: true },
          value: { type: 'string', optional: true },
        },
      },
    ],
    idKeys: ['habit', 'value'],
    responses: [
      { type: 'spendingHabitsUpdated', fields: { success: 'boolean', habit: 'string' } },
      { type: 'playerObject' },
      { type: 'error' },
    ],
    clients: ['ios', 'android'],
  },
  retire: {
    direction: 'client-to-server',
    envelope: 'empty',
    payloads: [{ kind: 'empty' }],
    responses: [
      { type: 'retired', fields: { success: 'boolean' } },
      { type: 'playerObject' },
      { type: 'error' },
    ],
    clients: ['ios', 'android'],
  },
  quitHabit: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      legacyStringId('Legacy habit name'),
      idObject('habitId'),
      {
        kind: 'object',
        fields: {
          habitId: { type: 'string', optional: true },
          habit: { type: 'string', optional: true },
        },
      },
    ],
    idKeys: ['habitId', 'habit'],
    responses: [
      { type: 'habitQuitting', fields: { success: 'boolean', habitId: 'string' } },
      { type: 'playerObject' },
      { type: 'error' },
    ],
    clients: ['ios', 'android'],
  },
  stopQuitHabit: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      legacyStringId('Legacy habit name'),
      idObject('habitId'),
      {
        kind: 'object',
        fields: {
          habitId: { type: 'string', optional: true },
          habit: { type: 'string', optional: true },
        },
      },
    ],
    idKeys: ['habitId', 'habit'],
    responses: [
      { type: 'habitQuitStopped', fields: { success: 'boolean', habitId: 'string' } },
      { type: 'playerObject' },
      { type: 'error' },
    ],
    clients: ['ios', 'android'],
  },

  // ── Conversations ───────────────────────────────────────────────────────
  conversation: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      {
        kind: 'object',
        fields: {
          characterID: { type: 'string' },
          response: { type: 'string', optional: true },
          conversationEvent: { type: 'string' },
          cType: { type: 'string', optional: true },
        },
        required: ['characterID', 'conversationEvent'],
      },
    ],
    responses: [
      { type: 'conversationEvent' },
      { type: 'conversationError' },
      { type: 'npcTyping' },
    ],
    clients: ['ios', 'android'],
  },
  retrievePerson: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      legacyStringId('Legacy person ID string'),
      idObject('personId'),
    ],
    idKeys: ['personId'],
    responses: [{ type: 'personObject' }, { type: 'error' }],
    clients: ['ios', 'android'],
  },
  markConversationAsRead: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      {
        kind: 'object',
        fields: { conversationId: { type: 'string' } },
        required: ['conversationId'],
      },
    ],
    clients: ['ios', 'android'],
  },

  // ── Purchases ───────────────────────────────────────────────────────────
  purchaseItem: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      legacyStringId('Legacy item ID string'),
      idObject('itemId'),
    ],
    idKeys: ['itemId'],
    responses: [
      { type: 'purchaseComplete', fields: { success: 'boolean' } },
      { type: 'playerObject' },
      { type: 'error' },
    ],
    clients: ['ios', 'android'],
  },
  purchaseInAppItem: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      {
        kind: 'object',
        fields: {
          productId: { type: 'string' },
          receiptData: { type: 'string', optional: true },
        },
        required: ['productId'],
      },
    ],
    responses: [
      { type: 'inAppPurchaseComplete', fields: { success: 'boolean' } },
      { type: 'error' },
    ],
    clients: ['ios', 'android'],
  },
  purchaseEnergyRefill: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [idObject('refillType')],
    responses: [{ type: 'energyRefillComplete' }, { type: 'error' }],
    clients: ['ios', 'android'],
  },
  purchaseTimeSkip: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [idObject('skipType')],
    responses: [{ type: 'timeSkipComplete' }, { type: 'error' }],
    clients: ['ios', 'android'],
  },
  getEnergyRefillTiers: {
    direction: 'client-to-server',
    envelope: 'empty',
    payloads: [{ kind: 'empty' }],
    responses: [{ type: 'energyRefillTiers' }],
    clients: ['ios', 'android'],
  },
  getTimeSkipTiers: {
    direction: 'client-to-server',
    envelope: 'empty',
    payloads: [{ kind: 'empty' }],
    responses: [{ type: 'timeSkipTiers' }],
    clients: ['ios', 'android'],
  },

  // ── Retention ───────────────────────────────────────────────────────────
  getAchievements: {
    direction: 'client-to-server',
    envelope: 'empty',
    payloads: [{ kind: 'empty' }],
    responses: [{ type: 'achievements' }],
    clients: ['ios', 'android'],
  },
  acknowledgeAchievement: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [idObject('achievementId')],
    clients: ['ios', 'android'],
  },
  getDailyRewards: {
    direction: 'client-to-server',
    envelope: 'empty',
    payloads: [{ kind: 'empty' }],
    responses: [{ type: 'dailyRewards' }],
    clients: ['ios', 'android'],
  },
  claimDailyReward: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      {
        kind: 'object',
        fields: { day: { type: 'number' } },
        required: ['day'],
      },
    ],
    responses: [{ type: 'dailyRewardClaimed', fields: { success: 'boolean' } }],
    clients: ['ios', 'android'],
  },
  getDailyRewardState: {
    direction: 'client-to-server',
    envelope: 'empty',
    payloads: [{ kind: 'empty' }],
    clients: ['ios', 'android'],
  },
  getDailyQuests: {
    direction: 'client-to-server',
    envelope: 'empty',
    payloads: [{ kind: 'empty' }],
    responses: [{ type: 'dailyQuests' }],
    clients: ['ios', 'android'],
  },
  claimQuestReward: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [idObject('questId')],
    responses: [{ type: 'questRewardClaimed', fields: { success: 'boolean' } }],
    clients: ['ios', 'android'],
  },
  claimEvent: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      {
        kind: 'object',
        fields: {
          eventId: { type: 'string' },
          timestamp: { type: 'string', optional: true },
        },
        required: ['eventId'],
      },
    ],
    clients: ['ios', 'android'],
  },
  tutorialStepComplete: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      {
        kind: 'object',
        fields: { step: { type: 'number' } },
        required: ['step'],
      },
    ],
    responses: [{ type: 'tutorialStepUpdated' }],
    clients: ['ios', 'android'],
  },
  completeOnboarding: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [{ kind: 'empty' }],
    responses: [
      { type: 'onboardingComplete' },
      { type: 'onboardingAlreadyComplete' },
    ],
    clients: ['ios', 'android'],
  },
  tooltipSeen: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [idObject('tooltipId')],
    clients: ['ios', 'android'],
  },

  // ── Debug ───────────────────────────────────────────────────────────────
  debugSetup: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [idObject('preset')],
    responses: [{ type: 'debugSetupComplete' }, { type: 'error' }],
    clients: ['ios', 'android'],
    flags: ['dev-only'],
  },
  debugGrant: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      {
        kind: 'object',
        fields: {
          mode: { type: 'string', optional: true },
          money: { type: 'number', optional: true },
          energy: { type: 'number', optional: true },
          diamonds: { type: 'number', optional: true },
        },
      },
    ],
    responses: [{ type: 'debugGrantComplete' }, { type: 'error' }],
    clients: ['ios', 'android'],
    flags: ['dev-only'],
  },

  // ── Romance & dating ────────────────────────────────────────────────────
  romance: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [idObject('partnerId')],
    idKeys: ['partnerId'],
    clients: ['ios', 'android'],
  },
  dateNight: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      {
        kind: 'object',
        fields: { ideaName: { type: 'string' } },
        required: ['ideaName'],
      },
    ],
    clients: ['ios', 'android'],
  },
  relationshipEventResponse: {
    direction: 'client-to-server',
    envelope: 'top-level',
    payloads: [
      {
        kind: 'object',
        fields: {
          eventId: { type: 'string' },
          choiceId: { type: 'string' },
        },
        required: ['eventId', 'choiceId'],
      },
    ],
    clients: ['ios', 'android'],
    semanticNote: 'Android sends eventId/choiceId at envelope root; iOS uses message-field wrapper.',
  },
  dateMiniGameResponse: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      {
        kind: 'object',
        fields: {
          gameId: { type: 'string' },
          round: { type: 'number' },
          responseId: { type: 'string' },
        },
        required: ['gameId', 'round', 'responseId'],
      },
    ],
    clients: ['ios', 'android'],
  },
  startDate: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      {
        kind: 'object',
        fields: {
          activityId: { type: 'string' },
          partnerId: { type: 'string' },
        },
        required: ['activityId', 'partnerId'],
      },
    ],
    semanticNote: 'activityId must be the date activity ID, not display name.',
    clients: ['ios', 'android'],
  },
  getDateIdeas: {
    direction: 'client-to-server',
    envelope: 'empty',
    payloads: [{ kind: 'empty' }],
    responses: [{ type: 'dateIdeas' }],
    clients: ['ios', 'android'],
  },
  breakUp: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [idObject('partnerId')],
    idKeys: ['partnerId'],
    clients: ['ios', 'android'],
    semanticNote: 'Android historically sent partnerId at envelope root; server accepts message-field.',
  },
  divorce: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [idObject('partnerId')],
    idKeys: ['partnerId'],
    clients: ['ios', 'android'],
  },
  propose: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [idObject('partnerId')],
    idKeys: ['partnerId'],
    clients: ['ios', 'android'],
  },
  marry: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [idObject('partnerId')],
    idKeys: ['partnerId'],
    clients: ['ios', 'android'],
  },
  partnerGift: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      legacyStringId('Legacy partner/character ID string'),
      idObject('partnerId'),
    ],
    idKeys: ['partnerId'],
    clients: ['ios', 'android'],
  },
  getSwipeCharacter: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      { kind: 'empty' },
      legacyStringId('Preferred sex filter'),
    ],
    responses: [{ type: 'swipeCharacter' }],
    clients: ['ios', 'android'],
  },
  swipeMatch: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      legacyStringId('Matched character ID'),
      { kind: 'empty' },
    ],
    clients: ['ios', 'android'],
  },

  // ── Data / GDPR ─────────────────────────────────────────────────────────
  exportData: {
    direction: 'client-to-server',
    envelope: 'top-level',
    payloads: [
      {
        kind: 'object',
        fields: { userId: { type: 'string', optional: true } },
      },
    ],
    responses: [{ type: 'dataExportComplete' }, { type: 'dataExportReady' }],
    clients: ['ios', 'android'],
  },
  deleteAccount: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [
      {
        kind: 'object',
        fields: { confirmation: { type: 'string' } },
        required: ['confirmation'],
      },
    ],
    responses: [
      { type: 'accountDeletionScheduled' },
      { type: 'accountDeleted' },
    ],
    clients: ['ios', 'android'],
  },
  cancelAccountDeletion: {
    direction: 'client-to-server',
    envelope: 'empty',
    payloads: [{ kind: 'empty' }],
    responses: [{ type: 'accountDeletionCancelled' }],
    clients: ['ios'],
    semanticNote:
      'Cancels a scheduled account deletion during the 30-day grace period; server clears the persisted deletionScheduledAt marker. Only iOS sends this today (type-only envelope). The same accountDeletionCancelled response is also pushed unsolicited on login when a scheduled deletion is auto-cancelled.',
  },
  getPlayerStatistics: {
    direction: 'client-to-server',
    envelope: 'message-field',
    payloads: [{ kind: 'empty' }],
    responses: [{ type: 'playerStatistics' }],
    clients: ['ios', 'android'],
  },
};

/** Client→server commands that must exist in COMMAND_REGISTRY (excludes init). */
export const REGISTRY_COMMAND_NAMES = Object.keys(WEBSOCKET_COMMANDS).filter(
  (name) => name !== 'init'
);
