// Settings — subscribes the config draft (raw edits, resolved display values) +
// liveStatusAtom (the Files paths); mutates saveConfig via the sticky SaveBar.

import { useAtomRefresh, useAtomValue } from "@effect/atom-react";
import type { ArtMode, RawConfig } from "@playnite/contract";
import { PathListEditor } from "@unom/app-ui/path-list-editor";
import { SettingsGroup, SettingsRow } from "@unom/app-ui/settings";
import { CodeBlock } from "@unom/ui/code-block";
import { InputNumber } from "@unom/ui/form/input-number";
import { InputText } from "@unom/ui/form/input-text";
import {
	Select,
	SelectContent,
	SelectItem,
	SelectTrigger,
	SelectValue,
} from "@unom/ui/form/select";
import { Switch } from "@unom/ui/form/switch";
import { Skeleton } from "@unom/ui/skeleton";
import { Gate } from "@/components/gate";
import { SaveBar } from "@/components/save-bar";
import { configAtom, liveStatusAtom, statusAtom } from "@/data/atoms";
import { useConfigDraft } from "@/data/drafts";
import type { PageProps } from "@/routes";

type ArtRaw = NonNullable<RawConfig["art"]>;
type SyncRaw = NonNullable<RawConfig["sync"]>;
type FilterRaw = NonNullable<RawConfig["filter"]>;

const PageSkeleton = () => (
	<div className="space-y-6">
		<Skeleton className="h-72" />
		<Skeleton className="h-64" />
		<Skeleton className="h-52" />
		<Skeleton className="h-40" />
	</div>
);

const ART_MODES: ReadonlyArray<{
	readonly value: ArtMode;
	readonly label: string;
	readonly hint: string;
}> = [
	{
		value: "host",
		label: "Served by the host",
		hint: "Send each cover's local path and let the host serve the bytes. Scales to any library size — the recommended mode.",
	},
	{
		value: "dataurl",
		label: "Inlined in the payload",
		hint: "Embed each cover in the library payload. Self-contained, but only fit for small libraries.",
	},
	{ value: "off", label: "No cover art", hint: "Sync titles with no art." },
];

/** A string list editor over one of the source filters (`sources` / `excludeSources`). */
const SourceList = ({
	values,
	onChange,
	placeholder,
	addLabel,
}: {
	values: ReadonlyArray<string>;
	onChange: (next: ReadonlyArray<string>) => void;
	placeholder: string;
	addLabel: string;
}) => (
	<PathListEditor<string>
		items={values}
		onChange={onChange}
		path={{ get: (s) => s, set: (_, value) => value, placeholder }}
		makeItem={(s) => s}
		addLabel={addLabel}
		className="w-full"
	/>
);

