<?php
/**
 * Row-based Jobs home (2026-08-31 redesign, Mike's spec + approved
 * mockups): replaces the three-column Job Board as the DEFAULT Jobs
 * view with grouped rows in a fixed scan order, Active Jobs, then
 * Quotes Out, then Wrapping Up, so the page reads the same every day.
 * The board is kept intact behind a view toggle (option
 * paintops_jobs_view, 'rows' default / 'board'); nothing pipeline-side
 * was removed. Free-shipping file: everything here is free-plan
 * functionality (scheduling + quoting), no premium code.
 */

if (!defined('ABSPATH')) {
    exit;
}

/** The saved Jobs view, validated. Rows is the default. */
function paintops_jobs_view(): string {
    $view = (string) get_option('paintops_jobs_view', 'rows');
    return in_array($view, ['rows', 'board'], true) ? $view : 'rows';
}

/**
 * The small Board/Rows switch, one nonce'd POST form. Rendered by the
 * rows footer and after the board so either view can reach the other.
 */
function paintops_render_jobs_view_toggle(): string {
    if (!function_exists('paintops_user_can_manage') || !paintops_user_can_manage()) {
        return '';
    }
    $to = paintops_jobs_view() === 'rows' ? 'board' : 'rows';
    $label = $to === 'board' ? 'Switch to Board view' : 'Switch to Row view';
    $out  = '<form method="post" style="display:inline;margin:0;">';
    $out .= wp_nonce_field('paintops_jobs_view_action', 'paintops_jobs_view_nonce', true, false);
    $out .= '<input type="hidden" name="paintops_jobs_view_to" value="' . esc_attr($to) . '">';
    $out .= '<button type="submit" name="paintops_jobs_view_toggle" value="1" style="background:none;border:none;padding:0;margin:0;font:inherit;cursor:pointer;text-decoration:underline;color:var(--po-text-muted,#667581);">' . esc_html($label) . '</button>';
    $out .= '</form>';
    return $out;
}

/** Processes the view-toggle POST; call once before rendering either view. */
function paintops_handle_jobs_view_toggle(): void {
    if (!isset($_POST['paintops_jobs_view_toggle'])) {
        return;
    }
    if (!function_exists('paintops_user_can_manage') || !paintops_user_can_manage()) {
        return;
    }
    if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['paintops_jobs_view_nonce'] ?? '')), 'paintops_jobs_view_action')) {
        return;
    }
    $to = sanitize_key(wp_unslash($_POST['paintops_jobs_view_to'] ?? ''));
    if (in_array($to, ['rows', 'board'], true)) {
        update_option('paintops_jobs_view', $to);
    }
}

/**
 * Crew initials chips for a reqWorkers list, capped at three plus a
 * "+N" overflow chip. User lookups are memoized per request.
 */
function paintops_jobs_row_crew_html($raw_worker_ids): string {
    static $names = null;
    if ($names === null) {
        $names = [];
        foreach (get_users(['fields' => ['ID', 'display_name']]) as $u) {
            $names[(int) $u->ID] = (string) $u->display_name;
        }
    }
    $ids = function_exists('paintops_parse_worker_ids') ? paintops_parse_worker_ids($raw_worker_ids) : [];
    if (empty($ids)) {
        return '<span class="po-jrow-dim">&mdash;</span>';
    }
    $out = '<span class="po-jrow-crew">';
    $shown = 0;
    foreach ($ids as $wid) {
        if ($shown >= 3) {
            break;
        }
        $n = trim((string) ($names[(int) $wid] ?? ''));
        if ($n === '') {
            continue;
        }
        $bits = preg_split('/\s+/', $n);
        $ini = strtoupper(substr((string) $bits[0], 0, 1) . (count($bits) > 1 ? substr((string) $bits[count($bits) - 1], 0, 1) : ''));
        $out .= '<span class="po-jrow-avatar" title="' . esc_attr($n) . '">' . esc_html($ini) . '</span>';
        $shown++;
    }
    if (count($ids) > $shown) {
        $out .= '<span class="po-jrow-avatar po-jrow-avatar-more">+' . (count($ids) - $shown) . '</span>';
    }
    $out .= '</span>';
    return $out;
}

/** One section heading: uppercase label plus a muted count/meta. */
function paintops_jobs_rows_heading(string $label, string $meta): string {
    return '<div class="po-jrows-head"><span class="po-jrows-label">' . esc_html($label) . '</span>'
        . ($meta !== '' ? '<span class="po-jrows-meta">' . esc_html($meta) . '</span>' : '') . '</div>';
}

