const require_chunk = require("./chunk-9hOWP6kD.cjs");
const require_translation_api_context_factory = require("./translation-api-context-factory-BSRK6Z50.cjs");
const require_src = require("./src-Cx7UyT_c.cjs");
let react = require("react");
react = require_chunk.__toESM(react);
let _tanstack_react_query = require("@tanstack/react-query");
let react_jsx_runtime = require("react/jsx-runtime");
let lucide_react = require("lucide-react");
//#region ../../platform/api-client-core/src/parse-api-errors.ts
/**
* Framework-agnostic API error parsing utilities.
*
* Extracts structured field-level errors from API responses and formats
* them into human-readable messages. Works with ApiError from this package
* as well as any error object that has `status`, `data`, and optional `message`.
*/
/**
* Converts snake_case or camelCase field names to Title Case
*/
function formatFieldName(field) {
	return field.replace(/_/g, " ").replace(/([A-Z])/g, " $1").split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ").trim();
}
/**
* Type guard to check if an error looks like an API error
*/
function isApiLikeError(error) {
	if (!error || typeof error !== "object") return false;
	const err = error;
	return typeof err.status === "number" && "data" in err && (typeof err.message === "string" || err.message === void 0);
}
/**
* Extracts field-level errors from API error data
*/
function extractFieldErrors(data) {
	const errors = [];
	if (!data || typeof data !== "object") return errors;
	const errorObj = data;
	for (const [key, value] of Object.entries(errorObj)) if (Array.isArray(value) && value.length > 0) errors.push({
		field: formatFieldName(key),
		messages: value.map((item) => typeof item === "string" ? item : JSON.stringify(item))
	});
	else if (typeof value === "string" && value.length > 0) errors.push({
		field: formatFieldName(key),
		messages: [value]
	});
	else if (value && typeof value === "object" && !Array.isArray(value)) extractFieldErrors(value).forEach((nestedError) => {
		errors.push({
			field: `${formatFieldName(key)} → ${nestedError.field}`,
			messages: nestedError.messages
		});
	});
	return errors;
}
/**
* Formats field errors into a readable description string
*/
function formatErrorDescription(errors) {
	if (errors.length === 0) return "";
	if (errors.length === 1) {
		const err = errors[0];
		if (!err) return "";
		const message = err.messages[0] || "is invalid";
		return `${err.field} ${message}`;
	}
	if (errors.length <= 3) return errors.map((e) => `${e.field} ${e.messages[0] || "is invalid"}`).join("\n");
	const shown = errors.slice(0, 3).map((e) => `${e.field} ${e.messages[0] || "is invalid"}`).join("\n");
	const remaining = errors.length - 3;
	return `${shown}\n...and ${remaining} more ${remaining === 1 ? "error" : "errors"}`;
}
/**
* Parses an error and returns a human-readable description string.
*
* Handles:
* - API-like errors with structured field-level data
* - API-like errors with a top-level message
* - Standard Error instances
* - Falls back to the provided fallback string
*
* @param error - The error to parse (ApiError, Error, or unknown)
* @param fallback - Optional fallback description if error cannot be parsed
* @returns A human-readable error description, or undefined if nothing could be extracted
*/
function parseApiErrors(error, fallback) {
	if (isApiLikeError(error)) {
		if (error.data) {
			const fieldErrors = extractFieldErrors(error.data);
			if (fieldErrors.length > 0) return formatErrorDescription(fieldErrors);
		}
		if (error.message) return error.message;
	} else if (error instanceof Error) return error.message;
	return fallback;
}
//#endregion
//#region ../../contacts/core/src/contacts-api-context.ts
const ContactsApiContext = (0, react.createContext)(null);
const ContactsApiProvider = ContactsApiContext.Provider;
function useContactsDomainApi() {
	const api = (0, react.useContext)(ContactsApiContext);
	if (!api) throw new Error("useContactsDomainApi must be used within a ContactsApiProvider");
	return api;
}
function useContactsCrud() {
	return useContactsDomainApi().contacts;
}
function useNotesApi() {
	return useContactsDomainApi().notes;
}
function useTasksApi() {
	return useContactsDomainApi().tasks;
}
/** Returns GroupsApi if the provider supplies one, otherwise null. */
function useGroupsApi() {
	return useContactsDomainApi().groups ?? null;
}
//#endregion
//#region ../../contacts/core/src/translation-api-context.ts
const { Provider: ContactsProvider, useTranslation } = require_translation_api_context_factory.createTranslationContext("Contacts");
const ContactsTranslationProvider = ContactsProvider;
const useContactsTranslation = useTranslation;
//#endregion
//#region ../../contacts/core/src/parse-task-body.ts
/**
* Tasks store both a title (first line) and an optional body separated by a
* blank line. This is the canonical convention used by the task editor and
* by every consumer that displays tasks (contacts UI list, portal todo
* widget). Drift across consumers would mean some surfaces show "Title\n\n
* body details" verbatim while others split correctly — keep the delimiter
* convention in one place.
*/
function parseTaskBody(raw) {
	const split = raw.indexOf("\n\n");
	if (split >= 0) return {
		title: raw.slice(0, split),
		body: raw.slice(split + 2)
	};
	return {
		title: raw,
		body: ""
	};
}
//#endregion
//#region ../../contacts/core/src/query-keys.ts
const CONTACTS_QUERY_KEYS = {
	all: (prefix) => [prefix],
	list: (prefix) => [...CONTACTS_QUERY_KEYS.all(prefix), "list"],
	detail: (prefix, id) => [
		...CONTACTS_QUERY_KEYS.all(prefix),
		"detail",
		id
	]
};
const contactsKeys = {
	activities: (contactId) => [
		"portal-contacts",
		"activities",
		contactId
	],
	tasks: (contactId) => [
		"portal-contacts",
		"tasks",
		contactId
	],
	notes: (contactId) => [
		"portal-contacts",
		"notes",
		contactId
	],
	orders: (contactId) => [
		"rep-contacts",
		"orders",
		contactId
	],
	subscriptionOrders: (contactId) => [
		"rep-contacts",
		"subscription-orders",
		contactId
	],
	groups: () => ["portal-contacts", "groups"]
};
//#endregion
//#region ../../contacts/core/src/hooks/use-infinite-contacts.ts
function useInfiniteContacts(params) {
	const api = useContactsCrud();
	return (0, _tanstack_react_query.useInfiniteQuery)({
		queryKey: [...CONTACTS_QUERY_KEYS.list("contacts"), params],
		queryFn: ({ pageParam }) => api.listContacts({
			...params,
			page: pageParam
		}),
		getNextPageParam: (lastPage) => {
			const currentPage = lastPage.meta.current_page;
			if (currentPage == null) return void 0;
			if (lastPage.meta.next_cursor) return currentPage + 1;
			if (lastPage.meta.total_pages != null && currentPage < lastPage.meta.total_pages) return currentPage + 1;
		},
		initialPageParam: 1
	});
}
//#endregion
//#region ../../contacts/core/src/iso-date.ts
/**
* Format a date as YYYY-MM-DD in the renderer's local timezone, optionally
* shifted by `offsetDays`. Use this for due-date inputs where "today" must
* resolve to the user's local calendar date — never `toISOString().slice(0, 10)`,
* which returns the UTC date and silently rolls over a day for users east/west
* of UTC at the wrong hours.
*/
function isoDate(offsetDays, now = /* @__PURE__ */ new Date()) {
	const d = new Date(now);
	d.setDate(d.getDate() + offsetDays);
	return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
}
/**
* Return a Date pinned to local midnight of the calendar day represented by
* `input`. Use this whenever you need to compare two due dates by calendar day
* (overdue / today / tomorrow / future).
*
* Why not just `new Date(input)`?  `new Date("YYYY-MM-DD")` parses the string
* as UTC midnight, which lands on the *previous* calendar day in any UTC−
* timezone — a task due "May 1" then reads as April 30 for a user in the
* Americas, classifying it as "Overdue" all day. Parse the date components
* directly so the calendar-day intent of the string is preserved.
*/
function startOfLocalDay(input) {
	if (typeof input === "string") {
		const match = /^(\d{4})-(\d{2})-(\d{2})/.exec(input);
		if (match?.[1] && match[2] && match[3]) return new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
	}
	const d = typeof input === "string" ? new Date(input) : input;
	return new Date(d.getFullYear(), d.getMonth(), d.getDate());
}
//#endregion
//#region ../../contacts/ui/src/portal/hooks/contacts/use-create-contact-task.ts
function useCreateContactTask(contactId, options) {
	const queryClient = (0, _tanstack_react_query.useQueryClient)();
	const api = useTasksApi();
	return (0, _tanstack_react_query.useMutation)({
		mutationFn: (input) => api.createTask(contactId, input),
		onSuccess: () => {
			require_src.fluidToast({
				title: "Task created",
				type: "success"
			});
			queryClient.invalidateQueries({ queryKey: contactsKeys.tasks(contactId) });
			options?.onSuccess?.();
		},
		onError: (error) => {
			const description = parseApiErrors(error);
			require_src.fluidToast({
				title: "Failed to create task",
				type: "error",
				...description ? { description } : {}
			});
		}
	});
}
//#endregion
//#region ../../contacts/ui/src/portal/components/tasks/task-composer-form.tsx
const QUICK_DATES = [
	{
		key: "today",
		offsetDays: 0
	},
	{
		key: "tomorrow",
		offsetDays: 1
	},
	{
		key: "next_week",
		offsetDays: 7
	}
];
function TaskComposerForm({ contactId, onDone, onClose, autoFocus = true }) {
	const { t } = useContactsTranslation();
	const [body, setBody] = (0, react.useState)("");
	const [dueDate, setDueDate] = (0, react.useState)(null);
	const inputRef = (0, react.useRef)(null);
	const createTask = useCreateContactTask(contactId, { onSuccess: () => {
		setBody("");
		setDueDate(null);
		onDone?.();
	} });
	(0, react.useEffect)(() => {
		if (autoFocus) inputRef.current?.focus();
	}, [autoFocus]);
	const quickDates = (0, react.useMemo)(() => QUICK_DATES.map((q) => ({
		key: q.key,
		iso: isoDate(q.offsetDays)
	})), []);
	const quickLabels = {
		today: t("quick_today"),
		tomorrow: t("quick_tomorrow"),
		next_week: t("quick_next_week")
	};
	const submit = () => {
		const trimmed = body.trim();
		if (!trimmed || createTask.isPending) return;
		createTask.mutate({
			body: trimmed,
			due_at: dueDate
		});
	};
	const canSubmit = body.trim().length > 0 && !createTask.isPending;
	return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
		className: "border-border/50 focus-within:border-foreground/30 rounded-xl border p-3 transition-colors",
		children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
			className: "flex items-start gap-3",
			children: [
				/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
					className: "border-muted-foreground/50 mt-1.5 size-5 shrink-0 rounded-full border-2",
					"aria-hidden": "true"
				}),
				/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
					ref: inputRef,
					value: body,
					onChange: (e) => setBody(e.target.value),
					onKeyDown: (e) => {
						if (e.key === "Enter" && !e.shiftKey) {
							e.preventDefault();
							submit();
						} else if (e.key === "Escape" && onClose) {
							e.preventDefault();
							onClose();
						}
					},
					placeholder: t("task_describe_placeholder"),
					"aria-label": t("task_description_aria"),
					className: "placeholder:text-muted-foreground/80 text-foreground flex-1 border-0 bg-transparent text-sm font-medium outline-none pointer-coarse:text-base"
				}),
				onClose && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
					type: "button",
					onClick: onClose,
					"aria-label": t("task_discard_aria"),
					className: "text-muted-foreground hover:bg-muted hover:text-foreground -mt-0.5 -mr-0.5 flex size-7 shrink-0 items-center justify-center rounded-md transition-colors",
					children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, { className: "size-4" })
				})
			]
		}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
			className: "mt-3 flex flex-wrap items-center gap-1.5 pl-(--row-indent) [--row-indent:2rem]",
			children: [quickDates.map((q) => {
				const isActive = dueDate === q.iso;
				return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
					type: "button",
					onClick: () => setDueDate(isActive ? null : q.iso),
					"aria-pressed": isActive,
					className: require_src.cn("shrink-0 rounded-full px-3 py-1 text-xs font-medium transition-colors", isActive ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:bg-muted/70"),
					children: quickLabels[q.key]
				}, q.key);
			}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
				type: "button",
				onClick: submit,
				disabled: !canSubmit,
				className: "bg-primary text-primary-foreground hover:bg-primary/90 disabled:bg-muted disabled:text-muted-foreground mt-1 -ml-(--row-indent) inline-flex h-10 w-[calc(100%+var(--row-indent))] items-center justify-center rounded-lg text-sm font-semibold transition-colors disabled:cursor-not-allowed sm:mt-0 sm:ml-auto sm:h-auto sm:w-auto sm:shrink-0 sm:gap-1.5 sm:rounded-full sm:px-4 sm:py-1.5 sm:text-xs",
				children: createTask.isPending ? t("adding") : t("add_task")
			})]
		})]
	});
}
//#endregion
Object.defineProperty(exports, "CONTACTS_QUERY_KEYS", {
	enumerable: true,
	get: function() {
		return CONTACTS_QUERY_KEYS;
	}
});
Object.defineProperty(exports, "ContactsApiProvider", {
	enumerable: true,
	get: function() {
		return ContactsApiProvider;
	}
});
Object.defineProperty(exports, "ContactsTranslationProvider", {
	enumerable: true,
	get: function() {
		return ContactsTranslationProvider;
	}
});
Object.defineProperty(exports, "TaskComposerForm", {
	enumerable: true,
	get: function() {
		return TaskComposerForm;
	}
});
Object.defineProperty(exports, "contactsKeys", {
	enumerable: true,
	get: function() {
		return contactsKeys;
	}
});
Object.defineProperty(exports, "parseApiErrors", {
	enumerable: true,
	get: function() {
		return parseApiErrors;
	}
});
Object.defineProperty(exports, "parseTaskBody", {
	enumerable: true,
	get: function() {
		return parseTaskBody;
	}
});
Object.defineProperty(exports, "startOfLocalDay", {
	enumerable: true,
	get: function() {
		return startOfLocalDay;
	}
});
Object.defineProperty(exports, "useContactsCrud", {
	enumerable: true,
	get: function() {
		return useContactsCrud;
	}
});
Object.defineProperty(exports, "useContactsTranslation", {
	enumerable: true,
	get: function() {
		return useContactsTranslation;
	}
});
Object.defineProperty(exports, "useGroupsApi", {
	enumerable: true,
	get: function() {
		return useGroupsApi;
	}
});
Object.defineProperty(exports, "useInfiniteContacts", {
	enumerable: true,
	get: function() {
		return useInfiniteContacts;
	}
});
Object.defineProperty(exports, "useNotesApi", {
	enumerable: true,
	get: function() {
		return useNotesApi;
	}
});
Object.defineProperty(exports, "useTasksApi", {
	enumerable: true,
	get: function() {
		return useTasksApi;
	}
});

//# sourceMappingURL=task-composer-form-BEZGTBBZ.cjs.map