/** @odoo-module **/

import { registry } from "@web/core/registry";
import { Component, useState, onWillStart, onMounted, onWillUnmount, useRef } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks";
import { _t } from "@web/core/l10n/translation";

/* ──────────────────────────────────────────────────────────────────
   Constants
   ────────────────────────────────────────────────────────────────── */
const NODE_TYPES = {
    start:             { label: 'Start',              icon: 'fa-play',           color: '#10b981', width: 140, height: 50 },
    end:               { label: 'End',                icon: 'fa-stop',           color: '#ef4444', width: 140, height: 50 },
    decision:          { label: 'Decision',           icon: 'fa-code-fork',      color: '#f59e0b', width: 180, height: 80 },
    option_group:      { label: 'Option Group',       icon: 'fa-list-ul',        color: '#3b82f6', width: 200, height: 80 },
    product_select:    { label: 'Product Selection',  icon: 'fa-shopping-cart',   color: '#8b5cf6', width: 200, height: 80 },
    measurement_check: { label: 'Measurement Check',  icon: 'fa-ruler',          color: '#f97316', width: 200, height: 80 },
    action:            { label: 'Action',             icon: 'fa-bolt',           color: '#14b8a6', width: 180, height: 70 },
};

const GRID_SIZE = 20;
const MIN_ZOOM = 0.25;
const MAX_ZOOM = 3;
const PORT_RADIUS = 7;

/* ──────────────────────────────────────────────────────────────────
   Utility helpers
   ────────────────────────────────────────────────────────────────── */
function bezierPath(x1, y1, x2, y2) {
    const dx = Math.abs(x2 - x1) * 0.5;
    return `M${x1},${y1} C${x1 + dx},${y1} ${x2 - dx},${y2} ${x2},${y2}`;
}

function getPortsForNode(node) {
    const meta = NODE_TYPES[node.node_type] || NODE_TYPES.action;
    const w = meta.width;
    const h = meta.height;
    const ports = [];

    // Input port (all except start)
    if (node.node_type !== 'start') {
        ports.push({ key: 'in', type: 'input', x: 0, y: h / 2 });
    }

    // Output ports
    if (node.node_type === 'decision') {
        ports.push({ key: 'true',  type: 'output', x: w, y: h * 0.33, label: 'Yes' });
        ports.push({ key: 'false', type: 'output', x: w, y: h * 0.67, label: 'No' });
    } else if (node.node_type === 'measurement_check') {
        ports.push({ key: 'pass', type: 'output', x: w, y: h * 0.33, label: 'Pass' });
        ports.push({ key: 'fail', type: 'output', x: w, y: h * 0.67, label: 'Fail' });
    } else if (node.node_type === 'option_group' && node.node_options && node.node_options.length) {
        const count = node.node_options.length;
        node.node_options.forEach((opt, i) => {
            ports.push({
                key: opt.port_key || `opt_${opt.sequence || (i * 10 + 10)}`,
                type: 'output',
                x: w,
                y: h * ((i + 1) / (count + 1)),
                label: opt.name,
            });
        });
    } else if (node.node_type !== 'end') {
        ports.push({ key: 'out', type: 'output', x: w, y: h / 2 });
    }

    return ports;
}

function snapToGrid(val) {
    return Math.round(val / GRID_SIZE) * GRID_SIZE;
}


/* ══════════════════════════════════════════════════════════════════
   FlowDesignerAction — main OWL client action
   ══════════════════════════════════════════════════════════════════ */
export class FlowDesignerAction extends Component {
    static template = "fusion_quotations.FlowDesignerAction";
    static props = { "*": true };

