import { browser } from '$app/environment';
import type { MatrixClient, Room, MatrixEvent, RoomMember as SDKRoomMember } from 'matrix-js-sdk';
import {
	showMessageNotification,
	canShowNotifications,
	isDocumentFocused,
} from '$lib/notifications';
import type {
	SyncState,
	MatrixCredentials,
	SimpleRoom,
	SimpleMessage,
	MessageType,
	MessageReaction,
	ReadReceipt,
	RoomMember,
	VerificationStatus,
	DeviceInfo,
	VerificationRequest,
	CryptoCallbacks,
	CrossSigningStatus,
	PresenceState,
	UserPresence,
	SimpleCall,
	CallCallbacks,
	CallState as CallStateType,
	CallType,
	CallDirection,
	RoomWidget,
} from './types';

const STORAGE_KEY = 'matrix_credentials';
const LAST_ROOM_KEY = 'matrix_last_room';

/**
 * Reactive Matrix store using Svelte 5 runes
 */
class MatrixStore {
	// ─────────────────────────────────────────────────────────
	// Private State
	// ─────────────────────────────────────────────────────────
	private _client = $state<MatrixClient | null>(null);
	private _syncState = $state<SyncState>('STOPPED');
	private _rooms = $state<Room[]>([]);
	private _currentRoomId = $state<string | null>(null);
	private _timeline = $state<MatrixEvent[]>([]);
	private _typingUsers = $state<Map<string, string[]>>(new Map());
	private _userPresence = $state<Map<string, UserPresence>>(new Map());
	private _error = $state<string | null>(null);
	private _initialized = $state(false);

	// Crypto State
	private _cryptoReady = $state(false);
	private _verificationStatus = $state<VerificationStatus>('unknown');
	private _activeVerification = $state<VerificationRequest | null>(null);
	private _keyBackupEnabled = $state(false);
	private _crossSigningReady = $state(false);
	private _cryptoCallbacks: CryptoCallbacks = {};

	// VoIP / Call State
	private _activeCall = $state<SimpleCall | null>(null);
	private _incomingCall = $state<SimpleCall | null>(null);
	private _callCallbacks: CallCallbacks = {};
	// eslint-disable-next-line @typescript-eslint/no-explicit-any
	private _matrixCall: any = null; // The actual MatrixCall object

	// ─────────────────────────────────────────────────────────
	// Public Getters
	// ─────────────────────────────────────────────────────────
	get client() {
		return this._client;
	}
	get syncState() {
		return this._syncState;
	}
	get error() {
		return this._error;
	}
	get initialized() {
		return this._initialized;
	}
	get currentRoomId() {
		return this._currentRoomId;
	}

	// Crypto Getters
	get cryptoReady() {
		return this._cryptoReady;
	}
	get verificationStatus() {
		return this._verificationStatus;
	}
	get activeVerification() {
		return this._activeVerification;
	}
	get keyBackupEnabled() {
		return this._keyBackupEnabled;
	}
	get crossSigningReady() {
		return this._crossSigningReady;
	}

	// VoIP Getters
	get activeCall() {
		return this._activeCall;
	}
	get incomingCall() {
		return this._incomingCall;
	}
	get hasActiveCall() {
		return this._activeCall !== null;
	}
	get hasIncomingCall() {
		return this._incomingCall !== null;
	}

	/**
	 * Get presence for a specific user
	 */
	getUserPresence(userId: string): UserPresence | undefined {
		return this._userPresence.get(userId);
	}

	/**
	 * Check if a user is currently online
	 */
	isUserOnline(userId: string): boolean {
		const presence = this._userPresence.get(userId);
		return presence?.presence === 'online' || presence?.currentlyActive === true;
	}

	// ─────────────────────────────────────────────────────────
	// Derived State
	// ─────────────────────────────────────────────────────────

	/** Is the client ready to use? */
	isReady = $derived(this._syncState === 'PREPARED' || this._syncState === 'SYNCING');

	/** Is currently syncing? */
	isSyncing = $derived(this._syncState === 'SYNCING' || this._syncState === 'CATCHUP');

	/** Current user ID */
	userId = $derived(this._client?.getUserId() || null);

	/** Simplified room list sorted by last activity */
	rooms = $derived<SimpleRoom[]>(
		this._rooms
			.map((room) => this.roomToSimpleRoom(room))
			.sort((a, b) => (b.lastMessageTime || 0) - (a.lastMessageTime || 0))
	);

	/** Joined rooms only */
	joinedRooms = $derived(this.rooms.filter((r) => r.membership === 'join'));

	/** Invited rooms */
	invitedRooms = $derived(this.rooms.filter((r) => r.membership === 'invite'));

	/** Direct message rooms (joined only) */
	directRooms = $derived(this.joinedRooms.filter((r) => r.isDirect));

	/** Group rooms (non-DM, joined only) */
	groupRooms = $derived(this.joinedRooms.filter((r) => !r.isDirect));

	/** Current selected room */
	currentRoom = $derived(
		this._currentRoomId ? this._rooms.find((r) => r.roomId === this._currentRoomId) || null : null
	);

	/** Current room as SimpleRoom */
	currentSimpleRoom = $derived(this.currentRoom ? this.roomToSimpleRoom(this.currentRoom) : null);

	/** Messages in current room */
	messages = $derived<SimpleMessage[]>(
		this._timeline
			.filter((e) => e.getType() === 'm.room.message')
			.map((e) => this.eventToSimpleMessage(e, this._timeline))
	);

	/** Users currently typing in current room */
	currentRoomTyping = $derived(
		this._currentRoomId ? this._typingUsers.get(this._currentRoomId) || [] : []
	);

	/** Total unread count across all rooms */
	totalUnreadCount = $derived(this.rooms.reduce((sum, r) => sum + r.unreadCount, 0));

	// ─────────────────────────────────────────────────────────
	// Initialization
	// ─────────────────────────────────────────────────────────

	/**
	 * Initialize the Matrix client
	 * @param credentials Optional credentials, will load from storage if not provided
	 */
	async initialize(credentials?: MatrixCredentials): Promise<boolean> {
		if (!browser) return false;
		if (this._initialized && this._client) return true;

		// Load polyfills first
		await import('./polyfills');

		// Get credentials
		const creds = credentials || this.loadCredentials();
		if (!creds) {
			this._error = 'No credentials available';
			return false;
		}

		try {
			const sdk = await import('matrix-js-sdk');

			this._client = sdk.createClient({
				baseUrl: creds.homeserver,
				accessToken: creds.accessToken,
				userId: creds.userId,
				deviceId: creds.deviceId,
				timelineSupport: true,
			});

			this.setupEventHandlers(sdk);

			// Initialize Rust Crypto
			try {
				await this._client.initRustCrypto();
				this._cryptoReady = true;
				console.log('Rust crypto initialized successfully');

				// Setup crypto event handlers
				this.setupCryptoEventHandlers(sdk);
			} catch (cryptoErr) {
				console.warn('Crypto initialization failed, continuing without E2EE:', cryptoErr);
				this._cryptoReady = false;
			}

			await this._client.startClient({
				initialSyncLimit: 20,
				lazyLoadMembers: true,
			});

			this.saveCredentials(creds);
			this._initialized = true;
			this._error = null;

			return true;
		} catch (err) {
			this._error = err instanceof Error ? err.message : 'Failed to initialize Matrix client';
			console.error('Matrix initialization error:', err);
			return false;
		}
	}

