<?php
/**
 * Plugin Name: Painter Contractor Ballpark Calculator
 * Plugin URI: https://quietcitydesign.com/
 * Description: Configurable interior and exterior painting ballpark estimator with printable results, lead capture, saved submissions, and email delivery.
 * Version: 5.1.3.2
 * Author: Quiet City Design
 * Author URI: https://quietcitydesign.com/
 * Text Domain: pcec
 */

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

define('PCEC_VERSION', '5.1.3.2');
define('PCEC_DIR', plugin_dir_path(__FILE__));
define('PCEC_URL', plugin_dir_url(__FILE__));
define('PCEC_QCD_ENDPOINT', 'https://quietcitydesign.com/wp-json/qcd/v1/estimator-event');
require_once PCEC_DIR.'includes/class-estimator-core.php';
require_once PCEC_DIR.'includes/class-admin-ui.php';
require_once PCEC_DIR.'includes/class-qcd-client.php';
require_once PCEC_DIR.'includes/class-email-renderer.php';

final class PCEC_Plugin {
    private static $instance;
    private $option;
    private $admin_ui;

    public static function instance() {
        return self::$instance ?: (self::$instance = new self());
    }

    private function __construct() {
        $this->option = PCEC_Estimator_Core::option_name();
        $this->admin_ui = new PCEC_Admin_UI($this->option, 'pcec');
        add_action('init', [$this, 'register_submission_type']);
        add_action('admin_menu', [$this, 'admin_menu']);
        add_action('admin_init', [$this, 'register_settings']);
        add_action('admin_init', [$this, 'maybe_upgrade']);
        add_action('admin_enqueue_scripts', [$this, 'admin_assets']);
        add_action('all_admin_notices', [$this, 'estimates_page_promotion']);
        add_action('admin_footer', [$this, 'toolkit_menu_target']);
        add_action('wp_dashboard_setup', [$this, 'dashboard_widget']);
        add_action('admin_post_pcec_qcd_optin', [$this, 'handle_qcd_optin']);
        add_action('admin_post_pcec_dismiss_milestone', [$this, 'dismiss_milestone']);
        add_action('pcec_retry_milestone_event', [$this, 'retry_milestone_event']);
        add_action('wp_enqueue_scripts', [$this, 'register_assets']);
        add_shortcode(PCEC_Estimator_Core::manifest('shortcode'), [$this, 'shortcode']);
        add_action('wp_ajax_pcec_submit', [$this, 'submit']);
        add_action('wp_ajax_nopriv_pcec_submit', [$this, 'submit']);
        add_filter('manage_'.PCEC_Estimator_Core::post_type().'_posts_columns', [$this, 'clean_columns']);
        add_action('manage_'.PCEC_Estimator_Core::post_type().'_posts_custom_column', [$this, 'clean_column_value'], 10, 2);
        add_action('add_meta_boxes', [$this, 'meta_boxes']);
    }

    public static function presets() {
        $adjustments = [
            'height_adjustments'=>['h8'=>0,'h9'=>12,'h10'=>25,'h11_12'=>35,'vaulted'=>50],
            'condition_adjustments'=>['good'=>0,'normal'=>15,'significant'=>35],
            'occupancy_adjustments'=>['empty'=>0,'light'=>8,'occupied'=>15],
            'colour_adjustments'=>['one'=>0,'two_three'=>8,'four_plus'=>18,'undecided'=>5],
            'colour_change_adjustments'=>['similar'=>0,'light_to_dark'=>5,'dark_to_light'=>15,'unsure'=>7],
            'exterior_surface_adjustments'=>['vinyl_metal'=>0,'wood'=>10,'stucco'=>20,'brick'=>30,'other'=>15],
            'exterior_storey_adjustments'=>['one'=>0,'two'=>20,'three'=>40],
            'exterior_access_adjustments'=>['easy'=>0,'limited'=>12,'difficult'=>25],
            'premium_adjustment'=>8,'trim_complexity_adjustment'=>20,'estimate_range'=>15,
        ];
        return [
            'CA'=>array_merge(['market'=>'CA','currency'=>'CAD','currency_symbol'=>'$','unit'=>'ft','interior_wall_rate'=>2.20,'ceiling_rate'=>1.75,'trim_rate'=>2.50,'door_rate'=>115,'window_casing_rate'=>75,'crown_rate'=>3.25,'interior_door_rate'=>125,'accent_wall_rate'=>175,'one_storey_stairwell'=>625,'two_storey_stairwell'=>1200,'exterior_rate'=>2.75,'exterior_door_rate'=>225,'garage_door_rate'=>450,'minimum_interior'=>650,'minimum_exterior'=>1500],$adjustments),
            'US'=>array_merge(['market'=>'US','currency'=>'USD','currency_symbol'=>'$','unit'=>'ft','interior_wall_rate'=>1.85,'ceiling_rate'=>1.40,'trim_rate'=>2.00,'door_rate'=>95,'window_casing_rate'=>60,'crown_rate'=>2.60,'interior_door_rate'=>100,'accent_wall_rate'=>140,'one_storey_stairwell'=>500,'two_storey_stairwell'=>950,'exterior_rate'=>2.50,'exterior_door_rate'=>180,'garage_door_rate'=>350,'minimum_interior'=>500,'minimum_exterior'=>1200],$adjustments),
        ];
    }

    public function settings() {
        $defaults = array_merge(self::presets()['CA'], [
            'contractor_email' => get_option('admin_email'), 'booking_url' => '',
            'contractor_name' => get_bloginfo('name'), 'accent_color' => '#1d5dbf', 'language' => 'en_CA', 'service_scope' => 'both',
            'form_heading' => 'Save and email your estimate',
            'cta_text' => 'We will send you a copy and share your project details with '.get_bloginfo('name').' for follow-up.',
            'submit_button_text' => 'Schedule a free on-site visit',
            'lead_email_intro' => '<p>Thank you for taking the time to tell us about your painting project. This estimate provides a ballpark planning range. An on-site inspection is needed to confirm the project scope and final price.</p>',
            'lead_email_schedule' => '<p>To get an accurate estimate, schedule a no-obligation site inspection.</p>',
            'lead_email_signature' => '<p>Thank you,<br><strong>{contractor_name}</strong></p>',
            'lead_email_show_schedule_button' => 1,
            'lead_email_show_logo' => 0,
            'lead_email_logo_id' => 0,
            'include_tax' => 0, 'tax_rate' => 0, 'tax_label' => 'Applicable sales tax',
            'specialty_options' => [
                'wallpaper'=>1, 'popcorn'=>1, 'cabinets'=>1,
                'water_damage'=>1, 'plaster'=>1, 'new_drywall'=>1, 'old_trim'=>1,
                'ext_stripping'=>1, 'ext_trim'=>1, 'ext_shutters'=>1, 'ext_porches'=>1, 'ext_foundation'=>1, 'ext_staining'=>1,
                'ext_peeling'=>1, 'ext_bare'=>1, 'ext_rot'=>1, 'ext_mildew'=>1, 'ext_rust'=>1, 'ext_caulking'=>1, 'ext_lead'=>1, 'ext_access'=>1,
            ],
        ]);
        $saved = get_option($this->option, []);
        $out = wp_parse_args($saved, $defaults);
        $out['language'] = in_array($saved['language'] ?? '', ['en_CA','en_US'], true) ? $saved['language'] : (($out['market'] ?? 'CA') === 'US' ? 'en_US' : 'en_CA');
        $out['service_scope'] = in_array($out['service_scope'] ?? '', ['interior','exterior','both'], true) ? $out['service_scope'] : 'both';
        foreach (['height_adjustments','condition_adjustments','occupancy_adjustments','colour_adjustments','colour_change_adjustments','exterior_surface_adjustments','exterior_storey_adjustments','exterior_access_adjustments'] as $group) $out[$group] = array_merge($defaults[$group], is_array($saved[$group]??null)?$saved[$group]:[]);
        $out['specialty_options'] = array_merge($defaults['specialty_options'], is_array($saved['specialty_options']??null)?$saved['specialty_options']:[]);
        $out['accent_color'] = sanitize_hex_color($out['accent_color']) ?: '';
        $out['form_heading'] = trim((string)$out['form_heading']) ?: 'Save and email your estimate';
        $out['submit_button_text'] = trim((string)$out['submit_button_text']) ?: 'Schedule a free on-site visit';
        if (preg_match('/\x{00C3}|\x{00C2}|\x{00E2}/u', (string)$out['cta_text'])) $out['cta_text']='We will send you a copy and share your project details with '.get_bloginfo('name').' for follow-up.';
        foreach (['lead_email_intro','lead_email_schedule','lead_email_signature'] as $key) $out[$key] = wp_kses_post($out[$key]);
        return $out;
    }

    public function register_submission_type() {
        register_post_type(PCEC_Estimator_Core::post_type(), [
            'labels' => ['name' => 'Painting Estimates', 'singular_name' => 'Painting Estimate', 'menu_name' => 'Painting Estimates', 'add_new' => '', 'add_new_item' => ''],
            'public' => false, 'show_ui' => true, 'show_in_menu' => true, 'menu_icon' => 'dashicons-clipboard',
            'supports' => ['title'], 'capability_type' => 'post', 'map_meta_cap' => true,
            'capabilities' => ['create_posts' => 'do_not_allow'],
        ]);
    }

