"use client";

import { useId, useEffect, useRef, useState } from "react";
import { useRouter } from "next/navigation";
import { useForm, Controller } from "react-hook-form";
import { useQuery } from "@tanstack/react-query";
import { Icon } from "@iconify/react";
import { useTranslations } from "next-intl";
import { toast } from "sonner";
import type { SchemaDefinition, FieldDefinition } from "@/lib/api";
import { checkSlug, getBaseUrl, fetchAssets } from "@/lib/api";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Label } from "@/components/ui/label";
import { Checkbox } from "@/components/ui/checkbox";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog";
import { ReferenceArrayField } from "./ReferenceArrayField";
import { ReferenceField } from "./ReferenceField";
import { ReferenceOrInlineField } from "./ReferenceOrInlineField";
import { MarkdownEditor } from "./MarkdownEditor";
import { CodeField, isCodeFieldLanguage } from "./CodeField";
import { CollapsibleSection } from "./ui/collapsible";

/** Wrapper class for each form field to separate them visually. */
const FIELD_BLOCK_CLASS =
  "rounded-xl border border-gray-200 bg-white p-4 shadow-[0_1px_2px_rgba(0,0,0,0.04)]";

type Props = {
  collection: string;
  schema: SchemaDefinition;
  initialValues?: Record<string, unknown>;
  slug?: string;
  locale?: string;
  onSuccess?: () => void;
};

/** Slug prefix for new entries: collection name with underscores → hyphens, plus trailing hyphen (e.g. calendar_item → calendar-item-). */
export function slugPrefixForCollection(collection: string): string {
  return collection.replace(/_/g, "-") + "-";
}

/** _slug field with format and duplicate check via API. When slugPrefix is set, shows prefix as fixed text + input for the rest. */
function SlugField({
  collection,
  currentSlug,
  slugPrefix,
  locale,
  register,
  setValue,
  setError,
  clearErrors,
  error,
  slugValue,
}: {
  collection: string;
  currentSlug?: string;
  slugPrefix?: string;
  locale?: string;
  register: ReturnType<typeof useForm>["register"];
  setValue: ReturnType<typeof useForm>["setValue"];
  setError: ReturnType<typeof useForm>["setError"];
  clearErrors: ReturnType<typeof useForm>["clearErrors"];
  error: ReturnType<typeof useForm>["formState"]["errors"]["_slug"];
  slugValue: string | undefined;
}) {
  const t = useTranslations("ContentForm");
  const registered = register("_slug", {
    required: t("slugRequired"),
    validate:
      slugPrefix && !currentSlug
        ? (v) => {
            const val = (v ?? "").trim();
            if (!val) return true;
            const normalized = val.toLowerCase();
            if (!normalized.startsWith(slugPrefix.toLowerCase())) {
              return t("slugMustStartWith", { prefix: slugPrefix });
            }
            return true;
          }
        : undefined,
  });

  const fullSlug = (slugValue ?? "").trim();
  const suffix =
    slugPrefix && fullSlug.toLowerCase().startsWith(slugPrefix.toLowerCase())
      ? fullSlug.slice(slugPrefix.length)
      : slugPrefix
        ? fullSlug
        : fullSlug;

  const handleSuffixChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const v = e.target.value;
    const normalized = slugPrefix
      ? v
          .toLowerCase()
          .replace(/\s+/g, "-")
          .replace(/[^a-z0-9-]/g, "")
      : v;
    setValue("_slug", slugPrefix ? slugPrefix + normalized : v, {
      shouldValidate: true,
    });
  };

  const handleBlur = async (e: React.FocusEvent<HTMLInputElement>) => {
    const value = slugPrefix
      ? slugPrefix + (e.target.value ?? "")
      : (e.target.value ?? "");
    const full = value.trim();
    if (!full) return;
    if (slugPrefix && !full.toLowerCase().startsWith(slugPrefix.toLowerCase()))
      return;
    try {
      const res = await checkSlug(collection, full, {
        exclude: currentSlug ?? undefined,
        _locale: locale,
      });
      if (!res.valid && res.error) {
        setError("_slug", { type: "server", message: res.error });
        return;
      }
      if (!res.available) {
        setError("_slug", { type: "server", message: t("slugInUse") });
        return;
      }
      clearErrors("_slug");
    } catch {
      // Network error etc.
    }
  };

  const suffixPlaceholder = slugPrefix
    ? t("slugSuffixPlaceholder")
    : t("slugPlaceholder");

  if (slugPrefix) {
    return (
      <div>
        <Label className="mb-1 block font-medium">
          _slug <span className="text-red-500">*</span>
        </Label>
        <div className="flex h-9 items-center rounded-md border border-input bg-background font-mono text-sm shadow-xs ring-offset-background focus-within:ring-[3px] focus-within:ring-ring/50">
          <span className="shrink-0 border-r border-input bg-muted/50 px-3 py-2 text-muted-foreground">
            {slugPrefix}
          </span>
          <input
            type="text"
            value={suffix}
            onChange={handleSuffixChange}
            onBlur={handleBlur}
            placeholder={suffixPlaceholder}
            className="h-full flex-1 min-w-0 border-0 bg-transparent px-3 py-1 text-base outline-none placeholder:text-muted-foreground md:text-sm"
            autoComplete="off"
            aria-label={t("slugSuffixAriaLabel")}
          />
        </div>
        <p className="mt-1 text-xs text-gray-500">{t("slugHint")}</p>
        {error ? (
          <p className="mt-1 text-sm text-red-600">{String(error.message)}</p>
        ) : null}
      </div>
    );
  }

  return (
    <div>
      <Label className="mb-1 block font-medium">
        _slug <span className="text-red-500">*</span>
      </Label>
      <Input
        type="text"
        {...registered}
        onBlur={handleBlur}
        placeholder={t("slugPlaceholder")}
        className="font-mono"
        autoComplete="off"
      />
      <p className="mt-1 text-xs text-gray-500">{t("slugHint")}</p>
      {error ? (
        <p className="mt-1 text-sm text-red-600">{String(error.message)}</p>
      ) : null}
    </div>
  );
}