	/**
	 * Setup event handlers for Matrix SDK events
	 */
	private setupEventHandlers(sdk: typeof import('matrix-js-sdk')) {
		if (!this._client) return;

		// Sync state changes
		this._client.on(sdk.ClientEvent.Sync, (state, prevState) => {
			this._syncState = state as SyncState;

			if (state === 'PREPARED') {
				this._rooms = this._client!.getRooms();
				console.log(`Matrix sync prepared, ${this._rooms.length} rooms loaded`);

				// Note: Last room restore is now handled in the /chat page component
				// to support different behavior on mobile vs desktop
			}

			if (state === 'ERROR') {
				this._error = 'Sync error occurred';
			}
		});

		// Room timeline updates (new messages)
		this._client.on(sdk.RoomEvent.Timeline, (event, room, toStartOfTimeline) => {
			// Skip historical events from pagination
			if (toStartOfTimeline) return;

			// Update rooms list
			this._rooms = this._client!.getRooms();

			// Update timeline if we're in this room
			if (room?.roomId === this._currentRoomId) {
				this._timeline = [...(room.getLiveTimeline().getEvents() || [])];
			}

			// Show browser notification for new messages from others
			if (
				browser &&
				event.getType() === 'm.room.message' &&
				event.getSender() !== this._client!.getUserId() &&
				!isDocumentFocused()
			) {
				const content = event.getContent();
				const body = content?.body || '';
				const senderName = this.getSenderName(event);
				const roomName = room?.name || 'Unbekannt';

				showMessageNotification(senderName, body, roomName, {
					onClick: () => {
						if (room) {
							this.selectRoom(room.roomId);
						}
					},
				});
			}
		});

		// Typing indicators
		this._client.on(sdk.RoomMemberEvent.Typing, (event, member) => {
			const roomId = event.getRoomId();
			if (!roomId) return;

			const room = this._client!.getRoom(roomId);
			const typingMembers =
				room
					?.getMembersWithMembership('join')
					.filter((m) => m.typing && m.userId !== this._client!.getUserId())
					.map((m) => m.name || m.userId) || [];

			// Trigger reactivity by creating new Map
			const newMap = new Map(this._typingUsers);
			newMap.set(roomId, typingMembers);
			this._typingUsers = newMap;
		});

		// Room membership changes (invites, joins, leaves)
		this._client.on(sdk.RoomEvent.MyMembership, (room, membership, prevMembership) => {
			console.log(`Membership changed: ${room.roomId} - ${prevMembership} -> ${membership}`);
			this._rooms = this._client!.getRooms();
		});

		// Room name/state changes
		this._client.on(sdk.RoomStateEvent.Events, (event, state, prevEvent) => {
			// Trigger reactivity for room updates
			this._rooms = this._client!.getRooms();
		});

		// User presence changes
		this._client.on(sdk.UserEvent.Presence, (event, user) => {
			if (!user) return;

			const userId = user.userId;
			const presence: UserPresence = {
				userId,
				presence: (user.presence as PresenceState) || 'offline',
				lastActiveAgo: user.lastActiveAgo,
				statusMessage: user.presenceStatusMsg,
				currentlyActive: user.currentlyActive,
			};

			// Trigger reactivity by creating new Map
			const newMap = new Map(this._userPresence);
			newMap.set(userId, presence);
			this._userPresence = newMap;

			// Also trigger room list update for DMs
			this._rooms = this._client!.getRooms();
		});

		// Read receipt updates
		this._client.on(sdk.RoomEvent.Receipt, (event, room) => {
			// Update timeline if we're in this room to refresh read receipts
			if (room.roomId === this._currentRoomId) {
				this._timeline = [...(room.getLiveTimeline().getEvents() || [])];
			}
		});

		// Incoming calls
		// CallEvent is exported from matrix-js-sdk, but CallState needs dynamic import from webrtc module
		// eslint-disable-next-line @typescript-eslint/no-explicit-any
		this._client.on('Call.incoming' as any, async (call: any) => {
			console.log('Incoming call:', call.callId);
			try {
				const webrtc = await import('matrix-js-sdk/lib/webrtc/call');
				this.handleIncomingCall(call, webrtc.CallEvent, webrtc.CallState);
			} catch (err) {
				console.error('Error handling incoming call:', err);
			}
		});
	}

	/**
	 * Setup crypto event handlers
	 * Note: Uses loose typing due to matrix-js-sdk type complexity
	 */
	private async setupCryptoEventHandlers(_sdk: typeof import('matrix-js-sdk')) {
		if (!this._client || !this._cryptoReady) return;

		const crypto = this._client.getCrypto();
		if (!crypto) return;

		try {
			// Import CryptoEvent separately - types may vary by SDK version
			const cryptoApi = await import('matrix-js-sdk/lib/crypto-api');
			const CryptoEvent = cryptoApi.CryptoEvent;

			// Verification request received
			// eslint-disable-next-line @typescript-eslint/no-explicit-any
			(this._client as any).on(CryptoEvent.VerificationRequestReceived, (request: unknown) => {
				console.log('Verification request received:', request);

				// eslint-disable-next-line @typescript-eslint/no-explicit-any
				const req = request as any;
				const verificationRequest: VerificationRequest = {
					requestId: req.transactionId || req.id || '',
					otherUserId: req.otherUserId || '',
					otherDeviceId: req.otherDeviceId,
					phase: this.mapVerificationPhase(req.phase ?? 0),
					isSelfVerification: req.isSelfVerification ?? false,
					methods: (req.methods || []) as VerificationRequest['methods'],
				};

				this._activeVerification = verificationRequest;
				this._cryptoCallbacks.onVerificationRequest?.(verificationRequest);
			});

			// Keys changed (e.g., new device added)
			// eslint-disable-next-line @typescript-eslint/no-explicit-any
			(this._client as any).on(CryptoEvent.KeysChanged, () => {
				console.log('Crypto keys changed');
				this.checkVerificationStatus();
			});

			// Key backup status - check if event exists
			if ('KeyBackupStatus' in CryptoEvent) {
				// eslint-disable-next-line @typescript-eslint/no-explicit-any
				(this._client as any).on((CryptoEvent as any).KeyBackupStatus, (enabled: boolean) => {
					console.log('Key backup status:', enabled);
					this._keyBackupEnabled = enabled;
					this._cryptoCallbacks.onKeyBackupStatus?.(enabled);
				});
			}
		} catch (err) {
			console.warn('Could not setup crypto event handlers:', err);
		}

		// Initial status check
		this.checkVerificationStatus();
		this.checkKeyBackupStatus();
	}

	/**
	 * Map SDK verification phase to our type
	 */
	private mapVerificationPhase(phase: number): VerificationRequest['phase'] {
		// Phase values from matrix-js-sdk VerificationPhase enum
		const phaseMap: Record<number, VerificationRequest['phase']> = {
			0: 'created',
			1: 'requested',
			2: 'ready',
			3: 'started',
			4: 'done',
			5: 'cancelled',
		};
		return phaseMap[phase] || 'created';
	}

	// ─────────────────────────────────────────────────────────
	// Room Actions
	// ─────────────────────────────────────────────────────────

	/**
	 * Select a room to view
	 */
	selectRoom(roomId: string) {
		this._currentRoomId = roomId;
		const room = this._client?.getRoom(roomId);

		if (room) {
			this._timeline = room.getLiveTimeline().getEvents() || [];

			// Mark as read
			const lastEvent = this._timeline[this._timeline.length - 1];
			if (lastEvent) {
				this._client?.sendReadReceipt(lastEvent).catch(console.error);
			}

			// Save last room to localStorage
			if (browser) {
				localStorage.setItem(LAST_ROOM_KEY, roomId);
			}
		} else {
			this._timeline = [];
		}
	}