    setup() {
        this.orm = useService("orm");
        this.actionService = useService("action");
        this.notification = useService("notification");

        this.svgRef = useRef("svgCanvas");
        this.canvasGroupRef = useRef("canvasGroup");

        this.flowId = this.props.action?.context?.active_id || false;

        this.state = useState({
            flowName: '',
            equipmentType: '',
            nodes: [],
            connections: [],
            selectedNodeId: null,
            selectedConnectionId: null,
            dirty: false,
            saving: false,
            loading: true,
            panelOpen: false,

            // Viewport
            viewX: 0,
            viewY: 0,
            zoom: 1,
        });

        // Interaction state (non-reactive, doesn't need re-render)
        this._dragging = null;       // { nodeId, startX, startY, origX, origY }
        this._panning = null;        // { startX, startY, origVX, origVY }
        this._connecting = null;     // { sourceNodeId, sourcePort, tempX, tempY }
        this._tempLine = null;       // SVG path element for connection preview

        onWillStart(async () => {
            if (this.flowId) {
                await this._loadGraph();
            }
        });

        onMounted(() => {
            this._bindCanvasEvents();
        });

        onWillUnmount(() => {
            this._unbindCanvasEvents();
        });
    }

    /* ──────────────────────────────────────────────────────────────
       Data loading / saving
       ────────────────────────────────────────────────────────────── */
    async _loadGraph() {
        this.state.loading = true;
        try {
            const graph = await this.orm.call(
                'fusion.wc.config.flow',
                'load_flow_graph',
                [this.flowId]
            );
            this.state.flowName = graph.name || '';
            this.state.equipmentType = graph.equipment_type || '';
            this.state.nodes = graph.nodes || [];
            this.state.connections = graph.connections || [];

            if (graph.canvas) {
                this.state.viewX = graph.canvas.x || 0;
                this.state.viewY = graph.canvas.y || 0;
                this.state.zoom = graph.canvas.zoom || 1;
            }
        } catch (e) {
            this.notification.add(_t("Error loading flow: ") + e.message, { type: "danger" });
            console.error("Load flow error:", e);
        }
        this.state.loading = false;
    }

    async _saveGraph() {
        if (!this.flowId) return;
        this.state.saving = true;
        try {
            await this.orm.call(
                'fusion.wc.config.flow',
                'save_flow_graph',
                [this.flowId, {
                    canvas: { x: this.state.viewX, y: this.state.viewY, zoom: this.state.zoom },
                    nodes: this.state.nodes,
                    connections: this.state.connections,
                }]
            );
            this.state.dirty = false;
            this.notification.add(_t("Flow saved successfully"), { type: "success" });
            // Reload to get real IDs from server
            await this._loadGraph();
        } catch (e) {
            this.notification.add(_t("Error saving flow: ") + e.message, { type: "danger" });
            console.error("Save flow error:", e);
        }
        this.state.saving = false;
    }

    /* ──────────────────────────────────────────────────────────────
       Coordinate transforms
       ────────────────────────────────────────────────────────────── */
    screenToCanvas(sx, sy) {
        const svg = this.svgRef.el;
        if (!svg) return { x: sx, y: sy };
        const rect = svg.getBoundingClientRect();
        return {
            x: (sx - rect.left - this.state.viewX) / this.state.zoom,
            y: (sy - rect.top - this.state.viewY) / this.state.zoom,
        };
    }

    /* ──────────────────────────────────────────────────────────────
       Computed data for template
       ────────────────────────────────────────────────────────────── */
    get transformStr() {
        return `translate(${this.state.viewX}, ${this.state.viewY}) scale(${this.state.zoom})`;
    }

    get nodesWithPorts() {
        return this.state.nodes.map(n => {
            const meta = NODE_TYPES[n.node_type] || NODE_TYPES.action;
            return {
                ...n,
                width: meta.width,
                height: meta.height,
                meta,
                ports: getPortsForNode(n),
                isSelected: n.id === this.state.selectedNodeId,
            };
        });
    }

