/**
 * rSocials Local-First Client
 *
 * Wraps the shared local-first stack for thread and campaign data.
 * One Automerge doc per space stores all threads and campaigns.
 */

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 { socialsSchema, socialsDocId } from './schemas';
import type { SocialsDoc, ThreadData, Campaign, CampaignFlow, CampaignPlannerNode, CampaignEdge, CampaignWorkflow, CampaignWorkflowNode, CampaignWorkflowEdge, CampaignWizard } from './schemas';

export class SocialsLocalFirstClient {
	#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(socialsSchema);
	}

	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('socials', 'data');
		const cached = await this.#store.loadMany(cachedIds);
		for (const [docId, binary] of cached) {
			this.#documents.open<SocialsDoc>(docId, socialsSchema, 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('[SocialsClient] Working offline'); }
		this.#initialized = true;
	}

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

	// ── Reads ──

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

	listThreads(): ThreadData[] {
		const doc = this.getDoc();
		if (!doc?.threads) return [];
		return Object.values(doc.threads).sort((a, b) => b.updatedAt - a.updatedAt);
	}

	getThread(id: string): ThreadData | undefined {
		const doc = this.getDoc();
		return doc?.threads?.[id];
	}

	listCampaigns(): Campaign[] {
		const doc = this.getDoc();
		if (!doc?.campaigns) return [];
		return Object.values(doc.campaigns).sort((a, b) => b.updatedAt - a.updatedAt);
	}

	getCampaign(id: string): Campaign | undefined {
		const doc = this.getDoc();
		return doc?.campaigns?.[id];
	}

	// ── Thread writes ──

	saveThread(thread: ThreadData): void {
		const docId = socialsDocId(this.#space) as DocumentId;
		this.#sync.change<SocialsDoc>(docId, `Save thread ${thread.title || thread.id}`, (d) => {
			if (!d.threads) d.threads = {} as any;
			thread.updatedAt = Date.now();
			if (!thread.createdAt) thread.createdAt = Date.now();
			d.threads[thread.id] = thread;
		});
	}

	deleteThread(id: string): void {
		const docId = socialsDocId(this.#space) as DocumentId;
		this.#sync.change<SocialsDoc>(docId, `Delete thread ${id}`, (d) => {
			if (d.threads?.[id]) delete d.threads[id];
		});
	}

	// ── Campaign writes ──

	saveCampaign(campaign: Campaign): void {
		const docId = socialsDocId(this.#space) as DocumentId;
		this.#sync.change<SocialsDoc>(docId, `Save campaign ${campaign.title || campaign.id}`, (d) => {
			if (!d.campaigns) d.campaigns = {} as any;
			campaign.updatedAt = Date.now();
			if (!campaign.createdAt) campaign.createdAt = Date.now();
			d.campaigns[campaign.id] = campaign;
		});
	}

	deleteCampaign(id: string): void {
		const docId = socialsDocId(this.#space) as DocumentId;
		this.#sync.change<SocialsDoc>(docId, `Delete campaign ${id}`, (d) => {
			if (d.campaigns?.[id]) delete d.campaigns[id];
		});
	}

	// ── Campaign flow reads ──

	listCampaignFlows(): CampaignFlow[] {
		const doc = this.getDoc();
		if (!doc?.campaignFlows) return [];
		return Object.values(doc.campaignFlows).sort((a, b) => b.updatedAt - a.updatedAt);
	}

	getCampaignFlow(id: string): CampaignFlow | undefined {
		const doc = this.getDoc();
		return doc?.campaignFlows?.[id];
	}

	getActiveFlowId(): string {
		const doc = this.getDoc();
		return doc?.activeFlowId || '';
	}

	// ── Campaign flow writes ──

	saveCampaignFlow(flow: CampaignFlow): void {
		const docId = socialsDocId(this.#space) as DocumentId;
		this.#sync.change<SocialsDoc>(docId, `Save campaign flow ${flow.name || flow.id}`, (d) => {
			if (!d.campaignFlows) d.campaignFlows = {} as any;
			flow.updatedAt = Date.now();
			if (!flow.createdAt) flow.createdAt = Date.now();
			d.campaignFlows[flow.id] = flow;
		});
	}

	updateFlowNodesEdges(flowId: string, nodes: CampaignPlannerNode[], edges: CampaignEdge[]): void {
		const docId = socialsDocId(this.#space) as DocumentId;
		this.#sync.change<SocialsDoc>(docId, `Update flow nodes ${flowId}`, (d) => {
			if (d.campaignFlows?.[flowId]) {
				d.campaignFlows[flowId].nodes = nodes;
				d.campaignFlows[flowId].edges = edges;
				d.campaignFlows[flowId].updatedAt = Date.now();
			}
		});
	}

	renameCampaignFlow(id: string, name: string): void {
		const docId = socialsDocId(this.#space) as DocumentId;
		this.#sync.change<SocialsDoc>(docId, `Rename flow ${id}`, (d) => {
			if (d.campaignFlows?.[id]) {
				d.campaignFlows[id].name = name;
				d.campaignFlows[id].updatedAt = Date.now();
			}
		});
	}

	deleteCampaignFlow(id: string): void {
		const docId = socialsDocId(this.#space) as DocumentId;
		this.#sync.change<SocialsDoc>(docId, `Delete campaign flow ${id}`, (d) => {
			if (d.campaignFlows?.[id]) delete d.campaignFlows[id];
			if (d.activeFlowId === id) d.activeFlowId = '';
		});
	}

	setActiveFlow(id: string): void {
		const docId = socialsDocId(this.#space) as DocumentId;
		this.#sync.change<SocialsDoc>(docId, `Set active flow ${id}`, (d) => {
			d.activeFlowId = id;
		});
	}

	// ── Campaign workflow reads ──

	listCampaignWorkflows(): CampaignWorkflow[] {
		const doc = this.getDoc();
		if (!doc?.campaignWorkflows) return [];
		return Object.values(doc.campaignWorkflows).sort((a, b) => b.updatedAt - a.updatedAt);
	}

	getCampaignWorkflow(id: string): CampaignWorkflow | undefined {
		const doc = this.getDoc();
		return doc?.campaignWorkflows?.[id];
	}

	// ── Campaign workflow writes ──

	saveCampaignWorkflow(wf: CampaignWorkflow): void {
		const docId = socialsDocId(this.#space) as DocumentId;
		this.#sync.change<SocialsDoc>(docId, `Save campaign workflow ${wf.name || wf.id}`, (d) => {
			if (!d.campaignWorkflows) d.campaignWorkflows = {} as any;
			wf.updatedAt = Date.now();
			if (!wf.createdAt) wf.createdAt = Date.now();
			d.campaignWorkflows[wf.id] = wf;
		});
	}

	updateCampaignWorkflowNodesEdges(wfId: string, nodes: CampaignWorkflowNode[], edges: CampaignWorkflowEdge[]): void {
		const docId = socialsDocId(this.#space) as DocumentId;
		this.#sync.change<SocialsDoc>(docId, `Update campaign workflow nodes ${wfId}`, (d) => {
			if (d.campaignWorkflows?.[wfId]) {
				d.campaignWorkflows[wfId].nodes = nodes;
				d.campaignWorkflows[wfId].edges = edges;
				d.campaignWorkflows[wfId].updatedAt = Date.now();
			}
		});
	}

	deleteCampaignWorkflow(id: string): void {
		const docId = socialsDocId(this.#space) as DocumentId;
		this.#sync.change<SocialsDoc>(docId, `Delete campaign workflow ${id}`, (d) => {
			if (d.campaignWorkflows?.[id]) delete d.campaignWorkflows[id];
		});
	}

	// ── Campaign wizard reads ──

	listCampaignWizards(): CampaignWizard[] {
		const doc = this.getDoc();
		if (!doc?.campaignWizards) return [];
		return Object.values(doc.campaignWizards).sort((a, b) => b.updatedAt - a.updatedAt);
	}

	getCampaignWizard(id: string): CampaignWizard | undefined {
		const doc = this.getDoc();
		return doc?.campaignWizards?.[id];
	}

	// ── Campaign wizard writes ──

	saveCampaignWizard(wizard: CampaignWizard): void {
		const docId = socialsDocId(this.#space) as DocumentId;
		this.#sync.change<SocialsDoc>(docId, `Save campaign wizard ${wizard.id}`, (d) => {
			if (!d.campaignWizards) d.campaignWizards = {} as any;
			wizard.updatedAt = Date.now();
			if (!wizard.createdAt) wizard.createdAt = Date.now();
			d.campaignWizards[wizard.id] = wizard;
		});
	}

	deleteCampaignWizard(id: string): void {
		const docId = socialsDocId(this.#space) as DocumentId;
		this.#sync.change<SocialsDoc>(docId, `Delete campaign wizard ${id}`, (d) => {
			if (d.campaignWizards?.[id]) delete d.campaignWizards[id];
		});
	}

	// ── Events ──

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

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

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