import "./FilterEditor.css";

import { type FilterTree, getLocalizedString, toFilterTree } from "@olenbetong/appframe-core";
import clsx from "clsx";
import { useMemo, useState } from "react";

import { FilterGroupEditor } from "./FilterGroupEditor.js";
import { FilterStringField } from "./FilterStringField.js";
import type { FilterEditorProps } from "./types.js";

/**
 * The criteria tree of the filter builder. Groups can be nested and each group
 * combines its items with either `and` or `or`, matching the advanced filter
 * editor in the Windows client.
 *
 * Works both controlled (`value` + `onChange`) and uncontrolled
 * (`defaultValue`). Saving and sharing filters is handled by `FilterBuilder`,
 * which composes this component.
 *
 * @example
 * ```jsx
 * <FilterEditor fields={fields} value={filter} onChange={setFilter} showFilterString />
 * ```
 */
export function FilterEditor({
	fields,
	value,
	defaultValue,
	onChange,
	readOnly,
	showFilterString,
	viewName,
	getFieldOptions,
	className,
	"data-size": size = "sm",
}: FilterEditorProps) {
	let [uncontrolled, setUncontrolled] = useState<FilterTree>(() => toFilterTree(defaultValue));
	let isControlled = value !== undefined;
	// Normalizing on every render would create a new tree each time, which
	// retriggers the filter string conversion.
	let controlled = useMemo(() => toFilterTree(value), [value]);
	let filter = isControlled ? controlled : uncontrolled;

	let visibleFields = useMemo(() => fields.filter((field) => !field.excludeFromFilter), [fields]);

	function update(next: FilterTree) {
		if (!isControlled) setUncontrolled(next);
		onChange?.(next);
	}

	return (
		<div className={clsx("ObFilterEditor-root", className)} data-size={size}>
			<div className="ObFilterEditor-body">
				<p className="ObFilterEditor-intro">
					{filter.mode === "and"
						? getLocalizedString("Find rows that match all of these criteria:")
						: getLocalizedString("Find rows that match one or more of these criteria:")}
				</p>
				<FilterGroupEditor
					filter={filter}
					path={[]}
					fields={visibleFields}
					getFieldOptions={getFieldOptions}
					readOnly={readOnly}
					data-size={size}
					onChange={update}
				/>
			</div>
			{showFilterString && (
				<FilterStringField filter={filter} viewName={viewName} readOnly={readOnly} data-size={size} onChange={update} />
			)}
		</div>
	);
}
