"use client";

/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 */
import * as React from "react";
import { useEffect, useRef, useState } from "react";
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
import { $wrapNodeInElement, mergeRegister } from "@lexical/utils";
import {
    $createParagraphNode,
    $createRangeSelection,
    $getSelection,
    $insertNodes,
    $isNodeSelection,
    $isRootOrShadowRoot,
    $setSelection,
    FORMAT_ELEMENT_COMMAND,
    COMMAND_PRIORITY_EDITOR,
    COMMAND_PRIORITY_HIGH,
    COMMAND_PRIORITY_LOW,
    createCommand,
    DRAGOVER_COMMAND,
    DRAGSTART_COMMAND,
    DROP_COMMAND,
    type LexicalCommand,
    type LexicalEditor,
} from "lexical";

import {
    $createImageNode,
    $isImageNode,
    ImageNode,
    type ImagePayload,
} from "@/components/editor/nodes/image-node";
import { CAN_USE_DOM } from "@/components/editor/shared/can-use-dom";
import { Button } from "@/components/ui/button";
import { DialogFooter } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";

export type InsertImagePayload = Readonly<ImagePayload>;

const getDOMSelection = (targetWindow: Window | null): Selection | null =>
    CAN_USE_DOM ? (targetWindow || window).getSelection() : null;

export const INSERT_IMAGE_COMMAND: LexicalCommand<InsertImagePayload> =
    createCommand("INSERT_IMAGE_COMMAND");

/**
 * ✅ payload را با ImageNode جدید سازگار می‌کند
 * - اگر فقط src/altText آمد، بقیه را مقداردهی می‌کند
 */
function normalizeInsertImagePayload(
    payload: Partial<InsertImagePayload> | null | undefined
): InsertImagePayload {
    const p: any = payload ?? {};

    return {
        src: typeof p.src === "string" ? p.src : "",
        altText: typeof p.altText === "string" ? p.altText : "",

        width: typeof p.width === "number" ? p.width : undefined,
        height: typeof p.height === "number" ? p.height : undefined,

        align:
            p.align === "left" ||
                p.align === "center" ||
                p.align === "right" ||
                p.align === "justify"
                ? p.align
                : null,

        showCaption: typeof p.showCaption === "boolean" ? p.showCaption : false,

        captionState:
            typeof p.captionState === "string" ? p.captionState : null,
    } as any;
}

export function InsertImageUriDialogBody({
    onClick,
}: {
    onClick: (payload: InsertImagePayload) => void;
}) {
    const [src, setSrc] = useState("");
    const [altText, setAltText] = useState("");

    const isDisabled = src === "";

    return (
        <div className="grid gap-4 py-4">
            <div className="grid gap-2">
                <Label htmlFor="image-url">Image URL</Label>
                <Input
                    id="image-url"
                    placeholder="i.e. https://source.unsplash.com/random"
                    onChange={(e) => setSrc(e.target.value)}
                    value={src}
                    data-test-id="image-modal-url-input"
                />
            </div>
            <div className="grid gap-2">
                <Label htmlFor="alt-text">Alt Text</Label>
                <Input
                    id="alt-text"
                    placeholder="Random unsplash image"
                    onChange={(e) => setAltText(e.target.value)}
                    value={altText}
                    data-test-id="image-modal-alt-text-input"
                />
            </div>
            <DialogFooter>
                <Button
                    type="submit"
                    disabled={isDisabled}
                    onClick={() =>
                        onClick(normalizeInsertImagePayload({ altText, src }))
                    }
                    data-test-id="image-modal-confirm-btn"
                >
                    Confirm
                </Button>
            </DialogFooter>
        </div>
    );
}