	/**
	 * Clear current room selection
	 */
	clearRoom() {
		this._currentRoomId = null;
		this._timeline = [];
	}

	/**
	 * Join a room by ID or alias
	 */
	async joinRoom(roomIdOrAlias: string): Promise<boolean> {
		if (!this._client) return false;

		try {
			await this._client.joinRoom(roomIdOrAlias);
			this._rooms = this._client.getRooms();
			return true;
		} catch (err) {
			this._error = err instanceof Error ? err.message : 'Failed to join room';
			return false;
		}
	}

	/**
	 * Leave a room
	 */
	async leaveRoom(roomId: string): Promise<boolean> {
		if (!this._client) return false;

		try {
			await this._client.leave(roomId);

			if (this._currentRoomId === roomId) {
				this.clearRoom();
			}

			this._rooms = this._client.getRooms();
			return true;
		} catch (err) {
			this._error = err instanceof Error ? err.message : 'Failed to leave room';
			return false;
		}
	}

	/**
	 * Create a new room
	 */
	async createRoom(options: {
		name?: string;
		topic?: string;
		isDirect?: boolean;
		invite?: string[];
	}): Promise<string | null> {
		if (!this._client) return null;

		try {
			// eslint-disable-next-line @typescript-eslint/no-explicit-any
			const result = await this._client.createRoom({
				name: options.name,
				topic: options.topic,
				is_direct: options.isDirect,
				invite: options.invite,
				preset: (options.isDirect ? 'trusted_private_chat' : 'private_chat') as any,
			});

			this._rooms = this._client.getRooms();
			return result.room_id;
		} catch (err) {
			this._error = err instanceof Error ? err.message : 'Failed to create room';
			return null;
		}
	}

	// ─────────────────────────────────────────────────────────
	// Message Actions
	// ─────────────────────────────────────────────────────────

	/**
	 * Send a text message to current room
	 */
	async sendMessage(body: string): Promise<boolean> {
		if (!this._client || !this._currentRoomId) return false;

		try {
			await this._client.sendTextMessage(this._currentRoomId, body);
			return true;
		} catch (err) {
			this._error = err instanceof Error ? err.message : 'Failed to send message';
			return false;
		}
	}

	/**
	 * Send a message to a specific room (for forwarding)
	 */
	async sendMessageToRoom(roomId: string, body: string): Promise<boolean> {
		if (!this._client) return false;

		try {
			await this._client.sendTextMessage(roomId, body);
			return true;
		} catch (err) {
			this._error = err instanceof Error ? err.message : 'Failed to send message';
			return false;
		}
	}

	/**
	 * Send typing indicator
	 */
	async sendTyping(typing: boolean): Promise<void> {
		if (!this._client || !this._currentRoomId) return;

		try {
			await this._client.sendTyping(this._currentRoomId, typing, typing ? 30000 : 0);
		} catch (err) {
			// Ignore typing errors
		}
	}

	/**
	 * Load more messages (pagination)
	 */
	async loadMoreMessages(limit = 50): Promise<boolean> {
		if (!this._client || !this._currentRoomId) return false;

		const room = this._client.getRoom(this._currentRoomId);
		if (!room) return false;

		try {
			await this._client.scrollback(room, limit);
			this._timeline = room.getLiveTimeline().getEvents() || [];
			return true;
		} catch (err) {
			this._error = err instanceof Error ? err.message : 'Failed to load messages';
			return false;
		}
	}

	/**
	 * Send a file/image to current room
	 */
	async sendFile(file: File, onProgress?: (progress: number) => void): Promise<boolean> {
		if (!this._client || !this._currentRoomId) return false;

		try {
			// Upload to Matrix media repo
			const uploadResponse = await this._client.uploadContent(file, {
				progressHandler: (progress) => {
					if (onProgress) {
						onProgress(Math.round((progress.loaded / progress.total) * 100));
					}
				},
			});

			const mxcUrl = uploadResponse.content_uri;

			// Determine message type based on MIME type
			const isImage = file.type.startsWith('image/');
			const isVideo = file.type.startsWith('video/');
			const isAudio = file.type.startsWith('audio/');

			let msgtype = 'm.file';
			if (isImage) msgtype = 'm.image';
			if (isVideo) msgtype = 'm.video';
			if (isAudio) msgtype = 'm.audio';

			// Build content based on type
			const content: Record<string, unknown> = {
				msgtype,
				body: file.name,
				filename: file.name,
				info: {
					mimetype: file.type,
					size: file.size,
				},
				url: mxcUrl,
			};

			// Add dimensions for images
			if (isImage) {
				const dimensions = await this.getImageDimensions(file);
				if (dimensions) {
					(content.info as Record<string, unknown>).w = dimensions.width;
					(content.info as Record<string, unknown>).h = dimensions.height;
				}
			}

			// eslint-disable-next-line @typescript-eslint/no-explicit-any
			await this._client.sendMessage(this._currentRoomId, content as any);
			return true;
		} catch (err) {
			this._error = err instanceof Error ? err.message : 'Failed to send file';
			return false;
		}
	}

	/**
	 * Get image dimensions
	 */
	private getImageDimensions(file: File): Promise<{ width: number; height: number } | null> {
		return new Promise((resolve) => {
			if (!file.type.startsWith('image/')) {
				resolve(null);
				return;
			}

			const img = new Image();
			img.onload = () => {
				resolve({ width: img.width, height: img.height });
				URL.revokeObjectURL(img.src);
			};
			img.onerror = () => resolve(null);
			img.src = URL.createObjectURL(file);
		});
	}

	/**
	 * Get HTTP URL for Matrix media (mxc:// URLs)
	 */
	getMediaUrl(mxcUrl: string, width?: number, height?: number): string | null {
		if (!this._client || !mxcUrl?.startsWith('mxc://')) return null;

		if (width && height) {
			return this._client.mxcUrlToHttp(mxcUrl, width, height, 'scale') || null;
		}
		return this._client.mxcUrlToHttp(mxcUrl) || null;
	}

	/**
	 * Reply to a message
	 */
	async replyToMessage(eventId: string, body: string): Promise<boolean> {
		if (!this._client || !this._currentRoomId) return false;

		const room = this._client.getRoom(this._currentRoomId);
		const originalEvent = room?.findEventById(eventId);
		if (!originalEvent) return false;

		try {
			const content = {
				msgtype: 'm.text',
				body: `> <${originalEvent.getSender()}> ${originalEvent.getContent().body}\n\n${body}`,
				format: 'org.matrix.custom.html',
				formatted_body: `<mx-reply><blockquote><a href="https://matrix.to/#/${this._currentRoomId}/${eventId}">In reply to</a> <a href="https://matrix.to/#/${originalEvent.getSender()}">${originalEvent.getSender()}</a><br>${originalEvent.getContent().body}</blockquote></mx-reply>${body}`,
				'm.relates_to': {
					'm.in_reply_to': {
						event_id: eventId,
					},
				},
			};

			// eslint-disable-next-line @typescript-eslint/no-explicit-any
			await this._client.sendMessage(this._currentRoomId, content as any);
			return true;
		} catch (err) {
			this._error = err instanceof Error ? err.message : 'Failed to send reply';
			return false;
		}
	}

