<?php
if ( ! defined( 'ABSPATH' ) ) exit;

class EMPR_Admin {

    public static function init() {
        add_action( 'admin_init',            array( __CLASS__, 'handle_settings_save' ) );
        add_action( 'admin_menu',            array( __CLASS__, 'menus' ) );
        add_action( 'admin_enqueue_scripts', array( __CLASS__, 'assets' ) );
        add_action( 'admin_notices',         array( __CLASS__, 'setup_notice' ) );
        add_action( 'admin_footer',          array( __CLASS__, 'render_wizard' ) );

        add_action( 'wp_ajax_empr_wizard_save_step', array( __CLASS__, 'wizard_save_step' ) );
        add_action( 'wp_ajax_empr_wizard_complete',  array( __CLASS__, 'wizard_complete' ) );

        // Hide all non-EMR admin UI from roster users
        add_action( 'admin_menu',         array( __CLASS__, 'remove_non_emr_menus' ),    9999 );
        add_action( 'wp_dashboard_setup', array( __CLASS__, 'remove_dashboard_widgets' ), 9999 );
        add_action( 'admin_init',         array( __CLASS__, 'suppress_plugin_notices' ),  9999 );

        // Prefix plugin role names with "EMR" on the All Users screen only
        add_action( 'current_screen', array( __CLASS__, 'prefix_role_labels_for_users_list' ) );


        add_filter( 'admin_footer_text', array( __CLASS__, 'footer_text' ) );
        add_filter( 'update_footer',     array( __CLASS__, 'footer_version' ), 11 );
    }

    /**
     * Returns true when the current user holds any roster role but is NOT a WP
     * administrator. Used to gate UI-restriction logic.
     */
    private static function is_restricted_roster_user() {
        if ( EMPR_Roles::is_admin() ) return false;
        $user = wp_get_current_user();
        return ! empty( array_intersect(
            (array) $user->roles,
            array( 'emr_manager', 'emr_supervisor', 'emr_employee' )
        ) );
    }

    /**
     * Sanitize a decoded list of location entries. Drops entries without a
     * non-empty name, then rebuilds each as a clean array containing only the
     * known `name` and `color` keys so any extra keys from a crafted payload
     * are discarded. Colors fall back to the default brand blue when missing
     * or invalid. Used by both the settings save and the onboarding wizard so
     * the `locations` option is sanitized identically wherever it is written.
     *
     * @param mixed $decoded Raw decoded value (expected: array of entries).
     * @return array List of array( 'name' => string, 'color' => hex ).
     */
    private static function sanitize_location_entries( $decoded ) {
        if ( ! is_array( $decoded ) ) {
            return array();
        }
        $clean = array();
        foreach ( $decoded as $raw_entry ) {
            if ( ! is_array( $raw_entry ) ) {
                continue;
            }
            $name = sanitize_text_field( isset( $raw_entry['name'] ) ? $raw_entry['name'] : '' );
            if ( $name === '' ) {
                continue;
            }
            $color   = isset( $raw_entry['color'] ) ? sanitize_hex_color( $raw_entry['color'] ) : '';
            $clean[] = array(
                'name'  => $name,
                'color' => $color ? $color : '#2563EB',
            );
        }
        return $clean;
    }

