/*
 * Admin data table.
 *
 * Loaded after app-modern.css and after the inline <style> in base.html.twig, so these
 * rules win over the generic `table { }` defaults without needing !important.
 *
 * Three problems this solves:
 *  1. A long unbroken value (base URL, request id) used to be broken character by
 *     character and stretched a row to several hundred pixels. Columns now opt into
 *     one-line truncation with the full value in the tooltip.
 *  2. Once a table was wider than the viewport the last column - Actions - could not be
 *     reached. Columns can pin themselves to either edge while the body scrolls.
 *  3. On a phone a twelve-column table was unusable. Below 900px each row becomes a card
 *     and every cell prints its own label from data-label.
 */

.data-table {
    /* Tuning knobs; a page can override them on a wrapper if it needs to. */
    --dt-border: var(--color-border-cool-soft);
    --dt-header-bg: var(--color-surface-cool);
    --dt-header-fg: var(--color-slate-650);
    --dt-row-hover: var(--color-surface-cool);
    --dt-zebra: var(--color-surface);
    --dt-fg: var(--color-slate-900);
    --dt-muted: var(--color-slate-450);
    --dt-pad-y: 10px;
    --dt-pad-x: 14px;
    --dt-radius: 12px;

    width: 100%;
    margin: 0;
    /*
     * Fixed layout is what removes the horizontal scrollbar for good. The <colgroup>
     * widths stop being hard pixel demands and become proportions: whatever the viewport,
     * the columns always add up to the table width and the overflow is absorbed by
     * per-cell truncation instead of by a scrollbar. Below 900px the stacked card layout
     * takes over, so the columns never get squeezed to uselessness.
     */
    table-layout: fixed;
    border-collapse: separate;
    border-spacing: 0;
    font-size: 14px;
    color: var(--dt-fg);
    /* Lining figures keep numeric columns visually aligned. */
    font-variant-numeric: tabular-nums;
}

.data-table--dense {
    --dt-pad-y: 8px;
    --dt-pad-x: 10px;
    font-size: 13px;
}

/* ---------- scroll container ---------------------------------------------------- */

.data-table__scroll {
    /*
     * The container scrolls in BOTH axes on purpose. Sticky only works against a
     * scrolling ancestor, so without a height of its own the header would never actually
     * stick - the page would scroll past it. Capping the height keeps the column titles
     * visible for as long as the rows last. A page can override --dt-max-h.
     */
    --dt-max-h: min(78vh, 900px);

    width: 100%;
    max-height: var(--dt-max-h);
    overflow: auto;
    overscroll-behavior: contain;
    border: 1px solid var(--dt-border, var(--color-border-cool-soft));
    border-radius: 12px;
    background: var(--color-surface);
    /* Reserve the scrollbar so the layout does not jump when it appears. */
    scrollbar-gutter: stable;

    /*
     * Pure-CSS scroll shadows: the two `local` layers sit at the edges of the CONTENT and
     * slide away as you scroll, uncovering the two `scroll` layers that are painted
     * relative to the viewport. No JS scroll listener involved.
     */
    background-image:
        linear-gradient(to right, var(--color-surface) 30%, rgba(255, 255, 255, 0)),
        linear-gradient(to left, var(--color-surface) 30%, rgba(255, 255, 255, 0)),
        linear-gradient(to right, rgba(15, 23, 42, 0.10), rgba(15, 23, 42, 0)),
        linear-gradient(to left, rgba(15, 23, 42, 0.10), rgba(15, 23, 42, 0));
    background-position: left center, right center, left center, right center;
    background-repeat: no-repeat;
    background-size: 40px 100%, 40px 100%, 14px 100%, 14px 100%;
    background-attachment: local, local, scroll, scroll;
}

/* ---------- header --------------------------------------------------------------- */

.data-table thead th {
    overflow: hidden;
    text-overflow: ellipsis;
    position: sticky;
    top: 0;
    z-index: 3;
    padding: var(--dt-pad-y) var(--dt-pad-x);
    background: var(--dt-header-bg);
    color: var(--dt-header-fg);
    font-size: 12px;
    font-weight: 600;
    line-height: 1.3;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    text-align: left;
    white-space: nowrap;
    border-bottom: 1px solid var(--dt-border);
}