	/**
	 * Edit a message
	 */
	async editMessage(eventId: string, newBody: string): Promise<boolean> {
		if (!this._client || !this._currentRoomId) return false;

		try {
			const content = {
				msgtype: 'm.text',
				body: `* ${newBody}`,
				'm.new_content': {
					msgtype: 'm.text',
					body: newBody,
				},
				'm.relates_to': {
					rel_type: 'm.replace',
					event_id: eventId,
				},
			};

			// eslint-disable-next-line @typescript-eslint/no-explicit-any
			await this._client.sendMessage(this._currentRoomId, content as any);
			return true;
		} catch (err) {
			this._error = err instanceof Error ? err.message : 'Failed to edit message';
			return false;
		}
	}

	/**
	 * Delete (redact) a message
	 */
	async deleteMessage(eventId: string, reason?: string): Promise<boolean> {
		if (!this._client || !this._currentRoomId) return false;

		try {
			await this._client.redactEvent(this._currentRoomId, eventId, undefined, { reason });
			return true;
		} catch (err) {
			this._error = err instanceof Error ? err.message : 'Failed to delete message';
			return false;
		}
	}

	/**
	 * React to a message with an emoji
	 */
	async reactToMessage(eventId: string, emoji: string): Promise<boolean> {
		if (!this._client || !this._currentRoomId) return false;

		try {
			// eslint-disable-next-line @typescript-eslint/no-explicit-any
			await (this._client as any).sendEvent(this._currentRoomId, 'm.reaction', {
				'm.relates_to': {
					rel_type: 'm.annotation',
					event_id: eventId,
					key: emoji,
				},
			});
			return true;
		} catch (err) {
			this._error = err instanceof Error ? err.message : 'Failed to react';
			return false;
		}
	}

	// ─────────────────────────────────────────────────────────
	// User Actions
	// ─────────────────────────────────────────────────────────

	/**
	 * Invite a user to a room
	 */
	async inviteUser(roomId: string, userId: string): Promise<boolean> {
		if (!this._client) return false;

		try {
			await this._client.invite(roomId, userId);
			return true;
		} catch (err) {
			this._error = err instanceof Error ? err.message : 'Failed to invite user';
			return false;
		}
	}

	/**
	 * Kick a user from a room
	 */
	async kickUser(roomId: string, userId: string, reason?: string): Promise<boolean> {
		if (!this._client) return false;

		try {
			await this._client.kick(roomId, userId, reason);
			return true;
		} catch (err) {
			this._error = err instanceof Error ? err.message : 'Failed to kick user';
			return false;
		}
	}

	/**
	 * Search for users by name or ID
	 */
	async searchUsers(
		query: string,
		limit = 10
	): Promise<{ userId: string; displayName?: string; avatarUrl?: string }[]> {
		if (!this._client || !query.trim()) return [];

		try {
			const result = await this._client.searchUserDirectory({ term: query, limit });
			return result.results.map((user) => ({
				userId: user.user_id,
				displayName: user.display_name,
				avatarUrl: user.avatar_url
					? this.getMediaUrl(user.avatar_url, 40, 40) || undefined
					: undefined,
			}));
		} catch {
			return [];
		}
	}

	/**
	 * Search messages in the current room
	 */
	async searchMessages(
		query: string,
		roomId?: string
	): Promise<
		{
			eventId: string;
			sender: string;
			senderName: string;
			body: string;
			timestamp: number;
			roomId: string;
			roomName: string;
		}[]
	> {
		if (!this._client || !query.trim()) return [];

		const targetRoomId = roomId || this._currentRoomId;

		try {
			// Use Matrix search API
			const searchResult = await this._client.searchRoomEvents({
				term: query,
				filter: targetRoomId
					? {
							rooms: [targetRoomId],
						}
					: undefined,
			});

			const results: {
				eventId: string;
				sender: string;
				senderName: string;
				body: string;
				timestamp: number;
				roomId: string;
				roomName: string;
			}[] = [];

			// Process search results - cast to any since SDK types are incomplete
			// eslint-disable-next-line @typescript-eslint/no-explicit-any
			const searchData = searchResult as any;
			const searchResults = searchData?.search_categories?.room_events?.results || [];
			for (const result of searchResults) {
				const event = result.result;
				if (!event) continue;

				const eventRoomId = event.room_id;
				const room = this._client.getRoom(eventRoomId);
				const content = event.content as { body?: string };

				results.push({
					eventId: event.event_id || '',
					sender: event.sender || '',
					senderName: room?.getMember(event.sender || '')?.name || event.sender || 'Unbekannt',
					body: content?.body || '',
					timestamp: event.origin_server_ts || 0,
					roomId: eventRoomId || '',
					roomName: room?.name || 'Unbekannt',
				});
			}

			return results;
		} catch (e) {
			console.error('Search failed:', e);
			return [];
		}
	}

	/**
	 * Get room members
	 */
	getRoomMembers(roomId?: string): RoomMember[] {
		const id = roomId || this._currentRoomId;
		if (!this._client || !id) return [];

		const room = this._client.getRoom(id);
		if (!room) return [];

		// Get power levels from room state
		const powerLevelsEvent = room.currentState.getStateEvents('m.room.power_levels', '');
		// eslint-disable-next-line @typescript-eslint/no-explicit-any
		const powerLevels = (powerLevelsEvent as any)?.getContent?.()?.users || {};
		const defaultPowerLevel = (powerLevelsEvent as any)?.getContent?.()?.users_default || 0;

		return room.getMembersWithMembership('join').map((member) => ({
			userId: member.userId,
			displayName: member.name || member.userId,
			avatarUrl:
				member.getAvatarUrl(this._client!.baseUrl, 40, 40, 'scale', false, false) || undefined,
			membership: member.membership as RoomMember['membership'],
			powerLevel: powerLevels[member.userId] ?? defaultPowerLevel,
		}));
	}

	/**
	 * Get widgets in a room
	 */
	getRoomWidgets(roomId?: string): RoomWidget[] {
		const id = roomId || this._currentRoomId;
		if (!this._client || !id) return [];

		const room = this._client.getRoom(id);
		if (!room) return [];

		const widgets: RoomWidget[] = [];

		// Get all widget state events (im.vector.modular.widgets is the standard type)
		const widgetEvents = room.currentState.getStateEvents('im.vector.modular.widgets');

		for (const event of widgetEvents) {
			// eslint-disable-next-line @typescript-eslint/no-explicit-any
			const content = (event as any).getContent?.();
			if (!content || !content.url) continue; // Skip removed widgets

			widgets.push({
				id: content.id || (event as any).getStateKey?.() || '',
				type: content.type || 'm.custom',
				name: content.name || 'Widget',
				url: content.url,
				creatorUserId: content.creatorUserId || (event as any).getSender?.() || '',
				data: content.data,
			});
		}

		return widgets;
	}

	/**
	 * Build widget URL with Matrix variable substitution
	 */
	buildWidgetUrl(widget: RoomWidget, roomId?: string): string {
		const id = roomId || this._currentRoomId;
		const userId = this._client?.getUserId() || '';

		let url = widget.url;

		// Substitute Matrix variables
		url = url.replace(/\$matrix_user_id/g, encodeURIComponent(userId));
		url = url.replace(/\$matrix_room_id/g, encodeURIComponent(id || ''));
		url = url.replace(
			/\$matrix_display_name/g,
			encodeURIComponent(userId.split(':')[0].substring(1))
		);
		url = url.replace(/\$matrix_avatar_url/g, '');

		return url;
	}

