<?php
if (!function_exists('enrepReportDataFromRow')) {
    require_once __DIR__ . '/../master/form_functions.php';
}

$clientnumber = (int)($_REQUEST['clientnumber'] ?? 0);
$filter = (string)($_REQUEST['filter'] ?? '');
$status = ($filter === 'archive') ? 0 : 1;
$clientName = function_exists('getClientName') ? getClientName($clientnumber) : '';

function schedulePrintText($value): string
{
    return htmlspecialchars((string)($value ?? ''), ENT_QUOTES, 'UTF-8');
}

function schedulePrintDue($value): string
{
    if (empty($value) || !is_numeric($value)) {
        return 'No date stored';
    }

    return date('d-m-Y', (int)$value);
}

function schedulePrintColumnExists(mysqli $con, string $table, string $column): bool
{
    $stmt = $con->prepare("
        SELECT COUNT(*)
        FROM INFORMATION_SCHEMA.COLUMNS
        WHERE TABLE_SCHEMA = DATABASE()
          AND TABLE_NAME = ?
          AND COLUMN_NAME = ?
    ");
    if (!$stmt) {
        return false;
    }

    $stmt->bind_param('ss', $table, $column);
    $stmt->execute();
    $stmt->bind_result($count);
    $stmt->fetch();
    $stmt->close();

    return (int)$count > 0;
}

function schedulePrintDefaultPrefs(): array
{
    return [
        'itemnumber'   => ['label' => 'Item ID',      'visible' => true],
        'description'  => ['label' => 'Description',  'visible' => true],
        'manufacturer' => ['label' => 'Manufacturer', 'visible' => true],
        'make_model'   => ['label' => 'Make/Model',   'visible' => true],
        'condition'    => ['label' => 'Condition',    'visible' => true],
        'plantnumber'  => ['label' => 'Plant No.',    'visible' => true],
        'serialnumber' => ['label' => 'Serial',       'visible' => true],
        'locname'      => ['label' => 'Location',     'visible' => true],
        'subloc'       => ['label' => 'Sub-location', 'visible' => true],
        'postcode'     => ['label' => 'Post Code',    'visible' => true],
        'lastReport'   => ['label' => 'Result',       'visible' => true],
        'duedate'      => ['label' => 'Due',          'visible' => true],
         'swl'      => ['label' => 'SWL',          'visible' => true],
    ];
}

function schedulePrintLoadPrefs(): array
{
    $prefs = $GLOBALS['assetPrefs'] ?? [];

    if (isset($GLOBALS['con']) && $GLOBALS['con'] instanceof mysqli) {
        try {
            $stmt = $GLOBALS['con']->prepare("SELECT `asset_table_prefs` FROM `system` WHERE `id`='1' LIMIT 1");
            if ($stmt) {
                $stmt->execute();
                $stmt->bind_result($prefsJson);
                if ($stmt->fetch() && trim((string)$prefsJson) !== '') {
                    $decoded = json_decode((string)$prefsJson, true);
                    if (is_array($decoded)) {
                        $prefs = $decoded;
                    }
                }
                $stmt->close();
            }
        } catch (Throwable $e) {
            // Keep any global/default preferences.
        }
    }

    if (isset($prefs['model']) && !isset($prefs['make_model'])) {
        $prefs['make_model'] = $prefs['model'];
    }

    $defaults = schedulePrintDefaultPrefs();
    foreach ($defaults as $key => $default) {
        if (!isset($prefs[$key]) || !is_array($prefs[$key])) {
            $prefs[$key] = $default;
            continue;
        }

        $prefs[$key]['label'] = isset($prefs[$key]['label']) && trim((string)$prefs[$key]['label']) !== ''
            ? (string)$prefs[$key]['label']
            : $default['label'];

        if (array_key_exists('visible', $prefs[$key])) {
            $prefs[$key]['visible'] = (bool)$prefs[$key]['visible'];
        } elseif (array_key_exists('enabled', $prefs[$key])) {
            $prefs[$key]['visible'] = (bool)$prefs[$key]['enabled'];
        } else {
            $prefs[$key]['visible'] = (bool)$default['visible'];
        }
    }

    return $prefs;
}

function schedulePrintColumnLabel(array $prefs, string $key): string
{
    return schedulePrintText($prefs[$key]['label'] ?? $key);
}

function schedulePrintColumnVisible(array $prefs, string $key): bool
{
    return !isset($prefs[$key]['visible']) || (bool)$prefs[$key]['visible'];
}

function schedulePrintColumnValue(array $row, array $reportRow, string $key, string $dueText, array $conditionLabels): string
{
    switch ($key) {
        case 'itemnumber':
            return (string)($row['itemnumber'] ?? '');
        case 'description':
            return (string)($row['desc'] ?? '');
        case 'manufacturer':
            return (string)($row['manf'] ?? '');
        case 'make_model':
            return (string)($row['model'] ?? '');
        case 'condition':
            $conditionRaw = trim((string)($row['asset_condition'] ?? ''));
            $conditionCode = strtoupper($conditionRaw);
            return $conditionRaw !== '' ? ($conditionLabels[$conditionCode] ?? $conditionRaw) : '';
        case 'plantnumber':
            return (string)($row['plantnumber'] ?? '');
        case 'serialnumber':
            return (string)($row['serialnumber'] ?? '');
        case 'locname':
            return (string)($row['locname'] ?? '');
        case 'subloc':
            return (string)($row['subloc'] ?? '');
        case 'postcode':
            return (string)($row['postcode'] ?? '');
        case 'lastReport':
            return (string)($row['lastReport'] ?? '');
            case 'swl':
            return (string)($row['swl'] ?? '');
        case 'duedate':
            return $dueText;
        default:
            return (string)($row[$key] ?? $reportRow[$key] ?? '');
    }
}

$assetPrefs = schedulePrintLoadPrefs();
$assetColumnOrder = [
    'itemnumber',
    'description',
    'manufacturer',
    'make_model',
    'condition',
    'plantnumber',
    'serialnumber',
    'swl',
    'locname',
    'subloc',
    'postcode',
    'lastReport',
    'duedate',
];
$visibleAssetColumns = array_values(array_filter($assetColumnOrder, static function ($columnKey) use ($assetPrefs): bool {
    return schedulePrintColumnVisible($assetPrefs, $columnKey);
}));
$conditionSelect = (isset($GLOBALS['con']) && $GLOBALS['con'] instanceof mysqli && schedulePrintColumnExists($GLOBALS['con'], 'itemdetails', 'condition'))
    ? 'a.`condition`'
    : "''";
$conditionLabels = [
    'A' => 'As New',
    'B' => 'In use. Used',
    'C' => 'In use. Tired',
    'D' => 'In storage. Ready to use',
    'E' => 'In storage. Attention Needed',
    'F' => 'Disregarded',
    'G' => 'Decommissioned',
];

$stmt = $GLOBALS['con']->prepare("
    SELECT
        a.itemnumber,
        a.locnumber,
        a.desc,
        a.manf,
        a.model,
         a.swl,
        a.code,
        {$conditionSelect} AS asset_condition,
        a.plantnumber,
        a.serialnumber,
        a.subloc,
        a.swl,
        a.duedate,
        a.lastReport,
        a.linked,
        a.itemnotes,
        a.statuslive,
        b.locname,
        b.locnotes,
        b.postcode
    FROM itemdetails a
    JOIN locations b ON a.locnumber = b.locnumber
    WHERE a.clientnumber = ? AND a.statuslive = ?
") or die(mysqli_error($GLOBALS['con']));
$stmt->bind_param('ii', $clientnumber, $status);
$stmt->execute();
$ic = $stmt->get_result();
$stmt->close();
?>

<style>
    .schedule-print-panel {
        background: #f8fafc;
        border: 1px solid #d9e1ec;
        border-left: 4px solid #0055A6;
        border-radius: 6px;
        margin-bottom: 1rem;
        padding: .9rem;
    }

    .schedule-print-title {
        color: #2d4661;
        font-weight: 600;
        margin: 0;
    }

    .schedule-print-help {
        color: #6c757d;
        font-size: .82rem;
    }

    .schedule-filter-row {
        align-items: end;
        background: #ffffff;
        border: 1px solid #dde4ee;
        border-radius: 6px;
        display: grid;
        gap: .5rem;
        grid-template-columns: minmax(160px, 1fr) minmax(150px, .85fr) minmax(220px, 1.1fr) auto;
        margin-top: .6rem;
        padding: .65rem;
    }

    .schedule-filter-row .btn {
        min-height: 34px;
    }

    .schedule-filter-row .select-wrapper {
        width: 100%;
    }

    .schedule-filter-row .select-input,
    .schedule-filter-row .form-outline {
        min-height: 34px;
    }

    .schedule-print-actions {
        display: flex;
        flex-wrap: wrap;
        gap: .5rem;
        justify-content: flex-end;
    }

    .schedule-column-panel {
        background: #ffffff;
        border: 1px solid #dde4ee;
        border-radius: 6px;
        display: none;
        margin-top: .75rem;
        padding: .75rem;
    }

    .schedule-column-panel.is-open {
        display: block;
    }

    .schedule-column-grid {
        display: grid;
        gap: .5rem;
        grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    }

    .schedule-column-toggle {
        align-items: center;
        border: 1px solid #e4e9f0;
        border-radius: 6px;
        display: flex;
        gap: .5rem;
        margin: 0;
        padding: .4rem .55rem;
    }

    .schedule-table-wrap {
        overflow-x: auto;
    }

    #assetTable th[data-sort-field] {
        cursor: pointer;
        white-space: nowrap;
    }

    #assetTable th.schedule-sort-asc::after {
        content: " \25B2";
        font-size: .75rem;
    }

    #assetTable th.schedule-sort-desc::after {
        content: " \25BC";
        font-size: .75rem;
    }

    @media (max-width: 991.98px) {
        .schedule-filter-row {
            grid-template-columns: 1fr 1fr;
        }

        .schedule-filter-value {
            grid-column: 1 / -1;
        }
    }

    @media (max-width: 575.98px) {
        .schedule-filter-row {
            grid-template-columns: 1fr;
        }

        .schedule-print-actions {
            justify-content: stretch;
        }

        .schedule-print-actions .btn {
            width: 100%;
        }
    }