    public function admin_menu() {
        add_submenu_page('edit.php?post_type=pcec_submission', 'Calculator Settings', 'Settings', 'manage_options', 'pcec-settings', [$this, 'settings_page']);
        global $submenu;
        $parent = 'edit.php?post_type=pcec_submission';
        $submenu[$parent][] = ['Lead Toolkit', 'manage_options', $this->qcd_url('admin-menu')];
    }

    public function toolkit_menu_target() {
        ?><script>document.addEventListener('DOMContentLoaded',function(){var link=document.querySelector('#adminmenu a[href*="utm_content=admin-menu"]');if(link){link.target='_blank';link.rel='noopener noreferrer';}});</script><?php
    }

    public function register_settings() {
        register_setting('pcec_group', $this->option, ['sanitize_callback' => [$this, 'sanitize_settings']]);
    }

    public function sanitize_settings($input) {
        $preset_key = in_array($input['market'] ?? 'CA', ['CA', 'US'], true) ? $input['market'] : 'CA';
        $preset = self::presets()[$preset_key];
        $out = [];
        foreach (['market','language','service_scope','currency','currency_symbol','unit','contractor_name','form_heading','submit_button_text','tax_label'] as $key) $out[$key] = sanitize_text_field($input[$key] ?? $preset[$key] ?? '');
        $out['language'] = in_array($out['language'], ['en_CA','en_US'], true) ? $out['language'] : ($preset_key === 'US' ? 'en_US' : 'en_CA');
        $out['service_scope'] = in_array($out['service_scope'], ['interior','exterior','both'], true) ? $out['service_scope'] : 'both';
        $out['form_heading'] = trim($out['form_heading']) ?: 'Save and email your estimate';
        $out['submit_button_text'] = trim($out['submit_button_text']) ?: 'Schedule a free on-site visit';
        $out['accent_color'] = sanitize_hex_color($input['accent_color'] ?? '') ?: '';
        $out['cta_text'] = sanitize_textarea_field($input['cta_text'] ?? '');
        foreach (['lead_email_intro','lead_email_schedule','lead_email_signature'] as $key) $out[$key] = wp_kses_post($input[$key] ?? '');
        $out['lead_email_show_schedule_button'] = empty($input['lead_email_show_schedule_button']) ? 0 : 1;
        $out['lead_email_show_logo'] = empty($input['lead_email_show_logo']) ? 0 : 1;
        $out['lead_email_logo_id'] = absint($input['lead_email_logo_id'] ?? 0);
        $out['contractor_email'] = sanitize_email($input['contractor_email'] ?? get_option('admin_email'));
        $out['booking_url'] = esc_url_raw($input['booking_url'] ?? '');
        $out['include_tax'] = empty($input['include_tax']) ? 0 : 1;
        $out['tax_rate'] = min(100, max(0, (float)($input['tax_rate'] ?? 0)));
        $out['tax_label'] = trim($out['tax_label']) ?: 'Applicable sales tax';
        foreach (['wallpaper','popcorn','cabinets','water_damage','plaster','new_drywall','old_trim','ext_stripping','ext_trim','ext_shutters','ext_porches','ext_foundation','ext_staining','ext_peeling','ext_bare','ext_rot','ext_mildew','ext_rust','ext_caulking','ext_lead','ext_access'] as $key) $out['specialty_options'][$key] = empty($input['specialty_options'][$key]) ? 0 : 1;
        foreach (['interior_wall_rate','ceiling_rate','trim_rate','door_rate','window_casing_rate','crown_rate','interior_door_rate','accent_wall_rate','one_storey_stairwell','two_storey_stairwell','exterior_rate','exterior_door_rate','garage_door_rate','minimum_interior','minimum_exterior','premium_adjustment','trim_complexity_adjustment','estimate_range'] as $key) $out[$key] = max(0, (float)($input[$key] ?? $preset[$key]));
        foreach (['height_adjustments','condition_adjustments','occupancy_adjustments','colour_adjustments','colour_change_adjustments','exterior_surface_adjustments','exterior_storey_adjustments','exterior_access_adjustments'] as $group) foreach ($preset[$group] as $key=>$value) $out[$group][$key]=min(200,max(0,(float)($input[$group][$key]??$value)));
        return $out;
    }

    public function maybe_upgrade() {
        $installed = get_option('pcec_version', '1.0.0');
        if (version_compare($installed, PCEC_VERSION, '>=')) return;
        $saved = get_option($this->option, []);
        if (version_compare($installed, '1.8.0', '<')) {
            $triggered = get_option('pcec_triggered_milestones', []);
            $count = $this->estimate_count();
            foreach ($this->milestones_up_to($count) as $milestone) {
                if (empty($triggered[$milestone])) {
                    $triggered[$milestone] = ['status' => 'historical', 'timestamp' => current_time('mysql')];
                }
            }
            update_option('pcec_triggered_milestones', $triggered, false);
        }
        update_option('pcec_version', PCEC_VERSION);
    }

    public function settings_page() {
        if (!current_user_can('manage_options')) return;
        $s = $this->settings();
        $this->render_painter_settings_v2($s);
    }

