@use '~flux/components/css/mixin';

.table {
    composes: basePaneElement from './base/Pane.module.scss';

    position: relative;
    display: flex;
    container: table / inline-size;
    flex-flow: column;
    overflow: auto;
}

.tableBase {
    display: grid;
    align-content: start;
    flex-grow: 1;
    width: fit-content;
    min-width: 100%;
    grid-template-columns: var(--flux-table-columns, none);
    text-align: left;
}

.tableLoader {
    composes: basePaneLoader from './base/Pane.module.scss';
}

.tableHead,
.tableBody,
.tableFoot {
    display: grid;
    grid-column: 1 / -1;
    grid-template-columns: subgrid;
}

.tableBody {
    position: relative;
}

// Rows render as display: contents so their cells become direct grid items of
// the row group, sharing one grid across every row. That is what lets a cell
// span rows (grid-row: span N); with each row as its own grid container a cell
// could never reach into the next row. display: contents keeps the DOM intact,
// so sibling/descendant selectors and DOM measurements are unaffected — only
// styling that needs the row's own box (focus ring, sticky group row,
// scroll-margin) moves onto the cells or the spanning cell.
.tableRow {
    display: contents;
}

// The filler and empty rows are direct children of .tableBase, each occupying
// its own 1fr row track, so they stay real grid rows instead of display: contents.
.tableFill,
.tableEmptyFill {
    display: grid;
    grid-column: 1 / -1;
    grid-template-columns: subgrid;
}

// A clickable row keeps its own box: a display: contents element is not keyboard
// focusable, so the roving tabindex and the focus ring both need the row box.
// Such a row is a single grid row and therefore cannot hold a rowspan cell —
// rowspan lives in the (display: contents) rows of non-clickable tables.
.tableRow.isClickable {
    display: grid;
    grid-column: 1 / -1;
    grid-template-columns: subgrid;
    scroll-margin-top: var(--flux-table-head-height, 0px);
    cursor: pointer;

    @include mixin.focus-ring(-2px);
}

.tableCellBase {
    padding: 0;
    border: 0 solid var(--gray-100);
}

.tableCell {
    composes: tableCellBase;

    display: flex;
    align-items: flex-start;
    padding: 12px 15px;
    word-break: break-word;

    :global(p):only-child {
        margin: 0;
    }

    &.isRaw {
        display: block;
        padding: 0;
    }
}

.tableHeadSticky {
    position: sticky;
    top: 0;
    z-index: 3;
}

.tableHeader {
    composes: tableCell;

    @include mixin.text(small);

    font-weight: 700;
    align-items: center;
    color: var(--foreground-prominent);
    border-bottom: 2px solid var(--gray-100);

    &.isShrinking {
        white-space: nowrap;
    }

    > * {
        position: relative;
        z-index: 2;
    }
}

.tableHeadSticky .tableHeader {
    background: var(--gray-50);
}

.tableRowGray {
    --table-row-fill: var(--gray-25);
    --table-row-fill-hover: var(--gray-50);
    --table-row-foreground: var(--gray-800);
    --table-row-stroke: var(--gray-100);
}

.tableRowPrimary {
    --table-row-fill: var(--primary-25);
    --table-row-fill-hover: var(--primary-50);
    --table-row-foreground: var(--primary-800);
    --table-row-stroke: var(--primary-100);
}

.tableRowDanger {
    --table-row-fill: var(--danger-25);
    --table-row-fill-hover: var(--danger-50);
    --table-row-foreground: var(--danger-800);
    --table-row-stroke: var(--danger-100);
}

.tableRowInfo {
    --table-row-fill: var(--info-25);
    --table-row-fill-hover: var(--info-50);
    --table-row-foreground: var(--info-800);
    --table-row-stroke: var(--info-100);
}

.tableRowSuccess {
    --table-row-fill: var(--success-25);
    --table-row-fill-hover: var(--success-50);
    --table-row-foreground: var(--success-800);
    --table-row-stroke: var(--success-100);
}

.tableRowWarning {
    --table-row-fill: var(--warning-25);
    --table-row-fill-hover: var(--warning-50);
    --table-row-foreground: var(--warning-800);
    --table-row-stroke: var(--warning-100);
}

.tableBody .tableRow.isColored > .tableCell {
    background: var(--table-row-fill);
    border-color: var(--table-row-stroke);
    color: var(--table-row-foreground);
}

// The separator below a row is drawn by the next row's top border, so every
// variant tints that too (the same trick as the .isSelected rule below).
.tableBody .tableRowGray + .tableRow > .tableCell {
    border-top-color: var(--gray-100);
}

