import EventEmitter from "node:events"
import * as crypto from "node:crypto"

import ipc from "node-ipc"

import {
	type TaskCommand,
	type IpcClientEvents,
	type IpcMessage,
	IpcOrigin,
	IpcMessageType,
	TaskCommandName,
	ipcMessageSchema,
} from "@roo-code/types"

export class IpcClient extends EventEmitter<IpcClientEvents> {
	private readonly _socketPath: string
	private readonly _id: string
	private readonly _log: (...args: unknown[]) => void
	private _isConnected = false
	private _clientId?: string

	constructor(socketPath: string, log = console.log) {
		super()

		this._socketPath = socketPath
		this._id = `costrict-evals-${crypto.randomBytes(6).toString("hex")}`
		this._log = log

		ipc.config.silent = true

		ipc.connectTo(this._id, this.socketPath, () => {
			ipc.of[this._id]?.on("connect", () => this.onConnect())
			ipc.of[this._id]?.on("disconnect", () => this.onDisconnect())
			ipc.of[this._id]?.on("message", (data) => this.onMessage(data))
		})
	}

	private onConnect() {
		if (this._isConnected) {
			return
		}

		this.log("[client#onConnect]")
		this._isConnected = true
		this.emit(IpcMessageType.Connect)
	}

	private onDisconnect() {
		if (!this._isConnected) {
			return
		}

		this.log("[client#onDisconnect]")
		this._isConnected = false
		this.emit(IpcMessageType.Disconnect)
	}

	private onMessage(data: unknown) {
		if (typeof data !== "object") {
			this.log(`[client#onMessage] invalid data -> ${JSON.stringify(data)}`)
			return
		}

		const result = ipcMessageSchema.safeParse(data)

		if (!result.success) {
			this.log(
				`[client#onMessage] invalid payload -> ${JSON.stringify(result.error.issues)} -> ${JSON.stringify(data)}`,
			)
			return
		}

		const payload = result.data

		if (payload.origin === IpcOrigin.Server) {
			switch (payload.type) {
				case IpcMessageType.Ack:
					this._clientId = payload.data.clientId
					this.emit(IpcMessageType.Ack, payload.data)
					break
				case IpcMessageType.TaskEvent:
					this.emit(IpcMessageType.TaskEvent, payload.data)
					break
			}
		}
	}

	private log(...args: unknown[]) {
		this._log(...args)
	}

	public sendCommand(command: TaskCommand) {
		const message: IpcMessage = {
			type: IpcMessageType.TaskCommand,
			origin: IpcOrigin.Client,
			clientId: this._clientId!,
			data: command,
		}

		this.sendMessage(message)
	}

	public sendTaskMessage(text?: string, images?: string[]) {
		this.sendCommand({
			commandName: TaskCommandName.SendMessage,
			data: { text, images },
		})
	}

	public deleteQueuedMessage(messageId: string) {
		this.sendCommand({
			commandName: TaskCommandName.DeleteQueuedMessage,
			data: messageId,
		})
	}

	public sendMessage(message: IpcMessage) {
		ipc.of[this._id]?.emit("message", message)
	}

	public disconnect() {
		try {
			ipc.disconnect(this._id)
		} catch (error) {
			this.log(
				`[client#disconnect] error disconnecting -> ${error instanceof Error ? error.message : String(error)}`,
			)
		}
	}

	public get socketPath() {
		return this._socketPath
	}

	public get clientId() {
		return this._clientId
	}

	public get isConnected() {
		return this._isConnected
	}

	public get isReady() {
		return this._isConnected && this._clientId !== undefined
	}
}