    get connectionPaths() {
        const nodeMap = {};
        for (const n of this.state.nodes) {
            const meta = NODE_TYPES[n.node_type] || NODE_TYPES.action;
            nodeMap[n.id] = { ...n, width: meta.width, height: meta.height, ports: getPortsForNode(n) };
        }
        return this.state.connections.map(c => {
            const src = nodeMap[c.source_node_id];
            const tgt = nodeMap[c.target_node_id];
            if (!src || !tgt) return null;

            const srcPort = src.ports.find(p => p.key === c.source_port) || src.ports.find(p => p.type === 'output');
            const tgtPort = tgt.ports.find(p => p.type === 'input') || { x: 0, y: tgt.height / 2 };

            if (!srcPort) return null;

            const x1 = src.pos_x + srcPort.x;
            const y1 = src.pos_y + srcPort.y;
            const x2 = tgt.pos_x + tgtPort.x;
            const y2 = tgt.pos_y + tgtPort.y;

            const label = c.label || '';
            const labelW = Math.max(label.length * 7 + 16, 32);  // approx width from char count

            return {
                ...c,
                path: bezierPath(x1, y1, x2, y2),
                isSelected: c.id === this.state.selectedConnectionId,
                midX: (x1 + x2) / 2,
                midY: (y1 + y2) / 2,
                labelW,
            };
        }).filter(Boolean);
    }

    getNodeTypeLabel(nodeType) {
        return (NODE_TYPES[nodeType] || {}).label || nodeType;
    }

    /* ──────────────────────────────────────────────────────────────
       Toolbar / Palette actions
       ────────────────────────────────────────────────────────────── */
    onAddNode(ev) {
        const nodeType = ev.currentTarget.dataset.nodeType;
        if (!nodeType) return;
        const meta = NODE_TYPES[nodeType];
        if (!meta) return;

        // Place in center of current viewport
        const svg = this.svgRef.el;
        const rect = svg ? svg.getBoundingClientRect() : { width: 800, height: 600 };
        const pos = this.screenToCanvas(rect.width / 2, rect.height / 2);

        const tempId = 'new_' + Date.now() + '_' + Math.random().toString(36).substr(2, 5);
        const newNode = {
            id: tempId,
            name: meta.label,
            node_type: nodeType,
            pos_x: snapToGrid(pos.x - meta.width / 2),
            pos_y: snapToGrid(pos.y - meta.height / 2),
            color: meta.color,
            icon: meta.icon,
            section_id: false,
            section_name: '',
            decision_field: '',
            decision_operator: '',
            decision_value: '',
            measurement_field: '',
            comparison: '',
            threshold_value: 0,
            action_type: '',
            target_option_ids: [],
            target_step: 0,
            config_json: '{}',
            node_options: [],
        };

        this.state.nodes.push(newNode);
        this.state.selectedNodeId = tempId;
        this.state.selectedConnectionId = null;
        this.state.panelOpen = true;
        this.state.dirty = true;
    }

    onDeleteSelected() {
        if (this.state.selectedConnectionId) {
            this.state.connections = this.state.connections.filter(
                c => c.id !== this.state.selectedConnectionId
            );
            this.state.selectedConnectionId = null;
            this.state.dirty = true;
        }
        if (this.state.selectedNodeId) {
            const nodeId = this.state.selectedNodeId;
            this.state.nodes = this.state.nodes.filter(n => n.id !== nodeId);
            this.state.connections = this.state.connections.filter(
                c => c.source_node_id !== nodeId && c.target_node_id !== nodeId
            );
            this.state.selectedNodeId = null;
            this.state.panelOpen = false;
            this.state.dirty = true;
        }
    }

    onSave() {
        this._saveGraph();
    }

    onZoomIn() {
        this.state.zoom = Math.min(MAX_ZOOM, this.state.zoom * 1.2);
    }

    onZoomOut() {
        this.state.zoom = Math.max(MIN_ZOOM, this.state.zoom / 1.2);
    }

    onZoomReset() {
        this.state.zoom = 1;
        this.state.viewX = 0;
        this.state.viewY = 0;
    }

    onBack() {
        // Navigate back to the flow form / list.
        // history.back() is the most reliable way to return to the
        // previous Odoo view that opened this client action.
        window.history.back();
    }