/** True only when the schema explicitly requests image/asset URL UI via widget (no heuristics). */
function isImageUrlField(def: FieldDefinition): boolean {
  return def.widget === "imageUrl" || def.widget === "assetUrl";
}

function assetPreviewUrl(value: string | undefined): string | null {
  if (!value || typeof value !== "string") return null;
  const v = value.trim();
  if (!v) return null;
  if (v.startsWith("http://") || v.startsWith("https://")) return v;
  if (v.startsWith("/api/assets/")) return getBaseUrl() + v;
  if (/\.(jpg|jpeg|png|webp|gif|avif|svg)(\?|$)/i.test(v))
    return getBaseUrl() + (v.startsWith("/") ? v : "/" + v);
  return null;
}

/** Asset picker content: grid of assets, click to select URL. Use inside DialogContent. */
function AssetPickerContent({ onSelect }: { onSelect: (url: string) => void }) {
  const t = useTranslations("ContentForm");
  const { data, isLoading, error } = useQuery({
    queryKey: ["assets", "all"],
    queryFn: () => fetchAssets(),
  });
  const assets = data?.assets ?? [];

  return (
    <div className="min-h-0 flex-1 overflow-auto">
      {isLoading && (
        <p className="text-muted-foreground text-sm">{t("loadingAssets")}</p>
      )}
      {error && (
        <p className="text-destructive text-sm">
          {String((error as Error).message)}
        </p>
      )}
      {!isLoading && !error && assets.length === 0 && (
        <p className="text-muted-foreground text-sm">{t("noAssets")}</p>
      )}
      {!isLoading && assets.length > 0 && (
        <div className="grid grid-cols-3 sm:grid-cols-4 gap-3">
          {assets.map((asset) => {
            const src = getBaseUrl() + asset.url;
            return (
              <button
                key={asset.url}
                type="button"
                onClick={() => onSelect(asset.url)}
                className="flex flex-col items-center rounded-lg border border-input bg-muted/30 p-2 hover:bg-muted/60 focus:ring-2 focus:ring-ring focus:ring-offset-2"
              >
                <span className="aspect-square w-full overflow-hidden rounded border bg-background flex items-center justify-center">
                  {/\.(svg)$/i.test(asset.filename) ? (
                    <img
                      src={src}
                      alt=""
                      className="max-h-full max-w-full object-contain"
                    />
                  ) : (
                    <img
                      src={src}
                      alt=""
                      className="h-full w-full object-cover"
                    />
                  )}
                </span>
                <span className="mt-1 truncate w-full text-center text-xs text-muted-foreground">
                  {asset.filename}
                </span>
              </button>
            );
          })}
        </div>
      )}
    </div>
  );
}

/** String field for image/asset URL: preview + input + asset picker button. */
function ImageUrlField({
  value,
  onChange,
  label,
  fieldError,
  required,
  readonly,
}: {
  value: string;
  onChange: (v: string) => void;
  label: React.ReactNode;
  fieldError: unknown;
  required: boolean;
  readonly: boolean;
}) {
  const t = useTranslations("ContentForm");
  const [pickerOpen, setPickerOpen] = useState(false);
  const previewUrl = assetPreviewUrl(value);

  return (
    <div>
      {label}
      {previewUrl && (
        <div className="mt-2 rounded-lg border border-input overflow-hidden bg-muted/20 inline-block max-w-[200px]">
          <img
            src={previewUrl}
            alt=""
            className="max-h-40 w-full object-contain"
          />
        </div>
      )}
      <div className="mt-2 flex flex-wrap items-center gap-2">
        <Input
          type="text"
          value={value}
          onChange={(e) => onChange(e.target.value)}
          placeholder="/api/assets/…"
          readOnly={readonly}
          className="flex-1 min-w-[200px] font-mono text-sm"
        />
        {!readonly && (
          <Dialog open={pickerOpen} onOpenChange={setPickerOpen}>
            <DialogTrigger asChild>
              <Button type="button" variant="outline" size="sm">
                <Icon icon="mdi:image-multiple" className="mr-1 size-4" />
                {t("pickFromAssets")}
              </Button>
            </DialogTrigger>
            <DialogContent className="max-h-[85vh] max-w-2xl overflow-hidden flex flex-col">
              <DialogHeader>
                <DialogTitle>{t("pickAsset")}</DialogTitle>
              </DialogHeader>
              <AssetPickerContent
                onSelect={(url) => {
                  onChange(url);
                  setPickerOpen(false);
                }}
              />
            </DialogContent>
          </Dialog>
        )}
      </div>
      {fieldError ? (
        <p className="mt-1 text-sm text-red-600">
          {String((fieldError as { message?: string })?.message)}
        </p>
      ) : null}
    </div>
  );
}

