import { z } from 'zod';

const actionMetadataSchema = z.object({
  id: z.string(),
  label: z.string(),
  tooltip: z.string().optional(),
});

export const itemSchema = z.object({
  id: z.string(),
  type: z.string().min(1),
  title: z.string(),
  x: z.number().int(),
  y: z.number().int(),
  createdBy: z.string(),
  updatedBy: z.string(),
  createdAt: z.number().int(),
  updatedAt: z.number().int(),
  version: z.number().int(),
  capabilities: z.array(z.string()),
  useSound: z.string().optional(),
  emitSound: z.string().optional(),
  params: z.record(z.string(), z.unknown()),
  carrierId: z.string().nullable().optional(),
  display: z.record(z.string(), z.string()).optional(),
});

export const welcomeMessageSchema = z.object({
  type: z.literal('welcome'),
  id: z.string(),
  player: z.object({
    id: z.string(),
    userId: z.string().nullable().optional(),
    nickname: z.string(),
    x: z.number().int(),
    y: z.number().int(),
  }),
  users: z.array(
    z.object({
      id: z.string(),
      userId: z.string().nullable().optional(),
      nickname: z.string(),
      x: z.number().int(),
      y: z.number().int(),
    }),
  ),
  items: z.array(itemSchema).optional(),
  worldConfig: z
    .object({
      gridSize: z.number().int().positive(),
      movementTickMs: z.number().int().positive().optional(),
      movementMaxStepsPerTick: z.number().int().positive().optional(),
    })
    .optional(),
  serverInfo: z
    .object({
      instanceId: z.string(),
      releaseVersion: z.string().optional(),
      serverVersion: z.string().optional(),
      expectedClientRevision: z.string().optional(),
      gridName: z.string().optional(),
      welcomeMessage: z.string().optional(),
    })
    .optional(),
  auth: z
    .object({
      authenticated: z.boolean(),
      userId: z.string().nullable().optional(),
      username: z.string().nullable().optional(),
      role: z.string().nullable().optional(),
      permissions: z.array(z.string()).optional(),
      adminMenuActions: z.array(actionMetadataSchema).optional(),
      policy: z
        .object({
          usernameMinLength: z.number().int().positive(),
          usernameMaxLength: z.number().int().positive(),
          passwordMinLength: z.number().int().positive(),
          passwordMaxLength: z.number().int().positive(),
        })
        .optional(),
    })
    .optional(),
  uiDefinitions: z
    .object({
      itemTypeOrder: z.array(z.string().min(1)),
      itemTypes: z.array(
        z.object({
          type: z.string().min(1),
          label: z.string().optional(),
          tooltip: z.string().optional(),
          editableProperties: z.array(z.string()),
          capabilities: z.array(z.string()).optional(),
          propertyMetadata: z
            .record(
              z.string(),
              z.object({
                valueType: z.enum(['boolean', 'text', 'number', 'list', 'sound']).optional(),
                label: z.string().optional(),
                tooltip: z.string().optional(),
                maxLength: z.number().int().positive().optional(),
                options: z.array(z.string()).optional(),
                visibleWhen: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).optional(),
                range: z
                  .object({
                    min: z.number(),
                    max: z.number(),
                    step: z.number().optional(),
                  })
                  .optional(),
              }),
            )
            .optional(),
          globalProperties: z.record(z.string(), z.unknown()).optional(),
        }),
      ),
      adminMenu: z
        .object({
          actions: z.array(actionMetadataSchema),
        })
        .optional(),
      commandMetadata: z
        .object({
          mainModeActions: z.array(actionMetadataSchema).optional(),
        })
        .optional(),
      itemManagement: z
        .object({
          actions: z.array(
            actionMetadataSchema.extend({
              anyPermission: z.string().optional(),
              ownPermission: z.string().optional(),
            }),
          ),
        })
        .optional(),
    })
    .optional(),
});

export const authRequiredSchema = z.object({
  type: z.literal('auth_required'),
  message: z.string(),
  gridName: z.string().optional(),
  welcomeMessage: z.string().optional(),
  releaseVersion: z.string().optional(),
  expectedClientRevision: z.string().optional(),
  serverVersion: z.string().optional(),
  authPolicy: z
    .object({
      usernameMinLength: z.number().int().positive(),
      usernameMaxLength: z.number().int().positive(),
      passwordMinLength: z.number().int().positive(),
      passwordMaxLength: z.number().int().positive(),
    })
    .optional(),
});