    public static function handle_settings_save() {
        if ( ! isset( $_POST['empr_save'] ) ) return;
        if ( ! check_admin_referer( 'empr_settings_save' ) ) return;

        $is_wp_admin    = current_user_can( 'manage_options' );

        if ( ! $is_wp_admin ) return;

        // admin_include_* fields control WP administrator visibility in the roster.
        $fields = array( 'company_name', 'departments', 'locations', 'week_start', 'primary_color', 'allow_self_clock', 'hours_fulltime', 'hours_parttime', 'payroll_enabled', 'tax_rate', 'daily_budget', 'daily_budget_mon', 'daily_budget_tue', 'daily_budget_wed', 'daily_budget_thu', 'daily_budget_fri', 'daily_budget_sat', 'daily_budget_sun', 'penalty_rate_enabled', 'saturday_rate', 'sunday_rate', 'notifications_enabled' );
        $fields[] = 'admin_include_all';
        $bool_fields  = array( 'allow_self_clock', 'admin_include_all', 'payroll_enabled', 'penalty_rate_enabled', 'notifications_enabled' );
        $int_fields   = array( 'hours_fulltime', 'hours_parttime' );
        $float_fields = array( 'tax_rate', 'daily_budget', 'daily_budget_mon', 'daily_budget_tue', 'daily_budget_wed', 'daily_budget_thu', 'daily_budget_fri', 'daily_budget_sat', 'daily_budget_sun', 'saturday_rate', 'sunday_rate' );
        $json_fields  = array( 'locations' );

        // The form declares which settings it actually rendered (see empr_field() in
        // admin/views/settings.php). A field the form never showed is NOT the same as a
        // field the user switched off: sections are omitted by build (free ZIP) or by
        // a feature not shipped in this build, and without this manifest the
        // "absent from POST means off" default below would silently overwrite stored
        // values a downgraded install still owns.
        // array_filter( ..., 'is_scalar' ) first: a crafted empr_present[0][0]=x posts a
        // nested array, and sanitize_key() only gained its own is_scalar() guard in WP 6.2 —
        // on older cores it would reach strtolower( array ) and fatal under PHP 8.
        $present = isset( $_POST['empr_present'] )
            ? array_map( 'sanitize_key', array_filter( (array) wp_unslash( $_POST['empr_present'] ), 'is_scalar' ) )
            : array();

        foreach ( $fields as $f ) {
            if ( ! in_array( $f, $present, true ) ) {
                continue; // not on this form — leave the stored value untouched
            }
            if ( in_array( $f, $bool_fields, true ) ) {
                $val = isset( $_POST[ 'empr_' . $f ] ) ? '1' : '0';
            } elseif ( in_array( $f, $int_fields, true ) ) {
                $val = max( 1, (int) ( isset( $_POST[ 'empr_' . $f ] ) ? $_POST[ 'empr_' . $f ] : 0 ) );
            } elseif ( in_array( $f, $float_fields, true ) ) {
                $val = max( 0, (float) ( isset( $_POST[ 'empr_' . $f ] ) ? $_POST[ 'empr_' . $f ] : 0 ) );
            } elseif ( in_array( $f, $json_fields, true ) ) {
                $raw     = isset( $_POST[ 'empr_' . $f ] ) ? wp_unslash( $_POST[ 'empr_' . $f ] ) : '[]';
                $decoded = self::sanitize_location_entries( json_decode( $raw, true ) );
                $val = wp_json_encode( $decoded );
            } else {
                $raw = isset( $_POST[ 'empr_' . $f ] ) ? wp_unslash( $_POST[ 'empr_' . $f ] ) : '';
                $val = sanitize_textarea_field( $raw );
                if ( $f === 'departments' ) {
                    $dept_lines  = array_values( array_filter( array_map( 'trim', explode( "\n", $val ) ) ) );
                    $val = implode( "\n", $dept_lines );
                }
            }
            EMPR_Settings::set( $f, $val );
        }

        // Switching per-day budgeting off does NOT clear the seven weekday targets.
        // Consumers already gate on the daily_budget_per_day_enabled flag alone
        // (EMPR_Settings::for_js() exposes dailyBudgetPerDayEnabled separately from
        // dailyBudgetByDay), so the stored amounts are inert while the flag is off and
        // are restored intact if it is switched back on. They are user-entered figures
        // with no other source of truth — a disabled feature must never destroy them.

        // Per-admin self-inclusion: stored as user meta, not a global option.
        // Each administrator independently controls whether they personally appear in the roster.
        // Manifest-guarded like every option in the $fields loop above: this is a checkbox, so
        // absent-from-POST means '0' ONLY when the form actually rendered it. If the row is not
        // on this form, the stored meta must be left alone rather than silently cleared.
        $admin_self_included_flag = false;
        if ( $is_wp_admin && in_array( 'admin_include_self', $present, true ) ) {
            $prev_self = get_user_meta( get_current_user_id(), 'empr_admin_include_self', true );
            $new_self  = isset( $_POST['empr_admin_include_self'] ) ? '1' : '0';
            update_user_meta( get_current_user_id(), 'empr_admin_include_self', $new_self );
            if ( $prev_self !== '1' && $new_self === '1' ) {
                $admin_self_included_flag = true;
            }
        }

        // When "include all admins" is enabled, permanently sync every admin's personal
        // inclusion setting so they remain in the roster even if the global is later disabled.
        // Keyed off the stored admin_include_all value, so it is independent of whether the
        // self-inclusion row above was on the form.
        if ( $is_wp_admin && EMPR_Settings::get( 'admin_include_all', '0' ) === '1' ) {
            $all_admin_ids = get_users( array( 'role' => 'administrator', 'fields' => 'ID', 'number' => -1 ) );
            foreach ( $all_admin_ids as $uid ) {
                update_user_meta( (int) $uid, 'empr_admin_include_self', '1' );
            }
        }


        $redirect_args = array( 'page' => 'employee-roster-settings', 'saved' => '1' );
        if ( $admin_self_included_flag ) {
            $redirect_args['admin_self_included'] = '1';
        }
        wp_safe_redirect( add_query_arg( $redirect_args, admin_url( 'admin.php' ) ) );
        exit;
    }

    public static function menus() {
        add_menu_page(
            __( 'EmpRoster', 'employee-roster' ),
            __( 'EmpRoster', 'employee-roster' ),
            'read',
            'employee-roster',
            array( __CLASS__, 'render_app' ),
            'dashicons-calendar-alt',
            58
        );
        add_submenu_page( 'employee-roster', __( 'Dashboard',    'employee-roster' ), __( 'Dashboard',    'employee-roster' ), 'read',                  'employee-roster',              array( __CLASS__, 'render_app' ) );
        add_submenu_page( 'employee-roster', __( 'Shifts',       'employee-roster' ), __( 'Shifts',       'employee-roster' ), 'emr_view_own_shifts',   'employee-roster-shifts',       array( __CLASS__, 'render_app' ) );
        add_submenu_page( 'employee-roster', __( 'Timesheets',   'employee-roster' ), __( 'Timesheets',   'employee-roster' ), 'read',                  'employee-roster-timesheets',   array( __CLASS__, 'render_app' ) );
        add_submenu_page( 'employee-roster', __( 'Staff',        'employee-roster' ), __( 'Staff',        'employee-roster' ), 'manage_options',    'employee-roster-staff',        array( __CLASS__, 'render_app' ) );
        add_submenu_page( 'employee-roster', __( 'Profile',      'employee-roster' ), __( 'Profile',      'employee-roster' ), 'read',                  'employee-roster-profile',      array( __CLASS__, 'render_app' ) );
        add_submenu_page( 'employee-roster', __( 'Settings',     'employee-roster' ), __( 'Settings',     'employee-roster' ), 'manage_options',        'employee-roster-settings',     array( __CLASS__, 'render_settings' ) );

        // External link to the EmpRoster website. Passing a fully-qualified URL as
        // the menu slug makes WordPress emit the item as a plain <a href="…">
        // (see wp-admin/menu-header.php), so there is no admin page, no callback
        // and no redirect behind it. The page title is empty because nothing is
        // ever rendered. Registered unconditionally — nothing to condition it on.
        add_submenu_page(
            'employee-roster',
            '',
            __( 'Upgrade', 'employee-roster' ),
            'manage_options',
            'https://employeeroster.io/#plans'
        );
    }

