/*------------------------------------------------------------------------------
  tc11180x6_deadframe.p

  Regression probe X6 for review finding "oldFocusGone cannot tell 'the frame
  died during THIS dispatch' from 'the frame was ALREADY dead'"
  (ThinClient.processProgressEvent, #11180b post-2026-08-12).

    boolean oldFocusGone = oldFrame != null && !oldFrame.isAlive();   // <-- post-state only

  Frame.isAlive() is a sticky flag that nothing ever resets, and the value is
  sampled AFTER the LEAVE dispatch, so the predicate is also true for a frame
  that was destroyed long BEFORE this APPLY arrived.  A dead frame keeps the
  focus: destroyFrame() marks a regular visible frame setAlive(false), runs
  selectiveDisable() and DEFERS the hide, and the deadFrames state sync flips
  liveness with no visibility, enablement or focus change at all.

  Consequence claimed by the review: an ORDINARY APPLY 'ENTRY' issued while the
  focus still sits in an already-dead frame takes the frame-teardown
  "WITHOUT-steal" arm (src.requestFocus(); evt.consume(); return;) and therefore
  skips BOTH sendEntry(frame) (the target FRAME's ENTRY trigger) AND
  invokeTriggers(src, .., SE_ENTRY) (the target widget's ON ENTRY).  Trunk fired
  both.  Nothing in this probe involves a LEAVE trigger, a deletion mid-LEAVE or
  a focus steal - it is plain, everyday 4GL.

  This is the ONLY probe in the suite where the frame dies OUTSIDE a LEAVE
  trigger; every other #11180 probe (tc11180d/e/f*, tc11180x1..x5,
  test-11180-f/-g) deletes from inside the LEAVE, i.e. the legitimate case.

  Two independent phases, two independent target frames, so a phase that fails
  to reach the branch on FWD does not spoil the other:

    Phase A - STATIC frame scoped to an internal procedure.  Returning from the
              procedure ends the frame's scope -> markFrames(MarkEntry.DESTROY)
              -> destroyFrame().  No LEAVE trigger runs.
    Phase B - DYNAMIC frame holding a read-only BROWSE, deleted at MAINLINE
              (not from any trigger).  The browse is used because a read-only
              browse reliably remains the focus widget after its frame is
              destroyed (established by tc11180d_false_steal.p).

  Each phase then issues a plain APPLY 'ENTRY' at a fill-in in a different,
  fully alive frame and records whether that frame's and that widget's ENTRY
  triggers fired.

  READ (per phase):
    * target FRAME ENTRY + target widget ENTRY both present
        -> correct (native OE, and FWD once oldFocusGone is a transition test)
    * either ENTRY absent, FOCUS reported on the dead frame's widget
        -> the reviewed defect reproduces

  MEASURED 2026-08-14 - NEITHER PHASE REACHES THE BRANCH.  Both phases MATCH
  native OE (docs/11180b/4gl/tc11180x6.log vs deploy/client/tc11180x6.log, FWD at
  revno 16725), because neither idiom actually produces the required state, namely
  "focus sitting in a frame that is already dead":

    Phase A - FWD leaves fDoomed ALIVE.  FOCUS is still FILL-IN/fDoom after the
              procedure returns AND both target ENTRYs fire; a dead frame would
              have made the pre-fix oldFocusGone true and dropped both.  So an
              internal-procedure-scoped frame whose widget holds focus is not
              marked dead here.
    Phase B - FOCUS goes <none/invalid> in BOTH engines after the mainline
              DELETE WIDGET, so inFocus/oldFrame are null and oldFocusGone is
              false either way.  The tc11180d idiom "a read-only browse survives
              as the focus widget after its frame dies" holds only INSIDE a LEAVE
              dispatch, not at mainline.

  So the r4 finding "oldFocusGone is a post-state test" remains UNREACHED rather
  than refuted, and its proposed fix is a provable no-op on this probe (which is
  itself the useful result: no regression).  A probe that settles it must create a
  dead frame while a LIVE widget still holds focus; these two routes do not.
  See 11180b/11180b_v4_proposals.txt for the backed-out fix.

  RESIDUAL DIVERGENCE this probe DID find, unrelated to the above (see phase B
  setup, the APPLY that parks focus in frame B): OE fires fT1 LEAVE then
  frmT1 LEAVE; FWD omits the FRAME LEAVE.  Structurally upstream of the reviewed
  code - sendLeave(oldFrame, frame) runs before oldFocusGone is computed.

  Expected (native OpenEdge, MEASURED): both phases fire both ENTRY triggers.
  Expected (FWD 16725, MEASURED): identical in both measured windows; the only
  difference anywhere in the log is the missing frmT1 LEAVE noted above.

  Runs head-less on both systems - the whole path is driven synchronously from
  APPLY 'ENTRY'.  Log: tc11180x6.log (client cwd).
------------------------------------------------------------------------------*/

