<?php
/**
 * Hours block — renders the owner's opening hours FROM their profile.
 *
 * Replaces a text element full of hand-authored HTML. That block was a picture of a
 * mechanism: to change their hours an owner had to edit a <div> table inside a rich-text
 * field, so nobody knew how, and nothing else could read what they wrote. Meanwhile the
 * real structured hours (BookingService::business_hours, already driving the slot engine
 * and Google's schema) were unreachable behind /admin. Two copies of one fact, and the
 * one the owner could edit was the one nothing read.
 *
 * Same arrangement as team.php: no editable content of its own. The rows ARE the profile,
 * so /dashboard/settings is the single source and the site cannot drift from the slots.
 * The settings here are presentation only.
 *
 * Owner-scoped: BusinessProfile reads per owner on the shared app, so one salon's hours
 * can never surface on another's site.
 *
 * @package Instinctor
 */

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

/**
 * The owner's week, from their profile. The one place hours live is the dashboard grid
 * (/dashboard/settings); a demo has no profile yet and simply shows no hours until the
 * owner sets them after claiming. (An hours_seed fallback existed for the Google Places
 * integration, which never ran in production and was removed 2026-07-17.)
 *
 * @return array<string,array{open:bool,start:string,end:string}>
 */
function impulse_hours_rows(array $settings = []): array
{
    if (class_exists('\\Instinctor\\Services\\BusinessProfile')) {
        $raw = trim((string) \Instinctor\Services\BusinessProfile::get('hours'));
        if ($raw !== '') {
            $rows = \Instinctor\Services\BusinessProfile::hoursFrom($raw);
            foreach ($rows as $r) {
                if (!empty($r['open'])) return $rows;   // at least one real open day
            }
        }
    }

    return [];
}

/**
 * The owner's address and phone, from their profile (the address falls back to what a
 * demo visitor typed over the prompt).
 *
 * The block owns the whole "where we are" card, not just the hours: the address had the
 * identical disease (structured data behind /admin, a hand-typed copy on the page, nothing
 * keeping the two in step). One block, one source.
 *
 * A blank street address is a real answer, not a gap: a mobile plumber has no premises to
 * show. Only the demo (seeded, no profile yet) nudges for one.
 *
 * @return array{0:string,1:string} [address, phone]
 */
/**
 * Display form for a stored phone number.
 *
 * The publish card strips the number to bare digits before storing it, so a
 * published site printed "5125550130" under its Visit-us heading and read as a
 * stray number rather than a phone (Sergei 2026-07-25). Formats NANP numbers
 * (10 digits, or 11 starting with 1); anything else - international, extensions,
 * whatever the owner typed - is returned untouched.
 */
function impulse_hours_phone_pretty(string $phone): string
{
    $d = preg_replace('/\D+/', '', $phone);
    if (strlen($d) === 11 && $d[0] === '1') $d = substr($d, 1);
    if (strlen($d) !== 10) return $phone;
    return '(' . substr($d, 0, 3) . ') ' . substr($d, 3, 3) . '-' . substr($d, 6);
}

function impulse_hours_contact(array $settings = []): array
{
    $address = ''; $phone = '';

    if (class_exists('\\Instinctor\\Services\\BusinessProfile')) {
        $street = trim((string) \Instinctor\Services\BusinessProfile::get('address'));
        // City alone is not an address. Without a street we have nothing worth printing:
        // "Austin, TX" under a heading that says where we are reads as a broken field.
        if ($street !== '') {
            $tail = trim(implode(' ', array_filter([
                trim((string) \Instinctor\Services\BusinessProfile::get('city')),
                trim((string) \Instinctor\Services\BusinessProfile::get('postal_code')),
            ])));
            $address = $tail !== '' ? $street . ', ' . $tail : $street;
        }
        $phone = impulse_hours_phone_pretty(trim((string) \Instinctor\Services\BusinessProfile::get('phone')));
    }

    // address_seed: what a demo visitor typed over the address prompt (demoPersist
    // routes that one edit here). No phone fallback: the phone appears when the owner
    // fills their profile.
    if ($address === '') $address = trim((string) ($settings['address_seed'] ?? ''));

    return [$address, $phone];
}

