import { lazy, Suspense, useEffect, useMemo, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { GripVertical, Link, Plus, Settings2, Table2, X } from "lucide-react";
import { AnimatePresence } from "motion/react";
import {
    DndContext,
    closestCenter,
    PointerSensor,
    KeyboardSensor,
    useSensor,
    useSensors,
    type DragEndEvent,
    type Modifier,
} from "@dnd-kit/core";
import {
    SortableContext,
    useSortable,
    arrayMove,
    sortableKeyboardCoordinates,
    verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import {
    useDbViewerStore,
    type ViewerTab,
} from "../../../stores/dbViewerStore";
import { useConnectionStore } from "../../../stores/connectionStore";
import * as cmd from "../../../lib/commands";
import type {
    ColumnInfo,
    ConstraintInfo,
    TablespaceInfo,
    DbType,
} from "../../../lib/types";
import { getCapabilities } from "../../../lib/dbCapabilities";
import { FkPanel, type FkDefinition } from "./FkPanel";
import {
    FormRow,
    FormSectionHeader,
    inputClass,
} from "./formRow";
import { DataTypeIcon } from "../../ui/DataTypeIcon";

const SqlEditorField = lazy(() =>
    import("../../editor/SqlEditorField").then((m) => ({
        default: m.SqlEditorField,
    })),
);

const PG_TYPES = [
    "int",
    "int8",
    "int2",
    "serial",
    "bigserial",
    "smallserial",
    "text",
    "varchar",
    "char",
    "bool",
    "numeric",
    "real",
    "float8",
    "date",
    "time",
    "timestamp",
    "timestamptz",
    "interval",
    "uuid",
    "json",
    "jsonb",
    "bytea",
    "inet",
    "cidr",
    "macaddr",
    "money",
];

const SQLITE_TYPES = ["integer", "real", "text", "blob", "numeric"];

interface TableFormColumn {
    rowId: string;
    name: string;
    type: string;
    nullable: boolean;
    default: string | null;
    is_pk: boolean;
    params?: string;
    auto_increment?: boolean;
    unique?: boolean;
    /** transient: true when a FK was just assigned to this column */
    fk?: boolean;
}

interface SqlColumn {
    name: string;
    type: string;
    nullable: boolean;
    default: string | null;
    is_pk: boolean;
    unique?: boolean;
    auto_increment?: boolean;
}

interface TableFormAction {
    op: "create" | "edit";
    columns: TableFormColumn[];
    old_columns?: TableFormColumn[];
    tablespace?: string | null;
    rls?: "disable" | "enable" | "force" | null;
}

interface TableFormParams {
    schema: string;
    name: string;
    action: TableFormAction;
}

function toSqlColumn(
    c: TableFormColumn,
    mode: "create" | "edit",
    dbType: DbType | undefined,
): SqlColumn {
    let type = c.type;
    const base = c.type.trim().toLowerCase();
    if (mode === "create" && c.auto_increment && dbType !== "sqlite") {
        if (base === "integer" || base === "int" || base === "int4")
            type = "serial";
        else if (base === "bigint" || base === "int8") type = "bigserial";
        else if (base === "smallint" || base === "int2") type = "smallserial";
    }
    if (c.params && c.params.trim()) {
        type = `${type}(${c.params.trim()})`;
    }
    const out: SqlColumn = {
        name: c.name,
        type,
        nullable: c.nullable,
        default: c.default,
        is_pk: c.is_pk,
        unique: c.unique ?? undefined,
    };
    if (dbType === "sqlite" && c.auto_increment) {
        out.auto_increment = true;
    }
    return out;
}

function sameColumns(a: TableFormColumn[], b: TableFormColumn[]): boolean {
    if (a.length !== b.length) return false;
    return a.every(
        (c, i) =>
            c.name === b[i]?.name &&
            c.type.trim().toLowerCase() === b[i]?.type.trim().toLowerCase(),
    );
}

function namesInOrder(cols: TableFormColumn[]): string {
    return cols.map((c) => c.name).join(",");
}

function sameColumnNames(a: TableFormColumn[], b: TableFormColumn[]): boolean {
    const sa = new Set(a.map((c) => c.name));
    const sb = new Set(b.map((c) => c.name));
    return sa.size === sb.size && [...sa].every((n) => sb.has(n));
}

let rowSeq = 0;
function emptyColumn(): TableFormColumn {
    rowSeq += 1;
    return {
        rowId: `col-${rowSeq}-${Math.random().toString(36).slice(2, 8)}`,
        name: "",
        type: "text",
        nullable: true,
        default: null,
        is_pk: false,
        params: "",
        auto_increment: false,
        unique: false,
    };
}

// Y-axis-only drag (like the tab bar): zero out the X component of the transform.
const restrictToVerticalAxis: Modifier = ({ transform }) => ({
    ...transform,
    x: 0,
});

function pickTypeList(dbType: DbType | undefined): string[] {
    return dbType === "sqlite" ? SQLITE_TYPES : PG_TYPES;
}

// serial types only exist for the integer family (short + long forms).
function supportsAutoIncrement(type: string): boolean {
    const t = type.trim().toLowerCase();
    return (
        t === "integer" ||
        t === "int" ||
        t === "int4" ||
        t === "bigint" ||
        t === "int8" ||
        t === "smallint" ||
        t === "int2"
    );
}

// Cell-local input styles for the columns grid — no horizontal padding so the
// cell's px-3 supplies it (matches the data-grid cell look).
const cellInput =
    "min-w-0 flex-1 bg-transparent font-heading text-xs text-text outline-none placeholder:text-text-muted";
const cellMono =
    "min-w-0 flex-1 bg-transparent font-mono text-xs text-text outline-none placeholder:text-text-muted";
// Select styling matching the FK panel (transparent, no surface bg).
const selectTransparent =
    "min-w-0 flex-1 bg-transparent font-heading text-xs text-text outline-none placeholder:text-text-muted cursor-pointer";

function buildTablePayload(
    params: TableFormParams,
    op: "create" | "edit" | "rebuild",
    foreignKeys: FkDefinition[] = [],
    dbType: DbType | undefined,
): Record<string, unknown> {
    const action = params.action;
    const sqlColumns = action.columns.map((c) =>
        toSqlColumn(c, action.op, dbType),
    );
    return {
        ...params,
        action: {
            ...action,
            op,
            columns: sqlColumns,
            // CREATE TABLE embeds FKs inline (single staged change); edit uses separate ALTERs.
            ...(op === "create" ? { foreign_keys: foreignKeys } : {}),
        },
    } as unknown as Record<string, unknown>;
}

export function TableForm({
    connectionId,
    tab,
}: {
    connectionId: string;
    tab: ViewerTab;
}) {
    const liveTab = useDbViewerStore(
        (s) =>
            s.tabs.find((t) => t.id === tab.id && t.tabType === "objectForm") ??
            tab,
    );
    const form = liveTab.form;
    if (!form) return null;

    const dbType = useConnectionStore(
        (s) => s.connections.find((c) => c.id === connectionId)?.db_type,
    );
    const capabilities = getCapabilities(dbType ?? "postgresql");
    const schemas = useDbViewerStore((s) => s.schemas);

    if (!capabilities.tableManagement) {
        return (
            <div className="flex h-full items-center justify-center p-6 text-center">
                <p className="text-sm text-text-muted">
                    Table management is not supported for this database type.
                </p>
            </div>
        );
    }

    const params = form.params as unknown as TableFormParams;
    const action = params.action;
    const mode = form.mode;

    const isRebuild =
        mode === "edit" &&
        action.old_columns !== undefined &&
        sameColumnNames(action.columns, action.old_columns) &&
        namesInOrder(action.columns) !== namesInOrder(action.old_columns);

    const [view, setView] = useState<"visual" | "sql">("visual");
    const [preview, setPreview] = useState("");
    const [error, setError] = useState<string | null>(null);
    const [refusal, setRefusal] = useState<string | null>(null);
    const [staging, setStaging] = useState(false);

    const [fkPanel, setFkPanel] = useState<{
        open: boolean;
        column: string | null;
    } | null>(null);
    const [fkColumns, setFkColumns] = useState<Set<string>>(new Set());
    const [createFks, setCreateFks] = useState<FkDefinition[]>([]);

    const op = isRebuild ? "rebuild" : action.op;

    const setParams = (next: TableFormParams) => {
        useDbViewerStore
            .getState()
            .updateFormTabParams(
                tab.id,
                next as unknown as Record<string, unknown>,
            );
    };

    // SQLite has a single schema.
    useEffect(() => {
        if (dbType === "sqlite" && params.schema !== "main") {
            setParams({ ...params, schema: "main" });
        }
    }, [dbType, params.schema]);

    // Rebuild readiness check
    useEffect(() => {
        if (!isRebuild) {
            setRefusal(null);
            return;
        }
        let active = true;
        cmd.getTableRebuildReadiness(connectionId, params.schema, params.name)
            .then((r) => {
                if (active) setRefusal(r.ok ? null : r.reasons.join("; "));
            })
            .catch(() => {
                if (active) setRefusal(null);
            });
        return () => {
            active = false;
        };
    }, [isRebuild, connectionId, params.schema, params.name]);

    // Edit mode: mark columns that participate in FKs (from the constraint list).
    useEffect(() => {
        if (mode !== "edit") {
            setFkColumns(new Set());
            return;
        }
        let active = true;
        cmd.getConstraints(connectionId, params.schema)
            .then((cs) => {
                if (!active) return;
                const names = new Set<string>();
                for (const c of cs.filter((x) => x.table === params.name)) {
                    const m = /FOREIGN KEY\s*\(([^)]+)\)/i.exec(
                        c.definition ?? "",
                    );
                    if (m) m[1].split(",").forEach((n) => names.add(n.trim()));
                }
                setFkColumns(names);
            })
            .catch(() => {
                if (active) setFkColumns(new Set());
            });
        return () => {
            active = false;
        };
    }, [mode, connectionId, params.schema, params.name]);

    // SQL preview
    useEffect(() => {
        let active = true;
        setError(null);
        const promise = isRebuild
            ? cmd.buildRebuildScript(
                  connectionId,
                  params.schema,
                  params.name,
                  action.columns,
              )
            : cmd.buildObjectDdl(
                  connectionId,
                  "table",
                  buildTablePayload(params, op, createFks, dbType),
              );
        promise
            .then((sqls: string[] | string) => {
                if (active)
                    setPreview(
                        Array.isArray(sqls)
                            ? sqls.join("\n;\n")
                            : (sqls as string),
                    );
            })
            .catch((e: unknown) => {
                if (active) {
                    setPreview("");
                    setError(e instanceof Error ? e.message : String(e));
                }
            });
        return () => {
            active = false;
        };
    }, [
        params,
        action,
        op,
        isRebuild,
        connectionId,
        params.schema,
        params.name,
        createFks,
    ]);

    const patchColumns = (cols: TableFormColumn[]) =>
        setParams({ ...params, action: { ...action, columns: cols } });

    const addColumn = () => {
        const next = [...action.columns, emptyColumn()];
        // The first column auto-starts as the PK.
        if (next.length === 1) next[0].is_pk = true;
        patchColumns(next);
    };

    const removeColumn = (i: number) =>
        patchColumns(action.columns.filter((_, j) => j !== i));

    const setCell = (i: number, key: keyof TableFormColumn, value: unknown) => {
        const next = [...action.columns];
        next[i] = { ...next[i], [key]: value } as TableFormColumn;
        patchColumns(next);
    };

    const applyFkTypes = (pairs: { localCol: string; refType: string }[]) => {
        const next = action.columns.map((c) => {
            const p = pairs.find((x) => x.localCol === c.name);
            return p ? { ...c, type: p.refType || c.type, fk: true } : c;
        });
        patchColumns(next);
    };

    const handleFkStaged = (result: {
        pairs: { localCol: string; refType: string }[];
        fk?: FkDefinition;
    }) => {
        applyFkTypes(result.pairs);
        if (mode === "create" && result.fk) {
            setCreateFks((prev) => [...prev, result.fk as FkDefinition]);
        }
    };

    const removeCreateFk = (fk: FkDefinition) =>
        setCreateFks((prev) => prev.filter((f) => f !== fk));

    const editCreateFk = (fk: FkDefinition) => {
        setCreateFks((prev) => prev.filter((f) => f !== fk));
        setFkPanel({ open: true, column: fk.columns[0] ?? null });
    };

    const stage = async () => {
        setStaging(true);
        setError(null);
        try {
            if (isRebuild) {
                if (refusal) return;
                const sql = await cmd.buildRebuildScript(
                    connectionId,
                    params.schema,
                    params.name,
                    action.columns,
                );
                useDbViewerStore.getState().addChange({
                    type: "rebuild_table",
                    sql,
                    schema: params.schema,
                    table: params.name,
                    description: `Rebuild table ${params.schema}.${params.name}`,
                });
                useDbViewerStore.getState().closeTab(tab.id);
                return;
            }

            if (mode === "edit" && action.old_columns !== undefined) {
                const live = await cmd.getTableColumns(
                    connectionId,
                    params.schema,
                    params.name,
                );
                const liveCols: TableFormColumn[] = live.map(
                    (c: ColumnInfo, i) => ({
                        rowId: `live-${i}`,
                        name: c.name,
                        type: c.data_type,
                        nullable: c.is_nullable,
                        default: c.default_value,
                        is_pk: c.is_pk,
                    }),
                );
                if (!sameColumns(liveCols, action.old_columns)) {
                    setError(
                        "Table changed since you opened it — re-open to see current state",
                    );
                    return;
                }
            }

            const sqls = await cmd.buildObjectDdl(
                connectionId,
                "table",
                buildTablePayload(params, op, createFks, dbType),
            );
            sqls.forEach((sql, i) =>
                useDbViewerStore.getState().addChange({
                    type: "ddl",
                    sql,
                    schema: params.schema,
                    table: params.name,
                    description:
                        sqls.length > 1
                            ? `${form.description} (${i + 1}/${sqls.length})`
                            : form.description,
                }),
            );
            useDbViewerStore.getState().closeTab(tab.id);
        } catch (e) {
            setError(e instanceof Error ? e.message : String(e));
        } finally {
            setStaging(false);
        }
    };

    const cols = useMemo(
        () =>
            (action.columns ?? []).map((c, i) => ({
                ...c,
                rowId: c.rowId ?? `col-${i}`,
            })),
        [action.columns],
    );

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

    const handleDragEnd = (e: DragEndEvent) => {
        const { active, over } = e;
        if (!over || active.id === over.id) return;
        const from = cols.findIndex((c) => c.rowId === active.id);
        const to = cols.findIndex((c) => c.rowId === over.id);
        if (from === -1 || to === -1) return;
        patchColumns(arrayMove(cols, from, to));
    };

    return (
        <div className="flex h-full flex-col bg-transparent">
            <div className="flex items-center justify-between border-b border-border px-4 py-2">
                <span className="text-[11px] font-semibold text-text-muted uppercase tracking-wider">
                    {mode} table{isRebuild ? " (rebuild)" : ""}
                </span>

                <div className="flex items-center gap-2">
                    <div className="flex rounded-md border border-border overflow-hidden">
                        <button
                            type="button"
                            aria-label="Visual"
                            onClick={() => setView("visual")}
                            className={[
                                "px-2 py-0.5 text-xs transition-colors cursor-pointer",
                                view === "visual"
                                    ? "bg-surface-raised text-text"
                                    : "text-text-muted hover:text-text",
                            ].join(" ")}
                        >
                            Visual
                        </button>
                        <button
                            type="button"
                            aria-label="SQL"
                            onClick={() => setView("sql")}
                            className={[
                                "px-2 py-0.5 text-xs transition-colors cursor-pointer",
                                view === "sql"
                                    ? "bg-surface-raised text-text"
                                    : "text-text-muted hover:text-text",
                            ].join(" ")}
                        >
                            SQL
                        </button>
                    </div>

                    <button
                        type="button"
                        onClick={stage}
                        disabled={
                            !!error || !!refusal || staging || cols.length === 0
                        }
                        className="rounded-lg bg-accent px-3 py-1.5 text-xs font-medium text-white hover:bg-accent-hover disabled:cursor-not-allowed disabled:opacity-50 cursor-pointer"
                    >
                        {mode === "create" ? "Create Table" : "Stage"}
                    </button>
                </div>
            </div>

            <div
                className="flex-1 overflow-y-auto"
                style={{ overscrollBehavior: "none" }}
            >
                {refusal && (
                    <div className="border-b border-border px-4 py-2">
                        <p className="text-xs text-amber-400">
                            Cannot reorder: {refusal}. Use the Query tab with
                            pg_dump for these tables.
                        </p>
                    </div>
                )}

                {view === "visual" ? (
                    <>
                        {mode === "create" && dbType !== "sqlite" && (
                            <FormRow label="Schema">
                                {schemas && schemas.length > 0 ? (
                                    <select
                                        aria-label="Schema"
                                        value={params.schema}
                                        onChange={(e) =>
                                            setParams({
                                                ...params,
                                                schema: e.target.value,
                                            })
                                        }
                                        className={selectTransparent}
                                    >
                                        {params.schema !== "" &&
                                            !schemas.includes(
                                                params.schema,
                                            ) && (
                                                <option value={params.schema}>
                                                    {params.schema}
                                                </option>
                                            )}
                                        {schemas.map((s) => (
                                            <option key={s} value={s}>
                                                {s}
                                            </option>
                                        ))}
                                    </select>
                                ) : (
                                    <input
                                        className={inputClass}
                                        value={params.schema}
                                        onChange={(e) =>
                                            setParams({
                                                ...params,
                                                schema: e.target.value,
                                            })
                                        }
                                    />
                                )}
                            </FormRow>
                        )}

                        {mode === "create" && (
                            <FormRow label="Name">
                                <input
                                    className={inputClass}
                                    placeholder="Table name"
                                    value={params.name}
                                    onChange={(e) =>
                                        setParams({
                                            ...params,
                                            name: e.target.value,
                                        })
                                    }
                                />
                            </FormRow>
                        )}

                        <FormSectionHeader
                            label="Columns"
                            count={cols.length}
                        />
                        <div
                            className="overflow-x-auto"
                            style={{ overscrollBehavior: "none" }}
                        >
                            <div className="border-b border-border flex items-stretch w-full min-w-[760px]">
                                <div className="w-8 shrink-0 border-r border-border px-3 py-1.5 flex items-center justify-center" />
                                <div className="w-8 shrink-0 border-r border-border px-3 py-1.5 flex items-center text-[11px] font-semibold text-text-muted uppercase tracking-wider">
                                    #
                                </div>
                                <div className="flex-1 min-w-48 border-r border-border px-3 py-1.5 flex items-center text-[11px] font-semibold text-text-muted uppercase tracking-wider">
                                    Name
                                </div>
                                <div className="flex-1 min-w-40 border-r border-border px-3 py-1.5 flex items-center text-[11px] font-semibold text-text-muted uppercase tracking-wider">
                                    Type
                                </div>
                                <div className="w-24 shrink-0 border-r border-border px-3 py-1.5 flex items-center text-[11px] font-semibold text-text-muted uppercase tracking-wider">
                                    Parameters
                                </div>
                                <div className="flex-1 min-w-44 border-r border-border px-3 py-1.5 flex items-center text-[11px] font-semibold text-text-muted uppercase tracking-wider">
                                    Default Value
                                </div>
                                <div className="w-max shrink-0 border-r border-border px-3 py-1.5 flex items-center justify-end gap-1 invisible">
                                    <Link size={12} />
                                    <X size={12} />
                                </div>
                            </div>
                            <DndContext
                                sensors={sensors}
                                collisionDetection={closestCenter}
                                modifiers={[restrictToVerticalAxis]}
                                onDragEnd={handleDragEnd}
                            >
                                <SortableContext
                                    items={cols.map((c) => c.rowId)}
                                    strategy={verticalListSortingStrategy}
                                >
                                    {cols.map((c, i) => (
                                        <ColumnRow
                                            key={c.rowId}
                                            c={c}
                                            index={i}
                                            mode={mode}
                                            setCell={setCell}
                                            hasFk={
                                                !!c.fk || fkColumns.has(c.name)
                                            }
                                            onRemove={() => removeColumn(i)}
                                            onFk={() =>
                                                setFkPanel({
                                                    open: true,
                                                    column: c.name,
                                                })
                                            }
                                            dbType={dbType}
                                        />
                                    ))}
                                </SortableContext>
                            </DndContext>
                            <div className="border-b border-border px-4 h-max">
                                <button
                                    type="button"
                                    aria-label="Add column"
                                    onClick={addColumn}
                                    className="text-xs text-accent hover:text-accent-hover cursor-pointer py-2 flex items-center gap-1"
                                >
                                    <Plus size={12} /> Add column
                                </button>
                            </div>
                        </div>

                        <OptionsSection
                            connectionId={connectionId}
                            schema={params.schema}
                            table={params.name}
                            mode={mode}
                            tablespace={action.tablespace ?? null}
                            rls={action.rls ?? null}
                            onTablespace={(tablespace) =>
                                setParams({
                                    ...params,
                                    action: { ...action, tablespace },
                                })
                            }
                            onRls={(rls) =>
                                setParams({
                                    ...params,
                                    action: { ...action, rls },
                                })
                            }
                        />

                        <RelationshipsSection
                            connectionId={connectionId}
                            schema={params.schema}
                            table={params.name}
                            createFks={mode === "create" ? createFks : []}
                            onAddFk={() =>
                                setFkPanel({ open: true, column: null })
                            }
                            onRemoveCreateFk={removeCreateFk}
                            onEditCreateFk={editCreateFk}
                            onOpenPanel={(col) =>
                                setFkPanel({ open: true, column: col })
                            }
                        />

                        <AnimatePresence>
                            {fkPanel?.open && (
                                <FkPanel
                                    connectionId={connectionId}
                                    schema={params.schema}
                                    table={params.name}
                                    column={fkPanel.column}
                                    mode={mode ?? "edit"}
                                    localColumns={(action.columns ?? []).map(
                                        (c) => ({
                                            name: c.name,
                                            data_type: c.type,
                                        }),
                                    )}
                                    onStaged={handleFkStaged}
                                    onClose={() => setFkPanel(null)}
                                />
                            )}
                        </AnimatePresence>
                    </>
                ) : (
                    <div>
                        <Suspense
                            fallback={
                                <pre className="text-xs leading-6 font-mono whitespace-pre-wrap text-text">
                                    {preview}
                                </pre>
                            }
                        >
                            <SqlEditorField
                                value={preview}
                                onChange={() => {}}
                                readOnly
                                height={420}
                            />
                        </Suspense>
                    </div>
                )}
            </div>

            {error && (
                <div className="border-t border-border px-4 py-2">
                    <p className="text-xs text-red-400">{error}</p>
                </div>
            )}
        </div>
    );
}

interface ColumnRowProps {
    c: TableFormColumn;
    index: number;
    mode: "create" | "edit";
    setCell: (i: number, key: keyof TableFormColumn, value: unknown) => void;
    onRemove: () => void;
    onFk: () => void;
    hasFk: boolean;
    dbType: DbType | undefined;
}

function ColumnRow({
    c,
    index,
    mode,
    setCell,
    onRemove,
    onFk,
    hasFk,
    dbType,
}: ColumnRowProps) {
    const {
        attributes,
        listeners,
        setNodeRef,
        setActivatorNodeRef,
        transform,
        transition,
        isDragging,
    } = useSortable({ id: c.rowId });

    const [menuOpen, setMenuOpen] = useState(false);
    const cogRef = useRef<HTMLButtonElement | null>(null);
    const [menuPos, setMenuPos] = useState<{
        top: number;
        right: number;
    } | null>(null);

    const toggleMenu = () => {
        if (!menuOpen && cogRef.current) {
            const r = cogRef.current.getBoundingClientRect();
            setMenuPos({
                top: r.bottom + 4,
                right: window.innerWidth - r.right,
            });
        }
        setMenuOpen((v) => !v);
    };

    const constraintsMenu =
        menuOpen && menuPos
            ? createPortal(
                  <>
                      <div
                          className="fixed inset-0 z-40"
                          onClick={() => setMenuOpen(false)}
                      />
                      <div
                          role="menu"
                          className="fixed z-50 bg-surface-raised border border-border rounded-lg shadow-lg p-2 min-w-44 flex flex-col gap-1"
                          style={{ top: menuPos.top, right: menuPos.right }}
                      >
                          <label className="flex items-center gap-2 px-2 py-1 text-xs text-text whitespace-nowrap cursor-pointer">
                              <input
                                  type="checkbox"
                                  aria-label="PK"
                                  checked={c.is_pk}
                                  disabled={mode === "edit"}
                                  onChange={(e) =>
                                      setCell(index, "is_pk", e.target.checked)
                                  }
                                  className="rounded border-border bg-surface text-accent focus:ring-accent"
                              />
                              Primary key
                          </label>
                          {mode === "create" &&
                              (dbType === "sqlite"
                                  ? supportsAutoIncrement(c.type) && c.is_pk
                                  : supportsAutoIncrement(c.type)) && (
                                  <label className="flex items-center gap-2 px-2 py-1 text-xs text-text whitespace-nowrap cursor-pointer">
                                      <input
                                          type="checkbox"
                                          aria-label="Auto-Increment"
                                          checked={c.auto_increment ?? false}
                                          onChange={(e) =>
                                              setCell(
                                                  index,
                                                  "auto_increment",
                                                  e.target.checked,
                                              )
                                          }
                                          className="rounded border-border bg-surface text-accent focus:ring-accent"
                                      />
                                      Auto-Increment
                                  </label>
                              )}
                          {mode === "create" && (
                              <label className="flex items-center gap-2 px-2 py-1 text-xs text-text whitespace-nowrap cursor-pointer">
                                  <input
                                      type="checkbox"
                                      aria-label="Unique"
                                      checked={c.unique ?? false}
                                      onChange={(e) =>
                                          setCell(
                                              index,
                                              "unique",
                                              e.target.checked,
                                          )
                                      }
                                      className="rounded border-border bg-surface text-accent focus:ring-accent"
                                  />
                                  Unique
                              </label>
                          )}
                          <label className="flex items-center gap-2 px-2 py-1 text-xs text-text whitespace-nowrap cursor-pointer">
                              <input
                                  type="checkbox"
                                  aria-label="Nullable"
                                  checked={c.nullable}
                                  onChange={(e) =>
                                      setCell(
                                          index,
                                          "nullable",
                                          e.target.checked,
                                      )
                                  }
                                  className="rounded border-border bg-surface text-accent focus:ring-accent"
                              />
                              Nullable
                          </label>
                      </div>
                  </>,
                  document.body,
              )
            : null;

    return (
        <div
            ref={setNodeRef}
            style={{ transform: CSS.Transform.toString(transform), transition }}
            className={`border-b border-border flex items-stretch w-full min-w-[760px] ${isDragging ? "opacity-60" : ""}`}
        >
            <div className="w-8 shrink-0 border-r border-border px-2 py-2 flex items-center justify-center">
                <button
                    ref={setActivatorNodeRef}
                    {...attributes}
                    {...listeners}
                    type="button"
                    aria-label="Drag to reorder"
                    className="cursor-grab active:cursor-grabbing touch-none text-text-muted hover:text-text transition-colors"
                >
                    <GripVertical size={14} />
                </button>
            </div>
            <div className="w-8 shrink-0 border-r border-border px-3 py-2 flex items-center text-xs font-mono text-text-muted">
                {index + 1}
            </div>
            <div className="flex-1 min-w-48 border-r border-border px-3 py-2 flex items-center gap-2">
                <input
                    className={cellInput}
                    placeholder="name"
                    value={c.name}
                    onChange={(e) => setCell(index, "name", e.target.value)}
                />
                {!c.is_pk && (
                    <button
                        type="button"
                        aria-label="Set foreign key"
                        onClick={onFk}
                        title={
                            hasFk
                                ? "This column has a foreign key"
                                : "Set foreign key"
                        }
                        className={`shrink-0 transition-colors ${hasFk ? "text-accent" : "text-text-muted hover:text-accent"}`}
                    >
                        <Link size={12} />
                    </button>
                )}
            </div>
            <div className="flex-1 min-w-40 border-r border-border px-3 py-2 flex items-center gap-1.5">
                <DataTypeIcon dataType={c.type} size={12} />
                <select
                    aria-label="type"
                    value={c.type}
                    onChange={(e) => setCell(index, "type", e.target.value)}
                    className="min-w-0 flex-1 bg-transparent font-mono text-xs text-text outline-none cursor-pointer"
                >
                    {c.type !== "" && !pickTypeList(dbType).includes(c.type) && (
                        <option value={c.type}>{c.type}</option>
                    )}
                    {pickTypeList(dbType).map((t) => (
                        <option key={t} value={t}>
                            {t}
                        </option>
                    ))}
                </select>
            </div>
            <div className="w-24 shrink-0 border-r border-border px-3 py-2 flex items-center">
                <input
                    className={cellMono}
                    placeholder="length"
                    value={c.params ?? ""}
                    onChange={(e) => setCell(index, "params", e.target.value)}
                />
            </div>
            <div className="flex-1 min-w-44 border-r border-border px-3 py-2 flex items-center gap-2">
                <input
                    type="checkbox"
                    aria-label="Has default"
                    checked={c.default !== null}
                    onChange={(e) =>
                        setCell(
                            index,
                            "default",
                            e.target.checked ? (c.default ?? "") : null,
                        )
                    }
                    className="rounded border-border bg-surface text-accent focus:ring-accent"
                />
                <input
                    className={cellMono}
                    placeholder="default"
                    disabled={c.default === null}
                    value={c.default ?? ""}
                    onChange={(e) => setCell(index, "default", e.target.value)}
                />
            </div>
            <div className="w-max shrink-0 border-r border-border px-3 py-2 flex items-center justify-end gap-1">
                <button
                    ref={cogRef}
                    type="button"
                    aria-label="Column settings"
                    onClick={toggleMenu}
                    className="text-text-muted hover:text-text transition-colors"
                >
                    <Settings2 size={12} />
                </button>
                <button
                    type="button"
                    aria-label="Remove column"
                    onClick={onRemove}
                    className="text-text-muted hover:text-red-400"
                >
                    <X size={12} />
                </button>
            </div>
            {constraintsMenu}
        </div>
    );
}

interface OptionsSectionProps {
    connectionId: string;
    schema: string;
    table: string;
    mode: "create" | "edit";
    tablespace: string | null;
    rls: "disable" | "enable" | "force" | null;
    onTablespace: (value: string | null) => void;
    onRls: (value: "disable" | "enable" | "force" | null) => void;
}

function OptionsSection({
    connectionId,
    mode,
    tablespace,
    rls,
    onTablespace,
    onRls,
}: OptionsSectionProps) {
    const [tablespaces, setTablespaces] = useState<TablespaceInfo[]>([]);

    useEffect(() => {
        let active = true;
        cmd.getTablespaces(connectionId)
            .then((ts) => {
                if (active) setTablespaces(Array.isArray(ts) ? ts : []);
            })
            .catch(() => {
                if (active) setTablespaces([]);
            });
        return () => {
            active = false;
        };
    }, [connectionId]);

    return (
        <>
            <FormRow label="Tablespace">
                <select
                    aria-label="Tablespace"
                    value={tablespace ?? ""}
                    onChange={(e) => onTablespace(e.target.value || null)}
                    className={selectTransparent}
                >
                    <option value="">(default)</option>
                    {tablespaces.map((ts) => (
                        <option key={ts.name} value={ts.name}>
                            {ts.name}
                        </option>
                    ))}
                </select>
            </FormRow>

            {mode === "edit" && (
                <FormRow label="Row level security">
                    <div className="flex items-center gap-4 px-3">
                        {[
                            { value: "disable", label: "Disabled" },
                            { value: "enable", label: "Enabled" },
                            { value: "force", label: "Forced" },
                        ].map(({ value, label }) => (
                            <label
                                key={value}
                                className="flex items-center gap-1 text-xs text-text"
                            >
                                <input
                                    type="radio"
                                    name="rls"
                                    value={value}
                                    checked={rls === value}
                                    onChange={() =>
                                        onRls(
                                            value as
                                                | "disable"
                                                | "enable"
                                                | "force",
                                        )
                                    }
                                />
                                {label}
                            </label>
                        ))}
                    </div>
                </FormRow>
            )}
        </>
    );
}

interface RelationshipsSectionProps {
    connectionId: string;
    schema: string;
    table: string;
    /** FKs to inline into a CREATE TABLE (create mode only). */
    createFks?: FkDefinition[];
    onAddFk: () => void;
    onRemoveCreateFk: (fk: FkDefinition) => void;
    onEditCreateFk: (fk: FkDefinition) => void;
    onOpenPanel: (column: string | null) => void;
}

interface FkRow {
    key: string;
    source: "create" | "queued" | "db";
    localTable: string;
    localCols: string[];
    refSchema: string;
    refTable: string;
    refCols: string[];
    onDelete?: string;
    onUpdate?: string;
    changeId?: string;
    constraintName?: string;
    fk?: FkDefinition;
}

/** Normalize a raw FK SQL fragment (ALTER … or pg_get_constraintdef) into structured parts. */
function parseFkSql(
    text: string,
): Omit<FkRow, "key" | "source" | "localTable"> {
    const strip = (s: string) => s.replace(/"/g, "").trim();
    const local = /FOREIGN\s+KEY\s*\(([^)]*)\)/i.exec(text);
    const ref = /REFERENCES\s+([^(\s]+)\s*\(([^)]*)\)/i.exec(text);
    const del = /ON\s+DELETE\s+([A-Z\s]+?)(?=\s+ON\s|$)/i.exec(text);
    const upd = /ON\s+UPDATE\s+([A-Z\s]+?)(?=\s+ON\s|$)/i.exec(text);
    const localCols = local
        ? local[1].split(",").map(strip).filter(Boolean)
        : [];
    const refCols = ref ? ref[2].split(",").map(strip).filter(Boolean) : [];
    let refSchema = "";
    let refTable = "";
    if (ref) {
        const parts = strip(ref[1]).split(".");
        if (parts.length >= 2) {
            refSchema = parts[0];
            refTable = parts.slice(1).join(".");
        } else {
            refTable = parts[0] ?? "";
        }
    }
    return {
        localCols,
        refSchema,
        refTable,
        refCols,
        onDelete: del ? del[1].trim() : undefined,
        onUpdate: upd ? upd[1].trim() : undefined,
    };
}

function RelationshipsSection({
    connectionId,
    schema,
    table,
    createFks = [],
    onAddFk,
    onRemoveCreateFk,
    onEditCreateFk,
    onOpenPanel,
}: RelationshipsSectionProps) {
    const [constraints, setConstraints] = useState<ConstraintInfo[]>([]);
    const queued = useDbViewerStore((s) => s.changesQueue);

    useEffect(() => {
        let active = true;
        cmd.getConstraints(connectionId, schema)
            .then((cs) => {
                if (active)
                    setConstraints(
                        Array.isArray(cs)
                            ? cs.filter((c) => c.table === table)
                            : [],
                    );
            })
            .catch(() => {
                if (active) setConstraints([]);
            });
        return () => {
            active = false;
        };
    }, [connectionId, schema, table]);

    const fks = useMemo(
        () =>
            constraints.filter((c) =>
                (c.definition ?? "").toUpperCase().includes("FOREIGN"),
            ),
        [constraints],
    );

    // FKs staged this session live in the changes queue.
    const queuedFks = useMemo(
        () =>
            queued.filter(
                (q) =>
                    q.type === "ddl" &&
                    q.sql.toUpperCase().includes("FOREIGN KEY") &&
                    q.sql.includes(`"${schema}"."${table}"`),
            ),
        [queued, schema, table],
    );

    const rows = useMemo<FkRow[]>(() => {
        const out: FkRow[] = createFks.map((fk, i) => ({
            key: `cfk-${i}`,
            source: "create",
            localTable: table,
            localCols: fk.columns,
            refSchema: fk.ref_schema,
            refTable: fk.ref_table,
            refCols: fk.ref_columns,
            onDelete: fk.on_delete,
            onUpdate: fk.on_update,
            fk,
        }));
        for (const q of queuedFks) {
            out.push({
                key: q.id,
                source: "queued",
                localTable: table,
                changeId: q.id,
                ...parseFkSql(q.sql),
            });
        }
        for (const c of fks) {
            out.push({
                key: `db-${c.name}`,
                source: "db",
                localTable: table,
                constraintName: c.name,
                ...parseFkSql(c.definition ?? ""),
            });
        }
        return out;
    }, [createFks, queuedFks, fks, table]);

    const removeFk = async (row: FkRow) => {
        if (row.source === "create") {
            if (row.fk) onRemoveCreateFk(row.fk);
            return;
        }
        if (row.source === "queued") {
            useDbViewerStore.getState().removeChange(row.changeId!);
            return;
        }
        // db: stage a DROP CONSTRAINT change
        try {
            const sqls = await cmd.buildObjectDdl(connectionId, "constraint", {
                schema,
                table,
                name: row.constraintName,
                action: { op: "drop" },
            });
            sqls.forEach((sql) =>
                useDbViewerStore.getState().addChange({
                    type: "ddl",
                    sql,
                    description: `Drop FK ${row.constraintName}`,
                }),
            );
        } catch {
            // ignore — preview handles errors
        }
    };

    const editFk = (row: FkRow) => {
        if (row.source === "create") {
            if (row.fk) onEditCreateFk(row.fk);
            return;
        }
        if (row.source === "queued") {
            useDbViewerStore.getState().removeChange(row.changeId!);
        }
        onOpenPanel(row.localCols[0] ?? null);
    };

    return (
        <div>
            <div className="border-b border-border px-4 py-2 flex items-center justify-between">
                <span className="text-[11px] font-semibold text-text-muted uppercase tracking-wider">
                    Foreign keys
                </span>
                <button
                    type="button"
                    aria-label="Add FK"
                    onClick={onAddFk}
                    className="text-xs text-accent hover:text-accent-hover"
                >
                    Add FK
                </button>
            </div>

            {rows.length === 0 && (
                <div className="border-b border-border px-4 py-2">
                    <p className="text-xs text-text-muted">
                        No foreign keys listed.
                    </p>
                </div>
            )}

            {rows.map((row) => (
                <div key={row.key} className="border-b border-border px-4 py-2">
                    <div className="flex items-center justify-between gap-3">
                        <div className="min-w-0 flex-1">
                            <div className="flex items-center gap-1.5">
                                <p className="text-[11px] font-semibold text-text-muted uppercase tracking-wider shrink-0">
                                    Foreign key relation to:
                                </p>
                                <div className="flex flex-row gap-1 items-center bg-text/10 px-2 py-1 rounded">
                                    <Table2
                                        size={12}
                                        className="text-text-muted shrink-0"
                                    />
                                    <span className="font-mono text-xs text-text truncate">
                                        {row.refSchema}.{row.refTable}
                                    </span>
                                </div>
                            </div>
                            <p className="font-mono text-[11px] text-text-muted mt-0.5 truncate">
                                {row.localTable ? `${row.localTable}.` : ""}
                                {row.localCols.join(", ")} → {row.refTable}.
                                {row.refCols.join(", ")}
                                {row.onDelete ? ` · ${row.onDelete}` : ""}
                                {row.onUpdate ? ` · ${row.onUpdate}` : ""}
                            </p>
                        </div>
                        <div className="flex items-center gap-2 shrink-0">
                            <button
                                type="button"
                                aria-label={`Edit FK ${row.refSchema}.${row.refTable}`}
                                onClick={() => editFk(row)}
                                className="text-xs text-accent hover:text-accent-hover cursor-pointer"
                            >
                                Edit
                            </button>
                            <button
                                type="button"
                                aria-label={`Remove FK ${row.refSchema}.${row.refTable}`}
                                onClick={() => removeFk(row)}
                                className="text-xs text-text-muted hover:text-red-400 cursor-pointer"
                            >
                                Remove
                            </button>
                        </div>
                    </div>
                </div>
            ))}
        </div>
    );
}