export function InsertImageUploadedDialogBody({
    onClick,
}: {
    onClick: (payload: InsertImagePayload) => void;
}) {
    const [src, setSrc] = useState("");
    const [altText, setAltText] = useState("");

    const isDisabled = src === "";

    const loadImage = (files: FileList | null) => {
        const reader = new FileReader();
        reader.onload = function () {
            if (typeof reader.result === "string") {
                setSrc(reader.result);
            }
            return "";
        };
        if (files !== null) {
            reader.readAsDataURL(files[0]);
        }
    };

    return (
        <div className="grid gap-4 py-4">
            <div className="grid gap-2">
                <Label htmlFor="image-upload">Image Upload</Label>
                <Input
                    id="image-upload"
                    type="file"
                    onChange={(e) => loadImage(e.target.files)}
                    accept="image/*"
                    data-test-id="image-modal-file-upload"
                />
            </div>
            <div className="grid gap-2">
                <Label htmlFor="alt-text">Alt Text</Label>
                <Input
                    id="alt-text"
                    placeholder="Descriptive alternative text"
                    onChange={(e) => setAltText(e.target.value)}
                    value={altText}
                    data-test-id="image-modal-alt-text-input"
                />
            </div>
            <Button
                type="submit"
                disabled={isDisabled}
                onClick={() =>
                    onClick(normalizeInsertImagePayload({ altText, src }))
                }
                data-test-id="image-modal-file-upload-btn"
            >
                Confirm
            </Button>
        </div>
    );
}

export function InsertImageDialog({
    activeEditor,
    onClose,
}: {
    activeEditor: LexicalEditor;
    onClose: () => void;
}): React.ReactElement {
    const hasModifier = useRef(false);

    useEffect(() => {
        hasModifier.current = false;
        const handler = (e: KeyboardEvent) => {
            hasModifier.current = e.altKey;
        };
        document.addEventListener("keydown", handler);
        return () => {
            document.removeEventListener("keydown", handler);
        };
    }, [activeEditor]);

    const onClick = (payload: InsertImagePayload) => {
        // ✅ حتی اگر dispatch خطا داد، دیالوگ حتماً بسته شود
        try {
            activeEditor.dispatchCommand(INSERT_IMAGE_COMMAND, payload);
        } finally {
            onClose();
        }
    };

    return (
        <Tabs defaultValue="url">
            <TabsList className="w-full">
                <TabsTrigger value="url" className="w-full">
                    URL
                </TabsTrigger>
                <TabsTrigger value="file" className="w-full">
                    File
                </TabsTrigger>
            </TabsList>
            <TabsContent value="url">
                <InsertImageUriDialogBody onClick={onClick} />
            </TabsContent>
            <TabsContent value="file">
                <InsertImageUploadedDialogBody onClick={onClick} />
            </TabsContent>
        </Tabs>
    );
}

export function ImagesPlugin({
    captionsEnabled,
}: {
    captionsEnabled?: boolean;
}): React.ReactElement | null {
    const [editor] = useLexicalComposerContext();

    useEffect(() => {
        if (!editor.hasNodes([ImageNode])) {
            throw new Error("ImagesPlugin: ImageNode not registered on editor");
        }

        return mergeRegister(
            editor.registerCommand<InsertImagePayload>(
                INSERT_IMAGE_COMMAND,
                (payload) => {
                    // ✅ اینجا دیگر اجازه نمی‌دهیم payload ناقص باعث throw شود
                    const normalized = normalizeInsertImagePayload(payload);

                    if (!normalized.src) return true;

                    const imageNode = $createImageNode(normalized as any);
                    $insertNodes([imageNode]);

                    if ($isRootOrShadowRoot(imageNode.getParentOrThrow())) {
                        $wrapNodeInElement(
                            imageNode,
                            $createParagraphNode
                        ).selectEnd();
                    }

                    return true;
                },
                COMMAND_PRIORITY_EDITOR
            ),
            editor.registerCommand<DragEvent>(
                DRAGSTART_COMMAND,
                (event) => $onDragStart(event),
                COMMAND_PRIORITY_HIGH
            ),
            editor.registerCommand<DragEvent>(
                DRAGOVER_COMMAND,
                (event) => $onDragover(event),
                COMMAND_PRIORITY_LOW
            ),
            editor.registerCommand<DragEvent>(
                DROP_COMMAND,
                (event) => $onDrop(event, editor),
                COMMAND_PRIORITY_HIGH
            ),
            // ✅ Fix Alignment Persistence: Update ImageNode.__align when toolbar buttons are clicked
            editor.registerCommand(
                FORMAT_ELEMENT_COMMAND,
                (formatType) => {
                    const selection = $getSelection();
                    if ($isNodeSelection(selection)) {
                        const node = selection.getNodes()[0];
                        if ($isImageNode(node)) {
                            // Map generic format types to valid alignment types
                            const validAligns = ["left", "center", "right", "justify"];
                            if (validAligns.includes(formatType)) {
                                editor.update(() => {
                                    node.setAlign(formatType as any);
                                });
                                return false; // ✅ Allow bubbling so parent Paragraph also gets aligned
                            }
                        }
                    }
                    return false;
                },
                COMMAND_PRIORITY_EDITOR
            )
        );
    }, [captionsEnabled, editor]);

    return null;
}