/** Builds form initial values from API response: references → slug strings, objects recursively. */
function buildDefaultValues(
  schema: SchemaDefinition,
  initialValues?: Record<string, unknown>,
): Record<string, unknown> {
  const out: Record<string, unknown> = {};
  const fields = schema.fields ?? {};
  if (initialValues) {
    for (const [key, value] of Object.entries(initialValues)) {
      const def = fields[key] as FieldDefinition | undefined;
      if (value == null) {
        out[key] = value;
        continue;
      }
      if (def?.type === "multiSelect" && !Array.isArray(value)) {
        out[key] = [];
        continue;
      }
      if (def?.type === "reference") {
        out[key] =
          typeof value === "object" && value !== null && "_slug" in value
            ? String((value as { _slug?: string })._slug ?? "")
            : typeof value === "string"
              ? value
              : "";
        continue;
      }
      if (def?.type === "array" && Array.isArray(value)) {
        const items = def.items as FieldDefinition | undefined;
        const isRefArray =
          items?.type === "reference" || items?.type === "referenceOrInline";
        if (isRefArray) {
          out[key] = value.map((v) =>
            typeof v === "object" && v !== null && "_slug" in v
              ? (v as { _slug: string })._slug
              : String(v),
          );
          continue;
        }
      }
      if (
        def?.type === "object" &&
        def.fields &&
        typeof value === "object" &&
        value !== null &&
        !Array.isArray(value)
      ) {
        out[key] = buildDefaultValues(
          { name: key, fields: def.fields as Record<string, FieldDefinition> },
          value as Record<string, unknown>,
        );
        continue;
      }
      if (
        def?.type === "object" &&
        def.additionalProperties &&
        typeof value === "object" &&
        value !== null &&
        !Array.isArray(value)
      ) {
        out[key] = value;
        continue;
      }
      if (
        typeof value === "object" &&
        value !== null &&
        "_slug" in value &&
        (def?.type === "reference" || !def)
      ) {
        out[key] = String((value as { _slug?: string })._slug ?? "");
        continue;
      }
      out[key] = value;
    }
  }
  for (const key of Object.keys(fields)) {
    if (!(key in out)) {
      const def = fields[key] as FieldDefinition | undefined;
      if (def?.default !== undefined && def.default !== null) {
        if (
          def?.type === "object" &&
          def.fields &&
          typeof def.default === "object" &&
          def.default !== null &&
          !Array.isArray(def.default)
        ) {
          out[key] = buildDefaultValues(
            {
              name: key,
              fields: def.fields as Record<string, FieldDefinition>,
            },
            def.default as Record<string, unknown>,
          );
        } else {
          out[key] = def.default;
        }
      } else if (def?.type === "boolean") out[key] = false;
      else if (def?.type === "array") {
        const items = (def as FieldDefinition).items as
          | FieldDefinition
          | undefined;
        const isRefArray =
          items?.type === "reference" || items?.type === "referenceOrInline";
        const isPrimitiveArray =
          items?.type === "string" ||
          items?.type === "number" ||
          items?.type === "integer";
        out[key] = isRefArray || isPrimitiveArray ? [] : "";
      } else if (def?.type === "object" && def.fields)
        out[key] = buildDefaultValues(
          { name: key, fields: def.fields as Record<string, FieldDefinition> },
          {},
        );
      else if (def?.type === "object" && def.additionalProperties)
        out[key] = {};
      else out[key] = "";
    }
  }
  return out;
}

