import { Dropdown } from "@digdir/designsystemet-react";
import {
	canDeleteSavedFilter,
	canRenameSavedFilter,
	getLocalizedString,
	type SavedFilter,
} from "@olenbetong/appframe-core";
import {
	ChevronDownIcon,
	ChevronRightIcon,
	FilesIcon,
	FileTextIcon,
	MenuElipsisVerticalIcon,
	PadlockLockedIcon,
	PencilIcon,
	StarIcon,
	TrashIcon,
} from "@navikt/aksel-icons";
import clsx from "clsx";
import { useState } from "react";

import "./SavedFilterTree.css";

/** `null` is the "new filter" node, i.e. no saved filter selected. */
export type SavedFilterSelection = number | null;

export type SavedFilterTreeProps = {
	mine: SavedFilter[];
	shared: SavedFilter[];
	/** `FilterID` of the selected filter, or `null` for "new filter". */
	value: SavedFilterSelection;
	onChange: (filterId: SavedFilterSelection) => void;
	/**
	 * Marks the selected filter as having unsaved changes; it is then rendered
	 * in bold, like the Windows client does.
	 */
	dirty?: boolean;
	onRename?: (filter: SavedFilter) => void;
	onDelete?: (filter: SavedFilter) => void;
	/** Shows an "info" item in the context menu when supplied. */
	onShowInfo?: (filter: SavedFilter) => void;
	loading?: boolean;
	className?: string;
	"data-size"?: "sm" | "md" | "lg";
};

/**
 * The saved-filter list of the filter builder: a "new filter" entry followed by
 * the user's own filters and the filters shared with them.
 *
 * Which actions a filter offers depends on the access the database resolved for
 * it — renaming and deleting are restricted to filters the user created, even
 * when they manage a filter someone else made.
 *
 * @example
 * ```jsx
 * let { mine, shared, rename, remove } = useSavedFilters({ dbObjectId: "aviw_Personnel_Persons" });
 *
 * <SavedFilterTree
 *   mine={mine}
 *   shared={shared}
 *   value={selected}
 *   onChange={setSelected}
 *   onRename={(filter) => rename(filter.filterId, prompt("Name", filter.name))}
 * />
 * ```
 */
export function SavedFilterTree({
	mine,
	shared,
	value,
	onChange,
	dirty,
	onRename,
	onDelete,
	onShowInfo,
	loading,
	className,
	"data-size": size = "sm",
}: SavedFilterTreeProps) {
	let [expanded, setExpanded] = useState({ mine: true, shared: true });

	function renderBranch(key: "mine" | "shared", label: string, icon: React.ReactNode, filters: SavedFilter[]) {
		let isExpanded = expanded[key];

		return (
			<li className="ObSavedFilterTree-branch">
				<button
					type="button"
					className="ObSavedFilterTree-node"
					aria-expanded={isExpanded}
					onClick={() => setExpanded((state) => ({ ...state, [key]: !state[key] }))}
				>
					<span className="ObSavedFilterTree-chevron" aria-hidden>
						{isExpanded ? <ChevronDownIcon /> : <ChevronRightIcon />}
					</span>
					<span className="ObSavedFilterTree-icon" aria-hidden>
						{icon}
					</span>
					<span className="ObSavedFilterTree-label">{label}</span>
				</button>
				{isExpanded && (
					<ul className="ObSavedFilterTree-children">
						{filters.length === 0 && !loading && (
							<li className="ObSavedFilterTree-empty">{getLocalizedString("No filters")}</li>
						)}
						{filters.map((filter) => (
							<SavedFilterNode
								key={filter.filterId}
								filter={filter}
								selected={filter.filterId === value}
								dirty={dirty && filter.filterId === value}
								onSelect={() => onChange(filter.filterId)}
								onRename={onRename}
								onDelete={onDelete}
								onShowInfo={onShowInfo}
							/>
						))}
					</ul>
				)}
			</li>
		);
	}

	return (
		<ul className={clsx("ObSavedFilterTree-root", className)} data-size={size}>
			<li>
				<button
					type="button"
					className={clsx("ObSavedFilterTree-node", value === null && "ObSavedFilterTree-selected")}
					aria-current={value === null}
					onClick={() => onChange(null)}
				>
					<span className="ObSavedFilterTree-chevron" aria-hidden />
					<span className="ObSavedFilterTree-icon" aria-hidden>
						<FileTextIcon />
					</span>
					<span className="ObSavedFilterTree-label">{getLocalizedString("New Filter")}</span>
				</button>
			</li>
			{renderBranch("mine", getLocalizedString("My Filters"), <StarIcon />, mine)}
			{renderBranch("shared", getLocalizedString("Shared With Me"), <FilesIcon />, shared)}
		</ul>
	);
}

type SavedFilterNodeProps = {
	filter: SavedFilter;
	selected: boolean;
	dirty?: boolean;
	onSelect: () => void;
	onRename?: (filter: SavedFilter) => void;
	onDelete?: (filter: SavedFilter) => void;
	onShowInfo?: (filter: SavedFilter) => void;
};

function SavedFilterNode({ filter, selected, dirty, onSelect, onRename, onDelete, onShowInfo }: SavedFilterNodeProps) {
	let canRename = Boolean(onRename) && canRenameSavedFilter(filter);
	let canDelete = Boolean(onDelete) && canDeleteSavedFilter(filter);
	let hasMenu = canRename || canDelete || Boolean(onShowInfo);
	// A shared filter the user may not change; the Windows client shows a lock.
	let isReadOnly = !filter.createdByUser && filter.accessLevel === "Reader";

	return (
		<li className="ObSavedFilterTree-item">
			<button
				type="button"
				className={clsx(
					"ObSavedFilterTree-node",
					selected && "ObSavedFilterTree-selected",
					dirty && "ObSavedFilterTree-dirty",
				)}
				aria-current={selected}
				onClick={onSelect}
			>
				<span className="ObSavedFilterTree-icon" aria-hidden>
					{isReadOnly ? <PadlockLockedIcon /> : <FileTextIcon />}
				</span>
				<span className="ObSavedFilterTree-label">{filter.name}</span>
			</button>
			{hasMenu && (
				<Dropdown.TriggerContext>
					<Dropdown.Trigger
						variant="tertiary"
						icon
						className="ObSavedFilterTree-menuButton"
						aria-label={getLocalizedString("Options for {0}").replace("{0}", filter.name)}
					>
						<MenuElipsisVerticalIcon aria-hidden />
					</Dropdown.Trigger>
					<Dropdown>
						<Dropdown.List>
							{canRename && (
								<Dropdown.Item>
									<Dropdown.Button onClick={() => onRename?.(filter)}>
										<PencilIcon aria-hidden />
										{getLocalizedString("Rename")}
									</Dropdown.Button>
								</Dropdown.Item>
							)}
							{onShowInfo && (
								<Dropdown.Item>
									<Dropdown.Button onClick={() => onShowInfo(filter)}>
										{getLocalizedString("Information")}
									</Dropdown.Button>
								</Dropdown.Item>
							)}
							{canDelete && (
								<Dropdown.Item>
									<Dropdown.Button data-color="danger" onClick={() => onDelete?.(filter)}>
										<TrashIcon aria-hidden />
										{getLocalizedString("Delete")}
									</Dropdown.Button>
								</Dropdown.Item>
							)}
						</Dropdown.List>
					</Dropdown>
				</Dropdown.TriggerContext>
			)}
		</li>
	);
}
