///|
/// Event modifiers: OnTap, OnSecondaryTap, OnLongPress, OnDoubleTap, OnDrag,
/// OnDragWithFrame, OnTapWithFrame, OnFileDrop, KeyboardShortcut.

///|
pub(all) struct OnTapModifier[Msg] {
  message : Msg
}

///|
/// Secondary mouse-button activation. Native desktop backends normalize the
/// context-menu button to `button == 1`; primary taps continue to use 0.
///
/// The modifier is deliberately transparent: it contributes no semantics and
/// no focusability. A context-menu hook is an observer, not a control — making
/// it focusable would insert it into focus traversal, and any semantics role
/// it declared would override the host view's role (a tree row must stay a
/// TreeItem, a plain container must not become a Button).
priv struct OnSecondaryTapModifier[Msg] {
  message : Msg
}

///|
fn[Msg] OnSecondaryTapModifier::to_any_modifier(
  self : OnSecondaryTapModifier[Msg],
) -> AnyModifier[Msg] {
  let message = self.message
  {
    ..noop_modifier(),
    identity_kind: "OnSecondaryTap",
    event: fn(ctx) {
      match ctx.event {
        // A secondary tap is a press, not a press/release pair: native
        // context menus open on button-down, and the host backends show the
        // context menu before the release can be dispatched to the tree.
        Pointer(event) if event.button == 1 => {
          let inside = ctx.frame.contains(event.position)
          let current = view_state_to_pressable_state(ctx.state)
          let tap = recognize_press_tap(current, inside, event.phase)
          let next_state = pressable_state_to_view_state(
            ctx.state,
            tap.next_state,
          )
          let activated = event.phase == Down && inside
          let changed = next_state != ctx.state || activated
          {
            changed,
            activated,
            focused: false,
            // No pointer capture: the tap is delivered on press and owns no
            // gesture. Capturing would route later presses (a second
            // right-click elsewhere before the release) to this element and
            // mis-attribute their context-menu target.
            captured: false,
            state: Some(next_state),
            text_control: None,
            dirty: if changed {
              ViewPaintDirty
            } else {
              ViewClean
            },
            messages: if activated {
              [message]
            } else {
              []
            },
          }
        }
        _ => ViewEventResult::ignored()
      }
    },
  }
}

///|
/// Secondary (right-button) tap **with position and frame**.
///
/// `OnSecondaryTapModifier` carries a fixed message, so a context-menu host
/// cannot learn *where* the user right-clicked and therefore cannot hit-test
/// which item to build a menu for. This variant hands over the pointer point
/// and the child frame, mirroring `OnTapWithFrameModifier`.
///
/// Fires on press (not press/release): native context menus open on
/// button-down, and the host backends show the menu before the release can be
/// dispatched to the tree — same rationale as `OnSecondaryTapModifier`.
priv struct OnSecondaryTapWithFrameModifier[Msg] {
  handler : (Point, Rect) -> Msg
}

///|
fn[Msg] OnSecondaryTapWithFrameModifier::to_any_modifier(
  self : OnSecondaryTapWithFrameModifier[Msg],
) -> AnyModifier[Msg] {
  let handler = self.handler
  {
    ..noop_modifier(),
    identity_kind: "OnSecondaryTapWithFrame",
    event: fn(ctx) {
      match ctx.event {
        Pointer(event) if event.button == 1 => {
          let inside = ctx.frame.contains(event.position)
          let current = view_state_to_pressable_state(ctx.state)
          let tap = recognize_press_tap(current, inside, event.phase)
          let next_state = pressable_state_to_view_state(
            ctx.state,
            tap.next_state,
          )
          let activated = event.phase == Down && inside
          let changed = next_state != ctx.state || activated
          let frame = match ctx.child_frames {
            [cf, ..] => cf
            [] => ctx.frame
          }
          {
            changed,
            activated,
            focused: false,
            // No pointer capture — see `OnSecondaryTapModifier`: capturing
            // would mis-attribute a second right-click elsewhere.
            captured: false,
            state: Some(next_state),
            text_control: None,
            dirty: if changed {
              ViewPaintDirty
            } else {
              ViewClean
            },
            messages: if activated {
              [handler(event.position, frame)]
            } else {
              []
            },
          }
        }
        _ => ViewEventResult::ignored()
      }
    },
  }
}