DEFINE TEMP-TABLE tt NO-UNDO FIELD nm AS CHARACTER.
DEFINE QUERY qB FOR tt SCROLLING.

DEFINE VARIABLE fDoom AS CHARACTER NO-UNDO FORMAT "x(8)" INITIAL "doom".
DEFINE VARIABLE fT1   AS CHARACTER NO-UNDO FORMAT "x(8)" INITIAL "tgt1".
DEFINE VARIABLE fT2   AS CHARACTER NO-UNDO FORMAT "x(8)" INITIAL "tgt2".
DEFINE VARIABLE seq   AS INTEGER   NO-UNDO.

DEFINE VARIABLE hFrmB AS HANDLE NO-UNDO.
DEFINE VARIABLE hBrwB AS HANDLE NO-UNDO.

/* the two measured destinations - both alive and untouched throughout */
DEFINE FRAME frmT1 fT1
   WITH TITLE "FRAME-T1 (phase A target)" AT ROW 10 COLUMN 2 SIZE 44 BY 3 SIDE-LABELS.

DEFINE FRAME frmT2 fT2
   WITH TITLE "FRAME-T2 (phase B target)" AT ROW 14 COLUMN 2 SIZE 44 BY 3 SIDE-LABELS.

/* Suppress the "Press SPACE BAR to continue" pause on frame hide. */
PAUSE 0 BEFORE-HIDE.

PROCEDURE logit:
   DEFINE INPUT PARAMETER m AS CHARACTER NO-UNDO.
   seq = seq + 1.
   OUTPUT TO "tc11180x6.log" APPEND.
   PUT UNFORMATTED STRING(seq, "99") " " m SKIP.
   OUTPUT CLOSE.
END PROCEDURE.

/* NB FOCUS:NAME is the UNKNOWN value for a dynamic widget that was never given a
   NAME, and "text" + ? is ? in 4GL - which silently collapsed the whole line to a
   bare "?" in the 2026-08-14 runs.  Guard the concatenation. */
PROCEDURE logFocus:
   DEFINE INPUT PARAMETER m AS CHARACTER NO-UNDO.
   RUN logit(m + (IF VALID-HANDLE(FOCUS)
                  THEN STRING(FOCUS:TYPE)
                       + "/"
                       + (IF FOCUS:NAME = ? THEN "<noname>" ELSE FOCUS:NAME)
                  ELSE "<none/invalid>")).
END PROCEDURE.

/* ---- phase A: park focus in a frame that dies at SCOPE END --------------- */
/* FRAME fDoomed is referenced ONLY inside this internal procedure, so it is
   scoped to the procedure and is destroyed when the procedure returns.  No
   LEAVE trigger is involved - this is scope-end destruction, the path the
   review names (markFrames(MarkEntry.DESTROY) -> destroyFrame). */
PROCEDURE parkInDoomedFrame:
   DISPLAY fDoom WITH FRAME fDoomed
      TITLE "FRAME-DOOMED (dies at scope end)" AT ROW 2 COLUMN 2 SIZE 44 BY 3 SIDE-LABELS.
   ENABLE fDoom WITH FRAME fDoomed.
   APPLY "ENTRY" TO fDoom IN FRAME fDoomed.
   RUN logFocus("   phase A: focus parked, procedure about to return; FOCUS = ").
END PROCEDURE.

/* ---- target triggers (the measured emits) ------------------------------- */
ON ENTRY OF FRAME frmT1
   RUN logit("frmT1 ENTRY  <== phase A target FRAME ENTRY (MUST fire)").
ON LEAVE OF FRAME frmT1
   RUN logit("frmT1 LEAVE").
