component Modal {
  outputs {
    open(payload: { sourceEvent: Event })
    close(payload: { reason: string; sourceEvent: Event })
    cancel(payload: { sourceEvent: Event })
    confirm(payload: { sourceEvent: Event })
  }

  props {

    open: boolean = false
    defaultOpen: boolean = false
    title: string = "Modal"
    description: string = ""
    icon: string = ""
    label: string = "Modal dialog"
    size: string = "md"
    placement: string = "center"
    color: string = "primary"
    variant: string = "default"
    showClose: boolean = true
    closeLabel: string = "Close modal"
    closeOnBackdrop: boolean = true
    closeOnEscape: boolean = true
    closeOnCancel: boolean = true
    closeOnConfirm: boolean = false
    showFooter: boolean = true
    cancelLabel: string = "Cancel"
    cancelIcon: string = ""
    confirmLabel: string = "Confirm"
    confirmIcon: string = ""
    confirmDisabled: boolean = false
    confirmLoading: boolean = false
    destructive: boolean = false
    triggerLabel: string = ""
    triggerIcon: string = ""
    scrollable: boolean = true
    scrollBehavior: string = "inside"
    class: string = ""
  }

  state visible = defaultOpen

  functions {
    shared function isOpen() {
      return open || visible
    }

    client function showModal(sourceEvent) {
      visible = true
      output.open({ sourceEvent: sourceEvent })
    }

    // Slot content can close the modal it sits in by dispatching a bubbling
    // wrnexus:modal:close event, e.g. from a form success handler:
    //
    //   event.target.dispatchEvent(
    //     new CustomEvent("wrnexus:modal:close", { bubbles: true })
    //   )
    //
    // The listener is on the modal root, so the event only ever closes the
    // modal the dispatching element is actually inside -- no ids to wire up
    // and no way to close somebody else's modal by accident.
    client function hideModal(reason, sourceEvent) {
      visible = false
      output.close({
        reason: reason,
        sourceEvent: sourceEvent
      })
    }

    client function cancelModal(sourceEvent) {
      output.cancel({ sourceEvent: sourceEvent })
      if (closeOnCancel) {
        hideModal("cancel", sourceEvent)
      }
    }

    client function confirmModal(sourceEvent) {
      if (confirmDisabled || confirmLoading) {
        return
      }
      output.confirm({ sourceEvent: sourceEvent })
      if (closeOnConfirm) {
        hideModal("confirm", sourceEvent)
      }
    }

    client function handleKeydown(sourceEvent) {
      if (closeOnEscape && sourceEvent.key === "Escape") {
        sourceEvent.preventDefault()
        cancelModal(sourceEvent)
      }
    }
  }

  view {
    <div
      {...attrs}
      data-ui-component="Modal"
      data-open='{isOpen() ? "true" : "false"}'
      data-size='{size}'
      data-placement='{placement}'
      data-color='{color}'
      data-variant='{variant}'
      data-scrollable='{scrollable ? "true" : "false"}'
      data-scroll='{scrollBehavior}'
      data-destructive='{destructive ? "true" : "false"}'
      class='wire-modal {class}'
      @wrnexus:modal:close='hideModal("api", event)'
    >
      {#if triggerLabel}
        <button
          type="button"
          class="wire-modal__trigger"
          aria-haspopup="dialog"
          aria-expanded='{isOpen() ? "true" : "false"}'
          @click='showModal(event)'
        >
          {#if triggerIcon}
            <span
              class='{triggerIcon}'
              aria-hidden="true"
            >
            </span>
          {/if}
          <span>{triggerLabel}</span>
        </button>
      {/if}

      <span
        class="wire-modal__trigger-slot"
        @click='showModal(event)'
      >
        <slot
          name="trigger"
        >
        </slot>
      </span>

      <div
        class="wire-modal__layer"
        data-show="isOpen()"
        role="presentation"
        @keydown='handleKeydown(event)'
      >
        <button
          type="button"
          class="wire-modal__backdrop"
          aria-label='{closeLabel}'
          @click='if (closeOnBackdrop) { cancelModal(event) }'
        >
        </button>

        <section
          class="wire-modal__panel"
          role="dialog"
          aria-modal="true"
          aria-label='{label || title}'
          tabindex="-1"
        >
          <header
            class="wire-modal__header"
          >
            <div
              class="wire-modal__heading"
            >
              {#if icon}
                <span
                  class='wire-modal__icon {icon}'
                  aria-hidden="true"
                >
                </span>
              {/if}

              <div
                class="wire-modal__heading-copy"
              >
                <slot
                  name="header"
                >
                </slot>
                {#if title}
                  <h2>{title}</h2>
                {/if}
                {#if description}
                  <p>{description}</p>
                {/if}
              </div>
            </div>

            {#if showClose}
              <button
                type="button"
                class="wire-modal__close"
                aria-label='{closeLabel}'
                @click='hideModal("close-button", event)'
              >
                <svg
                  viewBox="0 0 24 24"
                  width="16"
                  height="16"
                  fill="none"
                  stroke="currentColor"
                  stroke-width="2"
                  stroke-linecap="round"
                  stroke-linejoin="round"
                  aria-hidden="true"
                >
                  <path d="M18 6 6 18" />
                  <path d="M6 6 18 18" />
                </svg>
              </button>
            {/if}
          </header>

          <div
            class="wire-modal__body"
          >
            <slot></slot>
          </div>

          {#if showFooter}
            <footer
              class="wire-modal__footer"
            >
              <div
                class="wire-modal__custom-footer"
              >
                <slot
                  name="footer"
                >
                </slot>
              </div>

              <div
                class="wire-modal__actions"
              >
                {#if cancelLabel}
                  <button
                    type="button"
                    class="wire-modal__button wire-modal__button--secondary"
                    @click='cancelModal(event)'
                  >
                    {#if cancelIcon}
                      <span
                        class='{cancelIcon}'
                        aria-hidden="true"
                      >
                      </span>
                    {/if}
                    <span>{cancelLabel}</span>
                  </button>
                {/if}

                {#if confirmLabel}
                  <button
                    type="button"
                    class="wire-modal__button wire-modal__button--primary"
                    disabled='{confirmDisabled || confirmLoading}'
                    @click='confirmModal(event)'
                  >
                    {#if confirmLoading}
                      <span
                        class="wire-modal__spinner"
                        aria-hidden="true"
                      >
                      </span>
                    {:else if confirmIcon}
                      <span
                        class='{confirmIcon}'
                        aria-hidden="true"
                      >
                      </span>
                    {/if}
                    <span>{confirmLabel}</span>
                  </button>
                {/if}
              </div>
            </footer>
          {/if}
        </section>
      </div>
    </div>
  }

  style {
    .wire-modal {
      --modal-accent: var(--wire-color-primary);
      --modal-soft: var(--wire-color-primary-soft);
      --modal-contrast: var(--wire-color-primary-contrast);
      position: relative;
      display: inline-flex;
    }

    .wire-modal[data-color="secondary"] {
      --modal-accent: var(--wire-color-secondary);
      --modal-soft: var(--wire-color-secondary-soft);
      --modal-contrast: var(--wire-color-secondary-contrast);
    }

    /*
     * Fallbacks below (the second var() argument): the theme token generator
     * (packages/styles/src/theme.ts) only emits a real -contrast token for
     * primary and secondary. info, success, and danger have no contrast
     * token defined at all, so var(--wire-color-info-contrast) with no
     * fallback resolves to nothing, and --modal-contrast becomes invalid --
     * which made confirm-button and solid-panel text unreadable. White is a
     * safe default against these saturated colors until the theme package
     * defines real tokens for them.
     */
    .wire-modal[data-color="info"] {
      --modal-accent: var(--wire-color-info);
      --modal-soft: var(--wire-color-info-soft);
      --modal-contrast: var(--wire-color-info-contrast, white);
    }

    .wire-modal[data-color="success"] {
      --modal-accent: var(--wire-color-success);
      --modal-soft: var(--wire-color-success-soft);
      --modal-contrast: var(--wire-color-success-contrast, white);
    }

    .wire-modal[data-color="warning"] {
      --modal-accent: var(--wire-color-warning);
      --modal-soft: var(--wire-color-warning-soft);
      --modal-contrast: var(--wire-color-warning-text);
    }

    .wire-modal[data-color="danger"],
    .wire-modal[data-destructive="true"] {
      --modal-accent: var(--wire-color-danger);
      --modal-soft: var(--wire-color-danger-soft);
      --modal-contrast: var(--wire-color-on-danger, white);
    }

    .wire-modal__trigger,
    .wire-modal__trigger-slot {
      display: inline-flex;
      align-items: center;
      gap: 0.55rem;
    }

    .wire-modal__trigger {
      appearance: none;
      min-height: 2.55rem;
      padding: 0.65rem 1rem;
      color: var(--modal-contrast);
      background: var(--modal-accent);
      border: 0;
      border-radius: 0.8rem;
      font: inherit;
      font-size: 0.85rem;
      font-weight: 650;
      cursor: pointer;
      transition: opacity 150ms ease, transform 150ms ease, box-shadow 150ms ease;
    }

    .wire-modal__trigger:hover {
      opacity: 0.92;
    }

    .wire-modal__trigger:active {
      transform: scale(0.97);
    }

    .wire-modal__trigger:focus-visible {
      outline: none;
      box-shadow: 0 0 0 3px color-mix(in srgb, var(--modal-accent) 40%, transparent);
    }

    /*
     * Hidden by default so the SSR-rendered HTML never paints the layer before
     * hydration runs. data-open on the wire-modal root is evaluated and
     * serialized to a real true or false string at render time -- a plain
     * bind, not the raw-expression data-show directive -- so this selector
     * is correct on first paint with zero flash, with no dependency on
     * client JS having run yet. The data-show attribute and client directive
     * still run after hydration to keep things in sync for state changes,
     * but visibility itself is driven by CSS here. visibility (rather than
     * display) is used so the open and close transitions below can actually
     * animate -- a box that starts at display: none has no prior frame to
     * transition from.
     */
    .wire-modal__layer {
      position: fixed;
      inset: 0;
      z-index: 1250;
      display: flex;
      align-items: center;
      justify-content: center;
      padding: 1rem;
      visibility: hidden;
      opacity: 0;
      transition: opacity 180ms ease, visibility 0s linear 180ms;
    }

    .wire-modal[data-open="true"] .wire-modal__layer {
      visibility: visible;
      opacity: 1;
      transition: opacity 180ms ease, visibility 0s linear 0s;
    }

    .wire-modal[data-placement="top"] .wire-modal__layer {
      align-items: flex-start;
      padding-top: clamp(1rem, 8vh, 5rem);
    }

    .wire-modal[data-scroll="page"] .wire-modal__layer {
      align-items: flex-start;
      overflow-y: auto;
      padding: 2.5rem 1rem;
    }

    .wire-modal__backdrop {
      position: absolute;
      inset: 0;
      z-index: 0;
      appearance: none;
      padding: 0;
      background: color-mix(in srgb, black 62%, transparent);
      border: 0;
      backdrop-filter: blur(9px);
    }

    .wire-modal__panel {
      position: relative;
      z-index: 1;
      display: flex;
      flex-direction: column;
      width: min(32rem, calc(100vw - 2rem));
      max-height: min(88vh, 52rem);
      color: var(--wire-color-text);
      background:
      radial-gradient(
      circle at 100% 0%,
      color-mix(in srgb, var(--modal-accent) 9%, transparent),
      transparent 34%
      ),
      var(--wire-color-surface-raised);
      border: 1px solid color-mix(in srgb, var(--modal-accent) 18%, var(--wire-color-border));
      border-radius: 1.35rem;
      box-shadow:
      0 1px 0 color-mix(in srgb, white 5%, transparent) inset,
      0 40px 110px color-mix(in srgb, black 38%, transparent);
      overflow: hidden;
      opacity: 0;
      transform: scale(0.96) translateY(10px);
      transition: opacity 180ms ease, transform 220ms cubic-bezier(0.16, 1, 0.3, 1);
    }

    .wire-modal[data-open="true"] .wire-modal__panel {
      opacity: 1;
      transform: none;
    }

    .wire-modal[data-size="xs"] .wire-modal__panel {
      width: min(19rem, calc(100vw - 2rem));
    }

    .wire-modal[data-size="sm"] .wire-modal__panel {
      width: min(25rem, calc(100vw - 2rem));
    }

    .wire-modal[data-size="lg"] .wire-modal__panel {
      width: min(44rem, calc(100vw - 2rem));
    }

    .wire-modal[data-size="xl"] .wire-modal__panel {
      width: min(64rem, calc(100vw - 2rem));
    }

    .wire-modal[data-size="full"] .wire-modal__panel {
      width: calc(100vw - 2rem);
      height: calc(100vh - 2rem);
      max-height: none;
    }

    .wire-modal[data-scroll="page"] .wire-modal__panel {
      max-height: none;
    }

    .wire-modal[data-variant="soft"] .wire-modal__panel {
      background:
      linear-gradient(145deg, var(--modal-soft), transparent 68%),
      var(--wire-color-surface-raised);
    }

    .wire-modal[data-variant="outline"] .wire-modal__panel {
      background: var(--wire-color-surface-raised);
      box-shadow: 0 24px 72px color-mix(in srgb, black 24%, transparent);
    }

    .wire-modal[data-variant="solid"] .wire-modal__panel {
      color: var(--modal-contrast);
      background: var(--modal-accent);
      border-color: color-mix(in srgb, white 20%, transparent);
    }

    .wire-modal__header {
      display: flex;
      align-items: flex-start;
      justify-content: space-between;
      gap: 1rem;
      padding: 1.4rem 1.4rem 1.15rem;
      border-bottom: 1px solid var(--wire-color-border);
    }

    .wire-modal__heading {
      display: flex;
      align-items: flex-start;
      gap: 0.9rem;
      min-width: 0;
    }

    .wire-modal__icon {
      flex: 0 0 auto;
      width: 1.3rem;
      height: 1.3rem;
      margin-top: 0.15rem;
      color: var(--modal-accent);
    }

    .wire-modal[data-variant="solid"] .wire-modal__icon {
      color: currentColor;
    }

    .wire-modal__heading-copy {
      display: grid;
      gap: 0.3rem;
      min-width: 0;
    }

    .wire-modal__heading-copy h2,
    .wire-modal__heading-copy p {
      margin: 0;
    }

    .wire-modal__heading-copy h2 {
      color: inherit;
      font-size: 1.08rem;
      font-weight: 650;
      line-height: 1.3;
    }

    .wire-modal__heading-copy p {
      color: var(--wire-color-text-muted);
      font-size: 0.8rem;
      line-height: 1.55;
    }

    .wire-modal[data-variant="solid"] .wire-modal__heading-copy p {
      color: color-mix(in srgb, currentColor 76%, transparent);
    }

    /*
     * padding is reset explicitly: an app-level `button { padding: … }` rule
     * outranks the browser default, and 1rem of horizontal padding left this
     * 2.15rem button with a ~2px content box -- which squeezed the icon to
     * 0.4px wide and read as "the close button has no icon".
     */
    .wire-modal__close {
      appearance: none;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      flex: 0 0 auto;
      padding: 0;
      width: 2.15rem;
      height: 2.15rem;
      color: var(--wire-color-text-muted);
      background: transparent;
      border: 1px solid transparent;
      border-radius: 9999px;
      cursor: pointer;
      transition: background 150ms ease, color 150ms ease, border-color 150ms ease, transform 150ms ease;
    }

    /*
     * On a solid panel the background is the accent color, so the muted-grey
     * default is close to invisible -- the dismiss affordance reads as
     * missing rather than subtle. Derive it from the panel contrast color
     * instead, and give it a faint ring so it is unmistakably a control.
     */
    .wire-modal[data-variant="solid"] .wire-modal__close {
      color: color-mix(in srgb, currentColor 82%, transparent);
      border-color: color-mix(in srgb, currentColor 35%, transparent);
    }

    .wire-modal[data-variant="solid"] .wire-modal__close:hover {
      color: currentColor;
      background: color-mix(in srgb, black 18%, transparent);
      border-color: color-mix(in srgb, currentColor 55%, transparent);
    }

    .wire-modal__close:hover {
      color: var(--wire-color-text);
      background: var(--wire-color-surface-soft);
    }

    .wire-modal__close:focus-visible {
      color: var(--modal-accent);
      border-color: color-mix(in srgb, var(--modal-accent) 45%, transparent);
      outline: none;
    }

    .wire-modal__close:active {
      transform: scale(0.92);
    }

    /* Never let the glyph be shrunk by the flex container. */
    .wire-modal__close svg {
      flex: 0 0 auto;
      width: 1rem;
      height: 1rem;
    }

    /*
     * Slot content is authored by the host app, so the app global stylesheet
     * styles it too. A bare element selector there (p { color: ... }) beats
     * anything the panel merely *inherits*, which is how solid-variant modals
     * ended up with muted grey body copy on a saturated accent background --
     * unreadable, and worst exactly where contrast matters most (the
     * destructive confirm). Setting the color on the body makes the panel
     * choice explicit instead of leaving it to inheritance.
     *
     * NOTE: apostrophes are avoided in .wrn style comments on purpose -- the
     * block scanner treats a quote as a string delimiter while it counts
     * braces, so a stray one breaks parsing of the whole component.
     */
    .wire-modal__body {
      flex: 1 1 auto;
      min-height: 0;
      padding: 1.4rem;
      color: var(--wire-color-text);
    }

    /*
     * :where() keeps this at the specificity of .wire-modal__body alone, so it
     * outranks a global element selector but still yields to any class the app
     * puts on its own slot content (an error message, a muted caption). A
     * plain .wire-modal__body p list would have quietly overridden those.
     */
    .wire-modal__body :where(p, li, dd, dt, h1, h2, h3, h4, h5, h6, span, label, code) {
      color: inherit;
    }

    .wire-modal[data-variant="solid"] .wire-modal__body {
      color: var(--modal-contrast);
    }

    .wire-modal[data-scrollable="true"] .wire-modal__body {
      overflow: auto;
      overscroll-behavior: contain;
    }

    .wire-modal__footer {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 1rem;
      padding: 1rem 1.4rem 1.4rem;
      border-top: 1px solid var(--wire-color-border);
    }

    .wire-modal__custom-footer:empty {
      display: none;
    }

    .wire-modal__actions {
      display: flex;
      align-items: center;
      justify-content: flex-end;
      gap: 0.65rem;
      margin-left: auto;
    }

    .wire-modal__button {
      appearance: none;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      gap: 0.5rem;
      min-height: 2.55rem;
      padding: 0.65rem 1rem;
      border-radius: 0.78rem;
      font: inherit;
      font-size: 0.83rem;
      font-weight: 650;
      cursor: pointer;
      transition: transform 150ms ease, opacity 150ms ease, border-color 150ms ease;
    }

    .wire-modal__button:active {
      transform: scale(0.985);
    }

    .wire-modal__button--secondary {
      color: var(--wire-color-text);
      background: transparent;
      border: 1px solid var(--wire-color-border);
    }

    .wire-modal__button--primary {
      color: var(--modal-contrast);
      background: var(--modal-accent);
      border: 1px solid transparent;
    }

    /*
     * On a solid-variant panel the panel background is also --modal-accent,
     * so a plain primary button (same color) has no visible edge against it.
     * Darken the fill slightly and add a light border so the button still
     * reads as a distinct, clickable pill instead of blending into the panel.
     */
    .wire-modal[data-variant="solid"] .wire-modal__button--primary {
      background: color-mix(in srgb, black 18%, var(--modal-accent));
      border-color: color-mix(in srgb, white 32%, transparent);
      box-shadow: 0 1px 0 color-mix(in srgb, white 12%, transparent) inset;
    }

    .wire-modal__button:disabled {
      opacity: 0.55;
      cursor: not-allowed;
    }

    .wire-modal__spinner {
      width: 0.95rem;
      height: 0.95rem;
      border: 2px solid color-mix(in srgb, currentColor 35%, transparent);
      border-top-color: currentColor;
      border-radius: 50%;
      animation: wire-modal-spin 750ms linear infinite;
    }

    @keyframes wire-modal-spin {
      to {
        transform: rotate(360deg);
      }
    }

    @media (max-width: 639px) {
      .wire-modal__layer {
        align-items: flex-end;
        padding: 0;
      }

      .wire-modal__panel,
      .wire-modal[data-size="xs"] .wire-modal__panel,
      .wire-modal[data-size="sm"] .wire-modal__panel,
      .wire-modal[data-size="lg"] .wire-modal__panel,
      .wire-modal[data-size="xl"] .wire-modal__panel {
        width: 100%;
        max-height: 92vh;
        border-radius: 1.25rem 1.25rem 0 0;
      }

      .wire-modal__footer {
        align-items: stretch;
        flex-direction: column;
      }

      .wire-modal__actions {
        width: 100%;
      }

      .wire-modal__button {
        flex: 1 1 0;
      }
    }

    @media (prefers-reduced-motion: reduce) {
      .wire-modal__button,
      .wire-modal__spinner,
      .wire-modal__layer,
      .wire-modal__panel,
      .wire-modal__close {
        animation: none;
        transition: none;
      }

      .wire-modal__panel {
        transform: none;
      }
    }
  }
}