.tableBody .tableRowPrimary + .tableRow > .tableCell {
    border-top-color: var(--primary-100);
}

.tableBody .tableRowDanger + .tableRow > .tableCell {
    border-top-color: var(--danger-100);
}

.tableBody .tableRowInfo + .tableRow > .tableCell {
    border-top-color: var(--info-100);
}

.tableBody .tableRowSuccess + .tableRow > .tableCell {
    border-top-color: var(--success-100);
}

.tableBody .tableRowWarning + .tableRow > .tableCell {
    border-top-color: var(--warning-100);
}

.tableRow.isSelected {
    --table-row-stroke: var(--primary-100);
}

.tableBody .tableRow.isSelected > .tableCell {
    background: var(--primary-50);
    border-color: var(--table-row-stroke);
}

.tableBody .tableRow.isSelected + .tableRow > .tableCell {
    border-top-color: var(--primary-100);
}

// Row hover keys off :has(:hover) rather than .tableRow:hover: under
// display: contents the row has no box, so :hover on the row itself is
// unreliable, while :hover on the cells (which do have boxes) is not.
// The cells are matched with a child combinator so the highlight stays within
// one table: a nested table lives deep inside a cell, never as a direct child
// of the parent row, so hovering the parent row cannot light up its sub-rows.
// A rowspan cell belongs to the first row it covers but stretches across the
// rest, so it is excluded from the hover highlight (like .tableGroup): otherwise
// hovering the first row would light up the full-height cell on its own.
@media (hover: hover) {
    .table.isHoverable .tableBody .tableRow:has(:hover) > .tableCell:not(.tableGroup):not(.hasRowspan),
    .table.isHoverable .tableBody .tableRow:has(:focus-visible) > .tableCell:not(.tableGroup):not(.hasRowspan),
    .table.isHoverable .tableBody .tableRow:has(:hover) > .tableGroup.isHoverable,
    .table.isHoverable .tableBody .tableRow:has(:focus-visible) > .tableGroup.isHoverable {
        background: var(--gray-50);
    }

    .table.isHoverable .tableBody .tableRow.isColored:has(:hover) > .tableCell:not(.hasRowspan),
    .table.isHoverable .tableBody .tableRow.isColored:has(:focus-visible) > .tableCell:not(.hasRowspan) {
        background: var(--table-row-fill-hover);
    }

    .table.isHoverable .tableBody .tableRow.isSelected:has(:hover) > .tableCell:not(.hasRowspan),
    .table.isHoverable .tableBody .tableRow.isSelected:has(:focus-visible) > .tableCell:not(.hasRowspan) {
        background: var(--primary-50);
    }

    // The top border of a row is drawn by the row itself, but its color comes
    // from the row above. While a row is highlighted it takes its own stroke,
    // so the highlight is bounded by its own color instead of its neighbour's.
    .table.isHoverable .tableBody .tableRow:has(:hover) > .tableCell:not(.hasRowspan),
    .table.isHoverable .tableBody .tableRow:has(:focus-visible) > .tableCell:not(.hasRowspan) {
        border-top-color: var(--table-row-stroke, var(--gray-100));
    }
}

.tableFoot .tableCell {
    border-top: 1px solid var(--gray-100);
}

.tableCell + .tableCell {
    border-left-width: 1px;
}

// A cell that is the first in its row's markup but sits after a rowspan cell
// from a previous row has no preceding sibling, so it never gets the left
// border above. The spanning cell instead draws its own right border across
// every row it covers, and its immediate sibling drops the now-doubled left
// border so the first covered row keeps a single 1px separator.
.tableCell.hasRowspan {
    border-right-width: 1px;
}

.tableCell.hasRowspan + .tableCell {
    border-left-width: 0;
}

.tableBody .tableRow + .tableRow > .tableCell {
    border-top-width: 1px;
}

.tableBody .tableGroupRow + .tableRow > .tableCell {
    border-top-width: 0;
}

.tableBody .tableGroupSection:not(:first-child) .tableGroup {
    border-top-width: 1px;
}

.tableBody .tableGroupSection + .tableRow > .tableCell {
    border-top-width: 1px;
}

.tableBody .tableGroupSection:last-child .tableGroupRow:last-child .tableGroup {
    border-bottom-width: 0;
}

.tableActions {
    margin: -4px 0 -4px -3px;
}

.tableCellSelection {
    white-space: nowrap;
}

.tableCell.isNoWrap {
    white-space: nowrap;
}

.tableCell.isNumeric {
    font-variant-numeric: tabular-nums;
}