/* ---------- body ----------------------------------------------------------------- */

.data-table tbody td {
    padding: var(--dt-pad-y) var(--dt-pad-x);
    text-align: left;
    vertical-align: middle;
    border-bottom: 1px solid var(--dt-border);
    overflow: hidden;
    /* Long words wrap inside their column instead of pushing it wider. */
    overflow-wrap: anywhere;
}

.data-table tbody tr:last-child td {
    border-bottom: 0;
}

.data-table tbody tr:nth-child(even) td {
    background: var(--dt-zebra);
}

.data-table tbody tr:hover td {
    background: var(--dt-row-hover);
}

.data-table__empty-row td {
    padding: 28px var(--dt-pad-x);
    color: var(--dt-muted);
    text-align: center;
}

.data-table__empty-row:hover td {
    background: transparent;
}

/* ---------- per-column modifiers -------------------------------------------------- */

.data-table .is-truncate {
    overflow: hidden;
}

/* The actual one-line clamp. Its max-width is set inline from the column width. */
.data-table .dt-clip {
    display: block;
    max-width: 100%;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.data-table .is-nowrap {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.data-table .is-mono {
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
    font-size: 0.92em;
    color: var(--dt-muted);
}

.data-table .is-center {
    text-align: center;
}

.data-table .is-right {
    text-align: right;
}

/* ---------- pinned columns -------------------------------------------------------- */

.data-table .is-sticky-left,
.data-table .is-sticky-right {
    position: sticky;
    z-index: 2;
    background: var(--color-surface);
}

.data-table .is-sticky-left {
    left: 0;
    box-shadow: 1px 0 0 var(--dt-border);
}

.data-table .is-sticky-right {
    right: 0;
    /* A hard edge plus a short gradient, so the column reads as floating above the row
       instead of silently covering whatever scrolled underneath it. */
    box-shadow: -1px 0 0 var(--dt-border), -8px 0 8px -8px rgba(15, 23, 42, 0.18);
}

/* The pinned cell paints over the row, so it has to repeat the row's own background. */
.data-table tbody tr:nth-child(even) .is-sticky-left,
.data-table tbody tr:nth-child(even) .is-sticky-right {
    background: var(--dt-zebra);
}

.data-table tbody tr:hover .is-sticky-left,
.data-table tbody tr:hover .is-sticky-right {
    background: var(--dt-row-hover);
}

.data-table thead .is-sticky-left,
.data-table thead .is-sticky-right {
    z-index: 4;
    background: var(--dt-header-bg);
}

/* ---------- row states ------------------------------------------------------------ */

.data-table tbody tr.is-highlighted td,
.data-table tbody tr.is-highlighted .is-sticky-left,
.data-table tbody tr.is-highlighted .is-sticky-right {
    background: var(--color-brand-surface);
}

/* The accent belongs to the row, so it is drawn once on the leading cell - painting it on
   every cell turned the row into a set of green vertical bars, one per column. */
.data-table tbody tr.is-muted td {
    color: var(--dt-muted);
}

/*
 * The left marker is the row's one-glance verdict, in three grades:
 *   green - the key generating right now
 *   red   - the key is dead until a person fixes it
 *
 * Deliberately only two, and red deliberately does NOT mean "unavailable". A key waiting
 * out a rate-limit cooldown is unavailable and will recover by itself; painting it red
 * teaches the reader to ignore red. What earns the bar is a missing credential, a missing
 * model, or a provider verdict the router cannot argue with - revoked, forbidden, out of
 * money. Everything softer is stated in the State column and left there.
 */
.data-table tbody tr.is-dead td:first-child {
    /* One pixel wider than the "in use" marker: this one is asking for work. */
    box-shadow: inset 4px 0 0 var(--color-danger-mark);
}

.data-table tbody tr.is-dead td,
.data-table tbody tr.is-dead.data-table__sub-row td,
.data-table tbody tr.is-dead.data-table__detail-row td {
    background: var(--color-level-bad-surface);
}

.data-table tbody tr.is-dead:hover td {
    background: var(--color-danger-badge-surface);
}


/*
 * Declared last so it wins on a tie: a key can be both the one generating right now and
 * one that failed earlier, and "this is what your users are being served by" is the more
 * useful of the two facts. The old error is still on the line below.
 */
.data-table tbody tr.is-highlighted td:first-child {
    box-shadow: inset 3px 0 0 var(--color-brand);
}


.data-table tbody tr.is-draggable {
    cursor: grab;
}

.data-table tbody tr.is-drag-over td {
    box-shadow: inset 0 2px 0 var(--color-link-strong);
}

/* ---------- cell helpers ----------------------------------------------------------- */

/* Secondary line under the main value (an error message, a timestamp). */
/*
 * Secondary line under the main value. Deliberately clamped to a single line: these carry
 * provider error text and quota descriptions, and letting them wrap stretched rows to
 * several hundred pixels. The full text stays available through the cell's title.
 */
.data-table .dt-sub {
    display: block;
    max-width: 100%;
    margin-top: 2px;
    overflow: hidden;
    color: var(--dt-muted);
    font-size: 11.5px;
    line-height: 1.3;
    white-space: nowrap;
    text-overflow: ellipsis;
}

/*
 * Wrap onto a second line rather than cutting a value off mid-word, then stop. Two lines
 * show the whole of almost every real value while still bounding the row height - which
 * is what went wrong when cells were free to wrap as far as they liked.
 */
.data-table .dt-wrap-2 {
    display: -webkit-box;
    overflow: hidden;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow-wrap: anywhere;
}

/*
 * Primary line of a two-line cell. Together with .dt-sub this caps every cell at exactly
 * two lines, which is what keeps rows scannable: before this, one long value could wrap
 * into six lines and drag the whole row to ~180px.
 */
.data-table .dt-main {
    display: block;
    max-width: 100%;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

/* Actions: keep buttons on one line and stop them from stretching the column. */
/*
 * Actions scale with the column like everything else under table-layout: fixed, so the
 * buttons are sized to survive the narrowest desktop the table still renders at.
 */
/* A secondary line that carries a positive signal rather than a caveat. */
/* A value that is itself the bad news, rather than a caveat next to good news. */
.data-table .dt-error {
    color: var(--color-danger);
}

.data-table .dt-sub--accent {
    color: var(--color-success-deep);
    font-weight: 600;
}

.data-table .dt-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 4px;
    white-space: nowrap;
}

.data-table .dt-actions button,
.data-table .dt-actions a.btn {
    flex: 0 0 auto;
}

.data-table .dt-handle {
    color: var(--color-slate-400);
    cursor: grab;
    user-select: none;
    text-align: center;
}

/*
 * Badges inside a table are labels, not buttons: the page-wide .badge is sized for a
 * headline area and reads as a heavy block when it is repeated down sixteen rows.
 */
.data-table .badge {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 999px;
    font-size: 11.5px;
    font-weight: 600;
    line-height: 1.5;
    letter-spacing: 0.01em;
    white-space: nowrap;
}

/* Inline controls inside a cell should not inherit the page's full-width field styling. */
.data-table td .field-control {
    min-width: 110px;
    padding: 6px 8px;
    font-size: 13px;
}

/* ---------- stacked layout for narrow screens --------------------------------------- */

@media (max-width: 1280px) {
    /* Stacked layout: proportional columns would be meaningless at this width. */
    .data-table {
        table-layout: auto;
    }

    .data-table__scroll {
        max-height: none;
        overflow: visible;
        border: 0;
        border-radius: 0;
        background: none;
        background-image: none;
    }

    .data-table,
    .data-table thead,
    .data-table tbody,
    .data-table tr,
    .data-table td {
        display: block;
        width: auto;
    }

    /* The header is replaced by per-cell labels below. */
    .data-table thead {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }

    .data-table tbody tr {
        margin-bottom: 12px;
        padding: 4px 0;
        border: 1px solid var(--dt-border);
        border-radius: var(--dt-radius);
        background: var(--color-surface);
        overflow: hidden;
    }

    .data-table tbody td,
    .data-table tbody tr:nth-child(even) td,
    .data-table tbody tr:hover td {
        display: flex;
        gap: 12px;
        align-items: baseline;
        justify-content: space-between;
        max-width: none;
        padding: 9px 14px;
        background: transparent;
        white-space: normal;
        text-overflow: clip;
        overflow: visible;
    }

    .data-table tbody td::before {
        content: attr(data-label);
        flex: 0 0 40%;
        color: var(--dt-muted);
        font-size: 11px;
        font-weight: 600;
        letter-spacing: 0.04em;
        text-transform: uppercase;
    }

    /* Cells with no label (drag handle) keep the full width. */
    .data-table tbody td:not([data-label])::before {
        content: none;
    }

    .data-table .is-sticky-left,
    .data-table .is-sticky-right {
        position: static;
        box-shadow: none;
    }

    /* A secondary line that carries a positive signal rather than a caveat. */
/* A value that is itself the bad news, rather than a caveat next to good news. */
.data-table .dt-error {
    color: var(--color-danger);
}

.data-table .dt-sub--accent {
    color: var(--color-success-deep);
    font-weight: 600;
}

.data-table .dt-actions {
        justify-content: flex-start;
    }

    /*
     * The clamp width is an inline style (it mirrors the column width), so releasing it
     * for the stacked layout is the one place !important is warranted.
     */
    .data-table .dt-clip {
        max-width: none !important;
        white-space: normal;
        text-align: right;
    }

    .data-table__empty-row td {
        justify-content: center;
        text-align: center;
    }

    .data-table__empty-row td::before {
        content: none;
    }
}

/* ---------- always-visible detail line ------------------------------------------------ */

/*
 * Values that do not fit a column live here rather than in a title attribute: a tooltip
 * cannot be read without a mouse, does not appear in the browser's find-on-page, and does
 * not exist on touch. A line costs about twenty pixels; a hidden value costs a support
 * question.
 */
.data-table tbody tr.has-sub td {
    border-bottom: 0;
}

/* Tied to its row by the shared background, the missing border above it and this indent -
   a rule of its own would just repeat the row accent sitting a few pixels to the left. */
.data-table__sub-row td {
    padding: 0 var(--dt-pad-x) 10px calc(var(--dt-pad-x) + 34px);
    border-bottom: 1px solid var(--dt-border);
    overflow: hidden;
}

.data-table tbody tr.data-table__sub-row:hover td {
    background: inherit;
}

.dt-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 3px 18px;
    font-size: 11.5px;
    line-height: 1.5;
    color: var(--dt-muted);
}

.dt-meta__item {
    display: inline-flex;
    gap: 5px;
    align-items: baseline;
    min-width: 0;
}

.dt-meta__label {
    color: var(--color-slate-400);
    font-size: 10px;
    font-weight: 600;
    line-height: 1.45;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    white-space: nowrap;
}

.dt-meta__value {
    color: var(--color-slate-650);
    line-height: 1.45;
    overflow-wrap: anywhere;
}

/* Provider error text runs to several sentences; two lines is enough to recognise it. */
.dt-meta__value--clip {
    display: -webkit-box;
    max-width: 150ch;
    overflow: hidden;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
}

.dt-meta__item--alert {
    flex: 1 1 100%;
    min-width: 0;
}

.dt-meta__item--alert .dt-meta__label,
.dt-meta__item--alert .dt-meta__value {
    color: var(--color-danger);
}

.dt-meta__item--alert .dt-meta__value {
    font-weight: 500;
}

/*
 * The label and the message have different font sizes, so they can only line up on their
 * baselines - aligning their boxes to the top left the smaller label sitting visibly lower
 * than the text next to it. The icon is a box with no baseline of its own: it inherits the
 * bottom edge, which reads a couple of pixels high, hence the nudge.
 */
.dt-warn {
    flex: 0 0 auto;
    align-self: baseline;
    color: var(--color-danger-mark);
    transform: translateY(2px);
}

@media (max-width: 1280px) {
    .data-table__sub-row td {
        padding: 0 14px 12px;
    }

    .data-table__sub-row td::before {
        content: none;
    }
}

/* ---------- expandable full record ---------------------------------------------------- */

.data-table .dt-toggle-cell {
    padding-right: 8px;
    padding-left: 4px;
}

.dt-toggle {
    display: inline-flex;
    width: 26px;
    height: 26px;
    padding: 0;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--dt-border);
    border-radius: 7px;
    background: var(--color-surface);
    color: var(--dt-muted);
    cursor: pointer;
    font-size: 12px;
    line-height: 1;
}