	// ─────────────────────────────────────────────────────────
	// Crypto Actions
	// ─────────────────────────────────────────────────────────

	/**
	 * Set crypto callbacks for UI notifications
	 */
	setCryptoCallbacks(callbacks: CryptoCallbacks) {
		this._cryptoCallbacks = callbacks;
	}

	/**
	 * Check current verification status
	 */
	async checkVerificationStatus(): Promise<void> {
		if (!this._client || !this._cryptoReady) {
			this._verificationStatus = 'unknown';
			return;
		}

		try {
			const crypto = this._client.getCrypto();
			if (!crypto) {
				this._verificationStatus = 'unknown';
				return;
			}

			const crossSigningStatus = await crypto.getCrossSigningStatus();
			if (crossSigningStatus.publicKeysOnDevice && crossSigningStatus.privateKeysCachedLocally) {
				this._verificationStatus = 'verified';
				this._crossSigningReady = true;
			} else {
				this._verificationStatus = 'unverified';
				this._crossSigningReady = false;
			}
		} catch (err) {
			console.error('Error checking verification status:', err);
			this._verificationStatus = 'unknown';
		}
	}

	/**
	 * Check key backup status
	 */
	async checkKeyBackupStatus(): Promise<void> {
		if (!this._client || !this._cryptoReady) {
			this._keyBackupEnabled = false;
			return;
		}

		try {
			const crypto = this._client.getCrypto();
			if (!crypto) return;

			const backupInfo = await crypto.getActiveSessionBackupVersion();
			this._keyBackupEnabled = backupInfo !== null;
		} catch (err) {
			console.error('Error checking key backup status:', err);
			this._keyBackupEnabled = false;
		}
	}

	/**
	 * Get current device ID
	 */
	getDeviceId(): string | null {
		return this._client?.getDeviceId() || null;
	}

	/**
	 * Get all devices for a user
	 */
	async getDevices(userId?: string): Promise<DeviceInfo[]> {
		if (!this._client || !this._cryptoReady) return [];

		const targetUserId = userId || this._client.getUserId();
		if (!targetUserId) return [];

		try {
			const crypto = this._client.getCrypto();
			if (!crypto) return [];

			const deviceMap = await crypto.getUserDeviceInfo([targetUserId]);
			const devices = deviceMap.get(targetUserId);
			if (!devices) return [];

			const currentDeviceId = this._client.getDeviceId();

			// eslint-disable-next-line @typescript-eslint/no-explicit-any
			return Array.from(devices.values()).map((device: any) => ({
				deviceId: device.deviceId,
				displayName: device.displayName,
				// DeviceVerification enum values may vary - check for Verified state
				verified:
					device.verified === 1 || device.verified === 'Verified' || device.isVerified?.() === true,
				blocked: device.verified === 2 || device.verified === 'Blocked',
				isCurrentDevice: device.deviceId === currentDeviceId,
			}));
		} catch (err) {
			console.error('Error getting devices:', err);
			return [];
		}
	}

	/**
	 * Start verification with another device
	 * Note: Verification flow varies by SDK version - this is a simplified approach
	 */
	async startVerification(targetUserId?: string, _targetDeviceId?: string): Promise<boolean> {
		if (!this._client || !this._cryptoReady) return false;

		const userId = targetUserId || this._client.getUserId();
		if (!userId) return false;

		try {
			const crypto = this._client.getCrypto();
			if (!crypto) return false;

			// Use requestOwnUserVerification for self-verification
			// eslint-disable-next-line @typescript-eslint/no-explicit-any
			const cryptoAny = crypto as any;
			if (userId === this._client.getUserId() && cryptoAny.requestOwnUserVerification) {
				const request = await cryptoAny.requestOwnUserVerification();
				console.log('Self-verification started:', request);
			} else if (cryptoAny.requestVerificationDM) {
				// Try DM-based verification for other users
				const request = await cryptoAny.requestVerificationDM(userId);
				console.log('Verification started:', request);
			} else {
				console.warn('Verification method not available in this SDK version');
				return false;
			}

			return true;
		} catch (err) {
			console.error('Error starting verification:', err);
			this._error = 'Failed to start verification';
			return false;
		}
	}

	/**
	 * Accept incoming verification request
	 */
	async acceptVerification(_requestId: string): Promise<boolean> {
		if (!this._client || !this._cryptoReady) return false;

		try {
			// Verification request handling is complex and varies by SDK version
			// For now, log and return success to allow UI to proceed
			console.log('Accept verification - handled by SDK automatically');
			return true;
		} catch (err) {
			console.error('Error accepting verification:', err);
			return false;
		}
	}

	/**
	 * Confirm SAS verification (emoji match)
	 */
	async confirmSasVerification(_requestId: string): Promise<boolean> {
		if (!this._client || !this._cryptoReady) return false;

		try {
			// In newer SDK versions, verification is handled via verifier events
			// This is a simplified approach
			console.log('Confirm SAS verification - handled by SDK');
			return true;
		} catch (err) {
			console.error('Error confirming SAS verification:', err);
			return false;
		}
	}

	/**
	 * Cancel verification
	 */
	async cancelVerification(_requestId: string): Promise<void> {
		if (!this._client || !this._cryptoReady) return;

		try {
			// Cancel is handled at the request level
			this._activeVerification = null;
			console.log('Verification cancelled');
		} catch (err) {
			console.error('Error cancelling verification:', err);
		}
	}

	/**
	 * Bootstrap secret storage and cross-signing
	 */
	async bootstrapSecretStorage(passphrase?: string): Promise<{ recoveryKey: string } | null> {
		if (!this._client || !this._cryptoReady) return null;

		try {
			const crypto = this._client.getCrypto();
			if (!crypto) return null;

			let recoveryKey = '';

			// Bootstrap cross-signing first
			await crypto.bootstrapCrossSigning({
				authUploadDeviceSigningKeys: async (makeRequest) => {
					// This callback is called when we need to authenticate for uploading keys
					// In a real app, this might show a UIA (User Interactive Auth) dialog
					await makeRequest({});
				},
			});

			// Bootstrap secret storage
			await crypto.bootstrapSecretStorage({
				createSecretStorageKey: async () => {
					// Generate a new recovery key
					const keyInfo = await crypto.createRecoveryKeyFromPassphrase(passphrase);
					recoveryKey = keyInfo.encodedPrivateKey || '';
					return keyInfo;
				},
			});

			// Reset key backup
			await crypto.resetKeyBackup();

			this._crossSigningReady = true;
			this._keyBackupEnabled = true;
			this._verificationStatus = 'verified';

			return { recoveryKey };
		} catch (err) {
			console.error('Error bootstrapping secret storage:', err);
			this._error = 'Failed to setup encryption keys';
			return null;
		}
	}

