import React, {memo, useMemo} from "react";
import {EdgeProps, getSmoothStepPath, SmoothStepEdgeProps} from "@xyflow/react";
import {Color} from "./tree/Color";
import {colors, getColorForComponentType} from "./tree/Colors";
import {useNodesStore} from "./tree/store";
import {StateBranchNode} from "./tree/StateBranchNode";
import {TestableCompositeNode} from "./tree/TestableCompositeNode";
import {TreeNode} from "./tree/TreeNode";
import {getConditionPaletteFromNodeData, getTierBaseColor} from "./tree/tierColorTheme";

export type ConnectingLineProps = SmoothStepEdgeProps & EdgeProps

function resolveComponentType(mode: string): 'condition' | 'filter' | 'offer' | string {
    return mode === 'test' ? 'condition' : mode
}

function getTestableNodeColor(node: TestableCompositeNode): Color {
    const testable = node.getTestable()
    const testableData = testable.getData()
    const parentData = testable.getParent()?.getData()
    const testableType = (
        testableData
        && typeof testableData === 'object'
        && 'testableType' in testableData
        && typeof testableData.testableType === 'string'
    ) ? testableData.testableType : undefined
    const conditionPalette =
        getConditionPaletteFromNodeData(testableData)
        || getConditionPaletteFromNodeData(parentData)
    const isTierTestableNode = node.getTreeNode().isTier() || node.isBaseTestableForTier()

    if (isTierTestableNode) {
        return getTierBaseColor({
            place: 'tier-edge',
            mode: testable.getMode(),
            testableType,
            inheritedConditionPalette: conditionPalette,
        })
    }

    return getColorForComponentType(
        resolveComponentType(testable.getMode()),
        {conditionPalette}
    )
}

function resolveColor(getGroveNode: (id: string) => unknown, nodeId: string): Color {
    const node = getGroveNode(nodeId);

    const getBaseTestableColor = (branch: StateBranchNode): Color => {
        const baseTestableId = branch.getExtraData<{baseTestableId?: string}>()?.baseTestableId

        if (!baseTestableId) {
            return colors.gray
        }

        const baseTestable = getGroveNode(baseTestableId)

        if (baseTestable instanceof TestableCompositeNode) {
            return getTestableNodeColor(baseTestable)
        }

        return colors.gray
    }

    if (node instanceof TestableCompositeNode) {
        return getTestableNodeColor(node)
    } else if (node instanceof StateBranchNode) {
        if (node.isSimple()) {
            return colors.blue;
        } else if (node.isTiered()) {
            return getBaseTestableColor(node);
        }
        return colors.gray;
    } else if (node instanceof TreeNode) {
        if (node.hasParent()) {
            const parentBranch = node.getParentBranch()
            return parentBranch ? getBaseTestableColor(parentBranch) : colors.gray
        }
        return colors.gray;
    }
    if (nodeId.endsWith('.add-tier-button')) {
        const tieredBranch = getGroveNode(nodeId.replace(/\.add-tier-button$/, ''))

        if (tieredBranch instanceof StateBranchNode && tieredBranch.isTiered()) {
            return getBaseTestableColor(tieredBranch)
        }
    }

    return colors.gray;
}

function getEdgeColorSource(color: Color, preferredIntensity: number, fallbackIntensity: number = 100): string {
    const preferredColor = color.get(preferredIntensity) as { source?: string } | undefined

    if (preferredColor?.source) {
        return preferredColor.source
    }

    const fallbackColor = color.get(fallbackIntensity) as { source?: string } | undefined

    if (fallbackColor?.source) {
        return fallbackColor.source
    }

    const defaultColor = color.get(100) as { source?: string } | undefined

    return defaultColor?.source || '#9ca3af'
}

// Single selector extracts both colors in one subscription to avoid redundant re-renders.
// Returns stable string tuple so useShallow isn't needed.
function useEdgeColors(sourceID: string, targetID: string): [string, string] {
    return useNodesStore(
        (state) => {
            const getGroveNode = state.getGroveNode;
            const srcColor = resolveColor(getGroveNode, sourceID);
            const tgtColor = resolveColor(getGroveNode, targetID);
            return [
                getEdgeColorSource(srcColor, 80),
                getEdgeColorSource(tgtColor, 100)
            ] as [string, string];
        },
        // Custom equality: only re-render when the actual color strings change
        (a, b) => a[0] === b[0] && a[1] === b[1]
    );
}

export const ConnectingLine = memo<ConnectingLineProps>(({id, source: sourceID, target: targetID, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition}) => {
    const [sourceColorStr, targetColorStr] = useEdgeColors(sourceID, targetID);

    const gradientId = `edge-gradient-${id.replace(/[^a-zA-Z0-9_-]/g, '_')}`;
    const [edgePath] = useMemo(() => getSmoothStepPath({
        sourceX,
        sourceY,
        targetX,
        targetY,
        sourcePosition,
        targetPosition,
        borderRadius: 30
    }), [sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition]);

    return <>
        <defs>
            <linearGradient id={gradientId} gradientUnits="userSpaceOnUse" x1={sourceX} y1={sourceY} x2={targetX} y2={targetY}>
                <stop offset="0%" style={{stopColor: sourceColorStr, stopOpacity: 1}} />
                <stop offset="100%" style={{stopColor: targetColorStr, stopOpacity: 1}} />
            </linearGradient>
        </defs>

        <path
            d={edgePath}
            fill="none"
            strokeWidth={2}
            strokeLinecap="round"
            strokeLinejoin="round"
            stroke={`url(#${gradientId})`}
        />
    </>
})