export function ContentForm({
  collection,
  schema,
  initialValues,
  slug,
  locale,
  onSuccess,
}: Props) {
  const t = useTranslations("ContentForm");
  const router = useRouter();
  const isEdit = !!slug;
  const defaultValues = buildDefaultValues(schema, initialValues);
  if (!isEdit && !initialValues?._slug && defaultValues._slug === undefined) {
    defaultValues._slug = slugPrefixForCollection(collection);
  }
  if (!isEdit && defaultValues._status === undefined) {
    defaultValues._status = "draft";
  }
  // Backend treats missing _status as published; show same in UI when editing existing entry.
  if (isEdit && defaultValues._status === undefined) {
    defaultValues._status = "published";
  }
  const {
    register,
    handleSubmit,
    setValue,
    setError,
    clearErrors,
    control,
    watch,
    formState: { errors, isSubmitting, isDirty },
  } = useForm({ defaultValues });

  // Unsaved changes: warn when leaving (browser or in-app)
  useEffect(() => {
    const onBeforeUnload = (e: BeforeUnloadEvent) => {
      if (isDirty) e.preventDefault();
    };
    window.addEventListener("beforeunload", onBeforeUnload);
    return () => window.removeEventListener("beforeunload", onBeforeUnload);
  }, [isDirty]);

  const onSubmit = async (data: Record<string, unknown>) => {
    const payload: Record<string, unknown> = {};
    for (const [key, value] of Object.entries(data)) {
      const def = schema.fields?.[key];
      const items = def?.type === "array" ? def.items : undefined;
      const isRefArray =
        items?.type === "reference" || items?.type === "referenceOrInline";
      if (def?.type === "array" && isRefArray) {
        payload[key] = Array.isArray(value)
          ? value
          : typeof value === "string"
            ? value
                .split(/[\n,]+/)
                .map((s) => s.trim())
                .filter(Boolean)
            : [];
      } else {
        payload[key] = value;
      }
    }
    if (isEdit) {
      delete payload._slug;
    }
    try {
      delete payload._contentSource;
      const params = locale ? { _locale: locale } : {};
      if (isEdit) {
        const { updateEntry } = await import("@/lib/api");
        await updateEntry(collection, slug!, payload, params);
        clearErrors("root");
        toast.success(t("savedSuccessfully"));
        onSuccess?.();
      } else {
        const { createEntry } = await import("@/lib/api");
        await createEntry(collection, payload, params);
        onSuccess?.();
        const newSlug = payload._slug as string | undefined;
        const q = locale ? `?_locale=${locale}` : "";
        if (newSlug) {
          router.push(
            `/content/${collection}/${encodeURIComponent(newSlug)}${q}`,
          );
        } else {
          router.push(`/content/${collection}${q}`);
        }
      }
    } catch (err) {
      const apiErrors = (err as Error & { apiErrors?: string[] }).apiErrors;
      if (Array.isArray(apiErrors) && apiErrors.length > 0) {
        const fieldErrRe = /^Field '([^']+)': (.+)$/;
        for (const line of apiErrors) {
          const m = line.match(fieldErrRe);
          if (m)
            setError(m[1] as Parameters<typeof setError>[0], {
              type: "server",
              message: m[2],
            });
        }
        const msg = apiErrors.join(" ");
        setError("root", { message: msg });
        toast.error(msg);
      } else {
        const msg = err instanceof Error ? err.message : t("errorSaving");
        setError("root", { message: msg });
        toast.error(msg);
      }
    }
  };

  const onSubmitRef = useRef(onSubmit);
  onSubmitRef.current = onSubmit;
  useEffect(() => {
    const onKeyDown = (e: KeyboardEvent) => {
      if ((e.metaKey || e.ctrlKey) && e.key === "s") {
        e.preventDefault();
        if (isDirty) handleSubmit((data) => onSubmitRef.current(data))();
      }
    };
    window.addEventListener("keydown", onKeyDown);
    return () => window.removeEventListener("keydown", onKeyDown);
  }, [isDirty, handleSubmit]);

  const fields = schema.fields ?? {};
  const order = schema.fieldOrder;
  const orderedFieldEntries: [string, FieldDefinition][] = order?.length
    ? [
        ...order.filter((n) => n in fields).map((n) => [n, fields[n] as FieldDefinition] as [string, FieldDefinition]),
        ...Object.keys(fields)
          .filter((n) => !order.includes(n))
          .map((n) => [n, fields[n] as FieldDefinition] as [string, FieldDefinition]),
      ]
    : (Object.entries(fields) as [string, FieldDefinition][]);

  const slugParam = locale ? `?_locale=${locale}` : "";
  const showSlugField = !isEdit && !(fields._slug != null);

  type FormItem =
    | { kind: "object"; name: string; def: FieldDefinition }
    | { kind: "section"; title: string; entries: [string, FieldDefinition][] };
  const hasSection = Object.entries(fields).some(
    ([, def]) =>
      def &&
      (def as FieldDefinition).type !== "object" &&
      (def as FieldDefinition).section != null &&
      (def as FieldDefinition).section !== "",
  );
  const formItems: FormItem[] = [];
  if (hasSection) {
    let currentTitle: string | null = null;
    let currentEntries: [string, FieldDefinition][] = [];
    const flush = () => {
      if (currentEntries.length > 0) {
        formItems.push({
          kind: "section",
          title: currentTitle ?? "Details",
          entries: currentEntries,
        });
        currentEntries = [];
      }
      currentTitle = null;
    };
    for (const [name, def] of orderedFieldEntries) {
      const fd = def as FieldDefinition;
      if (fd.type === "object" && fd.fields) {
        flush();
        formItems.push({ kind: "object", name, def: fd });
      } else {
        const title = fd.section ?? "Details";
        if (currentTitle !== null && currentTitle !== title) {
          flush();
        }
        currentTitle = title;
        currentEntries.push([name, fd]);
      }
    }
    flush();
  }

  const onInvalid = (errs: Record<string, unknown>) => {
    const firstKey = Object.keys(errs).find((k) => k !== "root" && errs[k]);
    if (firstKey) {
      const el = document.querySelector(`[name="${firstKey}"], [data-field="${firstKey}"]`);
      if (el instanceof HTMLElement) el.scrollIntoView({ behavior: "smooth", block: "center" });
    }
    toast.error(t("validationErrors"));
  };

  return (
    <form onSubmit={handleSubmit(onSubmit, onInvalid)} className="max-w-2xl space-y-6">
      {errors.root && (
        <div
          className="rounded bg-red-50 p-3 text-sm text-red-700"
          role="alert"
        >
          {errors.root.message}
        </div>
      )}
      {showSlugField && (
        <div data-field="_slug">
          <SlugField
            collection={collection}
            currentSlug={slug}
            slugPrefix={!isEdit ? slugPrefixForCollection(collection) : undefined}
            locale={locale}
            register={register}
            setValue={setValue}
            setError={setError}
            clearErrors={clearErrors}
            error={errors._slug}
            slugValue={watch("_slug") as string | undefined}
          />
        </div>
      )}
      <div data-field="_status">
        <Label className="mb-1 block font-medium">{t("status")}</Label>
        <Controller
          name="_status"
          control={control!}
          render={({ field }) => {
            const value = (field.value as string) ?? "published";
            return (
              <div
                className="flex flex-wrap gap-2"
                role="group"
                aria-label={t("status")}
              >
                <Button
                  type="button"
                  variant={value === "draft" ? "destructive" : "outline"}
                  size="sm"
                  onClick={() => field.onChange("draft")}
                  className="min-w-[100px]"
                >
                  {t("statusDraft")}
                </Button>
                <Button
                  type="button"
                  variant="outline"
                  size="sm"
                  onClick={() => field.onChange("published")}
                  className={`min-w-[100px] ${value === "published" ? "border-2 border-success-400 bg-success-200 text-success-800 hover:bg-success-300 hover:border-success-500 hover:text-success-900" : ""}`}
                >
                  {t("statusPublished")}
                </Button>
              </div>
            );
          }}
        />
        <p className="mt-0.5 text-xs text-muted-foreground">
          {t("statusHint")}
        </p>
      </div>
      {hasSection
        ? formItems.map((item, sectionIndex) =>
            item.kind === "object" ? (
              <ObjectFieldSet
                key={item.name}
                name={item.name}
                def={item.def}
                register={register}
                control={control}
                errors={errors}
                isEdit={isEdit}
                locale={locale}
              />
            ) : (
              <CollapsibleSection
                key={`section-${sectionIndex}-${item.title}`}
                title={item.title}
                defaultOpen={true}
                contentClassName="space-y-4"
              >
                {item.entries.map(([name, def]) => (
                  <div key={name} className={FIELD_BLOCK_CLASS} data-field={name}>
                    <Field
                      name={name}
                      def={def}
                      register={register}
                      control={control}
                      errors={errors}
                      isEdit={isEdit}
                      locale={locale}
                    />
                  </div>
                ))}
              </CollapsibleSection>
            ),
          )
        : orderedFieldEntries.map(([name, def]) =>
            (def as FieldDefinition).type === "object" &&
            (def as FieldDefinition).fields ? (
              <ObjectFieldSet
                key={name}
                name={name}
                def={def as FieldDefinition}
                register={register}
                control={control}
                errors={errors}
                isEdit={isEdit}
                locale={locale}
              />
            ) : (
              <div key={name} className={FIELD_BLOCK_CLASS} data-field={name}>
                <Field
                  name={name}
                  def={def as FieldDefinition}
                  register={register}
                  control={control}
                  errors={errors}
                  isEdit={isEdit}
                  locale={locale}
                />
              </div>
            ),
          )}
      <div className="flex flex-wrap gap-3 pt-6">
        <Button
          type="submit"
          disabled={isSubmitting}
          className="min-h-[44px] sm:min-h-0"
        >
          {isSubmitting ? t("saving") : t("save")}
        </Button>
        {isEdit && (
          <Button variant="outline" asChild className="min-h-[44px] sm:min-h-0">
            <a href={`/content/${collection}${slugParam}`}>{t("backToList")}</a>
          </Button>
        )}
      </div>
    </form>
  );
}