	/**
	 * Restore keys from recovery key
	 */
	async restoreFromRecoveryKey(recoveryKey: string): Promise<boolean> {
		if (!this._client || !this._cryptoReady) return false;

		try {
			const crypto = this._client.getCrypto();
			if (!crypto) return false;

			// eslint-disable-next-line @typescript-eslint/no-explicit-any
			const cryptoAny = crypto as any;
			const clientAny = this._client as any;

			// Restore from backup using recovery key
			// Method names may vary by SDK version
			if (cryptoAny.restoreKeyBackupWithRecoveryKey) {
				await cryptoAny.restoreKeyBackupWithRecoveryKey(recoveryKey);
			} else if (clientAny.restoreKeyBackupWithRecoveryKey) {
				const backupInfo = await clientAny.getKeyBackupVersion?.();
				if (backupInfo) {
					await clientAny.restoreKeyBackupWithRecoveryKey(
						recoveryKey,
						undefined,
						undefined,
						backupInfo
					);
				}
			} else {
				console.warn('Key backup restore not available in this SDK version');
				return false;
			}

			this._keyBackupEnabled = true;
			await this.checkVerificationStatus();
			return true;
		} catch (err) {
			console.error('Error restoring from recovery key:', err);
			this._error = 'Failed to restore encryption keys';
			return false;
		}
	}

	/**
	 * Get cross-signing status
	 */
	async getCrossSigningStatus(): Promise<CrossSigningStatus | null> {
		if (!this._client || !this._cryptoReady) return null;

		try {
			const crypto = this._client.getCrypto();
			if (!crypto) return null;

			const status = await crypto.getCrossSigningStatus();
			// Status properties may be booleans or objects depending on SDK version
			// eslint-disable-next-line @typescript-eslint/no-explicit-any
			const statusAny = status as any;
			return {
				publicKeysOnDevice: !!statusAny.publicKeysOnDevice,
				privateKeysInSecretStorage: !!statusAny.privateKeysInSecretStorage,
				privateKeysCachedLocally: !!statusAny.privateKeysCachedLocally,
			};
		} catch (err) {
			console.error('Error getting cross-signing status:', err);
			return null;
		}
	}

	/**
	 * Check if a room is encrypted
	 */
	isRoomEncrypted(roomId?: string): boolean {
		const id = roomId || this._currentRoomId;
		if (!this._client || !id) return false;

		const room = this._client.getRoom(id);
		return room?.hasEncryptionStateEvent() ?? false;
	}

	/**
	 * Get room encryption status with details
	 */
	async getRoomEncryptionStatus(roomId?: string): Promise<{
		encrypted: boolean;
		allDevicesVerified: boolean;
		unverifiedDevices: number;
	}> {
		const id = roomId || this._currentRoomId;
		if (!this._client || !id) {
			return { encrypted: false, allDevicesVerified: false, unverifiedDevices: 0 };
		}

		const room = this._client.getRoom(id);
		if (!room) {
			return { encrypted: false, allDevicesVerified: false, unverifiedDevices: 0 };
		}

		const encrypted = room.hasEncryptionStateEvent();
		if (!encrypted || !this._cryptoReady) {
			return { encrypted, allDevicesVerified: false, unverifiedDevices: 0 };
		}

		try {
			const crypto = this._client.getCrypto();
			if (!crypto) {
				return { encrypted, allDevicesVerified: false, unverifiedDevices: 0 };
			}

			// Get all members and their devices
			const members = room.getMembersWithMembership('join');
			const userIds = members.map((m) => m.userId);
			const deviceMap = await crypto.getUserDeviceInfo(userIds);

			let unverifiedCount = 0;
			for (const [userId, devices] of deviceMap) {
				for (const device of devices.values()) {
					if (device.verified !== 1) {
						// Not verified
						unverifiedCount++;
					}
				}
			}

			return {
				encrypted,
				allDevicesVerified: unverifiedCount === 0,
				unverifiedDevices: unverifiedCount,
			};
		} catch {
			return { encrypted, allDevicesVerified: false, unverifiedDevices: 0 };
		}
	}

	// ─────────────────────────────────────────────────────────
	// VoIP / Call Actions
	// ─────────────────────────────────────────────────────────

	/**
	 * Set call callbacks for UI notifications
	 */
	setCallCallbacks(callbacks: CallCallbacks) {
		this._callCallbacks = callbacks;
	}

	/**
	 * Place a voice call to the current room
	 */
	async placeVoiceCall(roomId?: string): Promise<boolean> {
		const targetRoomId = roomId || this._currentRoomId;
		if (!this._client || !targetRoomId) return false;

		try {
			// Import WebRTC types from the submodule
			const webrtc = await import('matrix-js-sdk/lib/webrtc/call');
			const { CallEvent, CallState } = webrtc;

			// Create the call
			const call = this._client.createCall(targetRoomId);
			if (!call) {
				console.error('Failed to create call');
				return false;
			}

			this._matrixCall = call;

			// Set up event handlers
			this.setupCallEventHandlers(call, CallEvent, CallState);

			// Place the voice call
			await call.placeVoiceCall();

			// Update active call state
			this._activeCall = this.matrixCallToSimpleCall(call, 'voice', 'outbound');

			return true;
		} catch (err) {
			console.error('Error placing voice call:', err);
			this._error = 'Failed to start voice call';
			return false;
		}
	}

	/**
	 * Place a video call to the current room
	 */
	async placeVideoCall(roomId?: string): Promise<boolean> {
		const targetRoomId = roomId || this._currentRoomId;
		if (!this._client || !targetRoomId) return false;

		try {
			// Import WebRTC types from the submodule
			const webrtc = await import('matrix-js-sdk/lib/webrtc/call');
			const { CallEvent, CallState } = webrtc;

			// Create the call
			const call = this._client.createCall(targetRoomId);
			if (!call) {
				console.error('Failed to create call');
				return false;
			}

			this._matrixCall = call;

			// Set up event handlers
			this.setupCallEventHandlers(call, CallEvent, CallState);

			// Place the video call
			await call.placeVideoCall();

			// Update active call state
			this._activeCall = this.matrixCallToSimpleCall(call, 'video', 'outbound');

			return true;
		} catch (err) {
			console.error('Error placing video call:', err);
			this._error = 'Failed to start video call';
			return false;
		}
	}

	/**
	 * Answer an incoming call
	 */
	async answerCall(): Promise<boolean> {
		if (!this._matrixCall || !this._incomingCall) return false;

		try {
			await this._matrixCall.answer();
			this._activeCall = { ...this._incomingCall };
			this._incomingCall = null;
			return true;
		} catch (err) {
			console.error('Error answering call:', err);
			return false;
		}
	}

	/**
	 * Reject an incoming call
	 */
	rejectCall(): boolean {
		if (!this._matrixCall || !this._incomingCall) return false;

		try {
			this._matrixCall.reject();
			this._incomingCall = null;
			this._matrixCall = null;
			return true;
		} catch (err) {
			console.error('Error rejecting call:', err);
			return false;
		}
	}

	/**
	 * Hang up the current call
	 */
	hangupCall(): boolean {
		if (!this._matrixCall) return false;

		try {
			this._matrixCall.hangup('user_hangup', false);
			this._activeCall = null;
			this._matrixCall = null;
			return true;
		} catch (err) {
			console.error('Error hanging up call:', err);
			return false;
		}
	}

	/**
	 * Toggle microphone mute
	 */
	toggleMicMute(): boolean {
		if (!this._matrixCall || !this._activeCall) return false;

		try {
			const muted = this._matrixCall.isMicrophoneMuted();
			this._matrixCall.setMicrophoneMuted(!muted);
			this._activeCall = { ...this._activeCall, isMicMuted: !muted };
			return true;
		} catch (err) {
			console.error('Error toggling mic mute:', err);
			return false;
		}
	}

