import SwiftUI

/// The **shadow placeholder** — the outline occupying an item's proposed landing spot
/// (DRAG-REORDER.md § The pieces).
///
/// One shape for both the drag's shadows and the lane resize's resting footprint, because they are
/// the same thing said twice: "here is the space this item occupies in the layout". They differ only
/// in whether the space is a *proposal* — the drag's is dashed, because a dashed outline is what
/// 03-board-ui.md's placeholder vocabulary asks for and what makes "the drop lands exactly here"
/// legible; the resize's is a plain fill, because the lane is already there.
///
/// **Hit-transparent, always.** The strip's own drop target has to stay live beneath the shadows
/// (DRAG-REORDER.md § Single-target dispatch: "shadow placeholders are hit-transparent, so the strip
/// target stays live beneath them"), and a shadow that swallowed the release would strand the drop.
struct DragShadow: View {

    /// Matched to the surface it stands in for — the lane's plate radius or the card's, both
    /// font-derived (`BoardMetrics`) and both supplied by the caller, which is the one that knows
    /// which surface this shadow is standing in for.
    let cornerRadius: CGFloat

    /// A drop proposal (dashed) or a resting footprint (plain).
    var dashed: Bool = true

    /// Increase Contrast and Reduce Transparency, for the outline and the fill below
    /// (10-accessibility.md; `Accommodations`). A shadow's whole job is to say "here", and both
    /// halves of how it says so are alpha-based by default.
    @Environment(\.colorSchemeContrast) private var contrast
    @Environment(\.accessibilityReduceTransparency) private var reduceTransparency

    var body: some View {
        RoundedRectangle(cornerRadius: cornerRadius)
            .fill(Accommodations.dragShadowWash(reduceTransparency: reduceTransparency).style)
            .overlay {
                if dashed {
                    RoundedRectangle(cornerRadius: cornerRadius)
                        .strokeBorder(
                            Color.accentColor.opacity(Accommodations.accentOpacity(0.55, contrast: contrast)),
                            style: StrokeStyle(
                                lineWidth: Accommodations.borderWidth(1.5, contrast: contrast),
                                dash: [6]
                            )
                        )
                }
            }
            .allowsHitTesting(false)
            .accessibilityHidden(true)
    }
}

/// The count badge a multi-drag's replica wears — Finder's own affordance for "this many are coming
/// with me" (04-interactions.md ▸ Drag and drop: "dragging any member of a multi-selection drags the
/// whole selection").
///
/// Draws nothing for a single item, so every replica can carry it unconditionally.
struct DragCountBadge: View {

    let count: Int

    /// The board's ruler (`BoardZoom`) — the badge sits over the shadow of a card drawn at this zoom,
    /// so it has to be sized on the same one.
    @Environment(\.boardZoom) private var zoom

    /// The badge is a disc around a numeral, so every figure in it follows the numeral's font
    /// (`BoardMetrics`, 10-accessibility.md's full-relative-scaling rule) — at the standard body size
    /// they are the 6, 3 and 10 points it has always drawn.
    private var pointSize: CGFloat { zoom.bodyPointSize }

    var body: some View {
        if count > 1 {
            Text("\(count)")
                .boardFont(.caption2, weight: .bold)
                .monospacedDigit()
                .foregroundStyle(.white)
                .padding(.horizontal, BoardMetrics.em(0.45, bodyPointSize: pointSize))
                .padding(.vertical, BoardMetrics.em(0.25, bodyPointSize: pointSize))
                .background(Circle().fill(Color.accentColor))
                .offset(
                    x: BoardMetrics.em(0.75, bodyPointSize: pointSize),
                    y: -BoardMetrics.em(0.75, bodyPointSize: pointSize)
                )
        }
    }
}

/// The pickup lift's shadow figures — 03-board-ui.md § Motion's "the pickup lift (scale + shadow as
/// it detaches from the card)": the system drag session supplies the scale and the tracking, but
/// paints no shadow onto its preview image, so the shadow can only come from the replica view
/// itself. Values are the pathfinder's own constant ambient shadow, reused verbatim rather than
/// re-derived (`~/Documents/indie/Kanban` CardView.swift:623) — its drags read subtly raised, and
/// that read is the point.
enum DragReplicaStyle {
    static let shadowColor = Color.black.opacity(0.16)
    static let shadowRadius: CGFloat = 2.5
    static let shadowY: CGFloat = 1.5
}

extension View {
    /// The pickup lift's shadow (`DragReplicaStyle`), for a drag replica's face — shared by the
    /// card, lane, and trashed-lane replicas so the figures are spelled once.
    ///
    /// **It re-asserts the board's zoom, and that is not decoration.** A `.onDrag(_:preview:)`
    /// preview is hosted for the drag *image*, outside the strip's view hierarchy, so the environment
    /// the replica's own `.boardFont(_:)` modifiers resolve against is not reliably the board's. The
    /// replica must be drawn on the same ruler as the face it was lifted from — otherwise the image
    /// under the cursor is a card the board does not contain, which is the exact failure
    /// `BoardMetrics.cardReplicaWidth(measured:)` exists to prevent on the width axis. Passing the
    /// level in explicitly closes the type axis the same way.
    ///
    /// It rides on this modifier rather than being a fourth line in each replica for the reason the
    /// shadow does: three replicas, one rule, spelled once.
    func dragReplicaShadow(zoom: BoardZoomContext) -> some View {
        environment(\.boardZoom, zoom)
            .shadow(color: DragReplicaStyle.shadowColor, radius: DragReplicaStyle.shadowRadius, y: DragReplicaStyle.shadowY)
    }
}
