import { Button, Select } from "@digdir/designsystemet-react";
import { FolderPlusIcon, TrashIcon } from "@navikt/aksel-icons";
import {
	addGroupAtPath,
	createExpression,
	type FieldMetadata,
	type FilterExpression,
	type FilterGroup,
	type FilterNode,
	type FilterPath,
	type FilterTree,
	getLocalizedString,
	getNodeAtPath,
	insertNodeAtPath,
	isExpression,
	isGroup,
	removeNodeAtPath,
	setGroupMode,
	updateNodeAtPath,
} from "@olenbetong/appframe-core";
import { Combobox } from "@olenbetong/appframe-ds";

import { FilterRow, getFieldLabel } from "./FilterRow.js";
import type { FilterEditorSize, GetFieldOptions } from "./types.js";

export type FilterGroupEditorProps = {
	/** The whole tree. Groups edit it through their own `path`. */
	filter: FilterTree;
	/** Path to the group this editor renders. The root group is the empty path. */
	path: FilterPath;
	fields: FieldMetadata[];
	onChange: (filter: FilterTree) => void;
	getFieldOptions?: GetFieldOptions;
	readOnly?: boolean;
	"data-size"?: FilterEditorSize;
};

/**
 * One group of the filter tree. Groups can be nested and each one combines its
 * items with either `and` or `or`, matching the advanced (DevExpress) filter
 * editor in the Windows client.
 *
 * A blank row is always rendered at the end; picking a field in it appends a
 * real condition to the group.
 */
export function FilterGroupEditor({
	filter,
	path,
	fields,
	onChange,
	getFieldOptions,
	readOnly,
	"data-size": size = "sm",
}: FilterGroupEditorProps) {
	let node = getNodeAtPath(filter, path);
	if (!node || !isGroup(node)) return null;

	let group: FilterGroup = node;
	let isRoot = path.length === 0;

	return (
		<div className="ObFilterEditor-group" data-root={isRoot ? "" : undefined}>
			<div className="ObFilterEditor-groupHeader">
				<Select
					className="ObFilterEditor-groupMode"
					data-size={size}
					disabled={readOnly}
					aria-label={getLocalizedString("Group operator")}
					value={group.mode}
					onChange={(event) => onChange(setGroupMode(filter, path, event.target.value as FilterGroup["mode"]))}
				>
					<Select.Option value="and">{getLocalizedString("And")}</Select.Option>
					<Select.Option value="or">{getLocalizedString("Or")}</Select.Option>
				</Select>
				{!readOnly && (
					<>
						<Button
							type="button"
							variant="tertiary"
							data-size={size}
							className="ObFilterEditor-groupButton"
							aria-label={getLocalizedString("Add group")}
							onClick={() => onChange(addGroupAtPath(filter, path))}
						>
							<FolderPlusIcon aria-hidden />
							<span className="ObFilterEditor-groupButtonLabel">{getLocalizedString("Add group")}</span>
						</Button>
						{!isRoot && (
							<Button
								type="button"
								variant="tertiary"
								data-color="danger"
								data-size={size}
								className="ObFilterEditor-groupButton"
								aria-label={getLocalizedString("Remove group")}
								onClick={() => onChange(removeNodeAtPath(filter, path))}
							>
								<TrashIcon aria-hidden />
								<span className="ObFilterEditor-groupButtonLabel">{getLocalizedString("Remove group")}</span>
							</Button>
						)}
					</>
				)}
			</div>
			<div className="ObFilterEditor-groupItems">
				{group.items.map((item, index) =>
					isExpression(item) ? (
						<FilterRow
							// Rows have no stable identity, and reordering is not supported,
							// so the index is the only key available.
							// oxlint-disable-next-line no-array-index-key -- see above
							key={index}
							expression={item}
							fields={fields}
							getFieldOptions={getFieldOptions}
							readOnly={readOnly}
							data-size={size}
							onChange={(next) => onChange(replaceNode(filter, [...path, index], next))}
							onRemove={() => onChange(removeNodeAtPath(filter, [...path, index]))}
						/>
					) : (
						<FilterGroupEditor
							// oxlint-disable-next-line no-array-index-key -- see above
							key={index}
							filter={filter}
							path={[...path, index]}
							fields={fields}
							getFieldOptions={getFieldOptions}
							readOnly={readOnly}
							data-size={size}
							onChange={onChange}
						/>
					),
				)}
				{!readOnly && (
					<div className="ObFilterEditor-row ObFilterEditor-draftRow">
						<Combobox
							className="ObFilterEditor-field"
							data-size={size}
							aria-label={getLocalizedString("Add condition")}
							placeholder={getLocalizedString("Add condition")}
							options={fields}
							getOptionLabel={getFieldLabel}
							getOptionKey={(option) => option.name}
							isOptionEqualToValue={(a, b) => a.name === b.name}
							value={null}
							onChange={(_event, field) => {
								if (field) onChange(insertNodeAtPath(filter, path, createExpression(field)));
							}}
						/>
					</div>
				)}
			</div>
		</div>
	);
}

function replaceNode(filter: FilterTree, path: FilterPath, node: FilterExpression): FilterTree {
	return updateNodeAtPath<FilterNode>(filter, path, () => node) as FilterTree;
}