	/**
	 * Toggle camera mute (for video calls)
	 */
	toggleCameraMute(): boolean {
		if (!this._matrixCall || !this._activeCall) return false;

		try {
			const muted = this._matrixCall.isLocalVideoMuted();
			this._matrixCall.setLocalVideoMuted(!muted);
			this._activeCall = { ...this._activeCall, isCameraMuted: !muted };
			return true;
		} catch (err) {
			console.error('Error toggling camera mute:', err);
			return false;
		}
	}

	/**
	 * Toggle screen sharing
	 */
	async toggleScreenShare(): Promise<boolean> {
		if (!this._matrixCall || !this._activeCall) return false;

		try {
			const isSharing = this._activeCall.isScreenSharing;

			if (isSharing) {
				// Stop screen sharing - switch back to camera
				await this._matrixCall.setScreensharingEnabled(false);
				this._activeCall = { ...this._activeCall, isScreenSharing: false };
			} else {
				// Start screen sharing
				const success = await this._matrixCall.setScreensharingEnabled(true, {
					audio: true, // Include system audio if available
				});
				if (success) {
					this._activeCall = { ...this._activeCall, isScreenSharing: true };
				} else {
					console.warn('Screen sharing was denied or failed');
					return false;
				}
			}
			return true;
		} catch (err) {
			console.error('Error toggling screen share:', err);
			this._error = 'Bildschirmfreigabe fehlgeschlagen';
			return false;
		}
	}

	/**
	 * Set up call event handlers
	 */
	// eslint-disable-next-line @typescript-eslint/no-explicit-any
	private setupCallEventHandlers(call: any, CallEvent: any, CallState: any) {
		// State changes
		call.on(CallEvent.State, (state: string, oldState: string) => {
			console.log(`Call state: ${oldState} -> ${state}`);

			if (this._activeCall) {
				this._activeCall = {
					...this._activeCall,
					state: state as CallStateType,
				};
				this._callCallbacks.onCallStateChange?.(this._activeCall);
			}

			// Handle call ending
			if (state === CallState.Ended) {
				const reason = call.hangupReason;
				const endedCall = this._activeCall;
				this._activeCall = null;
				this._matrixCall = null;
				if (endedCall) {
					this._callCallbacks.onCallEnded?.(endedCall, reason);
				}
			}
		});

		// Feeds changed (audio/video streams)
		call.on(CallEvent.FeedsChanged, (feeds: any[]) => {
			if (this._activeCall) {
				const localFeed = feeds.find((f) => f.isLocal());
				const remoteFeed = feeds.find((f) => !f.isLocal());

				this._activeCall = {
					...this._activeCall,
					localStream: localFeed?.stream,
					remoteStream: remoteFeed?.stream,
				};
			}
		});

		// Error handling
		call.on(CallEvent.Error, (error: any) => {
			console.error('Call error:', error);
			this._error = `Call error: ${error.message || 'Unknown error'}`;
		});

		// Hangup
		call.on(CallEvent.Hangup, () => {
			const endedCall = this._activeCall;
			this._activeCall = null;
			this._matrixCall = null;
			if (endedCall) {
				this._callCallbacks.onCallEnded?.(endedCall, call.hangupReason);
			}
		});
	}

	/**
	 * Handle incoming call from the SDK
	 */
	// eslint-disable-next-line @typescript-eslint/no-explicit-any
	private handleIncomingCall(call: any, CallEvent: any, CallState: any) {
		this._matrixCall = call;

		// Determine call type from the call object
		const callType: CallType = call.type === 'video' ? 'video' : 'voice';

		// Create simple call representation
		const simpleCall = this.matrixCallToSimpleCall(call, callType, 'inbound');
		this._incomingCall = simpleCall;

		// Set up event handlers
		this.setupCallEventHandlers(call, CallEvent, CallState);

		// Notify UI
		this._callCallbacks.onIncomingCall?.(simpleCall);
	}

	/**
	 * Convert MatrixCall to SimpleCall
	 */
	// eslint-disable-next-line @typescript-eslint/no-explicit-any
	private matrixCallToSimpleCall(call: any, type: CallType, direction: CallDirection): SimpleCall {
		const opponent = call.getOpponentMember?.();
		const room = this._client?.getRoom(call.roomId);

		return {
			callId: call.callId,
			roomId: call.roomId,
			state: (call.state || 'fledgling') as CallStateType,
			type,
			direction,
			opponentUserId: opponent?.userId,
			opponentName: opponent?.name || room?.name || 'Unbekannt',
			opponentAvatar: opponent?.getAvatarUrl?.(this._client?.baseUrl || '', 48, 48, 'scale'),
			isMicMuted: call.isMicrophoneMuted?.() || false,
			isCameraMuted: call.isLocalVideoMuted?.() || false,
			isScreenSharing: false,
			isRemoteOnHold: call.isRemoteOnHold?.() || false,
		};
	}

	// ─────────────────────────────────────────────────────────
	// Cleanup
	// ─────────────────────────────────────────────────────────

	/**
	 * Stop the client and clean up
	 */
	destroy() {
		// Hang up any active call
		if (this._matrixCall) {
			try {
				this._matrixCall.hangup('user_hangup', false);
			} catch {
				// Ignore errors during cleanup
			}
		}

		this._client?.stopClient();
		this._client = null;
		this._syncState = 'STOPPED';
		this._rooms = [];
		this._timeline = [];
		this._currentRoomId = null;
		this._typingUsers = new Map();
		this._initialized = false;
		// Reset crypto state
		this._cryptoReady = false;
		this._verificationStatus = 'unknown';
		this._activeVerification = null;
		this._keyBackupEnabled = false;
		this._crossSigningReady = false;
		this._cryptoCallbacks = {};
		// Reset call state
		this._activeCall = null;
		this._incomingCall = null;
		this._matrixCall = null;
		this._callCallbacks = {};
	}

	/**
	 * Logout and clear credentials
	 */
	logout() {
		this.destroy();
		if (browser) {
			localStorage.removeItem(STORAGE_KEY);
			localStorage.removeItem(LAST_ROOM_KEY);
		}
	}

	// ─────────────────────────────────────────────────────────
	// Helper Methods
	// ─────────────────────────────────────────────────────────

	/**
	 * Convert SDK Room to SimpleRoom
	 */
	private roomToSimpleRoom(room: Room): SimpleRoom {
		const lastEvent = room
			.getLiveTimeline()
			.getEvents()
			.filter((e) => e.getType() === 'm.room.message')
			.pop();

		// Get topic from state event
		const topicEvent = room.currentState.getStateEvents('m.room.topic', '');
		const topic = (topicEvent as MatrixEvent | null)?.getContent()?.topic;

		// Get membership status
		const myUserId = this._client?.getUserId();
		const myMember = myUserId ? room.getMember(myUserId) : null;
		const membership = (myMember?.membership || 'leave') as SimpleRoom['membership'];

		// Get inviter if this is an invite
		let inviter: string | undefined;
		if (membership === 'invite' && myMember) {
			// The events array contains the invite event
			const inviteEvent = room.currentState.getStateEvents('m.room.member', myUserId || '');
			if (inviteEvent) {
				const sender = (inviteEvent as MatrixEvent).getSender();
				if (sender) {
					const senderMember = room.getMember(sender);
					inviter = senderMember?.name || sender;
				}
			}
		}

		// Get DM user presence info
		const isDirect = this.isDirectRoom(room);
		let dmUserId: string | undefined;
		let presence: SimpleRoom['presence'];
		let lastActiveAgo: number | undefined;

		if (isDirect && myUserId) {
			// Find the other user in the DM
			const members = room.getJoinedMembers();
			const otherMember = members.find((m) => m.userId !== myUserId);
			if (otherMember) {
				dmUserId = otherMember.userId;
				const userPresence = this._userPresence.get(dmUserId);
				if (userPresence) {
					presence = userPresence.presence;
					lastActiveAgo = userPresence.lastActiveAgo;
				} else {
					// Try to get from user object directly
					const user = this._client?.getUser(dmUserId);
					if (user) {
						presence = (user.presence as SimpleRoom['presence']) || 'offline';
						lastActiveAgo = user.lastActiveAgo;
					}
				}
			}
		}

		return {
			id: room.roomId,
			name: room.name || 'Unnamed Room',
			topic,
			avatar: room.getAvatarUrl(this._client?.baseUrl || '', 48, 48, 'scale') || undefined,
			lastMessage: lastEvent?.getContent()?.body,
			lastMessageSender: lastEvent ? this.getSenderName(lastEvent) : undefined,
			lastMessageTime: room.getLastActiveTimestamp() || undefined,
			unreadCount: room.getUnreadNotificationCount('total' as any) || 0,
			highlightCount: room.getUnreadNotificationCount('highlight' as any) || 0,
			isDirect,
			isEncrypted: room.hasEncryptionStateEvent(),
			memberCount: room.getJoinedMemberCount(),
			membership,
			inviter,
			dmUserId,
			presence,
			lastActiveAgo,
		};
	}

