import type { IShare, ISubscription } from '@wovin/core'
import type { AgentStateClass } from './AgentState'
import { comparer, flow, observable, runInAction } from '@wovin/core/mobx'

import { Logger } from 'besonders-logger'
import { notifyToast } from '../../ui/utils-ui'
import { stateDB } from '../local-state'
import { useAgent } from './AgentState'

const { WARN, LOG, DEBUG, VERBOSE, ERROR } = Logger.setup(Logger.INFO) // eslint-disable-line unused-imports/no-unused-vars

export class AgentPubSub {
	mixed = this as unknown as AgentStateClass
	readonly shares = observable.array([] as IShare[], { deep: true, equals: comparer.structural })
	readonly subscriptions = observable.array([] as ISubscription[], { deep: true, equals: comparer.structural })

	get sharesMap() {
		return new Map(this.shares.map(share => [share.id, share]))
	}

	get subscriptionsMap() {
		return new Map(this.subscriptions.map(sub => [sub.id, sub]))
	}

	get mostRecentShare() {
		if (!this.shares.length) return null
		return this.shares[this.shares.length - 1]
	}

	getDirectoryHandle = flow(function* (this: AgentStateClass) {
		return (yield stateDB.directoryHandles.get(this.ag))?.directoryHandle
	})

	setDirectoryHandle = flow(function* (this: AgentStateClass, directoryHandle: FileSystemDirectoryHandle) {
		LOG(`Set Handle:`, directoryHandle)
		// (i) dexie does some type checking, so we do it first & await
		yield stateDB.directoryHandles.put({ ag: this.ag, directoryHandle })
	})

	addShare = flow(function* (this: AgentStateClass, share: IShare) {
		LOG(`Adding share:`, share)
		// (i) dexie does some type checking, so we do it first & await
		yield stateDB.shares.add(share)
		this.reloadPubSub()
	})

	addSub = flow(function* (this: AgentStateClass, sub: ISubscription) {
		LOG(`Adding subscription:`, sub)
		yield stateDB.subscriptions.add(sub)
		this.reloadPubSub()
	})

	updateSub = flow(function* (this: AgentStateClass, id: string, changes: Partial<Omit<ISubscription, 'id'>>) {
		const sub = this.subscriptions.find(p => p.id === id)
		if (!sub) throw ERROR(`[updateSub] id not found:`, id, changes)
		LOG(`Updating subscription:`, id, changes, sub)
		yield stateDB.subscriptions.update(id, changes)
		Object.assign(sub, changes)
		// this.reloadPubSub()
	})

	updateShare = flow(function* (this: AgentStateClass, id: string, changes: Partial<Omit<IShare, 'id'>>) {
		const share = this.shares.find(p => p.id === id)
		if (!share) throw ERROR(`[updateShare] id not found:`, id, changes)
		const { publishedBy } = share
		const { ag } = useAgent()
		if (ag !== publishedBy) {
			WARN(`[updateShare] last push was a different agent`, { publishedBy, ag })
			notifyToast(
				`This share was last pushed by a different agent (${publishedBy} --> ${ag}) if you did not change your agent string, something may be up with your DID situation`,
				'warning',
			)
			changes.publishedBy = ag
		}
		LOG(`Updating share:`, id, changes, share)
		void stateDB.shares.update(id, changes)
		Object.assign(share, changes)
		// this.reloadPubSub()
	})

	deleteShare = flow(function* (this: AgentStateClass, shareID: string) {
		const share = this.shares.find(p => p.id === shareID)
		LOG(`Deleting share:`, shareID, share)
		if (!share) throw ERROR(`Trying to delete non-existing share:`, shareID)
		// yield stateDB.shares.update(shareID, { isDeleted: true })
		yield stateDB.shares.delete(shareID)
		this.reloadPubSub()
	})

	deleteSub = flow(function* (this: AgentStateClass, subID: string) {
		const sub = this.subscriptions.find(p => p.id === subID)
		LOG(`Deleting subscription:`, subID, sub)
		if (!sub) throw ERROR(`Trying to delete non-existing subscription:`, subID)
		// yield stateDB.subscriptions.update(subID, { isDeleted: true })
		yield stateDB.subscriptions.delete(subID)
		this.reloadPubSub()
	})

	reloadPubSub = async () => {
		const shares = (await stateDB.shares.orderBy(/* 'lastPush',  */ 'createdAt').toArray()).filter(p => !p.isDeleted)
		const subscriptions = (await stateDB.subscriptions.orderBy(/* 'lastPull',  */ 'createdAt').toArray()).filter(p => !p.isDeleted)
		runInAction(() => {
			this.shares.replace(shares)
			this.subscriptions.replace(subscriptions)
		})
		DEBUG(`Loaded from IDB:`, { shares: this.shares, subs: this.subscriptions, stateDB })
	}
}
