import { Signal, computed } from '@tldraw/state'
import { CameraRecordType } from './records/TLCamera'
import { TLINSTANCE_ID } from './records/TLInstance'
import { InstancePageStateRecordType } from './records/TLPageState'
import { TLPOINTER_ID } from './records/TLPointer'
import { InstancePresenceRecordType, TLInstancePresence } from './records/TLPresence'
import { TLUser } from './records/TLUser'
import { TLStore } from './TLStore'

/** @public */
export interface CreatePresenceStateDerivationOpts {
	/** Custom instance ID. If not provided, one is generated from the store ID. */
	instanceId?: TLInstancePresence['id']
	/**
	 * Override how presence state is built from the store and current user.
	 * Defaults to {@link getDefaultUserPresence}.
	 */
	getUserPresence?(store: TLStore, user: TLUser): TLPresenceStateInfo | null
}

/**
 * Creates a derivation that represents the current presence state of the current user.
 *
 * This function returns a derivation factory that, when given a store, creates a computed signal
 * containing the user's current presence state. The presence state includes information like cursor
 * position, selected shapes, camera position, and user metadata that gets synchronized in
 * multiplayer scenarios.
 *
 * @param $user - A reactive signal containing the user information, or `null` when anonymous
 * @param opts - Optional configuration for instance ID and presence derivation
 * @returns A function that takes a store and returns a computed signal of the user's presence state
 *
 * @example
 * ```ts
 * import { createPresenceStateDerivation } from '@tldraw/tlschema'
 * import { atom } from '@tldraw/state'
 *
 * const userSignal = atom('user', { id: 'user-123', name: 'Alice', color: '#ff0000', meta: {} })
 * const presenceDerivation = createPresenceStateDerivation(userSignal)
 *
 * // Use with a store to get reactive presence state
 * const presenceState = presenceDerivation(store)
 * console.log(presenceState.get()) // Current user presence or null
 * ```
 *
 * @public
 */
export function createPresenceStateDerivation(
	$user: Signal<TLUser | null>,
	opts?: CreatePresenceStateDerivationOpts
) {
	const { instanceId, getUserPresence: _getUserPresence } = opts ?? {}
	const getUserPresence = _getUserPresence ?? getDefaultUserPresence
	return (store: TLStore): Signal<TLInstancePresence | null> => {
		return computed('instancePresence', () => {
			const user = $user.get()
			if (!user) return null

			const state = getUserPresence(store, user)
			if (!state) return null

			return InstancePresenceRecordType.create({
				...state,
				id: instanceId ?? InstancePresenceRecordType.createId(store.id),
			})
		})
	}
}

/**
 * The shape of data used to create a presence record.
 *
 * This type represents all the properties needed to construct a TLInstancePresence record.
 * It includes user information, cursor state, camera position, selected shapes, and other
 * presence-related data that gets synchronized across multiplayer clients.
 *
 * @public
 */
export type TLPresenceStateInfo = Parameters<(typeof InstancePresenceRecordType)['create']>[0]

/**
 * Creates default presence state information for a user based on the current store state.
 *
 * This function extracts the current state from various store records (instance, page state,
 * camera, pointer) and combines them with user information to create a complete presence
 * state object. This is commonly used as a starting point for custom presence implementations.
 *
 * @param store - The tldraw store containing the current editor state
 * @param user - The user information to include in the presence state
 * @returns The default presence state info, or null if required store records are missing
 *
 * @example
 * ```ts
 * import { getDefaultUserPresence } from '@tldraw/tlschema'
 *
 * const user = { id: 'user-123', name: 'Alice', color: '#ff0000', meta: {} }
 * const presenceInfo = getDefaultUserPresence(store, user)
 *
 * if (presenceInfo) {
 *   console.log('Current cursor:', presenceInfo.cursor)
 *   console.log('Selected shapes:', presenceInfo.selectedShapeIds)
 *   console.log('Camera position:', presenceInfo.camera)
 * }
 * ```
 *
 * @example
 * ```ts
 * // Common pattern: customize default presence
 * const customPresence = {
 *   ...getDefaultUserPresence(store, user),
 *   // Remove camera for privacy
 *   camera: undefined,
 *   // Add custom metadata
 *   customField: 'my-data'
 * }
 * ```
 *
 * @public
 */
export function getDefaultUserPresence(store: TLStore, user: TLUser) {
	const instance = store.get(TLINSTANCE_ID)
	const pageState = store.get(InstancePageStateRecordType.createId(instance?.currentPageId))
	const camera = store.get(CameraRecordType.createId(instance?.currentPageId))
	const pointer = store.get(TLPOINTER_ID)
	if (!pageState || !instance || !camera || !pointer) {
		return null
	}

	return {
		selectedShapeIds: pageState.selectedShapeIds,
		brush: instance.brush,
		scribbles: instance.scribbles,
		userId: user.id,
		userName: user.name,
		followingUserId: instance.followingUserId,
		camera: {
			x: camera.x,
			y: camera.y,
			z: camera.z,
		},
		color: user.color || '#FF0000',
		currentPageId: instance.currentPageId,
		cursor: {
			x: pointer.x,
			y: pointer.y,
			rotation: instance.cursor.rotation,
			type: instance.cursor.type,
		},
		lastActivityTimestamp: pointer.lastActivityTimestamp,
		screenBounds: instance.screenBounds,
		chatMessage: instance.chatMessage,
		meta: {},
	} satisfies TLPresenceStateInfo
}
