package center.sciprog.maps.features

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.PointerMatcher
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.input.pointer.PointerEvent
import androidx.compose.ui.input.pointer.PointerKeyboardModifiers
import center.sciprog.attributes.Attribute
import center.sciprog.attributes.AttributesBuilder
import center.sciprog.attributes.SetAttribute
import center.sciprog.attributes.withAttribute

public object ZAttribute : Attribute<Float>

public object DraggableAttribute : Attribute<DragHandle<Any>>

public object DragListenerAttribute : SetAttribute<DragListener<Any>>

/**
 * Click radius for point-like and line objects
 */
public object ClickRadius : Attribute<Float>

public object ClickListenerAttribute : SetAttribute<MouseListener<Any>>

public object HoverListenerAttribute : SetAttribute<MouseListener<Any>>

//public object TapListenerAttribute : SetAttribute<TapListener<Any>>

public object VisibleAttribute : Attribute<Boolean>

public object ColorAttribute : Attribute<Color>


public fun <T : Any, F : Feature<T>> FeatureRef<T, F>.color(color: Color): FeatureRef<T, F> =
    modifyAttribute(ColorAttribute, color)


public object ZoomRangeAttribute : Attribute<FloatRange>


public fun <T : Any, F : Feature<T>> FeatureRef<T, F>.zoomRange(range: FloatRange): FeatureRef<T, F> =
    modifyAttribute(ZoomRangeAttribute, range)


public object AlphaAttribute : Attribute<Float>

public fun <T : Any, F : Feature<T>> FeatureRef<T, F>.modifyAttributes(modify: AttributesBuilder.() -> Unit): FeatureRef<T, F> {
    @Suppress("UNCHECKED_CAST")
    parent.feature(
        id,
        resolve().withAttributes {
            AttributesBuilder(this).apply(modify).build()
        } as F
    )
    return this
}

public fun <T : Any, F : Feature<T>, V> FeatureRef<T, F>.modifyAttribute(
    key: Attribute<V>,
    value: V?,
): FeatureRef<T, F> {
    @Suppress("UNCHECKED_CAST")
    parent.feature(id, resolve().withAttributes { withAttribute(key, value) } as F)
    return this
}

/**
 * Add drag to this feature
 *
 * @param constraint optional drag constraint
 */
@Suppress("UNCHECKED_CAST")
public fun <T : Any, F : DraggableFeature<T>> FeatureRef<T, F>.draggable(
    constraint: ((T) -> T)? = null,
    listener: (PointerEvent.(from: ViewPoint<T>, to: ViewPoint<T>) -> Unit)? = null,
): FeatureRef<T, F> = with(parent) {
    if (attributes[DraggableAttribute] == null) {
        val handle = DragHandle.withPrimaryButton<Any> { event, start, end ->
            val feature = featureMap[id] as? DraggableFeature<T> ?: return@withPrimaryButton DragResult(end)
            start as ViewPoint<T>
            end as ViewPoint<T>
            if (start in feature) {
                val finalPosition = constraint?.invoke(end.focus) ?: end.focus
                feature(id, feature.withCoordinates(finalPosition))
                feature.attributes[DragListenerAttribute]?.forEach {
                    it.handle(event, start, ViewPoint(finalPosition, end.zoom))
                }
                DragResult(ViewPoint(finalPosition, end.zoom), false)
            } else {
                DragResult(end, true)
            }
        }
        modifyAttribute(DraggableAttribute, handle)
    }

    //Apply callback
    if (listener != null) {
        onDrag(listener)
    }
    return this@draggable
}


@Suppress("UNCHECKED_CAST")
public fun <T : Any, F : DraggableFeature<T>> FeatureRef<T, F>.onDrag(
    listener: PointerEvent.(from: ViewPoint<T>, to: ViewPoint<T>) -> Unit,
): FeatureRef<T, F> = modifyAttributes {
    DragListenerAttribute.add(
        DragListener { event, from, to -> event.listener(from as ViewPoint<T>, to as ViewPoint<T>) }
    )
}

@Suppress("UNCHECKED_CAST")
public fun <T : Any, F : DomainFeature<T>> FeatureRef<T, F>.onClick(
    onClick: PointerEvent.(click: ViewPoint<T>) -> Unit,
): FeatureRef<T, F> = modifyAttributes {
    ClickListenerAttribute.add(
        MouseListener { event, point ->
            event.onClick(point as ViewPoint<T>)
        }
    )
}

@OptIn(ExperimentalFoundationApi::class)
@Suppress("UNCHECKED_CAST")
public fun <T : Any, F : DomainFeature<T>> FeatureRef<T, F>.onClick(
    pointerMatcher: PointerMatcher,
    keyboardModifiers: PointerKeyboardModifiers.() -> Boolean = { true },
    onClick: PointerEvent.(click: ViewPoint<T>) -> Unit,
): FeatureRef<T, F> = modifyAttributes {
    ClickListenerAttribute.add(
        MouseListener { event, point ->
            if (pointerMatcher.matches(event) && keyboardModifiers(event.keyboardModifiers)) {
                event.onClick(point as ViewPoint<T>)
            }
        }
    )
}

@Suppress("UNCHECKED_CAST")
public fun <T : Any, F : DomainFeature<T>> FeatureRef<T, F>.onHover(
    onClick: PointerEvent.(move: ViewPoint<T>) -> Unit,
): FeatureRef<T, F> = modifyAttributes {
    HoverListenerAttribute.add(
        MouseListener { event, point -> event.onClick(point as ViewPoint<T>) }
    )
}

//    @Suppress("UNCHECKED_CAST")
//    @OptIn(ExperimentalFoundationApi::class)
//    public fun <F : DomainFeature<T>> FeatureId<F>.onTap(
//        pointerMatcher: PointerMatcher = PointerMatcher.Primary,
//        keyboardFilter: PointerKeyboardModifiers.() -> Boolean = { true },
//        onTap: (point: ViewPoint<T>) -> Unit,
//    ): FeatureId<F> = modifyAttributes {
//        TapListenerAttribute.add(
//            TapListener(pointerMatcher, keyboardFilter) { point -> onTap(point as ViewPoint<T>) }
//        )
//    }


public object PathEffectAttribute : Attribute<PathEffect>

public fun <T : Any> FeatureRef<T, LineSegmentFeature<T>>.pathEffect(effect: PathEffect): FeatureRef<T, LineSegmentFeature<T>> =
    modifyAttribute(PathEffectAttribute, effect)

public object StrokeAttribute : Attribute<Float>

public fun <T : Any, F : LineSegmentFeature<T>> FeatureRef<T, F>.stroke(width: Float): FeatureRef<T, F> =
    modifyAttribute(StrokeAttribute, width)

public fun <T : Any, F : PointsFeature<T>> FeatureRef<T, F>.pointSize(width: Float): FeatureRef<T, F> =
    modifyAttribute(StrokeAttribute, width)