    public static function render_app() {
        $page = isset( $_GET['page'] ) ? sanitize_key( $_GET['page'] ) : 'employee-roster';
        if ( $page === 'employee-roster' ) {
            $page = 'employee-roster';
        } else {
            $page = str_replace( 'employee-roster-', '', $page );
        }
        echo '<div id="emr-app" class="emr-app" data-page="' . esc_attr( $page ) . '"></div>';
    }

    public static function render_settings() {
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'employee-roster' ) );
        }

        if ( isset( $_GET['saved'] ) ) {
            echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'Settings saved.', 'employee-roster' ) . '</p></div>';
        }
        if ( isset( $_GET['admin_self_included'] ) ) {
            echo '<div class="notice notice-info is-dismissible"><p>' .
                 esc_html__( 'You are now included in the EmpRoster staff list. You will appear in shifts, timesheets, and analytics alongside your team.', 'employee-roster' ) .
                 '</p></div>';
        }

        include EMPR_PLUGIN_DIR . 'admin/views/settings.php';
    }

    public static function assets( $hook ) {
        $page = isset( $_GET['page'] )
            ? sanitize_key( $_GET['page'] )
            : '';

        // Colour for the Upgrade link in the sidebar. Attached to core's 'admin-menu'
        // stylesheet: it is the sheet that styles this exact chrome, it is pulled in
        // on every admin screen as a dependency of 'wp-admin', and it has a real
        // source file — so its inline styles always print. This plugin's own
        // stylesheets are gated to its own pages below, hence a core handle.
        //
        // !important is deliberate, and is the only thing that makes the colour hold
        // in EVERY state. Core sets submenu anchor colour from several selectors that
        // outrank a plain attribute selector — notably the
        // '#adminmenu .wp-submenu li.current a' family (admin-menu.css:215-220), which
        // is 0,1,2,2 against our 0,1,2,1 — and each of the eight non-default admin
        // colour schemes ships its own colors.css that loads AFTER this inline block,
        // so an equal-specificity rule there would win on order alone. Rather than
        // trying to out-specify every scheme in every state, one !important settles
        // all of them: core uses no !important on these rules.
        //
        // Scoped to a single unique href, so it cannot affect any other menu item —
        // this plugin's or another plugin's. The four selectors cover normal, hover,
        // focus and active; folded/fly-out and current states need no separate rule
        // because they reuse this same anchor and !important already beats them.
        $empr_upgrade_link = '#adminmenu .wp-submenu a[href="https://employeeroster.io/#plans"]';
        wp_add_inline_style(
            'admin-menu',
            $empr_upgrade_link . ','
            . $empr_upgrade_link . ':hover,'
            . $empr_upgrade_link . ':focus,'
            . $empr_upgrade_link . ':active{color:#adff2f !important;font-weight:600}'
        );

        // Open that link in a new tab. WordPress builds the <a> itself in
        // wp-admin/menu-header.php and emits no target attribute, and there is no
        // filter for an individual submenu item — so the attributes are set on the
        // client. Inline on core's admin-wide 'common' script (wp-admin/admin.php:121)
        // rather than a shipped .js file: it is four lines and needs no request.
        wp_add_inline_script(
            'common',
            '(function(){function e(){var a=document.querySelector(\'' . $empr_upgrade_link . '\');'
            . 'if(a){a.setAttribute("target","_blank");a.setAttribute("rel","noopener noreferrer");}}'
            . 'if(document.readyState!=="loading"){e();}else{document.addEventListener("DOMContentLoaded",e);}})();'
        );

        // Only load SPA assets on known EmpRoster app pages.
        $emr_pages = array(
            'employee-roster',
            'employee-roster-shifts',
            'employee-roster-staff',
            'employee-roster-timesheets',
            'employee-roster-settings',
            'employee-roster-leave',
            'employee-roster-attendance',
            'employee-roster-profile',
            'employee-roster-login-logs',
            'employee-roster-wizard',
        );

        if ( ! in_array( $page, $emr_pages, true ) ) return;

        wp_enqueue_style(  'empr-fonts', EMPR_PLUGIN_URL . 'admin/fonts/fonts.css', array(), EMPR_VERSION );
        wp_enqueue_style(  'empr-admin', EMPR_PLUGIN_URL . 'admin/css/admin.css', array(), EMPR_VERSION );
        // Shared with the frontend (see EMPR_Public::maybe_assets). Depends on emr-admin so it
        // always loads last and can correct that file's own breakpoints without !important.
        wp_enqueue_style(  'empr-responsive', EMPR_PLUGIN_URL . 'shared/css/responsive.css', array( 'empr-admin' ), EMPR_VERSION );
        wp_enqueue_script( 'empr-app',    EMPR_PLUGIN_URL . 'admin/js/app.js',    array(), EMPR_VERSION, true );
        wp_enqueue_script( 'empr-wizard', EMPR_PLUGIN_URL . 'admin/js/wizard.js', array( 'empr-app' ), EMPR_VERSION, true );
        wp_localize_script( 'empr-app', 'EMPR', self::js_data() );

        if ( $page === 'employee-roster-settings' ) {
            wp_add_inline_style( 'empr-admin', self::settings_page_css() );
            wp_localize_script( 'empr-app', 'EMPR_SettingsPage', array(
                'depts'     => EMPR_Settings::departments(),
                'locs'      => EMPR_Settings::locations(),
            ) );
            wp_add_inline_script( 'empr-app', self::settings_page_js() );
        }
    }

    public static function js_data() {
        return array(
            'ajaxUrl'  => esc_url_raw( admin_url( 'admin-ajax.php' ) ),
            'nonce'    => wp_create_nonce( 'empr_nonce' ),
            'user'     => array(
                'id'        => get_current_user_id(),
                'name'      => wp_get_current_user()->display_name,
                'isManager'    => EMPR_Roles::is_manager(),
                'isAdmin'      => EMPR_Roles::is_admin(),
                'pay_rate'  => (float) get_user_meta( get_current_user_id(), 'empr_pay_rate', true ),
                'jobType'   => EMPR_Roles::job_type(),
            ),
            'settings'   => EMPR_Settings::for_js(),
            'pluginUrl'    => EMPR_PLUGIN_URL,
            'wizardNeeded' => current_user_can( 'manage_options' ) && get_option( 'empr_wizard_completed', '0' ) !== '1',
            'pendingRequestsCount' => self::get_pending_requests_count(),
        );
    }

    private static function get_pending_requests_count() {
        $uid      = get_current_user_id();
        $count    = 0;
        $leave_on = EMPR_Settings::get( 'leave_enabled', '1' ) === '1';

        if ( EMPR_Roles::is_manager() ) {
            // Open-shift and swap requests are not counted — EMPR_SwapRequests
            // does not ship in this build.
            if ( $leave_on ) {
                $count += count( EMPR_Leave::query( array( 'status' => 'pending' ) ) );
            }
        } else {
            if ( $leave_on ) {
                $count += count( EMPR_Leave::query( array( 'user_id' => $uid, 'status' => 'pending' ) ) );
            }
        }

        return $count;
    }

    /**
     * Strip every top-level admin menu that isn't part of Employee Roster.
     * Runs at priority 9999 so all third-party menus are already registered.
     * WP administrators (manage_options) are NEVER restricted — hard gate.
     */
    public static function remove_non_emr_menus() {
        if ( current_user_can( 'manage_options' ) ) return;
        if ( ! self::is_restricted_roster_user() ) return;
        global $menu;
        $allowed = array( 'employee-roster', 'index.php', 'profile.php' );
        foreach ( (array) $menu as $item ) {
            if ( empty( $item[2] ) ) continue;
            if ( ! in_array( $item[2], $allowed, true ) ) {
                remove_menu_page( $item[2] );
            }
        }
    }

    /**
     * Remove all dashboard widgets so no third-party plugin names are visible
     * on the WP admin dashboard for roster users.
     */
    public static function remove_dashboard_widgets() {
        if ( current_user_can( 'manage_options' ) ) return;
        if ( ! self::is_restricted_roster_user() ) return;
        global $wp_meta_boxes;
        if ( isset( $wp_meta_boxes['dashboard'] ) ) {
            $wp_meta_boxes['dashboard'] = array();
        }
    }

    /**
     * Remove all admin_notices callbacks registered by other plugins, then
     * restore only EMR's own setup notice. Prevents plugin activation banners
     * and update nags from revealing which plugins are installed.
     */
    public static function suppress_plugin_notices() {
        if ( current_user_can( 'manage_options' ) ) return;
        if ( ! self::is_restricted_roster_user() ) return;
        remove_all_actions( 'admin_notices' );
        // Re-register only EMR's own notice so the onboarding prompt still works.
        add_action( 'admin_notices', array( __CLASS__, 'setup_notice' ) );
    }

    public static function render_wizard() {
        $screen = get_current_screen();
        if ( ! $screen || strpos( $screen->id, 'roster' ) === false ) return;
        if ( ! current_user_can( 'manage_options' ) ) return;
        include EMPR_PLUGIN_DIR . 'admin/views/wizard.php';
    }

    public static function wizard_save_step() {
        if ( ! check_ajax_referer( 'empr_nonce', 'nonce', false ) ) {
            wp_send_json_error( array( 'message' => 'Invalid nonce.' ), 403 );
        }
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => 'Not authorized.' ), 403 );
        }

        $body = json_decode( file_get_contents( 'php://input' ), true );
        $step = isset( $body['step'] ) ? sanitize_key( $body['step'] ) : '';
        $data = isset( $body['data'] ) && is_array( $body['data'] ) ? $body['data'] : array();

        switch ( $step ) {
            case 'company':
                if ( isset( $data['company_name'] ) ) {
                    EMPR_Settings::set( 'company_name', sanitize_text_field( $data['company_name'] ) );
                }
                if ( isset( $data['primary_color'] ) ) {
                    $color = sanitize_hex_color( $data['primary_color'] );
                    if ( $color ) EMPR_Settings::set( 'primary_color', $color );
                }
                if ( isset( $data['week_start'] ) ) {
                    $ws = in_array( (string) $data['week_start'], array( '0', '1', '2', '3', '4', '5', '6' ), true ) ? (string) $data['week_start'] : '1';
                    EMPR_Settings::set( 'week_start', $ws );
                }
                break;

            case 'locations':
                $locs  = self::sanitize_location_entries( isset( $data['locations'] ) ? $data['locations'] : array() );
                EMPR_Settings::set( 'locations', wp_json_encode( $locs ) );
                break;

            case 'departments':
                $raw   = isset( $data['departments'] ) ? wp_unslash( $data['departments'] ) : '';
                $lines = array_values( array_filter( array_map( 'trim', explode( "\n", sanitize_textarea_field( $raw ) ) ) ) );
                EMPR_Settings::set( 'departments', implode( "\n", $lines ) );
                break;

            case 'employment':
                if ( isset( $data['hours_fulltime'] ) ) {
                    EMPR_Settings::set( 'hours_fulltime', max( 1, (int) $data['hours_fulltime'] ) );
                }
                if ( isset( $data['hours_parttime'] ) ) {
                    EMPR_Settings::set( 'hours_parttime', max( 1, (int) $data['hours_parttime'] ) );
                }
                break;

            case 'features':
                $feature_fields = array( 'allow_self_clock', 'notifications_enabled' );
                foreach ( $feature_fields as $f ) {
                    if ( isset( $data[ $f ] ) ) EMPR_Settings::set( $f, $data[ $f ] ? '1' : '0' );
                }
                break;
        }

        wp_send_json_success( array( 'step' => $step ) );
    }

    public static function wizard_complete() {
        if ( ! check_ajax_referer( 'empr_nonce', 'nonce', false ) ) {
            wp_send_json_error( array( 'message' => 'Invalid nonce.' ), 403 );
        }
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => 'Not authorized.' ), 403 );
        }
        update_option( 'empr_wizard_completed', '1' );
        wp_send_json_success( array( 'redirectUrl' => admin_url( 'admin.php?page=employee-roster' ) ) );
    }

    /**
     * Prepends "EMR" to plugin role display names on the All Users screen only.
     * Mutates $wp_roles in memory for this request — no database writes, no
     * effect on any other admin page or on the plugin's own capability checks.
     */
    public static function prefix_role_labels_for_users_list( $screen ) {
        if ( $screen->id !== 'users' ) return;

        global $wp_roles;
        $prefixed = array(
            'emr_employee'   => 'EMR Employee',
        );
        foreach ( $prefixed as $role => $label ) {
            if ( isset( $wp_roles->roles[ $role ] ) ) {
                $wp_roles->roles[ $role ]['name'] = $label;
                $wp_roles->role_names[ $role ]    = $label;
            }
        }
    }

    private static function settings_page_css() {
        return '
/* ── Page shell ───────────────────────────────────────────────────────────── */
.emr-settings-wrap { max-width: 1100px; }
.emr-pg-header {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 1.75rem; padding-bottom: 1.25rem;
  border-bottom: 2px solid #e2e8f0;
}
.emr-pg-title  { margin: 0 0 .25rem !important; font-size: 1.35rem !important; font-weight: 700; color: #0f172a; }
.emr-pg-desc   { margin: 0; color: #64748b; font-size: .875rem; }
.emr-pg-actions { display: flex; align-items: center; gap: .6rem; flex-shrink: 0; }
.emr-reset-btn  { color: #94a3b8 !important; border-color: #e2e8f0 !important; font-size: .8rem !important; }
.emr-reset-btn:hover { color: #475569 !important; border-color: #cbd5e1 !important; }
/* ── Two-column masonry layout ────────────────────────────────────────────── */
.emr-settings-grid { column-count: 2; column-gap: 1rem; margin-bottom: 1rem; }
.emr-settings-grid .emr-card { break-inside: avoid; margin-bottom: 1rem; }
/* ── Cards ────────────────────────────────────────────────────────────────── */
.emr-card {
  background: #fff;
  border: 1px solid #e2e8f0;
  border-radius: 10px;
  overflow: hidden;
  transition: box-shadow .15s;
}
.emr-card--muted { background: #f8fafc; }
.emr-card.emr-card--moving {
  box-shadow: 0 0 0 2px var(--wpr-brand, #2563eb), 0 8px 24px color-mix(in srgb, var(--wpr-brand, #2563eb) 15%, transparent);
}
/* ── Card header ──────────────────────────────────────────────────────────── */
.emr-card__hd {
  display: flex; align-items: flex-start; gap: .7rem;
  padding: .85rem 1rem;
  background: #fafbfd;
  border-bottom: 1px solid #f1f5f9;
}
.emr-card__icon {
  width: 28px; height: 28px; border-radius: 6px; flex-shrink: 0;
  background: #f1f5f9; border: 1px solid #e8edf3;
  display: flex; align-items: center; justify-content: center;
  font-size: 12px; color: #64748b; line-height: 1; margin-top: 1px;
}
.emr-card__meta   { flex: 1; min-width: 0; }
.emr-card__title  { font-size: .825rem; font-weight: 700; color: #0f172a; line-height: 1.3; }
.emr-card__desc   { font-size: .73rem; color: #94a3b8; margin-top: 2px; line-height: 1.45; }
/* ── Directional controls ─────────────────────────────────────────────────── */
.emr-card__controls {
  display: flex; gap: 2px; flex-shrink: 0; align-self: flex-start;
  opacity: 0; transition: opacity .15s;
}
.emr-card:hover .emr-card__controls,
.emr-card__hd:focus-within .emr-card__controls { opacity: 1; }
.emr-ctrl {
  width: 22px; height: 22px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border-radius: 4px; border: 1px solid #e2e8f0;
  background: #f8fafc; color: #64748b;
  font-size: 11px; line-height: 1; cursor: pointer;
  transition: background .1s, color .1s, border-color .1s;
}
.emr-ctrl:hover:not(:disabled) {
  background: var(--wpr-brand, #2563eb); border-color: var(--wpr-brand, #2563eb); color: #fff;
}
.emr-ctrl:disabled {
  opacity: .25; cursor: default; pointer-events: none;
}
/* ── Card body / form tables ──────────────────────────────────────────────── */
.emr-card__body { padding: 1rem 1.1rem; }
.emr-card .form-table { width: 100%; table-layout: fixed; margin: 0; }
.emr-card .form-table th {
  width: 150px; white-space: normal; padding-left: 0;
  font-size: .78rem; font-weight: 600; color: #64748b;
  vertical-align: top; padding-top: 9px;
}
.emr-card .form-table td  { padding-right: 0; vertical-align: top; padding-top: 7px; }
.emr-card .form-table td input.regular-text { width: 100%; max-width: 100%; box-sizing: border-box; }
.emr-card .form-table .description { margin-top: .35rem; font-size: .75rem; color: #94a3b8; }
.emr-subheading {
  font-size: .7rem !important; font-weight: 700 !important;
  text-transform: uppercase; letter-spacing: .07em;
  color: #94a3b8 !important; padding-left: 0 !important;
  padding-top: 1rem !important; border-top: 1px solid #f1f5f9;
}
.emr-subheading span { font-weight: 400; text-transform: none; letter-spacing: 0; color: #cbd5e1; margin-left: .35rem; }
.emr-not-created {
  display: inline-block; font-size: .75rem; color: #94a3b8;
  background: #f1f5f9; border: 1px dashed #e2e8f0;
  border-radius: 4px; padding: 2px 8px;
}
/* ── Footer ───────────────────────────────────────────────────────────────── */
.emr-settings-footer {
  display: flex; align-items: center; gap: 1rem;
  margin-top: 1.25rem; padding-top: 1.25rem;
  border-top: 1px solid #e2e8f0;
}
.emr-version { font-size: .75rem; color: #cbd5e1; }
/* ── Row-list (departments + locations) ───────────────────────────────────── */
.emr-row-list { display: flex; flex-direction: column; }
.emr-row {
  display: flex; align-items: center; gap: .6rem;
  padding: .35rem .4rem; border-radius: 6px;
  transition: background .12s;
}
.emr-row:hover { background: #f8fafc; }
.emr-row__name {
  flex: 1; min-width: 0; border: none; background: transparent;
  font-size: .875rem; color: #1e293b;
  padding: .25rem .3rem; border-radius: 4px;
  outline: none; transition: background .12s, box-shadow .12s;
}
.emr-row__name:focus     { background: #fff; box-shadow: 0 0 0 2px color-mix(in srgb, var(--wpr-brand, #2563eb) 25%, transparent); }
.emr-row__name--err      { background: #fef2f2 !important; box-shadow: 0 0 0 2px #dc262660 !important; }
.emr-row__clr {
  width: 18px; height: 18px; border-radius: 50%; flex-shrink: 0;
  cursor: pointer; position: relative; overflow: hidden;
  border: 2px solid rgba(0,0,0,.12);
  transition: transform .12s, box-shadow .12s;
}
.emr-row__clr:hover { transform: scale(1.15); box-shadow: 0 0 0 3px rgba(0,0,0,.08); }
.emr-row__clr input[type=color] {
  position: absolute; inset: 0; opacity: 0; cursor: pointer;
  width: 100%; height: 100%; padding: 0; border: none;
}
.emr-row__del {
  flex-shrink: 0; display: flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; border-radius: 4px;
  background: none; border: none; cursor: pointer;
  color: #94a3b8; font-size: .9rem; line-height: 1; padding: 0;
  opacity: 0; transition: opacity .12s, color .12s, background .12s;
}
.emr-row:hover .emr-row__del { opacity: 1; }
.emr-row__del:hover           { color: #dc2626; background: #fee2e2; }
.emr-row--ghost         { cursor: pointer; user-select: none; }
.emr-row--ghost:hover   { background: #f1f5f9; }
.emr-row__ghost-dot {
  width: 18px; height: 18px; border-radius: 50%; flex-shrink: 0;
  border: 2px dashed #cbd5e1; background: transparent;
}
.emr-row__ghost-label                       { font-size: .8rem; color: #94a3b8; }
.emr-row--ghost:hover .emr-row__ghost-label { color: #64748b; }
/* ── Per-day budget inputs (label left, amount right — one row per day) ────── */
.wpr-budget-days {
  display: flex; flex-direction: column; gap: .4rem; margin-top: .65rem;
  padding: .8rem; border: 1px solid #e2e8f0; border-radius: 8px; background: #f8fafc;
}
.wpr-budget-day {
  display: grid; grid-template-columns: 6.5rem 1fr;
  align-items: center; gap: .6rem;
}
.wpr-budget-day label { font-size: .78rem; font-weight: 600; color: #475569; }
.wpr-budget-day__field { font-size: .8rem; color: #64748b; white-space: nowrap; }
.wpr-budget-day__field input { width: 7rem; max-width: 100%; box-sizing: border-box; }
/* ── Responsive ───────────────────────────────────────────────────────────── */
@media (max-width: 900px) {
  .emr-settings-grid { column-count: 1; }
}';
    }

    private static function settings_page_js() {
        return '(function () {

  /* ══════════════════════════════════════════════════════════════════════════
     SECTION 1 — Dept / Loc row-lists
  ══════════════════════════════════════════════════════════════════════════ */

  var depts      = EMPR_SettingsPage.depts;
  var locs       = EMPR_SettingsPage.locs;
  var palette   = [\'#2563EB\',\'#16A34A\',\'#DC2626\',\'#D97706\',\'#7C3AED\',\'#0891B2\',\'#BE185D\',\'#374151\'];
  var palIdx    = locs.length;

  function nextColor() { return palette[palIdx++ % palette.length]; }

  function syncDepts() { document.getElementById(\'emr-dept-val\').value = depts.join(\'\\n\'); }
  function syncLocs()  { document.getElementById(\'emr-loc-val\').value  = JSON.stringify(locs); }

  function renderDepts() {
    var list = document.getElementById(\'emr-dept-list\');
    list.innerHTML = \'\';
    depts.forEach(function (name, i) {
      var row = makeRow(), inp = makeNameInput(name, \'Department name\'), del = makeDelBtn();
      inp.addEventListener(\'input\', function () { depts[i] = inp.value; inp.classList.remove(\'emr-row__name--err\'); syncDepts(); });
      del.addEventListener(\'click\', function () { depts.splice(i, 1); renderDepts(); });
      row.appendChild(inp); row.appendChild(del); list.appendChild(row);
    });
    list.appendChild(makeDeptGhost());
    syncDepts();
  }

  function makeDeptGhost() {
    var ghost = document.createElement(\'div\');
    ghost.className = \'emr-row emr-row--ghost\';
    var lbl = document.createElement(\'span\');
    lbl.className = \'emr-row__ghost-label\';
    lbl.textContent = \'+ Add department\';
    ghost.appendChild(lbl);
    ghost.addEventListener(\'click\', function () { depts.push(\'\'); renderDepts(); focusLast(document.getElementById(\'emr-dept-list\')); });
    return ghost;
  }

  function renderLocs() {
    var list = document.getElementById(\'emr-loc-list\');
    list.innerHTML = \'\';
    locs.forEach(function (loc, i) {
      var row = makeRow(), swatch = makeColorSwatch(loc.color), cInput = swatch.querySelector(\'input[type=color]\');
      var inp = makeNameInput(loc.name, \'Location name\'), del = makeDelBtn();
      cInput.addEventListener(\'input\', function () { locs[i].color = cInput.value; swatch.style.background = cInput.value; syncLocs(); });
      inp.addEventListener(\'input\',   function () { locs[i].name  = inp.value;    inp.classList.remove(\'emr-row__name--err\'); syncLocs(); });
      del.addEventListener(\'click\',   function () { locs.splice(i, 1); renderLocs(); });
      row.appendChild(swatch); row.appendChild(inp); row.appendChild(del); list.appendChild(row);
    });
    list.appendChild(makeLocGhost());
    syncLocs();
  }

  function makeLocGhost() {
    var ghost = document.createElement(\'div\');
    ghost.className = \'emr-row emr-row--ghost\';
    var dot = document.createElement(\'span\'); dot.className = \'emr-row__ghost-dot\';
    var lbl = document.createElement(\'span\'); lbl.className = \'emr-row__ghost-label\'; lbl.textContent = \'+ Add location\';
    ghost.appendChild(dot); ghost.appendChild(lbl);
    ghost.addEventListener(\'click\', function () { locs.push({ name: \'\', color: nextColor() }); renderLocs(); focusLast(document.getElementById(\'emr-loc-list\')); });
    return ghost;
  }

  function makeRow()              { var el = document.createElement(\'div\');   el.className = \'emr-row\'; return el; }
  function makeNameInput(v, ph)   { var el = document.createElement(\'input\'); el.type = \'text\'; el.className = \'emr-row__name\'; el.value = v; el.placeholder = ph; return el; }
  function makeColorSwatch(color) {
    var label = document.createElement(\'label\'); label.className = \'emr-row__clr\'; label.style.background = color; label.title = \'Change colour\';
    var inp = document.createElement(\'input\'); inp.type = \'color\'; inp.value = color;
    label.appendChild(inp); return label;
  }
  function makeDelBtn() {
    var btn = document.createElement(\'button\'); btn.type = \'button\'; btn.className = \'emr-row__del\'; btn.title = \'Remove\'; btn.innerHTML = \'&#x2715;\'; return btn;
  }
  function focusLast(list) { var inputs = list.querySelectorAll(\'.emr-row__name\'); if (inputs.length) inputs[inputs.length - 1].focus(); }

  document.getElementById(\'emr-settings-form\').addEventListener(\'submit\', function (e) {
    var first = null;
    document.querySelectorAll(\'#emr-loc-list .emr-row__name\').forEach(function (inp) {
      if (!inp.value.trim()) { inp.classList.add(\'emr-row__name--err\'); if (!first) first = inp; }
    });
    if (first) { e.preventDefault(); first.scrollIntoView({ behavior: \'smooth\', block: \'center\' }); first.focus(); }
  });

  /* ══════════════════════════════════════════════════════════════════════════
     SECTION 2 — Card layout management
  ══════════════════════════════════════════════════════════════════════════ */

  var LAYOUT_KEY = \'empr_settings_layout_v2\';

  function colGrid() { return document.getElementById(\'emr-settings-grid\'); }

  function saveLayout() {
    var ids = Array.prototype.slice.call(colGrid().children).map(function (c) { return c.dataset.card; }).filter(Boolean);
    try { localStorage.setItem(LAYOUT_KEY, JSON.stringify(ids)); } catch (e) {}
  }

  function restoreLayout() {
    var saved;
    try { saved = JSON.parse(localStorage.getItem(LAYOUT_KEY)); } catch (e) {}
    if (!Array.isArray(saved)) return;
    var grid = colGrid();
    var all = {};
    document.querySelectorAll(\'.emr-card[data-card]\').forEach(function (c) {
      all[c.dataset.card] = c;
      c.parentNode.removeChild(c);
    });
    saved.forEach(function (id) {
      if (all[id]) { grid.appendChild(all[id]); delete all[id]; }
    });
    Object.keys(all).forEach(function (id) { grid.appendChild(all[id]); });
  }

  function updateButtons() {
    var cards = Array.prototype.slice.call(colGrid().children);
    var total = cards.length;
    cards.forEach(function (card, idx) {
      var bUp    = card.querySelector(\'[data-dir="up"]\');
      var bDown  = card.querySelector(\'[data-dir="down"]\');
      var bLeft  = card.querySelector(\'[data-dir="left"]\');
      var bRight = card.querySelector(\'[data-dir="right"]\');
      if (!bUp) return;
      bUp.disabled    = idx === 0;
      bDown.disabled  = idx === total - 1;
      bLeft.disabled  = idx === 0;
      bRight.disabled = idx === total - 1;
    });
  }

  function moveCard(card, dir) {
    var grid = colGrid();
    var prev = card.previousElementSibling;
    var next = card.nextElementSibling;
    card.classList.add(\'emr-card--moving\');
    setTimeout(function () { card.classList.remove(\'emr-card--moving\'); }, 350);
    if      (dir === \'up\'    && prev) { grid.insertBefore(card, prev); }
    else if (dir === \'down\'  && next) { grid.insertBefore(next, card); }
    else if (dir === \'left\')          { grid.insertBefore(card, grid.firstChild); }
    else if (dir === \'right\')         { grid.appendChild(card); }
    updateButtons();
    saveLayout();
  }

  function initControls() {
    document.querySelectorAll(\'.emr-card[data-card]\').forEach(function (card) {
      card.querySelectorAll(\'.emr-ctrl\').forEach(function (btn) {
        btn.addEventListener(\'click\', function (e) {
          e.preventDefault();
          e.stopPropagation();
          moveCard(card, btn.dataset.dir);
        });
      });
    });
    var resetBtn = document.getElementById(\'emr-reset-layout\');
    if (resetBtn) {
      resetBtn.addEventListener(\'click\', function () {
        try { localStorage.removeItem(LAYOUT_KEY); } catch (e) {}
        location.reload();
      });
    }
  }

  /* ── Per-day budget fields show/hide ────────────────────────────────────── */
  function initBudgetDays() {
    var cb   = document.getElementById(\'empr_daily_budget_per_day_enabled\');
    var pane = document.getElementById(\'emr-budget-days\');
    if (!cb || !pane) return;
    function sync() { pane.style.display = cb.checked ? \'\' : \'none\'; }
    cb.addEventListener(\'change\', sync);
    sync();
  }

  /* ── Bootstrap ──────────────────────────────────────────────────────────── */
  restoreLayout();
  updateButtons();
  initControls();
  renderDepts();
  renderLocs();
  initBudgetDays();

})();';
    }

    /**
     * Email Notifications settings card.
     *
     * Always available: the body carries no conditional gate. This method once
     * carried a mislabeled annotation which removed the card entirely, leaving
     * users no way to see or toggle the setting — and making every settings save
     * silently write '0', since the checkbox that feeds handle_settings_save()
     * never rendered. Keep it unannotated.
     */
    private static function render_notifications_settings() {
        ?>
        <div class="emr-card" data-card="notifications">
          <div class="emr-card__hd">
            <span class="emr-card__icon">✉</span>
            <div class="emr-card__meta">
              <div class="emr-card__title">Email Notifications</div>
              <div class="emr-card__desc">Automatically email employees when key events occur.</div>
            </div>
            <?php empr_card_controls(); ?>
          </div>
          <div class="emr-card__body">
            <table class="form-table">
              <tr>
                <th>Enable Notifications</th>
                <td>
                  <label>
                    <input type="checkbox" name="empr_notifications_enabled" value="1"
                      <?php checked( EMPR_Settings::get( 'notifications_enabled', '0' ), '1' ); ?>>
                    Send email notifications to employees
                  </label>
                  <?php empr_field( 'notifications_enabled' ); ?>
                  <p class="description">When enabled, employees receive emails for key roster events: being added to the roster, new shift assignments, and changes to the time of a shift they are on. Notifications for other features, such as leave decisions and shift swaps, are sent where those features are available on your plan. Applies to all staff including existing employees.</p>
                </td>
              </tr>
            </table>
          </div>
        </div>
        <?php
    }

    public static function setup_notice() {
        $screen = get_current_screen();
        if ( ! $screen || strpos( $screen->id, 'roster' ) === false ) return;

        // Safe role count check — avoid wp_count_users() which can be heavy
        $manager_count = count( get_users( array(
            'role__in' => array( 'emr_manager', 'emr_supervisor', 'emr_employee' ),
            'number'   => 1,
            'fields'   => 'ID',
        ) ) );

        if ( $manager_count === 0 ) {
            echo '<div class="notice notice-info is-dismissible"><p>';
            echo wp_kses(
                sprintf(
                    /* translators: 1: Manager role name, 2: Employee role name */
                    __( '&#x1F44B; EmpRoster is active. Assign %1$s or %2$s roles to your users to get started.', 'employee-roster' ),
                    '<strong>Roster Manager</strong>',
                    '<strong>Roster Employee</strong>'
                ),
                array( 'strong' => array() )
            );
            echo '</p></div>';
        }
    }


    /* ── Admin footer branding ──────────────────────────────────────────────── */

    private static function is_emr_screen() {
        $screen = get_current_screen();
        return $screen && strpos( $screen->id, 'employee-roster' ) !== false;
    }

    public static function footer_text( $text ) {
        if ( ! self::is_emr_screen() ) return $text;
        return sprintf(
            'Please rate <strong>EmpRoster</strong> <a href="%s" target="_blank" rel="noopener noreferrer">&#9733;&#9733;&#9733;&#9733;&#9733; (5 Star)</a> on WordPress.org to help us spread the word.',
            'https://wordpress.org/support/plugin/employee-roster/reviews/#new-post'
        );
    }

    public static function footer_version( $text ) {
        if ( ! self::is_emr_screen() ) return $text;
        return '<a href="https://wordpress.org/support/plugin/employee-roster/" target="_blank" rel="noopener noreferrer">Support</a>'
            . ' &nbsp;|&nbsp; '
            . '<a href="https://employeeroster.io/docs/" target="_blank" rel="noopener noreferrer">Docs</a>'
            . ' &nbsp;|&nbsp; '
            . '<a href="https://wordpress.org/plugins/employee-roster/" target="_blank" rel="noopener noreferrer">Free Plugin</a>';
    }

}