///|
pub fn[Msg] OnTapModifier::to_any_modifier(
  self : OnTapModifier[Msg],
) -> AnyModifier[Msg] {
  let message = self.message
  {
    ..noop_modifier(),
    identity_kind: "OnTap",
    focusable: true,
    semantics: ViewSemanticsInfo::new(role=Button),
    semantics_actions: [
      ViewSemanticsActionHandler::activate(_ctx => {
        ViewSemanticsActionResult::message(message)
      }),
    ],
    event: fn(ctx) {
      // Only the primary button activates a tap. Host backends now route
      // right-button presses into the view tree (so per-target context menus
      // can observe them), and a secondary press must not also fire the
      // control's primary action. Touch/pen events decode to button 0.
      match ctx.event {
        Pointer(event) if event.button == 0 => {
          let inside = ctx.frame.contains(event.position)
          let current = view_state_to_pressable_state(ctx.state)
          let tap = recognize_press_tap(current, inside, event.phase)
          let next_state = pressable_state_to_view_state(
            ctx.state,
            tap.next_state,
          )
          let changed = next_state != ctx.state || tap.activated
          {
            changed,
            activated: tap.activated,
            focused: false,
            captured: inside || current == Pressed,
            state: Some(next_state),
            text_control: None,
            dirty: if changed {
              ViewPaintDirty
            } else {
              ViewClean
            },
            messages: if tap.activated {
              [message]
            } else {
              []
            },
          }
        }
        Keyboard(event) =>
          if event.pressed && (event.key == "Enter" || event.key == " ") {
            ViewEventResult::message(message)
          } else {
            ViewEventResult::ignored()
          }
        _ => ViewEventResult::ignored()
      }
    },
  }
}

///|
pub(all) struct OnLongPressModifier[Msg] {
  message : Msg
}

///|
pub fn[Msg] OnLongPressModifier::to_any_modifier(
  self : OnLongPressModifier[Msg],
) -> AnyModifier[Msg] {
  let message = self.message
  {
    ..noop_modifier(),
    identity_kind: "OnLongPress",
    focusable: true,
    semantics: ViewSemanticsInfo::new(role=Button),
    event: fn(ctx) {
      // Primary button only: see OnTapModifier.
      match ctx.event {
        Pointer(event) if event.button == 0 => {
          let result = LongPressGestureState::new().update(
            event,
            inside=ctx.frame.contains(event.position),
          )
          if result.recognized {
            ViewEventResult::message(message)
          } else {
            {
              ..ViewEventResult::ignored(),
              changed: result.cancelled,
              captured: result.cancelled,
              dirty: if result.cancelled {
                ViewPaintDirty
              } else {
                ViewClean
              },
            }
          }
        }
        _ => ViewEventResult::ignored()
      }
    },
  }
}

///|
pub(all) struct OnDoubleTapModifier[Msg] {
  message : Msg
}

///|
pub fn[Msg] OnDoubleTapModifier::to_any_modifier(
  self : OnDoubleTapModifier[Msg],
) -> AnyModifier[Msg] {
  let message = self.message
  {
    ..noop_modifier(),
    identity_kind: "OnDoubleTap",
    focusable: true,
    semantics: ViewSemanticsInfo::new(role=Button),
    event: fn(ctx) {
      // Primary button only: see OnTapModifier.
      match ctx.event {
        Pointer(event) if event.button == 0 => {
          let result = DoubleTapGestureState::new().update(
            event,
            inside=ctx.frame.contains(event.position),
          )
          if result.recognized {
            ViewEventResult::message(message)
          } else {
            ViewEventResult::ignored()
          }
        }
        _ => ViewEventResult::ignored()
      }
    },
  }
}

///|
pub(all) struct OnDragModifier[Msg] {
  handler : (DragGestureEvent) -> Msg
}

///|
pub fn[Msg] OnDragModifier::to_any_modifier(
  self : OnDragModifier[Msg],
) -> AnyModifier[Msg] {
  let handler = self.handler
  {
    ..noop_modifier(),
    identity_kind: "OnDrag",
    semantics: ViewSemanticsInfo::new(role=Button),
    event: fn(ctx) {
      handle_drag_modifier_event(ctx, handler=fn(event, _frame) {
        handler(event)
      })
    },
  }
}

///|
pub(all) struct OnDragWithFrameModifier[Msg] {
  handler : (DragGestureEvent, Rect) -> Msg
}

///|
/// Tap-with-position modifier for canvas/custom-drawn surfaces: unlike
/// `OnTapModifier` (fixed message, no geometry) this hands the tap position
/// and the child frame to the handler so the app can hit-test in its own
/// coordinate space (`position - frame.origin` = view-local point).
///
/// Deliberately transparent like `OnSecondaryTapModifier`: a canvas surface
/// is an observer, not a control — a Button semantics role here would
/// override the host view's role and pollute focus traversal.
priv struct OnTapWithFrameModifier[Msg] {
  handler : (Point, Rect) -> Msg
}

///|
fn[Msg] OnTapWithFrameModifier::to_any_modifier(
  self : OnTapWithFrameModifier[Msg],
) -> AnyModifier[Msg] {
  let handler = self.handler
  {
    ..noop_modifier(),
    identity_kind: "OnTapWithFrame",
    event: fn(ctx) {
      // Primary button only: see OnTapModifier.
      match ctx.event {
        Pointer(event) if event.button == 0 => {
          let inside = ctx.frame.contains(event.position)
          let current = view_state_to_pressable_state(ctx.state)
          let tap = recognize_press_tap(current, inside, event.phase)
          let next_state = pressable_state_to_view_state(
            ctx.state,
            tap.next_state,
          )
          let activated = tap.activated
          let changed = next_state != ctx.state || activated
          let frame = match ctx.child_frames {
            [cf, ..] => cf
            [] => ctx.frame
          }
          {
            changed,
            activated,
            focused: false,
            captured: inside || current == Pressed,
            state: Some(next_state),
            text_control: None,
            dirty: if changed {
              ViewPaintDirty
            } else {
              ViewClean
            },
            messages: if activated {
              [handler(event.position, frame)]
            } else {
              []
            },
          }
        }
        _ => ViewEventResult::ignored()
      }
    },
  }
}

