// gomuks - A Matrix client written in Go.
// Copyright (C) 2024 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
import { use, useCallback, useState } from "react"
import { RoomStateStore, usePreferences } from "@/api/statestore"
import { Preference, PreferenceContext, PreferenceValueType, Preferences, preferences } from "@/api/types/preferences"
import useEvent from "@/util/useEvent.ts"
import ClientContext from "../ClientContext.ts"
import JSONView from "../util/JSONView.tsx"
import Toggle from "../util/Toggle.tsx"
import CloseIcon from "@/icons/close.svg?react"
import "./SettingsView.css"

interface PreferenceCellProps<T extends PreferenceValueType> {
	context: PreferenceContext
	name: keyof Preferences
	pref: Preference<T>
	setPref: SetPrefFunc
	value: T | undefined
	inheritedValue: T
}

const useRemover = (
	context: PreferenceContext, setPref: SetPrefFunc, name: keyof Preferences, value: PreferenceValueType | undefined,
) => {
	const onClear = useCallback(() => {
		setPref(context, name, undefined)
	}, [setPref, context, name])
	if (value === undefined) {
		return null
	}
	return <button onClick={onClear}><CloseIcon /></button>
}

const BooleanPreferenceCell = ({ context, name, setPref, value, inheritedValue }: PreferenceCellProps<boolean>) => {
	const onChange = useCallback((evt: React.ChangeEvent<HTMLInputElement>) => {
		setPref(context, name, evt.target.checked)
	}, [setPref, context, name])
	return <div className="preference boolean-preference">
		<Toggle checked={value ?? inheritedValue} onChange={onChange}/>
		{useRemover(context, setPref, name, value)}
	</div>
}

const SelectPreferenceCell = ({ context, name, pref, setPref, value, inheritedValue }: PreferenceCellProps<string>) => {
	const onChange = useCallback((evt: React.ChangeEvent<HTMLSelectElement>) => {
		setPref(context, name, evt.target.value)
	}, [setPref, context, name])
	const remover = useRemover(context, setPref, name, value)
	if (!pref.allowedValues) {
		return null
	}
	return <div className="preference select-preference">
		<select value={value ?? inheritedValue} onChange={onChange}>
			{pref.allowedValues.map(value =>
				<option key={value} value={value}>{value}</option>)}
		</select>
		{remover}
	</div>
}

type SetPrefFunc = (context: PreferenceContext, key: keyof Preferences, value: PreferenceValueType | undefined) => void

interface PreferenceRowProps {
	name: keyof Preferences
	pref: Preference
	setPref: SetPrefFunc
	globalServer?: PreferenceValueType
	globalLocal?: PreferenceValueType
	roomServer?: PreferenceValueType
	roomLocal?: PreferenceValueType
}

const PreferenceRow = ({
	name, pref, setPref, globalServer, globalLocal, roomServer, roomLocal,
}: PreferenceRowProps) => {
	const prefType = typeof pref.defaultValue
	if (prefType !== "boolean" && !pref.allowedValues) {
		return null
	}
	const makeContentCell = (
		context: PreferenceContext,
		val: PreferenceValueType | undefined,
		inheritedVal: PreferenceValueType,
	) => {
		if (prefType === "boolean") {
			return <BooleanPreferenceCell
				name={name}
				setPref={setPref}
				context={context}
				pref={pref as Preference<boolean>}
				value={val as boolean | undefined}
				inheritedValue={inheritedVal as boolean}
			/>
		} else if (typeof prefType === "string" && pref.allowedValues) {
			return <SelectPreferenceCell
				name={name}
				setPref={setPref}
				context={context}
				pref={pref as Preference<string>}
				value={val as string | undefined}
				inheritedValue={inheritedVal as string}
			/>
		} else {
			return null
		}
	}
	let inherit: PreferenceValueType
	return <tr>
		<th title={pref.description}>{pref.displayName}</th>
		<td>{makeContentCell(PreferenceContext.Account, globalServer, inherit = pref.defaultValue)}</td>
		<td>{makeContentCell(PreferenceContext.Device, globalLocal, inherit = globalServer ?? inherit)}</td>
		<td>{makeContentCell(PreferenceContext.RoomAccount, roomServer, inherit = globalLocal ?? inherit)}</td>
		<td>{makeContentCell(PreferenceContext.RoomDevice, roomLocal, inherit = roomServer ?? inherit)}</td>
	</tr>
}

interface SettingsViewProps {
	room: RoomStateStore
}

