import SwiftUI

/// Motion tokens for the paneling system.
///
/// Panels are the one part of the window the user physically rearranges, so their
/// motion has to read as *weight* rather than as a transition: a pane picked up, slid
/// past its neighbours and set down should look like it has mass and settles. Almost
/// everything here is therefore a spring rather than an ease.
///
/// Every token takes the user's Reduce Motion setting and collapses to a short
/// cross-fade under it — a pane travelling across the window is exactly the kind of
/// large-area movement that setting exists to suppress, and the arrangement still
/// needs to change visibly, just without the journey.
enum PanelMotion {
    /// Add / remove / reorder / re-slot — the arrangement itself changing. Deliberately
    /// the same spring the todo list uses for its reorder, so the two lists in this app
    /// that animate their own order move alike.
    static func layout(_ reduceMotion: Bool) -> Animation {
        reduceMotion ? .easeInOut(duration: 0.16) : .spring(response: 0.38, dampingFraction: 0.82)
    }

    /// The insertion bar sliding between drop points. Much tighter than ``layout(_:)``:
    /// it has to keep up with the cursor to read as something the cursor is pushing,
    /// rather than as decoration lagging behind it.
    static func indicator(_ reduceMotion: Bool) -> Animation {
        reduceMotion ? .easeInOut(duration: 0.10) : .spring(response: 0.20, dampingFraction: 0.85)
    }

    /// A pane being lifted into — or dropped out of — a drag.
    static func lift(_ reduceMotion: Bool) -> Animation {
        reduceMotion ? .easeInOut(duration: 0.14) : .spring(response: 0.26, dampingFraction: 0.80)
    }

    /// Hover affordances: the header tint and grabber, the close button, the resize
    /// hairline. Short and linear-ish — these track the pointer, so a spring's
    /// overshoot would read as sloppiness rather than life.
    static func hover(_ reduceMotion: Bool) -> Animation {
        .easeInOut(duration: reduceMotion ? 0.10 : 0.14)
    }

    /// How a pane enters and leaves a stack. It scales up from just under full size so
    /// it looks like it *settles into* the slot instead of being cross-faded on top of
    /// it, and leaves from closer to full size so a removal feels quicker than an add.
    static func pane(_ reduceMotion: Bool) -> AnyTransition {
        guard !reduceMotion else { return .opacity }
        return .asymmetric(
            insertion: .scale(scale: 0.93).combined(with: .opacity),
            removal: .scale(scale: 0.96).combined(with: .opacity))
    }

    /// How a whole region (a side column, or the bottom row) enters and leaves: sliding
    /// from the window edge it docks against, so the chat is visibly *making room* for
    /// it rather than being cropped by it.
    static func region(_ slot: PanelSlot, _ reduceMotion: Bool) -> AnyTransition {
        guard !reduceMotion else { return .opacity }
        let edge: Edge =
            switch slot {
            case .left: .leading
            case .right: .trailing
            case .bottom: .bottom
            }
        return .move(edge: edge).combined(with: .opacity)
    }
}