ON ENTRY OF fT1 IN FRAME frmT1
   RUN logit("fT1   ENTRY  <== phase A target widget ENTRY (MUST fire; dropped by bug)").
ON LEAVE OF fT1 IN FRAME frmT1
   RUN logit("fT1   LEAVE").

ON ENTRY OF FRAME frmT2
   RUN logit("frmT2 ENTRY  <== phase B target FRAME ENTRY (MUST fire)").
ON LEAVE OF FRAME frmT2
   RUN logit("frmT2 LEAVE").
ON ENTRY OF fT2 IN FRAME frmT2
   RUN logit("fT2   ENTRY  <== phase B target widget ENTRY (MUST fire; dropped by bug)").
ON LEAVE OF fT2 IN FRAME frmT2
   RUN logit("fT2   LEAVE").

/* ---- main --------------------------------------------------------------- */
OUTPUT TO "tc11180x6.log".
PUT UNFORMATTED "=== tc11180x6_deadframe start ===" SKIP.
OUTPUT CLOSE.

CREATE tt. tt.nm = "row1".
CREATE tt. tt.nm = "row2".
CREATE tt. tt.nm = "row3".

ENABLE fT1 WITH FRAME frmT1.
VIEW FRAME frmT1.
ENABLE fT2 WITH FRAME frmT2.
VIEW FRAME frmT2.

/* ===================== PHASE A - scope-end destruction ==================== */
RUN logit("=== PHASE A: static frame destroyed at SCOPE END, then a plain APPLY ===").
RUN parkInDoomedFrame.
RUN logit("   phase A: parkInDoomedFrame returned - FRAME fDoomed scope has ENDED").
RUN logFocus("   phase A: FOCUS after scope end = ").

RUN logit("--- phase A MEASURED WINDOW: APPLY ENTRY to fT1 (ordinary APPLY) ---").
APPLY "ENTRY" TO fT1 IN FRAME frmT1.
RUN logFocus("--- phase A done; FOCUS = ").
RUN logit("--- phase A EXPECT (OE/fixed): frmT1 ENTRY + fT1 ENTRY, FOCUS=FILL-IN/fT1 ---").

/* ===================== PHASE B - mainline DELETE WIDGET =================== */
RUN logit("=== PHASE B: dynamic frame + browse deleted at MAINLINE, then a plain APPLY ===").

CREATE FRAME hFrmB
   ASSIGN TITLE = "FRAME-B (browse source, deleted at mainline)"
          ROW = 6 COLUMN = 2 WIDTH = 34 HEIGHT = 5
          BOX = TRUE VISIBLE = FALSE.

OPEN QUERY qB FOR EACH tt.

CREATE BROWSE hBrwB
   ASSIGN FRAME = hFrmB
          QUERY = QUERY qB:HANDLE
          X = 4 Y = 4 WIDTH = 30 DOWN = 2
          SEPARATORS = TRUE ROW-MARKERS = FALSE
          SENSITIVE = TRUE VISIBLE = TRUE.
hBrwB:ADD-COLUMNS-FROM(BUFFER tt:HANDLE).
hBrwB:READ-ONLY = TRUE.

hFrmB:VISIBLE = TRUE.

RUN logit("   phase B: APPLY ENTRY to the browse (park focus in frame B)").
APPLY "ENTRY" TO hBrwB.
RUN logFocus("   phase B: FOCUS parked = ").

/* Delete the FRAME only, from MAINLINE - no trigger, no LEAVE, no steal.
   The browse deliberately survives so it remains the focus widget. */
RUN logit("   phase B: DELETE WIDGET frame B at mainline (no trigger involved)").
IF VALID-HANDLE(hFrmB) THEN DELETE WIDGET hFrmB.
RUN logFocus("   phase B: FOCUS after the frame was deleted = ").

RUN logit("--- phase B MEASURED WINDOW: APPLY ENTRY to fT2 (ordinary APPLY) ---").
APPLY "ENTRY" TO fT2 IN FRAME frmT2.
RUN logFocus("--- phase B done; FOCUS = ").
RUN logit("--- phase B EXPECT (OE/fixed): frmT2 ENTRY + fT2 ENTRY, FOCUS=FILL-IN/fT2 ---").

RUN logit("=== tc11180x6_deadframe end ===").
QUIT.
