// Postgres 变更订阅参数类型（强类型导出，便于 UTS Android 复用）
export type PostgresChangesSubscribeParams = {
	event : string;
	schema : string;
	table : string;
	filter ?: string;
	topic ?: string;
	onChange : (payload : any) => void;
};

type PostgresChangeListener = {
	topic : string;
	event : string;
	schema : string;
	table : string;
	filter : string | null;
	onChange : (payload : any) => void;
};

export type AkSupaRealtimeOptions = {
	url : string; // ws/wss 地址
	channel : string; // 订阅频道
	token ?: string; // 可选，鉴权token
	apikey ?: string; // 可选，supabase apikey
	onMessage : (data : UTSJSONObject) => void;
	onOpen ?: (res : any) => void;
	onClose ?: (res : any) => void;
	onError ?: (err : any) => void;
};

export class AkSupaRealtime {
	ws : SocketTask | null = null;
	options : AkSupaRealtimeOptions | null = null;
	isOpen : boolean = false;
	heartbeatTimer : any = 0;
	joinedTopics : Set<string> = new Set<string>();
	listeners : Array<PostgresChangeListener> = [];

	constructor(options : AkSupaRealtimeOptions) {
		this.options = options;
	}

	connect() {
		const opts = this.options;
		if (opts == null) return;
		// 拼接 apikey 和 vsn=1.0.0 到 ws url
		let wsUrl = opts.url;
		// apikey 兼容 query 已有参数和无参数两种情况
		if (opts.apikey != null && opts.apikey !== "") {
			const hasQuery = wsUrl.indexOf('?') != -1;
			// 移除已有 apikey 参数，避免重复
			wsUrl = wsUrl.replace(/([&?])apikey=[^&]*/g, '$1').replace(/[?&]$/, '');
			wsUrl += (hasQuery ? '&' : '?') + 'apikey=' + encodeURIComponent('' + opts.apikey);
		}
		if (wsUrl.indexOf('vsn=') == -1) {
			wsUrl += (wsUrl.indexOf('?') == -1 ? '?' : '&') + 'vsn=1.0.0';
		}
		this.ws = uni.connectSocket({
			url: wsUrl,
			success: (res) => { console.log(res); },
			fail: (err) => { if (opts.onError != null) opts.onError?.(err); }
		});
		if (this.ws != null) {
			const wsTask = this.ws;
			wsTask?.onOpen((result : OnSocketOpenCallbackResult) => {
				this.isOpen = true;
				console.log('onopen', result)
				if (opts.onOpen != null) opts.onOpen?.(result);
				// 启动 heartbeat 定时器
				this.startHeartbeat();
			});
			wsTask?.onMessage((msg) => {
				console.log(msg)
				let data : UTSJSONObject | null = null;
				try {
					const msgData = (typeof msg == 'object' && msg.data !== null) ? msg.data : msg;
					data = typeof msgData == 'string' ? JSON.parse(msgData) as UTSJSONObject : msgData as UTSJSONObject;
				} catch (e) { }
				// 处理 pong
				if (
					data != null &&
					data.event == 'phx_reply' &&
					typeof data.payload == 'object' &&
					data.payload != null &&
					(data.payload as UTSJSONObject).status != null &&
					(data.payload as UTSJSONObject).status == 'ok' &&
					(data.payload as UTSJSONObject).response != null &&
					(data.payload as UTSJSONObject).response == 'heartbeat'
				) {
					// 收到 pong，可用于续约
					// 可选：重置定时器
				}
				console.log(data)
				if (data != null) this.dispatchPostgresChange(data);
				if (opts?.onMessage != null) opts.onMessage?.(data ?? ({} as UTSJSONObject));
			});
			wsTask?.onClose((res) => {
				console.log('onclose', res)
				this.isOpen = false;
				this.joinedTopics.clear();
				this.listeners = [];
				if (opts.onClose != null) opts.onClose?.(res);
				this.stopHeartbeat();
			});
			wsTask?.onError((err) => {
				console.log(err)
				if (opts.onError != null) opts.onError?.(err);
				this.stopHeartbeat();
			});
		}
	}

	send(options : SendSocketMessageOptions) {
		const wsTask = this.ws;
		if (wsTask != null && this.isOpen) {
			console.log('send:', options)
			// 兼容 uni-app-x send API，支持 success/fail 回调
			// 只允许 SendSocketMessageOptions 类型，避免 UTSJSONObject 混用
			let sendData : any = options.data;
			// 若 data 不是字符串，自动序列化
			if (typeof sendData !== 'string') {
				sendData = JSON.stringify(sendData);
			}
			options.success ?? ((res) => {
				if (typeof options.success == 'function') options.success?.(res)
			})
			options.fail ?? ((err : any) => {
				console.log(err)
				const opts = this.options;
				if (opts != null && opts.onError != null) opts.onError?.(err);
			})
			wsTask.send(options);
		}

	}

	close(options : CloseSocketOptions) {
		this.ws?.close(options);
	}

