"use client";

import * as React from "react";
import {
  DndContext,
  type DragEndEvent,
  KeyboardSensor,
  PointerSensor,
  closestCenter,
  useSensor,
  useSensors,
} from "@dnd-kit/core";
import {
  SortableContext,
  arrayMove,
  sortableKeyboardCoordinates,
  useSortable,
  verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { GripVertical, Trash2 } from "lucide-react";
import type { inferRouterOutputs } from "@trpc/server";

import type { AppRouter } from "@/server/root";
import { api } from "@/lib/trpc";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator";

import type { TemplateSchemaJson } from "./template-picker";

type TemplateRow = inferRouterOutputs<AppRouter>["templates"]["getById"];

const TARGET_TYPES = ["task", "document", "project"] as const;

const FIELD_TYPES = [
  "text",
  "textarea",
  "number",
  "date",
  "select",
  "checkbox",
  "url",
  "email",
] as const;

type PropertyRow = {
  id: string;
  name: string;
  fieldType: string;
  defaultValue: string;
};

function newPropertyRow(): PropertyRow {
  return {
    id:
      typeof crypto !== "undefined" && "randomUUID" in crypto
        ? crypto.randomUUID()
        : `p-${Date.now()}-${Math.random().toString(16).slice(2)}`,
    name: "",
    fieldType: "text",
    defaultValue: "",
  };
}

function schemaToRows(schema: TemplateSchemaJson | null | undefined): PropertyRow[] {
  const props = schema?.properties ?? [];
  return props.map((p, i) => ({
    id:
      typeof crypto !== "undefined" && "randomUUID" in crypto
        ? crypto.randomUUID()
        : `p-${i}`,
    name: p.name,
    fieldType: p.fieldType,
    defaultValue:
      p.defaultValue === undefined || p.defaultValue === null
        ? ""
        : typeof p.defaultValue === "string"
          ? p.defaultValue
          : JSON.stringify(p.defaultValue),
  }));
}

function rowsToSchema(
  rows: PropertyRow[],
  defaultContent: string,
): TemplateSchemaJson {
  return {
    properties: rows
      .filter((r) => r.name.trim() !== "")
      .map((r) => {
        let defaultValue: unknown = r.defaultValue;
        if (r.fieldType === "number" && r.defaultValue.trim() !== "") {
          const n = Number(r.defaultValue);
          defaultValue = Number.isFinite(n) ? n : r.defaultValue;
        } else if (r.fieldType === "checkbox") {
          defaultValue = r.defaultValue === "true" || r.defaultValue === "1";
        } else if (r.defaultValue.trim() === "") {
          defaultValue = undefined;
        }
        return {
          name: r.name.trim(),
          fieldType: r.fieldType,
          ...(defaultValue !== undefined ? { defaultValue } : {}),
        };
      }),
    ...(defaultContent.trim() !== "" ? { defaultContent } : {}),
  };
}

function SortablePropertyRow({
  row,
  onChange,
  onRemove,
}: {
  row: PropertyRow;
  onChange: (id: string, patch: Partial<PropertyRow>) => void;
  onRemove: (id: string) => void;
}) {
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } =
    useSortable({ id: row.id });

  const style = {
    transform: CSS.Transform.toString(transform),
    transition,
  };

  return (
    <div
      ref={setNodeRef}
      style={style}
      className={cn(
        "flex flex-col gap-2 rounded-md border bg-card p-3 sm:flex-row sm:items-end",
        isDragging && "z-10 opacity-90 shadow-md",
      )}
    >
      <button
        type="button"
        className="flex h-10 w-10 shrink-0 items-center justify-center rounded-md border border-dashed text-muted-foreground hover:bg-muted"
        {...attributes}
        {...listeners}
        aria-label="Reorder property"
      >
        <GripVertical className="h-4 w-4" />
      </button>
      <div className="grid min-w-0 flex-1 gap-2 sm:grid-cols-3">
        <div className="space-y-1">
          <label className="text-xs font-medium text-muted-foreground">Name</label>
          <Input
            value={row.name}
            onChange={(e) => onChange(row.id, { name: e.target.value })}
            placeholder="Property name"
          />
        </div>
        <div className="space-y-1">
          <label className="text-xs font-medium text-muted-foreground">Field type</label>
          <select
            className={cn(
              "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
            )}
            value={row.fieldType}
            onChange={(e) => onChange(row.id, { fieldType: e.target.value })}
          >
            {FIELD_TYPES.map((ft) => (
              <option key={ft} value={ft}>
                {ft}
              </option>
            ))}
          </select>
        </div>
        <div className="space-y-1">
          <label className="text-xs font-medium text-muted-foreground">Default value</label>
          <Input
            value={row.defaultValue}
            onChange={(e) => onChange(row.id, { defaultValue: e.target.value })}
            placeholder="Optional"
          />
        </div>
      </div>
      <Button
        type="button"
        variant="ghost"
        size="icon"
        className="shrink-0 text-destructive hover:text-destructive"
        onClick={() => onRemove(row.id)}
        aria-label="Remove property"
      >
        <Trash2 className="h-4 w-4" />
      </Button>
    </div>
  );
}

export type TemplateEditorProps = {
  template?: TemplateRow;
  workspaceHandle: string;
  onSave: () => void;
};