export const authResultSchema = z.object({
  type: z.literal('auth_result'),
  ok: z.boolean(),
  message: z.string(),
  sessionToken: z.string().optional(),
  username: z.string().optional(),
  role: z.string().optional(),
  permissions: z.array(z.string()).optional(),
  adminMenuActions: z.array(actionMetadataSchema).optional(),
  nickname: z.string().optional(),
  authPolicy: z
    .object({
      usernameMinLength: z.number().int().positive(),
      usernameMaxLength: z.number().int().positive(),
      passwordMinLength: z.number().int().positive(),
      passwordMaxLength: z.number().int().positive(),
    })
    .optional(),
});

export const livekitTokenSchema = z.object({
  type: z.literal('livekit_token'),
  token: z.string(),
  url: z.string(),
});

export const updatePositionSchema = z.object({
  type: z.literal('update_position'),
  id: z.string(),
  x: z.number().int(),
  y: z.number().int(),
});

export const teleportCompleteSchema = z.object({
  type: z.literal('teleport_complete'),
  id: z.string(),
  x: z.number().int(),
  y: z.number().int(),
});

export const updateNicknameSchema = z.object({
  type: z.literal('update_nickname'),
  id: z.string(),
  nickname: z.string().min(1).max(32),
});

export const userLeftSchema = z.object({
  type: z.literal('user_left'),
  id: z.string(),
});

export const chatMessageSchema = z.object({
  type: z.literal('chat_message'),
  message: z.string(),
  senderId: z.string().optional(),
  senderNickname: z.string().optional(),
  system: z.boolean().optional(),
  action: z.boolean().optional(),
});

export const pongSchema = z.object({
  type: z.literal('pong'),
  clientSentAt: z.number().int(),
});

export const nicknameResultSchema = z.object({
  type: z.literal('nickname_result'),
  accepted: z.boolean(),
  requestedNickname: z.string(),
  effectiveNickname: z.string(),
  reason: z.string().optional(),
});

export const itemUpsertSchema = z.object({
  type: z.literal('item_upsert'),
  item: itemSchema,
});

export const itemRemoveSchema = z.object({
  type: z.literal('item_remove'),
  itemId: z.string(),
});

export const itemActionResultSchema = z.object({
  type: z.literal('item_action_result'),
  ok: z.boolean(),
  action: z.enum(['add', 'pickup', 'drop', 'delete', 'transfer', 'use', 'secondary_use', 'update']),
  message: z.string(),
  itemId: z.string().optional(),
});

export const itemTransferTargetsSchema = z.object({
  type: z.literal('item_transfer_targets'),
  itemId: z.string(),
  targets: z.array(
    z.object({
      userId: z.string(),
      username: z.string(),
      online: z.boolean(),
    }),
  ),
});

export const itemUseSoundSchema = z.object({
  type: z.literal('item_use_sound'),
  itemId: z.string(),
  sound: z.string(),
  x: z.number().int(),
  y: z.number().int(),
  range: z.number().int().positive().optional(),
});

export const itemClockAnnounceSchema = z.object({
  type: z.literal('item_clock_announce'),
  itemId: z.string(),
  sounds: z.array(z.string()),
  x: z.number().int(),
  y: z.number().int(),
  range: z.number().int().positive().optional(),
});

export const itemPianoNoteSchema = z.object({
  type: z.literal('item_piano_note'),
  itemId: z.string(),
  senderId: z.string(),
  keyId: z.string(),
  midi: z.number().int().min(0).max(127),
  on: z.boolean(),
  instrument: z.string(),
  voiceMode: z.enum(['mono', 'poly']),
  octave: z.number().int().min(-2).max(2),
  attack: z.number().int().min(0).max(100),
  decay: z.number().int().min(0).max(100),
  release: z.number().int().min(0).max(100),
  brightness: z.number().int().min(0).max(100),
  x: z.number().int(),
  y: z.number().int(),
  emitRange: z.number().int().min(1),
});

export const itemPianoStatusSchema = z.object({
  type: z.literal('item_piano_status'),
  itemId: z.string(),
  event: z.enum([
    'use_mode_entered',
    'record_started',
    'record_paused',
    'record_resumed',
    'record_stopped',
    'playback_started',
    'playback_stopped',
  ]),
  recordingState: z.enum(['idle', 'recording', 'paused', 'playback']).optional(),
});