	/**
	 * 订阅 Postgres 变更事件（如 INSERT/UPDATE/DELETE）
	 * @param params { event: 'INSERT'|'UPDATE'|'DELETE', schema: string, table: string, onChange: (payload: any) => void }
	 */
	/**
	 * 订阅 Postgres 变更事件（如 INSERT/UPDATE/DELETE）
	 * @param params { event: 'INSERT'|'UPDATE'|'DELETE', schema: string, table: string, onChange: (payload: any) => void }
	 */
	/**
	 * 订阅 Postgres 变更事件（如 INSERT/UPDATE/DELETE）
	 * @param params { event: 'INSERT'|'UPDATE'|'DELETE', schema: string, table: string, onChange: (payload: any) => void }
	 */
	/**
	 * 订阅 Postgres 变更事件（如 INSERT/UPDATE/DELETE）
	 * @param params { event: 'INSERT'|'UPDATE'|'DELETE', schema: string, table: string, onChange: (payload: any) => void }
	 */
	/**
	 * 订阅 Postgres 变更事件（如 INSERT/UPDATE/DELETE）
	 * @param params { event: 'INSERT'|'UPDATE'|'DELETE', schema: string, table: string, onChange: (payload: any) => void }
	 */
	public subscribePostgresChanges(params : PostgresChangesSubscribeParams) : void {
		const opts = this.options;
		if (this.isOpen !== true || opts == null) {
			throw new Error('WebSocket 未连接');
		}
		const topic = params.topic != null && params.topic !== '' ? params.topic : `realtime:${params.schema}:${params.table}`;
		this.joinTopicIfNeeded(topic, params);
		this.listeners.push({
			topic: topic,
			event: params.event,
			schema: params.schema,
			table: params.table,
			filter: params.filter != null ? params.filter : null,
			onChange: params.onChange
		});
	}

	startHeartbeat() {
		this.stopHeartbeat();
		console.log('make heartbeat')
		// 每 30 秒发送一次 heartbeat（官方建议）
		this.heartbeatTimer = setInterval(() => {
			console.log('should startHeartbeat')
			if (this.isOpen && this.ws != null) {
				const heartbeatMsg = {
					topic: 'phoenix',
					event: 'heartbeat',
					payload: {},
					ref: Date.now().toString()
				};
				this.send({ data: JSON.stringify(heartbeatMsg) });
			}
		}, 30000);
	}

	stopHeartbeat() {
		console.log('stop heartbeat')
		if (typeof this.heartbeatTimer == 'number' && this.heartbeatTimer > 0) {
			clearInterval(this.heartbeatTimer as number);
			this.heartbeatTimer = 0;
		}
	}

	private joinTopicIfNeeded(topic : string, params : PostgresChangesSubscribeParams) {
		if (topic == null || topic == '') return;
		if (this.joinedTopics.has(topic)) return;
		
		const changeConfig : any = (params.filter != null && params.filter !== '') ? {
			event: params.event,
			schema: params.schema,
			table: params.table,
			filter: params.filter
		} : {
			event: params.event,
			schema: params.schema,
			table: params.table
		};

		const joinMsg = {
			event: 'phx_join',
			payload: {
				config: {
					broadcast: { self: false, ack: false },
					postgres_changes: [changeConfig],
					presence: { key: '', enabled: false },
					private: false
				},
				access_token: this.options != null && this.options.token != null ? this.options.token : null
			},
			ref: Date.now().toString(),
			topic: topic
		};
		this.send({ data: JSON.stringify(joinMsg) });
		this.joinedTopics.add(topic);
	}

	private dispatchPostgresChange(data : UTSJSONObject) : void {
		if (data.event !== 'postgres_changes') return;
		const topic = typeof data.topic == 'string' ? data.topic : '';
		const payload = data.payload as UTSJSONObject | null;
		if (payload == null) return;
		const dataSection = payload.get('data') as UTSJSONObject | null;
		let payloadEvent = payload.getString('event') as string | null;
		if ((payloadEvent == null || payloadEvent == '') && dataSection != null) {
			const typeValue = dataSection.getString('type') as string | null;
			if (typeValue != null && typeValue !== '') payloadEvent = typeValue;
		}
		let schemaName = payload.getString('schema') as string | null;
		if ((schemaName == null || schemaName == '') && dataSection != null) {
			const dataSchema = dataSection.getString('schema') as string | null;
			if (dataSchema != null && dataSchema !== '') schemaName = dataSchema;
		}
		let tableName = payload.getString('table') as string | null;
		if ((tableName == null || tableName == '') && dataSection != null) {
			const dataTable = dataSection.getString('table') as string | null;
			if (dataTable != null && dataTable !== '') tableName = dataTable;
		}
		const filterValue = payload.getString('filter') as string | null;
		for (let i = 0; i < this.listeners.length; i++) {
			const listener = this.listeners[i];
			if (listener.topic !== topic) continue;
			if (listener.event !== '*' && payloadEvent != null && listener.event !== payloadEvent) continue;
			if (schemaName != null && listener.schema !== schemaName) continue;
			if (tableName != null && listener.table !== tableName) continue;
			if (
				listener.filter != null && listener.filter !== '' &&
				filterValue != null && listener.filter !== filterValue
			) continue;
			if (typeof listener.onChange == 'function') {
				const changeData = dataSection != null ? dataSection : payload;
				listener.onChange(changeData);
			}
		}
	}
}

export default AkSupaRealtime;