    private function render_painter_settings_v2($s) {
        $save=function(){echo '<div class="pcec-tab-save"><button type="submit" class="button button-primary">Save Settings</button></div>';};
        $table=function($callback){echo '<table class="form-table" role="presentation"><tbody>';call_user_func($callback);echo '</tbody></table>';};
        ?>
        <div class="wrap pcec-admin"><form id="pcec-settings-form" method="post" action="options.php" data-pcec-settings-form><?php settings_fields('pcec_group'); ?>
        <div class="pcec-settings-header"><div><h1>Painter Calculator Settings</h1><p>Configure the calculator, then add <code>[painter_estimate_calculator]</code> to any page.</p></div></div><?php settings_errors($this->option); ?>
        <nav class="nav-tab-wrapper pcec-settings-tabs" aria-label="Calculator settings sections">
          <?php foreach(['welcome'=>'Start Here','business'=>'Business & Market','form'=>'Form Details','pricing'=>'Pricing & Allowances','adjustments'=>'Project Adjustments','emails'=>'Email Notifications','test'=>'Test Estimator'] as $key=>$label): ?><button type="button" class="nav-tab<?php echo $key==='welcome'?' nav-tab-active':''; ?>" data-pcec-tab="<?php echo esc_attr($key); ?>" aria-selected="<?php echo $key==='welcome'?'true':'false'; ?>"><?php echo esc_html($label); ?></button><?php endforeach; ?>
        </nav>
        <?php $this->admin_ui->welcome_panel(['shortcode'=>'[painter_estimate_calculator]','product_label'=>'interior and exterior painting rates and allowances','toolkit_url'=>$this->qcd_toolkit_url('settings-start-here','the-toolkit/'),'has_test_tab'=>true],[$this,'render_compact_optin']); ?>
        <section class="pcec-settings-panel" data-pcec-panel="business" hidden><h2>Business &amp; Market</h2><p class="pcec-tab-intro">Set lead delivery, scheduling and the market format shown to homeowners.</p>
          <?php $table(function()use($s){$this->text_row('Contractor name','contractor_name',$s['contractor_name']);$this->text_row('Lead notification email','contractor_email',$s['contractor_email'],'email');$this->text_row('On-site booking link','booking_url',$s['booking_url'],'url');$this->select_row('Pricing preset','market',$s['market'],['CA'=>'Canada','US'=>'United States']);$this->select_row('Language & region','language',$s['language'],['en_CA'=>'English (Canada)','en_US'=>'English (United States)'],'Controls regional spelling and address terminology throughout the estimator, emails and printouts.');$this->text_row('Currency code','currency',$s['currency']);$this->text_row('Currency symbol','currency_symbol',$s['currency_symbol']);$this->text_row('Measurement unit','unit',$s['unit']);});$save(); ?>
        </section>
        <section class="pcec-settings-panel" data-pcec-panel="form" hidden><h2>Form Details</h2><p class="pcec-tab-intro">Customize the public call to action, accent colour and optional tax treatment.</p>
          <?php $table(function()use($s){echo '<tr><th scope="row">Services offered</th><td><fieldset><legend class="screen-reader-text">Painting services offered</legend>';foreach(['interior'=>'Interior painting','exterior'=>'Exterior painting','both'=>'Interior and exterior painting'] as $value=>$label)echo '<label style="display:block;margin:8px 0"><input type="radio" name="'.esc_attr($this->field_name('service_scope')).'" value="'.esc_attr($value).'" '.checked($s['service_scope'],$value,false).'> '.esc_html($label).'</label>';echo '<p class="description">When only one service is offered, the estimator skips the first choice and starts with the relevant project questions.</p></fieldset></td></tr>';$this->text_row('Final form heading','form_heading',$s['form_heading']);$this->textarea_row('Final form call to action','cta_text',$s['cta_text']);$this->text_row('Final form button text','submit_button_text',$s['submit_button_text']);$this->color_row('Calculator accent colour','accent_color',$s['accent_color']);echo '<tr><th scope="row">Sales tax</th><td><label><input type="checkbox" name="'.esc_attr($this->field_name('include_tax')).'" value="1" '.checked($s['include_tax'],1,false).'> Include tax in the estimate</label></td></tr>';$this->text_row('Tax label','tax_label',$s['tax_label']);$this->number_row('Tax rate (%)','tax_rate',$s['tax_rate'],'0.01');$groups=['Interior options'=>['wallpaper'=>'Wallpaper removal','popcorn'=>'Popcorn ceiling removal','cabinets'=>'Cabinet painting','water_damage'=>'Water damage','plaster'=>'Extensive plaster repair','new_drywall'=>'New drywall','old_trim'=>'Older glossy trim'],'Exterior services'=>['ext_stripping'=>'Paint or finish stripping','ext_trim'=>'Trim, fascia and soffit painting','ext_shutters'=>'Shutter painting','ext_porches'=>'Porch, railing or column painting','ext_foundation'=>'Foundation painting','ext_staining'=>'Deck or fence staining'],'Exterior conditions'=>['ext_peeling'=>'Peeling or flaking paint','ext_bare'=>'Bare wood or exposed substrate','ext_rot'=>'Rot or damaged siding','ext_mildew'=>'Mildew or algae','ext_rust'=>'Rust on metal surfaces','ext_caulking'=>'Failed caulking','ext_lead'=>'Possible lead-based paint','ext_access'=>'Difficult landscaping or restricted access']];echo '<tr><th scope="row">Additional services and conditions</th><td><p class="description">Choose which inspection flags customers can select. These options are not priced automatically.</p>';foreach($groups as $heading=>$options){echo '<fieldset style="margin:18px 0"><legend><strong>'.esc_html($heading).'</strong></legend>';foreach($options as $key=>$label)echo '<label style="display:block;margin:8px 0"><input type="checkbox" name="'.esc_attr($this->field_name('specialty_options['.$key.']')).'" value="1" '.checked(!empty($s['specialty_options'][$key]),true,false).'> '.esc_html($label).'</label>';echo '</fieldset>';}echo '</td></tr>';});$save(); ?>
        </section>
        <section class="pcec-settings-panel" data-pcec-panel="pricing" hidden><h2>Pricing &amp; Allowances</h2><p class="pcec-tab-intro">Base rates assume two finish coats, standard preparation, professional mid-grade paint, masking, protection, labour and cleanup.</p>
          <?php $table(function()use($s){foreach(['Interior walls - per wall sq. ft.'=>'interior_wall_rate','Ceilings - per sq. ft.'=>'ceiling_rate','Baseboards - per linear ft.'=>'trim_rate','Painted door frame and casing allowance'=>'door_rate','Painted window casing allowance'=>'window_casing_rate','Crown moulding - per linear ft.'=>'crown_rate','Painted interior door slab allowance'=>'interior_door_rate','Accent wall allowance'=>'accent_wall_rate','One-storey stairwell allowance'=>'one_storey_stairwell','Two-storey stairwell allowance'=>'two_storey_stairwell','Exterior base rate - per estimated paintable sq. ft.'=>'exterior_rate','Exterior door allowance'=>'exterior_door_rate','Garage door allowance'=>'garage_door_rate','Minimum interior project'=>'minimum_interior','Minimum exterior project'=>'minimum_exterior','Estimate range (+/- %)'=>'estimate_range'] as $label=>$key)$this->number_row($label,$key,$s[$key],$key==='estimate_range'?'0.1':'0.01');});$save(); ?>
        </section>
        <section class="pcec-settings-panel" data-pcec-panel="adjustments" hidden><h2>Project Adjustments</h2><p class="pcec-tab-intro">Enter percentage additions. These adjustments apply to their relevant base component and do not compound.</p><div class="pcec-multiplier-groups">
          <?php $this->percentage_group('Ceiling height','Applied to the wall and ceiling painting in each room.','height_adjustments',['h8'=>'8 ft','h9'=>'9 ft','h10'=>'10 ft','h11_12'=>'11-12 ft','vaulted'=>'Vaulted / cathedral'],$s['height_adjustments']);$this->percentage_group('Average surface condition','Applied once across painted surfaces.','condition_adjustments',['good'=>'Good condition','normal'=>'Normal wear and repairs','significant'=>'Significant preparation'],$s['condition_adjustments']);$this->percentage_group('Work space','Protection and furniture handling.','occupancy_adjustments',['empty'=>'Empty or mostly clear','light'=>'Some furniture to work around','occupied'=>'Fully furnished or in use'],$s['occupancy_adjustments']);$this->percentage_group('Paint colours','Ceiling and trim white do not count.','colour_adjustments',['one'=>'One colour throughout','two_three'=>'Two or three colours','four_plus'=>'Four or more colours','undecided'=>'Not decided'],$s['colour_adjustments']);$this->percentage_group('Colour coverage','Primer or added coverage allowance.','colour_change_adjustments',['similar'=>'Similar colour or tone','light_to_dark'=>'Light to darker colour or tone','dark_to_light'=>'Dark to lighter colour or tone','unsure'=>'Not sure'],$s['colour_change_adjustments']);$this->percentage_group('Exterior surface','Applied to exterior base painting.','exterior_surface_adjustments',['vinyl_metal'=>'Vinyl / metal','wood'=>'Wood','stucco'=>'Stucco','brick'=>'Brick / masonry','other'=>'Other'],$s['exterior_surface_adjustments']);$this->percentage_group('Exterior storeys','Ladders, staging and slower access.','exterior_storey_adjustments',['one'=>'One storey','two'=>'Two storeys','three'=>'Three or more'],$s['exterior_storey_adjustments']);$this->percentage_group('Exterior access','Applied to exterior base painting.','exterior_access_adjustments',['easy'=>'Easy','limited'=>'Some restrictions','difficult'=>'Difficult / steep grade'],$s['exterior_access_adjustments']); ?>
          </div><?php $table(function()use($s){$this->number_row('Premium paint adjustment (%)','premium_adjustment',$s['premium_adjustment'],'0.1');});$save(); ?>
        </section>
        <section class="pcec-settings-panel" data-pcec-panel="emails" hidden><h2>Lead Email</h2><p class="pcec-tab-intro">These minimal controls affect only the homeowner email. The contractor notification remains fully templated.</p>
          <div class="pcec-email-settings"><div class="pcec-email-controls">
          <?php $this->email_logo_control($s);$this->email_editor('Intro message','lead_email_intro',$s['lead_email_intro'],'Shown near the beginning of the lead email.');$this->email_editor('Scheduling section message','lead_email_schedule',$s['lead_email_schedule'],'Shown before the optional scheduling button.'); ?><p><label><input type="checkbox" name="<?php echo esc_attr($this->field_name('lead_email_show_schedule_button')); ?>" value="1" <?php checked($s['lead_email_show_schedule_button']); ?>> Show Scheduling Button</label></p><?php $this->email_editor('Email signature','lead_email_signature',$s['lead_email_signature'],'Shown at the end of the email.',true);$save(); ?>
          </div><div class="pcec-email-previews"><h3>Lead email preview</h3><?php echo $this->email_preview(false); ?></div></div>
        </section>
        <section class="pcec-settings-panel" data-pcec-panel="test" hidden><h2>Test Estimator</h2><p class="pcec-tab-intro">This uses the last saved settings. It does not save a lead, send email or trigger milestones.</p><div class="pcec-test-warning"><strong>Save first:</strong> unsaved changes on other tabs are not reflected here.</div><?php echo $this->test_estimate_form(); ?></section>
        </form><form id="pcec-qcd-optin-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>"></form></div><?php
    }

    public function render_compact_optin($compact = true) {
        $this->qcd_optin_panel((bool) $compact);
    }

    private function qcd_optin_panel($compact = false) {
        if (!current_user_can('manage_options')) return;
        $optin = get_option('pcec_qcd_optin', []);
        $user = wp_get_current_user();
        $prefill = sanitize_email($_GET['pcec_owner_email'] ?? ($optin['owner_email'] ?? $user->user_email));
        $failed = isset($_GET['pcec_optin']) && $_GET['pcec_optin'] === 'failed';
        if (!empty($optin['success'])): ?>
            <div class="<?php echo $compact ? 'pcec-optin-confirmation' : 'pcec-qcd-panel'; ?>">
                <h2>Access Granted</h2>
                <p>Check your inbox for setup instructions, calculator updates and contractor onboarding tactics from Quiet City Design. You may unsubscribe using the link in any email.</p>
                <p><strong>Subscribed email:</strong> <?php echo esc_html($optin['owner_email'] ?? ''); ?><br><strong>Consent date:</strong> <?php echo esc_html($optin['consent_timestamp'] ?? ''); ?></p>
            </div>
        <?php return; endif; ?>
        <div class="<?php echo $compact ? 'pcec-optin-inline' : 'pcec-qcd-panel'; ?>">
            <?php if ($failed): ?><div class="notice notice-error inline"><p><?php echo esc_html(get_transient('pcec_qcd_optin_error') ?: 'The opt-in could not be submitted. Please try again.'); ?></p></div><?php endif; ?>
            <input form="pcec-qcd-optin-form" type="hidden" name="_wpnonce" value="<?php echo esc_attr(wp_create_nonce('pcec_qcd_optin')); ?>">
            <input form="pcec-qcd-optin-form" type="hidden" name="action" value="pcec_qcd_optin">
            <input form="pcec-qcd-optin-form" type="hidden" name="wordpress_user_email" value="<?php echo esc_attr($user->user_email); ?>">
            <div class="pcec-optin-fields">
                <label for="pcec-owner-email">Email address</label>
                <input form="pcec-qcd-optin-form" class="regular-text" id="pcec-owner-email" type="email" name="owner_email" value="<?php echo esc_attr($prefill); ?>" required>
                <span></span>
                <label class="pcec-optin-consent"><input form="pcec-qcd-optin-form" type="checkbox" name="consent" value="1" required> Send me setup training, calculator updates and contractor onboarding tactics from Quiet City Design. I can unsubscribe at any time.</label>
            </div>
            <p class="pcec-optin-submit"><?php submit_button('Access Training', 'secondary', 'submit', false, ['form'=>'pcec-qcd-optin-form']); ?></p>
        </div>
        <?php
    }