function getFieldError(errors: Record<string, unknown>, name: string): unknown {
  const parts = name.split(".");
  let current: unknown = errors;
  for (const p of parts) {
    current =
      current != null && typeof current === "object" && p in current
        ? (current as Record<string, unknown>)[p]
        : undefined;
  }
  return current;
}

function ObjectFieldSet({
  name,
  def,
  register,
  control,
  errors,
  isEdit,
  locale,
}: {
  name: string;
  def: FieldDefinition;
  register: ReturnType<typeof useForm>["register"];
  control: ReturnType<typeof useForm>["control"];
  errors: ReturnType<typeof useForm>["formState"]["errors"];
  isEdit: boolean;
  locale?: string;
}) {
  const nestedFields = def.fields ?? {};
  const displayName = name.split(".").pop() ?? name;
  const title = (def.description as string) || displayName;
  return (
    <CollapsibleSection
      title={title}
      defaultOpen={true}
      contentClassName="space-y-4"
    >
      {Object.entries(nestedFields).map(([subName, subDef]) => (
        <div key={subName} className={FIELD_BLOCK_CLASS}>
          <Field
            name={`${name}.${subName}`}
            def={subDef as FieldDefinition}
            register={register}
            control={control}
            errors={errors}
            isEdit={isEdit}
            locale={locale}
          />
        </div>
      ))}
    </CollapsibleSection>
  );
}

function StringMapField({
  value,
  onChange,
  label,
  description,
  error,
}: {
  value: Record<string, string>;
  onChange: (v: Record<string, string>) => void;
  label: React.ReactNode;
  description?: string | null;
  error?: unknown;
}) {
  const t = useTranslations("ContentForm");
  const obj =
    value && typeof value === "object" && !Array.isArray(value) ? value : {};
  const baseEntries = Object.entries(obj);
  const entries: [string, string][] =
    baseEntries.length > 0 && baseEntries[baseEntries.length - 1][0] === ""
      ? baseEntries
      : [...baseEntries, ["", ""]];
  const update = (pairs: [string, string][]) => {
    const next: Record<string, string> = {};
    for (const [k, v] of pairs) {
      const key = k.trim();
      if (key !== "") next[key] = v;
    }
    onChange(next);
  };
  const setRow = (i: number, key: string, val: string) => {
    const next = entries.map((e, idx) =>
      idx === i ? ([key, val] as [string, string]) : e,
    );
    update(next);
  };
  const removeRow = (i: number) => {
    update(entries.filter((_, idx) => idx !== i));
  };
  const addRow = () => update([...entries, ["", ""]]);
  return (
    <div>
      {label}
      {description ? (
        <p className="mt-0.5 text-xs text-gray-500">{description}</p>
      ) : null}
      <div className="mt-2 space-y-2 rounded border border-gray-200 bg-white p-3">
        {entries.map(([k, v], i) => (
          <div key={i} className="flex flex-wrap items-center gap-2">
            <input
              type="text"
              value={k}
              onChange={(e) => setRow(i, e.target.value, v)}
              placeholder={t("keyPlaceholder")}
              className="min-w-[120px] flex-1 rounded border border-gray-300 px-2 py-1.5 text-sm font-mono"
            />
            <Icon
              icon="mdi:arrow-right"
              className="size-4 shrink-0 text-gray-400"
              aria-hidden
            />
            <input
              type="text"
              value={v}
              onChange={(e) => setRow(i, k, e.target.value)}
              placeholder={t("valuePlaceholder")}
              className="min-w-[160px] flex-2 rounded border border-gray-300 px-2 py-1.5 text-sm"
            />
            <button
              type="button"
              onClick={() => removeRow(i)}
              title={t("removeEntry")}
              aria-label={t("removeEntry")}
              className="rounded border border-gray-300 bg-gray-100 p-1.5 text-gray-600 hover:bg-gray-200 hover:text-gray-900"
            >
              <Icon icon="mdi:delete-outline" className="size-5" />
            </button>
          </div>
        ))}
        <button
          type="button"
          onClick={addRow}
          className="rounded border border-dashed border-gray-300 bg-gray-50 px-3 py-1.5 text-sm text-gray-600 hover:bg-gray-100"
        >
          {t("addEntry")}
        </button>
      </div>
      {error ? (
        <p className="mt-1 text-sm text-red-600">
          {String((error as { message?: string })?.message)}
        </p>
      ) : null}
    </div>
  );
}