function $onDragStart(event: DragEvent): boolean {
    const node = $getImageNodeInSelection();
    if (!node) return false;

    const dataTransfer = event.dataTransfer;
    if (!dataTransfer) return false;

    const TRANSPARENT_IMAGE =
        "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
    const img = document.createElement("img");
    img.src = TRANSPARENT_IMAGE;

    dataTransfer.setData("text/plain", "_");
    dataTransfer.setDragImage(img, 0, 0);

    // ✅ دیگر به __caption و __maxWidth دست نمی‌زنیم (در ImageNode جدید نیست)
    const latest: any =
        typeof (node as any).getLatest === "function"
            ? (node as any).getLatest()
            : (node as any);

    const dragPayload = normalizeInsertImagePayload({
        src: latest.__src ?? "",
        altText: latest.__altText ?? "",
        width: typeof latest.__width === "number" ? latest.__width : undefined,
        height:
            typeof latest.__height === "number" ? latest.__height : undefined,
        showCaption:
            typeof latest.__showCaption === "boolean"
                ? latest.__showCaption
                : false,
        captionState:
            typeof latest.__captionStateJson === "string"
                ? latest.__captionStateJson
                : null,
        align:
            latest.__align === "left" ||
                latest.__align === "center" ||
                latest.__align === "right" ||
                latest.__align === "justify"
                ? latest.__align
                : null,
    } as any);

    dataTransfer.setData(
        "application/x-lexical-drag",
        JSON.stringify({
            data: dragPayload,
            type: "image",
        })
    );

    return true;
}

function $onDragover(event: DragEvent): boolean {
    const node = $getImageNodeInSelection();
    if (!node) return false;

    if (!canDropImage(event)) {
        event.preventDefault();
    }
    return true;
}

function $onDrop(event: DragEvent, editor: LexicalEditor): boolean {
    const node = $getImageNodeInSelection();
    if (!node) return false;

    const data = getDragImageData(event);
    if (!data) return false;

    event.preventDefault();

    if (canDropImage(event)) {
        const range = getDragSelection(event);
        node.remove();

        const rangeSelection = $createRangeSelection();
        if (range !== null && range !== undefined) {
            rangeSelection.applyDOMRange(range);
        }
        $setSelection(rangeSelection);

        editor.dispatchCommand(INSERT_IMAGE_COMMAND, data);
    }

    return true;
}

function $getImageNodeInSelection(): ImageNode | null {
    const selection = $getSelection();
    if (!$isNodeSelection(selection)) return null;

    const nodes = selection.getNodes();
    const node = nodes[0];
    return $isImageNode(node) ? node : null;
}

function getDragImageData(event: DragEvent): null | InsertImagePayload {
    const dragData = event.dataTransfer?.getData("application/x-lexical-drag");
    if (!dragData) return null;

    const parsed = JSON.parse(dragData);
    const type = parsed?.type;
    const data = parsed?.data;

    if (type !== "image") return null;

    return normalizeInsertImagePayload(data);
}

declare global {
    interface DragEvent {
        rangeOffset?: number;
        rangeParent?: Node;
    }
}

function canDropImage(event: DragEvent): boolean {
    const target = event.target;
    return !!(
        target &&
        target instanceof HTMLElement &&
        !target.closest("code, span.editor-image") &&
        target.parentElement &&
        target.parentElement.closest("div.ContentEditable__root")
    );
}

function getDragSelection(event: DragEvent): Range | null | undefined {
    let range;
    const target = event.target as null | Element | Document;
    const targetWindow =
        target == null
            ? null
            : target.nodeType === 9
                ? (target as Document).defaultView
                : (target as Element).ownerDocument.defaultView;
    const domSelection = getDOMSelection(targetWindow);

    if (document.caretRangeFromPoint) {
        range = document.caretRangeFromPoint(event.clientX, event.clientY);
    } else if (event.rangeParent && domSelection !== null) {
        domSelection.collapse(event.rangeParent, event.rangeOffset || 0);
        range = domSelection.getRangeAt(0);
    } else {
        throw Error(`Cannot get the selection when dragging`);
    }

    return range;
}