.tableCell.isPinnedStart,
.tableCell.isPinnedEnd {
    position: sticky;
    background: var(--surface);
    transition: box-shadow .2s var(--swift-out);
    z-index: 1;
}

.tableCell.isPinnedStart {
    left: 0;
}

.tableCell.isPinnedEnd {
    right: 0;
}

.tableHeader.isPinnedStart,
.tableHeader.isPinnedEnd {
    z-index: 4;
}

.tableHeadSticky .tableHeader.isPinnedStart,
.tableHeadSticky .tableHeader.isPinnedEnd {
    background: var(--gray-50);
}

.table[data-scrolled-start] .tableCell.isPinnedStart.isPinnedEdge {
    box-shadow: 1px 0 0 var(--surface-stroke-out), 6px 0 10px -6px rgb(from var(--gray-800) r g b / 0.15);
}

.table[data-scrollable-end] .tableCell.isPinnedEnd.isPinnedEdge {
    border-left-color: var(--surface-stroke-out);
    box-shadow: -6px 0 10px -6px rgb(from var(--gray-800) r g b / 0.15);
}

.tableFill {
    pointer-events: none;
    border-top: 1px solid var(--gray-100);

    .tableCell {
        padding: 0;
    }
}

.tableEmptyFill .tableCellBase {
    display: flex;
    flex-flow: column;
    justify-content: center;
}

.tableBar {
    --bg: var(--surface);

    composes: tableCell;

    position: relative;
    grid-column: 1 / -1;
    padding: 0;
    background: var(--bg);
    box-shadow: 0 0 18px var(--bg),
                0 0 18px var(--bg),
                0 0 18px var(--bg),
                0 0 18px var(--bg),
                0 0 18px var(--bg),
                0 0 18px var(--bg);
    z-index: 2;
}

.tableHeadSticky .tableBar {
    --bg: var(--gray-50);
}

.tableBarContent {
    position: sticky;
    left: 0;
    display: flex;
    align-items: flex-start;
    flex-flow: row nowrap;
    flex-grow: 1;
    gap: 15px;
    max-width: 100cqw;
    padding: 12px 15px;
    word-break: break-word;
}

.tableGroupSection {
    display: grid;
    grid-column: 1 / -1;
    grid-template-columns: subgrid;
}

.tableGroup {
    composes: tableCell;

    grid-column: 1 / -1;
    padding: 0;
    border-bottom-width: 1px;
    background: var(--surface);
}

// Under display: contents the group row has no box, so stickiness lives on the
// group cell, which is the full-width (grid-column: 1 / -1) box that remains.
.table.isSticky .tableBody .tableGroupRow .tableGroup {
    position: sticky;
    top: calc(var(--flux-table-head-height, 0px) - 1px);
    margin-top: -1px;
    border-top-width: 1px;
    z-index: 2;
}

.tableGroupContent {
    composes: tableBarContent;

    align-items: center;
    gap: 12px;
    background: color-mix(in srgb, var(--surface), var(--gray-50));

    &:is(button) {
        width: 100%;
        border: 0;
        color: inherit;
        font: inherit;
        text-align: left;
        cursor: pointer;
        transition: background var(--transition-default);
        transition-property: background, mixin.focus-ring-transition-properties();

        @include mixin.focus-ring(-2px);

        @include mixin.hover {
            background: var(--gray-50);
        }
    }
}

.tableGroupChevron {
    color: var(--foreground-secondary);
    transition: rotate .2s var(--swift-out);
}

.tableGroupChevron.isExpanded {
    rotate: 90deg;
}

.tableGroupIcon {
    color: var(--foreground);
}

.tableGroupLabel {
    @include mixin.text(small);

    flex-grow: 1;
    color: var(--foreground-prominent);
    font-weight: 600;
}

.tableGroupAfter {
    display: flex;
    align-items: center;
    gap: 9px;
}

.tableCaption {
    @include mixin.text(small);

    position: sticky;
    left: 0;
    max-width: 100cqw;
    color: var(--foreground-secondary);
    text-align: left;

    &.isTop {
        order: -1;
    }
}

.tablePagination {
    position: sticky;
    left: 0;
    bottom: 0;
    margin-top: auto;
    margin-bottom: -1px;
    background: var(--surface);
    border-top: 1px solid var(--gray-100);
    translate: 0 -1px;
    z-index: 100;
}