export const authPermissionsSchema = z.object({
  type: z.literal('auth_permissions'),
  role: z.string(),
  permissions: z.array(z.string()),
  adminMenuActions: z.array(actionMetadataSchema).optional(),
});

const adminRoleSummarySchema = z.object({
  id: z.number().int(),
  name: z.string(),
  isSystem: z.boolean(),
  userCount: z.number().int().nonnegative(),
  permissions: z.array(z.string()),
});

export const adminRolesListSchema = z.object({
  type: z.literal('admin_roles_list'),
  roles: z.array(adminRoleSummarySchema),
  permissionKeys: z.array(z.string()),
  permissionTooltips: z.record(z.string(), z.string()).optional(),
});

export const adminUsersListSchema = z.object({
  type: z.literal('admin_users_list'),
  users: z.array(
    z.object({
      id: z.string(),
      username: z.string(),
      role: z.string(),
      status: z.enum(['active', 'disabled']),
    }),
  ),
});

export const adminActionResultSchema = z.object({
  type: z.literal('admin_action_result'),
  ok: z.boolean(),
  action: z.enum([
    'role_create',
    'role_update_permissions',
    'role_delete',
    'user_set_role',
    'user_ban',
    'user_unban',
    'user_delete',
  ]),
  message: z.string(),
});

export const incomingMessageSchema = z.discriminatedUnion('type', [
  authRequiredSchema,
  authResultSchema,
  welcomeMessageSchema,
  livekitTokenSchema,
  updatePositionSchema,
  teleportCompleteSchema,
  updateNicknameSchema,
  userLeftSchema,
  chatMessageSchema,
  pongSchema,
  nicknameResultSchema,
  itemUpsertSchema,
  itemRemoveSchema,
  itemActionResultSchema,
  itemTransferTargetsSchema,
  itemUseSoundSchema,
  itemClockAnnounceSchema,
  itemPianoNoteSchema,
  itemPianoStatusSchema,
  authPermissionsSchema,
  adminRolesListSchema,
  adminUsersListSchema,
  adminActionResultSchema,
]);

export type IncomingMessage = z.infer<typeof incomingMessageSchema>;

export type OutgoingMessage =
  | { type: 'auth_register'; username: string; password: string; email?: string }
  | { type: 'auth_login'; username: string; password: string }
  | { type: 'auth_resume'; sessionToken: string }
  | { type: 'auth_logout' }
  | { type: 'welcome_ready' }
  | { type: 'admin_roles_list' }
  | { type: 'admin_role_create'; name: string }
  | { type: 'admin_role_update_permissions'; role: string; permissions: string[] }
  | { type: 'admin_role_delete'; role: string; replacementRole: string }
  | { type: 'admin_users_list'; action?: 'set_role' | 'ban' | 'unban' | 'delete_account' }
  | { type: 'admin_user_set_role'; username: string; role: string }
  | { type: 'admin_user_ban'; username: string }
  | { type: 'admin_user_unban'; username: string }
  | { type: 'admin_user_delete'; username: string }
  | { type: 'update_position'; x: number; y: number }
  | { type: 'teleport_complete'; x: number; y: number }
  | { type: 'update_nickname'; nickname: string }
  | { type: 'chat_message'; message: string }
  | { type: 'ping'; clientSentAt: number }
  | { type: 'item_add'; itemType: string }
  | { type: 'item_pickup'; itemId: string }
  | { type: 'item_drop'; itemId: string; x: number; y: number }
  | { type: 'item_delete'; itemId: string }
  | { type: 'item_transfer_targets'; itemId: string }
  | { type: 'item_transfer'; itemId: string; targetUserId: string }
  | { type: 'item_use'; itemId: string }
  | { type: 'item_secondary_use'; itemId: string }
  | { type: 'item_piano_note'; itemId: string; keyId: string; midi: number; on: boolean }
  | { type: 'item_piano_recording'; itemId: string; action: 'toggle_record' | 'playback' | 'stop_playback' | 'stop_record' }
  | {
      type: 'item_update';
      itemId: string;
      title?: string;
      params?: Record<string, unknown>;
    };

export type RemoteUser = {
  id: string;
  userId?: string | null;
  nickname: string;
  x: number;
  y: number;
};