    private function percentage_group($title, $description, $group, $items, $values) {
        echo '<div class="pcec-multiplier-group"><h3>'.esc_html($title).'</h3><p>'.esc_html($description).'</p><table><thead><tr><th>Option</th><th>Addition</th></tr></thead><tbody>';
        foreach ($items as $key => $label) {
            $name = $this->field_name($group.'['.$key.']');
            $value = isset($values[$key]) ? (float) $values[$key] : 0;
            echo '<tr><td>'.esc_html($label).'</td><td><input type="number" min="-100" max="500" step="0.1" name="'.esc_attr($name).'" value="'.esc_attr($value).'"> %</td></tr>';
        }
        echo '</tbody></table></div>';
    }

    private function email_editor($label, $key, $value, $help = '', $media_buttons = false) {
        $this->admin_ui->email_editor($label, $key, $value, $help, $media_buttons);
    }

    private function email_logo_control($settings) {
        $this->admin_ui->email_logo_control($settings);
    }

    private function email_preview($contractor = false) {
        $s = $this->settings();
        $calc = [
            'lines' => [
                ['label'=>'Interior wall painting','amount'=>1850,'low'=>1573,'high'=>2128,'fixed'=>false],
                ['label'=>'Surface preparation','amount'=>278,'low'=>236,'high'=>319,'fixed'=>false],
            ],
            'low'=>1809, 'high'=>2447, 'total'=>2128,
            'tax_included'=>false, 'tax_label'=>$s['tax_label'],
        ];
        return '<div class="pcec-email-preview">'.$this->email_html('Jamie Homeowner','jamie@example.com','555-555-0148','123 Sample Street','Sampletown','A1A 1A1',[],$calc,$contractor).'</div>';
    }

    private function field_name($key) {
        // Convert material_rates[cedar] to pcec_settings[material_rates][cedar].
        // Wrapping the full string in one pair of brackets prevents WordPress
        // from building the nested arrays expected by sanitize_settings().
        return $this->admin_ui->field_name($key);
    }
    private function text_row($label,$key,$value,$type='text',$help='') { $this->admin_ui->text_row($label,$key,$value,$type,$help); }
    private function textarea_row($label,$key,$value,$help='') { $this->admin_ui->textarea_row($label,$key,$value,$help); }
    private function color_row($label,$key,$value,$help='') { $this->admin_ui->color_row($label,$key,$value,$help); }
    private function multiplier_group($title,$description,$group,$items,$values) { $this->admin_ui->multiplier_group($title,$description,$group,$items,$values); }
    private function number_row($label,$key,$value,$step='0.01') { $this->admin_ui->number_row($label,$key,$value,$step); }
    private function select_row($label,$key,$value,$options,$help='') { $this->admin_ui->select_row($label,$key,$value,$options,$help); }

    public function materials() { return ['architectural_asphalt'=>'Architectural asphalt shingles','premium_asphalt'=>'Premium / designer asphalt shingles','metal_shingles'=>'Metal shingles','standing_seam'=>'Standing seam metal','cedar'=>'Cedar shakes','slate'=>'Natural or synthetic slate','flat_membrane'=>'Flat / low-slope membrane']; }
    private function custom_materials() { return ['custom_1'=>'Custom Painting System 1','custom_2'=>'Custom Painting System 2','custom_3'=>'Custom Painting System 3']; }
    private function default_complexity_options() { return ['simple'=>'Simple gable','average'=>'Average','complex'=>'Complex','very_complex'=>'Very complex']; }
    public function complexity_options($settings = null) { $settings=$settings?:$this->settings(); return wp_parse_args($settings['complexity_names']??[], $this->default_complexity_options()); }
    public function complexity_image_url($key, $settings = null) { $settings=$settings?:$this->settings(); $image_id=absint($settings['complexity_images'][$key]??0); return ($image_id?wp_get_attachment_image_url($image_id,'medium_large'):'') ?: PCEC_URL.'assets/complexity/'.$key.'.jpeg'; }
    private function style_options() { return ['standard'=>'Standard system','premium'=>'Premium system','designer'=>'Designer / specialty system']; }
    private function style_compatibility() {
        $all=array_keys($this->style_options());
        return [
            'architectural_asphalt'=>['standard','premium'],
            'premium_asphalt'=>$all,
            'metal_shingles'=>['standard','premium'],
            'standing_seam'=>['standard','premium'],
            'cedar'=>$all,
            'slate'=>$all,
            'flat_membrane'=>['standard','premium'],
            'custom_1'=>$all,
            'custom_2'=>$all,
            'custom_3'=>$all,
        ];
    }