.tableSort {
    display: flex;
    height: 24px;
    width: 24px;
    margin: -3px -6px -3px 6px;
    padding: 0;
    align-items: center;
    justify-content: center;
    background: unset;
    border: 0;
    border-radius: var(--radius-half);
    color: var(--foreground-secondary);
    cursor: pointer;
    outline: 0;

    @include mixin.hover {
        background: var(--gray-100);
        color: var(--foreground);
    }
}

.basePaneStructure:has(.table) {
    --table-spacing: 18px;
}

.basePaneStructure .basePaneStructure {
    --table-spacing: 15px;
}

.basePaneStructure > .table .tableCell:not(.tableBar):not(.tableGroup):not(.isRaw):first-child {
    padding-left: var(--table-spacing);
}

.basePaneStructure > .table .tableCell:not(.tableBar):not(.tableGroup):not(.isRaw):last-child {
    padding-right: var(--table-spacing);
}

.basePaneStructure > .table :is(.tableBarContent, .tableGroupContent) {
    padding-left: var(--table-spacing);
    padding-right: var(--table-spacing);
}

.basePaneStructure > .table .tableActions {
    margin-right: -3px;
}

.basePaneStructure > .table .tableCaption {
    padding: 12px var(--table-spacing);
    border-top: 1px solid var(--gray-100);
}

.basePaneStructure > .table .tableLoader {
    border-radius: var(--radius);
}

.tableCellExpand {
    white-space: nowrap;
}

.tableExpandToggle :global(svg) {
    transition: rotate .2s var(--swift-out);
}

.tableExpandToggle.isExpanded :global(svg) {
    rotate: 90deg;
}

.tableExpandContent {
    padding: 12px 15px;
}

.tableTreeCell {
    composes: tableCell;

    align-items: center;
    padding-block: 0;
}

.treeBranch {
    position: relative;
    align-self: stretch;
    flex-shrink: 0;
    margin-right: 12px;
}

.tableTreeLines {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    fill: none;
    stroke: var(--surface-active);
    stroke-width: 2;
    stroke-linecap: round;
    pointer-events: none;
}

.treeMarker {
    --tree-marker-fill: var(--gray-100);
    --tree-marker-stroke: var(--gray-200);
    --tree-marker-foreground: var(--foreground-prominent);

    position: absolute;
    top: 50%;
    display: flex;
    box-sizing: border-box;
    width: 22px;
    height: 22px;
    align-items: center;
    justify-content: center;
    background: var(--tree-marker-fill);
    border: 1px solid var(--tree-marker-stroke);
    border-radius: 50%;
    color: var(--tree-marker-foreground);
    translate: 0 -50%;
    z-index: 1;
}

.treeMarker.isToggle {
    padding: 0;
    cursor: pointer;
    outline: 0;

    @include mixin.focus-ring(2px);

    @include mixin.hover {
        opacity: .75;
    }
}

.treeMarker.isToggle :global(svg) {
    transition: rotate .2s var(--swift-out);
}

.treeMarker.isToggle.isExpanded :global(svg) {
    rotate: 90deg;
}

.treeMarkerGray {
    --tree-marker-fill: var(--gray-100);
    --tree-marker-stroke: var(--gray-300);
    --tree-marker-foreground: var(--foreground-prominent);
}

.treeMarkerPrimary {
    --tree-marker-fill: var(--primary-100);
    --tree-marker-stroke: var(--primary-300);
    --tree-marker-foreground: var(--primary-900);
}

.treeMarkerDanger {
    --tree-marker-fill: var(--danger-100);
    --tree-marker-stroke: var(--danger-300);
    --tree-marker-foreground: var(--danger-900);
}

.treeMarkerInfo {
    --tree-marker-fill: var(--info-100);
    --tree-marker-stroke: var(--info-300);
    --tree-marker-foreground: var(--info-900);
}

.treeMarkerSuccess {
    --tree-marker-fill: var(--success-100);
    --tree-marker-stroke: var(--success-300);
    --tree-marker-foreground: var(--success-900);
}

.treeMarkerWarning {
    --tree-marker-fill: var(--warning-100);
    --tree-marker-stroke: var(--warning-300);
    --tree-marker-foreground: var(--warning-900);
}

.treeMarkerCustom {
    --tree-marker-fill: color-mix(in srgb, var(--tree-marker-color) 15%, var(--surface));
    --tree-marker-stroke: color-mix(in srgb, var(--tree-marker-color) 35%, var(--surface));
    --tree-marker-foreground: color-mix(in srgb, var(--tree-marker-color) 75%, var(--foreground));
}

.tableEmpty {
    display: flex;
    padding: 45px 15px;
    align-items: center;
    justify-content: center;
    color: var(--foreground-secondary);
    text-align: center;
}
