import { Button, Dialog, Heading, Select, Table } from "@digdir/designsystemet-react";
import { getLocalizedString } from "@olenbetong/appframe-core";
import { type FilterPermission, useDebounce, useFilterPermissions } from "@olenbetong/appframe-react";
import { Combobox } from "@olenbetong/appframe-ds";
import { PersonGroupIcon, PersonIcon, TrashIcon } from "@navikt/aksel-icons";
import { useEffect, useState } from "react";

import "./FilterShareDialog.css";

type PermissionCandidate = {
	primKey: string;
	name: string;
	login: string;
	type: string;
};

export type FilterShareDialogProps = {
	open: boolean;
	onClose: () => void;
	/** `FilterID` of the filter to manage sharing for. */
	filterId: number | null;
	/** Shown in the dialog heading. */
	filterName?: string;
	"data-size"?: "sm" | "md" | "lg";
};

/**
 * Manages who a saved filter is shared with.
 *
 * Only a manager of the filter can load or change the grants — for anyone else
 * `sstp_WebSiteCMS_FilterBuilder_GetPermissions` returns nothing, so the list
 * simply stays empty.
 *
 * @example
 * ```jsx
 * <FilterShareDialog open={sharing} filterId={selected} filterName={name} onClose={() => setSharing(false)} />
 * ```
 */
export function FilterShareDialog({
	open,
	onClose,
	filterId,
	filterName,
	"data-size": size = "sm",
}: FilterShareDialogProps) {
	let { permissions, loading, error, setPermission, removePermission, search } = useFilterPermissions(
		open ? filterId : null,
	);
	let [candidates, setCandidates] = useState<PermissionCandidate[]>([]);
	let [searchText, setSearchText] = useState("");
	let query = useDebounce(searchText, 300);

	useEffect(() => {
		if (!open || query.length < 2) {
			setCandidates([]);

			return;
		}

		let isCancelled = false;
		search(query)
			.then((results) => {
				if (isCancelled) return;
				// Anyone who already has a grant is not a candidate for a new one.
				let granted = new Set(permissions.map((permission) => permission.groupRef));
				setCandidates(results.filter((result) => !granted.has(result.primKey)));
			})
			.catch(() => {
				if (!isCancelled) setCandidates([]);
			});

		return () => {
			isCancelled = true;
		};
	}, [open, query, search, permissions]);

	function handleAdd(candidate: PermissionCandidate | null) {
		if (!candidate) return;

		setSearchText("");
		void setPermission(candidate.primKey, "Reader");
	}

	return (
		<Dialog open={open} closedby="any" data-size={size} onClose={onClose} className="ObFilterShareDialog-root">
			<Dialog.Block>
				<Heading level={2} data-size="xs">
					{filterName ? getLocalizedString("Sharing - {0}").replace("{0}", filterName) : getLocalizedString("Sharing")}
				</Heading>
			</Dialog.Block>
			<Dialog.Block>
				<Combobox<PermissionCandidate>
					options={candidates}
					value={null}
					inputValue={searchText}
					loading={loading}
					data-size={size}
					label={getLocalizedString("Add user or group")}
					placeholder={getLocalizedString("Search...")}
					getOptionLabel={(option) => option.name}
					getOptionKey={(option) => option.primKey}
					// The procedure searches both name and login, so filtering again on
					// the label would drop everything matched by login.
					filterOptions={(options) => [...options]}
					noOptionsText={query.length < 2 ? getLocalizedString("Type to search") : getLocalizedString("No matches")}
					renderOption={(option) => (
						<span className="ObFilterShareDialog-candidate">
							{option.type === "User" ? <PersonIcon aria-hidden /> : <PersonGroupIcon aria-hidden />}
							<span>{option.name}</span>
							<span className="ObFilterShareDialog-candidateLogin">{option.login}</span>
						</span>
					)}
					// The picker never keeps a selection; picking an option adds a grant.
					onInputChange={(_event, value) => setSearchText(value)}
					onChange={(_event, value) => handleAdd(value)}
				/>
			</Dialog.Block>
			<Dialog.Block>
				{error && <p className="ObFilterShareDialog-error">{String(error.message ?? error)}</p>}
				<Table data-size={size} className="ObFilterShareDialog-table">
					<Table.Head>
						<Table.Row>
							<Table.HeaderCell>{getLocalizedString("Name")}</Table.HeaderCell>
							<Table.HeaderCell>{getLocalizedString("Login")}</Table.HeaderCell>
							<Table.HeaderCell>{getLocalizedString("Access")}</Table.HeaderCell>
							<Table.HeaderCell />
						</Table.Row>
					</Table.Head>
					<Table.Body>
						{permissions.length === 0 && (
							<Table.Row>
								<Table.Cell colSpan={4}>
									{loading ? getLocalizedString("Loading...") : getLocalizedString("Not shared with anyone")}
								</Table.Cell>
							</Table.Row>
						)}
						{permissions.map((permission) => (
							<PermissionRow
								key={permission.primKey}
								permission={permission}
								size={size}
								onChangeAccess={(accessLevel) => void setPermission(permission.groupRef, accessLevel)}
								onRemove={() => void removePermission(permission.groupRef)}
							/>
						))}
					</Table.Body>
				</Table>
			</Dialog.Block>
			<Dialog.Block>
				<Button type="button" variant="secondary" data-size={size} onClick={onClose}>
					{getLocalizedString("Close")}
				</Button>
			</Dialog.Block>
		</Dialog>
	);
}

type PermissionRowProps = {
	permission: FilterPermission;
	size: "sm" | "md" | "lg";
	onChangeAccess: (accessLevel: "Reader" | "Manager") => void;
	onRemove: () => void;
};

function PermissionRow({ permission, size, onChangeAccess, onRemove }: PermissionRowProps) {
	return (
		<Table.Row>
			<Table.Cell>
				<span className="ObFilterShareDialog-candidate">
					{permission.type === "User" ? <PersonIcon aria-hidden /> : <PersonGroupIcon aria-hidden />}
					{permission.name}
				</span>
			</Table.Cell>
			<Table.Cell>{permission.login}</Table.Cell>
			<Table.Cell>
				<Select
					data-size={size}
					value={permission.accessLevel}
					aria-label={getLocalizedString("Access for {0}").replace("{0}", permission.name)}
					onChange={(event) => onChangeAccess(event.currentTarget.value as "Reader" | "Manager")}
				>
					<Select.Option value="Reader">{getLocalizedString("Reader")}</Select.Option>
					<Select.Option value="Manager">{getLocalizedString("Manager")}</Select.Option>
				</Select>
			</Table.Cell>
			<Table.Cell>
				<Button
					type="button"
					variant="tertiary"
					data-color="danger"
					data-size={size}
					icon
					aria-label={getLocalizedString("Remove access for {0}").replace("{0}", permission.name)}
					onClick={onRemove}
				>
					<TrashIcon aria-hidden />
				</Button>
			</Table.Cell>
		</Table.Row>
	);
}
