import { ffi } from "../proc/native";
import sparkBunEventEmitter from "../events/eventEmitter";
import { BrowserView } from "./BrowserView";
import { type Pointer } from "bun:ffi";
import { BuildConfig } from "./BuildConfig";
import { type RPCWithTransport, type AnyRPC } from "../../shared/rpc.js";
import { WGPUView } from "./WGPUView";

const buildConfig = BuildConfig.getSync();
ffi.request.setExitOnLastWindowClosed({
	enabled: buildConfig.runtime?.exitOnLastWindowClosed ?? true,
});

export type WindowOptionsType<T = undefined> = {
	trafficLightOffset?: {
		x: number;
		y: number;
	};
	activate?: boolean;
	title: string;
	frame: {
		x: number;
		y: number;
		width: number;
		height: number;
	};
	url: string | null;
	html: string | null;
	preload: string | null;
	viewsRoot: string | null;
	renderer: "native" | "cef";
	// Storage partition for the webview's cookies/localStorage/cache/IndexedDB.
	// A value starting with "persist:" is written to disk; any other non-empty
	// value is ephemeral — in-memory only, gone when the process exits — which
	// is what incognito-style/diskless sessions want. Omitted/null falls back to
	// "persist:default" (on-disk).
	partition?: string;
	rpc?: T;
	styleMask?: {};
	// titleBarStyle options:
	// - 'default': normal titlebar with native window controls
	// - 'hidden': no titlebar, no native window controls (for fully custom chrome)
	// - 'hiddenInset': transparent titlebar with inset native controls
	titleBarStyle: "hidden" | "hiddenInset" | "default";
	// transparent: when true, window background is transparent (see-through)
	transparent: boolean;
	// passthrough: when true, mouse events pass through transparent regions
	passthrough: boolean;
	hidden?: boolean;
	navigationRules: string | null;
	// Sandbox mode: when true, disables RPC and only allows event emission
	// Use for untrusted content (remote URLs) to prevent malicious sites from
	// accessing internal APIs, creating OOPIFs, or communicating with Bun
	sandbox: boolean;
};

const defaultOptions: WindowOptionsType = {
	title: "SparkBun",
	frame: {
		x: 0,
		y: 0,
		width: 800,
		height: 600,
	},
	url: "https://sparkbun.dev",
	html: null,
	preload: null,
	viewsRoot: null,
	renderer: buildConfig.defaultRenderer,
	titleBarStyle: "default",
	transparent: false,
	passthrough: false,
	hidden: false,
	navigationRules: null,
	sandbox: false,
};

export const BrowserWindowMap: {
	[id: number]: BrowserWindow<RPCWithTransport>;
} = {};

// Clean up JS wrapper state when a window closes. Native child cleanup is core-owned.
sparkBunEventEmitter.on("close", (event: { data: { id: number } }) => {
	const windowId = event.data.id;
	delete BrowserWindowMap[windowId];

	// Clean up all webviews associated with this window
	for (const view of BrowserView.getAll()) {
		if (view.windowId === windowId) {
			view.remove();
		}
	}

	// Clean up all WGPU views associated with this window
	const wgpuViews = WGPUView.getAll().filter(v => v.windowId === windowId);
	for (const view of wgpuViews) {
		try {
			view.remove();
		} catch (e) {
			console.error(`Error cleaning up WGPU view ${view.id}:`, e);
		}
	}

});

export class BrowserWindow<T extends RPCWithTransport = AnyRPC> {
	id = 0;
	title: string = "SparkBun";
	state: "creating" | "created" = "creating";
	url: string | null = null;
	html: string | null = null;
	preload: string | null = null;
	viewsRoot: string | null = null;
	partition: string | null = null;
	renderer: "native" | "cef" = "native";
	transparent: boolean = false;
	passthrough: boolean = false;
	hidden: boolean = false;
	trafficLightOffset: { x: number; y: number } = { x: 0, y: 0 };
	navigationRules: string | null = null;
	// Sandbox mode disables RPC and only allows event emission (for untrusted content)
	sandbox: boolean = false;
	frame: {
		x: number;
		y: number;
		width: number;
		height: number;
	} = {
		x: 0,
		y: 0,
		width: 800,
		height: 600,
	};
	webviewId!: number;