</style>

<div class="row">
    <div class="col-12">
        <div id="loadingObject">
            <h1 class="text-center">
                <i class="fad fa-flux-capacitor fa-spin fa-3x fa-fw" style="--fa-primary-color: #0055A6; --fa-secondary-color: gray;"></i><br>
                Loading Data <br><small>Please wait</small>
            </h1>
        </div>

        <div id="exportTitle" style="display:none; background-color:#353b50; color:#ffffff">
            <p style="font-size:10px"><?= schedulePrintText($clientName) ?> : Schedule</p>
        </div>

        <div id="getDetailsbox"></div>

        <div id="schedulePrintFilters" class="schedule-print-panel" style="display:none;">
            <div class="d-flex align-items-start justify-content-between flex-wrap gap-2 mb-3">
                <div>
                    <p class="schedule-print-title">
                        <i class="fad fa-filter me-1"></i> Print Filters
                    </p>
                    <div class="schedule-print-help">Filter, print, or export the current schedule view.</div>
                </div>
                <div class="schedule-print-actions">
                    <button type="button" id="schedulePrintColumns" class="btn btn-light btn-sm">
                        <i class="fad fa-columns-3"></i> Columns
                    </button>
                    <button type="button" id="schedulePrintPrint" class="btn btn-secondary btn-sm">
                        <i class="fad fa-print"></i> Print
                    </button>
                    <button type="button" id="schedulePrintCsv" class="btn btn-success btn-sm text-light">
                        <i class="fad fa-file-excel"></i> CSV
                    </button>
                    <button type="button" id="schedulePrintResetFilters" class="btn btn-light btn-sm">
                        <i class="fad fa-rotate-left"></i> Reset
                    </button>
                </div>
            </div>

            <div id="schedulePrintColumnPanel" class="schedule-column-panel">
                <div class="d-flex align-items-center justify-content-between flex-wrap gap-2 mb-2">
                    <strong>Visible Columns</strong>
                    <div class="d-flex gap-2">
                        <button type="button" id="schedulePrintColumnsAll" class="btn btn-light btn-sm">All</button>
                        <button type="button" id="schedulePrintColumnsDefault" class="btn btn-light btn-sm">Default</button>
                    </div>
                </div>
                <div id="schedulePrintColumnList" class="schedule-column-grid"></div>
            </div>

            <div class="row g-2 align-items-end">
                <div class="col-12 col-lg-5">
                    <label for="schedulePrintSearch" class="form-label mb-1">Search schedule</label>
                    <input type="search" id="schedulePrintSearch" class="form-control form-control-sm"
                        placeholder="Search visible schedule columns and defects">
                </div>
                <div class="col-6 col-lg-2">
                    <label for="schedulePrintRows" class="form-label mb-1">Rows</label>
                    <select id="schedulePrintRows" class="form-select form-select-sm">
                        <option value="10">10</option>
                        <option value="25" selected>25</option>
                        <option value="50">50</option>
                        <option value="100">100</option>
                        <option value="200">200</option>
                        <option value="-1">All</option>
                    </select>
                </div>
                <div class="col-6 col-lg-2">
                    <label for="schedulePrintLogic" class="form-label mb-1">Require</label>
                    <select id="schedulePrintLogic" class="form-select form-select-sm">
                        <option value="all">Every condition</option>
                        <option value="any">At least one condition</option>
                    </select>
                    <div class="form-text small">Used when more than one condition is added.</div>
                </div>
                <div class="col-12 col-lg-3 text-lg-end">
                    <button type="button" id="schedulePrintAddCondition" class="btn btn-info btn-sm text-light">
                        <i class="fad fa-plus"></i> Add condition
                    </button>
                </div>
            </div>

            <div id="schedulePrintConditions" class="mt-2"></div>
            <div id="schedulePrintSummary" class="small text-muted mt-3"></div>
        </div>

        <div class="schedule-table-wrap">
            <table id="assetTable" class="table table-striped table-sm table-bordered table-hover responsive" width="100%" style="width:100%; display:none">
                <thead class="table-dark">
                    <tr>
                        <th class="noExport" data-sort-field="chain" data-column-key="chain">Chain</th>
                        <?php foreach ($visibleAssetColumns as $columnKey) { ?>
                        <th data-sort-field="<?= schedulePrintText($columnKey) ?>" data-column-key="<?= schedulePrintText($columnKey) ?>"<?= $columnKey === 'duedate' ? ' class="date-uk"' : '' ?>>
                            <?= schedulePrintColumnLabel($assetPrefs, $columnKey) ?>
                        </th>
                        <?php } ?>
                        <th data-sort-field="adefect" data-column-key="adefect">A Defect</th>
                        <th data-sort-field="bdefect" data-column-key="bdefect">B Defect</th>
                        <th data-sort-field="obs" data-column-key="obs">Observations</th>
                    </tr>
                </thead>
                <tbody id="schedulePrintBody">
                    <?php
                    while ($id = mysqli_fetch_array($ic)) {
                        $stmt = $GLOBALS['con']->prepare("SELECT `reportdata` FROM `reportitems` WHERE `itemnumber`=? ORDER BY abs(insdate) DESC LIMIT 1") or die(mysqli_error($GLOBALS['con']));
                        $itemnumber = (int)$id['itemnumber'];
                        $stmt->bind_param('i', $itemnumber);
                        $stmt->execute();
                        $r = $stmt->get_result();
                        $stmt->close();
                        $reportRow = $r ? mysqli_fetch_array($r) : [];
                        $rd = is_array($reportRow) ? enrepReportDataFromRow($reportRow) : [];

                        $ldesc = explode(":", (string)($id['linked'] ?? ''));
                        $chain = !empty($ldesc[0]) ? "C" . $ldesc[0] : '';
                        $dueText = schedulePrintDue($id['duedate'] ?? '');
                        $dueSort = (empty($id['duedate']) || !is_numeric($id['duedate'])) ? 0 : (int)$id['duedate'];
                        $assetColumnValues = [];
                        foreach ($visibleAssetColumns as $columnKey) {
                            $assetColumnValues[$columnKey] = schedulePrintColumnValue($id, $rd, $columnKey, $dueText, $conditionLabels);
                        }
                    ?>
                    <tr
                        data-chain="<?= schedulePrintText($chain) ?>"
                        <?php foreach ($visibleAssetColumns as $columnKey) { ?>
                        data-<?= schedulePrintText($columnKey) ?>="<?= schedulePrintText($assetColumnValues[$columnKey] ?? '') ?>"
                        <?php } ?>
                        data-adefect="<?= schedulePrintText($rd['adefect'] ?? '') ?>"
                        data-bdefect="<?= schedulePrintText($rd['bdefect'] ?? '') ?>"
                        data-obs="<?= schedulePrintText($rd['obs'] ?? '') ?>"
                        <?php if (!in_array('duedate', $visibleAssetColumns, true)) { ?>
                        data-duedate="<?= schedulePrintText($dueText) ?>"
                        <?php } ?>
                        data-duesort="<?= $dueSort ?>">
                        <td data-column-key="chain" style="vertical-align: top;"><?= schedulePrintText($chain) ?></td>
                        <?php foreach ($visibleAssetColumns as $columnKey) { ?>
                        <td data-column-key="<?= schedulePrintText($columnKey) ?>" style="vertical-align: top;"><?= schedulePrintText($assetColumnValues[$columnKey] ?? '') ?></td>
                        <?php } ?>
                        <td data-column-key="adefect" style="vertical-align: top;" width="15%"><?= schedulePrintText($rd['adefect'] ?? '') ?></td>
                        <td data-column-key="bdefect" style="vertical-align: top;" width="15%"><?= schedulePrintText($rd['bdefect'] ?? '') ?></td>
                        <td data-column-key="obs" style="vertical-align: top;" width="15%"><?= schedulePrintText($rd['obs'] ?? '') ?></td>
                    </tr>
                    <?php } ?>
                </tbody>
            </table>
        </div>

        <div id="schedulePrintNoRows" class="alert alert-info text-center" style="display:none;">
            No schedule items match the current filters.
        </div>

        <div id="schedulePrintPager" class="d-flex align-items-center justify-content-between flex-wrap gap-2 mt-2" style="display:none;">
            <div class="small text-muted" id="schedulePrintPageInfo"></div>
            <div class="d-flex gap-2">
                <button type="button" id="schedulePrintPrev" class="btn btn-secondary btn-sm">
                    <i class="fad fa-chevron-left"></i> Prev
                </button>
                <button type="button" id="schedulePrintNext" class="btn btn-secondary btn-sm">
                    Next <i class="fad fa-chevron-right"></i>
                </button>
            </div>
        </div>

        <div id="tableWrapper" class="d-none"></div>
    </div>
