import { AUDIO_EXTS, IMAGE_EXTS, MODEL3D_EXTS, VIDEO_EXTS } from "./utils/constants.js";
import { pickBestMediaPathWidget } from "./targets/node.js";
import { ensureComboHasValue } from "./targets/node.js";
import { markCanvasDirty } from "./targets/canvas.js";
import { createHostNode } from "../../app/hostAdapter.js";

const _getPayloadExt = (payload: any) => {
    const filename = String(payload?.filename || "");
    const dot = filename.lastIndexOf(".");
    return dot >= 0 ? filename.slice(dot).toLowerCase() : "";
};

const _getLoaderNodeCandidates = (payload: any) => {
    const kind = String(payload?.kind || "").toLowerCase();
    const ext = _getPayloadExt(payload);
    if (kind === "image" || IMAGE_EXTS.has(ext)) return ["LoadImage"];
    if (kind === "video" || VIDEO_EXTS.has(ext)) {
        return ["LoadVideo", "VHS_LoadVideo", "VideoLoader"];
    }
    if (kind === "audio" || AUDIO_EXTS.has(ext)) {
        return ["LoadAudio", "VHS_LoadAudioUpload", "VHS_LoadAudio", "AudioLoader"];
    }
    if (kind === "model3d" || MODEL3D_EXTS.has(ext)) {
        return ["Load3D", "LoadModel", "LoadMesh", "LoadGLB", "LoadOBJ", "LoadSTL", "LoadPLY"];
    }
    return [];
};

const _setWidgetValue = (widget: any, value: any) => {
    if (!widget) return;
    if (widget.type === "combo") ensureComboHasValue(widget, value);
    widget.value = value;
    try {
        widget.callback?.(widget.value);
    } catch (e: any) {
        console.debug?.(e);
    }
    try {
        if (typeof widget.onchange === "function") {
            widget.onchange(value);
        }
    } catch (e: any) {
        console.debug?.(e);
    }
};

const _getCanvasCenterPos = (app: any) => {
    const canvasEl = app?.canvas?.canvas || document.querySelector("canvas");
    const rect = canvasEl?.getBoundingClientRect?.();
    const rectWidth = rect ? Number(rect.width || rect.right - rect.left) : 0;
    const rectHeight = rect ? Number(rect.height || rect.bottom - rect.top) : 0;
    const width = Number(rectWidth || canvasEl?.width || 800);
    const height = Number(rectHeight || canvasEl?.height || 600);
    if (!rect) return [Math.max(0, width / 2), Math.max(0, height / 2)];
    const left = rect ? Number(rect.left) || 0 : 0;
    const top = rect ? Number(rect.top) || 0 : 0;
    return _getCanvasPosFromClient(app, left + Math.max(0, width / 2), top + Math.max(0, height / 2));
};

const _getCanvasPosFromClient = (app: any, clientX: any, clientY: any) => {
    const canvasEl = app?.canvas?.canvas || document.querySelector("canvas");
    const rect = canvasEl?.getBoundingClientRect?.();
    const ds = app?.canvas?.ds || {};
    const scale = Number(ds.scale) || 1;
    const off = ds.offset || [0, 0];
    const offX = Array.isArray(off) ? Number(off[0]) || 0 : Number(off?.x) || 0;
    const offY = Array.isArray(off) ? Number(off[1]) || 0 : Number(off?.y) || 0;
    if (!rect || !Number.isFinite(Number(clientX)) || !Number.isFinite(Number(clientY))) {
        return _getCanvasCenterPos(app);
    }
    return [
        (Number(clientX) - rect.left) / scale - offX,
        (Number(clientY) - rect.top) / scale - offY,
    ];
};

export const getDropCanvasPos = (app: any, event: any = null) => {
    if (Number.isFinite(Number(event?.clientX)) && Number.isFinite(Number(event?.clientY))) {
        return _getCanvasPosFromClient(app, event.clientX, event.clientY);
    }
    try {
        const pos = app?.canvas?.convertEventToCanvasOffset?.(event);
        if (Array.isArray(pos)) return [Number(pos[0]) || 0, Number(pos[1]) || 0];
    } catch (e: any) {
        console.debug?.(e);
    }
    return _getCanvasPosFromClient(app, event?.clientX, event?.clientY);
};

export const createCanvasLoaderNode = ({
    app,
    payload,
    relativePath,
    event = null,
    droppedExt = "",
    position = null,
 }: any) => {
    const graph = app?.graph ?? app?.canvas?.graph ?? null;
    if (!graph || typeof graph.add !== "function") return false;

    let created: any = null;
    for (const nodeType of _getLoaderNodeCandidates(payload)) {
        try {
            created = createHostNode(nodeType, app);
            if (created) break;
        } catch (e: any) {
            console.debug?.(e);
        }
    }
    if (!created) return false;

    created.pos = Array.isArray(position)
        ? [Number(position[0]) || 0, Number(position[1]) || 0]
        : getDropCanvasPos(app, event);
    graph.add(created);

    const widget = pickBestMediaPathWidget(created, payload, droppedExt);
    if (widget) _setWidgetValue(widget, relativePath);
    markCanvasDirty(app);
    return true;
};

export const createCanvasLoaderNodes = ({  app, items = [], event = null, gap = 90  }: any) => {
    const list = Array.isArray(items) ? items.filter(Boolean) : [];
    if (!list.length) return 0;
    const origin = getDropCanvasPos(app, event);
    let count = 0;
    for (const item of list) {
        const ok = createCanvasLoaderNode({
            app,
            payload: item.payload,
            relativePath: item.relativePath,
            event,
            droppedExt: item.droppedExt,
            position: [origin[0] + count * Number(gap || 90), origin[1]],
        });
        if (ok) count += 1;
    }
    return count;
};

export const writeMediaPathWidgetValue = _setWidgetValue;

export const createLoaderAndConnect = ({
    app,
    payload,
    relativePath,
    targetNode,
    inputSlotIndex,
    event = null,
 }: any) => {
    const graph = app?.graph ?? app?.canvas?.graph ?? null;
    if (!graph || typeof graph.add !== "function" || !targetNode) return false;

    let loaderNode: any = null;
    for (const nodeType of _getLoaderNodeCandidates(payload)) {
        try {
            loaderNode = createHostNode(nodeType, app);
            if (loaderNode) break;
        } catch (e: any) {
            console.debug?.(e);
        }
    }
    if (!loaderNode) return false;

    const targetX = Number(targetNode.pos[0]) || 0;
    const targetY = Number(targetNode.pos[1]) || 0;
    const sizeW = Array.isArray(loaderNode.size) ? Number(loaderNode.size[0]) || 220 : 220;
    loaderNode.pos = [targetX - sizeW - 60, targetY + Number(inputSlotIndex) * 20];

    graph.add(loaderNode);

    const droppedExt = _getPayloadExt(payload);
    const widget = pickBestMediaPathWidget(loaderNode, payload, droppedExt);
    if (widget) {
        _setWidgetValue(widget, relativePath);
    }

    try {
        loaderNode.connect(0, targetNode, inputSlotIndex);
    } catch (e: any) {
        console.debug?.(e);
    }

    markCanvasDirty(app);
    return true;
};