///|
pub fn[Msg] OnDragWithFrameModifier::to_any_modifier(
  self : OnDragWithFrameModifier[Msg],
) -> AnyModifier[Msg] {
  let handler = self.handler
  {
    ..noop_modifier(),
    identity_kind: "OnDragWithFrame",
    semantics: ViewSemanticsInfo::new(role=Button),
    event: fn(ctx) { handle_drag_modifier_event(ctx, handler~) },
  }
}

///|
fn[Msg] handle_drag_modifier_event(
  ctx : ViewEventContext,
  handler~ : (DragGestureEvent, Rect) -> Msg,
) -> ViewEventResult[Msg] {
  // Primary button only: secondary presses belong to context menus.
  match ctx.event {
    Pointer(event) if event.button == 0 => {
      let slot = drag_gesture_view_state_slot()
      let gesture = ctx.state.read_slot(slot)
      let result = gesture.update(
        event,
        inside=ctx.frame.contains(event.position),
      )
      let active = result.state.phase != Idle
      let next_state = if active {
        ctx.state.with_slot(slot, result.state)
      } else {
        ctx.state.without_slot(slot)
      }
      match result.event {
        Some(drag_event) => {
          let frame = match ctx.child_frames {
            [cf, ..] => cf
            [] => ctx.frame
          }
          {
            changed: true,
            activated: true,
            focused: false,
            captured: true,
            state: Some(next_state),
            text_control: None,
            dirty: ViewPaintDirty,
            messages: [handler(drag_event, frame)],
          }
        }
        None =>
          if active {
            {
              changed: next_state != ctx.state,
              activated: false,
              focused: false,
              captured: true,
              state: Some(next_state),
              text_control: None,
              dirty: if next_state != ctx.state {
                ViewPaintDirty
              } else {
                ViewClean
              },
              messages: [],
            }
          } else {
            // Terminal pointer phases must commit the reset even when the
            // gesture did not cross the drag slop.  In particular, a simple
            // click leaves the recognizer in `Pending`; returning `ignored`
            // here would preserve that slot forever and the next Move could
            // be interpreted as a drag from the old click.
            {
              // State is still committed, but terminal cleanup is transparent
              // to parent tap modifiers so they can observe the same Up.
              changed: false,
              activated: false,
              focused: false,
              captured: false,
              state: Some(next_state),
              text_control: None,
              dirty: if next_state != ctx.state {
                ViewPaintDirty
              } else {
                ViewClean
              },
              messages: [],
            }
          }
      }
    }
    _ => ViewEventResult::ignored()
  }
}

///|
pub(all) struct OnFileDropModifier[Msg] {
  handler : (Array[String], Point) -> Msg
}

///|
pub fn[Msg] OnFileDropModifier::to_any_modifier(
  self : OnFileDropModifier[Msg],
) -> AnyModifier[Msg] {
  let handler = self.handler
  {
    ..noop_modifier(),
    identity_kind: "OnFileDrop",
    event: fn(ctx) {
      match ctx.event {
        DragDrop(event) =>
          match (event.phase, event.position) {
            (Dropped, Some(position)) =>
              if ctx.frame.contains(position) && event.paths.length() > 0 {
                {
                  changed: true,
                  activated: true,
                  focused: false,
                  captured: true,
                  state: None,
                  text_control: None,
                  dirty: ViewPaintDirty,
                  messages: [handler(event.paths, position)],
                }
              } else {
                ViewEventResult::ignored()
              }
            _ => ViewEventResult::ignored()
          }
        _ => ViewEventResult::ignored()
      }
    },
  }
}

///|
pub(all) struct KeyboardShortcutModifier {
  shortcut : KeyboardShortcut
}

///|
pub fn[Msg] KeyboardShortcutModifier::to_any_modifier(
  self : KeyboardShortcutModifier,
) -> AnyModifier[Msg] {
  let shortcut = self.shortcut
  {
    ..noop_modifier(),
    identity_kind: "KeyboardShortcut",
    focusable: true,
    semantics: ViewSemanticsInfo::new(role=Button),
    event: fn(ctx) { handle_keyboard_shortcut_modifier(shortcut, ctx) },
  }
}

///|
fn[Msg] handle_keyboard_shortcut_modifier(
  shortcut : KeyboardShortcut,
  ctx : ViewEventContext,
) -> ViewEventResult[Msg] {
  match ctx.event {
    Keyboard(event) =>
      if shortcut.matches(event) {
        { ..ViewEventResult::ignored(), activated: true, captured: true, }
      } else {
        ViewEventResult::ignored()
      }
    _ => ViewEventResult::ignored()
  }
}