export function TemplateEditor({ template, workspaceHandle, onSave }: TemplateEditorProps) {
  const [name, setName] = React.useState(template?.name ?? "");
  const [targetType, setTargetType] = React.useState(
    template?.targetType && TARGET_TYPES.includes(template.targetType as (typeof TARGET_TYPES)[number])
      ? template.targetType
      : "task",
  );
  const [defaultContent, setDefaultContent] = React.useState(
    (template?.schema as TemplateSchemaJson | null | undefined)?.defaultContent ?? "",
  );
  const [rows, setRows] = React.useState<PropertyRow[]>(() =>
    template?.schema ? schemaToRows(template.schema as TemplateSchemaJson) : [newPropertyRow()],
  );

  React.useEffect(() => {
    if (!template) return;
    setName(template.name);
    setTargetType(
      TARGET_TYPES.includes(template.targetType as (typeof TARGET_TYPES)[number])
        ? template.targetType
        : "task",
    );
    const sch = template.schema as TemplateSchemaJson | null | undefined;
    setDefaultContent(sch?.defaultContent ?? "");
    setRows(schemaToRows(sch));
  }, [template]);

  const sensors = useSensors(
    useSensor(PointerSensor, { activationConstraint: { distance: 6 } }),
    useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),
  );

  const createMut = api.templates.create.useMutation({ onSuccess: onSave });
  const updateMut = api.templates.update.useMutation({ onSuccess: onSave });

  const pending = createMut.isPending || updateMut.isPending;

  const updateRow = React.useCallback((id: string, patch: Partial<PropertyRow>) => {
    setRows((prev) => prev.map((r) => (r.id === id ? { ...r, ...patch } : r)));
  }, []);

  const removeRow = React.useCallback((id: string) => {
    setRows((prev) => (prev.length <= 1 ? prev : prev.filter((r) => r.id !== id)));
  }, []);

  const onDragEnd = React.useCallback((event: DragEndEvent) => {
    const { active, over } = event;
    if (!over || active.id === over.id) return;
    setRows((items) => {
      const oldIndex = items.findIndex((i) => i.id === active.id);
      const newIndex = items.findIndex((i) => i.id === over.id);
      if (oldIndex < 0 || newIndex < 0) return items;
      return arrayMove(items, oldIndex, newIndex);
    });
  }, []);

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    const schema = rowsToSchema(rows, defaultContent);
    if (!name.trim()) return;

    if (template?.id) {
      updateMut.mutate({
        workspace: workspaceHandle,
        id: template.id,
        name: name.trim(),
        schema,
      });
    } else {
      createMut.mutate({
        workspace: workspaceHandle,
        name: name.trim(),
        targetType,
        schema,
      });
    }
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-6">
      <div className="space-y-2">
        <label className="text-sm font-medium">Name</label>
        <Input value={name} onChange={(e) => setName(e.target.value)} placeholder="Template name" />
      </div>

      <div className="space-y-2">
        <label className="text-sm font-medium">Target type</label>
        <select
          className={cn(
            "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm",
          )}
          value={targetType}
          onChange={(e) => setTargetType(e.target.value)}
          disabled={Boolean(template?.id)}
        >
          {TARGET_TYPES.map((t) => (
            <option key={t} value={t}>
              {t}
            </option>
          ))}
        </select>
        {template?.id ? (
          <p className="text-xs text-muted-foreground">Target type cannot be changed after creation.</p>
        ) : null}
      </div>

      <div className="space-y-3">
        <div className="flex items-center justify-between gap-2">
          <span className="text-sm font-medium">Properties</span>
          <Button
            type="button"
            variant="outline"
            size="sm"
            onClick={() => setRows((r) => [...r, newPropertyRow()])}
          >
            Add property
          </Button>
        </div>

        <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={onDragEnd}>
          <SortableContext items={rows.map((r) => r.id)} strategy={verticalListSortingStrategy}>
            <div className="space-y-2">
              {rows.map((row) => (
                <SortablePropertyRow
                  key={row.id}
                  row={row}
                  onChange={updateRow}
                  onRemove={removeRow}
                />
              ))}
            </div>
          </SortableContext>
        </DndContext>
      </div>

      <Separator />

      <div className="space-y-2">
        <label className="text-sm font-medium">Default content</label>
        <textarea
          className={cn(
            "flex min-h-[120px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
          )}
          value={defaultContent}
          onChange={(e) => setDefaultContent(e.target.value)}
          placeholder="Initial body text or outline for new objects using this template"
        />
      </div>

      <div className="flex flex-wrap justify-end gap-2">
        <Button
          type="button"
          variant="outline"
          disabled={pending}
          onClick={() => {
            if (template) {
              setName(template.name);
              setTargetType(
                TARGET_TYPES.includes(
                  template.targetType as (typeof TARGET_TYPES)[number],
                )
                  ? template.targetType
                  : "task",
              );
              const sch = template.schema as TemplateSchemaJson | null | undefined;
              setDefaultContent(sch?.defaultContent ?? "");
              setRows(schemaToRows(sch));
            } else {
              setName("");
              setTargetType("task");
              setDefaultContent("");
              setRows([newPropertyRow()]);
            }
          }}
        >
          Cancel
        </Button>
        <Button type="submit" disabled={pending || !name.trim()}>
          Save
        </Button>
      </div>
    </form>
  );
}