/** Array of strings or numbers: list of inputs with add/remove. */
function PrimitiveArrayField({
  def,
  value,
  onChange,
  error,
  label,
}: {
  name: string;
  def: FieldDefinition;
  value: unknown;
  onChange: (v: (string | number)[]) => void;
  required: boolean;
  error?: unknown;
  label: React.ReactNode;
}) {
  const t = useTranslations("ContentForm");
  const itemsDef = def.items as FieldDefinition | undefined;
  const itemType = (itemsDef?.type ?? "string") as string;
  const isNumber = itemType === "number" || itemType === "integer";
  const arr = Array.isArray(value) ? value : [];
  const list: (string | number)[] = arr.map((v) =>
    isNumber ? (typeof v === "number" ? v : Number(v) || 0) : String(v ?? ""),
  );

  const update = (next: (string | number)[]) => {
    onChange(next);
  };
  const setItem = (index: number, val: string | number) => {
    const next = [...list];
    next[index] = val;
    update(next);
  };
  const remove = (index: number) => {
    update(list.filter((_, i) => i !== index));
  };
  const add = () => {
    update([...list, isNumber ? 0 : ""]);
  };

  const placeholder = isNumber
    ? t("arrayItemPlaceholderNumber")
    : t("arrayItemPlaceholder");

  return (
    <div>
      {label}
      <p className="mt-0.5 text-xs text-muted-foreground">{t("arrayHint")}</p>
      <div className="mt-2 space-y-2 rounded-md border border-input bg-muted/20 p-3">
        {list.length === 0 ? (
          <p className="text-sm text-muted-foreground">{t("arrayAddItem")} ↓</p>
        ) : null}
        {list.map((item, i) => (
          <div key={i} className="flex flex-wrap items-center gap-2">
            {isNumber ? (
              <Input
                type="number"
                step={itemType === "integer" ? 1 : "any"}
                value={item}
                onChange={(e) => setItem(i, e.target.valueAsNumber ?? 0)}
                placeholder={placeholder}
                className="min-w-[120px] flex-1"
              />
            ) : (
              <Input
                type="text"
                value={item}
                onChange={(e) => setItem(i, e.target.value)}
                placeholder={placeholder}
                className="min-w-[200px] flex-1"
              />
            )}
            <Button
              type="button"
              variant="outline"
              size="icon-sm"
              onClick={() => remove(i)}
              title={t("arrayRemoveItem")}
              aria-label={t("arrayRemoveItem")}
            >
              <Icon icon="mdi:delete-outline" className="size-4" />
            </Button>
          </div>
        ))}
        <Button type="button" variant="outline" size="sm" onClick={add}>
          <Icon icon="mdi:plus" className="size-4" />
          {t("arrayAddItem")}
        </Button>
      </div>
      {error ? (
        <p className="mt-1 text-sm text-red-600">
          {String((error as { message?: string })?.message)}
        </p>
      ) : null}
    </div>
  );
}

