import { Select, Textfield } from "@digdir/designsystemet-react";
import { PencilIcon } from "@navikt/aksel-icons";
import {
	type DynamicValue,
	type FieldMetadata,
	type FilterExpression,
	type FilterOperator,
	getDynamicValueLabel,
	getDynamicValuesForField,
	getLocalizedString,
	getOperatorArity,
	getValueType,
	normalizeOperator,
} from "@olenbetong/appframe-core";
import { Combobox } from "@olenbetong/appframe-ds";

import { ChipListInput } from "./ChipListInput.js";
import type { FilterEditorSize, FilterValueOption } from "./types.js";

export type FilterValueEditorProps = {
	expression: FilterExpression;
	field?: FieldMetadata;
	options?: FilterValueOption[];
	onChange: (expression: FilterExpression) => void;
	readOnly?: boolean;
	"data-size"?: FilterEditorSize;
};

/** The `input` type used for a field's scalar editor. */
function getInputType(field: FieldMetadata | undefined): "text" | "number" | "date" | "datetime-local" {
	switch (field?.type) {
		case "number":
			return "number";
		case "date":
			return "date";
		case "datetime":
			return "datetime-local";
		default:
			return "text";
	}
}

function toStringValue(value: FilterExpression["value"]): string {
	if (value === null || value === undefined) return "";
	if (value instanceof Date) return value.toISOString();

	return String(value);
}

function toArrayValue(value: FilterExpression["value"], length?: number): string[] {
	let array = Array.isArray(value) ? value.map(toStringValue) : [];
	if (length === undefined) return array;

	// Pair editors always render two inputs, even before both are filled in.
	return Array.from({ length }, (_, index) => array[index] ?? "");
}

/**
 * The value part of a filter row. Which editor is rendered depends on the
 * operator's arity, the field's type, and whether the value is a dynamic
 * ("special") value such as `Today` or `CurrentUser`.
 */
export function FilterValueEditor({
	expression,
	field,
	options,
	onChange,
	readOnly,
	"data-size": size = "sm",
}: FilterValueEditorProps) {
	let operator = normalizeOperator(expression.operator);
	let arity = getOperatorArity(operator);
	let isDynamic = expression.valueType === "special";
	let dynamicValues = field ? getDynamicValuesForField(field) : [];
	let inputType = getInputType(field);

	function setValue(value: FilterExpression["value"]) {
		onChange({ ...expression, value });
	}

	function setDynamic(enabled: boolean) {
		if (enabled) {
			onChange({ ...expression, value: dynamicValues[0] ?? null, valueType: "special" });
		} else {
			onChange({
				...expression,
				value: arity === "pair" ? ["", ""] : arity === "list" ? [] : "",
				valueType: field ? getValueType(field, operator as FilterOperator) : "string",
			});
		}
	}

	if (arity === "none") return null;

	let editor: React.ReactNode;

	if (isDynamic) {
		editor = (
			<Select
				className="ObFilterEditor-value"
				aria-label={getLocalizedString("Value")}
				data-size={size}
				disabled={readOnly}
				value={toStringValue(expression.value)}
				onChange={(event) => setValue(event.target.value)}
			>
				{dynamicValues.map((dynamicValue) => (
					<Select.Option key={dynamicValue} value={dynamicValue}>
						{getDynamicValueLabel(dynamicValue as DynamicValue)}
					</Select.Option>
				))}
			</Select>
		);
	} else if (arity === "list") {
		editor = options ? (
			<Combobox
				className="ObFilterEditor-value"
				multiple
				data-size={size}
				readOnly={readOnly}
				placeholder={getLocalizedString("Select values")}
				aria-label={getLocalizedString("Value")}
				options={options}
				getOptionLabel={(option) => option.label ?? option.value}
				isOptionEqualToValue={(a, b) => a.value === b.value}
				value={toArrayValue(expression.value).map(
					(item) => options.find((option) => option.value === item) ?? { value: item },
				)}
				onChange={(_event, selected) => setValue(selected.map((option) => option.value))}
			/>
		) : (
			<ChipListInput
				aria-label={getLocalizedString("Value")}
				data-size={size}
				readOnly={readOnly}
				type={inputType}
				value={toArrayValue(expression.value)}
				onChange={setValue}
			/>
		);
	} else if (arity === "pair") {
		let [from, to] = toArrayValue(expression.value, 2);

		editor = (
			<div className="ObFilterEditor-pair">
				<Textfield
					className="ObFilterEditor-value"
					aria-label={getLocalizedString("From")}
					data-size={size}
					readOnly={readOnly}
					type={inputType}
					value={from}
					onChange={(event) => setValue([event.target.value, to])}
				/>
				<span className="ObFilterEditor-pairSeparator">{getLocalizedString("and")}</span>
				<Textfield
					className="ObFilterEditor-value"
					aria-label={getLocalizedString("To")}
					data-size={size}
					readOnly={readOnly}
					type={inputType}
					value={to}
					onChange={(event) => setValue([from, event.target.value])}
				/>
			</div>
		);
	} else if (options) {
		let current = toStringValue(expression.value);

		editor = (
			<Combobox
				className="ObFilterEditor-value"
				data-size={size}
				readOnly={readOnly}
				placeholder={getLocalizedString("Select value")}
				aria-label={getLocalizedString("Value")}
				options={options}
				getOptionLabel={(option) => option.label ?? option.value}
				isOptionEqualToValue={(a, b) => a.value === b.value}
				value={current ? (options.find((option) => option.value === current) ?? { value: current }) : null}
				onChange={(_event, selected) => setValue(selected?.value ?? "")}
			/>
		);
	} else {
		editor = (
			<Textfield
				className="ObFilterEditor-value"
				aria-label={getLocalizedString("Value")}
				data-size={size}
				readOnly={readOnly}
				type={inputType}
				value={toStringValue(expression.value)}
				onChange={(event) => setValue(event.target.value)}
			/>
		);
	}

	// Dynamic values are resolved server side, so they can only replace the whole
	// value. Pairs would need one toggle per side, which the Windows editor does
	// not offer either.
	let canToggleDynamic = !readOnly && dynamicValues.length > 0 && arity === "single";

	return (
		<div className="ObFilterEditor-valueCell">
			{editor}
			{canToggleDynamic && (
				<button
					type="button"
					className="ObFilterEditor-iconButton"
					aria-pressed={isDynamic}
					title={getLocalizedString("Use a relative value")}
					aria-label={getLocalizedString("Use a relative value")}
					onClick={() => setDynamic(!isDynamic)}
				>
					<PencilIcon aria-hidden />
				</button>
			)}
		</div>
	);
}