    public function admin_assets($hook) {
        $screen = get_current_screen();
        $is_settings = $hook === 'pcec_submission_page_pcec-settings';
        $is_estimates = $hook === 'edit.php' && $screen && $screen->post_type === 'pcec_submission';
        $is_dashboard = $hook === 'index.php';
        if (!$is_settings && !$is_estimates && !$is_dashboard) return;
        if ($is_settings) {
            wp_enqueue_media();
            wp_enqueue_script('pcec-admin', PCEC_URL.'assets/qcd-admin-core.js', ['jquery'], PCEC_VERSION, true);
            wp_enqueue_style('pcec-admin-core', PCEC_URL.'assets/qcd-admin-core.css', [], PCEC_VERSION);
            wp_enqueue_style('pcec', PCEC_URL.'assets/painter-calculator.css', [], PCEC_VERSION);
            wp_enqueue_script('pcec', PCEC_URL.'assets/painter-calculator.js', [], PCEC_VERSION, true);
        }
        wp_add_inline_style('common', '.pcec-settings-header{align-items:flex-start;display:flex;gap:24px;justify-content:space-between;max-width:1100px}.pcec-settings-header h1{margin-bottom:4px}.pcec-settings-header p{margin-top:4px}.pcec-settings-tabs{max-width:1100px}.pcec-settings-tabs .nav-tab{cursor:pointer}.pcec-settings-panel{max-width:1100px;padding:18px 2px 4px}.pcec-settings-panel[hidden]{display:none}.pcec-settings-panel>h2:first-child{margin-top:0}.pcec-tab-intro{background:#fff;border-left:4px solid #72aee6;box-sizing:border-box;color:#3c434a;line-height:1.55;margin:0 0 22px;max-width:1060px;padding:13px 16px}.pcec-tab-save{align-items:center;border-top:1px solid #dcdcde;display:flex;flex-wrap:wrap;gap:8px;margin-top:24px;max-width:1100px;padding-top:18px}.pcec-tab-save .description{flex-basis:100%;margin:0}.pcec-test-warning{background:#fff8e5;border-left:4px solid #dba617;box-sizing:border-box;margin:0 0 18px;max-width:1060px;padding:12px 16px}.pcec__test-note{background:#f6f7f7;border-left:4px solid var(--pcec-accent,#1d5dbf);box-sizing:border-box;margin:18px 0;padding:12px 14px}.pcec-material-settings{max-width:1100px}.pcec-material-settings input.regular-text{max-width:190px}.pcec-material-settings input[type=number]{width:100px}.pcec-material-settings th,.pcec-material-settings td{vertical-align:middle}.pcec-admin-image{align-items:center;display:flex;gap:14px}.pcec-admin-image img{aspect-ratio:16/10;border:1px solid #c3c4c7;border-radius:6px;height:80px;object-fit:cover;width:128px}.pcec-admin-image .button-link-delete{margin-left:6px}.pcec-calculation-note{background:#fff;border-left:4px solid #1d5dbf;margin:12px 0 18px;max-width:1060px;padding:14px 18px}.pcec-calculation-note p{line-height:1.55;margin:5px 0}.pcec-multiplier-groups{display:grid;gap:16px;grid-template-columns:repeat(2,minmax(0,520px));max-width:1060px}.pcec-multiplier-group{background:#fff;border:1px solid #dcdcde;padding:18px}.pcec-multiplier-group h3{font-size:16px;margin:0 0 5px}.pcec-multiplier-group>p{color:#50575e;line-height:1.45;margin:0 0 14px;min-height:40px}.pcec-multiplier-group table{border-collapse:collapse;width:100%}.pcec-multiplier-group th,.pcec-multiplier-group td{border-top:1px solid #eee;padding:8px 5px;text-align:left}.pcec-multiplier-group thead th{border-top:0;color:#646970;font-size:11px;text-transform:uppercase}.pcec-multiplier-group input{width:82px}@media(max-width:782px){.pcec-settings-header{align-items:stretch;flex-direction:column;gap:4px}.pcec-settings-tabs{display:flex;overflow-x:auto}.pcec-settings-tabs .nav-tab{flex:0 0 auto;margin-bottom:0}.pcec-admin-image{align-items:flex-start;flex-direction:column}}@media(max-width:900px){.pcec-multiplier-groups{grid-template-columns:1fr}.pcec-multiplier-group>p{min-height:0}}.pcec-qcd-panel{background:#fff;border:1px solid #dcdcde;border-left:4px solid #1d5dbf;box-shadow:0 1px 1px rgba(0,0,0,.04);box-sizing:border-box;margin:16px 0;padding:16px 20px;max-width:1100px}.pcec-qcd-panel--settings{margin-top:28px}.pcec-qcd-panel h2{font-size:18px;margin:2px 0 8px}.pcec-qcd-panel p{font-size:13px;line-height:1.55;margin:6px 0}.pcec-qcd-kicker{color:#646970;font-size:11px!important;font-weight:700;letter-spacing:.08em;text-transform:uppercase}.pcec-dashboard-summary{display:grid;gap:10px;grid-template-columns:1.25fr 1fr 1fr;margin:4px 0 10px}.pcec-dashboard-summary p{background:#fff;border:1px solid #72aee6;box-shadow:inset 4px 0 0 #2271b1;margin:0;padding:12px}.pcec-dashboard-summary strong{display:block;margin-bottom:8px}.pcec-dashboard-summary span{color:#1d2327;display:block;font-size:15px;font-weight:600;line-height:1.45}.pcec-dashboard-summary__primary span{font-size:42px;font-weight:700;line-height:1}@media(max-width:900px){.pcec-dashboard-summary{grid-template-columns:1fr}}#pcec_dashboard_activity .widefat{margin-top:10px}#pcec_dashboard_activity .notice h3{margin-bottom:6px}#pcec_dashboard_activity .button{margin-top:4px}.edit-php.post-type-pcec_submission .notice-success h3{margin:8px 0}.edit-php.post-type-pcec_submission .notice-success{padding-bottom:10px}.pcec-welcome-grid{display:grid;gap:18px;grid-template-columns:minmax(0,1fr) minmax(0,1.2fr);max-width:1100px}.pcec-welcome-card{background:#fff;border:1px solid #dcdcde;box-shadow:0 1px 1px rgba(0,0,0,.04);box-sizing:border-box;min-width:0;padding:18px 22px}.pcec-welcome-card h3{font-size:16px;margin:0 0 10px}.pcec-welcome-card p,.pcec-welcome-card li{line-height:1.55}.pcec-welcome-card ol{margin-left:18px}.pcec-toolkit-callout{border-top:1px solid #dcdcde;margin-top:18px;padding-top:16px}.pcec-toolkit-callout h3{font-size:15px;margin-bottom:6px}.pcec-toolkit-callout p{margin:6px 0}.pcec-optin-fields{align-items:start;display:grid;gap:12px 14px;grid-template-columns:90px minmax(0,1fr);margin-top:22px}.pcec-optin-fields>label:first-child{font-weight:600;padding-top:5px}.pcec-optin-fields input[type=email]{box-sizing:border-box;max-width:100%;width:100%}.pcec-optin-consent{line-height:1.45}.pcec-optin-consent input{margin-top:2px}.pcec-optin-inline .pcec-optin-submit{margin:14px 0 0 104px}.pcec-optin-confirmation{background:#f6f7f7;border-left:4px solid #00a32a;margin-top:14px;padding:12px 14px}@media(max-width:900px){.pcec-welcome-grid{grid-template-columns:1fr}}@media(max-width:782px){.pcec-optin-fields{grid-template-columns:1fr}.pcec-optin-fields>span{display:none}.pcec-optin-inline .pcec-optin-submit{margin-left:0}}');
    }

    private function qcd_url($placement) {
        return PCEC_QCD_Client::tracked_url($placement);
    }

    private function qcd_toolkit_url($placement, $path = 'the-toolkit/universal-inbox/') {
        return PCEC_QCD_Client::tracked_url($placement,$path);
    }

    private function qcd_endpoint_url() {
        return PCEC_QCD_Client::endpoint_url();
    }

    public function handle_qcd_optin() {
        if (!current_user_can('manage_options')) wp_die('Permission denied.');
        check_admin_referer('pcec_qcd_optin');
        $owner_email = sanitize_email(wp_unslash($_POST['owner_email'] ?? ''));
        $wp_email = sanitize_email(wp_unslash($_POST['wordpress_user_email'] ?? ''));
        $user = wp_get_current_user();
        if (!is_email($owner_email) || empty($_POST['consent'])) {
            set_transient('pcec_qcd_optin_error', 'Please enter a valid email address and confirm consent.', 60);
            wp_safe_redirect(add_query_arg(['page'=>'pcec-settings','pcec_optin'=>'failed','pcec_owner_email'=>$owner_email], admin_url('edit.php?post_type=pcec_submission')));
            exit;
        }
        $timestamp = current_time('c');
        $payload = [
            'event' => 'plugin_optin',
            'plugin' => 'contractor-painting-estimate-calculator',
            'plugin_version' => PCEC_VERSION,
            'owner_email' => $owner_email,
            'wordpress_user_email' => $wp_email,
            'first_name' => sanitize_text_field($user->first_name),
            'last_name' => sanitize_text_field($user->last_name),
            'business_name' => get_bloginfo('name'),
            'website_url' => home_url('/'),
            'consent' => true,
            'consent_timestamp' => $timestamp,
        ];
        $result = $this->post_qcd_event($payload);
        $record = [
            'owner_email' => $owner_email,
            'wordpress_user_email' => $wp_email,
            'first_name' => $payload['first_name'],
            'last_name' => $payload['last_name'],
            'business_name' => $payload['business_name'],
            'website_url' => $payload['website_url'],
            'consent_timestamp' => $timestamp,
            'success' => !is_wp_error($result),
            'last_error' => is_wp_error($result) ? $result->get_error_message() : '',
        ];
        update_option('pcec_qcd_optin', $record, false);
        if (is_wp_error($result)) {
            set_transient('pcec_qcd_optin_error', $result->get_error_message(), 60);
            wp_safe_redirect(add_query_arg(['page'=>'pcec-settings','pcec_optin'=>'failed','pcec_owner_email'=>$owner_email], admin_url('edit.php?post_type=pcec_submission')));
            exit;
        }
        wp_safe_redirect(add_query_arg(['page'=>'pcec-settings','pcec_optin'=>'success'], admin_url('edit.php?post_type=pcec_submission')));
        exit;
    }

    private function post_qcd_event(array $payload) {
        return PCEC_QCD_Client::post_event($payload);
    }

    public function dashboard_widget() {
        if (!$this->can_view_estimates()) return;
        wp_add_dashboard_widget('pcec_dashboard_activity', 'Painting Estimate Activity', [$this, 'render_dashboard_widget']);
    }

    private function can_view_estimates() {
        return current_user_can('edit_posts') || current_user_can('manage_options');
    }

    public function render_dashboard_widget() {
        $s = $this->settings();
        $stats = $this->estimate_stats();
        $notice = $this->latest_undismissed_milestone();
        if ($notice) echo $this->milestone_notice_html($notice, $s, 'dashboard-milestone', true);
        echo '<div class="pcec-dashboard-summary"><p class="pcec-dashboard-summary__primary"><strong>Total estimates</strong><span>'.esc_html(number_format_i18n($stats['total'])).'</span></p><p><strong>Estimated project value</strong><span>'.esc_html($this->money($stats['low'], $s)).' to '.esc_html($this->money($stats['high'], $s)).'</span></p><p><strong>Estimates this month</strong><span>'.esc_html(number_format_i18n($stats['month'])).'</span></p></div>';
        echo '<p class="description">Based on estimate ranges generated by the calculator. This does not represent booked or completed work.</p>';
        if (!$stats['total']) {
            echo '<p>No estimate submissions have been saved yet.</p><p><a class="button button-secondary" href="'.esc_url(admin_url('edit.php?post_type=pcec_submission&page=pcec-settings')).'">Review Calculator Settings</a></p>';
            return;
        }
        $recent = get_posts(['post_type'=>'pcec_submission','post_status'=>'publish','numberposts'=>5,'orderby'=>'date','order'=>'DESC']);
        echo '<table class="widefat striped"><thead><tr><th>Lead name</th><th>Project</th><th>Estimate range</th><th>Submitted</th></tr></thead><tbody>';
        foreach ($recent as $post) {
            $calc = get_post_meta($post->ID, '_pcec_calculation', true);
            echo '<tr><td><a href="'.esc_url(get_edit_post_link($post->ID)).'">'.esc_html(get_the_title($post)).'</a></td><td>'.esc_html($this->calculation_painting_label($calc)).'</td><td>'.esc_html($calc ? $this->money($calc['low'], $s).' to '.$this->money($calc['high'], $s) : '').'</td><td>'.esc_html(get_date_from_gmt(get_gmt_from_date($post->post_date), 'M j, Y')).'</td></tr>';
        }
        echo '</tbody></table><p><a class="button button-secondary" href="'.esc_url(admin_url('edit.php?post_type=pcec_submission')).'">View All Estimates</a></p>';
    }