	get ptr(): Pointer | null {
		return ffi.request.getWindowPointer({ winId: this.id }) as Pointer | null;
	}

	constructor(options: Partial<WindowOptionsType<T>> = defaultOptions) {
		this.title = options.title || "New Window";
		this.frame = options.frame
			? { ...defaultOptions.frame, ...options.frame }
			: { ...defaultOptions.frame };
		this.url = options.url || null;
		this.html = options.html || null;
		this.preload = options.preload || null;
		this.viewsRoot = options.viewsRoot || null;
		this.partition = options.partition ?? null;
		this.renderer = options.renderer || defaultOptions.renderer;
		this.transparent = options.transparent ?? false;
		this.passthrough = options.passthrough ?? false;
		this.hidden = options.hidden ?? false;
		this.trafficLightOffset = {
			x: options.trafficLightOffset?.x ?? 0,
			y: options.trafficLightOffset?.y ?? 0,
		};
		this.navigationRules = options.navigationRules || null;
		this.sandbox = options.sandbox ?? false;

		this.init(options);
	}

	init({
		rpc,
		styleMask,
		titleBarStyle,
		transparent,
		hidden,
		activate,
	}: Partial<WindowOptionsType<T>>) {
		const windowId = ffi.request.createWindow({
			title: this.title,
			url: this.url || "",
			frame: {
				width: this.frame.width,
				height: this.frame.height,
				x: this.frame.x,
				y: this.frame.y,
			},
			styleMask: {
				Borderless: false,
				Titled: true,
				Closable: true,
				Miniaturizable: true,
				Resizable: true,
				UnifiedTitleAndToolbar: false,
				FullScreen: false,
				FullSizeContentView: false,
				UtilityWindow: false,
				DocModalWindow: false,
				NonactivatingPanel: false,
				HUDWindow: false,
				...(styleMask || {}),
				// hiddenInset: transparent titlebar with inset native controls
				...(titleBarStyle === "hiddenInset"
					? {
							Titled: true,
							FullSizeContentView: true,
						}
					: {}),
				// hidden: no titlebar, no native controls (for fully custom chrome)
				...(titleBarStyle === "hidden"
					? {
							Titled: false,
							FullSizeContentView: true,
						}
					: {}),
			},
			titleBarStyle: titleBarStyle || "default",
			transparent: transparent ?? false,
			hidden: hidden ?? false,
			activate: activate ?? true,
			trafficLightOffset: this.trafficLightOffset,
		});

		if (!windowId) {
			throw "Failed to create window";
		}

		this.id = windowId as number;

		BrowserWindowMap[this.id] = this;

		// own webview instance, or instances for attaching to the window.
		const webview = new BrowserView({
			// TODO: decide whether we want to keep sending url/html
			// here, if we're manually calling loadURL/loadHTML below
			// then we can remove it from the api here
			url: this.url,
			html: this.html,
			preload: this.preload,
			viewsRoot: this.viewsRoot,
			partition: this.partition,
			// frame: this.frame,
			renderer: this.renderer,
			frame: {
				x: 0,
				y: 0,
				width: this.frame.width,
				height: this.frame.height,
			},
			rpc,
			// todo: we need to send the window here and attach it in one go
			// then the view creation code in objc can toggle between offscreen
			// or on screen views depending on if windowId is null
			// does this mean browserView needs to track the windowId or handle it ephemerally?
			windowId: this.id,
			navigationRules: this.navigationRules,
			sandbox: this.sandbox,
			startPassthrough: this.passthrough,
		});

		this.webviewId = webview.id;
	}

	get webview() {
		// link directly to the browserview object instead of a getter
		return BrowserView.getById(this.webviewId) as BrowserView<T>;
	}

	static getById(id: number) {
		return BrowserWindowMap[id];
	}

	setTitle(title: string) {
		this.title = title;
		return ffi.request.setTitle({ winId: this.id, title });
	}

