/**
 * rNetwork Local-First Client
 *
 * Wraps the shared local-first stack for collaborative CRM data.
 * Contact metadata, relationships, and graph layout sync in real-time.
 */

import { DocumentManager } from '../../shared/local-first/document';
import type { DocumentId } from '../../shared/local-first/document';
import { EncryptedDocStore } from '../../shared/local-first/storage';
import { DocSyncManager } from '../../shared/local-first/sync';
import { DocCrypto } from '../../shared/local-first/crypto';
import { networkSchema, networkDocId } from './schemas';
import type { NetworkDoc, CrmContact, CrmRelationship, LayerConfig, CrossLayerFlowConfig } from './schemas';

export class NetworkLocalFirstClient {
	#space: string;
	#documents: DocumentManager;
	#store: EncryptedDocStore;
	#sync: DocSyncManager;
	#initialized = false;

	constructor(space: string, docCrypto?: DocCrypto) {
		this.#space = space;
		this.#documents = new DocumentManager();
		this.#store = new EncryptedDocStore(space, docCrypto);
		this.#sync = new DocSyncManager({
			documents: this.#documents,
			store: this.#store,
		});
		this.#documents.registerSchema(networkSchema);
	}

	get isConnected(): boolean { return this.#sync.isConnected; }

	async init(): Promise<void> {
		if (this.#initialized) return;
		await this.#store.open();
		const cachedIds = await this.#store.listByModule('network', 'crm');
		const cached = await this.#store.loadMany(cachedIds);
		for (const [docId, binary] of cached) {
			this.#documents.open<NetworkDoc>(docId, networkSchema, binary);
		}
		await this.#sync.preloadSyncStates(cachedIds);
		const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
		const wsUrl = `${proto}//${location.host}/ws/${this.#space}`;
		try { await this.#sync.connect(wsUrl, this.#space); } catch { console.warn('[NetworkClient] Working offline'); }
		this.#initialized = true;
	}

	async subscribe(): Promise<NetworkDoc | null> {
		const docId = networkDocId(this.#space) as DocumentId;
		let doc = this.#documents.get<NetworkDoc>(docId);
		if (!doc) {
			const binary = await this.#store.load(docId);
			doc = binary
				? this.#documents.open<NetworkDoc>(docId, networkSchema, binary)
				: this.#documents.open<NetworkDoc>(docId, networkSchema);
		}
		await this.#sync.subscribe([docId]);
		return doc ?? null;
	}

	getDoc(): NetworkDoc | undefined {
		return this.#documents.get<NetworkDoc>(networkDocId(this.#space) as DocumentId);
	}

	onChange(cb: (doc: NetworkDoc) => void): () => void {
		return this.#sync.onChange(networkDocId(this.#space) as DocumentId, cb as (doc: any) => void);
	}

	onConnect(cb: () => void): () => void { return this.#sync.onConnect(cb); }

	// ── Contact CRUD ──

	saveContact(contact: CrmContact): void {
		const docId = networkDocId(this.#space) as DocumentId;
		this.#sync.change<NetworkDoc>(docId, `Save contact ${contact.name}`, (d) => {
			d.contacts[contact.did] = contact;
		});
	}

	deleteContact(did: string): void {
		const docId = networkDocId(this.#space) as DocumentId;
		this.#sync.change<NetworkDoc>(docId, `Delete contact`, (d) => {
			delete d.contacts[did];
			// Clean up relationships involving this contact
			for (const key of Object.keys(d.relationships)) {
				const rel = d.relationships[key];
				if (rel.fromDid === did || rel.toDid === did) delete d.relationships[key];
			}
		});
	}

	// ── Relationship CRUD ──

	saveRelationship(relationship: CrmRelationship): void {
		const docId = networkDocId(this.#space) as DocumentId;
		const key = `${relationship.fromDid}:${relationship.toDid}`;
		this.#sync.change<NetworkDoc>(docId, `Save relationship`, (d) => {
			d.relationships[key] = relationship;
		});
	}

	deleteRelationship(fromDid: string, toDid: string): void {
		const docId = networkDocId(this.#space) as DocumentId;
		const key = `${fromDid}:${toDid}`;
		this.#sync.change<NetworkDoc>(docId, `Delete relationship`, (d) => {
			delete d.relationships[key];
		});
	}

	// ── Graph Layout ──

	saveGraphLayout(positions: Record<string, { x: number; y: number }>, zoom: number, panX: number, panY: number): void {
		const docId = networkDocId(this.#space) as DocumentId;
		this.#sync.change<NetworkDoc>(docId, `Save layout`, (d) => {
			d.graphLayout.positions = positions as any;
			d.graphLayout.zoom = zoom;
			d.graphLayout.panX = panX;
			d.graphLayout.panY = panY;
		});
	}

	saveNodePosition(did: string, x: number, y: number): void {
		const docId = networkDocId(this.#space) as DocumentId;
		this.#sync.change<NetworkDoc>(docId, `Move node`, (d) => {
			if (!d.graphLayout.positions) d.graphLayout.positions = {} as any;
			d.graphLayout.positions[did] = { x, y } as any;
		});
	}

	// ── Layer Config ──

	saveLayerConfig(layers: LayerConfig[], flows: CrossLayerFlowConfig[]): void {
		const docId = networkDocId(this.#space) as DocumentId;
		this.#sync.change<NetworkDoc>(docId, `Save layer config`, (d) => {
			d.layerConfigs = layers as any;
			d.crossLayerFlowConfigs = flows as any;
		});
	}

	getLayerConfig(): { layers: LayerConfig[]; flows: CrossLayerFlowConfig[] } | null {
		const doc = this.getDoc();
		if (!doc || !doc.layerConfigs?.length) return null;
		return {
			layers: [...doc.layerConfigs] as LayerConfig[],
			flows: [...(doc.crossLayerFlowConfigs || [])] as CrossLayerFlowConfig[],
		};
	}

	async disconnect(): Promise<void> {
		await this.#sync.flush();
		this.#sync.disconnect();
	}
}