    private function milestone_notice_html($notice, $settings, $placement, $inline = false) {
        $milestone = absint($notice['milestone']);
        $dismiss = wp_nonce_url(admin_url('admin-post.php?action=pcec_dismiss_milestone&milestone='.$milestone), 'pcec_dismiss_milestone_'.$milestone);
        return '<div class="notice notice-success '.($inline ? 'inline' : '').'"><h3>'.esc_html($milestone).' estimates generated</h3><p>Your calculator has now captured '.esc_html($milestone).' estimate requests, representing an estimated project value of '.esc_html($this->money($notice['estimated_value_low'], $settings)).' to '.esc_html($this->money($notice['estimated_value_high'], $settings)).'.</p><p>At this stage, response speed may have more impact than generating more traffic.</p><p><a class="button button-primary" href="'.esc_url($this->qcd_toolkit_url($placement)).'" target="_blank" rel="noopener noreferrer">Review Your Lead Response Process</a> <a class="button" href="'.esc_url($dismiss).'">Dismiss</a></p></div>';
    }

    public function dismiss_milestone() {
        $milestone = absint($_GET['milestone'] ?? 0);
        if (!$this->can_view_estimates() || !$milestone) wp_die('Permission denied.');
        check_admin_referer('pcec_dismiss_milestone_'.$milestone);
        update_user_meta(get_current_user_id(), 'pcec_dismissed_milestone_'.$milestone, 1);
        wp_safe_redirect(admin_url('index.php'));
        exit;
    }

    private function calculation_painting_label($calc) {
        if (empty($calc['lines'][0]['label'])) return '';
        return preg_replace('/ painting \(.*/', '', (string) $calc['lines'][0]['label']);
    }

    private function estimate_count() {
        return (int) wp_count_posts('pcec_submission')->publish;
    }

    private function estimate_stats() {
        $stats = ['total'=>0,'month'=>0,'low'=>0,'high'=>0];
        $ids = get_posts(['post_type'=>'pcec_submission','post_status'=>'publish','numberposts'=>-1,'fields'=>'ids']);
        $stats['total'] = count($ids);
        $start = new DateTimeImmutable(wp_date('Y-m-01 00:00:00'), wp_timezone());
        foreach ($ids as $id) {
            $calc = get_post_meta($id, '_pcec_calculation', true);
            if (is_array($calc)) {
                $stats['low'] += (float)($calc['low'] ?? 0);
                $stats['high'] += (float)($calc['high'] ?? 0);
            }
            $post = get_post($id);
            if ($post) {
                $submitted = new DateTimeImmutable($post->post_date, wp_timezone());
                if ($submitted >= $start) $stats['month']++;
            }
        }
        return $stats;
    }

    private function milestones_up_to($count) {
        $milestones = [];
        foreach ([10,25,50] as $milestone) if ($count >= $milestone) $milestones[] = $milestone;
        for ($milestone = 100; $milestone <= $count; $milestone += 50) $milestones[] = $milestone;
        return $milestones;
    }

    private function reached_milestone($count) {
        if (in_array($count, [10,25,50], true)) return $count;
        return $count >= 100 && $count % 50 === 0 ? $count : 0;
    }

    private function check_milestone_after_submission() {
        $count = $this->estimate_count();
        $milestone = $this->reached_milestone($count);
        if (!$milestone) return;
        $triggered = get_option('pcec_triggered_milestones', []);
        if (!empty($triggered[$milestone])) return;
        $stats = $this->estimate_stats();
        $record = [
            'milestone' => $milestone,
            'total_estimates' => $count,
            'estimated_value_low' => round($stats['low'], 2),
            'estimated_value_high' => round($stats['high'], 2),
            'timestamp' => current_time('mysql'),
            'status' => 'local',
            'attempts' => 0,
            'last_error' => '',
        ];
        $triggered[$milestone] = $record;
        update_option('pcec_triggered_milestones', $triggered, false);
        $this->send_milestone_if_opted_in($milestone);
    }

    private function latest_undismissed_milestone() {
        $triggered = get_option('pcec_triggered_milestones', []);
        if (!is_array($triggered)) return null;
        krsort($triggered, SORT_NUMERIC);
        foreach ($triggered as $milestone => $record) {
            if (($record['status'] ?? '') === 'historical') continue;
            if (get_user_meta(get_current_user_id(), 'pcec_dismissed_milestone_'.absint($milestone), true)) continue;
            return $record + ['milestone'=>absint($milestone)];
        }
        return null;
    }

    private function milestone_payload($milestone) {
        $triggered = get_option('pcec_triggered_milestones', []);
        $record = $triggered[$milestone] ?? [];
        $optin = get_option('pcec_qcd_optin', []);
        $s = $this->settings();
        return [
            'event' => 'estimate_milestone',
            'plugin' => 'contractor-painting-estimate-calculator',
            'plugin_version' => PCEC_VERSION,
            'owner_email' => sanitize_email($optin['owner_email'] ?? ''),
            'wordpress_user_email' => sanitize_email($optin['wordpress_user_email'] ?? ''),
            'first_name' => sanitize_text_field($optin['first_name'] ?? ''),
            'last_name' => sanitize_text_field($optin['last_name'] ?? ''),
            'business_name' => sanitize_text_field($optin['business_name'] ?? get_bloginfo('name')),
            'website_url' => home_url('/'),
            'milestone' => absint($milestone),
            'total_estimates' => absint($record['total_estimates'] ?? $this->estimate_count()),
            'estimated_value_low' => round((float)($record['estimated_value_low'] ?? 0), 2),
            'estimated_value_high' => round((float)($record['estimated_value_high'] ?? 0), 2),
            'currency_code' => $s['currency'],
            'currency_symbol' => $s['currency_symbol'],
            'timestamp' => current_time('c'),
        ];
    }

    private function send_milestone_if_opted_in($milestone) {
        $optin = get_option('pcec_qcd_optin', []);
        if (empty($optin['success']) || empty($optin['owner_email']) || !is_email($optin['owner_email'])) return;
        $this->send_milestone_event($milestone);
    }

    private function send_milestone_event($milestone) {
        $payload = $this->milestone_payload($milestone);
        $result = $this->post_qcd_event($payload);
        $triggered = get_option('pcec_triggered_milestones', []);
        if (empty($triggered[$milestone])) return;
        $triggered[$milestone]['attempts'] = (int)($triggered[$milestone]['attempts'] ?? 0) + 1;
        if (is_wp_error($result)) {
            $triggered[$milestone]['status'] = 'send_failed';
            $triggered[$milestone]['last_error'] = $result->get_error_message();
            if ($triggered[$milestone]['attempts'] < 3 && !wp_next_scheduled('pcec_retry_milestone_event', [$milestone])) {
                wp_schedule_single_event(time() + HOUR_IN_SECONDS, 'pcec_retry_milestone_event', [$milestone]);
            }
        } else {
            $triggered[$milestone]['status'] = 'sent';
            $triggered[$milestone]['sent_timestamp'] = current_time('mysql');
            $triggered[$milestone]['last_error'] = '';
        }
        update_option('pcec_triggered_milestones', $triggered, false);
    }

    public function retry_milestone_event($milestone) {
        $triggered = get_option('pcec_triggered_milestones', []);
        if (empty($triggered[$milestone]) || ($triggered[$milestone]['status'] ?? '') === 'sent') return;
        $this->send_milestone_if_opted_in(absint($milestone));
    }

    public function estimates_page_promotion() {
        $screen = get_current_screen();
        if (!$screen || $screen->base !== 'edit' || $screen->post_type !== 'pcec_submission') return;
        $notice = $this->latest_undismissed_milestone();
        if ($notice) echo $this->milestone_notice_html($notice, $this->settings(), 'estimates-milestone');
    }

    public function available_materials($settings = null) {
        $settings = $settings ?: $this->settings();
        $materials = $this->materials();
        foreach ($this->custom_materials() as $key=>$fallback) {
            $name = trim((string)($settings['material_names'][$key] ?? ''));
            if ($name && (float)($settings['material_rates'][$key] ?? 0) > 0) $materials[$key] = $name;
        }
        return array_filter($materials, function($key) use ($settings) { return !empty($settings['material_enabled'][$key]); }, ARRAY_FILTER_USE_KEY);
    }