	/**
	 * Convert SDK MatrixEvent to SimpleMessage
	 */
	private eventToSimpleMessage(event: MatrixEvent, timeline?: MatrixEvent[]): SimpleMessage {
		const content = event.getContent();
		const relatesTo = content['m.relates_to'];
		const msgtype = content.msgtype || 'm.text';

		// Check if message was redacted
		const isRedacted = event.isRedacted();

		// Extract media info for file/image/video/audio messages
		let media: SimpleMessage['media'] = undefined;
		if (['m.image', 'm.file', 'm.video', 'm.audio'].includes(msgtype) && content.url) {
			const info = content.info || {};
			media = {
				mxcUrl: content.url,
				mimetype: info.mimetype,
				size: info.size,
				width: info.w,
				height: info.h,
				filename: content.filename || content.body,
				thumbnailUrl: info.thumbnail_url,
				duration: info.duration,
			};
		}

		// Get reply-to body if this is a reply
		let replyToBody: string | undefined;
		const replyToId = relatesTo?.['m.in_reply_to']?.event_id;
		if (replyToId) {
			const room = this._client?.getRoom(event.getRoomId() || '');
			const replyEvent = room?.findEventById(replyToId);
			if (replyEvent) {
				replyToBody = replyEvent.getContent().body;
			}
		}

		// Collect reactions for this message
		const reactions = this.getReactionsForEvent(event.getId() || '', timeline);

		// Get read receipts for this message (only for own messages)
		const isOwn = event.getSender() === this._client?.getUserId();
		const readBy = isOwn ? this.getReadReceiptsForEvent(event) : undefined;

		return {
			id: event.getId() || '',
			sender: event.getSender() || '',
			senderName: this.getSenderName(event),
			body: isRedacted ? 'Message deleted' : content.body || '',
			formattedBody: content.formatted_body,
			timestamp: event.getTs(),
			type: msgtype as MessageType,
			isOwn,
			replyTo: replyToId,
			replyToBody,
			edited: !!event.replacingEvent(),
			redacted: isRedacted,
			media,
			reactions: reactions.length > 0 ? reactions : undefined,
			readBy: readBy && readBy.length > 0 ? readBy : undefined,
		};
	}

	/**
	 * Get reactions for a specific event
	 */
	private getReactionsForEvent(eventId: string, timeline?: MatrixEvent[]): MessageReaction[] {
		if (!timeline || !eventId) return [];

		const myUserId = this._client?.getUserId();
		const reactionMap = new Map<string, { users: string[]; senders: Set<string> }>();

		// Find all m.reaction events that relate to this event
		for (const event of timeline) {
			if (event.getType() !== 'm.reaction') continue;

			const content = event.getContent();
			const relatesTo = content['m.relates_to'];

			if (
				relatesTo?.rel_type === 'm.annotation' &&
				relatesTo?.event_id === eventId &&
				relatesTo?.key
			) {
				const emoji = relatesTo.key;
				const sender = event.getSender() || '';

				if (!reactionMap.has(emoji)) {
					reactionMap.set(emoji, { users: [], senders: new Set() });
				}

				const entry = reactionMap.get(emoji)!;
				// Avoid duplicates from same user
				if (!entry.senders.has(sender)) {
					entry.senders.add(sender);
					entry.users.push(sender);
				}
			}
		}

		// Convert to MessageReaction array
		const reactions: MessageReaction[] = [];
		for (const [key, data] of reactionMap) {
			reactions.push({
				key,
				count: data.users.length,
				users: data.users,
				includesMe: myUserId ? data.senders.has(myUserId) : false,
			});
		}

		// Sort by count descending
		return reactions.sort((a, b) => b.count - a.count);
	}

	/**
	 * Get read receipts for a specific event
	 */
	private getReadReceiptsForEvent(event: MatrixEvent): ReadReceipt[] {
		const eventId = event.getId();
		const roomId = event.getRoomId();
		if (!eventId || !roomId || !this._client) return [];

		const room = this._client.getRoom(roomId);
		if (!room) return [];

		const myUserId = this._client.getUserId();
		const receipts: ReadReceipt[] = [];

		// Get all members who have read up to or past this event
		const members = room.getJoinedMembers();
		for (const member of members) {
			// Skip self
			if (member.userId === myUserId) continue;

			// Get the user's read receipt
			const receiptEvent = room.getEventReadUpTo(member.userId);
			if (!receiptEvent) continue;

			// Check if their read receipt is at or after this event
			const receiptEventObj = room.findEventById(receiptEvent);
			if (receiptEventObj && receiptEventObj.getTs() >= event.getTs()) {
				receipts.push({
					userId: member.userId,
					userName: member.name || member.userId.split(':')[0].substring(1),
					timestamp: receiptEventObj.getTs(),
				});
			}
		}

		return receipts;
	}

	/**
	 * Get display name for message sender
	 */
	private getSenderName(event: MatrixEvent): string {
		const room = this._client?.getRoom(event.getRoomId() || '');
		const member = room?.getMember(event.getSender() || '');
		return member?.name || event.getSender()?.split(':')[0].substring(1) || 'Unknown';
	}

	/**
	 * Check if room is a direct message room
	 */
	private isDirectRoom(room: Room): boolean {
		const dmContent = this._client?.getAccountData('m.direct' as any)?.getContent() || {};
		return Object.values(dmContent).flat().includes(room.roomId);
	}

	/**
	 * Load credentials from localStorage
	 */
	private loadCredentials(): MatrixCredentials | null {
		if (!browser) return null;

		try {
			const stored = localStorage.getItem(STORAGE_KEY);
			return stored ? JSON.parse(stored) : null;
		} catch {
			return null;
		}
	}

	/**
	 * Save credentials to localStorage
	 */
	private saveCredentials(creds: MatrixCredentials) {
		if (browser) {
			localStorage.setItem(STORAGE_KEY, JSON.stringify(creds));
		}
	}

	/**
	 * Check if credentials exist in storage
	 */
	hasStoredCredentials(): boolean {
		return this.loadCredentials() !== null;
	}
}

// Export singleton instance
export const matrixStore = new MatrixStore();