export const SettingsPage = (_: PageProps) => {
	const config = useAtomValue(configAtom);
	const retry = useAtomRefresh(configAtom);
	const status = useAtomValue(liveStatusAtom);
	const retryStatus = useAtomRefresh(statusAtom);
	const draft = useConfigDraft();
	const { resolved } = draft;

	const patchArt = (patch: Partial<ArtRaw>) =>
		draft.patch({ art: { ...(draft.draft.art ?? {}), ...patch } });
	const patchSync = (patch: Partial<SyncRaw>) =>
		draft.patch({ sync: { ...(draft.draft.sync ?? {}), ...patch } });
	const patchFilter = (patch: Partial<FilterRaw>) =>
		draft.patch({ filter: { ...(draft.draft.filter ?? {}), ...patch } });

	const artMode = resolved?.art.mode ?? "host";

	return (
		<Gate result={config} skeleton={<PageSkeleton />} retry={retry}>
			{() => (
				<div className="space-y-6">
					<SettingsGroup
						title="Which games sync"
						description="Playnite exports your whole library; these filters decide what reaches the Punktfunk library."
					>
						<SettingsRow
							label="Only installed games"
							description="Skip titles Playnite knows about but hasn't installed."
						>
							<Switch
								checked={resolved?.filter.installedOnly ?? true}
								onCheckedChange={(checked) =>
									patchFilter({ installedOnly: checked === true })
								}
							/>
						</SettingsRow>
						<SettingsRow
							label="Include hidden games"
							description="Titles you flagged Hidden in Playnite."
						>
							<Switch
								checked={resolved?.filter.includeHidden ?? false}
								onCheckedChange={(checked) =>
									patchFilter({ includeHidden: checked === true })
								}
							/>
						</SettingsRow>
						<SettingsRow
							label="Only these sources"
							description='Leave empty for every source. Names as Playnite shows them ("Steam", "GOG", "Epic"…); matching ignores case.'
							vertical
						>
							<SourceList
								values={resolved?.filter.sources ?? []}
								onChange={(sources) => patchFilter({ sources: [...sources] })}
								placeholder="Steam"
								addLabel="Add source"
							/>
						</SettingsRow>
						<SettingsRow
							label="Never these sources"
							description="Applied after the list above."
							vertical
						>
							<SourceList
								values={resolved?.filter.excludeSources ?? []}
								onChange={(excludeSources) =>
									patchFilter({ excludeSources: [...excludeSources] })
								}
								placeholder="Xbox"
								addLabel="Exclude source"
							/>
						</SettingsRow>
					</SettingsGroup>

					<SettingsGroup
						title="Cover art"
						description="Playnite keeps cover art as files on this machine; this decides how it reaches your clients."
					>
						<SettingsRow
							label="Delivery"
							description={
								ART_MODES.find((m) => m.value === artMode)?.hint ?? ""
							}
						>
							<Select
								value={artMode}
								onValueChange={(value) => patchArt({ mode: value as ArtMode })}
							>
								<SelectTrigger size="sm" className="w-52">
									<SelectValue />
								</SelectTrigger>
								<SelectContent>
									{ART_MODES.map((m) => (
										<SelectItem key={m.value} value={m.value}>
											{m.label}
										</SelectItem>
									))}
								</SelectContent>
							</Select>
						</SettingsRow>
						{artMode !== "off" ? (
							<SettingsRow
								label="Also send backgrounds"
								description="Playnite's background image becomes hero art. Usually large."
							>
								<Switch
									checked={resolved?.art.includeBackground ?? false}
									onCheckedChange={(checked) =>
										patchArt({ includeBackground: checked === true })
									}
								/>
							</SettingsRow>
						) : null}
						{artMode === "dataurl" ? (
							<SettingsRow
								label="Size cap per image"
								description="Bytes. A cover larger than this is dropped rather than inlined."
							>
								<InputNumber
									className="w-36"
									min={1}
									step={100_000}
									value={resolved?.art.maxBytes ?? 1_500_000}
									onChange={(maxBytes) => patchArt({ maxBytes })}
								/>
							</SettingsRow>
						) : null}
					</SettingsGroup>

					<SettingsGroup
						title="Sync"
						description="When the plugin re-reads Playnite's export and reconciles the library."
					>
						<SettingsRow
							label="Watch for changes"
							description="React to a new export immediately (polling stays on as the fallback)."
						>
							<Switch
								checked={resolved?.sync.watch ?? true}
								onCheckedChange={(checked) =>
									patchSync({ watch: checked === true })
								}
							/>
						</SettingsRow>
						<SettingsRow
							label="Poll interval"
							description="Minutes between re-reads."
						>
							<InputNumber
								className="w-28"
								min={1}
								value={resolved?.sync.pollMinutes ?? 5}
								onChange={(pollMinutes) => patchSync({ pollMinutes })}
							/>
						</SettingsRow>
						<SettingsRow
							label="Debounce"
							description="Milliseconds to wait after a change before re-reading."
						>
							<InputNumber
								className="w-28"
								min={0}
								step={100}
								value={resolved?.sync.debounceMs ?? 1500}
								onChange={(debounceMs) => patchSync({ debounceMs })}
							/>
						</SettingsRow>
					</SettingsGroup>

					<SettingsGroup
						title="Advanced"
						description="Only needed when Playnite isn't where the plugin expects it."
					>
						<SettingsRow
							label="Playnite data directory"
							description="Overrides the auto-detected %APPDATA%\Playnite — for a portable install. The host ingest inbox is still read first."
						>
							<InputText
								className="w-72"
								spellCheck={false}
								value={draft.draft.playniteDir ?? ""}
								placeholder="C:\Playnite"
								onChange={(event) => {
									const dir = event.target.value;
									// `playniteDir` is an optionalKey: undefined is dropped by
									// JSON.stringify, so the PUT body omits it entirely.
									draft.patch({
										playniteDir: dir.length > 0 ? dir : undefined,
									});
								}}
							/>
						</SettingsRow>
					</SettingsGroup>

					<SettingsGroup
						title="Files"
						description="Where this plugin keeps its state, and where Playnite drops the library."
					>
						<Gate
							result={status}
							skeleton={<Skeleton className="h-24" />}
							retry={retryStatus}
						>
							{(s) => (
								<CodeBlock copy>
									{`config: ${s.paths.config}\ncache:  ${s.paths.cache}\ninbox:  ${s.paths.ingest}\nexport: ${s.location.file ?? "(not written yet)"}`}
								</CodeBlock>
							)}
						</Gate>
					</SettingsGroup>

					<SaveBar
						dirty={draft.dirty}
						saving={draft.saving}
						onSave={draft.save}
						onDiscard={draft.discard}
					/>
				</div>
			)}
		</Gate>
	);
};