.dt-toggle:hover {
    border-color: var(--color-slate-400);
    color: var(--dt-fg);
}

.data-table__detail-row td {
    padding: 2px var(--dt-pad-x) 16px calc(var(--dt-pad-x) + 22px);
    border-bottom: 1px solid var(--dt-border);
    background: var(--color-surface-cool);
}

.data-table tbody tr.data-table__detail-row:hover td {
    background: var(--color-surface-cool);
}

/* Grouped so the panel is read by section rather than scanned as forty loose values. */
.dt-record {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
    gap: 14px 26px;
    padding: 14px 16px;
    border: 1px solid var(--dt-border);
    border-radius: 10px;
    background: var(--color-surface);
}

.dt-record__group h4 {
    margin: 0 0 8px;
    color: var(--color-slate-400);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.dt-record__row {
    display: flex;
    gap: 10px;
    justify-content: space-between;
    padding: 3px 0;
    font-size: 12.5px;
    line-height: 1.45;
    border-bottom: 1px dashed var(--color-surface-divider);
}

.dt-record__row:last-child {
    border-bottom: 0;
}

.dt-record__key {
    flex: 0 0 auto;
    color: var(--dt-muted);
}

.dt-record__val {
    color: var(--color-slate-900);
    text-align: right;
    overflow-wrap: anywhere;
}

.dt-record__val--muted {
    color: var(--color-slate-400);
}

.dt-record__val--alert {
    color: var(--color-danger);
}

.dt-record__group--wide {
    grid-column: 1 / -1;
}

.dt-record__group--wide .dt-record__val {
    text-align: left;
}

@media (max-width: 1280px) {
    .data-table__detail-row td {
        padding: 2px 14px 14px;
    }

    .data-table__detail-row td::before {
        content: none;
    }

    .dt-record {
        grid-template-columns: 1fr;
    }
}

/* A read-only value shown where a disabled field used to be: a form control nobody can
   use still invites clicking, and copies the text badly. */
.dt-readonly {
    margin: 4px 0 0;
    padding: 9px 11px;
    border: 1px solid var(--color-border-cool-soft);
    border-radius: 8px;
    background: var(--color-surface-cool);
    color: var(--color-slate-650);
    font-size: 13px;
    line-height: 1.5;
}

.dt-card-header__aside {
    margin-left: auto;
    color: var(--color-slate-400);
    font-size: 12px;
}

/* A card title with its own disclosure sitting immediately after it. */
.dt-card-header {
    display: flex;
    gap: 10px;
    align-items: center;
}

.dt-card-header h2 {
    margin: 0;
}

/* Plain-language note under a field label in the expanded record: the panel has to be
   readable by someone who did not build the pool. */
.dt-record__hint {
    display: block;
    max-width: 34ch;
    margin-top: 1px;
    color: var(--color-slate-400);
    font-size: 10.5px;
    font-weight: 400;
    line-height: 1.35;
}

.dt-record__key {
    flex: 1 1 auto;
    min-width: 0;
}

.dt-record__row {
    align-items: flex-start;
}

.dt-record__val {
    flex: 0 0 auto;
    padding-top: 1px;
    white-space: nowrap;
}

.dt-record__group--wide .dt-record__val {
    white-space: normal;
}

/* ---------- toolbar above a table ---------------------------------------------------- */

/*
 * Spans the table it belongs to. A measure of 60-90 characters is the comfortable range
 * for prose, and this text is above it on a wide screen - but a caption clipped to half
 * the width of the block it describes reads as a layout mistake, and it is three short
 * lines, not an article. Alignment wins here.
 */
.data-table-caption {
    margin: 0 0 12px;
    color: var(--color-slate-450);
    font-size: 13px;
    line-height: 1.5;
}