    public function material_image_url($key, $settings = null) {
        $settings = $settings ?: $this->settings();
        $image_id = absint($settings['material_images'][$key] ?? 0);
        $fallback = isset($this->custom_materials()[$key]) ? 'custom-placeholder.svg' : $key.'.jpeg';
        $custom = $image_id ? wp_get_attachment_image_url($image_id, 'medium_large') : '';
        return $custom ?: add_query_arg('ver', PCEC_VERSION, PCEC_URL.'assets/paintings/'.$fallback);
    }

    public function register_assets() {
        wp_register_style('pcec', PCEC_URL.'assets/painter-calculator.css', [], PCEC_VERSION);
        wp_register_script('pcec', PCEC_URL.'assets/painter-calculator.js', [], PCEC_VERSION, true);
    }

    public function shortcode() {
        $s = $this->settings();
        wp_enqueue_style('pcec'); wp_enqueue_script('pcec');
        // Keep each calculator's data beside its markup. This remains reliable
        // when performance plugins move, defer, or combine enqueued scripts.
        $config = ['ajaxUrl'=>admin_url('admin-ajax.php'),'nonce'=>wp_create_nonce('pcec_submit'),'settings'=>$s];
        ob_start(); include PCEC_DIR.'templates/calculator.php'; return PCEC_Estimator_Core::regionalize(ob_get_clean(), $s);
    }

    private function test_estimate_form() {
        $s = $this->settings();
        $config = ['ajaxUrl'=>admin_url('admin-ajax.php'),'nonce'=>wp_create_nonce('pcec_submit'),'settings'=>$s,'mode'=>'test'];
        $admin_test = true;
        ob_start(); include PCEC_DIR.'templates/calculator.php'; return PCEC_Estimator_Core::regionalize(ob_get_clean(), $s);
    }

    private function clean_payload($raw) {
        $scope=$this->settings()['service_scope']??'both';
        $requested=in_array($raw['project_type']??'', ['interior','exterior'],true)?$raw['project_type']:'';
        $p=['project_type'=>$scope==='both'?$requested:($requested===$scope?$scope:'')];
        foreach(['condition','occupancy','colours','colour_change','trim_detail','stairwell','exterior_surface','exterior_storeys','exterior_access'] as $key) $p[$key]=sanitize_key($raw[$key]??'');
        foreach(['doors','windows','crown_length','interior_doors','exterior_area','exterior_doors','garage_doors'] as $key) $p[$key]=max(0,(float)($raw[$key]??0));
        foreach(['walls','ceilings','trim','baseboards','door_casings','window_casings','crown','door_slabs','premium'] as $key) $p[$key]=!empty($raw[$key]);
        $available_specialties=array_keys(array_filter((array)($this->settings()['specialty_options']??[])));
        $interior_specialties=['wallpaper','popcorn','cabinets','water_damage','plaster','new_drywall','old_trim'];
        $exterior_specialties=['ext_stripping','ext_trim','ext_shutters','ext_porches','ext_foundation','ext_staining','ext_peeling','ext_bare','ext_rot','ext_mildew','ext_rust','ext_caulking','ext_lead','ext_access'];
        $available_specialties=array_intersect($available_specialties,$p['project_type']==='exterior'?$exterior_specialties:$interior_specialties);
        $p['specialty']=array_values(array_intersect(array_map('sanitize_key',(array)($raw['specialty']??[])),$available_specialties));
        $p['rooms']=[];
        foreach(array_slice((array)($raw['rooms']??[]),0,12) as $room){
            $length=max(0,min(200,(float)($room['length']??0))); $width=max(0,min(200,(float)($room['width']??0)));
            if($length<=0||$width<=0)continue;
            $height=in_array((string)($room['height']??'h8'),['h8','h9','h10','h11_12','vaulted'],true)?(string)$room['height']:'h8';
            $type=in_array($room['type']??'standard',['standard','kitchen','bathroom','hallway','other'],true)?$room['type']:'standard';
            $p['rooms'][]=['name'=>sanitize_text_field($room['name']??''),'length'=>$length,'width'=>$width,'height'=>$height,'type'=>$type,'accent'=>!empty($room['accent'])];
        }
        $room_accent_walls=count(array_filter($p['rooms'],static function($room){return !empty($room['accent']);}));
        $p['accent_walls']=min(20,max($room_accent_walls,max(0,(int)($raw['accent_walls']??0))));
        return $p;
    }

    public function calculate($p) {
        $s=$this->settings(); $lines=[];
        $add=function($label,$amount,$fixed=false)use(&$lines){if($amount>0)$lines[]=['label'=>$label,'amount'=>$amount,'low'=>$amount,'high'=>$amount,'fixed'=>$fixed];};
        if(($p['project_type']??'interior')==='interior'){
            $walls=$ceilings=$trim=$height_extra=0;
            foreach($p['rooms'] as $room){$h=['h8'=>8,'h9'=>9,'h10'=>10,'h11_12'=>11.5,'vaulted'=>12][$room['height']]??8;$perimeter=2*($room['length']+$room['width']);$room_walls=$perimeter*$h*(float)$s['interior_wall_rate'];$room_ceiling=$room['length']*$room['width']*(float)$s['ceiling_rate'];if($p['walls'])$walls+=$room_walls;if($p['ceilings'])$ceilings+=$room_ceiling;if($p['trim']&&$p['baseboards'])$trim+=$perimeter*(float)$s['trim_rate'];$height_extra+=(($p['walls']?$room_walls:0)+($p['ceilings']?$room_ceiling:0))*((float)($s['height_adjustments'][$room['height']]??0)/100);}
            $add('Interior wall painting',$walls);$add('Ceiling painting',$ceilings);$add('Baseboards',$trim);$surface_base=$walls+$ceilings+$trim;
            $add('Ceiling height and access',$height_extra);
            $add('Surface preparation',$surface_base*((float)($s['condition_adjustments'][$p['condition']]??0)/100));
            $add('Work-space protection',$surface_base*((float)($s['occupancy_adjustments'][$p['occupancy']]??0)/100));
            $add('Multiple colours',$surface_base*((float)($s['colour_adjustments'][$p['colours']]??0)/100));
            $add('Colour coverage allowance',($walls+$ceilings)*((float)($s['colour_change_adjustments'][$p['colour_change']]??0)/100));
            if($p['trim']&&$p['door_casings'])$add('Door frames and casings',$p['doors']*(float)$s['door_rate'],true);
            if($p['trim']&&$p['window_casings'])$add('Window casings',$p['windows']*(float)$s['window_casing_rate'],true);
            if($p['trim']&&$p['crown'])$add('Crown moulding',$p['crown_length']*(float)$s['crown_rate'],true);
            if($p['trim']&&$p['door_slabs'])$add('Interior door slabs',$p['interior_doors']*(float)$s['interior_door_rate'],true);
            $add('Accent walls',$p['accent_walls']*(float)$s['accent_wall_rate'],true);
            if($p['stairwell']==='one')$add('One-storey stairwell',$s['one_storey_stairwell'],true);elseif($p['stairwell']==='two')$add('Two-storey stairwell',$s['two_storey_stairwell'],true);
            if($p['premium'])$add('Premium paint allowance',$surface_base*((float)$s['premium_adjustment']/100));
            $minimum=(float)$s['minimum_interior'];
        }else{
            $base=$p['exterior_area']*(float)$s['exterior_rate'];$add('Exterior painting',$base);
            $add('Exterior surface adjustment',$base*((float)($s['exterior_surface_adjustments'][$p['exterior_surface']]??0)/100));
            $add('Storey and height access',$base*((float)($s['exterior_storey_adjustments'][$p['exterior_storeys']]??0)/100));
            $add('Site accessibility',$base*((float)($s['exterior_access_adjustments'][$p['exterior_access']]??0)/100));
            $add('Surface preparation',$base*((float)($s['condition_adjustments'][$p['condition']]??0)/100));
            $add('Multiple colours',$base*((float)($s['colour_adjustments'][$p['colours']]??0)/100));
            $add('Colour coverage allowance',$base*((float)($s['colour_change_adjustments'][$p['colour_change']]??0)/100));
            $add('Exterior doors',$p['exterior_doors']*(float)$s['exterior_door_rate'],true);$add('Garage doors',$p['garage_doors']*(float)$s['garage_door_rate'],true);
            if($p['premium'])$add('Premium exterior coating allowance',$base*((float)$s['premium_adjustment']/100));$minimum=(float)$s['minimum_exterior'];
        }
        $subtotal=array_sum(array_column($lines,'amount'));if($subtotal<$minimum)$add('Minimum project allowance',$minimum-$subtotal,true);
        $total=array_sum(array_column($lines,'amount'));$spread=(float)$s['estimate_range']/100;
        foreach($lines as &$line){$line['low']=$line['fixed']?$line['amount']:$line['amount']*(1-$spread);$line['high']=$line['fixed']?$line['amount']:$line['amount']*(1+$spread);}unset($line);
        $low=array_sum(array_column($lines,'low'));$high=array_sum(array_column($lines,'high'));
        if(!empty($s['include_tax'])&&$s['tax_rate']>0){$rate=$s['tax_rate']/100;$tax=['label'=>$s['tax_label'].' ('.number_format_i18n($s['tax_rate'],2).'%)','amount'=>$total*$rate,'low'=>$low*$rate,'high'=>$high*$rate,'fixed'=>false];$lines[]=$tax;$total+=$tax['amount'];$low+=$tax['low'];$high+=$tax['high'];}
        return ['lines'=>$lines,'total'=>round($total,2),'low'=>round($low,2),'high'=>round($high,2),'currency'=>$s['currency'],'symbol'=>$s['currency_symbol'],'tax_included'=>!empty($s['include_tax'])&&$s['tax_rate']>0,'tax_label'=>$s['tax_label']];
    }