/**
 * The rows view itself. Sections:
 *   ACTIVE JOBS  jobType 2, not completed: started first, then by date.
 *   QUOTES OUT   jobType 1, open (not completed, not Not Won).
 *   WRAPPING UP  completed jobs still owed money or missing receipts,
 *                straight from the dashboard-alert scans.
 * Statuses reuse the board's semantics (paintops_today_local naive
 * date math) so the two views never disagree about a job's state.
 */
function paintops_render_job_rows(): string {
    global $wpdb;
    if (!function_exists('paintops_user_can_manage') || !paintops_user_can_manage()) {
        return '';
    }

    $jobs_t = paintops_table('jobs');
    $est_t  = paintops_table('estimates');
    $comm_t = paintops_table('comm_log');
    $cur    = function_exists('paintops_currency_symbol') ? paintops_currency_symbol() : '$';
    $today  = function_exists('paintops_today_local') ? paintops_today_local() : current_time('Y-m-d');
    $tp     = array_map('intval', explode('-', $today));
    $today_ts = mktime(0, 0, 0, $tp[1], $tp[2], $tp[0]);

    $day_diff = function ($row) use ($today_ts) {
        if ((int) ($row->startYear ?? 0) <= 0) {
            return null;
        }
        $ts = mktime(0, 0, 0, (int) $row->startMonth, (int) $row->startDay, (int) $row->startYear);
        return (int) round(($ts - $today_ts) / DAY_IN_SECONDS);
    };
    $short_date = function ($row) use ($today_ts) {
        if ((int) ($row->startYear ?? 0) <= 0) {
            return '';
        }
        $ts = mktime(0, 0, 0, (int) $row->startMonth, (int) $row->startDay, (int) $row->startYear);
        return date('M j' . (((int) $row->startYear !== (int) date('Y', $today_ts)) ? ', Y' : ''), $ts);
    };
    $money = function ($n) use ($cur) {
        $n = (float) $n;
        return $n > 0 ? $cur . number_format($n, ($n == floor($n) ? 0 : 2)) : '';
    };
    $row_open = '<div class="po-jrow">';

    // ---- Active Jobs -------------------------------------------------
    $active = $wpdb->get_results($wpdb->prepare(
        "SELECT j.*, (SELECT e.estTotal FROM %i e WHERE e.jobID = j.id ORDER BY e.estID DESC LIMIT 1) AS rowTotal
         FROM %i j WHERE j.jobType = 2 AND j.completed = 0
         ORDER BY j.id DESC LIMIT 100", $est_t, $jobs_t)) ?: [];
    usort($active, function ($a, $b) use ($day_diff) {
        // Started/today first, then upcoming soonest-first, then unscheduled.
        $da = $day_diff($a);
        $db = $day_diff($b);
        $ka = ($da === null) ? 2 : (($da <= 0) ? 0 : 1);
        $kb = ($db === null) ? 2 : (($db <= 0) ? 0 : 1);
        if ($ka !== $kb) {
            return $ka <=> $kb;
        }
        if ($ka === 1) {
            return $da <=> $db;
        }
        if ($ka === 0) {
            return $db <=> $da;
        }
        return $b->id <=> $a->id;
    });

    $html = paintops_jobs_rows_heading('Active Jobs', (string) count($active));
    $html .= '<div class="po-jrows-card">';
    if (empty($active)) {
        $html .= '<div class="po-jrow po-jrow-empty">No active jobs. Win a quote below and it lands here.</div>';
    }
    foreach ($active as $j) {
        $dd = $day_diff($j);
        if ($dd === null) {
            $dot = 'po-dot-amber';
            $status = 'Not scheduled';
        } elseif ($dd === 0) {
            $dot = 'po-dot-green';
            $status = 'Starts today';
        } elseif ($dd < 0) {
            $dot = 'po-dot-green';
            $status = 'Started ' . $short_date($j);
        } else {
            $dot = 'po-dot-blue';
            $status = 'Scheduled';
        }
        $name = trim((string) $j->custFirst . ' ' . (string) $j->custLast);
        $sub  = trim($name . (trim((string) $j->custServiceCity) !== '' ? ' · ' . trim((string) $j->custServiceCity) : ''), ' ·');
        $when = ($dd !== null && $dd > 0)
            ? (($dd === 1 ? 'Tomorrow' : $short_date($j)))
            : (($dd === null) ? '<a href="' . esc_url(paintops_admin_url(['editEid' => (int) $j->id])) . '">Pick a date</a>' : 'On the board');
        if ($dd !== null && $dd <= 0) {
            $when = $short_date($j);
        }
        $html .= $row_open;
        $html .= '<div class="po-jrow-status"><span class="po-dot ' . esc_attr($dot) . '"></span>' . esc_html($status) . '</div>';
        $html .= '<div class="po-jrow-main"><a class="po-jrow-title" href="' . esc_url(paintops_admin_url(['viewEid' => (int) $j->id])) . '">#' . (int) $j->id . ' · ' . esc_html(trim((string) $j->jobTitle) !== '' ? (string) $j->jobTitle : 'Job') . '</a><span class="po-jrow-sub">' . esc_html($sub) . '</span></div>';
        $html .= '<div class="po-jrow-cell">' . paintops_jobs_row_crew_html($j->reqWorkers ?? '') . '</div>';
        $html .= '<div class="po-jrow-cell">' . wp_kses_post($when) . '</div>';
        $html .= '<div class="po-jrow-money">' . esc_html($money($j->rowTotal)) . '</div>';
        $html .= '</div>';
    }
    $html .= '</div>';

    // ---- Quotes Out --------------------------------------------------
    $quotes = $wpdb->get_results($wpdb->prepare(
        "SELECT j.*,
            (SELECT e.estTotal  FROM %i e WHERE e.jobID = j.id ORDER BY e.estID DESC LIMIT 1) AS rowTotal,
            (SELECT e.viewed_at FROM %i e WHERE e.jobID = j.id ORDER BY e.estID DESC LIMIT 1) AS rowViewed,
            (SELECT e.createdOn FROM %i e WHERE e.jobID = j.id ORDER BY e.estID DESC LIMIT 1) AS rowCreated,
            (SELECT MAX(c.sent_at) FROM %i c WHERE c.job_id = j.id AND c.comm_type IN ('quote_email','quote_text')) AS rowSent
         FROM %i j WHERE j.jobType = 1 AND j.completed = 0 AND j.not_won != 1
         ORDER BY j.id DESC LIMIT 50", $est_t, $est_t, $est_t, $comm_t, $jobs_t)) ?: [];

    $open_total = 0.0;
    foreach ($quotes as $q) {
        $open_total += (float) ($q->rowTotal ?? 0);
    }
    $html .= paintops_jobs_rows_heading('Quotes Out', count($quotes) . ($open_total > 0 ? ' · ' . $cur . number_format($open_total, 0) . ' open' : ''));
    $html .= '<div class="po-jrows-card">';
    if (empty($quotes)) {
        $html .= '<div class="po-jrow po-jrow-empty">No open quotes. Start one from New Request.</div>';
    }
    foreach ($quotes as $q) {
        $viewed = !empty($q->rowViewed);
        $sent_ts = !empty($q->rowSent) ? strtotime((string) $q->rowSent) : 0;
        $age_days = $sent_ts > 0 ? (int) floor((current_time('timestamp') - $sent_ts) / DAY_IN_SECONDS) : null;
        $stale = ($age_days !== null && $age_days >= (defined('PAINTOPS_QUOTE_STALE_DAYS') ? PAINTOPS_QUOTE_STALE_DAYS : 7));
        if ($stale) {
            $dot = 'po-dot-amber';
            $status = 'Quiet · ' . (int) $age_days . 'd';
        } elseif ($viewed) {
            $dot = 'po-dot-green';
            $status = 'Viewed';
        } elseif ($sent_ts > 0) {
            $dot = 'po-dot-blue';
            $status = 'Sent';
        } else {
            $dot = 'po-dot-gray';
            $status = 'Not sent yet';
        }
        $valid_line = '';
        if (!empty($q->rowCreated) && function_exists('paintops_quote_expiration_ts')) {
            $exp = paintops_quote_expiration_ts((object) ['createdOn' => (string) $q->rowCreated, 'accepted' => 0]);
            if ($exp !== null) {
                $valid_line = 'Valid until ' . gmdate('M j', $exp);
            }
        }
        $mid = $sent_ts > 0 ? 'Sent ' . date('M j', $sent_ts) : 'Created' . (!empty($q->rowCreated) ? ' ' . date('M j', strtotime((string) $q->rowCreated)) : '');
        $name = trim((string) $q->custFirst . ' ' . (string) $q->custLast);
        $sub  = trim($name . (trim((string) $q->custServiceCity) !== '' ? ' · ' . trim((string) $q->custServiceCity) : ''), ' ·');
        $action = $stale
            ? '<a href="' . esc_url(paintops_admin_url(['followupCompose' => (int) $q->id])) . '">Follow up</a>'
            : esc_html($valid_line);
        $html .= $row_open;
        $html .= '<div class="po-jrow-status"><span class="po-dot ' . esc_attr($dot) . '"></span>' . esc_html($status) . '</div>';
        $html .= '<div class="po-jrow-main"><a class="po-jrow-title" href="' . esc_url(paintops_admin_url(['viewEid' => (int) $q->id])) . '">' . esc_html(trim((string) $q->jobTitle) !== '' ? (string) $q->jobTitle : ('Request #' . (int) $q->id)) . '</a><span class="po-jrow-sub">' . esc_html($sub) . '</span></div>';
        $html .= '<div class="po-jrow-cell">' . esc_html($mid) . '</div>';
        $html .= '<div class="po-jrow-cell">' . wp_kses_post($action) . '</div>';
        $html .= '<div class="po-jrow-money">' . esc_html($money($q->rowTotal)) . '</div>';
        $html .= '</div>';
    }
    $html .= '</div>';

    // ---- Wrapping Up -------------------------------------------------
    $unpaid  = function_exists('paintops_get_unpaid_completed_jobs') ? paintops_get_unpaid_completed_jobs() : [];
    $noexp   = function_exists('paintops_get_jobs_missing_expenses') ? paintops_get_jobs_missing_expenses() : [];
    $wrap_n  = count($unpaid) + count($noexp);
    if ($wrap_n > 0) {
        $html .= paintops_jobs_rows_heading('Wrapping Up', (string) $wrap_n);
        $html .= '<div class="po-jrows-card">';
        foreach ($unpaid as $w) {
            $w_id  = (int) ($w->id ?? ($w->job_id ?? 0));
            $w_due = (float) ($w->po_balance ?? 0);
            $name  = trim((string) ($w->custFirst ?? '') . ' ' . (string) ($w->custLast ?? ''));
            $html .= $row_open;
            $html .= '<div class="po-jrow-status"><span class="po-dot po-dot-amber"></span>Done · unpaid</div>';
            $html .= '<div class="po-jrow-main"><a class="po-jrow-title" href="' . esc_url(paintops_admin_url(['viewEid' => $w_id])) . '">#' . $w_id . ' · ' . esc_html(trim((string) ($w->jobTitle ?? '')) !== '' ? (string) $w->jobTitle : 'Job') . '</a><span class="po-jrow-sub">' . esc_html($name) . '</span></div>';
            $html .= '<div class="po-jrow-cell"></div>';
            // Pro: straight to the Payments log on Financials. Free: the
            // job view, where the free Mark as Paid button lives; the old
            // target sent free users to the Financials upgrade teaser,
            // which read as a paywall on a money button (2026-08-31).
            $w_collect_url = (function_exists('paintops_is_pro') && paintops_is_pro())
                ? paintops_admin_url(['jobFinancials' => $w_id]) . '#paintops-payments'
                : paintops_admin_url(['viewEid' => $w_id]);
            $html .= '<div class="po-jrow-cell"><a href="' . esc_url($w_collect_url) . '">Collect</a></div>';
            $html .= '<div class="po-jrow-money po-jrow-due">' . esc_html($w_due > 0 ? $money($w_due) . ' due' : '') . '</div>';
            $html .= '</div>';
        }
        foreach ($noexp as $w) {
            $w_id = (int) ($w->id ?? 0);
            $name = trim((string) ($w->custFirst ?? '') . ' ' . (string) ($w->custLast ?? ''));
            $html .= $row_open;
            $html .= '<div class="po-jrow-status"><span class="po-dot po-dot-gray"></span>No receipts</div>';
            $html .= '<div class="po-jrow-main"><a class="po-jrow-title" href="' . esc_url(paintops_admin_url(['viewEid' => $w_id])) . '">#' . $w_id . ' · ' . esc_html(trim((string) ($w->jobTitle ?? '')) !== '' ? (string) $w->jobTitle : 'Job') . '</a><span class="po-jrow-sub">' . esc_html($name) . '</span></div>';
            $html .= '<div class="po-jrow-cell"></div>';
            $html .= '<div class="po-jrow-cell"><a href="' . esc_url(paintops_admin_url(['jobAddReceipt' => $w_id])) . '">Add receipts</a></div>';
            $html .= '<div class="po-jrow-money"></div>';
            $html .= '</div>';
        }
        $html .= '</div>';
    }

    // ---- Footer ------------------------------------------------------
    $html .= '<div class="po-jrows-foot">';
    $html .= '<a href="' . esc_url(paintops_admin_url(['jobSearch' => 1])) . '">Completed &amp; all jobs &rarr;</a>';
    $html .= '<span class="po-jrows-sep">|</span>';
    $html .= paintops_render_jobs_view_toggle();
    $html .= '</div>';

    return $html;
}
