"use client"; import { useMemo, type ReactNode } from "react"; import * as Option from "effect/Option"; import * as Schema from "effect/Schema"; import type { ProviderSettingsFormAnnotation, ProviderSettingsFormControl, ProviderSettingsFormOption, ProviderSettingsFormSchemaAnnotation, } from "@t3tools/contracts"; import { cn } from "../../lib/utils"; import { DraftInput } from "../ui/draft-input"; import { Input } from "../ui/input"; import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "../ui/select"; import { Switch } from "../ui/switch"; import { Textarea } from "../ui/textarea"; import type { ProviderClientDefinition } from "./providerDriverMeta"; import { SettingsRow } from "./settingsLayout"; export interface ProviderSettingsFieldModel { readonly key: string; readonly control: ProviderSettingsFormControl; readonly label: string; readonly description?: string | undefined; readonly placeholder?: string | undefined; readonly clearWhenEmpty: "omit" | "persist"; readonly defaultBooleanValue?: boolean | undefined; /** Choices for a `select` control. The first entry is the default. */ readonly options?: ReadonlyArray | undefined; } function titleizeFieldKey(key: string): string { return key .replace(/([a-z0-9])([A-Z])/g, "$1 $2") .replace(/[-_]+/g, " ") .replace(/^./, (char) => char.toUpperCase()); } function readFieldAnnotations( fieldSchema: ProviderClientDefinition["settingsSchema"]["fields"][string], ) { return Schema.resolveAnnotationsKey(fieldSchema) ?? Schema.resolveAnnotations(fieldSchema); } function readFieldAnnotationString( fieldSchema: ProviderClientDefinition["settingsSchema"]["fields"][string], key: "title" | "description", ): string | undefined { const annotations = readFieldAnnotations(fieldSchema); const value = annotations?.[key]; return typeof value === "string" ? value : undefined; } function readProviderSettingsFormAnnotation( fieldSchema: ProviderClientDefinition["settingsSchema"]["fields"][string], ): ProviderSettingsFormAnnotation { const annotation = readFieldAnnotations(fieldSchema)?.providerSettingsForm; return annotation ?? {}; } function readProviderSettingsFormSchemaAnnotation( definition: ProviderClientDefinition, ): ProviderSettingsFormSchemaAnnotation { return Schema.resolveAnnotations(definition.settingsSchema)?.providerSettingsFormSchema ?? {}; } function readFieldBooleanDefault( fieldSchema: ProviderClientDefinition["settingsSchema"]["fields"][string], ): boolean | undefined { const decodeDefault = Schema.decodeUnknownOption(fieldSchema as Schema.Decoder); const decoded = decodeDefault(undefined); return Option.isSome(decoded) && typeof decoded.value === "boolean" ? decoded.value : undefined; } export function deriveProviderSettingsFields( definition: ProviderClientDefinition, ): ReadonlyArray { const schemaAnnotation = readProviderSettingsFormSchemaAnnotation(definition); const orderedKeys = new Map( (schemaAnnotation.order ?? []).map((key, index) => [key, index] as const), ); const orderFallbackOffset = orderedKeys.size; return Object.keys(definition.settingsSchema.fields) .map((key, index) => ({ key, index })) .toSorted((left, right) => { return ( (orderedKeys.get(left.key) ?? orderFallbackOffset + left.index) - (orderedKeys.get(right.key) ?? orderFallbackOffset + right.index) ); }) .flatMap(({ key }) => { const fieldSchema = definition.settingsSchema.fields[key]!; const formAnnotation = readProviderSettingsFormAnnotation(fieldSchema); if (formAnnotation.hidden) return []; const annotatedTitle = readFieldAnnotationString(fieldSchema, "title"); const annotatedDescription = readFieldAnnotationString(fieldSchema, "description"); return [ { key, control: formAnnotation.control ?? "text", label: annotatedTitle ?? titleizeFieldKey(key), ...(annotatedDescription !== undefined ? { description: annotatedDescription } : {}), ...(formAnnotation.placeholder !== undefined ? { placeholder: formAnnotation.placeholder } : {}), clearWhenEmpty: formAnnotation.clearWhenEmpty ?? "omit", ...(formAnnotation.control === "switch" ? { defaultBooleanValue: readFieldBooleanDefault(fieldSchema) } : {}), ...(formAnnotation.control === "select" && formAnnotation.options ? { options: formAnnotation.options } : {}), } satisfies ProviderSettingsFieldModel, ]; }); } export function readProviderConfigString(config: unknown, key: string): string { if (config === null || typeof config !== "object") return ""; const value = (config as Record)[key]; return typeof value === "string" ? value : ""; } export function readProviderConfigBoolean( config: unknown, key: string, defaultValue = false, ): boolean { if (config === null || typeof config !== "object") return defaultValue; const value = (config as Record)[key]; return typeof value === "boolean" ? value : defaultValue; } export function nextProviderConfigWithFieldValue( config: unknown, field: ProviderSettingsFieldModel, value: string | boolean, ): Record | undefined { const base: Record = config !== null && typeof config === "object" ? { ...(config as Record) } : {}; if (typeof value === "boolean") { const emptyBooleanValue = field.defaultBooleanValue ?? false; if (field.clearWhenEmpty === "omit" && value === emptyBooleanValue) { delete base[field.key]; } else { base[field.key] = value; } return Object.keys(base).length > 0 ? base : undefined; } const trimmed = value.trim(); if (field.clearWhenEmpty === "omit" && trimmed.length === 0) { delete base[field.key]; } else { base[field.key] = value; } return Object.keys(base).length > 0 ? base : undefined; } interface ProviderSettingsFormProps { readonly definition: ProviderClientDefinition; readonly value: unknown; readonly idPrefix: string; /** * `card` stacks label over control, `dialog` is the compact wizard layout, * and `settings` renders the shared settings row treatment. */ readonly variant: "card" | "dialog" | "settings"; readonly onChange: (nextConfig: Record | undefined) => void; } /** Stores the default choice as an omitted key so unchanged configs stay small. */ function ProviderSettingsSelect({ field, value, inputId, size, className, onChange, }: { readonly field: ProviderSettingsFieldModel; readonly value: unknown; readonly inputId: string; readonly size: "sm" | "xs"; readonly className?: string | undefined; readonly onChange: ProviderSettingsFormProps["onChange"]; }) { const options = field.options ?? []; const fallback = options[0]?.value ?? ""; const current = readProviderConfigString(value, field.key) || fallback; const label = options.find((option) => option.value === current)?.label ?? current; return ( ); } function FieldFrame(props: { readonly variant: ProviderSettingsFormProps["variant"]; readonly children: ReactNode; }) { if (props.variant === "card") { return
{props.children}
; } return
{props.children}
; } interface ProviderSettingsFieldRowProps { readonly field: ProviderSettingsFieldModel; readonly value: unknown; readonly idPrefix: string; readonly variant: ProviderSettingsFormProps["variant"]; readonly onChange: ProviderSettingsFormProps["onChange"]; } function ProviderSettingsFieldRow({ field, value, idPrefix, variant, onChange, }: ProviderSettingsFieldRowProps) { const inputId = `${idPrefix}-${field.key}`; const descriptionClassName = variant === "dialog" ? "text-[11px] text-muted-foreground" : "mt-1 block text-xs text-muted-foreground"; const label = {field.label}; const description = field.description ? ( {field.description} ) : null; if (variant === "settings") { const descriptionId = field.description ? `${inputId}-description` : undefined; const control = field.control === "switch" ? ( onChange(nextProviderConfigWithFieldValue(value, field, Boolean(checked))) } aria-label={field.label} aria-describedby={descriptionId} /> ) : field.control === "select" ? ( ) : field.control === "textarea" ? (