    public function submit() {
        check_ajax_referer('pcec_submit','nonce');
        if (!empty($_POST['website'])) wp_send_json_error(['message'=>'Unable to submit.'],400);
        $email=sanitize_email(wp_unslash($_POST['email']??'')); $name=sanitize_text_field(wp_unslash($_POST['name']??''));
        $phone=sanitize_text_field(wp_unslash($_POST['phone']??'')); $address=sanitize_text_field(wp_unslash($_POST['address']??'')); $city=sanitize_text_field(wp_unslash($_POST['city']??'')); $postal=sanitize_text_field(wp_unslash($_POST['postal']??''));
        if (!$name || !is_email($email) || !$phone || !$address || !$city || !$postal) wp_send_json_error(['message'=>'Please complete all contact and address fields.'],422);
        $payload=json_decode(wp_unslash($_POST['estimate']??'{}'),true); $p=$this->clean_payload(is_array($payload)?$payload:[]);
        if (!$p['project_type']) wp_send_json_error(['message'=>'That painting service is not currently available.'],422);
        if ($p['project_type']==='interior' && !$p['rooms']) wp_send_json_error(['message'=>'Please enter at least one valid room.'],422);
        if ($p['project_type']==='exterior' && $p['exterior_area']<100) wp_send_json_error(['message'=>'Please enter an approximate home size of at least 100 sq. ft.'],422);
        $calc=$this->calculate($p); $s=$this->settings();
        $id=wp_insert_post(['post_type'=>'pcec_submission','post_status'=>'publish','post_title'=>$name.' - '.current_time('M j, Y g:i a')]);
        if (is_wp_error($id)) wp_send_json_error(['message'=>'The estimate could not be saved. Please try again.'],500);
        foreach (['name'=>$name,'email'=>$email,'phone'=>$phone,'address'=>$address,'city'=>$city,'postal'=>$postal,'inputs'=>$p,'calculation'=>$calc] as $k=>$v) update_post_meta($id,'_pcec_'.$k,$v);
        $visitor_html=$this->email_html($name,$email,$phone,$address,$city,$postal,$p,$calc,false);
        $contractor_html=$this->email_html($name,$email,$phone,$address,$city,$postal,$p,$calc,true);
        $headers=['Content-Type: text/html; charset=UTF-8'];
        wp_mail($email,'Your painting project estimate from '.$s['contractor_name'],$visitor_html,$headers);
        wp_mail($s['contractor_email'],'New painting estimate request: '.$name,$contractor_html,$headers);
        $this->check_milestone_after_submission();
        wp_send_json_success(['message'=>'Your estimate has been saved and emailed.','submission'=>$id,'redirect'=>$s['booking_url']]);
    }

    private function money($n,$s) { return PCEC_Estimator_Core::money($n,$s); }
    private function line_money($line,$s) { return !empty($line['fixed']) || abs((float)($line['high']??0)-(float)($line['low']??0))<1 ? $this->money($line['amount'],$s) : $this->money($line['low'],$s).' to '.$this->money($line['high'],$s); }
    private function email_html($name,$email,$phone,$address,$city,$postal,$p,$calc,$for_contractor=false) {
        $s=$this->settings();
        $qcd=$for_contractor?'<div style="border:1px solid #d9e2ef;border-left:4px solid #1d5dbf;border-radius:6px;margin-top:30px;padding:20px 22px;color:#4f5660;font-size:13px;line-height:1.6"><strong>Respond while the lead is hot.</strong><br>The Lead System Toolkit can reply to new leads in under 60 seconds and keep them warm with automatic follow-up messages. <a href="'.esc_url($this->qcd_url('contractor-email')).'">See The Lead System Toolkit</a>.</div>':'';
        return PCEC_Email_Renderer::render(['settings'=>$s,'lead'=>compact('name','email','phone','address','city','postal'),'calculation'=>$calc,'for_contractor'=>$for_contractor,'heading'=>$for_contractor?'Painting project estimate requested':'Your painting project estimate','money'=>function($value)use($s){return $this->money($value,$s);},'line_money'=>function($line)use($s){return $this->line_money($line,$s);},'extra_html'=>'','contractor_footer'=>$qcd]);
    }

    public function clean_columns($columns) {
        return ['cb'=>$columns['cb'],'title'=>'Customer','email'=>'Email','phone'=>'Phone','range'=>'Estimate range','date'=>$columns['date']];
    }

    public function clean_column_value($column,$id) {
        if ($column === 'email') echo esc_html(get_post_meta($id,'_pcec_email',true));
        if ($column === 'phone') echo esc_html(get_post_meta($id,'_pcec_phone',true));
        if ($column === 'range') {
            $calculation = get_post_meta($id,'_pcec_calculation',true);
            if (is_array($calculation)) {
                $settings = $this->settings();
                echo esc_html($this->money($calculation['low'] ?? 0,$settings).' to '.$this->money($calculation['high'] ?? 0,$settings));
            }
        }
    }
    public function columns($cols) { return ['cb'=>$cols['cb'],'title'=>'Customer','email'=>'Email','phone'=>'Phone','range'=>'Estimate range','date'=>$cols['date']]; }
    public function column_value($column,$id) {
        $s=$this->settings();
        if($column==='email') echo esc_html(get_post_meta($id,'_pcec_email',true));
        if($column==='phone') echo esc_html(get_post_meta($id,'_pcec_phone',true));
        if($column==='range'){$c=get_post_meta($id,'_pcec_calculation',true);if($c)echo esc_html($this->money($c['low'],$s).' – '.$this->money($c['high'],$s));}
    }
    public function meta_boxes() { add_meta_box('pcec_details','Estimate Details',[$this,'clean_details_box'],'pcec_submission','normal','high'); }
    public function clean_details_box($post) {
        $settings = $this->settings();
        $calculation = get_post_meta($post->ID,'_pcec_calculation',true);
        $inputs = get_post_meta($post->ID,'_pcec_inputs',true);
        echo '<p><strong>Email:</strong> '.esc_html(get_post_meta($post->ID,'_pcec_email',true)).'<br><strong>Phone:</strong> '.esc_html(get_post_meta($post->ID,'_pcec_phone',true)).'<br><strong>Street address:</strong> '.esc_html(get_post_meta($post->ID,'_pcec_address',true)).'<br><strong>City:</strong> '.esc_html(get_post_meta($post->ID,'_pcec_city',true)).'<br><strong>Postal / ZIP code:</strong> '.esc_html(get_post_meta($post->ID,'_pcec_postal',true)).'</p>';
        if (is_array($calculation)) {
            echo '<h3>'.esc_html($this->money($calculation['low'] ?? 0,$settings).' to '.$this->money($calculation['high'] ?? 0,$settings)).'</h3><table class="widefat striped"><tbody>';
            foreach ((array)($calculation['lines'] ?? []) as $line) echo '<tr><td>'.esc_html($line['label'] ?? '').'</td><td>'.esc_html($this->line_money($line,$settings)).'</td></tr>';
            echo '</tbody></table>';
        }
        if ($inputs) echo '<details><summary>Submitted factors</summary><pre>'.esc_html(wp_json_encode($inputs,JSON_PRETTY_PRINT)).'</pre></details>';
    }
    public function details_box($post) {
        $s=$this->settings();$c=get_post_meta($post->ID,'_pcec_calculation',true);$p=get_post_meta($post->ID,'_pcec_inputs',true);
        echo '<p><strong>Email:</strong> '.esc_html(get_post_meta($post->ID,'_pcec_email',true)).'<br><strong>Phone:</strong> '.esc_html(get_post_meta($post->ID,'_pcec_phone',true)).'<br><strong>Street address:</strong> '.esc_html(get_post_meta($post->ID,'_pcec_address',true)).'<br><strong>City:</strong> '.esc_html(get_post_meta($post->ID,'_pcec_city',true)).'<br><strong>Postal / ZIP code:</strong> '.esc_html(get_post_meta($post->ID,'_pcec_postal',true)).'</p>';
        if($c){echo '<h3>'.$this->money($c['low'],$s).' – '.$this->money($c['high'],$s).'</h3><table class="widefat striped"><tbody>';foreach((array)$c['lines'] as $line)echo '<tr><td>'.esc_html($line['label']).'</td><td>'.esc_html($this->line_money($line,$s)).'</td></tr>';echo '</tbody></table>';}
        if($p)echo '<details><summary>Submitted factors</summary><pre>'.esc_html(wp_json_encode($p,JSON_PRETTY_PRINT)).'</pre></details>';
    }
}

PCEC_Plugin::instance();