const CustomCSSInput = ({ setPref, room }: { setPref: SetPrefFunc, room: RoomStateStore }) => {
	const client = use(ClientContext)!
	const [context, setContext] = useState(PreferenceContext.Account)
	const getContextText = useCallback((context: PreferenceContext) => {
		if (context === PreferenceContext.Account) {
			return client.store.serverPreferenceCache.custom_css
		} else if (context === PreferenceContext.Device) {
			return client.store.localPreferenceCache.custom_css
		} else if (context === PreferenceContext.RoomAccount) {
			return room.serverPreferenceCache.custom_css
		} else if (context === PreferenceContext.RoomDevice) {
			return room.localPreferenceCache.custom_css
		}
	}, [client, room])
	const origText = getContextText(context)
	const [text, setText] = useState(origText ?? "")
	const onChangeContext = useCallback((evt: React.ChangeEvent<HTMLSelectElement>) => {
		const newContext = evt.target.value as PreferenceContext
		setContext(newContext)
		setText(getContextText(newContext) ?? "")
	}, [getContextText])
	const onChangeText = useCallback((evt: React.ChangeEvent<HTMLTextAreaElement>) => {
		setText(evt.target.value)
	}, [])
	const onSave = useEvent(() => {
		setPref(context, "custom_css", text)
	})
	const onDelete = useEvent(() => {
		setPref(context, "custom_css", undefined)
		setText("")
	})
	return <div className="custom-css-input">
		<div className="header">
			<h3>Custom CSS</h3>
			<select value={context} onChange={onChangeContext}>
				<option value={PreferenceContext.Account}>Account</option>
				<option value={PreferenceContext.Device}>Device</option>
				<option value={PreferenceContext.RoomAccount}>Room (account)</option>
				<option value={PreferenceContext.RoomDevice}>Room (device)</option>
			</select>
		</div>
		<textarea value={text} onChange={onChangeText}/>
		<div className="buttons">
			{origText !== undefined && <button className="delete" onClick={onDelete}>Delete</button>}
			<button className="save primary-color-button" onClick={onSave} disabled={origText === text}>Save</button>
		</div>
	</div>
}

const AppliedSettingsView = ({ room }: SettingsViewProps) => {
	const client = use(ClientContext)!

	return <div className="applied-settings">
		<h3>Raw settings data</h3>
		<details>
			<summary><h4>Applied settings in this room</h4></summary>
			<JSONView data={room.preferences}/>
		</details>
		<details open>
			<summary><h4>Global account settings</h4></summary>
			<JSONView data={client.store.serverPreferenceCache}/>
		</details>
		<details open>
			<summary><h4>Global device settings</h4></summary>
			<JSONView data={client.store.localPreferenceCache}/>
		</details>
		<details open>
			<summary><h4>Room account settings</h4></summary>
			<JSONView data={room.serverPreferenceCache}/>
		</details>
		<details open>
			<summary><h4>Room device settings</h4></summary>
			<JSONView data={room.localPreferenceCache}/>
		</details>
	</div>
}

const SettingsView = ({ room }: SettingsViewProps) => {
	const client = use(ClientContext)!
	const setPref = useCallback((context: PreferenceContext, key: keyof Preferences, value: PreferenceValueType | undefined) => {
		if (context === PreferenceContext.Account) {
			client.rpc.setAccountData("fi.mau.gomuks.preferences", {
				...client.store.serverPreferenceCache,
				[key]: value,
			})
		} else if (context === PreferenceContext.Device) {
			if (value === undefined) {
				delete client.store.localPreferenceCache[key]
			} else {
				(client.store.localPreferenceCache[key] as PreferenceValueType) = value
			}
		} else if (context === PreferenceContext.RoomAccount) {
			client.rpc.setAccountData("fi.mau.gomuks.preferences", {
				...room.serverPreferenceCache,
				[key]: value,
			}, room.roomID)
		} else if (context === PreferenceContext.RoomDevice) {
			if (value === undefined) {
				delete room.localPreferenceCache[key]
			} else {
				(room.localPreferenceCache[key] as PreferenceValueType) = value
			}
		}
	}, [client, room])
	usePreferences(client.store, room)
	const globalServer = client.store.serverPreferenceCache
	const globalLocal = client.store.localPreferenceCache
	const roomServer = room.serverPreferenceCache
	const roomLocal = room.localPreferenceCache
	return <>
		<h2>Settings</h2>
		<code>{room.roomID}</code>
		<table>
			<thead>
				<tr>
					<th>Name</th>
					<th>Account</th>
					<th>Device</th>
					<th>Room (account)</th>
					<th>Room (device)</th>
				</tr>
			</thead>
			<tbody>
				{Object.entries(preferences).map(([key, pref]) =>
					<PreferenceRow
						key={key}
						name={key as keyof Preferences}
						pref={pref}
						setPref={setPref}
						globalServer={globalServer[key as keyof Preferences]}
						globalLocal={globalLocal[key as keyof Preferences]}
						roomServer={roomServer[key as keyof Preferences]}
						roomLocal={roomLocal[key as keyof Preferences]}
					/>)}
			</tbody>
		</table>
		<CustomCSSInput setPref={setPref} room={room} />
		<AppliedSettingsView room={room} />
	</>
}

export default SettingsView