</div>

<script>
document.getElementById('navBox').innerHTML =
    '<li class="breadcrumb-item"><a href="index.php?t=includes/master&p=client.php&view=list">Client List</a><i class="fad fa-angle-double-right mx-2 mt-1" aria-hidden="true"></i></li><li class="breadcrumb-item"><a href="index.php?t=includes/master&p=client.php&clientnumber=<?= $clientnumber ?>&view=edit"><?= addslashes($clientName) ?></a><i class="fad fa-angle-double-right mx-2 mt-1" aria-hidden="true"></i></li><li class="breadcrumb-item"><a href="index.php?t=includes/master&p=client.php&clientnumber=<?= $clientnumber ?>&view=asset">Schedule</a><i class="fad fa-angle-double-right mx-2 mt-1" aria-hidden="true"></i></li><li class="breadcrumb-item active">Schedule: Print</li>';

document.addEventListener('DOMContentLoaded', function() {
    const fields = <?= json_encode(array_merge(
        [['key' => 'chain', 'label' => 'Chain']],
        array_map(static function ($columnKey) use ($assetPrefs): array {
            $field = [
                'key' => $columnKey,
                'label' => (string)($assetPrefs[$columnKey]['label'] ?? $columnKey),
            ];
            if ($columnKey === 'duedate') {
                $field['type'] = 'date';
            }
            return $field;
        }, $visibleAssetColumns),
        [
            ['key' => 'adefect', 'label' => 'A Defect'],
            ['key' => 'bdefect', 'label' => 'B Defect'],
            ['key' => 'obs', 'label' => 'Observations'],
        ]
    ), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) ?>;
    const columnDefaults = Object.fromEntries(fields.map((field) => [field.key, true]));
    let columnVisibility = { ...columnDefaults };

    const textOperators = [
        ['contains', 'Contains'],
        ['not_contains', 'Does not contain'],
        ['equals', 'Equals'],
        ['not_equals', 'Does not equal'],
        ['starts', 'Starts with'],
        ['ends', 'Ends with'],
        ['empty', 'Is empty'],
        ['not_empty', 'Is not empty']
    ];
    const dateOperators = [
        ['date_on', 'On'],
        ['date_before', 'Before'],
        ['date_after', 'After'],
        ['empty', 'Is empty'],
        ['not_empty', 'Is not empty']
    ];

    const table = document.getElementById('assetTable');
    const tbody = document.getElementById('schedulePrintBody');
    const allRows = Array.from(tbody.querySelectorAll('tr'));
    const filtersPanel = document.getElementById('schedulePrintFilters');
    const conditionsEl = document.getElementById('schedulePrintConditions');
    const searchEl = document.getElementById('schedulePrintSearch');
    const rowsEl = document.getElementById('schedulePrintRows');
    const logicEl = document.getElementById('schedulePrintLogic');
    const summaryEl = document.getElementById('schedulePrintSummary');
    const noRowsEl = document.getElementById('schedulePrintNoRows');
    const pagerEl = document.getElementById('schedulePrintPager');
    const pageInfoEl = document.getElementById('schedulePrintPageInfo');
    const prevEl = document.getElementById('schedulePrintPrev');
    const nextEl = document.getElementById('schedulePrintNext');
    const columnsButton = document.getElementById('schedulePrintColumns');
    const columnsPanel = document.getElementById('schedulePrintColumnPanel');
    const columnsList = document.getElementById('schedulePrintColumnList');
    const columnsAllButton = document.getElementById('schedulePrintColumnsAll');
    const columnsDefaultButton = document.getElementById('schedulePrintColumnsDefault');

    let conditions = [];
    let page = 1;
    let sort = { field: 'duedate', dir: 'asc' };
    let filteredRows = [];

    function rowValue(row, key) {
        return row.getAttribute('data-' + key) || row.getAttribute('data-' + String(key).toLowerCase()) || '';
    }

    function parseDateValue(value) {
        const match = String(value || '').match(/^(\d{2})-(\d{2})-(\d{4})$/);
        if (!match) return null;
        return new Date(Number(match[3]), Number(match[2]) - 1, Number(match[1])).getTime();
    }

    function escapeHtml(value) {
        return String(value ?? '')
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;')
            .replace(/'/g, '&#039;');
    }

    function visibleColumnKeys() {
        return fields
            .map((field) => field.key)
            .filter((key) => columnVisibility[key] !== false);
    }

    function applyColumnVisibility() {
        const visibleKeys = new Set(visibleColumnKeys());
        table.querySelectorAll('[data-column-key]').forEach((cell) => {
            const key = cell.getAttribute('data-column-key') || '';
            cell.style.display = visibleKeys.has(key) ? '' : 'none';
        });

        if (!visibleKeys.has(sort.field)) {
            sort = { field: visibleKeys.has('duedate') ? 'duedate' : (visibleColumnKeys()[0] || 'chain'), dir: 'asc' };
        }

        updateSortHeaders();
    }

    function renderColumnToggles() {
        columnsList.innerHTML = fields.map((field) => `
            <label class="schedule-column-toggle">
                <input type="checkbox" class="form-check-input m-0" data-column-toggle="${escapeHtml(field.key)}" ${columnVisibility[field.key] !== false ? 'checked' : ''}>
                <span>${escapeHtml(field.label)}</span>
            </label>
        `).join('');
    }

    function conditionMatches(row, condition) {
        const field = fields.find((item) => item.key === condition.field) || fields[0];
        const actual = rowValue(row, field.key);
        const expected = String(condition.value || '');
        const actualLower = actual.toLowerCase();
        const expectedLower = expected.toLowerCase();

        switch (condition.operator) {
            case 'not_contains': return !actualLower.includes(expectedLower);
            case 'equals': return actualLower === expectedLower;
            case 'not_equals': return actualLower !== expectedLower;
            case 'starts': return actualLower.startsWith(expectedLower);
            case 'ends': return actualLower.endsWith(expectedLower);
            case 'empty': return actual.trim() === '' || (field.type === 'date' && actual === 'No date stored');
            case 'not_empty': return actual.trim() !== '' && !(field.type === 'date' && actual === 'No date stored');
            case 'date_on': return parseDateValue(actual) === parseDateValue(expected);
            case 'date_before': {
                const a = parseDateValue(actual);
                const b = parseDateValue(expected);
                return a !== null && b !== null && a < b;
            }
            case 'date_after': {
                const a = parseDateValue(actual);
                const b = parseDateValue(expected);
                return a !== null && b !== null && a > b;
            }
            case 'contains':
            default:
                return actualLower.includes(expectedLower);
        }
    }

    function rowSearchText(row) {
        return fields
            .filter((field) => columnVisibility[field.key] !== false)
            .map((field) => rowValue(row, field.key))
            .join(' ')
            .toLowerCase();
    }

    function sortRows(rows) {
        const dir = sort.dir === 'desc' ? -1 : 1;
        return rows.slice().sort((a, b) => {
            let av = sort.field === 'duedate' ? Number(a.dataset.duesort || 0) : rowValue(a, sort.field).toLowerCase();
            let bv = sort.field === 'duedate' ? Number(b.dataset.duesort || 0) : rowValue(b, sort.field).toLowerCase();
            if (typeof av === 'number' && typeof bv === 'number') {
                return (av - bv) * dir;
            }
            return String(av).localeCompare(String(bv), undefined, { numeric: true, sensitivity: 'base' }) * dir;
        });
    }

    function applyFilters() {
        const search = searchEl.value.trim().toLowerCase();
        const activeConditions = conditions.filter((condition) => {
            return ['empty', 'not_empty'].includes(condition.operator) || String(condition.value || '').trim() !== '';
        });

        filteredRows = allRows.filter((row) => {
            const searchMatch = search === '' || rowSearchText(row).includes(search);
            if (!searchMatch) return false;
            if (activeConditions.length < 1) return true;

            const matches = activeConditions.map((condition) => conditionMatches(row, condition));
            return logicEl.value === 'any' ? matches.some(Boolean) : matches.every(Boolean);
        });

        filteredRows = sortRows(filteredRows);
        renderTable();
    }

    function renderTable() {
        const perPage = Number(rowsEl.value || 25);
        const totalPages = perPage === -1 ? 1 : Math.max(1, Math.ceil(filteredRows.length / perPage));
        if (page > totalPages) page = totalPages;
        if (page < 1) page = 1;

        const start = perPage === -1 ? 0 : (page - 1) * perPage;
        const pageRows = perPage === -1 ? filteredRows : filteredRows.slice(start, start + perPage);
        const visibleSet = new Set(pageRows);
        const fragment = document.createDocumentFragment();

        pageRows.forEach((row) => {
            row.style.display = '';
            fragment.appendChild(row);
        });

        allRows.forEach((row) => {
            if (visibleSet.has(row)) return;
            row.style.display = 'none';
            fragment.appendChild(row);
        });

        tbody.appendChild(fragment);

        const showingFrom = filteredRows.length ? start + 1 : 0;
        const showingTo = perPage === -1 ? filteredRows.length : Math.min(start + perPage, filteredRows.length);
        summaryEl.textContent = `Showing ${showingFrom} to ${showingTo} of ${filteredRows.length} matching items (${allRows.length} total)`;
        pageInfoEl.textContent = perPage === -1 ? 'All rows' : `Page ${page} of ${totalPages}`;
        prevEl.disabled = page <= 1 || perPage === -1;
        nextEl.disabled = page >= totalPages || perPage === -1;
        noRowsEl.style.display = filteredRows.length ? 'none' : '';
        pagerEl.style.display = filteredRows.length ? '' : 'none';
        updateSortHeaders();
    }

    function updateSortHeaders() {
        table.querySelectorAll('th[data-sort-field]').forEach((th) => {
            th.classList.remove('schedule-sort-asc', 'schedule-sort-desc');
            if (th.dataset.sortField === sort.field) {
                th.classList.add(sort.dir === 'asc' ? 'schedule-sort-asc' : 'schedule-sort-desc');
            }
        });
    }

    function distinctFieldValues(fieldKey) {
        const values = new Map();
        allRows.forEach((row) => {
            const value = rowValue(row, fieldKey).trim();
            if (value !== '') values.set(value.toLowerCase(), value);
        });

        return Array.from(values.values()).sort((a, b) => {
            return String(a).localeCompare(String(b), undefined, { numeric: true, sensitivity: 'base' });
        });
    }

    function initMdbFilterSelects() {
        if (!window.mdb || !mdb.Select) return;

        conditionsEl.querySelectorAll('[data-mdb-select-init]').forEach((select) => {
            try {
                const existing = mdb.Select.getInstance ? mdb.Select.getInstance(select) : null;
                if (!existing) new mdb.Select(select);
            } catch (e) {}
        });
    }

    function renderConditions() {
        conditionsEl.innerHTML = conditions.map((condition, index) => {
            const field = fields.find((item) => item.key === condition.field) || fields[0];
            const operators = field.type === 'date' ? dateOperators : textOperators;
            const disabled = ['empty', 'not_empty'].includes(condition.operator) ? 'disabled' : '';
            const values = distinctFieldValues(field.key);
            return `
                <div class="schedule-filter-row" data-condition-index="${index}">
                    <div>
                        <label class="form-label mb-1">Field</label>
                        <select class="form-select form-select-sm" data-filter-control="field" data-mdb-select-init data-mdb-filter="true">
                            ${fields.map((item) => `<option value="${item.key}" ${item.key === condition.field ? 'selected' : ''}>${escapeHtml(item.label)}</option>`).join('')}
                        </select>
                    </div>
                    <div>
                        <label class="form-label mb-1">Condition</label>
                        <select class="form-select form-select-sm" data-filter-control="operator">
                            ${operators.map(([value, label]) => `<option value="${value}" ${value === condition.operator ? 'selected' : ''}>${escapeHtml(label)}</option>`).join('')}
                        </select>
                    </div>
                    <div class="schedule-filter-value">
                        <label class="form-label mb-1">Value</label>
                        <select class="form-select form-select-sm" data-filter-control="value" data-mdb-select-init data-mdb-filter="true" ${disabled}>
                            <option value="" ${condition.value === '' ? 'selected' : ''}>Select value</option>
                            ${values.map((value) => `<option value="${escapeHtml(value)}" ${value === condition.value ? 'selected' : ''}>${escapeHtml(value)}</option>`).join('')}
                        </select>
                    </div>
                    <div>
                        <button type="button" class="btn btn-light btn-sm" data-filter-remove title="Remove condition">
                            <i class="fad fa-times"></i>
                        </button>
                    </div>
                </div>
            `;
        }).join('');
        initMdbFilterSelects();
    }

    function defaultFieldKey(preferred) {
        return fields.some((field) => field.key === preferred) ? preferred : (fields[0]?.key || 'chain');
    }

    function addCondition() {
        conditions.push({ field: defaultFieldKey('description'), operator: 'contains', value: '' });
        renderConditions();
    }

    function resetFilters() {
        conditions = [];
        searchEl.value = '';
        logicEl.value = 'all';
        rowsEl.value = '25';
        page = 1;
        sort = { field: defaultFieldKey('duedate'), dir: 'asc' };
        renderConditions();
        applyFilters();
        document.getElementById('getDetailsbox').textContent = '';
    }

    function visibleExportRows() {
        return filteredRows;
    }

    function tableHtmlForRows(rows) {
        const visibleKeys = new Set(visibleColumnKeys());
        const headers = Array.from(table.querySelectorAll('thead th')).filter((header) => {
            return visibleKeys.has(header.getAttribute('data-column-key') || '');
        });
        const indexes = headers.map((header) => Array.from(header.parentNode.children).indexOf(header));
        const body = rows.map((row) => {
            const cells = indexes.map((index) => row.children[index] ? `<td>${row.children[index].innerHTML}</td>` : '<td></td>').join('');
            return `<tr>${cells}</tr>`;
        }).join('');
        return `<table class="table table-striped table-sm table-bordered table-hover responsive" style="width:100%"><thead><tr>${headers.map((header) => `<th>${header.textContent.trim()}</th>`).join('')}</tr></thead><tbody>${body}</tbody></table>`;
    }

    function printCurrentRows(orientation = 'landscape') {
        const cleanOrientation = orientation === 'portrait' ? 'portrait' : 'landscape';
        const html = tableHtmlForRows(visibleExportRows());
        const target = document.getElementById('tableWrapper');
        target.innerHTML = html;

        if (typeof printContainer === 'function') {
            printContainer('tableWrapper', {
                orientation: cleanOrientation,
                header: '<p>Custom Print</p>',
                footer: '<p>Generated by EnRep Reporting System: <?= date('d-m-Y') ?></p>',
                logoUrl: 'images/company/logo.jpg',
                styles: [ROOT_URL + '/src/css/print.css'],
            });
            return;
        }

        const printWindow = window.open('', '_blank');
        printWindow.document.write(`<style>@page{size:${cleanOrientation};}</style>${html}`);
        printWindow.document.close();
        printWindow.print();
    }

    function openPrintOrientationModal() {
        const dest = `
            <div class="text-center">
                <p class="mb-3">Choose the page orientation for this print output.</p>
                <div class="d-grid gap-2">
                    <button type="button" class="btn btn-info text-light" onclick="schedulePrintConfirmOrientation('landscape')" autofocus>
                        <i class="fad fa-rectangle-wide"></i> Landscape
                    </button>
                    <button type="button" class="btn btn-light" onclick="schedulePrintConfirmOrientation('portrait')">
                        <i class="fad fa-rectangle-vertical"></i> Portrait
                    </button>
                </div>
            </div>
        `;

        if (typeof loadModalContent === 'function') {
            loadModalContent(dest, 'schedulePrintOrientationBox', 'modal-sm', 1060, 'true', 'Print');
            return;
        }

        printCurrentRows('landscape');
    }

    window.schedulePrintConfirmOrientation = function(orientation) {
        if (typeof killModal === 'function') {
            killModal('schedulePrintOrientationBox');
        }

        window.setTimeout(function() {
            printCurrentRows(orientation);
        }, 150);
    };

    function csvEscape(value) {
        return `"${String(value || '').replace(/"/g, '""')}"`;
    }

    function exportCsv() {
        const visibleKeys = new Set(visibleColumnKeys());
        const headers = Array.from(table.querySelectorAll('thead th')).filter((header) => {
            return visibleKeys.has(header.getAttribute('data-column-key') || '');
        });
        const indexes = headers.map((header) => Array.from(header.parentNode.children).indexOf(header));
        const rows = visibleExportRows();
        const lines = [
            headers.map((header) => csvEscape(header.textContent.trim())).join(',')
        ];

        rows.forEach((row) => {
            const cells = indexes.map((index) => csvEscape(row.children[index] ? row.children[index].textContent.trim() : ''));
            lines.push(cells.join(','));
        });

        const blob = new Blob([lines.join('\n')], { type: 'text/csv;charset=utf-8;' });
        const link = document.createElement('a');
        link.href = URL.createObjectURL(blob);
        link.download = '<?= preg_replace('/[^A-Za-z0-9_-]+/', '_', $clientName ?: 'schedule') ?>_schedule.csv';
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        URL.revokeObjectURL(link.href);
    }

    document.getElementById('schedulePrintAddCondition').addEventListener('click', addCondition);
    document.getElementById('schedulePrintResetFilters').addEventListener('click', resetFilters);
    document.getElementById('schedulePrintPrint').addEventListener('click', openPrintOrientationModal);
    document.getElementById('schedulePrintCsv').addEventListener('click', exportCsv);
    columnsButton.addEventListener('click', () => {
        columnsPanel.classList.toggle('is-open');
    });
    columnsAllButton.addEventListener('click', () => {
        columnVisibility = { ...columnDefaults };
        renderColumnToggles();
        applyColumnVisibility();
        applyFilters();
    });
    columnsDefaultButton.addEventListener('click', () => {
        columnVisibility = { ...columnDefaults };
        renderColumnToggles();
        applyColumnVisibility();
        applyFilters();
    });
    columnsList.addEventListener('change', (event) => {
        const toggle = event.target.closest('[data-column-toggle]');
        if (!toggle) return;

        const key = toggle.getAttribute('data-column-toggle');
        columnVisibility[key] = !!toggle.checked;
        if (visibleColumnKeys().length < 1) {
            columnVisibility[key] = true;
            toggle.checked = true;
        }

        page = 1;
        applyColumnVisibility();
        applyFilters();
    });
    searchEl.addEventListener('input', () => { page = 1; applyFilters(); });
    rowsEl.addEventListener('change', () => { page = 1; applyFilters(); });
    logicEl.addEventListener('change', () => { page = 1; applyFilters(); });
    prevEl.addEventListener('click', () => { page -= 1; renderTable(); });
    nextEl.addEventListener('click', () => { page += 1; renderTable(); });
    table.querySelectorAll('th[data-sort-field]').forEach((th) => {
        th.addEventListener('click', () => {
            const field = th.dataset.sortField;
            if (sort.field === field) {
                sort.dir = sort.dir === 'asc' ? 'desc' : 'asc';
            } else {
                sort = { field, dir: 'asc' };
            }
            applyFilters();
        });
    });

    conditionsEl.addEventListener('input', (event) => {
        const row = event.target.closest('.schedule-filter-row');
        if (!row) return;
        const index = Number(row.dataset.conditionIndex);
        if (!conditions[index]) return;
        const control = event.target.dataset.filterControl;
        if (control) {
            conditions[index][control] = event.target.value;
            page = 1;
            applyFilters();
        }
    });

    conditionsEl.addEventListener('change', (event) => {
        const row = event.target.closest('.schedule-filter-row');
        if (!row) return;
        const index = Number(row.dataset.conditionIndex);
        if (!conditions[index]) return;
        const control = event.target.dataset.filterControl;
        if (control) {
            conditions[index][control] = event.target.value;
            if (control === 'field') {
                const field = fields.find((item) => item.key === event.target.value) || fields[0];
                conditions[index].operator = field.type === 'date' ? 'date_on' : 'contains';
                conditions[index].value = '';
                renderConditions();
            } else if (control === 'operator') {
                if (['empty', 'not_empty'].includes(conditions[index].operator)) {
                    conditions[index].value = '';
                }
                renderConditions();
            }
            page = 1;
            applyFilters();
        }
    });

    conditionsEl.addEventListener('click', (event) => {
        const remove = event.target.closest('[data-filter-remove]');
        if (!remove) return;
        const row = remove.closest('.schedule-filter-row');
        const index = Number(row.dataset.conditionIndex);
        conditions.splice(index, 1);
        page = 1;
        renderConditions();
        applyFilters();
    });

    document.getElementById('loadingObject').style.display = 'none';
    filtersPanel.style.display = '';
    table.style.display = 'table';
    renderColumnToggles();
    applyColumnVisibility();
    addCondition();
    applyFilters();
});
</script>