    /* ──────────────────────────────────────────────────────────────
       Canvas events
       ────────────────────────────────────────────────────────────── */
    _bindCanvasEvents() {
        const svg = this.svgRef.el;
        if (!svg) return;

        this._onMouseDown = this._handleMouseDown.bind(this);
        this._onMouseMove = this._handleMouseMove.bind(this);
        this._onMouseUp = this._handleMouseUp.bind(this);
        this._onWheel = this._handleWheel.bind(this);
        this._onKeyDown = this._handleKeyDown.bind(this);

        svg.addEventListener('mousedown', this._onMouseDown);
        window.addEventListener('mousemove', this._onMouseMove);
        window.addEventListener('mouseup', this._onMouseUp);
        svg.addEventListener('wheel', this._onWheel, { passive: false });
        window.addEventListener('keydown', this._onKeyDown);
    }

    _unbindCanvasEvents() {
        const svg = this.svgRef.el;
        if (svg) {
            svg.removeEventListener('mousedown', this._onMouseDown);
            svg.removeEventListener('wheel', this._onWheel);
        }
        window.removeEventListener('mousemove', this._onMouseMove);
        window.removeEventListener('mouseup', this._onMouseUp);
        window.removeEventListener('keydown', this._onKeyDown);
    }

    /* ──────────────────────────────────────────────────────────────
       Port interaction — pure coordinate math.
       No DOM queries (elementsFromPoint / ev.target) — works
       entirely from component state + getPortsForNode().
       ────────────────────────────────────────────────────────────── */

    /**
     * Find the nearest port within hit radius using coordinate math.
     * @param {number} cx — x in canvas coordinate space
     * @param {number} cy — y in canvas coordinate space
     * @param {'input'|'output'} portType
     * @returns {{ node: Object, port: Object, x: number, y: number }|null}
     */
    _findPortNear(cx, cy, portType) {
        const HIT_R = 20;              // canvas-unit hit radius (~20px at zoom 1)
        const rSq = HIT_R * HIT_R;
        let best = null;
        let bestDist = Infinity;

        for (const node of this.state.nodes) {
            const ports = getPortsForNode(node);
            for (const port of ports) {
                if (port.type !== portType) continue;
                const px = node.pos_x + port.x;
                const py = node.pos_y + port.y;
                const dx = cx - px;
                const dy = cy - py;
                const distSq = dx * dx + dy * dy;
                if (distSq <= rSq && distSq < bestDist) {
                    bestDist = distSq;
                    best = { node, port, x: px, y: py };
                }
            }
        }
        return best;
    }

    _handleMouseDown(ev) {
        if (ev.button !== 0 && ev.button !== 1) return;

        const canvasPos = this.screenToCanvas(ev.clientX, ev.clientY);

        // ── 1. Port detection via coordinate math ──
        // No DOM queries — purely checks distance to port centres.
        if (ev.button === 0) {
            const outHit = this._findPortNear(canvasPos.x, canvasPos.y, 'output');
            if (outHit) {
                this._connecting = {
                    sourceNodeId: outHit.node.id,
                    sourcePort: outHit.port.key,
                    startX: outHit.x,
                    startY: outHit.y,
                };
                // Create temp bezier for visual feedback
                const svgNs = 'http://www.w3.org/2000/svg';
                this._tempLine = document.createElementNS(svgNs, 'path');
                this._tempLine.classList.add('fd-connection-temp');
                this._tempLine.setAttribute('stroke-width', '2');
                this._tempLine.setAttribute('pointer-events', 'none');
                const group = this.canvasGroupRef.el;
                if (group) group.appendChild(this._tempLine);
                ev.preventDefault();
                ev.stopPropagation();
                return;
            }

            // Input port click — block drag/pan, do nothing
            const inHit = this._findPortNear(canvasPos.x, canvasPos.y, 'input');
            if (inHit) {
                ev.stopPropagation();
                return;
            }
        }

        // If already connecting, ignore other interactions
        if (this._connecting) return;

        // ── 2. Node drag ──
        const target = ev.target;
        const nodeGroup = target.closest('.fd-node-group');
        if (nodeGroup && ev.button === 0) {
            const nodeId = nodeGroup.dataset.nodeId;
            const nid = isNaN(nodeId) ? nodeId : parseInt(nodeId);
            const node = this.state.nodes.find(n => n.id === nid || String(n.id) === nodeId);
            if (node) {
                this.state.selectedNodeId = node.id;
                this.state.selectedConnectionId = null;
                this.state.panelOpen = true;
                this._dragging = {
                    nodeId: node.id,
                    startX: ev.clientX,
                    startY: ev.clientY,
                    origX: node.pos_x,
                    origY: node.pos_y,
                };
                ev.stopPropagation();
                return;
            }
        }

        // ── 3. Connection click ──
        if (target.classList.contains('fd-connection-path') || target.classList.contains('fd-connection-hit')) {
            const connId = target.dataset.connId;
            if (connId) {
                const cid = isNaN(connId) ? connId : parseInt(connId);
                this.state.selectedConnectionId = cid;
                this.state.selectedNodeId = null;
                this.state.panelOpen = false;
                ev.stopPropagation();
                return;
            }
        }

        // ── 4. Canvas pan ──
        if (ev.button === 1 || (ev.button === 0 && !nodeGroup)) {
            this.state.selectedNodeId = null;
            this.state.selectedConnectionId = null;
            this.state.panelOpen = false;
            this._panning = {
                startX: ev.clientX,
                startY: ev.clientY,
                origVX: this.state.viewX,
                origVY: this.state.viewY,
            };
            ev.preventDefault();
        }
    }