/**
 * @param array|object $el       element row
 * @param array        $settings element settings
 * @param string       $context  'editor' | 'frontend'
 */
function impulse_element_render_hours($el, array $settings, string $context): string
{
    $rows = impulse_hours_rows($settings);
    [$address, $phone] = impulse_hours_contact($settings);
    // Off is off however the editor spells it: the panel's checkbox collects as a JSON
    // boolean (false), the tests write '0', and both must mean hidden. Absent means on.
    // RECORDING DRESSING (Sergei 2026-07-26). A take must never show "Add your
    // opening hours" on camera, but inventing hours for a real business is worse than
    // a prompt, so this is gated on the impap cookie exactly like the dashboard's
    // sample bookings and chats: it exists only while we are filming, and a real
    // visitor never sees it. Fills ONLY what is genuinely missing, so anything the
    // owner really has still wins (the phone below stays theirs).
    $impRec = !empty($_COOKIE['impap']);

    $offV = static fn($v): bool => $v === '0' || $v === 0 || $v === false || $v === '';
    $showAddr  = !isset($settings['show_address']) || !$offV($settings['show_address']);
    $showPhone = !isset($settings['show_phone'])   || !$offV($settings['show_phone']);
    if (!$showAddr)  $address = '';
    if (!$showPhone) $phone   = '';

    if ($impRec) {
        // Hours only. The address used to be dressed with the Austin sample too, which
        // hid the editable prompt the address BEAT types over, and being injected at
        // render it could never persist: the take showed 1200 Congress Ave before
        // typing, the typed street replaced nothing stored, and the published page
        // dressed itself right back to Austin (Sergei 2026-08-01). The beat now types
        // the address on camera, so the prompt is on screen for under a second.
        if (!array_filter($rows, static fn($r) => !empty($r['open']))) {
            $rows = [
                'mon' => ['open' => false, 'start' => '09:00', 'end' => '18:00'],
                'tue' => ['open' => true,  'start' => '09:00', 'end' => '19:00'],
                'wed' => ['open' => true,  'start' => '09:00', 'end' => '19:00'],
                'thu' => ['open' => true,  'start' => '09:00', 'end' => '19:00'],
                'fri' => ['open' => true,  'start' => '09:00', 'end' => '19:00'],
                'sat' => ['open' => true,  'start' => '09:00', 'end' => '17:00'],
                'sun' => ['open' => false, 'start' => '09:00', 'end' => '18:00'],
            ];
        }
    }

    $title    = (string) ($settings['title'] ?? 'Hours');
    $titleCol = (string) ($settings['title_color'] ?? '#2a231e');
    $dayCol   = (string) ($settings['day_color'] ?? '#5a534c');
    $lineCol  = (string) ($settings['line_color'] ?? '#eee6db');
    $size     = (int) ($settings['font_size'] ?? 15);

    // Nothing set yet. On a live site say so plainly rather than rendering an empty box;
    // in the editor, tell the owner where to fix it, because this block has no content of
    // its own to click. Hours alone no longer decide that: an owner with an address and a
    // phone but no hours yet has a card worth rendering.
    $open = array_filter($rows, static fn($r) => !empty($r['open']));
    $isSamplePreview = false;
    // DEMO: a fresh demo has no profile, so this section rendered as a bare
    // address prompt over an empty card - "orphaned" (Sergei 2026-07-25).
    // Give demos the sample week too (no caption: a demo should read real);
    // the address stays the editable prompt so typing it still works.
    if (!$open && !empty($GLOBALS['impulse_is_demo']) && $context !== 'editor') {
        $rows = [
            'mon' => ['open' => false, 'start' => '09:00', 'end' => '18:00'],
            'tue' => ['open' => true,  'start' => '09:00', 'end' => '19:00'],
            'wed' => ['open' => true,  'start' => '09:00', 'end' => '19:00'],
            'thu' => ['open' => true,  'start' => '09:00', 'end' => '19:00'],
            'fri' => ['open' => true,  'start' => '09:00', 'end' => '19:00'],
            'sat' => ['open' => true,  'start' => '09:00', 'end' => '17:00'],
            'sun' => ['open' => false, 'start' => '09:00', 'end' => '18:00'],
        ];
        $open = array_filter($rows, static fn($r) => !empty($r['open']));
    }
    if (!$open && $address === '' && $phone === '' && empty($settings['address_placeholder'])) {
        if ($context !== 'editor') return '';
        // EDITOR PREVIEW: render the real card on sample data so the style
        // panel (colors, size) has something to paint. The old dashed hint
        // box ignored every setting, which made the controls look dead
        // (Sergei 2026-07-24: "does not update colors at all"). Sample data
        // is render-only — nothing is stored, and live/demo contexts are
        // untouched by this branch.
        $rows = [
            'mon' => ['open' => false, 'start' => '09:00', 'end' => '18:00'],
            'tue' => ['open' => true,  'start' => '09:00', 'end' => '19:00'],
            'wed' => ['open' => true,  'start' => '09:00', 'end' => '19:00'],
            'thu' => ['open' => true,  'start' => '09:00', 'end' => '19:00'],
            'fri' => ['open' => true,  'start' => '09:00', 'end' => '19:00'],
            'sat' => ['open' => true,  'start' => '09:00', 'end' => '17:00'],
            'sun' => ['open' => false, 'start' => '09:00', 'end' => '18:00'],
        ];
        $address = '1200 Congress Ave, Austin, TX 78701';
        $phone   = '(512) 555 0142';
        $open    = array_filter($rows, static fn($r) => !empty($r['open']));
        $isSamplePreview = true;
    }

    // Collapse runs of identical days: "Tue - Fri  9am - 7pm" reads like a human wrote it,
    // where seven near-identical lines read like a database dump.
    $fmt = static function (string $t): string {
        [$h, $m] = array_pad(explode(':', $t), 2, '00');
        $h = (int) $h;
        $ap = $h >= 12 ? 'pm' : 'am';
        $h12 = ($h % 12) ?: 12;
        return $m === '00' ? $h12 . $ap : $h12 . ':' . $m . $ap;
    };
    $days   = \Instinctor\Services\BusinessProfile::HOURS_DAYS;
    $abbr   = ['mon' => 'Mon', 'tue' => 'Tue', 'wed' => 'Wed', 'thu' => 'Thu', 'fri' => 'Fri', 'sat' => 'Sat', 'sun' => 'Sun'];
    $lines  = [];
    $runKey = null; $runFrom = null; $runTo = null;
    $flush = static function () use (&$lines, &$runKey, &$runFrom, &$runTo, $abbr) {
        if ($runKey === null) return;
        $label = $runFrom === $runTo ? $abbr[$runFrom] : $abbr[$runFrom] . ' &ndash; ' . $abbr[$runTo];
        $lines[] = [$label, $runKey];
        $runKey = null;
    };
    foreach ($days as $d => $_label) {
        $r   = $rows[$d] ?? ['open' => false];
        $key = !empty($r['open']) ? ($fmt($r['start']) . ' &ndash; ' . $fmt($r['end'])) : 'Closed';
        if ($key === $runKey) { $runTo = $d; continue; }
        $flush();
        $runKey = $key; $runFrom = $d; $runTo = $d;
    }
    $flush();

    // .imp-hours-rows shields the title + day lines from the demo overlay's inline text
    // editor (WIDGET_SEL in demo-overlay.php). Those lines are RENDERED from the seed, not
    // stored: the overlay persists edits by old->new string replacement across settings,
    // so an edit here would show once and silently vanish on reload — the exact
    // editor-hijacks-widgets trap. The address stays OUTSIDE the shield: its string
    // lives in address_seed, so replacement finds and persists it.
    // Beside the map, not adrift. The block used to fill its whole grid cell with
    // text-align:left, so on a wide monitor it hugged the far-left edge while the map
    // stayed right and the gap grew with the window. Capping the width and pushing it
    // to the cell's right edge keeps the two together at any size. In a column flex
    // cell an auto cross-axis margin beats align-items, which is what makes this work
    // without touching the section (Sergei 2026-07-26).
    $h = '<div style="text-align:left;max-width:340px;margin-left:auto">'
       . '<div class="imp-hours-rows">';

    // No hours yet: show the SAME prompt the demo shows rather than nothing at all.
    // These fields must never render empty (Sergei 2026-07-26): a bare column with only
    // a phone number reads as a broken site, and the owner is the first person to visit
    // their own page, so the prompt is also how they learn the field exists. Seven
    // "Closed" lines are still wrong here, which is why this is one line and not a grid.
    if (!$open && !empty($settings['address_placeholder'])) {
        if ($title !== '') {
            $h .= '<div style="font-weight:700;color:' . esc_attr($titleCol) . ';font-size:' . ($size + 1) . 'px;margin-bottom:8px">' . esc_html($title) . '</div>';
        }
        // Same markup as DemoService::buildHoursHtml: imp-addr-ph + data-demo-hours keeps
        // the demo overlay's inline editor working on the demo, and is harmless once live.
        $h .= '<div class="imp-addr-ph" data-ph="Add your opening hours" data-demo-hours style="color:'
            . esc_attr($dayCol) . ';font-size:' . $size . 'px;line-height:1.6">Add your opening hours</div>';
    }

    // No hours yet but an address to show: skip the title and the seven "Closed" lines
    // that would collapse into one misleading "Mon - Sun  Closed".
    if ($open) {
        if ($title !== '') {
            $h .= '<div style="font-weight:700;color:' . esc_attr($titleCol) . ';font-size:' . ($size + 1) . 'px;margin-bottom:8px">' . esc_html($title) . '</div>';
        }
        foreach ($lines as [$label, $val]) {
            $h .= '<div style="display:flex;justify-content:space-between;gap:12px;color:' . esc_attr($dayCol) . ';font-size:' . $size . 'px;padding:5px 0;border-bottom:1px solid ' . esc_attr($lineCol) . '">'
                . '<span>' . wp_kses($label, ['span' => []]) . '</span>'
                . '<span>' . wp_kses($val, ['span' => []]) . '</span>'
                . '</div>';
        }
    }

    $h .= '</div>';

    // Address and phone. No "Find us" heading: the section already carries one ("Where we
    // work" / "Visit us"), so a second would just repeat it.
    $line = 'color:' . esc_attr($dayCol) . ';font-size:' . $size . 'px;line-height:1.6';
    if ($address !== '' || $phone !== '' || !empty($settings['address_placeholder'])) {
        $mt = $open ? 'margin-top:20px;' : '';
        if ($address !== '') {
            // data-demo-address is load-bearing on a demo: the overlay makes this leaf
            // inline-editable AND re-keys the map from whatever is typed here. Drop the
            // attribute and editing a demo's address silently stops moving its map.
            // It must stay its own leaf, so no <br> inside; the phone gets its own line.
            $h .= '<div style="' . $mt . $line . '"><span data-demo-address>' . esc_html($address) . '</span></div>';
        } elseif (!empty($settings['address_placeholder'])) {
            // A demo with no address typed yet: prompt for the real one rather than a
            // bare city. The map still centers on the city until they type it.
            //
            // SHOWN ON PUBLISHED PAGES TOO (Sergei 2026-07-26, overriding the earlier
            // demo-only rule). I had suppressed it here because a visitor seeing "Add
            // your business address" reads as an unfinished site. His call, and the
            // better one: the alternative was rendering NOTHING, which reads as broken
            // rather than unfinished, and the owner is the first visitor to their own
            // page so this is what tells them the field exists.
            $h .= '<div style="' . $mt . $line . '"><span data-demo-address class="imp-addr-ph" data-ph="Add your business address">Add your business address</span></div>';
        }
        if ($phone !== '') {
            $h .= '<div style="' . $line . '">' . esc_html($phone) . '</div>';
        }
    }

    if ($isSamplePreview && !$impRec) {   // never print the preview caption on camera
        $h .= '<div style="margin-top:10px;font-size:11px;color:#999;font-style:normal">Preview data. Real hours, address and phone come from Settings on the dashboard.</div>';
    }

    $h .= '</div>';
    return $h;
}