	close() {
		return ffi.request.closeWindow({ winId: this.id });
	}

	activate() {
		return ffi.request.activateWindow({ winId: this.id });
	}

	focus() {
		console.log(
			"[sparkbun] BrowserWindow.focus() is deprecated. Use window.activate() instead.",
		);
		return this.activate();
	}

	show() {
		return ffi.request.showWindow({ winId: this.id, activate: true });
	}

	showInactive() {
		return ffi.request.showWindow({ winId: this.id, activate: false });
	}

	hide() {
		return ffi.request.hideWindow({ winId: this.id });
	}

	minimize() {
		return ffi.request.minimizeWindow({ winId: this.id });
	}

	unminimize() {
		return ffi.request.restoreWindow({ winId: this.id });
	}

	isMinimized(): boolean {
		return ffi.request.isWindowMinimized({ winId: this.id });
	}

	maximize() {
		return ffi.request.maximizeWindow({ winId: this.id });
	}

	unmaximize() {
		return ffi.request.unmaximizeWindow({ winId: this.id });
	}

	isMaximized(): boolean {
		return ffi.request.isWindowMaximized({ winId: this.id });
	}

	setFullScreen(fullScreen: boolean) {
		return ffi.request.setWindowFullScreen({ winId: this.id, fullScreen });
	}

	isFullScreen(): boolean {
		return ffi.request.isWindowFullScreen({ winId: this.id });
	}

	setAlwaysOnTop(alwaysOnTop: boolean) {
		return ffi.request.setWindowAlwaysOnTop({ winId: this.id, alwaysOnTop });
	}

	isAlwaysOnTop(): boolean {
		return ffi.request.isWindowAlwaysOnTop({ winId: this.id });
	}
	
	setVisibleOnAllWorkspaces(visibleOnAllWorkspaces: boolean) {
		return ffi.request.setWindowVisibleOnAllWorkspaces({ winId: this.id, visibleOnAllWorkspaces });
	}
	
	isVisibleOnAllWorkspaces(): boolean {
		return ffi.request.isWindowVisibleOnAllWorkspaces({ winId: this.id });
	}

	setPosition(x: number, y: number) {
		this.frame.x = x;
		this.frame.y = y;
		return ffi.request.setWindowPosition({ winId: this.id, x, y });
	}

	setWindowButtonPosition(x: number, y: number) {
		return ffi.request.setWindowButtonPosition({ winId: this.id, x, y });
	}

	setSize(width: number, height: number) {
		this.frame.width = width;
		this.frame.height = height;
		return ffi.request.setWindowSize({ winId: this.id, width, height });
	}

	setFrame(x: number, y: number, width: number, height: number) {
		this.frame = { x, y, width, height };
		return ffi.request.setWindowFrame({ winId: this.id, x, y, width, height });
	}

	getFrame(): { x: number; y: number; width: number; height: number } {
		const frame = ffi.request.getWindowFrame({ winId: this.id });
		// Update internal state
		this.frame = frame;
		return frame;
	}

	getPosition(): { x: number; y: number } {
		const frame = this.getFrame();
		return { x: frame.x, y: frame.y };
	}

	getSize(): { width: number; height: number } {
		const frame = this.getFrame();
		return { width: frame.width, height: frame.height };
	}

	/**
	 * Set the page zoom level for the window's webview (WebKit only).
	 * @param zoomLevel - The zoom level (1.0 = 100%, 1.5 = 150%, etc.)
	 */
	setPageZoom(zoomLevel: number) {
		this.webview?.setPageZoom(zoomLevel);
	}

	/**
	 * Get the current page zoom level for the window's webview.
	 * @returns The current zoom level (1.0 = 100%)
	 */
	getPageZoom(): number {
		return this.webview?.getPageZoom() ?? 1.0;
	}

	// name should only allow browserWindow events
	on(name: string, handler: (event: unknown) => void) {
		const specificName = `${name}-${this.id}`;
		sparkBunEventEmitter.on(specificName, handler);
	}
}