    _handleMouseMove(ev) {
        if (this._dragging) {
            const dx = (ev.clientX - this._dragging.startX) / this.state.zoom;
            const dy = (ev.clientY - this._dragging.startY) / this.state.zoom;
            const node = this.state.nodes.find(n => n.id === this._dragging.nodeId);
            if (node) {
                node.pos_x = snapToGrid(this._dragging.origX + dx);
                node.pos_y = snapToGrid(this._dragging.origY + dy);
            }
            return;
        }

        if (this._panning) {
            this.state.viewX = this._panning.origVX + (ev.clientX - this._panning.startX);
            this.state.viewY = this._panning.origVY + (ev.clientY - this._panning.startY);
            return;
        }

        if (this._connecting && this._tempLine) {
            const pos = this.screenToCanvas(ev.clientX, ev.clientY);
            this._tempLine.setAttribute('d',
                bezierPath(this._connecting.startX, this._connecting.startY, pos.x, pos.y));
            return;
        }

        // Port hover cursor — crosshair when near any port
        const svg = this.svgRef.el;
        if (svg) {
            const canvasPos = this.screenToCanvas(ev.clientX, ev.clientY);
            const hit = this._findPortNear(canvasPos.x, canvasPos.y, 'output')
                     || this._findPortNear(canvasPos.x, canvasPos.y, 'input');
            svg.style.cursor = hit ? 'crosshair' : '';
        }
    }

    _handleMouseUp(ev) {
        if (this._dragging) {
            this.state.dirty = true;
            this._dragging = null;
            return;
        }

        if (this._panning) {
            this._panning = null;
            return;
        }

        if (this._connecting) {
            const canvasPos = this.screenToCanvas(ev.clientX, ev.clientY);
            const inHit = this._findPortNear(canvasPos.x, canvasPos.y, 'input');

            if (inHit) {
                const srcId = this._connecting.sourceNodeId;
                const tgtId = inHit.node.id;

                // Prevent self-connection
                if (String(tgtId) !== String(srcId)) {
                    // Check for duplicate
                    const exists = this.state.connections.some(
                        c => c.source_node_id === srcId &&
                             c.target_node_id === tgtId &&
                             c.source_port === this._connecting.sourcePort
                    );
                    if (!exists) {
                        this.state.connections.push({
                            id: 'new_conn_' + Date.now(),
                            source_node_id: srcId,
                            target_node_id: tgtId,
                            source_port: this._connecting.sourcePort,
                            label: '',
                            condition_json: '{}',
                            sequence: 10,
                        });
                        this.state.dirty = true;
                    }
                }
            }

            // Clean up temp line
            if (this._tempLine && this._tempLine.parentNode) {
                this._tempLine.parentNode.removeChild(this._tempLine);
            }
            this._tempLine = null;
            this._connecting = null;
        }
    }

