///|
/// Event modifiers: OnTap, OnSecondaryTap, OnLongPress, OnDoubleTap, OnDrag,
/// OnDragWithFrame, 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.
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",
    focusable: true,
    semantics: ViewSemanticsInfo::new(role=SemanticsRole::Button),
    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 changed = next_state != ctx.state || tap.activated
          {
            changed,
            activated: tap.activated,
            focused: false,
            captured: inside || current == PressableState::Pressed,
            state: Some(next_state),
            text_control: None,
            dirty: if changed {
              ViewDirtyHint::ViewPaintDirty
            } else {
              ViewDirtyHint::ViewClean
            },
            messages: if tap.activated {
              [message]
            } 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=SemanticsRole::Button),
    semantics_actions: [
      ViewSemanticsActionHandler::activate(_ctx => {
        ViewSemanticsActionResult::message(message)
      }),
    ],
    event: fn(ctx) {
      match ctx.event {
        Pointer(event) => {
          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 == PressableState::Pressed,
            state: Some(next_state),
            text_control: None,
            dirty: if changed {
              ViewDirtyHint::ViewPaintDirty
            } else {
              ViewDirtyHint::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=SemanticsRole::Button),
    event: fn(ctx) {
      match ctx.event {
        Pointer(event) => {
          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 {
                ViewDirtyHint::ViewPaintDirty
              } else {
                ViewDirtyHint::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=SemanticsRole::Button),
    event: fn(ctx) {
      match ctx.event {
        Pointer(event) => {
          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=SemanticsRole::Button),
    event: fn(ctx) {
      handle_drag_modifier_event(ctx, handler=fn(event, _frame) {
        handler(event)
      })
    },
  }
}

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

///|
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=SemanticsRole::Button),
    event: fn(ctx) { handle_drag_modifier_event(ctx, handler~) },
  }
}

///|
fn[Msg] handle_drag_modifier_event(
  ctx : ViewEventContext,
  handler~ : (DragGestureEvent, Rect) -> Msg,
) -> ViewEventResult[Msg] {
  match ctx.event {
    Pointer(event) => {
      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 != DragGesturePhase::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: ViewDirtyHint::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 {
                ViewDirtyHint::ViewPaintDirty
              } else {
                ViewDirtyHint::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 {
                ViewDirtyHint::ViewPaintDirty
              } else {
                ViewDirtyHint::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: ViewDirtyHint::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=SemanticsRole::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()
  }
}