function Field({
  name,
  def,
  register,
  control,
  errors,
  isEdit,
  locale,
}: {
  name: string;
  def: FieldDefinition;
  register: ReturnType<typeof useForm>["register"];
  control: ReturnType<typeof useForm>["control"];
  errors: ReturnType<typeof useForm>["formState"]["errors"];
  isEdit: boolean;
  locale?: string;
}) {
  const t = useTranslations("ContentForm");
  const type = (def.type ?? "string") as string;
  const required = !!def.required;
  const isSlug = name === "_slug";
  const readonly = isEdit && isSlug;
  const fieldError = getFieldError(errors as Record<string, unknown>, name);
  const fieldId = useId();

  const fieldDescription = def.description ? (
    <p className="mt-0.5 text-xs text-gray-500">{def.description}</p>
  ) : null;

  const label = (
    <>
      <Label className="mb-1 block font-bold">
        {name.split(".").pop()}
        {required && <span className="text-red-500"> *</span>}
      </Label>
      {fieldDescription}
    </>
  );

  const safeStringValue = (v: unknown): string => {
    if (v == null) return "";
    if (typeof v === "string") return v;
    if (typeof v === "object" && "_slug" in v)
      return String((v as { _slug?: string })._slug ?? "");
    return String(v);
  };

  if (type === "boolean") {
    return (
      <div>
        <div className="flex items-start gap-2">
          <Controller
            name={name}
            control={control!}
            render={({ field }) => (
              <Checkbox
                id={fieldId}
                checked={!!field.value}
                onCheckedChange={(checked) => field.onChange(!!checked)}
                className="mt-0.5"
              />
            )}
          />
          <label
            htmlFor={fieldId}
            className="cursor-pointer text-sm font-medium text-gray-700"
          >
            {name.split(".").pop()}
            {required && <span className="text-red-500"> *</span>}
          </label>
        </div>
        {fieldDescription}
      </div>
    );
  }

  if (type === "number") {
    return (
      <div>
        {label}
        <Input
          type="number"
          step="any"
          {...register(name, { valueAsNumber: true, required })}
        />
        {fieldError ? (
          <p className="mt-1 text-sm text-red-600">
            {String((fieldError as { message?: string })?.message)}
          </p>
        ) : null}
      </div>
    );
  }

  if (type === "integer") {
    return (
      <div>
        {label}
        <Input
          type="number"
          step={1}
          {...register(name, { valueAsNumber: true, required })}
        />
        {fieldError ? (
          <p className="mt-1 text-sm text-red-600">
            {String((fieldError as { message?: string })?.message)}
          </p>
        ) : null}
      </div>
    );
  }

  if (type === "textOrRef" || type === "markdown") {
    return (
      <Controller
        name={name}
        control={control!}
        rules={{ required }}
        render={({ field }) => (
          <MarkdownEditor
            value={typeof field.value === "string" ? field.value : ""}
            onChange={field.onChange}
            label={label}
            error={fieldError}
            required={required}
            readOnly={readonly}
            rows={12}
          />
        )}
      />
    );
  }

  if (type === "html") {
    return (
      <Controller
        name={name}
        control={control!}
        rules={{ required }}
        render={({ field }) => (
          <CodeField
            name={field.name}
            value={safeStringValue(field.value)}
            onChange={field.onChange}
            onBlur={field.onBlur}
            language="html"
            label={label}
            readonly={readonly}
            fieldError={fieldError}
            copyLabel={t("copyCode")}
          />
        )}
      />
    );
  }

  if (type === "richtext") {
    return (
      <div>
        {label}
        <Textarea
          {...register(name, { required })}
          rows={10}
          readOnly={readonly}
          className="font-mono"
        />
        {fieldError ? (
          <p className="mt-1 text-sm text-red-600">
            {String((fieldError as { message?: string })?.message)}
          </p>
        ) : null}
      </div>
    );
  }

  if (type === "datetime") {
    const parseDatetime = (v: unknown): { date: string; time: string } => {
      if (v == null || v === "") return { date: "", time: "" };
      const s = typeof v === "string" ? v.trim() : String(v);
      if (!s) return { date: "", time: "" };
      const match = s.match(/^(\d{4}-\d{2}-\d{2})T?(\d{2}:\d{2})?/);
      if (match) return { date: match[1] ?? "", time: match[2] ?? "00:00" };
      return { date: "", time: "" };
    };
    return (
      <div>
        {label}
        <Controller
          name={name}
          control={control!}
          rules={{ required }}
          render={({ field }) => {
            const { date, time } = parseDatetime(field.value);
            const update = (newDate: string, newTime: string) => {
              if (newDate && newTime)
                field.onChange(`${newDate}T${newTime}:00`);
              else if (newDate) field.onChange(`${newDate}T00:00:00`);
              else field.onChange("");
            };
            return (
              <div className="flex flex-wrap items-center gap-3">
                <Input
                  type="date"
                  value={date}
                  onChange={(e) => update(e.target.value, time)}
                  onBlur={field.onBlur}
                  readOnly={readonly}
                  className="w-auto"
                />
                <Input
                  type="time"
                  value={time}
                  onChange={(e) => update(date, e.target.value)}
                  onBlur={field.onBlur}
                  readOnly={readonly}
                  className="w-auto"
                />
              </div>
            );
          }}
        />
        {fieldError ? (
          <p className="mt-1 text-sm text-red-600">
            {String((fieldError as { message?: string })?.message)}
          </p>
        ) : null}
      </div>
    );
  }

  const isRefArray =
    type === "array" &&
    (def.items?.type === "reference" ||
      def.items?.type === "referenceOrInline");
  if (isRefArray) {
    return (
      <Controller
        name={name}
        control={control!}
        rules={{ required }}
        render={({ field }) => (
          <ReferenceArrayField
            name={name}
            def={def}
            value={Array.isArray(field.value) ? field.value : []}
            onChange={field.onChange}
            required={required}
            error={fieldError}
            locale={locale}
            label={label}
          />
        )}
      />
    );
  }

  const itemsType =
    def.items && typeof def.items === "object"
      ? (def.items as FieldDefinition).type
      : undefined;
  const isPrimitiveArray =
    type === "array" &&
    (itemsType === "string" ||
      itemsType === "number" ||
      itemsType === "integer");
  if (isPrimitiveArray) {
    return (
      <Controller
        name={name}
        control={control!}
        rules={{ required }}
        render={({ field }) => (
          <PrimitiveArrayField
            name={name}
            def={def}
            value={field.value}
            onChange={field.onChange}
            required={required}
            error={fieldError}
            label={label}
          />
        )}
      />
    );
  }

  if (type === "reference") {
    return (
      <Controller
        name={name}
        control={control!}
        rules={{ required }}
        render={({ field }) => (
          <ReferenceField
            name={name}
            def={def}
            value={typeof field.value === "string" ? field.value : ""}
            onChange={field.onChange}
            required={required}
            error={fieldError}
            locale={locale}
            label={label}
            readOnly={readonly}
          />
        )}
      />
    );
  }

  if (type === "referenceOrInline") {
    return (
      <Controller
        name={name}
        control={control!}
        rules={{ required }}
        render={({ field }) => (
          <ReferenceOrInlineField
            name={name}
            def={def}
            value={
              typeof field.value === "string"
                ? field.value
                : field.value != null &&
                    typeof field.value === "object" &&
                    !Array.isArray(field.value)
                  ? (field.value as Record<string, unknown>)
                  : ""
            }
            onChange={field.onChange}
            required={required}
            error={fieldError}
            locale={locale}
            label={label}
            readOnly={readonly}
          />
        )}
      />
    );
  }

  if (type === "object" && def.fields) {
    return (
      <ObjectFieldSet
        name={name}
        def={def}
        register={register}
        control={control}
        errors={errors}
        isEdit={isEdit}
        locale={locale}
      />
    );
  }

  const additionalProps = def.additionalProperties as
    | FieldDefinition
    | undefined;
  if (type === "object" && additionalProps) {
    return (
      <Controller
        name={name}
        control={control!}
        rules={{ required }}
        render={({ field }) => (
          <StringMapField
            value={
              field.value &&
              typeof field.value === "object" &&
              !Array.isArray(field.value)
                ? (field.value as Record<string, string>)
                : {}
            }
            onChange={field.onChange}
            label={label}
            error={fieldError}
          />
        )}
      />
    );
  }

  const enumValues = def.enum as string[] | undefined;

  if (type === "multiSelect" && Array.isArray(enumValues) && enumValues.length > 0) {
    return (
      <div>
        {label}
        <Controller
          name={name}
          control={control!}
          rules={{ required }}
          render={({ field }) => {
            const selected = Array.isArray(field.value) ? (field.value as string[]) : [];
            const toggle = (opt: string) => {
              const next = selected.includes(opt)
                ? selected.filter((s) => s !== opt)
                : [...selected, opt];
              field.onChange(next);
            };
            return (
              <div className="flex flex-wrap gap-3">
                {enumValues.map((opt) => (
                  <label key={opt} className="flex cursor-pointer items-center gap-2">
                    <Checkbox
                      checked={selected.includes(opt)}
                      onCheckedChange={() => toggle(opt)}
                    />
                    <span className="text-sm">{String(opt)}</span>
                  </label>
                ))}
              </div>
            );
          }}
        />
        {fieldError ? (
          <p className="mt-1 text-sm text-red-600">
            {String((fieldError as { message?: string })?.message)}
          </p>
        ) : null}
      </div>
    );
  }

  if (Array.isArray(enumValues) && enumValues.length > 0) {
    return (
      <div>
        {label}
        <Controller
          name={name}
          control={control!}
          rules={{ required }}
          render={({ field }) => (
            <Select
              value={field.value as string}
              onValueChange={field.onChange}
            >
              <SelectTrigger className="w-full">
                <SelectValue placeholder={t("pleaseSelect")} />
              </SelectTrigger>
              <SelectContent>
                {enumValues.map((v) => (
                  <SelectItem key={String(v)} value={String(v)}>
                    {String(v)}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          )}
        />
        {fieldError ? (
          <p className="mt-1 text-sm text-red-600">
            {String((fieldError as { message?: string })?.message)}
          </p>
        ) : null}
      </div>
    );
  }

  if (type === "string" && isImageUrlField(def)) {
    return (
      <Controller
        name={name}
        control={control!}
        rules={{ required }}
        render={({ field }) => (
          <ImageUrlField
            value={safeStringValue(field.value)}
            onChange={field.onChange}
            label={label}
            fieldError={fieldError}
            required={required}
            readonly={readonly}
          />
        )}
      />
    );
  }

  const codeLanguage = def.codeLanguage;
  if (type === "string" && def.widget === "code" && isCodeFieldLanguage(codeLanguage)) {
    return (
      <Controller
        name={name}
        control={control!}
        rules={{ required }}
        render={({ field }) => (
          <CodeField
            name={field.name}
            value={safeStringValue(field.value)}
            onChange={field.onChange}
            onBlur={field.onBlur}
            language={codeLanguage}
            label={label}
            readonly={readonly}
            fieldError={fieldError}
            copyLabel={t("copyCode")}
          />
        )}
      />
    );
  }

  if (type === "string" && def.widget === "textarea") {
    return (
      <div>
        {label}
        <Controller
          name={name}
          control={control!}
          rules={{ required }}
          render={({ field }) => (
            <Textarea
              name={field.name}
              value={safeStringValue(field.value)}
              onChange={(e) => field.onChange(e.target.value)}
              onBlur={field.onBlur}
              readOnly={readonly}
              rows={5}
              className="min-h-[100px] w-full"
            />
          )}
        />
        {fieldError ? (
          <p className="mt-1 text-sm text-red-600">
            {String((fieldError as { message?: string })?.message)}
          </p>
        ) : null}
      </div>
    );
  }

  return (
    <div>
      {label}
      <Controller
        name={name}
        control={control!}
        rules={{ required }}
        render={({ field }) => (
          <Input
            type="text"
            name={field.name}
            value={safeStringValue(field.value)}
            onChange={(e) => field.onChange(e.target.value)}
            onBlur={field.onBlur}
            readOnly={readonly}
          />
        )}
      />
      {fieldError ? (
        <p className="mt-1 text-sm text-red-600">
          {String((fieldError as { message?: string })?.message)}
        </p>
      ) : null}
    </div>
  );
}