    _handleWheel(ev) {
        ev.preventDefault();
        const delta = ev.deltaY > 0 ? 0.9 : 1.1;
        const newZoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, this.state.zoom * delta));

        // Zoom toward cursor position
        const svg = this.svgRef.el;
        const rect = svg.getBoundingClientRect();
        const mx = ev.clientX - rect.left;
        const my = ev.clientY - rect.top;

        const scale = newZoom / this.state.zoom;
        this.state.viewX = mx - (mx - this.state.viewX) * scale;
        this.state.viewY = my - (my - this.state.viewY) * scale;
        this.state.zoom = newZoom;
    }

    _handleKeyDown(ev) {
        if (ev.key === 'Delete' || ev.key === 'Backspace') {
            // Only if not typing in an input
            if (ev.target.tagName === 'INPUT' || ev.target.tagName === 'TEXTAREA' || ev.target.tagName === 'SELECT') return;
            this.onDeleteSelected();
            ev.preventDefault();
        }
        if ((ev.ctrlKey || ev.metaKey) && ev.key === 's') {
            ev.preventDefault();
            this.onSave();
        }
    }

    /* ──────────────────────────────────────────────────────────────
       Properties panel
       ────────────────────────────────────────────────────────────── */
    get selectedNode() {
        if (!this.state.selectedNodeId) return null;
        return this.state.nodes.find(n => n.id === this.state.selectedNodeId) || null;
    }

    onPanelClose() {
        this.state.panelOpen = false;
        this.state.selectedNodeId = null;
    }

    onNodeFieldChange(ev) {
        const node = this.selectedNode;
        if (!node) return;
        const field = ev.currentTarget.dataset.field;
        const value = ev.currentTarget.value;
        if (field && node.hasOwnProperty(field)) {
            node[field] = value;
            this.state.dirty = true;
        }
    }

    onNodeNumberChange(ev) {
        const node = this.selectedNode;
        if (!node) return;
        const field = ev.currentTarget.dataset.field;
        const value = parseFloat(ev.currentTarget.value) || 0;
        if (field) {
            node[field] = value;
            this.state.dirty = true;
        }
    }

    /* ──────────────────────────────────────────────────────────────
       Node option management (option_group)
       ────────────────────────────────────────────────────────────── */
    onAddNodeOption() {
        const node = this.selectedNode;
        if (!node) return;
        if (!node.node_options) node.node_options = [];
        const seq = (node.node_options.length + 1) * 10;
        node.node_options.push({
            id: 'new_opt_' + Date.now(),
            name: 'Option ' + (node.node_options.length + 1),
            sequence: seq,
            section_option_id: false,
            enables_option_ids: [],
            disables_option_ids: [],
            requires_option_ids: [],
            port_key: 'opt_' + seq,
        });
        this.state.dirty = true;
    }

    onRemoveNodeOption(ev) {
        const node = this.selectedNode;
        if (!node || !node.node_options) return;
        const idx = parseInt(ev.currentTarget.dataset.index);
        if (!isNaN(idx) && idx >= 0 && idx < node.node_options.length) {
            const removed = node.node_options.splice(idx, 1)[0];
            // Also remove connections using this port
            if (removed) {
                this.state.connections = this.state.connections.filter(
                    c => !(c.source_node_id === node.id && c.source_port === removed.port_key)
                );
            }
            this.state.dirty = true;
        }
    }

    onNodeOptionNameChange(ev) {
        const node = this.selectedNode;
        if (!node || !node.node_options) return;
        const idx = parseInt(ev.currentTarget.dataset.index);
        if (!isNaN(idx) && idx >= 0 && idx < node.node_options.length) {
            node.node_options[idx].name = ev.currentTarget.value;
            this.state.dirty = true;
        }
    }
}

registry.category("actions").add("fusion_flow_designer", FlowDesignerAction);
