import "./ReportParameters.css";

import { Checkbox, Field, FieldDescription, Label, Select, Textfield } from "@digdir/designsystemet-react";
import {
	coerceParameterValue,
	getLocalizedString,
	type ReportParameter,
	type ReportParameterValue,
	type ReportParameterValues,
} from "@olenbetong/appframe-core";
import { useParameterOptions } from "@olenbetong/appframe-react";
import { Combobox } from "@olenbetong/appframe-ds";
import clsx from "clsx";
import { useId } from "react";

import type { FilterEditorSize } from "../filter/types.js";

export type ReportParametersProps = {
	/** The report's parameters, from `useReport`. */
	parameters: ReportParameter[];
	/** Current values, by parameter name. */
	values: ReportParameterValues;
	onChange: (values: ReportParameterValues) => void;
	readOnly?: boolean;
	/** Heading above the parameters. Pass `null` to render none. */
	legend?: string | null;
	className?: string;
	"data-size"?: FilterEditorSize;
};

/**
 * The parameter form of the report launcher.
 *
 * Reports rarely have parameters, so this renders nothing at all when the list
 * is empty and the launcher can include it unconditionally.
 *
 * Each parameter gets the editor its metadata asks for: a lookup combobox when
 * it is backed by a record source, a select when it has a fixed item list, and
 * otherwise an editor for its data type. Values are coerced to the parameter's
 * type as they are entered, so what the caller holds is what gets sent to the
 * report.
 *
 * @example
 * ```jsx
 * let { parameters, defaultValues } = useReport(reportId);
 * let [values, setValues] = useState(defaultValues);
 *
 * <ReportParameters parameters={parameters} values={values} onChange={setValues} />
 * ```
 */
export function ReportParameters({
	parameters,
	values,
	onChange,
	readOnly,
	legend = getLocalizedString("Parameters"),
	className,
	"data-size": size = "sm",
}: ReportParametersProps) {
	if (parameters.length === 0) return null;

	function update(parameter: ReportParameter, value: ReportParameterValue) {
		onChange({ ...values, [parameter.name]: value });
	}

	return (
		<fieldset className={clsx("ObReportParameters-root", className)} data-size={size}>
			{legend && <legend className="ObReportParameters-legend">{legend}</legend>}
			<div className="ObReportParameters-grid">
				{parameters.map((parameter) => (
					<ReportParameterField
						key={parameter.name}
						parameter={parameter}
						value={values[parameter.name] ?? null}
						onChange={(value) => update(parameter, value)}
						readOnly={readOnly}
						size={size}
					/>
				))}
			</div>
		</fieldset>
	);
}

type ReportParameterFieldProps = {
	parameter: ReportParameter;
	value: ReportParameterValue;
	onChange: (value: ReportParameterValue) => void;
	readOnly?: boolean;
	size: FilterEditorSize;
};

/** The editor for a single parameter. */
export function ReportParameterField({ parameter, value, onChange, readOnly, size }: ReportParameterFieldProps) {
	// The hook returns nothing for parameters that are not lookups, so it can be
	// called for every parameter.
	let { options, loading } = useParameterOptions(parameter);
	let id = useId();

	let commonProps = {
		"data-size": size,
		label: parameter.label,
		description: parameter.description ?? undefined,
		readOnly,
	};

	switch (parameter.editor) {
		case "boolean":
			return (
				<div className="ObReportParameters-field">
					<Checkbox
						data-size={size}
						label={parameter.label}
						description={parameter.description ?? undefined}
						disabled={readOnly}
						checked={value === true}
						onChange={(event) => onChange(event.target.checked)}
					/>
				</div>
			);

		case "select":
			return (
				<div className="ObReportParameters-field">
					<Field data-size={size}>
						<Label htmlFor={id}>{parameter.label}</Label>
						{parameter.description && <FieldDescription>{parameter.description}</FieldDescription>}
						<Select
							id={id}
							readOnly={readOnly}
							value={value === null ? "" : String(value)}
							onChange={(event) => onChange(coerceParameterValue(parameter, event.currentTarget.value))}
						>
							<Select.Option value="">{getLocalizedString("Not selected")}</Select.Option>
							{parameter.items.map((item) => (
								<Select.Option key={item} value={item}>
									{item}
								</Select.Option>
							))}
						</Select>
					</Field>
				</div>
			);

		case "lookup":
			return (
				<div className="ObReportParameters-field">
					<Combobox
						{...commonProps}
						loading={loading}
						options={options}
						value={options.find((option) => option.value === String(value ?? "")) ?? null}
						getOptionLabel={(option) => option.label}
						getOptionKey={(option) => option.value}
						isOptionEqualToValue={(option, selected) => option.value === selected.value}
						onChange={(_event, option) => onChange(option ? coerceParameterValue(parameter, option.value) : null)}
					/>
				</div>
			);

		case "number":
			return (
				<div className="ObReportParameters-field">
					<Textfield
						{...commonProps}
						type="number"
						// The designer's `Double` parameters are free-form decimals.
						step={parameter.type === "Integer" ? 1 : "any"}
						value={value === null ? "" : String(value)}
						onChange={(event) => onChange(coerceParameterValue(parameter, event.target.value))}
					/>
				</div>
			);

		case "date":
			return (
				<div className="ObReportParameters-field">
					<Textfield
						{...commonProps}
						type="date"
						value={value === null ? "" : String(value)}
						onChange={(event) => onChange(event.target.value === "" ? null : event.target.value)}
					/>
				</div>
			);

		default:
			return (
				<div className="ObReportParameters-field">
					<Textfield
						{...commonProps}
						value={value === null ? "" : String(value)}
						onChange={(event) => onChange(event.target.value === "" ? null : event.target.value)}
					/>
				</div>
			);
	}
}
