const API = 'api.php';
let authToken = localStorage.getItem('admin_token');
let companyId = localStorage.getItem('admin_company_id');
let adminPermissionMap = {};
let adminSessionConfirmed = false;
let adminSessionRefreshTimer = null;
const ADMIN_SESSION_REFRESH_MS = 90000;
let qrInterval = null;
let qrCountdown = null;

// PERF: cache current lang to avoid repeated DOM access in t()
let _cachedLang = localStorage.getItem('admin_lang') || 'ar';

// ── TRANSLATIONS ──────────────────────────────────────────────────
const i18n = {
  en: {
    nav_dashboard: "Dashboard",
    nav_employees: "Employees",
    nav_contacts: "Contacts",
    contacts_title: "Contacts",
    contacts_sub: "Quick directory — call or message employees",
    contacts_refresh: "↻ Refresh",
    contacts_search_ph: "Search by name, phone, branch, or department...",
    contacts_loading: "Loading...",
    contacts_empty: "No results",
    contacts_meta: "{shown} / {total}",
    contacts_all_branches: "All branches",
    contacts_all_depts: "All departments",
    contacts_org_branch: "Branch:",
    contacts_org_dept: "Dept:",
    contacts_org_job: "Job title:",
    contacts_mail_btn: "Email",
    contacts_phone_btn: "Call",
    contacts_wa_btn: "WhatsApp",
    nav_attendance: "Attendance",
    nav_leave: "Employee requests management",
    nav_leave_short: "Requests",
    leave_sub_admin: "Review leave, permission & mission requests, or register on behalf of an employee",
    admin_add_leave: "Register leave for employee",
    admin_add_leave_title: "Register leave for employee",
    leave_request_kind: "Request type",
    leave_kind_leave: "Leave",
    leave_kind_permission: "Permission (time range)",
    leave_kind_mission: "Field mission",
    permission_mission_label: "Mission",
    leave_title: "Review employee requests",
    permission_mission_quota_exempt: "Field missions do not count toward the monthly permission allowance.",
    leave_toast_approved: "Request approved",
    leave_toast_rejected: "Request rejected",
    unknown_error: "Something went wrong",
    admin_leave_submit: "Submit request",
    admin_leave_pick_employee: "Please choose an employee",
    nav_qr: "QR Generator",
    company_admin: "Company Admin",
    account_settings: "Account Settings",
    language_pref: "Language Preference",
    change_pwd: "Change Password",
    current_pwd: "Current Password",
    new_pwd: "New Password",
    confirm_new_pwd: "Confirm New Password",
    update_pwd_btn: "Update Password",
    company_logo: "Company Logo",
    mobile_app_url_label: "Employee mobile app link",
    mobile_app_url_help: "The URL your employees use to open the app (PWA) on mobile, e.g. your employee login page. You can print a page with the company logo and a QR code only. Save the link first if you just edited it (or it uses the value already saved).",
    save_mobile_app_url: "Save link",
    print_mobile_app_qr: "Print QR poster",
    mobile_app_url_saved: "App link saved",
    mobile_app_url_empty: "Enter a valid link first",
    mobile_app_url_print_popup: "Allow pop-ups to print, or use your browser to print the tab.",
    mobile_app_qr_poster_title: "App download",
    mobile_app_qr_poster_note: "Please download the following app using your phone camera and the QR code.",
    clear_filters: "Clear Filters",
    sidebar_toggle_collapse: "Hide sidebar (full width)",
    sidebar_toggle_expand: "Show sidebar",
    bm_col_status: "Attendance",
    bm_status_present: "Present (check-in)",
    bm_status_absent: "Absent (no check-in)",
    bm_filter_all: "All",
    bm_filter_present: "Present only",
    bm_filter_absent: "Absent only",
    bm_req_pending: "Pending",
    bm_req_from: "From",
    bm_req_to: "to",
    dashboard_go_today: "Today",
    dashboard_pick_date: "Go to a specific day",
    dashboard_prev_day: "Previous day",
    dashboard_pending_live_hint: "Pending leaves reflects the current queue, not the selected date.",
    branch_unassigned: "No branch assigned",
    emp_by_branch_modal_title: "Employees by branch",
    emp_by_branch_col_branch: "Branch",
    emp_by_branch_col_count: "Active employees",
    emp_by_branch_hint: "View count per branch",
    emp_by_branch_no_data: "No branch breakdown yet. Refresh the dashboard.",
    ledger_title: "Leave ledger",
    ledger_leave_type: "Leave type",
    ledger_from_year: "From year",
    ledger_to_year: "To year",
    ledger_load: "Load",
    ledger_col_actions: "Ledger",
    ledger_open: "Ledger",
    ledger_col_kind: "Entry",
    ledger_col_delta: "Change",
    ledger_col_balance: "Balance",
    ledger_kind_accrual: "Year accrual",
    ledger_kind_leave_day: "Approved leave (day)",
    ledger_kind_manual: "Manual deduction",
    ledger_empty_hint: "Set years and click Load.",
    status_active: "Active",
    status_inactive: "Inactive",
    emp_account_active_label: "Login allowed (active account)",
    emp_account_active_hint: "When disabled, the employee cannot sign in until the account is restored.",
    emp_suspend_account: "Suspend account",
    emp_restore_account: "Restore account",
    emp_suspend_confirm: "Suspend login for {name}? They will not be able to sign in until the account is restored.",
    emp_restore_confirm: "Restore login for {name}?",
    emp_suspended_toast: "Account suspended",
    emp_restored_toast: "Account restored",
    edit: "Edit",
    delete: "Delete",
    message: "Message",
    reset_pwd: "Password",
    reset_device: "Reset Device",
    edit_desc: "Edit employee profile and settings",
    msg_desc: "Send a direct notification message",
    pwd_desc: "Change employee login password",
    manual_desc: "Record attendance manually for today",
    device_desc: "Unbind the current mobile device",
    delete_desc: "Remove employee from system permanently",
    bound: "Bound",
    unbound: "Unbound",
    close: "Close",
    lang_updated: "Language updated",
    pwd_updated: "Admin password updated successfully",
    pwd_mismatch: "New passwords do not match",
    fill_fields: "Please fill all fields",
    dashboard_title: "Dashboard",
    dashboard_sub: "Today's overview",
    total_emp: "Total Employees",
    checkins_today: "Check-ins Today",
    loc_alerts: "Location Alerts",
    pending_leaves: "Pending Leaves",
    today_activity: "Today's Activity",
    refresh: "↻ Refresh",
    loading: "Loading...",
    employee: "Employee",
    checkins: "Check-ins",
    emp_title: "Employees",
    emp_sub: "Manage your team",
    add_emp: "Add Employee",
    emp_birthday_week_aria: "Birthdays this week",
    emp_modal_add: "Add Employee",
    emp_modal_edit: "Edit Employee",
    save_employee: "Save Employee",
    emp_save_name_required: "Full name is required",
    emp_save_username_required: "Username is required",
    emp_save_password_required: "Password is required when adding an employee",
    emp_form_required_hint_add: "Fields marked with * are required: full name, username, and password. Other fields are optional.",
    emp_form_required_hint_edit: "Fields marked with * are required (name and username). Leave password blank to keep the current password.",
    all_emp: "All Employees",
    name: "Name",
    code: "Code",
    full_name: "Full Name",
    national_id: "National ID",
    salary: "Salary",
    hire_date: "Hire Date",
    birth_date: "Date of Birth",
    address: "Address",
    suggest_pwd: "🎲 Suggest",
    reset_pwd_title: "Reset Password",
    reset_pwd_for: "Reset password for",
    reset_pwd_btn: "Reset Password",
    reset_pwd_placeholder: "e.g. 8 digits or letters",
    suggest_pwd_hint: "Generate a random password",
    copy_pwd_hint: "Copy password",
    pwd_copied_toast: "Password copied to clipboard",
    job_title: "Job Title",
    phone: "Phone",
    device: "Device",
    status: "Status",
    email: "Email",
    actions: "Actions",
    att_title: "Attendance Reports",
    att_sub: "Track check-ins and location status",
    export_csv: "Export CSV",
    date: "Date",
    time: "Time",
    type: "Type",
    distance: "Distance",
    filter_all: "All Status",
    filter_alert: "Alerts Only",
    status_ok: "OK Only",
    search_placeholder: "Search by name or code...",
    att_emp_search_ph: "Search by name or employee code...",
    no_results: "No results found",
    no_records: "No records found matching filters",
    leave_no_new_requests: "No new requests",
    loading_employees: "Loading employees...",
    all_records: "Attendance Records",
    support_title: "Technical Support",
    support_reset_pwa: "Update app / clear cache",
    admin_logout: "Log out",
    admin_switch_employee: "Employee app — switch account",
    notification_settings: "Notification Settings",
    notification_email: "Leave Request Alerts Email",
    notification_email_master_hint: "This address is set only by Super Admin in the master panel (company settings). It receives leave and permission request emails so your company can use a dedicated inbox and avoid spam issues.",
    welcome_email_title: "Employee welcome / credentials email",
    welcome_email_help: "Sent when you create an employee or reset their password, if they have an email on file. Edit the text below; your last saved version becomes the default for this company.",
    save_welcome_template: "Save welcome template",
    welcome_template_saved: "Welcome email template saved",
    save_email: "Save Email",
    email_saved: "Notification email saved successfully",
    timezone_settings: "Timezone",
    timezone_cairo: "Cairo (+2)",
    timezone_riyadh: "Riyadh (+3)",
    timezone_dubai: "Dubai (+4)",
    timezone_saved: "Timezone updated successfully",
    nav_departments: "Departments",
    departments_title: "Manage Departments",
    departments_sub: "Organize your company structure",
    add_dept: "Add Department",
    all_dept: "All Departments",
    dept_employee_count: "Employees",
    department_name: "Department Name",
    department: "Department",
    no_dept: "No Department",
    all_depts: "All Departments (Filter)",
    annual: "Annual",
    casual: "Casual",
    sick: "Sick",
    rem: "Rem.",
    used: "Used",
    total: "Total",
    balance_summary: "Balance Summary",
    requested_days: "Requested Days",
    exceeds_balance: "⚠️ Exceeds Balance",
    net_days: "Net Days (Exc. Weekends)",
    leave_policy_missing_hint: "No leave policy is linked to this employee. Balances show as zero with used days from records; Sat–Sun count as weekend for the net-day estimate only. Link a policy on the employee profile.",
    status_pending: "Pending",
    status_approved: "Approved",
    status_rejected: "Rejected",
    nav_policies: "Leave & Deductions Policy",
    policies_sub: "Weekend days, leave balances, and manual-deduction fractions (no automatic balance deduction)",
    add_policy: "Add Policy",
    all_policies: "All Policies",
    policy_name: "Policy Name",
    policy_permission_monthly_limit: "Permission limit / month",
    policy_permission_monthly_limit_hint: "Leave empty for unlimited permissions.",
    permission_usage_month_summary: "Allowed {allowed} · Used {used} · Remaining {remaining}",
    permission_usage_month_unlimited: "Monthly permission limit: Unlimited · Used {used}",
    permission_usage_warn_limit_reached: "Monthly permission limit reached. You can still approve this request.",
    fd_employee_search_ph: "Search by name or code...",
    weekend_days: "Weekend Days",
    annual_leave: "Annual Leave",
    casual_leave: "Casual Leave",
    sick_leave: "Sick Leave",
    annual: "Annual",
    casual: "Casual",
    sick: "Sick",
    policy_deductions: "Deductions (full / half / quarter)",
    policy_deductions_section: "Day fractions for manual deductions (Final Report only)",
    deduct_hint: "These three numbers only define which day fractions the manager may choose when deducting manually from the Final Report screen. They do not deduct automatically — each deduction must be entered there by the manager.",
    deduct_full_day: "Full-day deduction",
    deduct_half_day: "Half-day deduction",
    deduct_quarter_day: "Quarter-day deduction",
    emp_leave_policy_label: "Leave & deductions policy",
    select_policy: "Select leave & deductions policy",
    all_dept: "All Departments",
    department_name: "Department Name",
    department: "Department",
    no_dept: "No Department",
    all_depts: "All Departments (Filter)",
    checkin_time: "Check-in Time",
    checkout_time: "Check-out Time",
    map_location: "Location",
    selfie: "Face Scan",
    no_selfie: "No Face Scan",
    selfie_msg: "📸 Face scan is captured automatically on scan",
    manual_att: "Manual Att.",
    emp_att_report_review: "Employee report",
    manual_att_title: "Manual Attendance",
    manual_att_type: "Type",
    manual_att_in: "Check In",
    manual_att_out: "Check Out",
    manual_att_date: "Date",
    manual_att_time: "Time",
    manual_att_success: "Attendance recorded successfully",
    register_attendance: "Register Attendance",
    select_employee: "Select Employee",
    select_employee_placeholder: "-- Select an employee --",
    work_note: "Work Note",
    external_note: "External",
    btn_save: "Save",
    btn_cancel: "Cancel",
    edit_note: "Edit Note",
    edit_att_record: "Edit attendance record",
    att_lat: "Latitude",
    att_lng: "Longitude",
    att_loc_status: "Location status",
    att_visit_details: "Visit / mission details",
    att_notes_field: "Notes",
    att_edit_hint: "Leave coordinates empty if not applicable. Distance and the map link follow latitude and longitude.",
    att_edit_success: "Attendance record updated",
    att_loc_ok: "OK",
    att_loc_alert: "Alert",
    att_not_found: "Record not found",
    edit_att_title_checkin: "Edit check-in record",
    edit_att_title_checkout: "Edit check-out record",
    att_pick_map: "Pick location on map",
    att_open_google: "Open in Google Maps",
    att_pick_branch: "Fill coordinates from branch",
    att_branch_none: "— None —",
    att_coords_required: "Enter latitude and longitude first, or pick on the map.",
    confirm_delete_record: "Are you sure you want to delete this record?",
    record_deleted: "Record deleted successfully",
    edit_leave: "Edit Leave Request",
    update_btn: "Update",
    nav_branches: "Branches",
    branches_title: "Manage Branches",
    branches_sub: "Define company locations and geofences",
    add_branch: "Add Branch",
    all_branches: "All Branches",
    branch_name: "Branch Name",
    branch: "Branch",
    no_branch: "No Branch",
    dept_status_badge_active: "Active",
    dept_status_badge_inactive: "Inactive",
    work_nature: "Work Nature",
    internal: "Internal (Office)",
    external: "External (Field)",
    radius: "Radius (m)",
    use_my_location: "Use My Location",
    branch_lat: "Latitude",
    branch_lng: "Longitude",
    branch_pick_map: "Pick on map",
    branch_open_google: "Open in Google Maps",
    branch_map_picker_title: "Set branch location on map",
    branch_map_picker_hint: "Map data © OpenStreetMap contributors. Tap to place the pin or drag it; then apply coordinates. Verify in Google Maps using the link.",
    branch_apply_coords: "Apply coordinates",
    confirm_delete_branch: "Delete this branch? Employees and records may lose this branch assignment.",
    branch_delete_step2_title: "Final confirmation",
    branch_delete_code_display_label: "Verification code (type it below)",
    branch_delete_type_code_hint: "To confirm deletion, enter the same 6-digit code shown above, then tap Delete branch.",
    branch_delete_code_label: "Enter the 6-digit code",
    branch_delete_intro_name: "Branch",
    branch_delete_confirm_btn: "Delete branch",
    branch_delete_code_wrong: "The code does not match. Enter the 6 digits shown above.",
    branch_deleted_toast: "Branch deleted",
    confirm_delete_dept: "Delete this department? Linked employees will become unassigned.",
    dept_delete_step2_title: "Final confirmation",
    dept_delete_code_display_label: "Verification code (type it below)",
    dept_delete_type_code_hint: "To confirm deletion, enter the same 6-digit code shown above, then tap Delete department.",
    dept_delete_code_label: "Enter the 6-digit code",
    dept_delete_intro_name: "Department",
    dept_delete_confirm_btn: "Delete department",
    dept_delete_code_wrong: "The code does not match. Enter the 6 digits shown above.",
    dept_deleted_toast: "Department deleted",
    fr_hide_days_label: "Hide from table",
    fr_hide_days_none: "Show all days",
    fr_hide_days_weekend: "Hide weekend days",
    fr_hide_days_official: "Hide official holidays",
    fr_hide_days_both: "Hide weekend & official holidays",
    fr_hide_days_empty: "No rows match this filter for the selected period.",
    permission: "Permission",
    permission_date: "Permission date",
    permission_no_balance: "Permission requests do not deduct annual/casual/sick balance.",
    permission_time_range: "Time",
    permission_time_invalid: "End time must be after start time.",
    loc_detected: "Location detected successfully",
    employee_photo: "Employee Photo",
    upload_file: "Upload File",
    camera: "Camera",
    capture_photo: "Capture Photo",
    take_photo: "Take Photo",
    all_status: "All Status",
    pending: "Pending",
    approved: "Approved",
    rejected: "Rejected",
    official_start: "Official Attendance",
    official_end: "Official Departure",
    all_requests: "All Requests",
    nav_final_report: "Monthly Timesheet",
    nav_final_decision: "Final decision",
    fd_title: "Final decision",
    fd_sub: "Review and apply suggested penalties from policy — leave balance debit or salary mark.",
    fd_col_toggle: "Columns",
    fd_exec_all: "Apply all",
    fd_stat_late: "Late (days flagged)",
    fd_stat_absent: "Absences",
    fd_stat_pending: "Pending apply",
    fd_stat_done: "Already applied",
    fd_col_issue: "Issue",
    fd_col_suggestion: "Suggestion",
    fd_col_date_day: "Date / day",
    fd_col_total_work_permission: "Work + permission",
    fd_empty_hint: "Select an employee to load the day register.",
    fd_sug_absent: "Absence — suggested leave debit",
    fd_min: "min",
    fd_fmt_leave_late: "Debit {days} {leave} ({mins} min late)",
    fd_fmt_leave_abs: "Debit {days} {leave}",
    fd_fmt_salary_abs: "Salary mark — {days} day(s)",
    fd_no_sug: "No penalty suggested",
    fd_nothing: "None",
    fd_exec_one: "Apply",
    fd_exec_salary: "Salary mark",
    fd_exec_leave: "Leave debit",
    fd_done: "Done",
    fd_reason_late: "Late {mins} min past {schIn} — policy: {tier}",
    fd_reason_absent_leave: "Absent (no check-in) — {leave} balance: {rem} day(s) remaining",
    fd_reason_absent_salary: "Absent (no check-in) — salary deduction",
    fd_reason_no_policy: "No system suggestion (within grace or no threshold matched) — manager may apply manually",
    fd_tier_quarter: "quarter day",
    fd_tier_half: "half day",
    fd_tier_full: "full day",
    fd_apply_btn: "Apply deduction",
    fd_modal_title: "Apply deduction",
    fd_modal_suggestion: "System suggestion",
    fd_balance_summary: "Leave balance summary",
    fd_bal_exhausted: "Balance = 0 — leave deduction will go negative",
    fd_all_exhausted_warn: "All leave balances are exhausted — salary deduction recommended",
    fd_section_salary: "Salary deduction (payroll mark)",
    fd_section_annual: "Annual leave",
    fd_section_casual: "Casual leave",
    fd_section_sick: "Sick leave",
    fd_bal_remaining: "{rem} / {total} remaining",
    fd_bal_zero_warn: "Balance exhausted",
    fd_deduct_full: "Full day",
    fd_deduct_half: "Half day",
    fd_deduct_quarter: "Quarter day",
    fd_modal_close: "Cancel",
    fd_stats_show: "Show stats",
    fd_stats_hide: "Hide stats",
    fd_stats_compact: "Compact",
    fd_stats_expand: "Expanded",
    fd_salary_disabled_policy: "Salary deduction is disabled by policy.",
    fd_salary_disabled_day: "Salary deduction is not allowed for this day status.",
    fd_salary_disabled_balance: "Salary deduction requires exhausted balances, or enable allow with balance in policy.",
    fd_confirm_exec_all: "Apply all pending suggestions for this period?",
    fd_confirm_remove_deduct: "Remove this deduction?",
    fd_toast_bulk: "{a} applied · {n} skipped (existing) · {m} skipped (no suggestion)",
    fd_delete_aria: "Remove deduction",
    fd_apply_suggested: "Apply system suggestion",
    fd_override_section: "Or choose a different decision:",
    fd_bal_warn_single: "Balance exhausted",
    nav_leave_settlement: "Leave settlement",
    nav_leave_settlement_short: "Settlement",
    nav_deductions_report: "Deductions log",
    dr_title: "Employee deductions report",
    dr_sub: "Lists leave-balance deductions and salary marks recorded in the selected period. Use Revert to remove one record (same as deleting from the final report).",
    dr_load: "Load report",
    dr_col_kind: "Kind",
    dr_col_detail: "Detail",
    dr_col_days: "Day units",
    dr_totals_title: "Totals in this period",
    dr_empty: "Select an employee and date range, then load.",
    dr_no_rows: "No deductions in this period for this employee.",
    dr_type_leave: "Leave balance",
    dr_type_salary: "Salary mark",
    dr_leave_sum: "Leave deductions (sum)",
    dr_salary_sum: "Salary marks (sum)",
    dr_revert: "Revert",
    dr_revert_confirm: "Remove this deduction from the system? This cannot be undone except by re-entering it.",
    fr_month_current: "Current month",
    fr_month_last: "Last month",
    fr_month_custom: "Custom range",
    policy_settlement_section: "Leave settlement (auto-suggest)",
    policy_settlement_hint: "Auto leave-balance suggestion applies to absence only. Other fields describe grace/thresholds for reference; managers enter lateness/early-leave deductions manually on settlement if your HR rules allow.",
    policy_grace_late: "Grace after start (minutes)",
    policy_grace_early: "Grace before end (minutes)",
    policy_late_q: "Late → quarter day (minutes)",
    policy_late_h: "Late → half day (minutes)",
    policy_late_f: "Late → full day (minutes)",
    policy_early_q: "Early leave → quarter (minutes)",
    policy_early_h: "Early leave → half (minutes)",
    policy_early_f: "Early leave → full (minutes)",
    policy_auto_lt: "Default leave type for auto-suggest",
    policy_auto_max: "Max day units per calendar day",
    policy_auto_combine: "Combine late + early minutes for one penalty",
    policy_auto_ignore: "Skip auto-suggest when day has these suggestion codes (comma-separated)",
    ls_title: "Leave balance settlement",
    ls_sub: "Preview matches the final report day rows. Apply leave deductions in one step; existing manual rows on edited dates are replaced when you save.",
    ls_auto_suggest_rule: "Automatic suggestion: debit leave balance only for absence (no attendance on a workday). Lateness, early leave, and incomplete checkout are not auto-suggested for leave—you may mark salary impact from the Final Report or choose leave type and days manually in this table.",
    ls_load_preview: "Load preview",
    ls_apply: "Apply deductions",
    ls_col_times: "Scheduled / actual",
    ls_col_system: "System suggestion",
    ls_col_days: "Deduct (days)",
    ls_existing_manual: "Current manual",
    ls_empty_hint: "Pick an employee and dates, then tap “Load preview”.",
    ls_summary_title: "Balances & draft deductions",
    ls_policy_missing: "This employee has no leave policy assigned. Suggestions may be limited — assign a policy under Employees or Policies.",
    ls_open_from_report: "Open settlement for this period",
    ls_reset_row: "Reset to system",
    ls_confirm_apply: "Apply {rows} deduction row(s)? Draft totals — annual: {a}, casual: {c}, sick: {s} (day units). Dates with cleared deductions will remove prior manual rows for those days.",
    ls_stale_warning: "Attendance may have changed since this preview was loaded. Reload preview before applying?",
    ls_no_valid_rows: "No rows to save: choose day units on at least one day, or clear existing manual rows.",
    ls_type_label: "Type",
    ls_balance_line: "{type}: remaining now {rem} — after this run {after} (this run −{draft})",
    final_report_title: "Employee Final Report",
    final_report_sub: "Attendance, location, face verification & leave — for supervision and development",
    select_employee: "Select Employee",
    select_month: "Select Month",
    generate_report: "Generate Report",
    print_report: "Print Report",
    fd_short_report_btn: "Short Report",
    fd_print_need_period: "Select start and end dates for the report.",
    first_in: "First In",
    last_out: "Last Out",
    status_present: "Present",
    status_absent: "Absent",
    status_leave: "Leave",
    status_official_holiday: "Official holiday",
    nav_official_holidays: "Official holidays",
    official_holidays_title: "Official holidays",
    official_holidays_sub: "Company or national holidays appear in the final report as non-working days (not absence) when there is no attendance and no approved leave.",
    official_holidays_list: "Holiday calendar",
    official_holiday_add: "Add holiday",
    official_holiday_edit: "Edit holiday",
    official_holiday_name: "Holiday name",
    official_holiday_start: "Start date",
    official_holiday_end: "End date (inclusive)",
    official_holiday_range_hint: "Same as start for one day, or pick the last day for a multi-day holiday.",
    official_holiday_range_saved: "Added {n} calendar day(s).",
    policy_salary_deduct_enabled: "Allow marking unpaid absence days for salary deduction (Final Report)",
    policy_salary_deduct_allow_balance: "Allow salary deduction even if the employee still has leave balance (requires “apply despite balance” when saving the mark)",
    btn_save_policy: "Save policy",
    policy_modal_intro: "Each section explains how the setting affects employees once this policy is assigned to them.",
    policy_sec_identity_title: "Policy name",
    policy_sec_identity_help: "The name is only a label for this bundle of rules. Nothing changes for employees until you assign this policy on their profile. You can keep several policies (e.g. office vs field) and move staff between them.",
    policy_sec_schedule_title: "Weekend and official hours",
    policy_sec_schedule_help: "Checked days count as weekend in reports (typically no “absent” expectation). Official start and end are compared to first check-in and last check-out to flag late, early leave, and missing checkout; they also drive grace minutes and settlement suggestions.",
    policy_sec_balances_title: "Leave balances (days per year)",
    policy_sec_balances_help: "These three numbers are the annual entitlement for annual, casual, and sick leave. Approved requests and confirmed manual deductions reduce the same balance. The app does not automatically reset balances at year-end—you adjust numbers if your HR rules require it.",
    policy_sec_deduct_title: "Manual deduction units",
    policy_sec_deduct_help: "When a manager deducts leave from the Final Report or Leave settlement, they pick a fraction of a day (e.g. 1, 0.5, 0.25). The three values here define what “one day”, “half day”, and “quarter day” mean in paid-day units. They do not deduct anything by themselves—only a manager action removes balance.",
    policy_sec_settlement_title: "Leave settlement (automatic suggestions)",
    policy_sec_settlement_help: "On the Leave settlement screen the system **automatically suggests a leave-balance debit only when the day is absent** (no check-in on a workday), using the default leave type and the full-day fraction above. Grace minutes, minute thresholds, combine-late/early, and ignore-codes do **not** drive automatic leave suggestions anymore—lateness and early leave are handled via salary marking or a **manual** leave deduction entered by the manager in the same settlement table. Those policy fields remain documented for transparency and possible future use.",
    policy_sub_late_thresholds: "Lateness (after grace)",
    policy_sub_early_thresholds: "Early departure (after grace)",
    policy_early_thresholds_note: "If left empty, defaults apply only as stored reference values; they do not drive automatic leave-balance suggestions (absence-only).",
    policy_fld_grace_late_help: "Used when building report flags (late vs on time), not for automatic leave-balance suggestions. Larger value = fewer minutes counted as late in the report.",
    policy_fld_grace_early_help: "Used when building report flags (early leave), not for automatic leave-balance suggestions. Larger value = fewer minutes counted as early in the report.",
    policy_fld_late_q_help: "Reference minutes (after grace) for HR documentation. Automatic leave-balance suggestion is absence-only; managers enter lateness deductions manually on settlement if allowed.",
    policy_fld_late_h_help: "Reference half-day threshold (minutes after grace). Not used for automatic leave suggestions under the absence-only rule.",
    policy_fld_late_f_help: "Reference full-day threshold (minutes after grace). Not used for automatic leave suggestions under the absence-only rule.",
    policy_fld_early_q_help: "Reference quarter-day threshold for early leave (minutes after grace). Not used for automatic leave suggestions.",
    policy_fld_early_h_help: "Reference half-day threshold for early leave. Not used for automatic leave suggestions.",
    policy_fld_early_f_help: "Reference full-day threshold for early leave. Not used for automatic leave suggestions.",
    policy_fld_auto_lt_help: "Leave type used when the system auto-suggests a deduction for an absent workday only.",
    policy_fld_auto_max_help: "Maximum day units suggested for that absent day (typically 1).",
    policy_fld_auto_combine_help: "Not used for automatic leave-balance suggestions in the current version (only absence is auto-suggested). Field kept for policy clarity.",
    policy_fld_auto_ignore_help: "Not used for automatic leave-balance suggestions in the current version. Flagged days are still visible in reports for manual review.",
    policy_sec_salary_title: "Salary marks (unpaid absence)",
    policy_sec_salary_help: "Controls whether the Final Report may mark a pure absence day for payroll as an unpaid day. This does not change leave balance by itself. Use the second option only if payroll rules allow marking unpaid days while the employee still has leave balance.",
    policy_fld_salary_enabled_help: "If disabled, managers cannot flag absence for salary deduction for employees on this policy in the Final Report.",
    policy_fld_salary_balance_help: "If enabled, payroll marking can proceed while leave balance remains; use with care so employees understand pay may be reduced despite available leave.",
    weekday_monday: "Monday",
    weekday_tuesday: "Tuesday",
    weekday_wednesday: "Wednesday",
    weekday_thursday: "Thursday",
    weekday_friday: "Friday",
    weekday_saturday: "Saturday",
    weekday_sunday: "Sunday",
    emp_clear_profile_photo: "Remove profile photo",
    confirm_clear_profile_photo: "Remove the saved profile photo from this employee’s file? The next check-in selfie can become the new profile photo.",
    fr_salary_section: "Payroll (unpaid day)",
    fr_salary_short: "Salary mark",
    fr_salary_btn: "Mark salary deduction",
    fr_salary_force_tag: "despite balance",
    fr_salary_modal_title: "Salary deduction (unpaid day)",
    fr_salary_modal_hint: "Flags an absent workday for payroll using the same day fractions as leave deductions. Does not change leave balance.",
    fr_salary_force_label: "Apply despite remaining leave balance (allowed by policy)",
    fr_salary_submit: "Save mark",
    fr_salary_deduct_total: "Salary marks (days)",
    year: "Year",
    branch_photo_col: "Photo",
    branch_photo_upload: "Branch photo (upload)",
    branch_photo_upload_hint: "Optional image stored on the server under uploads/branches/<company ID>/",
    emp_documents_label: "Employee documents",
    emp_documents_hint: "Contracts, IDs, certificates (PDF, Word, Excel, images). Max 12 MB per file. Stored under uploads/<company ID>/employee_documents/<employee ID>/",
    emp_documents_upload_btn: "Upload selected",
    emp_docs_save_first: "Save the employee first, then you can upload documents.",
    emp_docs_none: "No documents yet.",
    emp_docs_download: "Open",
    emp_docs_preview: "Preview (new tab)",
    emp_docs_new_tab_hint: "opens in new tab",
    emp_docs_col_doc: "Document",
    emp_docs_col_size: "Size",
    emp_docs_col_date: "Uploaded",
    emp_doc_edit_title: "Edit document label",
    emp_doc_edit_hint: "Changes the title shown in the list only; the stored file name on disk is unchanged.",
    emp_doc_edit_label: "Display name",
    emp_doc_edit_save: "Save",
    emp_docs_pick_files: "Please choose one or more files",
    emp_docs_popup_blocked: "Allow pop-ups for this site to open documents in a new tab.",
    official_holidays_empty: "No official holidays for this year.",
    notes: "Notes",
    day: "Day",
    select_employee_hint: "Please select an employee to generate the report",
    leave_start: "Start date",
    leave_end: "End date",
    leave_created: "Submitted on",
    fr_total_hours: "Total work hours",
    fr_late_days: "Late arrival days",
    fr_early_leave_days: "Early departure days",
    fr_loc_alerts_days: "Location alert days",
    fr_incomplete_days: "Incomplete days (no checkout)",
    fr_work_hours: "Work hrs",
    fr_location: "Location (in / out)",
    fr_selfie: "Face verify",
    fr_reg_method: "Registration",
    fr_field_day: "Day type",
    fr_loc_ok: "OK",
    fr_loc_alert: "Alert",
    fr_reg_qr: "QR",
    fr_reg_direct: "Direct",
    fr_reg_manual: "Manual",
    fr_policy: "Leave & deductions policy",
    nav_leave_overview: "Leave overview",
    leave_overview_title: "Leave balances overview",
    leave_overview_sub: "Per employee: totals, approved usage, admin deductions, and unpaid absence workdays for the selected year",
    leave_overview_year: "Year",
    leave_overview_absent: "Absent (workdays)",
    leave_overview_manual_total: "Admin deductions (days)",
    leave_overview_type_cell: "Rem. / total (approved · admin)",
    leave_overview_appr: "Approved",
    leave_overview_man: "Admin deduct",
    lov_print: "Print report",
    lov_filters_title: "Report filters",
    lov_search: "Search",
    lov_no_branch: "No branch",
    lov_no_dept: "No department",
    lov_all_policies: "All policies",
    lov_no_policy: "No policy",
    lov_policy_presence: "Policy assigned",
    lov_with_policy: "With policy",
    lov_without_policy: "Without policy",
    lov_sort: "Sort by",
    lov_sort_name_asc: "Name A–Z",
    lov_sort_name_desc: "Name Z–A",
    lov_sort_code: "Employee code",
    lov_sort_absent: "Absent days (high first)",
    lov_sort_ann_rem_low: "Annual remaining (low first)",
    lov_sort_ann_rem_high: "Annual remaining (high first)",
    lov_sort_manual: "Admin deductions (high first)",
    lov_print_year: "Year",
    lov_print_company: "Company",
    lov_print_filters: "Filters",
    lov_all: "All",
    fr_leave_deduct: "Deduction",
    fr_deduct_opt_full: "1 day",
    fr_deduct_opt_half: "½ day",
    fr_deduct_opt_quarter: "¼ day",
    fr_deduct_btn: "Deduct leave",
    fr_deduct_modal_title: "Deduct from leave balance",
    fr_deduct_amount: "Day fraction",
    fr_deduct_submit: "Apply deduction",
    fr_balance_year: "Leave balance year",
    fr_suggestions_hint: "Suggested review points only — not an automatic penalty.",
    fr_sug_absent: "Absent (workday)",
    fr_sug_late: "Late check-in",
    fr_sug_early_leave: "Early check-out",
    fr_sug_incomplete_checkout: "No check-out",
    fr_sug_overtime: "Long stay (possible overtime)",
    fr_sug_location_alert: "Location alert",
    fr_sug_distance_far: "Far from expected location",
    fr_sug_external_no_mission: "Field day — no mission details",
    fr_issues_panel_title: "Problem days — all employees",
    fr_issues_panel_hint: "Uses the same date range as the report above. Check one or more suggestion types, then load. Opening a row switches to that employee’s monthly report.",
    fr_issues_load: "Load list",
    fr_issues_empty: "Choose filters (optional) and tap “Load list”.",
    fr_issues_suggestions_col: "Suggestions",
    fr_issues_open: "Open in report",
    fr_issues_emp_missing: "Could not select this employee in the list (inactive or removed).",
    fr_table_density: "Table view",
    fr_density_full: "Full columns",
    fr_density_compact: "Compact (faster review)",
    fr_bulk_dept_label: "Bulk: department",
    fr_apply_settlement_employee: "Apply leave settlement (this employee)",
    fr_bulk_apply_btn: "Apply all suggested deductions (everyone)",
    fr_settlement_toolbar_hint: "Settlement editor mirrors the Leave settlement tab. Late deductions use grace minutes and late thresholds from the employee’s policy (leave type follows the policy auto-deduct setting). Skips days that already have a manual deduction.",
    fr_apply_late_bulk: "Apply late deductions (this employee)",
    fr_apply_late_row: "Apply late deduction",
    fr_late_bulk_confirm: "Apply leave-balance deductions for every eligible late day in this period for this employee? Days with an existing manual deduction are skipped.",
    fr_late_bulk_done: "Applied: {a}. Skipped (no rule): {n}. Skipped (already has deduction): {m}.",
    fr_print_caption_leave: "Leave balance deduction",
    fr_print_caption_salary: "Salary deduction",
    fr_print_sep: "·",
    fr_settlement_editor: "Settlement (same as tab)",
    fr_need_report: "Generate the report first.",
    fr_bulk_none: "No automatic rows to apply for this period/filter.",
    fr_bulk_confirm: "Apply {n} automatic leave deduction(s) across {emp} employee(s)? Existing manual rows are skipped.",
    fr_secondary_stats_toggle: "More statistics (hours, late, alerts…)",
    fr_reason_bulk: "Bulk policy settlement",
    fr_screen_table_title: "Attendance register — system view (review, deduct, settle)",
    fr_print_doc_title: "Official monthly attendance record",
    fr_print_legal_statement: "This document lists each day in the selected period, the official schedule (expected in/out), and the actual check-in and check-out recorded in the HR system. It is produced for payroll, leave accounting, and lawful supervision. Any dispute must be cross-checked with the underlying attendance evidence.",
    fr_print_recap_title: "Employee and period (for management approval)",
    fr_print_company: "Company",
    fr_print_final_decision_title: "Final administrative decision",
    fr_print_final_decision_body: "Based on the register above, this record may be relied on for HR decisions (payroll, leave balance, discipline) in line with company policy and applicable law, unless amended in writing by an authorized signatory.",
    fr_print_gm_signature: "General Manager — name & signature",
    fr_print_hr_manager: "HR / direct manager — signature",
    fr_print_employee_ack: "Employee — read and acknowledged",
    fr_print_stamp: "Stamp (if applicable)",
    fr_print_date_label: "Date",
    fr_print_generated: "Document generated",
    fr_print_sub_calendar: "Calendar",
    fr_print_sub_official_vs_actual_in: "Official time → recorded check-in",
    fr_print_sub_official_vs_actual_out: "Official time → recorded check-out",
    fr_print_sub_other: "Other details",
    admin_push_phone_hint: "For lock-screen alerts: use HTTPS, install the admin app (Add to Home Screen), open it once, enable push above, and allow notifications in browser and Android/iOS settings. iOS needs the app on the Home Screen for Web Push.",
    pwa_install_admin: "Install admin app",
    pwa_install_hint: "Install app on this device",
    pwa_install_section: "Install app (PWA)",
    pwa_install_help: "Install the admin portal as an app for quicker access and optional push alerts.",
    pwa_already_installed: "App is already installed",
    pwa_install_manual_hint: "Open this admin page in a normal Chrome tab (not inside the employee app), then Menu ⋮ → Install app. After updates, remove old shortcuts and reinstall if needed.",
    admin_alerts_title: "Account alerts",
    admin_notif_empty: "No alerts yet.",
    mark_all_read: "Mark all read",
    mark_read: "Mark as read",
    delete_notif: "Delete",
    delete_all: "Delete all",
    notif_filter_all: "All",
    notif_filter_unread: "Unread",
    notif_filter_archived: "Archived",
    notif_unread_count: "{n} unread",
    notif_read_done: "Notification marked as read",
    notif_delete_done: "Notification archived",
    notif_delete_all_done: "All notifications archived",
    confirm_delete_all_notif: "Archive all notifications?",
    delete_permanent: "Delete permanently",
    delete_permanent_all: "Delete permanently all",
    confirm_delete_perm_notif: "Delete this archived notification permanently?",
    confirm_delete_perm_all_notif: "Delete all archived notifications permanently?",
    notif_purge_done: "Archived notification deleted permanently",
    notif_purge_all_done: "All archived notifications deleted permanently",
    admin_push_section: "Mobile push alerts",
    admin_push_enable: "Enable push on this device",
    admin_push_disable: "Turn off push on this device",
    admin_push_server_off: "Web Push keys are missing. Add VAPID public + private in Master (Outbound mail → Web Push) or set HRPRO_VAPID_* on the server.",
    admin_push_need_composer: "VAPID keys are set, but the PHP library is missing. On the server run: composer install in the /hr folder (minishlink/web-push). Subscription was still saved; alerts will work after installing.",
    admin_push_need_https: "Push requires HTTPS (or localhost).",
    admin_push_subscribed: "Push enabled for this device",
    admin_push_denied: "Notifications were blocked. Enable them in browser settings.",
    nav_company_news: "Employee news",
    company_news_title: "Employee news ticker",
    company_news_sub: "Scrolling lines at the top of the employee app; publishing sends push to employees who enabled news alerts.",
    company_news_new: "Add news line",
    company_news_message: "Message",
    company_news_sort: "Priority",
    company_news_active: "Visible on employee app",
    company_news_add: "Publish",
    company_news_list: "All news lines",
    company_news_empty: "No news lines yet.",
    company_news_hide: "Hide",
    company_news_show: "Show",
    pwa_dual_hint: "Each app has a different manifest id, scope, and start URL so Chrome can install both. Install from a normal tab if the other PWA is already open in standalone.",
    banner_trial: "Trial mode: your company is on a trial subscription. Contact your provider to switch to a full plan when ready.",
    banner_close_text: "Subscription access ends on {date} ({n} calendar days remaining).",
    cred_modal_title: "Login Credentials",
    invite_message_preview: "Message preview",
    username: "Username",
    password: "Password",
    app_link: "App Link",
    instructions: "Instructions",
    btn_copy: "Copy Message",
    wa_msg_template: "Hello {name},\nWelcome to the team!\nYour login credentials:\nUsername: {user}\nPassword: {pass}\nApp link: {link}\n{instructions}\n\n*Note: Please change your password after first login.*"
  },
  ar: {
    nav_dashboard: "لوحة التحكم",
    nav_employees: "الموظفين",
    nav_contacts: "جهات الاتصال",
    contacts_title: "جهات الاتصال",
    contacts_sub: "دليل سريع — اتصل أو راسِل الموظفين",
    contacts_refresh: "↻ تحديث",
    contacts_search_ph: "ابحث بالاسم أو الهاتف أو الفرع أو القسم...",
    contacts_loading: "جارٍ التحميل...",
    contacts_empty: "لا توجد نتائج",
    contacts_meta: "{shown} / {total}",
    contacts_all_branches: "كل الفروع",
    contacts_all_depts: "كل الأقسام",
    contacts_org_branch: "الفرع:",
    contacts_org_dept: "القسم:",
    contacts_org_job: "المسمى الوظيفي:",
    contacts_mail_btn: "إرسال بريد",
    contacts_phone_btn: "اتصال",
    contacts_wa_btn: "واتساب",
    nav_attendance: "الحضور والانصراف",
    nav_leave: "إدارة طلبات الموظفين",
    nav_leave_short: "الطلبات",
    leave_sub_admin: "مراجعة الطلبات (إجازة، إذن، مأمورية) أو التسجيل بالنيابة عن الموظف",
    admin_add_leave: "تسجيل إجازة لموظف",
    admin_add_leave_title: "تسجيل طلب إجازة لموظف",
    leave_request_kind: "نوع الطلب",
    leave_kind_leave: "إجازة",
    leave_kind_permission: "إذن (فترة زمنية)",
    leave_kind_mission: "مأمورية",
    permission_mission_label: "مأمورية",
    leave_title: "مراجعة طلبات الموظفين",
    permission_mission_quota_exempt: "المأمورية لا تُخصم من رصيد الأذون الشهري (تُعامل كعمل اعتيادي).",
    leave_toast_approved: "تمت الموافقة على الطلب",
    leave_toast_rejected: "تم رفض الطلب",
    unknown_error: "حدث خطأ غير متوقع",
    admin_leave_submit: "إرسال الطلب",
    admin_leave_pick_employee: "يرجى اختيار الموظف",
    nav_qr: "توليد QR",
    company_admin: "مدير الشركة",
    account_settings: "إعدادات الحساب",
    language_pref: "تفضيلات اللغة",
    change_pwd: "تغيير كلمة المرور",
    current_pwd: "كلمة المرور الحالية",
    new_pwd: "كلمة المرور الجديدة",
    confirm_new_pwd: "تأكيد كلمة المرور الجديدة",
    update_pwd_btn: "تحديث كلمة المرور",
    company_logo: "لوجو الشركة",
    mobile_app_url_label: "رابط تطبيق الموبايل للموظفين",
    mobile_app_url_help: "الرابط الذي يفتح به الموظفون تطبيق الويب (PWA) على الجوال — مثلاً صفحة دخول الموظف. يمكنك إنشاء وطباعة نشرة بها شعار الشركة وباركود QR فقط. احفظ الرابط أولاً إن كنت عدّلته.",
    save_mobile_app_url: "حفظ الرابط",
    print_mobile_app_qr: "طباعة QR (شعار + باركود)",
    mobile_app_url_saved: "تم حفظ رابط التطبيق",
    mobile_app_url_empty: "أدخل رابطاً صحيحاً أولاً (ثم حفظ إن لزم).",
    mobile_app_url_print_popup: "اسمح بعرض النافذة المنبثقة للطباعة، أو اطبع من نفس التبويب من المتصفح.",
    mobile_app_qr_poster_title: "تحميل التطبيق",
    mobile_app_qr_poster_note: "برجاء تحميل تطبيق التالي باستخدام كاميرا هاتفك ومسح رمز QR.",
    clear_filters: "مسح الفلاتر",
    sidebar_toggle_collapse: "إخفاء القائمة الجانبية (عرض كامل)",
    sidebar_toggle_expand: "إظهار القائمة الجانبية",
    bm_col_status: "الحضور",
    bm_status_present: "حاضر (تسجيل دخول)",
    bm_status_absent: "غائب (لا بصمة)",
    bm_filter_all: "الكل",
    bm_filter_present: "حاضرون فقط",
    bm_filter_absent: "غائبون فقط",
    bm_req_pending: "قيد الموافقة",
    bm_req_from: "من",
    bm_req_to: "إلى",
    dashboard_go_today: "اليوم",
    dashboard_pick_date: "اختر تاريخاً لتحليل ذلك اليوم",
    dashboard_prev_day: "اليوم السابق",
    dashboard_pending_live_hint: "عدد الطلبات المعلّقة يعكس الطابور الحالي وليس التاريخ المعروض.",
    branch_unassigned: "بدون فرع",
    emp_by_branch_modal_title: "الموظفون حسب الفرع",
    emp_by_branch_col_branch: "الفرع",
    emp_by_branch_col_count: "عدد الموظفين النشطين",
    emp_by_branch_hint: "عرض العدد لكل فرع",
    emp_by_branch_no_data: "لا توجد بيانات فروع بعد. حدّث لوحة التحكم.",
    ledger_title: "كشف حساب الإجازة",
    ledger_leave_type: "نوع الإجازة",
    ledger_from_year: "من سنة",
    ledger_to_year: "إلى سنة",
    ledger_load: "تحميل",
    ledger_col_actions: "كشف",
    ledger_open: "كشف حساب",
    ledger_col_kind: "البند",
    ledger_col_delta: "المقدار",
    ledger_col_balance: "الرصيد",
    ledger_kind_accrual: "إضافة رصيد السنة",
    ledger_kind_leave_day: "خصم إجازة معتمدة (يوم)",
    ledger_kind_manual: "خصم يدوي",
    ledger_empty_hint: "اضبط السنوات ثم اضغط تحميل.",
    status_active: "نشط",
    status_inactive: "معطل",
    emp_account_active_label: "السماح بتسجيل الدخول (حساب نشط)",
    emp_account_active_hint: "عند إيقافه لا يستطيع الموظف الدخول للتطبيق حتى يُعاد تفعيل الحساب.",
    emp_suspend_account: "إيقاف الحساب",
    emp_restore_account: "تفعيل الحساب",
    emp_suspend_confirm: "إيقاف تسجيل الدخول للموظف {name}؟ لن يتمكن من الدخول حتى تفعيل الحساب مرة أخرى.",
    emp_restore_confirm: "إعادة تفعيل تسجيل الدخول للموظف {name}؟",
    emp_suspended_toast: "تم إيقاف الحساب",
    emp_restored_toast: "تم تفعيل الحساب",
    edit: "تعديل",
    delete: "حذف",
    message: "رسالة",
    reset_pwd: "كلمة المرور",
    reset_device: "فك الارتباط",
    edit_desc: "تعديل بيانات الموظف وإعداداته",
    msg_desc: "إرسال رسالة تنبيه مباشرة للموظف",
    pwd_desc: "تغيير كلمة مرور دخول الموظف",
    manual_desc: "تسجيل حضور أو انصراف يدوي للموظف",
    device_desc: "فك ارتباط الجهاز الحالي بالحساب",
    delete_desc: "حذف الموظف نهائياً من النظام",
    bound: "مرتبط",
    unbound: "غير مرتبط",
    close: "إغلاق",
    lang_updated: "تم تحديث اللغة",
    pwd_updated: "تم تغيير كلمة المرور بنجاح",
    pwd_mismatch: "كلمة المرور الجديدة غير متطابقة",
    fill_fields: "الرجاء إدخال جميع الحقول",
    dashboard_title: "لوحة التحكم",
    dashboard_sub: "نظرة عامة لليوم",
    total_emp: "إجمالي الموظفين",
    checkins_today: "حضور اليوم",
    loc_alerts: "تنبيهات الموقع",
    pending_leaves: "طلبات معلقة",
    today_activity: "نشاط اليوم",
    refresh: "↻ تحديث",
    loading: "جارٍ التحميل...",
    employee: "الموظف",
    checkins: "مرات الحضور",
    emp_title: "الموظفين",
    emp_sub: "إدارة فريق العمل",
    add_emp: "إضافة موظف",
    emp_birthday_week_aria: "أعياد ميلاد هذا الأسبوع",
    emp_modal_add: "إضافة موظف",
    emp_modal_edit: "تعديل بيانات الموظف",
    save_employee: "حفظ الموظف",
    emp_save_name_required: "الاسم الكامل مطلوب",
    emp_save_username_required: "اسم المستخدم مطلوب",
    emp_save_password_required: "كلمة المرور مطلوبة عند إضافة موظف جديد",
    emp_form_required_hint_add: "الحقول المعلّمة بـ (*) إلزامية: الاسم الكامل، اسم المستخدم، وكلمة المرور. باقي الحقول اختيارية.",
    emp_form_required_hint_edit: "الحقول المعلّمة بـ (*) إلزامية (الاسم واسم المستخدم). اترك كلمة المرور فارغة للإبقاء على كلمة المرور الحالية.",
    all_emp: "جميع الموظفين",
    name: "الاسم",
    code: "الكود",
    full_name: "الاسم الكامل",
    national_id: "رقم الهوية الوطنية",
    salary: "الراتب",
    hire_date: "تاريخ التعيين",
    birth_date: "تاريخ الميلاد",
    address: "العنوان",
    suggest_pwd: "🎲 اقتراح",
    reset_pwd_title: "إعادة تعيين كلمة المرور",
    reset_pwd_for: "إعادة تعيين كلمة المرور لـ",
    reset_pwd_btn: "تعيين كلمة المرور",
    reset_pwd_placeholder: "8 أرقام أو نص (حسب السياسة)",
    suggest_pwd_hint: "توليد كلمة مرور عشوائية",
    copy_pwd_hint: "نسخ كلمة المرور",
    pwd_copied_toast: "تم نسخ كلمة المرور",
    job_title: "المسمى الوظيفي",
    phone: "رقم الهاتف",
    device: "الجهاز",
    status: "الحالة",
    email: "البريد الإلكتروني",
    actions: "الإجراءات",
    att_title: "تقارير الحضور",
    att_sub: "تتبع الحضور وتنبيهات الموقع",
    export_csv: "تصدير CSV",
    date: "التاريخ",
    time: "الوقت",
    type: "النوع",
    distance: "المسافة",
    filter_all: "كل الحالات",
    filter_alert: "تنبيهات فقط",
    status_ok: "موافق فقط",
    search_placeholder: "ابحث بالاسم أو الكود...",
    att_emp_search_ph: "ابحث بالاسم أو رقم الموظف...",
    no_results: "لا توجد نتائج",
    no_records: "لا توجد سجلات مطابقة للفلاتر",
    leave_no_new_requests: "لا توجد طلبات جديدة",
    loading_employees: "جاري تحميل الموظفين...",
    all_records: "سجلات الحضور",
    support_title: "الدعم الفني",
    support_reset_pwa: "تحديث التطبيق / تنظيف الكاش",
    admin_logout: "خروج من الحساب",
    admin_switch_employee: "تطبيق الموظف / تبديل الحساب",
    notification_settings: "إعدادات الإشعارات",
    notification_email: "بريد استقبال التنبيهات (للإجازات)",
    notification_email_master_hint: "يُضبط هذا البريد من لوحة Super Admin فقط (إعدادات الشركة). يستقبل رسائل طلبات الإجازة والإذن؛ استخدم صندوقاً مخصصاً لتقليل تصنيف الرسائل كـ spam.",
    welcome_email_title: "رسالة ترحيب / بيانات الدخول للموظف",
    welcome_email_help: "تُرسل عند إنشاء موظف جديد أو عند إعادة تعيين كلمة المرور من الإدارة، إذا وُجد بريد للموظف. عدّل النص أدناه؛ آخر نسخة تحفظها تصبح القالب الافتراضي لهذه الشركة.",
    save_welcome_template: "حفظ قالب الرسالة",
    welcome_template_saved: "تم حفظ قالب رسالة الترحيب",
    save_email: "حفظ البريد",
    email_saved: "تم حفظ بريد الإشعارات بنجاح",
    timezone_settings: "المنطقة الزمنية (Timezone)",
    timezone_cairo: "القاهرة (+2)",
    timezone_riyadh: "الرياض (+3)",
    timezone_dubai: "دبي (+4)",
    timezone_saved: "تم تحديث المنطقة الزمنية بنجاح",
    annual: "سنوية",
    casual: "عارضة",
    sick: "مرضية",
    rem: "المتبقي",
    used: "المستهلك",
    total: "الإجمالي",
    balance_summary: "ملخص الرصيد الحالي",
    requested_days: "الأيام المطلوبة",
    exceeds_balance: "⚠️ يتجاوز الرصيد",
    net_days: "الأيام الفعلية (بدور العطلات)",
    leave_policy_missing_hint: "لا توجد سياسة إجازات مرتبطة بهذا الموظف. يظهر الرصيد كصفر مع الاستهلاك من السجلات؛ السبت والأحد يُعتمدان كعطلة لتقدير الأيام الفعلية فقط. اربط سياسة من بطاقة الموظف.",
    status_pending: "قيد الانتظار",
    status_approved: "مقبول",
    status_rejected: "مرفوض",
    nav_departments: "الأقسام",
    departments_title: "إدارة الأقسام",
    departments_sub: "تنظيم هيكل الشركة",
    add_dept: "إضافة قسم",
    all_dept: "جميع الأقسام",
    dept_employee_count: "عدد الموظفين",
    department_name: "اسم القسم",
    department: "القسم",
    no_dept: "بدون قسم",
    all_depts: "كل الأقسام",
    nav_policies: "سياسة الإجازات والخصومات",
    policies_sub: "أيام عطلة نهاية الأسبوع، أرصدة الإجازات، وأجزاء اليوم المسموح خصمها يدوياً (بدون خصم تلقائي من الرصيد)",
    add_policy: "إضافة سياسة",
    all_policies: "جميع السياسات",
    policy_name: "اسم السياسة",
    policy_permission_monthly_limit: "حد الأذونات / الشهر",
    policy_permission_monthly_limit_hint: "اتركه فارغًا ليكون غير محدود.",
    permission_usage_month_summary: "المسموح {allowed} · المستخدم {used} · المتبقي {remaining}",
    permission_usage_month_unlimited: "حد الأذونات الشهري: غير محدود · المستخدم {used}",
    permission_usage_warn_limit_reached: "تم الوصول إلى الحد الشهري للأذونات. ما زال يمكن الموافقة على الطلب.",
    fd_employee_search_ph: "ابحث بالاسم أو الكود...",
    weekend_days: "العطلة الأسبوعية",
    annual_leave: "الإجازة السنوية",
    casual_leave: "الإجازة العارضة",
    sick_leave: "الإجازة المرضية",
    annual: "سنوي",
    casual: "عارضة",
    sick: "مرضي",
    policy_deductions: "الخصومات (يوم / نصف / ربع)",
    policy_deductions_section: "أجزاء اليوم للخصم اليدوي (من التقرير الختامي فقط)",
    deduct_hint: "هذه القيم تحدد فقط أجزاء اليوم التي يستطيع المدير اختيارها عند الخصم يدوياً من شاشة التقرير الختامي. لا يُخصم شيء تلقائياً من رصيد الموظف — كل عملية خصم يجب أن يدخلها المدير من تلك الشاشة.",
    deduct_full_day: "خصم يوم كامل",
    deduct_half_day: "خصم نصف يوم",
    deduct_quarter_day: "خصم ربع يوم",
    emp_leave_policy_label: "سياسة الإجازات والخصومات",
    select_policy: "اختر سياسة الإجازات والخصومات",
    all_dept: "جميع الأقسام",
    department_name: "اسم القسم",
    department: "القسم",
    no_dept: "بدون قسم",
    all_depts: "كل الأقسام",
    checkin_time: "وقت الدخول",
    checkout_time: "وقت الخروج",
    map_location: "موقع البصمة",
    selfie: "بصمة الوجه",
    no_selfie: "بدون بصمة وجه",
    selfie_msg: "📸 يتم تسجيل بصمة الوجه تلقائياً عند المسح",
    manual_att: "حضور يدوي",
    emp_att_report_review: "تقرير الموظف",
    manual_att_title: "تسجيل حضور يدوي",
    manual_att_type: "نوع الحركة",
    manual_att_in: "حضور",
    manual_att_out: "انصراف",
    manual_att_date: "التاريخ",
    manual_att_time: "الوقت",
    manual_att_success: "تم تسجيل الحضور بنجاح",
    register_attendance: "تسجيل حضور / انصراف",
    select_employee: "اختر الموظف",
    select_employee_placeholder: "-- اختر الموظف --",
    work_note: "ملاحظة العمل",
    external_note: "عمل خارجي",
    btn_save: "حفظ",
    btn_cancel: "إلغاء",
    edit_note: "تعديل الملاحظة",
    edit_att_record: "تعديل سجل الحضور",
    att_lat: "خط العرض (Latitude)",
    att_lng: "خط الطول (Longitude)",
    att_loc_status: "حالة الموقع",
    att_visit_details: "تفاصيل المأمورية / الزيارة",
    att_notes_field: "ملاحظات",
    att_edit_hint: "اترك الإحداثيات فارغة إن لم تنطبق. المسافة ورابط الخريطة يتبعان خط العرض والطول.",
    att_edit_success: "تم تحديث سجل الحضور",
    att_loc_ok: "طبيعي",
    att_loc_alert: "تنبيه",
    att_not_found: "السجل غير موجود",
    edit_att_title_checkin: "تعديل سجل الحضور",
    edit_att_title_checkout: "تعديل سجل الانصراف",
    att_pick_map: "تحديد الموقع على الخريطة",
    att_open_google: "فتح في خرائط Google",
    att_pick_branch: "ملء الإحداثيات من فرع",
    att_branch_none: "— بدون —",
    att_coords_required: "أدخل خط العرض والطول أولاً، أو حدد الموقع على الخريطة.",
    confirm_delete_record: "هل أنت متأكد من حذف هذا السجل؟",
    record_deleted: "تم حذف السجل بنجاح",
    edit_leave: "تعديل طلب إجازة",
    update_btn: "تحديث",
    nav_branches: "الفروع",
    branches_title: "إدارة الفروع",
    branches_sub: "تحديد مواقع الفروع والنطاق الجغرافي",
    add_branch: "إضافة فرع",
    all_branches: "جميع الفروع",
    branch_name: "اسم الفرع",
    branch: "الفرع",
    no_branch: "بدون فرع",
    dept_status_badge_active: "نشط",
    dept_status_badge_inactive: "غير نشط",
    work_nature: "طبيعة العمل",
    internal: "داخلي (مكتبي)",
    external: "خارجي (ميداني)",
    radius: "نطاق الحرم (م)",
    use_my_location: "استخدم موقعي الحالي",
    branch_lat: "خط العرض",
    branch_lng: "خط الطول",
    branch_pick_map: "تحديد الموقع على الخريطة",
    branch_open_google: "فتح في خرائط Google",
    branch_map_picker_title: "تحديد موقع الفرع على الخريطة",
    branch_map_picker_hint: "بيانات الخريطة © مساهمو OpenStreetMap. انقر لوضع الدبوس أو اسحبه ثم «تطبيق الإحداثيات». للتحقق افتح رابط خرائط Google.",
    branch_apply_coords: "تطبيق الإحداثيات",
    confirm_delete_branch: "حذف هذا الفرع؟ قد يفقد الموظفون والسجلات ارتباطهم بهذا الفرع.",
    branch_delete_step2_title: "تأكيد نهائي",
    branch_delete_code_display_label: "رمز التحقق (اكتبه في المربع أدناه)",
    branch_delete_type_code_hint: "لتأكيد الحذف، أدخل نفس الرقم المكوّن من 6 أرقام الظاهر أعلاه، ثم اضغط «حذف الفرع».",
    branch_delete_code_label: "أدخل الرمز المكوّن من 6 أرقام",
    branch_delete_intro_name: "الفرع",
    branch_delete_confirm_btn: "حذف الفرع",
    branch_delete_code_wrong: "الرمز غير مطابق. أدخل الستة أرقام الظاهرة أعلاه.",
    branch_deleted_toast: "تم حذف الفرع",
    confirm_delete_dept: "حذف هذا القسم؟ سيصبح الموظفون المرتبطون به بدون قسم.",
    dept_delete_step2_title: "تأكيد حذف القسم",
    dept_delete_code_display_label: "رمز التحقق (اكتبه في المربع أدناه)",
    dept_delete_type_code_hint: "لتأكيد الحذف، أدخل نفس الرقم المكوّن من 6 أرقام الظاهر أعلاه، ثم اضغط «حذف القسم».",
    dept_delete_code_label: "أدخل الرمز المكوّن من 6 أرقام",
    dept_delete_intro_name: "القسم",
    dept_delete_confirm_btn: "حذف القسم",
    dept_delete_code_wrong: "الرمز غير مطابق. أدخل الستة أرقام الظاهرة أعلاه.",
    dept_deleted_toast: "تم حذف القسم",
    fr_hide_days_label: "إخفاء من الجدول",
    fr_hide_days_none: "عرض كل الأيام",
    fr_hide_days_weekend: "إخفاء أيام عطلة الأسبوع",
    fr_hide_days_official: "إخفاء الإجازات الرسمية",
    fr_hide_days_both: "إخفاء عطلة الأسبوع والرسمية",
    fr_hide_days_empty: "لا توجد صفوف تطابق هذا الإخفاء للفترة المختارة.",
    permission: "إذن",
    permission_date: "تاريخ الإذن",
    permission_no_balance: "طلب الإذن لا يخصم رصيد الإجازات السنوية / العارضة / المرضية.",
    permission_time_range: "الوقت",
    permission_time_invalid: "وقت النهاية يجب أن يكون بعد وقت البداية.",
    loc_detected: "تم تحديد الموقع بنجاح",
    employee_photo: "صورة الموظف",
    upload_file: "رفع ملف",
    camera: "الكاميرا",
    capture_photo: "التقاط صورة",
    take_photo: "التقاط",
    branch_monitor: "مراقبة الفروع",
    branch_active: "فرع نشط",
    branch_inactive: "فرع غير نشط",
    attendance_count: "الحضور اليوم",
    attended: "حضر",
    not_attended: "لم يحضر",
    employee_status_report: "تقرير حالة الموظفين",
    on_leave: "إجازة / إذن",
    all_status: "كل الحالات",
    pending: "معلق",
    approved: "مقبول",
    rejected: "مرفوض",
    official_start: "الحضور الرسمي",
    official_end: "الانصراف الرسمي",
    all_requests: "كافة الطلبات",
    nav_final_report: "التقرير الختامي",
    nav_final_decision: "القرار الختامي",
    fd_title: "القرار الختامي",
    fd_sub: "مراجعة وتنفيذ العقوبات المقترحة حسب السياسة — خصم من رصيد الإجازة أو تمييز راتب.",
    fd_col_toggle: "إظهار الأعمدة",
    fd_exec_all: "تنفيذ الكل",
    fd_stat_late: "أيام فيها تأخير",
    fd_stat_absent: "غيابات",
    fd_stat_pending: "بانتظار التنفيذ",
    fd_stat_done: "تم التنفيذ",
    fd_col_issue: "المشكلة",
    fd_col_suggestion: "الاقتراح",
    fd_col_date_day: "التاريخ / اليوم",
    fd_col_total_work_permission: "ساعات العمل + الإذن",
    fd_empty_hint: "اختر موظفاً لعرض سجل الأيام.",
    fd_sug_absent: "غياب — اقتراح خصم إجازة",
    fd_min: "د",
    fd_fmt_leave_late: "خصم {days} {leave} ({mins} د تأخير)",
    fd_fmt_leave_abs: "خصم {days} {leave}",
    fd_fmt_salary_abs: "تمييز راتب — {days} يوماً",
    fd_no_sug: "لا يوجد اقتراح عقوبة",
    fd_nothing: "لا شيء",
    fd_exec_one: "تنفيذ",
    fd_exec_salary: "خصم راتب",
    fd_exec_leave: "خصم إجازة",
    fd_done: "تم",
    fd_reason_late: "تأخر {mins} دقيقة عن {schIn} — السياسة: {tier}",
    fd_reason_absent_leave: "غياب بلا حضور — رصيد {leave}: {rem} يوم متبقي",
    fd_reason_absent_salary: "غياب بلا حضور — خصم من الراتب",
    fd_reason_no_policy: "لا اقتراح من النظام (ضمن السماح أو لا عتبة مطابقة) — للمدير تطبيق خصم يدوياً",
    fd_tier_quarter: "ربع يوم",
    fd_tier_half: "نصف يوم",
    fd_tier_full: "يوم كامل",
    fd_apply_btn: "تطبيق خصم",
    fd_modal_title: "تطبيق خصم",
    fd_modal_suggestion: "اقتراح النظام",
    fd_balance_summary: "ملخص رصيد الإجازات",
    fd_bal_exhausted: "الرصيد صفر — خصم الإجازة سيكون بالسالب",
    fd_all_exhausted_warn: "جميع أرصدة الإجازة صفر — يُنصح بخصم الراتب",
    fd_section_salary: "خصم من الراتب",
    fd_section_annual: "الإجازة السنوية",
    fd_section_casual: "الإجازة العارضة",
    fd_section_sick: "الإجازة المرضية",
    fd_bal_remaining: "{rem} / {total} متبقي",
    fd_bal_zero_warn: "الرصيد صفر",
    fd_deduct_full: "يوم كامل",
    fd_deduct_half: "نصف يوم",
    fd_deduct_quarter: "ربع يوم",
    fd_modal_close: "إلغاء",
    fd_stats_show: "إظهار الإحصائية",
    fd_stats_hide: "إخفاء الإحصائية",
    fd_stats_compact: "تصغير",
    fd_stats_expand: "تكبير",
    fd_salary_disabled_policy: "خصم الراتب متوقف من إعدادات السياسة.",
    fd_salary_disabled_day: "خصم الراتب غير مسموح لحالة هذا اليوم.",
    fd_salary_disabled_balance: "خصم الراتب يتطلب نفاد الأرصدة أو تفعيل السماح مع وجود رصيد في السياسة.",
    fd_confirm_exec_all: "تنفيذ كل الاقتراحات المعلقة لهذه الفترة؟",
    fd_confirm_remove_deduct: "إزالة هذا الخصم؟",
    fd_toast_bulk: "{a} منفّذ · {n} تخطي (موجود مسبقاً) · {m} تخطي (لا اقتراح)",
    fd_delete_aria: "إزالة الخصم",
    fd_apply_suggested: "تطبيق القرار المقترح من النظام",
    fd_override_section: "أو اختر قراراً مختلفاً:",
    fd_bal_warn_single: "الرصيد صفر",
    nav_leave_settlement: "تسوية الإجازات",
    nav_leave_settlement_short: "تسوية",
    nav_deductions_report: "سجل الخصومات",
    dr_title: "تقرير خصومات الموظف",
    dr_sub: "يعرض خصومات رصيد الإجازة وتعليمات الراتب المسجّلة في الفترة. «تراجع» يحذف السجل (نفس حذفه من التقرير الختامي).",
    dr_load: "عرض التقرير",
    dr_col_kind: "النوع",
    dr_col_detail: "التفاصيل",
    dr_col_days: "وحدات اليوم",
    dr_totals_title: "إجماليات الفترة",
    dr_empty: "اختر الموظف والفترة ثم اعرض التقرير.",
    dr_no_rows: "لا توجد خصومات في هذه الفترة لهذا الموظف.",
    dr_type_leave: "رصيد إجازة",
    dr_type_salary: "تعليم راتب",
    dr_leave_sum: "مجموع خصومات الإجازة",
    dr_salary_sum: "مجموع تعليمات الراتب",
    dr_revert: "تراجع",
    dr_revert_confirm: "إزالة هذا السجل من النظام؟ لا يمكن التراجع إلا بإعادة إدخاله يدوياً.",
    fr_month_current: "الشهر الحالي",
    fr_month_last: "الشهر الماضي",
    fr_month_custom: "فترة مخصصة",
    policy_settlement_section: "تسوية الإجازات (اقتراح تلقائي)",
    policy_settlement_hint: "الاقتراح التلقائي لخصم الرصيد ينطبق على الغياب فقط. الحقول الأخرى للسماح والعتبات للمرجعية؛ يدخل المدير خصم التأخير/الخروج المبكر يدوياً في التسوية إن سمحت سياسة الشركة.",
    policy_grace_late: "سماح بعد بداية الدوام (دقيقة)",
    policy_grace_early: "سماح قبل نهاية الدوام (دقيقة)",
    policy_late_q: "تأخير → ربع يوم (دقيقة)",
    policy_late_h: "تأخير → نصف يوم (دقيقة)",
    policy_late_f: "تأخير → يوم كامل (دقيقة)",
    policy_early_q: "خروج مبكر → ربع يوم (دقيقة)",
    policy_early_h: "خروج مبكر → نصف يوم (دقيقة)",
    policy_early_f: "خروج مبكر → يوم كامل (دقيقة)",
    policy_auto_lt: "نوع الإجازة الافتراضي للاقتراح",
    policy_auto_max: "أقصى وحدات يوم لكل يوم تقويمي",
    policy_auto_combine: "دمج دقائق التأخير والخروج المبكر في عقوبة واحدة",
    policy_auto_ignore: "تجاهل الاقتراح التلقائي عند وجود رموز اقتراح (مفصولة بفاصلة)",
    ls_title: "تسوية رصيد الإجازات",
    ls_sub: "المعاينة مطابقة لصفوف التقرير الختامي. نفّذ خصومات الرصيد دفعة واحدة؛ عند الحفظ يُستبدل الخصم اليدوي السابق للتواريخ التي تُعدّلها.",
    ls_auto_suggest_rule: "الاقتراح التلقائي: خصم من رصيد الإجازات للغياب فقط (لا حضور في يوم عمل). التأخير والخروج المبكر وعدم تسجيل الخروج لا يُقترح لها خصم إجازة تلقائياً — استخدم تعليم الراتب من التقرير الختامي أو أدخل النوع والأيام يدوياً هنا إن رغبت.",
    ls_load_preview: "تحميل المعاينة",
    ls_apply: "تنفيذ الخصومات",
    ls_col_times: "المواعيد / الفعلي",
    ls_col_system: "اقتراح النظام",
    ls_col_days: "الخصم (أيام)",
    ls_existing_manual: "خصم يدوي حالي",
    ls_empty_hint: "اختر الموظف والفترة ثم «تحميل المعاينة».",
    ls_summary_title: "الأرصدة ومسودة الخصم",
    ls_policy_missing: "لا توجد سياسة إجازات مرتبطة بهذا الموظف. قد تكون الاقتراحات محدودة — عيّن سياسة من الموظفين أو السياسات.",
    ls_open_from_report: "فتح التسوية لهذه الفترة",
    ls_reset_row: "استرجاع اقتراح النظام",
    ls_confirm_apply: "تطبيق {rows} صف خصم؟ المسودة — سنوي: {a}، عارضة: {c}، مرضي: {s} (وحدات يوم). إزالة الخصم من يوم تحذف الخصومات اليدوية السابقة لذلك التاريخ.",
    ls_stale_warning: "قد يكون الحضور تغيّر بعد تحميل المعاينة. أعد التحميل قبل التنفيذ؟",
    ls_no_valid_rows: "لا صفوف للحفظ: اختر وحدات يوم ليوم واحد على الأقل، أو أزل الخصومات اليدوية الحالية.",
    ls_type_label: "النوع",
    ls_balance_line: "{type}: المتبقي الآن {rem} — بعد التنفيذ {after} (هذه العملية −{draft})",
    final_report_title: "التقرير الختامي للموظف",
    final_report_sub: "حضور، موقع، بصمة وجه وإجازات — لمراقبة الأداء وتطويره",
    select_employee: "اختر الموظف",
    select_month: "اختر الشهر",
    generate_report: "عرض التقرير",
    print_report: "طباعة التقرير",
    fd_short_report_btn: "تقرير مختصر",
    fd_print_need_period: "اختر تاريخ البداية والنهاية للتقرير.",
    first_in: "أول دخول",
    last_out: "آخر خروج",
    status_present: "حاضر",
    status_absent: "غائب",
    status_leave: "إجازة",
    status_official_holiday: "إجازة رسمية",
    nav_official_holidays: "الإجازات الرسمية",
    official_holidays_title: "الإجازات الرسمية",
    official_holidays_sub: "عطلات الشركة أو الأعياد الوطنية تظهر في التقرير الختامي كيوم غير عمل (بدون غياب) ما لم يكن هناك حضور أو إجازة معتمدة.",
    official_holidays_list: "تقويم العطل",
    official_holiday_add: "إضافة عطلة",
    official_holiday_edit: "تعديل عطلة",
    official_holiday_name: "اسم العطلة",
    official_holiday_start: "تاريخ البداية",
    official_holiday_end: "تاريخ النهاية (شامل)",
    official_holiday_range_hint: "اتركه مساوياً للبداية ليوم واحد، أو اختر آخر يوم لعطلة متعددة الأيام.",
    official_holiday_range_saved: "تمت إضافة {n} يوماً في التقويم.",
    policy_salary_deduct_enabled: "السماح بتمييز أيام غياب (عمل) كخصم من الراتب في التقرير الختامي",
    policy_salary_deduct_allow_balance: "السماح بخصم الراتب حتى لو بقي للموظف رصيد إجازة (يتطلب عند الحفظ اختيار «تطبيق رغم وجود رصيد»)",
    btn_save_policy: "حفظ السياسة",
    policy_modal_intro: "كل مجموعة أدناه توضّح أثر الإعداد على الموظفين بعد ربط هذه السياسة بهم.",
    policy_sec_identity_title: "اسم السياسة",
    policy_sec_identity_help: "الاسم مجرّد تسمية لمجموعة القواعد؛ لا يتغيّر شيء للموظفين حتى تربط السياسة بملف الموظف. يمكنك الاحتفاظ بعدة سياسات (مثلاً مكتب مقابل ميدان) ونقل الموظفين بينها.",
    policy_sec_schedule_title: "عطلة الأسبوع والدوام الرسمي",
    policy_sec_schedule_help: "الأيام المحددة تُعرَض كعطلة أسبوعية في التقارير (عادةً دون اعتبارها غياباً متوقعاً). أوقات بداية ونهاية الدوام تُقارَن بأول دخول وآخر خروج لاكتشاف التأخير والخروج المبكر وعدم إكمال الخروج؛ كما تُستخدم في دقائق السماح واقتراحات التسوية.",
    policy_sec_balances_title: "أرصدة الإجازات (أيام في السنة)",
    policy_sec_balances_help: "الثلاثة أرقام هي الحد الأقصى السنوي لأيام الإجازة السنوية والعارضة والمرضية. الطلبات المعتمدة والخصومات اليدوية المؤكدة تستهلك نفس الرصيد. النظام لا يصفّر الأرصدة تلقائياً عند نهاية السنة — تعدّل الأرقام إذا كانت سياسة الموارد البشرية تقتضي ذلك.",
    policy_sec_deduct_title: "وحدات الخصم اليدوي",
    policy_sec_deduct_help: "عندما يخصم المدير من التقرير الختامي أو تسوية الإجازات يختار جزءاً من اليوم (مثلاً 1 أو 0.5 أو 0.25). القيم الثلاث هنا تعرّف معنى «يوم كامل» و«نصف يوم» و«ربع يوم» بوحدات اليوم المدفوع. لا يُخصم شيء تلقائياً — فقط قرار المدير ينقص الرصيد.",
    policy_sec_settlement_title: "تسوية الإجازات (اقتراحات تلقائية)",
    policy_sec_settlement_help: "في شاشة تسوية الإجازات يقترح النظام **خصماً من رصيد الإجازة تلقائياً فقط عند الغياب** (لا بصمة حضور في يوم عمل)، باستخدام نوع الإجازة الافتراضي وجزء اليوم الكامل أعلاه. دقائق السماح والعتبات ودمج التأخير/الخروج وتجاهل الرموز **لا تُستخدم حالياً لاقتراح خصم إجازة تلقائي**؛ التأخير والخروج المبكر يُعالجان عبر تعليم الراتب أو **إدخال يدوي** من المدير في جدول التسوية. تبقى الحقول موثّقة للوضوح ولأي استخدام لاحق.",
    policy_sub_late_thresholds: "التأخير (بعد السماح)",
    policy_sub_early_thresholds: "الخروج المبكر (بعد السماح)",
    policy_early_thresholds_note: "إذا تُركت فارغة تُخزَّن قيم افتراضية كمرجع فقط؛ لا تُشغّل اقتراح خصم رصيد تلقائي (القاعدة: الغياب فقط).",
    policy_fld_grace_late_help: "تُستخدم في تعليم التقرير (متأخر/في الوقت)، وليست لاقتراح خصم رصيد تلقائي. كلما زاد الرقم قلّت الدقائق المحسوبة كتأخير في التقرير.",
    policy_fld_grace_early_help: "تُستخدم في تعليم التقرير (خروج مبكر)، وليست لاقتراح خصم رصيد تلقائي. كلما زاد الرقم قلّت الدقائق المحسوبة كخروج مبكر.",
    policy_fld_late_q_help: "قيمة مرجعية للموارد البشرية. الاقتراح التلقائي لخصم الرصيد يقتصر على الغياب؛ يدخل المدير خصم التأخير يدوياً في التسوية إن رغب.",
    policy_fld_late_h_help: "عتبة مرجعية لنصف يوم (دقائق بعد السماح). لا تُستخدم لاقتراح خصم إجازة تلقائي ضمن قاعدة «الغياب فقط».",
    policy_fld_late_f_help: "عتبة مرجعية ليوم كامل (دقائق بعد السماح). لا تُستخدم لاقتراح خصم إجازة تلقائي ضمن قاعدة «الغياب فقط».",
    policy_fld_early_q_help: "عتبة مرجعية لربع يوم للخروج المبكر. لا تُستخدم لاقتراح خصم إجازة تلقائي.",
    policy_fld_early_h_help: "عتبة مرجعية لنصف يوم للخروج المبكر. لا تُستخدم لاقتراح خصم إجازة تلقائي.",
    policy_fld_early_f_help: "عتبة مرجعية ليوم كامل للخروج المبكر. لا تُستخدم لاقتراح خصم إجازة تلقائي.",
    policy_fld_auto_lt_help: "نوع الإجازة المستخدم عند اقتراح خصم تلقائي ليوم غياب عمل فقط.",
    policy_fld_auto_max_help: "أقصى وحدات يوم تُقترح لذلك اليوم الغائب (غالباً 1).",
    policy_fld_auto_combine_help: "لا يُستخدم حالياً لاقتراح خصم رصيد تلقائي (الاقتراح للغياب فقط). الحقل يبقى للتوثيق.",
    policy_fld_auto_ignore_help: "لا يُستخدم حالياً لاقتراح خصم رصيد تلقائي. الأيام ذات التنبيهات ما زالت تظهر في التقارير للمراجعة اليدوية.",
    policy_sec_salary_title: "تعليمات الراتب (غياب غير مدفوع)",
    policy_sec_salary_help: "يتحكم في إمكانية تعليم يوم غياب في التقرير الختامي لغرض الرواتب كيوم غير مدفوع. لا يغيّر رصيد الإجازة بذاته. استخدم الخيار الثاني فقط إذا كانت سياسة الرواتب تسمح بالخصم رغم بقاء رصيد إجازة.",
    policy_fld_salary_enabled_help: "عند الإيقاف لا يستطيع المدير تعليم الغياب لخصم الراتب لموظفي هذه السياسة من التقرير الختامي.",
    policy_fld_salary_balance_help: "عند التفعيل يمكن المتابعة مع الرواتب رغم بقاء رصيد إجازة؛ استخدمه بحذر حتى يفهم الموظف أن الراتب قد يُخصم رغم توفر إجازة.",
    weekday_monday: "الاثنين",
    weekday_tuesday: "الثلاثاء",
    weekday_wednesday: "الأربعاء",
    weekday_thursday: "الخميس",
    weekday_friday: "الجمعة",
    weekday_saturday: "السبت",
    weekday_sunday: "الأحد",
    emp_clear_profile_photo: "حذف الصورة الشخصية",
    confirm_clear_profile_photo: "إزالة الصورة المحفوظة من ملف الموظف؟ يمكن أن تصبح صورة أول بصمة دخول لاحقة الصورة الجديدة تلقائياً.",
    fr_salary_section: "الراتب (يوم غير مدفوع)",
    fr_salary_short: "تمييز راتب",
    fr_salary_btn: "تمييز خصم راتب",
    fr_salary_force_tag: "رغم الرصيد",
    fr_salary_modal_title: "خصم من الراتب (يوم غير مدفوع)",
    fr_salary_modal_hint: "يُميّز يوم غياب عمل لغرض الرواتب بنفس أجزاء اليوم المستخدمة في خصم الإجازة. لا يغيّر رصيد الإجازات.",
    fr_salary_force_label: "التطبيق رغم بقاء رصيد إجازة (مسموح بالسياسة)",
    fr_salary_submit: "حفظ التمييز",
    fr_salary_deduct_total: "تمييزات الراتب (أيام)",
    year: "السنة",
    branch_photo_col: "صورة الفرع",
    branch_photo_upload: "صورة الفرع (رفع)",
    branch_photo_upload_hint: "صورة اختيارية تُخزَّن على الخادم في uploads/branches/&lt;معرّف الشركة&gt;/",
    emp_documents_label: "مرفقات الموظف",
    emp_documents_hint: "عقود، هويات، شهادات (PDF، Word، Excel، صور). بحد أقصى 12 ميجابايت لكل ملف. التخزين في uploads/&lt;معرّف الشركة&gt;/employee_documents/&lt;معرّف الموظف&gt;/",
    emp_documents_upload_btn: "رفع المحدد",
    emp_docs_save_first: "احفظ الموظف أولاً ثم يمكنك رفع المرفقات.",
    emp_docs_none: "لا توجد مرفقات بعد.",
    emp_docs_download: "فتح",
    emp_docs_preview: "معاينة (تبويب جديد)",
    emp_docs_new_tab_hint: "يفتح في تبويب جديد",
    emp_docs_col_doc: "المستند",
    emp_docs_col_size: "الحجم",
    emp_docs_col_date: "تاريخ الرفع",
    emp_doc_edit_title: "تعديل اسم المستند",
    emp_doc_edit_hint: "يُغيّر العنوان المعروض في القائمة فقط؛ اسم الملف المخزَّن على الخادم لا يتغيّر.",
    emp_doc_edit_label: "اسم العرض",
    emp_doc_edit_save: "حفظ",
    emp_docs_pick_files: "يرجى اختيار ملف أو أكثر",
    emp_docs_popup_blocked: "اسمح بالنوافذ المنبثقة لهذا الموقع لفتح المستندات في تبويب جديد.",
    official_holidays_empty: "لا توجد إجازات رسمية لهذا العام.",
    notes: "ملاحظات",
    day: "اليوم",
    select_employee_hint: "الرجاء اختيار موظف لعرض التقرير",
    leave_start: "تاريخ البداية",
    leave_end: "تاريخ النهاية",
    leave_created: "تاريخ تقديم الطلب",
    fr_total_hours: "إجمالي ساعات العمل",
    fr_late_days: "أيام تأخر عن الدوام",
    fr_early_leave_days: "أيام خروج مبكر",
    fr_loc_alerts_days: "أيام تنبيه موقع",
    fr_incomplete_days: "أيام غير مكتملة (بدون خروج)",
    fr_work_hours: "ساعات العمل",
    fr_location: "الموقع (دخول / خروج)",
    fr_selfie: "بصمة الوجه",
    fr_reg_method: "طريقة التسجيل",
    fr_field_day: "طبيعة اليوم",
    fr_loc_ok: "سليم",
    fr_loc_alert: "تنبيه",
    fr_reg_qr: "QR",
    fr_reg_direct: "مباشر",
    fr_reg_manual: "يدوي",
    fr_policy: "سياسة الإجازات والخصومات",
    nav_leave_overview: "ملخص الإجازات",
    leave_overview_title: "تقرير أرصدة الإجازات",
    leave_overview_sub: "لكل موظف: الرصيد، الاستهلاك من الإجازات المعتمدة، خصومات المدير، وأيام الغياب (أيام عمل) للسنة المختارة",
    leave_overview_year: "السنة",
    leave_overview_absent: "غياب (أيام عمل)",
    leave_overview_manual_total: "خصم إداري (أيام)",
    leave_overview_type_cell: "متبقي / الإجمالي (معتمد · خصم)",
    leave_overview_appr: "معتمد",
    leave_overview_man: "خصم إداري",
    lov_print: "طباعة التقرير",
    lov_filters_title: "فلاتر التقرير",
    lov_search: "بحث",
    lov_no_branch: "بدون فرع",
    lov_no_dept: "بدون قسم",
    lov_all_policies: "كل السياسات",
    lov_no_policy: "بدون سياسة",
    lov_policy_presence: "ربط بسياسة",
    lov_with_policy: "مع سياسة",
    lov_without_policy: "بدون سياسة",
    lov_sort: "ترتيب",
    lov_sort_name_asc: "الاسم أ–ي",
    lov_sort_name_desc: "الاسم ي–أ",
    lov_sort_code: "كود الموظف",
    lov_sort_absent: "الغياب (الأعلى أولاً)",
    lov_sort_ann_rem_low: "متبقي سنوي (الأقل أولاً)",
    lov_sort_ann_rem_high: "متبقي سنوي (الأعلى أولاً)",
    lov_sort_manual: "خصومات إدارية (الأعلى أولاً)",
    lov_print_year: "السنة",
    lov_print_company: "الشركة",
    lov_print_filters: "الفلاتر",
    lov_all: "الكل",
    fr_leave_deduct: "خصم رصيد",
    fr_deduct_opt_full: "يوم كامل",
    fr_deduct_opt_half: "نصف يوم",
    fr_deduct_opt_quarter: "ربع يوم",
    fr_deduct_btn: "خصم من الرصيد",
    fr_deduct_modal_title: "خصم من رصيد الإجازة",
    fr_deduct_amount: "جزء من اليوم",
    fr_deduct_submit: "تطبيق الخصم",
    fr_balance_year: "سنة احتساب رصيد الإجازات",
    fr_suggestions_hint: "اقتراحات للمراجعة فقط — وليست عقوبة تلقائية.",
    fr_sug_absent: "غياب (يوم عمل)",
    fr_sug_late: "تأخر عن الحضور",
    fr_sug_early_leave: "انصراف مبكر",
    fr_sug_incomplete_checkout: "بدون تسجيل خروج",
    fr_sug_overtime: "بقاء طويل (احتمال وقت إضافي)",
    fr_sug_location_alert: "تنبيه موقع",
    fr_sug_distance_far: "بعيد عن الموقع المتوقع",
    fr_sug_external_no_mission: "يوم ميداني — بدون تفاصيل مأمورية",
    fr_issues_panel_title: "أيام بها ملاحظات — كل الموظفين",
    fr_issues_panel_hint: "يستخدم نفس الفترة أعلاه. اختر نوع الاقتراح (اختياري) ثم «تحميل القائمة». زر الفتح ينتقل لتقرير ذلك الموظف لنفس شهر التاريخ.",
    fr_issues_load: "تحميل القائمة",
    fr_issues_empty: "اختر الفلاتر (اختياري) ثم «تحميل القائمة».",
    fr_issues_suggestions_col: "الاقتراحات",
    fr_issues_open: "فتح في التقرير",
    fr_issues_emp_missing: "تعذر اختيار هذا الموظف في القائمة (غير مفعّل أو غير موجود).",
    fr_table_density: "عرض الجدول",
    fr_density_full: "كل الأعمدة",
    fr_density_compact: "مدمج (مراجعة أسرع)",
    fr_bulk_dept_label: "جماعي: القسم",
    fr_apply_settlement_employee: "تطبيق تسوية الإجازات (هذا الموظف)",
    fr_bulk_apply_btn: "تطبيق كل الخصومات المقترحة (الجميع)",
    fr_settlement_toolbar_hint: "محرر التسوية يعكس تبويب تسوية الإجازات. خصم التأخير يعتمد دقائق السماح وعتبات التأخير في سياسة الموظف (نوع الإجازة يتبع إعداد الخصم التلقائي في السياسة). يُتخطى اليوم إن وُجد خصم يدوي مسبقاً.",
    fr_apply_late_bulk: "تطبيق خصم التأخير (هذا الموظف)",
    fr_apply_late_row: "تطبيق خصم التأخير",
    fr_late_bulk_confirm: "تطبيق خصومات رصيد الإجازة لكل أيام التأخير المؤهلة في الفترة لهذا الموظف؟ الأيام التي بها خصم يدوي تُتخطى.",
    fr_late_bulk_done: "تم التطبيق: {a}. تخطّي (لا قاعدة): {n}. تخطّي (يوجد خصم): {m}.",
    fr_print_caption_leave: "خصم من رصيد الإجازات",
    fr_print_caption_salary: "خصم من الراتب",
    fr_print_sep: "؛",
    fr_settlement_editor: "تسوية (نفس التبويب)",
    fr_need_report: "عرض التقرير أولاً.",
    fr_bulk_none: "لا توجد صفوف تلقائية للتطبيق لهذه الفترة/الفلتر.",
    fr_bulk_confirm: "تطبيق {n} خصماً تلقائياً على {emp} موظفاً؟ الصفوف التي بها خصم يدوي تُتخطى.",
    fr_secondary_stats_toggle: "إحصائيات إضافية (ساعات، تأخير، تنبيهات…)",
    fr_reason_bulk: "تسوية جماعية حسب السياسة",
    fr_screen_table_title: "سجل الحضور — عرض النظام (مراجعة وخصم وتسوية)",
    fr_print_doc_title: "محضر حضور وانصراف شهري رسمي",
    fr_print_legal_statement: "يُدرج هذا المحضر أيام الفترة المحددة، والمواعيد الرسمية للعمل (الدخول والخروج المقررين)، وأوقات تسجيل الدخول والخروج الفعلية كما وردت في النظام. يُصدر لدعم صرف الأجور واحتساب الإجازات والرقابة الإدارية وفقاً للنظام. في حال المنازعة يُرجى الرجوع إلى أدلة التسجيل الأصلية.",
    fr_print_recap_title: "بيانات الموظف والفترة (للاعتماد الإداري)",
    fr_print_company: "الشركة",
    fr_print_final_decision_title: "القرار الإداري النهائي",
    fr_print_final_decision_body: "بناءً على جدول الحضور أعلاه، يُعتمد هذا المحضر لاتخاذ القرارات المتعلقة بالموارد البشرية (الأجور، رصيد الإجازات، الإجراءات التأديبية) في حدود سياسة الشركة والقانون المعمول به، ما لم يُعدَّل كتابةً من جهة مخولة.",
    fr_print_gm_signature: "المدير العام — الاسم والتوقيع",
    fr_print_hr_manager: "الموارد البشرية / المدير المباشر — التوقيع",
    fr_print_employee_ack: "الموظف — اطلعت وأقر",
    fr_print_stamp: "الختم (إن وجد)",
    fr_print_date_label: "التاريخ",
    fr_print_generated: "تاريخ إصدار المستند",
    fr_print_sub_calendar: "التقويم",
    fr_print_sub_official_vs_actual_in: "الموعد الرسمي ← وقت تسجيل الدخول الفعلي",
    fr_print_sub_official_vs_actual_out: "الموعد الرسمي ← وقت تسجيل الخروج الفعلي",
    fr_print_sub_other: "تفاصيل أخرى",
    admin_push_phone_hint: "لتظهر التنبيهات على شاشة القفل: اتصل عبر HTTPS، ثبّت تطبيق المدير (إضافة إلى الشاشة الرئيسية)، افتحه مرة، فعّل التنبيهات أعلام، واسمح بالإشعارات من إعدادات المتصفح والنظام. على iOS غالباً يلزم إضافة التطبيق للشاشة الرئيسية لدعم Web Push.",
    cred_modal_title: "بيانات الدخول",
    invite_message_preview: "معاينة الرسالة (نفس واتساب والنسخ)",
    username: "اسم المستخدم",
    password: "كلمة المرور",
    app_link: "رابط التطبيق",
    instructions: "إرشادات",
    btn_copy: "نسخ الرسالة",
    wa_msg_template: "مرحباً {name}،\nيسعدنا انضمامك لفريقنا.\nبيانات الدخول الخاصة بك:\nاسم المستخدم: {user}\nكلمة المرور: {pass}\nرابط التطبيق: {link}\n{instructions}\n\n*ملاحظة: يرجى تغيير كلمة المرور فور تسجيل الدخول لأول مرة.*",
    pwa_install_admin: "تثبيت تطبيق المدير",
    pwa_install_hint: "تثبيت التطبيق على الجهاز",
    pwa_install_section: "تثبيت التطبيق (PWA)",
    pwa_install_help: "ثبّت بوابة المدير كتطبيق للوصول السريع وتفعيل التنبيهات الفورية (اختياري).",
    pwa_already_installed: "التطبيق مثبت بالفعل",
    pwa_install_manual_hint: "افتح صفحة المدير في تبويب Chrome عادي (ليس من داخل تطبيق الموظف)، ثم القائمة ⋮ ← تثبيت التطبيق. بعد تحديث الموقع احذف الاختصارات القديمة وأعد التثبيت إن لزم.",
    admin_alerts_title: "تنبيهات الحساب",
    admin_notif_empty: "لا توجد تنبيهات حالياً.",
    mark_all_read: "تعيين الكل كمقروء",
    mark_read: "تحديد كمقروء",
    delete_notif: "حذف",
    delete_all: "حذف الكل",
    notif_filter_all: "الكل",
    notif_filter_unread: "غير المقروء",
    notif_filter_archived: "المؤرشفة",
    notif_unread_count: "{n} غير مقروء",
    notif_read_done: "تم تحديد الإشعار كمقروء",
    notif_delete_done: "تم أرشفة الإشعار",
    notif_delete_all_done: "تمت أرشفة جميع الإشعارات",
    confirm_delete_all_notif: "هل تريد أرشفة جميع الإشعارات؟",
    delete_permanent: "حذف نهائي",
    delete_permanent_all: "حذف نهائي الكل",
    confirm_delete_perm_notif: "هل تريد حذف هذا الإشعار المؤرشف نهائياً؟",
    confirm_delete_perm_all_notif: "هل تريد حذف كل الإشعارات المؤرشفة نهائياً؟",
    notif_purge_done: "تم حذف الإشعار المؤرشف نهائياً",
    notif_purge_all_done: "تم حذف جميع الإشعارات المؤرشفة نهائياً",
    admin_push_section: "تنبيهات الجوال (Push)",
    admin_push_enable: "تفعيل التنبيهات على هذا الجهاز",
    admin_push_disable: "إيقاف التنبيهات على هذا الجهاز",
    admin_push_server_off: "مفاتيح Web Push (VAPID) غير مضبوطة. أضف المفتاح العام والخاص من Master (البريد الصادر → Web Push) أو عيّن HRPRO_VAPID_* على الخادم.",
    admin_push_need_composer: "المفاتيح مضبوطة لكن مكتبة PHP غير مثبتة. على الخادم نفّذ: composer install داخل مجلد /hr (حزمة minishlink/web-push). تم حفظ الاشتراك؛ التنبيهات تعمل بعد التثبيت.",
    admin_push_need_https: "التنبيهات تتطلب اتصال HTTPS (أو localhost).",
    admin_push_subscribed: "تم تفعيل التنبيهات لهذا الجهاز",
    admin_push_denied: "تم رفض الإشعارات. فعّلها من إعدادات المتصفح.",
    nav_company_news: "أخبار الموظفين",
    company_news_title: "شريط أخبار الموظفين",
    company_news_sub: "أسطر تتحرك أعلى تطبيق الموظف؛ عند النشر يُرسل تنبيه للموظفين الذين فعّلوا تنبيهات الأخبار.",
    company_news_new: "إضافة سطر خبر",
    company_news_message: "نص الخبر",
    company_news_sort: "الأولوية (الأعلى يظهر أولاً)",
    company_news_active: "يظهر في تطبيق الموظف",
    company_news_add: "نشر",
    company_news_list: "جميع الأخبار",
    company_news_empty: "لا توجد أخبار بعد.",
    company_news_hide: "إخفاء",
    company_news_show: "إظهار",
    pwa_dual_hint: "لكل تطبيق معرف (id) ونطاق (scope) و start_url مختلفان في الـ manifest حتى يعرض Chrome تثبيتين منفصلين. إن لم يظهر التثبيت، افتح صفحة المدير من تبويب عادي وليس من داخل تطبيق الموظف.",
    banner_trial: "وضع تجريبي: شركتك على اشتراك Trial. تواصل مع مزود الخدمة للترقية إلى خطة مدفوعة عند الجاهزية.",
    banner_close_text: "تنتهي صلاحية الوصول في {date} (متبقي {n} يوماً تقويمياً)."
  }
};

function t(key, params = {}) {
  // PERF: use cached lang — avoids DOM read on every translation call
  const lang = _cachedLang || document.documentElement.lang || 'en';
  let text = (i18n[lang] && i18n[lang][key]) ? i18n[lang][key] : (i18n['en'][key] || key);
  for (const [k, v] of Object.entries(params)) {
    text = text.replace(`{${k}}`, v);
  }
  return text;
}

function updateUITranslations() {
  document.querySelectorAll('[data-i18n]').forEach(el => {
    el.textContent = t(el.getAttribute('data-i18n'));
  });
  document.querySelectorAll('[data-i18n-placeholder]').forEach(el => {
    el.placeholder = t(el.getAttribute('data-i18n-placeholder'));
  });
  document.querySelectorAll('[data-i18n-title]').forEach(el => {
    el.title = t(el.getAttribute('data-i18n-title'));
  });
  const empBd = document.getElementById('empModal');
  if (empBd && empBd.classList.contains('open')) syncEmpModalRequiredHint();
  refreshAdminSubscriptionBanners();
  const empTabEl = document.getElementById('tab-employees');
  if (empTabEl && empTabEl.style.display === 'block') {
    renderEmployeeBirthdayWeekBar();
  }
}

/** Add vs edit: required-field banner + password asterisk (i18n-aware). */
function syncEmpModalRequiredHint() {
  const hint = document.getElementById('empRequiredFieldHint');
  const pwdStar = document.getElementById('empPwdStar');
  const isAdd = !document.getElementById('editEmpId')?.value;
  if (hint) hint.textContent = t(isAdd ? 'emp_form_required_hint_add' : 'emp_form_required_hint_edit');
  if (pwdStar) pwdStar.style.display = isAdd ? 'inline' : 'none';
}

function refreshAdminSubscriptionBanners(data) {
  data = data || {};
  const status = (data.subscription_status !== undefined && data.subscription_status !== null && data.subscription_status !== '')
    ? data.subscription_status
    : (localStorage.getItem('admin_subscription_status') || '');
  const closeAt = (data.account_close_at !== undefined && data.account_close_at !== null && data.account_close_at !== '')
    ? data.account_close_at
    : (localStorage.getItem('admin_account_close_at') || '');
  const wrap = document.getElementById('adminSubscriptionBannerWrap');
  const trial = document.getElementById('adminTrialBanner');
  const closeB = document.getElementById('adminCloseSoonBanner');
  const closeTxt = document.getElementById('adminCloseSoonText');
  if (!wrap) return;
  let any = false;
  if (trial) {
    const showT = status === 'trial';
    trial.style.display = showT ? 'block' : 'none';
    if (showT) any = true;
  }
  let showC = false;
  if (closeAt && /^\d{4}-\d{2}-\d{2}$/.test(closeAt)) {
    const d0 = new Date();
    d0.setHours(0, 0, 0, 0);
    const d1 = new Date(closeAt + 'T12:00:00');
    const daysLeft = Math.round((d1.getTime() - d0.getTime()) / 86400000);
    if (daysLeft >= 0 && daysLeft <= 14) {
      showC = true;
      if (closeTxt) {
        closeTxt.textContent = t('banner_close_text', { date: closeAt, n: String(daysLeft) });
      }
    }
  }
  if (closeB) closeB.style.display = showC ? 'block' : 'none';
  if (showC) any = true;
  wrap.style.display = any ? 'block' : 'none';
}

// ── AUTH ──────────────────────────────────────────────────────────
async function doLogin() {
  const r = await api('POST', 'auth/admin-login', {
    username: document.getElementById('loginUser').value.trim(),
    password: document.getElementById('loginPass').value
  });
  if (r.success) {
    authToken = r.data.token;
    companyId = r.data.company_id;
    localStorage.setItem('admin_token', authToken);
    localStorage.setItem('admin_company_id', companyId);
    localStorage.setItem('admin_lang', r.data.language || 'ar');
    if (r.data.notification_email != null) {
      localStorage.setItem('admin_notif_email', String(r.data.notification_email));
    } else {
      localStorage.removeItem('admin_notif_email');
    }
    if (r.data.timezone) localStorage.setItem('admin_timezone', r.data.timezone);
    if (r.data.company_logo) localStorage.setItem('admin_company_logo', r.data.company_logo);
    if (r.data.mobile_app_url != null && String(r.data.mobile_app_url).trim() !== '') {
      localStorage.setItem('admin_mobile_app_url', String(r.data.mobile_app_url).trim());
    } else {
      localStorage.removeItem('admin_mobile_app_url');
    }
    localStorage.setItem('admin_company_name', r.data.company_name || '');
    localStorage.setItem('admin_subscription_status', r.data.subscription_status || '');
    localStorage.setItem('admin_account_close_at', r.data.account_close_at || '');
    localStorage.setItem('admin_type', r.data.admin_type || 'general');
    localStorage.setItem('admin_permissions', JSON.stringify(r.data.permissions || {}));
    void initApp(r.data);
  } else showToast(r.message, 'error');
}

function tabToModuleKey(name) {
  const map = {
    'final-report': 'final_report',
    'final-decision': 'final_decision',
    'leave-settlement': 'leave_settlement',
    'deductions-report': 'deductions_report',
    'leave-overview': 'leave_overview',
    'official-holidays': 'official_holidays',
    'company-news': 'company_news',
    contacts: 'employees'
  };
  return map[name] || name;
}

function canAdminModule(action, moduleKey) {
  const adminType = localStorage.getItem('admin_type') || '';
  if (!adminSessionConfirmed && adminType !== 'general') return false;
  if (!adminType) return false;
  if (adminType === 'general') return true;
  const mk = moduleKey;
  const mod = adminPermissionMap && adminPermissionMap[mk] ? adminPermissionMap[mk] : null;
  if (!mod) return false;
  if (Number(mod.read) !== 1) return false;
  if (action === 'read') return Number(mod.read) === 1;
  if (action === 'create') return Number(mod.create) === 1;
  if (action === 'update') return Number(mod.update) === 1;
  if (action === 'delete') return Number(mod.delete) === 1;
  return false;
}

function canAdmin(action, tabName) {
  const mk = tabToModuleKey(tabName);
  return canAdminModule(action, mk);
}

function applyAdminPermissionsToNav() {
  const adminType = localStorage.getItem('admin_type') || '';
  if (!adminType) {
    document.querySelectorAll('.sidebar-nav .nav-item, .bottom-nav .bottom-nav-item').forEach((el) => {
      el.style.display = 'none';
    });
    return;
  }
  if (adminType === 'general') return;
  document.querySelectorAll('.sidebar-nav .nav-item, .bottom-nav .bottom-nav-item').forEach((el) => {
    const on = el.getAttribute('onclick') || '';
    const m = on.match(/showTab\('([^']+)'/);
    if (!m || !m[1]) return;
    const tab = m[1];
    if (!canAdmin('read', tab)) {
      el.style.display = 'none';
    }
  });
}

function applyCrudVisibilityForTab(tabName) {
  const adminType = localStorage.getItem('admin_type') || '';
  if (!adminType) return;
  if (adminType === 'general') return;
  const root = document.getElementById('tab-' + tabName);
  if (!root) return;
  const allowRead = canAdmin('read', tabName);
  const allowCreate = canAdmin('create', tabName);
  const allowUpdate = canAdmin('update', tabName);
  const allowDelete = canAdmin('delete', tabName);
  root.querySelectorAll('button').forEach((btn) => {
    const on = String(btn.getAttribute('onclick') || '').toLowerCase();
    const txt = (btn.textContent || '').toLowerCase();
    const isCreateBtn =
      /(add|new|create|generate|save|submit|register|إضافة|جديد|حفظ|إنشاء|إرسال|تسجيل)/.test(txt) ||
      /(opencreate|save|submit|add|generate|openadminleavepicker|openmanualattfromtab|submitmanualatt|register)/.test(on);
    const isUpdateBtn =
      /(edit|update|approve|reject|تعديل|تحديث|اعتماد|رفض)/.test(txt) ||
      /(openedit|edit|update|approve|reject|reviewleave|submiteditattendance|savepolicy|savedepartment|savebranch)/.test(on);
    const isDeleteBtn =
      /(delete|remove|clear|مسح|حذف|إزالة)/.test(txt) ||
      /(delete|remove|clear)/.test(on);
    const isExportCsvBtn = /exportcsv\s*\(/.test(on);
    if (!allowRead && isExportCsvBtn) btn.style.display = 'none';
    if (!allowCreate && isCreateBtn) btn.style.display = 'none';
    if (!allowUpdate && isUpdateBtn) btn.style.display = 'none';
    if (!allowDelete && isDeleteBtn) btn.style.display = 'none';
  });
}

function persistAdminSessionData(data) {
  localStorage.setItem('admin_type', data.admin_type || '');
  localStorage.setItem('admin_permissions', JSON.stringify(data.permissions || {}));
  if (data.company_name != null) localStorage.setItem('admin_company_name', data.company_name || '');
  if (data.company_id != null) localStorage.setItem('admin_company_id', data.company_id || '');
  if (data.language != null) localStorage.setItem('admin_lang', data.language || 'ar');
  if (data.subscription_status != null) localStorage.setItem('admin_subscription_status', data.subscription_status || '');
  if (data.account_close_at != null) localStorage.setItem('admin_account_close_at', data.account_close_at || '');
  if (data.notification_email != null && String(data.notification_email).trim() !== '') localStorage.setItem('admin_notif_email', String(data.notification_email).trim());
  if (data.timezone) localStorage.setItem('admin_timezone', data.timezone);
  if (data.company_logo) localStorage.setItem('admin_company_logo', data.company_logo);
  if (data.mobile_app_url != null && String(data.mobile_app_url).trim() !== '') localStorage.setItem('admin_mobile_app_url', String(data.mobile_app_url).trim());
}

async function fetchAdminLiveSession() {
  const res = await api('GET', 'auth/admin-session');
  if (!res.success || !res.data) return null;
  return res.data;
}

function applySessionPermissionsInPlace() {
  applyAdminPermissionsToNav();
  const active = document.querySelector('.sidebar-nav .nav-item.active, .bottom-nav .bottom-nav-item.active');
  if (active) {
    const on = active.getAttribute('onclick') || '';
    const m = on.match(/showTab\('([^']+)'/);
    if (m && m[1]) {
      const tab = m[1];
      if (!canAdmin('read', tab)) {
        const fallback = canAdmin('read', 'dashboard') ? 'dashboard' : 'employees';
        const nav = findNavTriggerForTab(fallback);
        showTab(fallback, nav || null);
      } else {
        applyCrudVisibilityWithRetries(tab);
      }
    }
  }
  const empTabEl = document.getElementById('tab-employees');
  if (empTabEl && empTabEl.style.display === 'block' && allEmployeesData && allEmployeesData.length) {
    renderEmployees();
    renderEmployeeBirthdayWeekBar();
  }
}

async function refreshAdminSession(silent = true) {
  if (!authToken) return;
  try {
    const live = await fetchAdminLiveSession();
    if (!live) throw new Error('session');
    persistAdminSessionData(live);
    adminPermissionMap = live.permissions || {};
    adminSessionConfirmed = true;
    applySessionPermissionsInPlace();
  } catch (e) {
    if (!silent) showToast(t('session_invalid') || 'Session expired', 'error');
    logout();
  }
}

function startAdminSessionRefreshLoop() {
  if (adminSessionRefreshTimer) clearInterval(adminSessionRefreshTimer);
  adminSessionRefreshTimer = setInterval(() => { void refreshAdminSession(true); }, ADMIN_SESSION_REFRESH_MS);
}

function applyCrudVisibilityWithRetries(tabName) {
  [0, 250, 700, 1300].forEach((ms) => {
    setTimeout(() => applyCrudVisibilityForTab(tabName), ms);
  });
}

const ADMIN_SIDEBAR_COLLAPSED_KEY = 'admin_sidebar_collapsed';

function applyAdminSidebarCollapsedState() {
  const collapsed = localStorage.getItem(ADMIN_SIDEBAR_COLLAPSED_KEY) === '1';
  document.body.classList.toggle('admin-sidebar-collapsed', collapsed);
  updateAdminSidebarToggleUi(collapsed);
}

function toggleAdminSidebarCollapsed() {
  const next = !document.body.classList.contains('admin-sidebar-collapsed');
  document.body.classList.toggle('admin-sidebar-collapsed', next);
  localStorage.setItem(ADMIN_SIDEBAR_COLLAPSED_KEY, next ? '1' : '0');
  updateAdminSidebarToggleUi(next);
}

function updateAdminSidebarToggleUi(collapsed) {
  const btn = document.getElementById('adminSidebarToggle');
  const icon = document.getElementById('adminSidebarToggleIcon');
  if (btn) {
    btn.setAttribute('aria-pressed', collapsed ? 'true' : 'false');
    const k = collapsed ? 'sidebar_toggle_expand' : 'sidebar_toggle_collapse';
    btn.setAttribute('data-i18n-title', k);
    btn.title = t(k);
  }
  if (icon) {
    icon.innerHTML = collapsed
      ? '<polyline points="9 18 15 12 9 6"/>'
      : '<polyline points="15 18 9 12 15 6"/>';
  }
}

function scheduleAdminBackgroundLoads() {
  // PERF: stagger background loads so mobile doesn't get hit with 4 requests at once
  loadDepartments();
  setTimeout(() => loadBranches(), 150);
  setTimeout(() => loadPolicies(), 300);
  setTimeout(() => loadLeave(), 450);
  // Pre-fetch employee list so manual-attendance modal opens instantly
  setTimeout(() => {
    if (!allEmployeesData || !allEmployeesData.length) {
      api('GET', 'employees').then(res => {
        if (res.success && res.data) allEmployeesData = res.data;
      }).catch(() => {});
    }
  }, 600);
}

async function initApp(data) {
  const lang = data.language || localStorage.getItem('admin_lang') || 'ar';
  localStorage.setItem('admin_lang', lang);
  companyId = data.company_id || localStorage.getItem('admin_company_id');
  localStorage.setItem('admin_company_id', companyId);
  adminPermissionMap = data.permissions || {};
  if ((!adminPermissionMap || Object.keys(adminPermissionMap).length === 0) && localStorage.getItem('admin_permissions')) {
    try { adminPermissionMap = JSON.parse(localStorage.getItem('admin_permissions') || '{}'); } catch (e) { adminPermissionMap = {}; }
  }
  adminSessionConfirmed = true;
  applyLanguage(lang);

  document.getElementById('loginScreen').style.display = 'none';
  document.getElementById('appShell').style.display = 'block';
  document.getElementById('sidebarCompanyName').textContent = data.company_name || 'Company';
  document.getElementById('userName').textContent = data.username || 'Admin';
  document.getElementById('userAvatar').textContent = (data.company_name||'A')[0].toUpperCase();
  
  const notifFromServer = (data.notification_email != null && String(data.notification_email).trim() !== '')
    ? String(data.notification_email).trim()
    : (localStorage.getItem('admin_notif_email') || '');
  const ne = document.getElementById('adminNotifEmail');
  if (ne) ne.value = notifFromServer;
  if (notifFromServer) localStorage.setItem('admin_notif_email', notifFromServer);

  const mInp = document.getElementById('adminMobileAppUrl');
  if (mInp) {
    const mFrom =
      (data.mobile_app_url != null && String(data.mobile_app_url).trim() !== '')
        ? String(data.mobile_app_url).trim()
        : (localStorage.getItem('admin_mobile_app_url') || '');
    mInp.value = mFrom;
  }
  
  const savedTimezone = localStorage.getItem('admin_timezone');
  if (savedTimezone && document.getElementById('adminTimezoneSelect')) {
    document.getElementById('adminTimezoneSelect').value = savedTimezone;
  }
  
  const storedLogo = localStorage.getItem('admin_company_logo');
  if (storedLogo) {
      const sidebarLogoText = document.querySelector('.sidebar-logo-text');
      if (sidebarLogoText) sidebarLogoText.innerHTML = `<img src="${hrproMediaUrl(storedLogo)}" style="max-height:40px; max-width: 140px; border-radius:4px;" alt="Company Logo">`;
      const lp = document.getElementById('logoPreview');
      if (lp) lp.innerHTML = `<img src="${hrproMediaUrl(storedLogo)}" style="max-width:100%; max-height:100%;">`;
  }
  
  setDefaultDates();
  const leaveStatusEl = document.getElementById('leaveFilterStatus');
  if (leaveStatusEl) leaveStatusEl.value = 'pending';
  try {
    await loadDashboard();
  } catch (e) {
    try { console.error(e); } catch (e2) { /* ignore */ }
  }
  const runBg = () => scheduleAdminBackgroundLoads();
  if (typeof requestIdleCallback === 'function') {
    requestIdleCallback(runBg, { timeout: 800 });
  } else {
    setTimeout(runBg, 0);
  }
  refreshAdminInstallButtons();
  applyAdminPermissionsToNav();
  startAdminSessionRefreshLoop();
  document.addEventListener('visibilitychange', () => {
    if (document.visibilityState === 'visible') void refreshAdminSession(true);
  });
  startAdminAlertPolling();
  refreshAdminSubscriptionBanners(data);
  setTimeout(() => applyAdminDeepLinkFromUrl(), 120);
  applyAdminSidebarCollapsedState();
}

async function uploadLogo() {
  const input = document.getElementById('adminLogoInput');
  if (!input.files[0]) return showToast('Please select an image', 'error');
  const reader = new FileReader();
  reader.onload = async (e) => {
    const res = await api('POST', 'admin/settings/logo', { logo: e.target.result });
    if (res.success) {
      localStorage.setItem('admin_company_logo', res.data.logo_url);
      const storedLogo = res.data.logo_url;
      const lp = document.getElementById('logoPreview');
      if (lp) lp.innerHTML = `<img src="${hrproMediaUrl(storedLogo)}" style="max-width:100%; max-height:100%;">`;
      const sidebarLogoText = document.querySelector('.sidebar-logo-text');
      if (sidebarLogoText) sidebarLogoText.innerHTML = `<img src="${hrproMediaUrl(storedLogo)}" style="max-height:40px; max-width: 140px; border-radius:4px;" alt="Company Logo">`;
      showToast(res.message, 'success');
    } else {
      showToast(res.message, 'error');
    }
  };
  reader.readAsDataURL(input.files[0]);
}

function clearAdminSessionStorage() {
  localStorage.removeItem('admin_token');
  localStorage.removeItem('admin_company_id');
  localStorage.removeItem('admin_company_name');
  localStorage.removeItem('admin_subscription_status');
  localStorage.removeItem('admin_account_close_at');
  localStorage.removeItem('admin_mobile_app_url');
  localStorage.removeItem('admin_permissions');
  localStorage.removeItem('admin_type');
  localStorage.removeItem('emp_token');
}

function logout() {
  stopAdminAlertPolling();
  if (adminSessionRefreshTimer) {
    clearInterval(adminSessionRefreshTimer);
    adminSessionRefreshTimer = null;
  }
  clearAdminSessionStorage();
  location.reload();
}

/** Leave admin session and open employee login (same device — switch app / account). */
function goEmployeeLoginPortal() {
  stopAdminAlertPolling();
  if (adminSessionRefreshTimer) {
    clearInterval(adminSessionRefreshTimer);
    adminSessionRefreshTimer = null;
  }
  clearAdminSessionStorage();
  window.location.href = '/hr/login.html';
}

function getLocalISO(date) {
  const y = date.getFullYear();
  const m = String(date.getMonth() + 1).padStart(2, '0');
  const d = String(date.getDate()).padStart(2, '0');
  return `${y}-${m}-${d}`;
}

function setDefaultDates() {
  const now = new Date();
  const today = getLocalISO(now);
  const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
  document.getElementById('filterFrom').value = today;
  document.getElementById('filterTo').value = today;
  if (document.getElementById('finalReportFrom')) {
    document.getElementById('finalReportFrom').value = getLocalISO(monthStart);
    document.getElementById('finalReportTo').value = getLocalISO(now);
  }
}

const DEFAULT_AVATAR = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%238899b4'%3E%3Cpath d='M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z'/%3E%3C/svg%3E";
function handleImgError(img) {
  img.onerror = null;
  img.src = DEFAULT_AVATAR;
}

/** Normalize stored relative paths (slashes, optional uploads/ prefix). */
function hrproNormalizeUploadPath(path) {
  if (!path || typeof path !== 'string') return '';
  let p = path.trim().replace(/\\/g, '/');
  if (p.indexOf('..') !== -1) return '';
  if (/^https?:\/\//i.test(p) || p.indexOf('data:') === 0) return p;
  p = p.replace(/^\/+/, '');
  if (!/^uploads\//i.test(p) && /^(selfies\/|employees\/|branches\/)/i.test(p)) {
    p = 'uploads/' + p.replace(/^\/+/, '');
  }
  return p;
}

/** Absolute URL for stored paths (uploads/...) so images work under /hr/ and subfolders. */
function hrproMediaUrl(path) {
  const norm = hrproNormalizeUploadPath(path);
  if (!norm || typeof norm !== 'string') return '';
  if (/^https?:\/\//i.test(norm) || norm.indexOf('data:') === 0) return norm;
  if (typeof window.hrproResolveUploadUrl === 'function') return window.hrproResolveUploadUrl(norm);
  try {
    return new URL(norm, window.location.href).href;
  } catch (e) {
    return norm;
  }
}

// ── API ───────────────────────────────────────────────────────────
async function api(method, endpoint, body = null) {
  const opts = {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + (authToken||'') }
  };
  const payload = body ? { ...body } : {};
  if (method !== 'POST') payload._method = method;
  opts.body = JSON.stringify(payload);
  try {
    const r = await fetch(`${API}?route=${encodeURIComponent(endpoint)}`, opts);
    return await r.json();
  } catch(e) { return {success:false, message:'Network error'}; }
}

// ── PWA + ADMIN ALERTS ────────────────────────────────────────────
let adminAlertPollTimer = null;
let adminLastUnread = null;
let adminNotifItems = [];
let adminNotifUnreadCount = 0;

function hrproWatchSwUpdates(reg) {
  if (!reg || typeof reg.update !== 'function') return;
  const THROTTLE_MS = 30 * 60 * 1000;
  const INTERVAL_MS = 4 * 60 * 60 * 1000;
  let lastProbe = 0;
  const probeThrottled = () => {
    const now = Date.now();
    if (now - lastProbe < THROTTLE_MS) return;
    lastProbe = now;
    reg.update().catch(() => {});
  };
  reg.update().catch(() => {});
  lastProbe = Date.now();
  document.addEventListener('visibilitychange', () => {
    if (document.visibilityState === 'visible') probeThrottled();
  });
  window.addEventListener('focus', probeThrottled);
  setInterval(() => { reg.update().catch(() => {}); }, INTERVAL_MS);
}

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/hr/sw.js', { scope: '/hr/' })
    .then((reg) => {
      hrproWatchSwUpdates(reg);
      ensureAdminPushSubscription().catch(() => {});
    })
    .catch(() => {});
  navigator.serviceWorker.addEventListener('message', (event) => {
    const d = event.data;
    if (d && d.type === 'HRPRO_PUSH_NAV' && d.url) {
      window.location.href = d.url;
      return;
    }
    if (d && d.type === 'HRPRO_SW_UPDATED') {
      try {
        const k = 'hrpro_sw_updated_once';
        if (sessionStorage.getItem(k) !== '1') {
          sessionStorage.setItem(k, '1');
          window.location.reload();
        }
      } catch (e) {
        window.location.reload();
      }
    }
  });
}

async function ensureAdminPushSubscription() {
  try {
    if (!('serviceWorker' in navigator) || !('PushManager' in window) || !('Notification' in window)) return;
    if (!(location.protocol === 'https:' || location.hostname === 'localhost')) return;
    const vr = await api('GET', 'admin/push-vapid-public');
    if (!vr || !vr.success || !vr.data || !vr.data.publicKey) return;
    if (!vr.data.webpush_library_loaded) return;

    let perm = Notification.permission;
    if (perm === 'denied') return;
    if (perm === 'default') {
      const promptOnceKey = 'hrpro_admin_push_prompted';
      try {
        if (sessionStorage.getItem(promptOnceKey) === '1') return;
        sessionStorage.setItem(promptOnceKey, '1');
      } catch (e) { /* ignore */ }
      try {
        perm = await Notification.requestPermission();
      } catch (e) {
        return;
      }
      if (perm !== 'granted') return;
    }

    const reg = await navigator.serviceWorker.ready;
    let sub = await reg.pushManager.getSubscription();
    if (!sub) {
      const key = urlBase64ToUint8Array(vr.data.publicKey);
      sub = await reg.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: key });
    }
    if (sub) {
      try { await api('POST', 'admin/push-subscribe', { subscription: sub.toJSON() }); } catch (e) { /* ignore */ }
      try { refreshAdminPushButtonsInSettings(); } catch (e) { /* ignore */ }
    }
  } catch (e) {
    try { console.warn('ensureAdminPushSubscription:', e && e.message ? e.message : e); } catch (ee) { /* ignore */ }
  }
}

window.addEventListener('beforeinstallprompt', (e) => {
  e.preventDefault();
  window._adminDeferredPrompt = e;
  refreshAdminInstallButtons();
});
window.addEventListener('appinstalled', () => {
  window._adminDeferredPrompt = null;
  refreshAdminInstallButtons();
});

function refreshAdminInstallButtons() {
  const standalone = window.matchMedia('(display-mode: standalone)').matches
    || window.navigator.standalone === true;
  const canPwa = location.protocol === 'https:' || location.hostname === 'localhost';
  const showBase = !standalone && canPwa;
  const side = document.getElementById('adminPwaInstallBtnSide');
  const mob = document.getElementById('adminPwaInstallBtnMob');
  const set = document.getElementById('adminPwaInstallBtnSettings');
  const loginBtn = document.getElementById('adminPwaInstallBtnLogin');
  const loginHint = document.getElementById('adminPwaDualHintLogin');
  if (side) side.style.display = showBase ? 'inline-flex' : 'none';
  if (mob) mob.style.display = (showBase && window._adminDeferredPrompt) ? 'flex' : 'none';
  if (set) set.style.display = showBase ? 'inline-flex' : 'none';
  if (loginBtn) loginBtn.style.display = showBase ? 'inline-flex' : 'none';
  if (loginHint) loginHint.style.display = showBase ? 'block' : 'none';
}

async function installAdminPWA() {
  const standalone = window.matchMedia('(display-mode: standalone)').matches
    || window.navigator.standalone === true;
  if (standalone) {
    showToast(t('pwa_already_installed'), 'info');
    return;
  }
  if (!window._adminDeferredPrompt) {
    showToast(t('pwa_install_manual_hint'), 'warning');
    return;
  }
  const dp = window._adminDeferredPrompt;
  window._adminDeferredPrompt = null;
  dp.prompt();
  await dp.userChoice;
  refreshAdminInstallButtons();
}

function updateAdminNotifBell(n) {
  const v = n > 99 ? '99+' : String(n);
  ['adminNotifBadgeMob', 'adminNotifBadgeSide'].forEach(id => {
    const el = document.getElementById(id);
    if (!el) return;
    el.textContent = v;
    el.style.display = n > 0 ? 'flex' : 'none';
  });
}

async function adminPollAlerts() {
  if (!authToken) return;
  const [r, st] = await Promise.all([
    api('GET', 'admin/notifications/count'),
    api('GET', 'dashboard/stats')
  ]);
  if (st.success) {
    const el = document.getElementById('ds-leaves');
    if (el) el.textContent = st.data.pending_leaves;
    const badge = document.getElementById('leaveBadge');
    const pc = st.data.pending_leaves || 0;
    if (badge) {
      if (pc > 0) {
        badge.textContent = pc;
        badge.style.display = 'inline';
      } else {
        badge.style.display = 'none';
      }
    }
  }
  if (!r.success) return;
  const u = r.data.unread || 0;
  if (adminLastUnread !== null && u > adminLastUnread && 'Notification' in window && Notification.permission === 'granted') {
    const list = await api('GET', 'admin/notifications', { limit: 1 });
    const item = list.success && list.data && list.data[0] ? list.data[0] : null;
    try {
      new Notification(item ? item.title : 'HR Pro', {
        body: item && item.body ? String(item.body).slice(0, 160) : '',
        tag: 'hrpro-admin-' + (item ? item.id : Date.now())
      });
    } catch (e) { /* ignore */ }
  }
  adminLastUnread = u;
  updateAdminNotifBell(u);
}

function startAdminAlertPolling() {
  if (adminAlertPollTimer) clearInterval(adminAlertPollTimer);
  adminLastUnread = null;
  adminPollAlerts();
  adminAlertPollTimer = setInterval(adminPollAlerts, 45000);
}

function stopAdminAlertPolling() {
  if (adminAlertPollTimer) {
    clearInterval(adminAlertPollTimer);
    adminAlertPollTimer = null;
  }
}

async function openAdminNotifModal() {
  openModal('adminNotifModal');
  const box = document.getElementById('adminNotifList');
  if (box) box.innerHTML = `<div style="padding:20px;color:var(--text-muted);text-align:center;">${t('loading')}</div>`;
  const res = await api('GET', 'admin/notifications', { limit: 80, include_archived: true });
  if (!res.success || !Array.isArray(res.data)) {
    if (box) box.innerHTML = `<div style="padding:20px;color:var(--text-muted);">${res.message || '—'}</div>`;
    return;
  }
  adminNotifItems = res.data.slice();
  adminNotifUnreadCount = adminNotifItems.filter(row => row && (row.is_read == 0 || row.is_read === false) && !(row.is_deleted == 1 || row.is_deleted === true)).length;
  updateAdminNotifUnreadLabel();
  renderAdminNotifList();
}

function updateAdminNotifUnreadLabel() {
  const label = document.getElementById('adminNotifUnreadLabel');
  if (!label) return;
  label.textContent = adminNotifUnreadCount > 0 ? `(${t('notif_unread_count', { n: adminNotifUnreadCount })})` : '';
}

function renderAdminNotifList() {
  const box = document.getElementById('adminNotifList');
  if (!box) return;
  const filterEl = document.getElementById('adminNotifFilter');
  const mode = filterEl ? String(filterEl.value || 'all') : 'all';
  const filtered = adminNotifItems.filter(row => {
    if (!row) return false;
    const archived = row.is_deleted == 1 || row.is_deleted === true;
    if (mode === 'archived') return archived;
    if (mode === 'unread') return !archived && (row.is_read == 0 || row.is_read === false);
    return !archived;
  });
  const markAllBtn = document.getElementById('adminNotifMarkAllBtn');
  const deleteAllBtn = document.getElementById('adminNotifDeleteAllBtn');
  const purgeAllBtn = document.getElementById('adminNotifPurgeAllBtn');
  const isArchivedMode = mode === 'archived';
  if (markAllBtn) markAllBtn.style.display = isArchivedMode ? 'none' : 'inline-flex';
  if (deleteAllBtn) deleteAllBtn.style.display = isArchivedMode ? 'none' : 'inline-flex';
  if (purgeAllBtn) purgeAllBtn.style.display = isArchivedMode ? 'inline-flex' : 'none';
  if (!filtered.length) {
    box.innerHTML = `<div style="padding:24px;color:var(--text-muted);text-align:center;">${t('admin_notif_empty')}</div>`;
    return;
  }
  box.innerHTML = filtered.map(row => {
    const read = row.is_read == 1 || row.is_read === true;
    const archived = row.is_deleted == 1 || row.is_deleted === true;
    const body = String(row.body || '').replace(/</g, '&lt;').replace(/\n/g, '<br>');
    const actionUrl = String(row.action_url || '').trim();
    const notifType = String(row.type || '').replace(/"/g, '&quot;');
    const encUrl = actionUrl ? encodeURIComponent(actionUrl) : '';
    const cursor = actionUrl ? 'cursor:pointer;' : '';
    return `<div class="admin-notif-row" data-id="${row.id}" data-type="${notifType}" data-is-read="${read ? '1' : '0'}" data-action-url="${encUrl}" style="padding:12px;border:1px solid var(--border);border-radius:10px;margin-bottom:10px;background:${read ? 'transparent' : 'var(--surface2)'};${cursor}" role="${actionUrl ? 'button' : 'presentation'}" tabindex="${actionUrl ? '0' : '-1'}">
      <div style="display:flex;justify-content:space-between;align-items:flex-start;gap:8px;">
        <div style="font-weight:600;margin-bottom:4px;">${String(row.title || '').replace(/</g, '&lt;')}</div>
        <div style="display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end;">
          ${archived ? `<button type="button" class="btn btn-ghost notif-hard-delete-btn" data-id="${row.id}" style="padding:5px 9px;font-size:11px;color:var(--danger);">${t('delete_permanent')}</button>`
            : `${read ? '' : `<button type="button" class="btn btn-ghost notif-mark-read-btn" data-id="${row.id}" style="padding:5px 9px;font-size:11px;">${t('mark_read')}</button>`}
          <button type="button" class="btn btn-ghost notif-delete-btn" data-id="${row.id}" style="padding:5px 9px;font-size:11px;color:var(--danger);">${t('delete_notif')}</button>`}
        </div>
      </div>
      <div style="color:var(--text-muted);font-size:13px;">${body}</div>
      <div style="font-size:11px;color:var(--text-muted);margin-top:8px;">${row.created_at || ''}</div>
    </div>`;
  }).join('');
}

async function markAllAdminNotifRead() {
  const res = await api('PUT', 'admin/notifications/mark-read', { all: true });
  if (res.success) {
    showToast(res.message || t('notif_read_done'), 'success');
    adminLastUnread = 0;
    updateAdminNotifBell(0);
    adminNotifItems = adminNotifItems.map(row => ({ ...row, is_read: 1 }));
    adminNotifUnreadCount = 0;
    updateAdminNotifUnreadLabel();
    renderAdminNotifList();
    await openAdminNotifModal();
  } else {
    showToast(res.message, 'error');
  }
}

async function markAdminNotifRead(id) {
  const notifId = Number(id);
  if (!notifId) return;
  const res = await api('PUT', 'admin/notifications/mark-read', { ids: [notifId] });
  if (!res.success) {
    showToast(res.message || 'Error', 'error');
    return;
  }
  adminNotifItems = adminNotifItems.map(row => (Number(row.id) === notifId ? { ...row, is_read: 1 } : row));
  adminNotifUnreadCount = adminNotifItems.filter(row => row && (row.is_read == 0 || row.is_read === false) && !(row.is_deleted == 1 || row.is_deleted === true)).length;
  adminLastUnread = adminNotifUnreadCount;
  updateAdminNotifBell(adminNotifUnreadCount);
  updateAdminNotifUnreadLabel();
  renderAdminNotifList();
  showToast(t('notif_read_done'), 'success');
}

async function deleteAdminNotif(id) {
  const notifId = Number(id);
  if (!notifId) return;
  const res = await api('PUT', 'admin/notifications/delete', { ids: [notifId] });
  if (!res.success) {
    showToast(res.message || 'Error', 'error');
    return;
  }
  adminNotifItems = adminNotifItems.map(row => (Number(row.id) === notifId ? { ...row, is_deleted: 1 } : row));
  adminNotifUnreadCount = adminNotifItems.filter(row => row && (row.is_read == 0 || row.is_read === false) && !(row.is_deleted == 1 || row.is_deleted === true)).length;
  adminLastUnread = adminNotifUnreadCount;
  updateAdminNotifBell(adminNotifUnreadCount);
  updateAdminNotifUnreadLabel();
  renderAdminNotifList();
  showToast(t('notif_delete_done'), 'success');
}

async function hardDeleteAdminNotif(id) {
  const notifId = Number(id);
  if (!notifId) return;
  if (!window.confirm(t('confirm_delete_perm_notif'))) return;
  const res = await api('PUT', 'admin/notifications/purge', { ids: [notifId] });
  if (!res.success) {
    showToast(res.message || 'Error', 'error');
    return;
  }
  adminNotifItems = adminNotifItems.filter(row => Number(row.id) !== notifId);
  adminNotifUnreadCount = adminNotifItems.filter(row => row && (row.is_read == 0 || row.is_read === false) && !(row.is_deleted == 1 || row.is_deleted === true)).length;
  adminLastUnread = adminNotifUnreadCount;
  updateAdminNotifBell(adminNotifUnreadCount);
  updateAdminNotifUnreadLabel();
  renderAdminNotifList();
  showToast(t('notif_purge_done'), 'success');
}

async function hardDeleteAllArchivedAdminNotif() {
  if (!window.confirm(t('confirm_delete_perm_all_notif'))) return;
  const res = await api('PUT', 'admin/notifications/purge', { all: true });
  if (!res.success) {
    showToast(res.message || 'Error', 'error');
    return;
  }
  adminNotifItems = adminNotifItems.filter(row => !(row && (row.is_deleted == 1 || row.is_deleted === true)));
  adminNotifUnreadCount = adminNotifItems.filter(row => row && (row.is_read == 0 || row.is_read === false) && !(row.is_deleted == 1 || row.is_deleted === true)).length;
  adminLastUnread = adminNotifUnreadCount;
  updateAdminNotifBell(adminNotifUnreadCount);
  updateAdminNotifUnreadLabel();
  renderAdminNotifList();
  showToast(t('notif_purge_all_done'), 'success');
}

async function deleteAllAdminNotif() {
  if (!window.confirm(t('confirm_delete_all_notif'))) return;
  const res = await api('PUT', 'admin/notifications/delete', { all: true });
  if (!res.success) {
    showToast(res.message || 'Error', 'error');
    return;
  }
  adminNotifItems = adminNotifItems.map(row => ({ ...row, is_deleted: 1 }));
  adminNotifUnreadCount = 0;
  adminLastUnread = 0;
  updateAdminNotifBell(0);
  updateAdminNotifUnreadLabel();
  renderAdminNotifList();
  showToast(t('notif_delete_all_done'), 'success');
}

function urlBase64ToUint8Array(base64String) {
  const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
  const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
  const rawData = window.atob(base64);
  const outputArray = new Uint8Array(rawData.length);
  for (let i = 0; i < rawData.length; ++i) outputArray[i] = rawData.charCodeAt(i);
  return outputArray;
}

async function refreshAdminPushButtonsInSettings() {
  const subBtn = document.getElementById('adminPushSubscribeBtn');
  const unsubBtn = document.getElementById('adminPushUnsubscribeBtn');
  if (!subBtn || !unsubBtn) return;
  let hasSub = false;
  try {
    if ('serviceWorker' in navigator) {
      const reg = await navigator.serviceWorker.ready;
      const sub = await reg.pushManager.getSubscription();
      hasSub = !!sub;
    }
  } catch (e) { /* ignore */ }
  unsubBtn.style.display = hasSub ? 'inline-flex' : 'none';
}

async function subscribeAdminPush() {
  if (!('serviceWorker' in navigator) || !('PushManager' in window)) {
    showToast(t('admin_push_need_https'), 'error');
    return;
  }
  if (!(location.protocol === 'https:' || location.hostname === 'localhost')) {
    showToast(t('admin_push_need_https'), 'error');
    return;
  }
  const vr = await api('GET', 'admin/push-vapid-public');
  if (!vr.success || !vr.data || !vr.data.publicKey) {
    showToast(t('admin_push_server_off'), 'warning');
    return;
  }
  if (!vr.data.webpush_library_loaded) {
    showToast(t('admin_push_need_composer'), 'warning');
  }
  const perm = await Notification.requestPermission();
  if (perm !== 'granted') {
    showToast(t('admin_push_denied'), 'error');
    return;
  }
  try {
    const reg = await navigator.serviceWorker.ready;
    const key = urlBase64ToUint8Array(vr.data.publicKey);
    const sub = await reg.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: key });
    const res = await api('POST', 'admin/push-subscribe', { subscription: sub.toJSON() });
    if (res.success) {
      showToast(t('admin_push_subscribed'), 'success');
      await refreshAdminPushButtonsInSettings();
    } else {
      showToast(res.message, 'error');
    }
  } catch (e) {
    showToast(String(e.message || e), 'error');
  }
}

async function unsubscribeAdminPush() {
  try {
    const reg = await navigator.serviceWorker.ready;
    const sub = await reg.pushManager.getSubscription();
    if (sub) {
      const ep = sub.endpoint;
      await api('POST', 'admin/push-unsubscribe', { endpoint: ep });
      await sub.unsubscribe();
    }
    showToast('OK', 'success');
  } catch (e) { /* ignore */ }
  await refreshAdminPushButtonsInSettings();
}

async function sendAdminTestPush() {
  try {
    const res = await api('POST', 'admin/push-test', {});
    if (res && res.success) {
      const n = (res.data && typeof res.data.subscribers === 'number') ? res.data.subscribers : 0;
      showToast(res.message || ('Test push sent to ' + n + ' device(s)'), 'success');
    } else {
      showToast((res && res.message) ? res.message : 'Failed to send test push', 'error');
    }
  } catch (e) {
    showToast(String((e && e.message) || e), 'error');
  }
}

async function openAdminSettingsModal() {
  openModal('settingsModal');
  refreshAdminPushButtonsInSettings();
  _companyWelcomeTemplate = null; // reset cache so next invite loads fresh
  try {
    const resMu = await api('GET', 'admin/mobile-app-url');
    if (resMu.success && resMu.data) {
      const mInp = document.getElementById('adminMobileAppUrl');
      if (mInp) mInp.value = resMu.data.mobile_app_url != null ? String(resMu.data.mobile_app_url) : '';
    }
  } catch (e) { /* ignore */ }
  try {
    const res = await api('GET', 'admin/welcome-email-template');
    if (res.success && res.data) {
      const ta = document.getElementById('adminWelcomeEmailTemplate');
      const raw = res.data.template != null ? String(res.data.template).trim() : '';
      const def = res.data.default_template || '';
      if (ta) ta.value = raw !== '' ? res.data.template : def;
      const ph = document.getElementById('adminWelcomePlaceholders');
      if (ph) {
        const pt = res.data.placeholders_text;
        if (pt) ph.textContent = pt;
        else if (Array.isArray(res.data.placeholders)) ph.textContent = res.data.placeholders.join(' ');
      }
    }
  } catch (e) { /* ignore */ }
}

// ── TABS ──────────────────────────────────────────────────────────
function toggleSidebar(){
  const sb = document.querySelector('.sidebar');
  const ov = document.querySelector('.sidebar-overlay');
  const isOpening = !sb.classList.contains('open');
  // PERF: hint GPU before animation starts, clean up after
  if (isOpening) sb.style.willChange = 'transform';
  sb.classList.toggle('open');
  ov.classList.toggle('open');
  if (!isOpening) {
    sb.addEventListener('transitionend', () => { sb.style.willChange = ''; }, { once: true });
  }
}

function showTab(name, el, opts) {
  opts = opts || {};
  if (!canAdmin('read', name)) {
    showToast(t('forbidden') || 'You do not have permission for this screen', 'error');
    return;
  }
  const tabEl = document.getElementById('tab-' + name);
  if (!tabEl) {
    showToast(t('unknown_error') || 'Tab not found', 'error');
    return;
  }
  document.querySelectorAll('[id^="tab-"]').forEach(t => t.style.display = 'none');
  tabEl.style.display = 'block';
  
  // Sidebar items
  document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
  // Bottom nav items
  document.querySelectorAll('.bottom-nav-item').forEach(n => n.classList.remove('active'));
  
  // Find matching items in both navigations
  if (el) {
    el.classList.add('active');
    // Also find the "sibling" in the other navigation (e.g. if el is in sidebar, find in bottom nav)
    const sidebarMatch = document.querySelector(`.sidebar-nav .nav-item[onclick*="'${name}'"]`);
    const bottomMatch = document.querySelector(`.bottom-nav .bottom-nav-item[onclick*="'${name}'"]`);
    if (sidebarMatch) sidebarMatch.classList.add('active');
    if (bottomMatch) bottomMatch.classList.add('active');
  }
  
  if(window.innerWidth <= 768){
    const sb = document.querySelector('.sidebar');
    const ov = document.querySelector('.sidebar-overlay');
    if(sb) sb.classList.remove('open');
    if(ov) ov.classList.remove('open');
  }

  const loaders = {
    dashboard: loadDashboard, employees: loadEmployees,
    contacts: loadAdminContacts,
    branches: loadBranches,
    'official-holidays': loadOfficialHolidays,
    policies: loadPolicies,
    departments: loadDepartments, attendance: loadAttendance, 
    leave: loadLeave, qr: generateQR,
    'final-report': () => { void ensureFrBulkDeptsLoaded(); return loadEmployees(); },
    'final-decision': loadFinalDecisionTab,
    'leave-settlement': loadLeaveSettlementTab,
    'deductions-report': loadDeductionsReportTab,
    'leave-overview': loadLeaveOverview,
    'company-news': loadCompanyNewsAdmin
  };
  if (!opts.skipLoader && loaders[name]) loaders[name]();
  applyCrudVisibilityWithRetries(name);
}

// ── DASHBOARD ─────────────────────────────────────────────────────
function hrproTodayLocal() {
  const n = new Date();
  return n.getFullYear() + '-' + String(n.getMonth() + 1).padStart(2, '0') + '-' + String(n.getDate()).padStart(2, '0');
}

function hrproDashboardDateMin() {
  const minD = new Date();
  minD.setDate(minD.getDate() - 400);
  return minD.getFullYear() + '-' + String(minD.getMonth() + 1).padStart(2, '0') + '-' + String(minD.getDate()).padStart(2, '0');
}

function clampDashboardDateStr(v) {
  const s = String(v || '').trim();
  if (!/^\d{4}-\d{2}-\d{2}$/.test(s)) return null;
  const today = hrproTodayLocal();
  const minStr = hrproDashboardDateMin();
  if (s > today) return today;
  if (s < minStr) return minStr;
  return s;
}

function shiftDashboardDay(delta) {
  if (delta > 0) return;
  if (!window._dashboardDate) window._dashboardDate = hrproTodayLocal();
  const cur = new Date(window._dashboardDate + 'T12:00:00');
  cur.setDate(cur.getDate() + delta);
  const iso = cur.getFullYear() + '-' + String(cur.getMonth() + 1).padStart(2, '0') + '-' + String(cur.getDate()).padStart(2, '0');
  const today = hrproTodayLocal();
  if (iso > today) return;
  if (iso < hrproDashboardDateMin()) return;
  window._dashboardDate = iso;
  loadDashboard();
}

function goDashboardToday() {
  window._dashboardDate = hrproTodayLocal();
  loadDashboard();
}

function onDashboardDatePicked() {
  const el = document.getElementById('dashboardDatePick');
  if (!el) return;
  if (!el.value) {
    el.value = window._dashboardDate || hrproTodayLocal();
    return;
  }
  const clamped = clampDashboardDateStr(el.value);
  if (!clamped) {
    el.value = window._dashboardDate || hrproTodayLocal();
    return;
  }
  window._dashboardDate = clamped;
  loadDashboard();
}

function updateDashboardDateControls() {
  const pick = document.getElementById('dashboardDatePick');
  const todayBtn = document.getElementById('dashboardTodayBtn');
  const d = window._dashboardDate || hrproTodayLocal();
  const today = hrproTodayLocal();
  if (pick) {
    pick.value = d;
    pick.min = hrproDashboardDateMin();
    pick.max = today;
  }
  if (todayBtn) todayBtn.disabled = (d === today);
  const hint = document.getElementById('dashboardPendingHint');
  if (hint) {
    hint.style.display = d < today ? 'block' : 'none';
  }
}

async function loadDashboard() {
  if (!window._dashboardDate) {
    window._dashboardDate = hrproTodayLocal();
  }
  const dateParam = { date: window._dashboardDate };
  const [stats, branchActivity] = await Promise.all([
    api('GET', 'dashboard/stats', dateParam),
    api('GET', 'admin/branches-activity', dateParam)
  ]);

  if (stats.success) {
    if (stats.data.stats_date) {
      window._dashboardDate = stats.data.stats_date;
    }
    document.getElementById('ds-total').textContent = stats.data.total_employees;
    document.getElementById('ds-checkins').textContent = stats.data.checkins_today;
    document.getElementById('ds-alerts').textContent = stats.data.alerts_today;
    document.getElementById('ds-leaves').textContent = stats.data.pending_leaves;
  }

  updateDashboardDateControls();

  if (branchActivity.success) {
    const raw = branchActivity.data;
    let branches;
    let unassigned = null;
    if (Array.isArray(raw)) {
      branches = raw;
    } else {
      branches = raw.branches || [];
      unassigned = raw.unassigned || null;
      if (raw.activity_date) {
        window._dashboardDate = raw.activity_date;
      }
    }
    window._branchActivityData = branches;
    window._branchActivityUnassigned = unassigned;
    renderBranchMonitor(branches);
    updateDashboardDateControls();
  }
}

function openEmployeesByBranchModal() {
  const branches = window._branchActivityData;
  const u = window._branchActivityUnassigned;
  if (!Array.isArray(branches)) {
    showToast(t('emp_by_branch_no_data'), 'error');
    return;
  }
  if (!branches.length && !(u && u.count > 0)) {
    showToast(t('emp_by_branch_no_data'), 'error');
    return;
  }
  const esc = (s) => String(s ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;');
  const rows = [];
  branches.forEach((b) => {
    rows.push({ name: b.name, count: (b.employees || []).length });
  });
  if (u && u.count > 0) {
    rows.push({ name: t('branch_unassigned'), count: u.count });
  }
  rows.sort((a, b) => String(a.name).localeCompare(String(b.name), undefined, { sensitivity: 'base' }));
  const body = document.getElementById('employeesByBranchBody');
  if (!body) return;
  body.innerHTML = rows.map((r) => `<tr><td>${esc(r.name)}</td><td style="font-family:var(--mono);font-weight:700;">${r.count}</td></tr>`).join('');
  const title = document.getElementById('employeesByBranchTitle');
  if (title) title.textContent = t('emp_by_branch_modal_title');
  openModal('employeesByBranchModal');
  updateUITranslations();
}

function onDashboardTotalEmpKey(ev) {
  if (ev.key === 'Enter' || ev.key === ' ') {
    ev.preventDefault();
    openEmployeesByBranchModal();
  }
}

const ADMIN_CONTACTS_DEFAULT_AVATAR = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%238899b4'%3E%3Cpath d='M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z'/%3E%3C/svg%3E";

let _adminContactsAll = [];
let _adminContactsLoadedAt = 0;
let _adminContactsLoading = false;
let _adminContactsUiBound = false;
let _adminContactsLastQuery = '';

function adminContactsEsc(s) {
  return String(s ?? '')
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;');
}

function adminContactsDigitsOnly(v) {
  return String(v ?? '').replace(/[^\d+]/g, '').trim();
}

function adminContactsWaNumber(phone) {
  const d = String(phone ?? '').replace(/[^\d]/g, '');
  if (!d) return '';
  if (d.startsWith('01') && d.length === 11) return '20' + d.slice(1);
  if (d.startsWith('00') && d.length > 4) return d.slice(2);
  return d;
}

function adminContactsUpdateMeta(shown, total) {
  const m = document.getElementById('adminContactsMeta');
  if (m) m.textContent = t('contacts_meta', { shown: String(shown), total: String(total) });
}

function adminContactsFillFilterOptions() {
  const branchSel = document.getElementById('adminContactsBranch');
  const deptSel = document.getElementById('adminContactsDept');
  if (!branchSel || !deptSel) return;

  const prevBranch = branchSel.value || '';
  const prevDept = deptSel.value || '';

  const loc = (_cachedLang || document.documentElement.lang || 'ar') === 'en' ? 'en' : 'ar';
  const branches = Array.from(new Set(_adminContactsAll.map((r) => String(r?.branch_name || '').trim()).filter(Boolean)))
    .sort((a, b) => a.localeCompare(b, loc, { sensitivity: 'base' }));
  const depts = Array.from(new Set(_adminContactsAll.map((r) => String(r?.department_name || '').trim()).filter(Boolean)))
    .sort((a, b) => a.localeCompare(b, loc, { sensitivity: 'base' }));

  branchSel.innerHTML = `<option value="">${adminContactsEsc(t('contacts_all_branches'))}</option>`
    + branches.map((v) => `<option value="${adminContactsEsc(v)}">${adminContactsEsc(v)}</option>`).join('');
  deptSel.innerHTML = `<option value="">${adminContactsEsc(t('contacts_all_depts'))}</option>`
    + depts.map((v) => `<option value="${adminContactsEsc(v)}">${adminContactsEsc(v)}</option>`).join('');

  if (branches.includes(prevBranch)) branchSel.value = prevBranch;
  if (depts.includes(prevDept)) deptSel.value = prevDept;
}

function adminContactsApplyFilter() {
  const inp = document.getElementById('adminContactsSearch');
  const q = String(inp?.value ?? _adminContactsLastQuery ?? '').trim().toLowerCase();
  _adminContactsLastQuery = inp ? inp.value : _adminContactsLastQuery;
  const branchValue = String(document.getElementById('adminContactsBranch')?.value || '').trim();
  const deptValue = String(document.getElementById('adminContactsDept')?.value || '').trim();

  const total = Array.isArray(_adminContactsAll) ? _adminContactsAll.length : 0;
  if (!q && !branchValue && !deptValue) {
    adminContactsRenderList(_adminContactsAll, total);
    return;
  }
  const filtered = _adminContactsAll.filter((e) => {
    const name = String(e?.name ?? '').toLowerCase();
    const phone = String(e?.phone ?? '').toLowerCase();
    const branch = String(e?.branch_name ?? '').toLowerCase();
    const dept = String(e?.department_name ?? '').toLowerCase();
    const job = String(e?.job_title ?? '').toLowerCase();
    const searchMatch = !q || name.includes(q) || phone.includes(q) || branch.includes(q) || dept.includes(q) || job.includes(q);
    const branchMatch = !branchValue || String(e?.branch_name ?? '').trim() === branchValue;
    const deptMatch = !deptValue || String(e?.department_name ?? '').trim() === deptValue;
    return searchMatch && branchMatch && deptMatch;
  });
  adminContactsRenderList(filtered, total);
}

function adminContactsRenderList(data, totalCount) {
  const list = document.getElementById('adminContactsList');
  if (!list) return;
  const rows = Array.isArray(data) ? data : [];
  adminContactsUpdateMeta(rows.length, totalCount || rows.length);
  if (!rows.length) {
    list.innerHTML = `<div class="ac-empty">${adminContactsEsc(t('contacts_empty'))}</div>`;
    return;
  }
  list.innerHTML = rows.map((e) => {
    const name = adminContactsEsc(e?.name || '');
    const branch = adminContactsEsc(e?.branch_name || '');
    const dept = adminContactsEsc(e?.department_name || '');
    const job = adminContactsEsc(e?.job_title || '');
    const emailRaw = String(e?.email ?? '').trim();
    const phoneRaw = String(e?.phone ?? '').trim();
    const email = adminContactsEsc(emailRaw);
    const phone = adminContactsEsc(phoneRaw);
    const profilePic = String(e?.account_photo_url || e?.photo_url || '').trim();
    const photo = profilePic ? hrproMediaUrl(profilePic) : '';
    const photoCss = photo ? photo.replace(/'/g, '%27').replace(/\)/g, '%29') : '';
    const avatarStyle = photoCss ? `style="background-image:url('${photoCss}');"` : `style="background-image:url('${ADMIN_CONTACTS_DEFAULT_AVATAR}');"`;
    const avatarText = photoCss ? '' : (name ? name[0] : '');

    const mailHref = emailRaw ? `mailto:${encodeURIComponent(emailRaw)}` : '';
    const telHref = phoneRaw ? `tel:${encodeURIComponent(adminContactsDigitsOnly(phoneRaw) || phoneRaw)}` : '';
    const waNum = phoneRaw ? adminContactsWaNumber(phoneRaw) : '';
    const waHref = waNum ? `https://wa.me/${encodeURIComponent(waNum)}` : '';

    const mailBtn = mailHref ? `<a class="ac-link" href="${mailHref}" target="_blank" rel="noopener">${adminContactsEsc(t('contacts_mail_btn'))}</a>` : '';
    const telBtn = telHref ? `<a class="ac-link secondary" href="${telHref}"><span class="ac-phone-num">${phone}</span></a>` : '';
    const waBtn = waHref ? `<a class="ac-link secondary" href="${waHref}" target="_blank" rel="noopener">${adminContactsEsc(t('contacts_wa_btn'))}</a>` : '';

    return `
      <div class="ac-item">
        <div class="ac-avatar" ${avatarStyle}>${avatarText}</div>
        <div class="ac-main">
          <div class="ac-name">${name}</div>
          <div class="ac-org">
            ${branch ? `<span>🏢 ${adminContactsEsc(t('contacts_org_branch'))} ${branch}</span>` : ''}
            ${dept ? `<span>🧩 ${adminContactsEsc(t('contacts_org_dept'))} ${dept}</span>` : ''}
            ${job ? `<span>💼 ${adminContactsEsc(t('contacts_org_job'))} ${job}</span>` : ''}
          </div>
          <div class="ac-sub">
            ${phoneRaw ? `<span>📱 <span class="ac-phone-num">${phone}</span></span>` : ''}
            ${emailRaw ? `<span class="ac-email-chip">📧 ${email}</span>` : ''}
          </div>
        </div>
        <div class="ac-actions">
          ${mailBtn}
          ${telBtn}
          ${waBtn}
        </div>
      </div>`;
  }).join('');
}

function initAdminContactsUi() {
  if (_adminContactsUiBound) return;
  const inp = document.getElementById('adminContactsSearch');
  if (inp) {
    inp.addEventListener('input', () => adminContactsApplyFilter());
  }
  document.getElementById('adminContactsBranch')?.addEventListener('change', () => adminContactsApplyFilter());
  document.getElementById('adminContactsDept')?.addEventListener('change', () => adminContactsApplyFilter());
  _adminContactsUiBound = true;
}

async function loadAdminContacts(force) {
  initAdminContactsUi();
  const list = document.getElementById('adminContactsList');
  if (!list) return;
  const now = Date.now();
  if (!force && _adminContactsLoadedAt && (now - _adminContactsLoadedAt) < 5 * 60 * 1000 && Array.isArray(_adminContactsAll) && _adminContactsAll.length) {
    adminContactsApplyFilter();
    return;
  }
  if (_adminContactsLoading) return;
  _adminContactsLoading = true;
  list.innerHTML = `<div class="ac-empty">${adminContactsEsc(t('contacts_loading'))}</div>`;
  adminContactsUpdateMeta(0, 0);

  const res = await api('GET', 'admin/contacts');
  _adminContactsLoading = false;
  if (!res.success) {
    list.innerHTML = `<div class="ac-empty" style="color:var(--danger);border-style:solid;">${adminContactsEsc(res.message || t('unknown_error'))}</div>`;
    return;
  }
  const rows = Array.isArray(res.data) ? res.data : [];
  const activeOnly = rows.filter((r) => String(r?.is_active ?? '1') === '1' || r?.is_active === 1 || r?.is_active === true);
  const loc = (_cachedLang || document.documentElement.lang || 'ar') === 'en' ? 'en' : 'ar';
  activeOnly.sort((a, b) => String(a?.name ?? '').localeCompare(String(b?.name ?? ''), loc, { sensitivity: 'base' }));
  _adminContactsAll = activeOnly;
  _adminContactsLoadedAt = Date.now();
  adminContactsFillFilterOptions();
  adminContactsApplyFilter();
}

function _bmEsc(s) {
  return String(s ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/"/g, '&quot;');
}
function _bmAttr(s) {
  return String(s ?? '').replace(/"/g, '&quot;').replace(/</g, '');
}

function _bmTime(iso) {
  if (!iso) return '—';
  const s = String(iso);
  return s.length >= 16 ? s.substring(11, 16) : '—';
}

/** Earliest check-in first; absent (no check_in) after, then by name */
function _bmSortEmployeesForMonitor(arr) {
  const loc = (_cachedLang || document.documentElement.lang || 'ar') === 'en' ? 'en' : 'ar';
  const key = (e) => {
    const ci = e?.check_in;
    if (!ci) return Number.POSITIVE_INFINITY;
    const t = new Date(ci).getTime();
    return Number.isFinite(t) ? t : Number.POSITIVE_INFINITY;
  };
  return [...(arr || [])].sort((a, b) => {
    const ka = key(a);
    const kb = key(b);
    if (ka !== kb) return ka - kb;
    return String(a?.name ?? '').localeCompare(String(b?.name ?? ''), loc, { sensitivity: 'base' });
  });
}

function _bmTruncateReason(s, maxLen) {
  if (s == null || s === '') return '';
  let out = String(s).replace(/\s+/g, ' ').trim();
  if (out.length <= maxLen) return out;
  return out.slice(0, Math.max(0, maxLen - 1)) + '…';
}

function _bmFormatLeaveDateRange(startDate, endDate) {
  const a = (startDate != null ? String(startDate).trim() : '').slice(0, 10);
  const b = (endDate != null ? String(endDate).trim() : '').slice(0, 10);
  if (!a && !b) return '';
  if (a && b && a === b) return a;
  if (a && b) return `${t('bm_req_from')} ${a} ${t('bm_req_to')} ${b}`;
  return a || b;
}

/** One branch-monitor request: emoji + type + dates/times + reason + pending (plain text) */
function _bmFormatOneRequest(r) {
  if (!r || typeof r !== 'object') return '';
  const pend = t('bm_req_pending');
  const pendingSuf = r.status === 'pending' && pend ? ` · ${pend}` : '';
  const viewDay = (window._bmDate || '').slice(0, 10);

  if (r.kind === 'leave') {
    const lt = String(r.leave_type || 'annual').toLowerCase();
    const ltKey = lt === 'casual' ? 'casual_leave' : lt === 'sick' ? 'sick_leave' : 'annual_leave';
    const emoji = lt === 'sick' ? '🤒' : lt === 'casual' ? '📙' : '📗';
    const typeLabel = t(ltKey) || lt;
    const range = _bmFormatLeaveDateRange(r.start_date, r.end_date);
    const reason = _bmTruncateReason(r.reason, 120);
    const parts = [`${emoji} ${typeLabel}`];
    if (range) parts.push(range);
    if (reason) parts.push(reason);
    return parts.join(' · ') + pendingSuf;
  }

  if (r.kind === 'permission') {
    const mission = r.mission === true || r.mission === 1;
    const emoji = mission ? '🧭' : '⏰';
    const label = mission ? t('permission_mission_label') : t('permission');
    const from = r.from != null ? String(r.from).trim() : '';
    const to = r.to != null ? String(r.to).trim() : '';
    const timePart = from && to ? `${from}–${to}` : '';
    const reason = _bmTruncateReason(r.reason, 120);
    const sd = (r.start_date != null ? String(r.start_date).trim() : '').slice(0, 10);
    const ed = (r.end_date != null ? String(r.end_date).trim() : '').slice(0, 10);
    const parts = [`${emoji} ${label}`];
    if (timePart) parts.push(timePart);
    if (sd && ed && sd !== ed) {
      parts.push(_bmFormatLeaveDateRange(r.start_date, r.end_date));
    } else if (sd && sd !== viewDay) {
      parts.push(sd);
    }
    if (reason) parts.push(reason);
    return parts.join(' · ') + pendingSuf;
  }
  return '';
}

function _bmRequestsSubtitleHtml(reqs) {
  if (!Array.isArray(reqs) || !reqs.length) return '';
  const items = reqs.map((r) => {
    const line = _bmFormatOneRequest(r);
    return line ? `<div class="bm-req-item">${_bmEsc(line)}</div>` : '';
  }).filter(Boolean);
  if (!items.length) return '';
  return `<div class="bm-req-stack">${items.join('')}</div>`;
}

/** Open employee monthly report (admin review mode); same URL as employee list report button */
function openBranchMonitorEmployeeReport(empId, empName) {
  const tok = authToken || localStorage.getItem('admin_token');
  if (!tok) {
    showToast(t('session_expired') || 'Session expired', 'error');
    return;
  }
  const id = parseInt(String(empId), 10);
  if (!id || id <= 0) return;
  const q = new URLSearchParams();
  q.set('employee_id', String(id));
  if (empName != null && String(empName).trim() !== '') {
    q.set('name', String(empName));
  }
  const url = 'Reports/employee_report.html?' + q.toString();
  const w = window.open(url, '_blank', 'noopener,noreferrer');
  if (!w) {
    const a = document.createElement('a');
    a.href = url;
    a.target = '_blank';
    a.rel = 'noopener noreferrer';
    a.style.display = 'none';
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
  }
}

window.openBranchMonitorEmployeeReport = openBranchMonitorEmployeeReport;

/** Clicks on <tr onclick> are unreliable (especially mobile); delegate from mount */
function ensureBranchMonitorEmpRowDelegation() {
  if (window._branchMonitorEmpDelegationOk) return;
  const mount = document.getElementById('branchMonitorDeptMount');
  if (!mount) return;
  window._branchMonitorEmpDelegationOk = true;

  function activateRow(row) {
    const id = parseInt(row.getAttribute('data-bm-emp-id') || '', 10);
    if (!id) return;
    let name = '';
    try {
      const enc = row.getAttribute('data-bm-name-enc');
      name = enc ? decodeURIComponent(enc) : '';
    } catch (err) {
      name = '';
    }
    openBranchMonitorEmployeeReport(id, name);
  }

  mount.addEventListener('click', function (ev) {
    const row = ev.target.closest && ev.target.closest('tr.bm-emp-row--clickable');
    if (!row || !mount.contains(row)) return;
    activateRow(row);
  });

  mount.addEventListener('keydown', function (ev) {
    if (ev.key !== 'Enter' && ev.key !== ' ') return;
    const row = ev.target.closest && ev.target.closest('tr.bm-emp-row--clickable');
    if (!row || !mount.contains(row)) return;
    ev.preventDefault();
    activateRow(row);
  });
}

function renderBranchMonitor(data) {
  const grid = document.getElementById('branchMonitorGrid');
  if (!data || !data.length) {
    grid.innerHTML = `<div style="grid-column: 1/-1; text-align:center; padding:40px; color:var(--textM);">${t('no_branches')}</div>`;
    return;
  }
  
  grid.innerHTML = data.map(b => `
    <div class="branch-card ${b.is_active ? 'active' : 'inactive'}" onclick="openBranchActivityModal(${b.id})">
      <div class="branch-card-header">
        <div class="branch-card-title">${b.name}</div>
        <div class="branch-status-badge ${b.is_active ? 'active-badge' : 'inactive-badge'}">
          <div class="status-dot ${b.is_active ? 'active' : 'inactive'}"></div>
          ${b.is_active ? t('branch_active') : t('branch_inactive')}
        </div>
      </div>
      <div class="branch-card-stats">
        <div class="branch-stat-item">
          <div class="branch-stat-label">${t('attendance_count')}</div>
          <div class="branch-stat-value">${b.attendance_count}</div>
        </div>
      </div>
    </div>
  `).join('');
  updateUITranslations();
}

function openBranchActivityModal(branchId) {
  const branch = (window._branchActivityData || []).find(b => b.id == branchId);
  const titleBase = branch ? branch.name : ('Branch #' + branchId);
  document.getElementById('branchActivityTitle').textContent = titleBase + ' — ' + (t('employee_status_report') || 'Branch monitor');
  window._bmBranchId = branchId;
  window._bmDate = window._dashboardDate || hrproTodayLocal();
  window._bmStatusFilter = 'all';
  syncBranchMonitorFilterButtons();
  openModal('branchActivityModal');
  fetchBranchMonitorDetail();
  updateUITranslations();
}

async function fetchBranchMonitorDetail() {
  const res = await api('GET', 'admin/branch-monitor-detail', {
    branch_id: window._bmBranchId,
    date: window._bmDate
  });
  if (!res.success) {
    showToast(res.message || 'Error', 'error');
    return;
  }
  window._bmDetail = res.data;
  renderBranchMonitorDetailView();
}

function shiftBranchMonitorDay(delta) {
  const cur = new Date(window._bmDate + 'T12:00:00');
  cur.setDate(cur.getDate() + delta);
  const iso = cur.getFullYear() + '-' + String(cur.getMonth() + 1).padStart(2, '0') + '-' + String(cur.getDate()).padStart(2, '0');
  const today = hrproTodayLocal();
  if (iso > today) return;
  const min = new Date();
  min.setDate(min.getDate() - 400);
  if (cur < min) return;
  window._bmDate = iso;
  fetchBranchMonitorDetail();
}

function syncBranchMonitorFilterButtons() {
  const mode = window._bmStatusFilter || 'all';
  [['bmFilterAll', 'all'], ['bmFilterPresent', 'present'], ['bmFilterAbsent', 'absent']].forEach(([id, m]) => {
    const el = document.getElementById(id);
    if (!el) return;
    const on = mode === m;
    el.classList.toggle('btn-primary', on);
    el.classList.toggle('btn-ghost', !on);
    el.setAttribute('aria-pressed', on ? 'true' : 'false');
  });
}

function setBranchMonitorStatusFilter(mode) {
  window._bmStatusFilter = mode === 'present' || mode === 'absent' ? mode : 'all';
  syncBranchMonitorFilterButtons();
  applyBranchMonitorFilters();
}

function applyBranchMonitorFilters() {
  const q = (document.getElementById('branchMonitorSearch')?.value || '').trim().toLowerCase();
  const mode = window._bmStatusFilter || 'all';
  document.querySelectorAll('#branchMonitorDeptMount .bm-emp-row').forEach((row) => {
    const hay = (row.getAttribute('data-name-lc') || '');
    const pr = row.getAttribute('data-present');
    const nameOk = !q || hay.includes(q);
    const statOk = mode === 'all' || (mode === 'present' && pr === '1') || (mode === 'absent' && pr === '0');
    const hide = !(nameOk && statOk);
    row.classList.toggle('bm-filter-hidden', hide);
  });
  document.querySelectorAll('#branchMonitorDeptMount .bm-dept-block').forEach((blk) => {
    const vis = Array.from(blk.querySelectorAll('.bm-emp-row')).some((r) => !r.classList.contains('bm-filter-hidden'));
    blk.style.display = vis ? '' : 'none';
  });
}

function filterBranchMonitorTable() {
  applyBranchMonitorFilters();
}

async function toggleDepartmentFromMonitor(deptId, makeActive) {
  if (deptId == null || deptId === 0) return;
  const res = await api('PUT', `departments/${deptId}`, {
    is_active: makeActive ? 1 : 0
  });
  if (res.success) {
    showToast(res.message || 'Updated', 'success');
    loadDepartments();
    fetchBranchMonitorDetail();
  } else {
    showToast(res.message, 'error');
  }
}

async function toggleDepartmentRow(deptId, makeActive) {
  await toggleDepartmentFromMonitor(deptId, !!makeActive);
}

function renderBranchMonitorDetailView() {
  const data = window._bmDetail;
  const today = hrproTodayLocal();
  document.getElementById('branchMonitorDateLabel').textContent = window._bmDate;
  const nextBtn = document.getElementById('branchMonitorNextDay');
  if (nextBtn) nextBtn.disabled = (window._bmDate >= today);

  const meta = document.getElementById('branchMonitorBranchMeta');
  const br = data && data.branch;
  if (meta && br && br.photo_url) {
    meta.style.display = 'flex';
    meta.innerHTML = `<img src="${_bmAttr(hrproMediaUrl(br.photo_url))}" alt="" style="width:56px;height:56px;object-fit:cover;border-radius:10px;border:1px solid var(--border);" onerror="this.style.display='none'">`;
  } else if (meta) {
    meta.style.display = 'none';
    meta.innerHTML = '';
  }

  const mount = document.getElementById('branchMonitorDeptMount');
  if (!data || !mount) return;

  let html = '';
  const flat = Array.isArray(data.employees) ? _bmSortEmployeesForMonitor(data.employees) : null;
  if (flat !== null) {
    if (!flat.length) {
      html = `<p style="padding:16px;color:var(--textM);">${t('no_employees')}</p>`;
    } else {
      html = `<div class="table-wrapper" style="border:1px solid var(--border);border-radius:10px;background:var(--surface);">
          <table class="hrpro-responsive-table">
            <thead><tr>
              <th>${t('bm_col_status')}</th>
              <th data-i18n="employee">${t('employee')}</th>
              <th data-i18n="job_title">${t('job_title')}</th>
              <th>${t('checkin_time')}</th>
              <th>${t('checkout_time')}</th>
            </tr></thead>
            <tbody>
              ${flat.map((e) => {
                const nlc = String(e.name || '').toLowerCase();
                const profilePic = String(e.account_photo_url || e.photo_url || '').trim();
                const av = profilePic
                  ? `<img src="${_bmAttr(hrproMediaUrl(profilePic))}" onerror="handleImgError(this)" style="width:30px;height:30px;border-radius:50%;object-fit:cover;">`
                  : `<div style="width:30px;height:30px;border-radius:50%;background:var(--surface2);display:flex;align-items:center;justify-content:center;font-size:12px;color:var(--textM);">👤</div>`;
                const present = !!(e.check_in);
                const stLab = present ? t('bm_status_present') : t('bm_status_absent');
                const stSvg = present
                  ? `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><polyline points="20 6 9 17 4 12"/></svg>`
                  : `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
                const stCell = `<span class="bm-status ${present ? 'bm-status--in' : 'bm-status--out'}" title="${_bmAttr(stLab)}" aria-label="${_bmAttr(stLab)}">${stSvg}</span>`;
                const reqSub = _bmRequestsSubtitleHtml(e.requests_today);
                const repHint = _bmAttr(t('emp_att_report_review'));
                const nameEnc = encodeURIComponent(String(e.name || ''));
                return `<tr class="bm-emp-row bm-emp-row--clickable" data-bm-emp-id="${Number(e.id)}" data-bm-name-enc="${nameEnc}" data-name-lc="${_bmAttr(nlc)}" data-present="${present ? '1' : '0'}" role="button" tabindex="0" title="${repHint}">
                  <td data-label="${t('bm_col_status')}">${stCell}</td>
                  <td data-label="${t('employee')}"><div style="display:flex;align-items:flex-start;gap:10px;">${av}<div style="min-width:0;flex:1;"><strong>${_bmEsc(e.name)}</strong>${reqSub}</div></div></td>
                  <td data-label="${t('job_title')}">${_bmEsc(e.job_title || '—')}</td>
                  <td data-label="${t('checkin_time')}" style="font-family:var(--mono);">${_bmTime(e.check_in)}</td>
                  <td data-label="${t('checkout_time')}" style="font-family:var(--mono);">${_bmTime(e.check_out)}</td>
                </tr>`;
              }).join('')}
            </tbody>
          </table>
        </div>`;
    }
  } else {
    const depts = data.departments || [];
    if (!depts.length) {
      html = `<p style="padding:16px;color:var(--textM);">${t('no_employees')}</p>`;
    } else {
      depts.forEach((dept) => {
        const emps = _bmSortEmployeesForMonitor(dept.employees || []);
        const toggleBtn = dept.id != null
          ? `<button type="button" class="branch-status-badge ${dept.is_active ? 'active-badge' : 'inactive-badge'}" style="cursor:pointer;font:inherit;border:none;" onclick="event.stopPropagation();toggleDepartmentFromMonitor(${dept.id},${dept.is_active ? 0 : 1})"><span class="status-dot ${dept.is_active ? 'active' : 'inactive'}"></span>${dept.is_active ? t('dept_status_badge_active') : t('dept_status_badge_inactive')}</button>`
          : '';
        html += `<details open class="bm-dept-block" style="margin-bottom:12px;border:1px solid var(--border);border-radius:10px;background:var(--surface);">
        <summary style="cursor:pointer;padding:12px 14px;font-weight:600;list-style-position:outside;display:flex;align-items:center;gap:10px;flex-wrap:wrap;">
          <span>${_bmEsc(dept.department_name)}</span>
          <span style="flex:1"></span>
          ${toggleBtn}
          <span style="font-size:12px;color:var(--textM);font-weight:500;">${emps.length} ${t('employee') || 'employees'}</span>
        </summary>
        <div class="table-wrapper" style="border-top:1px solid var(--border);">
          <table class="hrpro-responsive-table">
            <thead><tr>
              <th>${t('bm_col_status')}</th>
              <th data-i18n="employee">${t('employee')}</th>
              <th data-i18n="job_title">${t('job_title')}</th>
              <th>${t('checkin_time')}</th>
              <th>${t('checkout_time')}</th>
            </tr></thead>
            <tbody>
              ${emps.length ? emps.map((e) => {
                const nlc = String(e.name || '').toLowerCase();
                const profilePic = String(e.account_photo_url || '').trim();
                const av = profilePic
                  ? `<img src="${_bmAttr(hrproMediaUrl(profilePic))}" onerror="handleImgError(this)" style="width:30px;height:30px;border-radius:50%;object-fit:cover;">`
                  : `<div style="width:30px;height:30px;border-radius:50%;background:var(--surface2);display:flex;align-items:center;justify-content:center;font-size:12px;color:var(--textM);">👤</div>`;
                const present = !!(e.check_in);
                const stLab = present ? t('bm_status_present') : t('bm_status_absent');
                const stSvg = present
                  ? `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><polyline points="20 6 9 17 4 12"/></svg>`
                  : `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
                const stCell = `<span class="bm-status ${present ? 'bm-status--in' : 'bm-status--out'}" title="${_bmAttr(stLab)}" aria-label="${_bmAttr(stLab)}">${stSvg}</span>`;
                const reqSub = _bmRequestsSubtitleHtml(e.requests_today);
                const repHint = _bmAttr(t('emp_att_report_review'));
                const nameEnc = encodeURIComponent(String(e.name || ''));
                return `<tr class="bm-emp-row bm-emp-row--clickable" data-bm-emp-id="${Number(e.id)}" data-bm-name-enc="${nameEnc}" data-name-lc="${_bmAttr(nlc)}" data-present="${present ? '1' : '0'}" role="button" tabindex="0" title="${repHint}">
                  <td data-label="${t('bm_col_status')}">${stCell}</td>
                  <td data-label="${t('employee')}"><div style="display:flex;align-items:flex-start;gap:10px;">${av}<div style="min-width:0;flex:1;"><strong>${_bmEsc(e.name)}</strong>${reqSub}</div></div></td>
                  <td data-label="${t('job_title')}">${_bmEsc(e.job_title || '—')}</td>
                  <td data-label="${t('checkin_time')}" style="font-family:var(--mono);">${_bmTime(e.check_in)}</td>
                  <td data-label="${t('checkout_time')}" style="font-family:var(--mono);">${_bmTime(e.check_out)}</td>
                </tr>`;
              }).join('') : `<tr><td colspan="5" style="text-align:center;padding:14px;color:var(--textM);">—</td></tr>`}
            </tbody>
          </table>
        </div>
      </details>`;
      });
    }
  }
  mount.innerHTML = html;

  ensureBranchMonitorEmpRowDelegation();
  applyBranchMonitorFilters();
}

function openAdminLeaveForEmp(empId, empName) {
  const pick = document.getElementById('adminLeaveEmpPickWrap');
  const line = document.getElementById('adminLeaveEmpLine');
  if (pick) pick.style.display = 'none';
  if (line) line.style.display = 'block';
  document.getElementById('adminLeaveEmpId').value = String(empId);
  document.getElementById('adminLeaveEmpName').textContent = empName || '';
  document.getElementById('adminLeaveRequestKind').value = 'leave';
  document.getElementById('adminLeaveType').value = 'annual';
  const today = new Date().toISOString().slice(0, 10);
  document.getElementById('adminLeaveStart').value = today;
  document.getElementById('adminLeaveEnd').value = today;
  document.getElementById('adminLeavePermDate').value = today;
  document.getElementById('adminLeavePermFrom').value = '';
  document.getElementById('adminLeavePermTo').value = '';
  document.getElementById('adminLeaveReason').value = '';
  updateAdminLeaveFormMode();
  syncAdminLeaveMissionOption();
  openModal('adminLeaveModal');
  updateUITranslations();
}

function openAdminLeaveForEmployee() {
  const id = document.getElementById('editEmpId').value;
  if (!id) return;
  const name = document.getElementById('eName').value.trim() || 'Employee';
  openAdminLeaveForEmp(id, name);
}

async function openAdminLeavePicker() {
  if (!allEmployeesData || !allEmployeesData.length) {
    const res = await api('GET', 'employees');
    if (res.success && Array.isArray(res.data)) {
      allEmployeesData = res.data;
    }
  }
  const list = allEmployeesData || [];
  const active = list.filter(e => parseInt(String(e.is_active), 10) === 1);
  const sel = document.getElementById('adminLeaveEmpSelect');
  if (!sel) return;
  if (!active.length) {
    showToast(t('no_employees') || 'No active employees', 'error');
    return;
  }
  sel.innerHTML = active.map(e => {
    const lab = `${String(e.name || '').replace(/</g, '')} (${String(e.employee_code || '').replace(/</g, '')})`;
    const wn = String(e.work_nature || 'internal').replace(/"/g, '');
    return `<option value="${e.id}" data-work-nature="${wn}">${lab}</option>`;
  }).join('');
  sel.onchange = () => syncAdminLeaveMissionOption();
  const pick = document.getElementById('adminLeaveEmpPickWrap');
  const line = document.getElementById('adminLeaveEmpLine');
  if (pick) pick.style.display = 'block';
  if (line) line.style.display = 'none';
  document.getElementById('adminLeaveEmpId').value = '';
  document.getElementById('adminLeaveRequestKind').value = 'leave';
  document.getElementById('adminLeaveType').value = 'annual';
  const today = new Date().toISOString().slice(0, 10);
  document.getElementById('adminLeaveStart').value = today;
  document.getElementById('adminLeaveEnd').value = today;
  document.getElementById('adminLeavePermDate').value = today;
  document.getElementById('adminLeavePermFrom').value = '';
  document.getElementById('adminLeavePermTo').value = '';
  document.getElementById('adminLeaveReason').value = '';
  updateAdminLeaveFormMode();
  syncAdminLeaveMissionOption();
  openModal('adminLeaveModal');
  updateUITranslations();
}

function toAdminHHMM(v) {
  const s = String(v || '').trim();
  if (!s) return '';
  const m = s.match(/^(\d{1,2}):(\d{2})(?::\d{2})?$/);
  if (!m) return '';
  const hh = parseInt(m[1], 10);
  const mm = parseInt(m[2], 10);
  if (!Number.isFinite(hh) || !Number.isFinite(mm) || hh < 0 || hh > 23 || mm < 0 || mm > 59) return '';
  return `${String(hh).padStart(2, '0')}:${String(mm).padStart(2, '0')}`;
}

function syncAdminLeaveMissionOption() {
  const opt = document.getElementById('adminLeaveKindMissionOption');
  const selKind = document.getElementById('adminLeaveRequestKind');
  if (!opt || !selKind) return;
  const pickWrap = document.getElementById('adminLeaveEmpPickWrap');
  let wn = 'internal';
  if (pickWrap && pickWrap.style.display !== 'none') {
    const empSel = document.getElementById('adminLeaveEmpSelect');
    const o = empSel && empSel.selectedOptions && empSel.selectedOptions[0];
    wn = (o && o.getAttribute('data-work-nature')) || 'internal';
  } else {
    const eid = parseInt(document.getElementById('adminLeaveEmpId')?.value || '0', 10);
    if (eid > 0) {
      const row = (allEmployeesData || []).find((e) => parseInt(String(e.id), 10) === eid);
      if (row && row.work_nature) {
        wn = String(row.work_nature);
      } else {
        const ew = document.getElementById('eWorkNature');
        const empEditId = parseInt(document.getElementById('editEmpId')?.value || '0', 10);
        if (ew && empEditId === eid) {
          wn = ew.value || 'internal';
        }
      }
    }
  }
  const isExternal = wn === 'external';
  opt.hidden = isExternal;
  opt.style.display = isExternal ? 'none' : '';
  if (isExternal && selKind.value === 'mission') {
    selKind.value = 'permission';
    updateAdminLeaveFormMode();
  }
}

function updateAdminLeaveFormMode() {
  const kind = document.getElementById('adminLeaveRequestKind')?.value || 'leave';
  const leaveBlock = document.getElementById('adminLeaveFieldsLeave');
  const permBlock = document.getElementById('adminLeaveFieldsPermission');
  const permLike = kind === 'permission' || kind === 'mission';
  if (permLike) {
    if (leaveBlock) leaveBlock.style.display = 'none';
    if (permBlock) permBlock.style.display = '';
  } else {
    if (leaveBlock) leaveBlock.style.display = '';
    if (permBlock) permBlock.style.display = 'none';
  }
}

async function submitAdminLeaveForEmployee() {
  const pickWrap = document.getElementById('adminLeaveEmpPickWrap');
  let employee_id = 0;
  if (pickWrap && pickWrap.style.display !== 'none') {
    employee_id = parseInt(document.getElementById('adminLeaveEmpSelect').value, 10);
  } else {
    employee_id = parseInt(document.getElementById('adminLeaveEmpId').value, 10);
  }
  if (!employee_id) {
    return showToast(t('admin_leave_pick_employee') || 'Choose an employee', 'error');
  }
  const kind = document.getElementById('adminLeaveRequestKind')?.value || 'leave';
  const reason = document.getElementById('adminLeaveReason').value.trim() || '—';
  let body;
  if (kind === 'permission' || kind === 'mission') {
    const d = document.getElementById('adminLeavePermDate').value;
    const pf = toAdminHHMM(document.getElementById('adminLeavePermFrom').value);
    const pt = toAdminHHMM(document.getElementById('adminLeavePermTo').value);
    if (!d || !pf || !pt) return showToast(t('fill_fields') || 'Fill required fields', 'error');
    const t1 = new Date(`${d}T${pf}:00`);
    const t2 = new Date(`${d}T${pt}:00`);
    if (!(t2 > t1)) return showToast(t('permission_time_invalid') || 'Invalid permission time range', 'error');
    body = {
      employee_id,
      request_kind: 'permission',
      leave_type: 'permission',
      start_date: d,
      end_date: d,
      permission_from: pf,
      permission_to: pt,
      reason
    };
    if (kind === 'mission') {
      body.permission_kind = 'mission';
    }
  } else {
    body = {
      employee_id,
      leave_type: document.getElementById('adminLeaveType').value,
      start_date: document.getElementById('adminLeaveStart').value,
      end_date: document.getElementById('adminLeaveEnd').value,
      reason
    };
    if (!body.start_date || !body.end_date) return showToast(t('fill_fields') || 'Fill dates', 'error');
  }
  const res = await api('POST', 'leave', body);
  if (res.success) {
    closeModal('adminLeaveModal');
    showToast(res.message || 'Submitted', 'success');
    try { await loadLeave(); } catch (err) { /* ignore */ }
  } else {
    showToast(res.message, 'error');
  }
}

// ── DEPARTMENTS ───────────────────────────────────────────────────
let departmentsList = [];
let pendingDeptDeleteId = null;
let pendingDeptDeleteCode = null;

function generateDeptDeleteSixDigitCode() {
  return String(100000 + Math.floor(Math.random() * 900000));
}

async function loadDepartments() {
  const res = await api('GET', 'departments');
  if (res.success) {
    departmentsList = res.data || [];
    const tbody = document.getElementById('deptTable');
    if (departmentsList.length === 0) {
      if (tbody) tbody.innerHTML = `<tr><td colspan="4" style="text-align:center;padding:30px;color:var(--text-muted);" data-i18n="loading">No departments found</td></tr>`;
    } else {
      if (tbody) tbody.innerHTML = departmentsList.map(d => {
        const active = Number(d.is_active) === 1;
        const activeEmp = Number(d.active_employee_count) || 0;
        const inactiveEmp = Number(d.inactive_employee_count) || 0;
        const empCountCell = `<div style="display:flex;flex-wrap:wrap;align-items:center;gap:10px 16px;font-size:15px;line-height:1.35;">
            <span style="display:inline-flex;align-items:center;gap:7px;color:var(--success);font-weight:600;">
              <span aria-hidden="true" style="font-size:17px;line-height:1;">✅</span>
              <span style="font-family:var(--mono);font-size:20px;font-weight:800;">${activeEmp}</span>
              <span style="font-size:14px;">${t('status_active')}</span>
            </span>
            <span style="display:inline-flex;align-items:center;gap:7px;color:var(--danger);font-weight:600;">
              <span aria-hidden="true" style="font-size:17px;line-height:1;">⛔</span>
              <span style="font-family:var(--mono);font-size:20px;font-weight:800;">${inactiveEmp}</span>
              <span style="font-size:14px;">${t('status_inactive')}</span>
            </span>
          </div>`;
        return `
        <tr>
          <td data-label="${t('department_name')}"><div style="font-weight:600;">${d.department_name}</div></td>
          <td data-label="${t('dept_employee_count')}">${empCountCell}</td>
          <td data-label="${t('status')}">
            <button type="button" class="btn btn-sm ${active ? 'btn-ghost' : 'btn-outline'}" onclick="toggleDepartmentRow(${d.id},${active ? 0 : 1})">${active ? (t('active') || 'Active') : (t('inactive') || 'Inactive')}</button>
          </td>
          <td data-label="${t('actions')}">
            <div class="actions-flex">
              <button class="btn-icon-sm btn-accent" onclick="openEditDepartment(${d.id})" title="${t('edit')}">
                <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
              </button>
              <button class="btn-icon-sm btn-del" onclick="deleteDepartment(${d.id})" title="${t('delete')}">
                <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg>
              </button>
            </div>
          </td>
        </tr>`;
      }).join('');
    }

    const eDept = document.getElementById('eDepartment');
    if (eDept) {
      eDept.innerHTML = `<option value="" data-i18n="no_dept">${t('no_dept')}</option>` + 
        departmentsList.map(d => {
          const inactive = Number(d.is_active) !== 1;
          const lab = inactive ? `${d.department_name} (${t('inactive') || 'inactive'})` : d.department_name;
          return `<option value="${d.id}">${lab}</option>`;
        }).join('');
    }

    const filterDept = document.getElementById('filterDept');
    const empFilterDept = document.getElementById('empFilterDept');
    const teamDeptFilter = document.getElementById('eTeamDeptFilter');
    const deptHtml = `<option value="all" data-i18n="all_depts">${t('all_depts')}</option>` + 
                     departmentsList.map(d => `<option value="${d.id}">${d.department_name}</option>`).join('');
    if (filterDept) filterDept.innerHTML = deptHtml;
    if (empFilterDept) empFilterDept.innerHTML = deptHtml;
    if (teamDeptFilter) {
      const prev = String(teamDeptFilter.value || 'all');
      teamDeptFilter.innerHTML = deptHtml;
      teamDeptFilter.value = [...teamDeptFilter.options].some(o => o.value === prev) ? prev : 'all';
      renderTeamChecklist();
    }
  }
}

// ── LEAVE POLICIES ────────────────────────────────────────────────
let policiesList = [];

function formatPolicyDeductions(p) {
  const f = parseFloat(p.deduct_full_day);
  const h = parseFloat(p.deduct_half_day);
  const q = parseFloat(p.deduct_quarter_day);
  const fv = Number.isFinite(f) ? f : 1;
  const hv = Number.isFinite(h) ? h : 0.5;
  const qv = Number.isFinite(q) ? q : 0.25;
  return `${fv} / ${hv} / ${qv}`;
}

async function loadPolicies() {
  const res = await api('GET', 'leave-policies');
  if (res.success) {
    policiesList = res.data || [];
    const tbody = document.getElementById('policyTable');
    if (tbody) {
      if (policiesList.length === 0) {
        tbody.innerHTML = `<tr><td colspan="8" style="text-align:center;padding:30px;color:var(--text-muted);">No policies found</td></tr>`;
      } else {
        tbody.innerHTML = policiesList.map(p => {
          const weekend = JSON.parse(p.weekend_days || '[]');
          const dFq = formatPolicyDeductions(p);
          const permLimit = (p.permission_monthly_limit == null || String(p.permission_monthly_limit) === '')
            ? '∞'
            : String(parseInt(String(p.permission_monthly_limit), 10));
          return `
            <tr>
              <td data-label="${t('policy_name')}"><div style="font-weight:600;">${p.policy_name}</div></td>
              <td data-label="${t('weekend_days')}"><div style="font-size:12px; color:var(--textM);">${weekend.join(', ')}</div></td>
              <td data-label="${t('annual')}"><span class="badge badge-ok">${p.annual_leave_days}</span></td>
              <td data-label="${t('casual')}"><span class="badge badge-warning">${p.casual_leave_days}</span></td>
              <td data-label="${t('sick')}"><span class="badge badge-rejected">${p.sick_leave_days}</span></td>
              <td data-label="${t('policy_deductions')}"><span style="font-family:var(--mono);font-size:12px;color:var(--textM);">${dFq}</span></td>
              <td data-label="${t('policy_permission_monthly_limit')}"><span style="font-family:var(--mono);font-size:12px;color:var(--textM);">${permLimit}</span></td>
              <td data-label="${t('actions')}">
                <div class="actions-flex">
                  <button class="btn-icon-sm btn-accent" onclick='openEditPolicy(${JSON.stringify(p)})' title="${t('edit')}">
                    <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
                  </button>
                  <button class="btn-icon-sm btn-del" onclick="deletePolicy(${p.id})" title="${t('delete')}">
                    <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg>
                  </button>
                </div>
              </td>
            </tr>
          `;
        }).join('');
      }
    }

    const ePolicy = document.getElementById('ePolicy');
    if (ePolicy) {
      ePolicy.innerHTML = `<option value="">${t('select_policy')}</option>` + 
        policiesList.map(p => `<option value="${p.id}">${p.policy_name}</option>`).join('');
    }
  }
}

function openCreatePolicy() {
  document.getElementById('editPolicyId').value = '';
  document.getElementById('pName').value = '';
  document.querySelectorAll('input[name="pWeekend"]').forEach(cb => cb.checked = false);
  document.getElementById('pAnnual').value = 21;
  document.getElementById('pCasual').value = 7;
  document.getElementById('pSick').value = 15;
  document.getElementById('pPermMonthLimit').value = '';
  document.getElementById('pDeductFull').value = 1;
  document.getElementById('pDeductHalf').value = 0.5;
  document.getElementById('pDeductQuarter').value = 0.25;
  document.getElementById('pStart').value = '09:00';
  document.getElementById('pEnd').value = '17:00';
  const psd = document.getElementById('pSalaryDeductEnabled');
  if (psd) psd.checked = true;
  const psa = document.getElementById('pSalaryDeductAllowBalance');
  if (psa) psa.checked = false;
  document.getElementById('pGraceLate').value = 5;
  document.getElementById('pGraceEarly').value = 5;
  document.getElementById('pLateQ').value = '';
  document.getElementById('pLateH').value = '';
  document.getElementById('pLateF').value = '';
  document.getElementById('pEarlyQ').value = '';
  document.getElementById('pEarlyH').value = '';
  document.getElementById('pEarlyF').value = '';
  document.getElementById('pAutoLt').value = 'annual';
  document.getElementById('pAutoMax').value = 1;
  document.getElementById('pAutoCombine').checked = false;
  document.getElementById('pAutoIgnore').value = '';
  document.getElementById('policyModalTitle').textContent = t('add_policy');
  openModal('policyModal');
}

function openEditPolicy(p) {
  document.getElementById('editPolicyId').value = p.id;
  document.getElementById('pName').value = p.policy_name;
  const weekend = JSON.parse(p.weekend_days || '[]');
  document.querySelectorAll('input[name="pWeekend"]').forEach(cb => {
    cb.checked = weekend.includes(cb.value);
  });
  document.getElementById('pAnnual').value = p.annual_leave_days;
  document.getElementById('pCasual').value = p.casual_leave_days;
  document.getElementById('pSick').value = p.sick_leave_days;
  document.getElementById('pPermMonthLimit').value = (p.permission_monthly_limit == null || String(p.permission_monthly_limit) === '')
    ? ''
    : String(parseInt(String(p.permission_monthly_limit), 10));
  document.getElementById('pStart').value = p.start_time ? p.start_time.substring(0,5) : '09:00';
  document.getElementById('pEnd').value = p.end_time ? p.end_time.substring(0,5) : '17:00';
  const df = parseFloat(p.deduct_full_day);
  const dh = parseFloat(p.deduct_half_day);
  const dq = parseFloat(p.deduct_quarter_day);
  document.getElementById('pDeductFull').value = Number.isFinite(df) ? df : 1;
  document.getElementById('pDeductHalf').value = Number.isFinite(dh) ? dh : 0.5;
  document.getElementById('pDeductQuarter').value = Number.isFinite(dq) ? dq : 0.25;
  const psd = document.getElementById('pSalaryDeductEnabled');
  if (psd) psd.checked = parseInt(String(p.salary_deduct_enabled ?? 1), 10) === 1;
  const psa = document.getElementById('pSalaryDeductAllowBalance');
  if (psa) psa.checked = parseInt(String(p.salary_deduct_allow_with_balance ?? 0), 10) === 1;
  const gl = p.grace_late_minutes != null ? parseInt(String(p.grace_late_minutes), 10) : 5;
  const ge = p.grace_early_leave_minutes != null ? parseInt(String(p.grace_early_leave_minutes), 10) : 5;
  document.getElementById('pGraceLate').value = Number.isFinite(gl) ? gl : 5;
  document.getElementById('pGraceEarly').value = Number.isFinite(ge) ? ge : 5;
  const setOpt = (id, v) => {
    const el = document.getElementById(id);
    if (!el) return;
    if (v == null || v === '') el.value = '';
    else el.value = String(parseInt(String(v), 10));
  };
  setOpt('pLateQ', p.late_minutes_quarter);
  setOpt('pLateH', p.late_minutes_half);
  setOpt('pLateF', p.late_minutes_full);
  setOpt('pEarlyQ', p.early_leave_minutes_quarter);
  setOpt('pEarlyH', p.early_leave_minutes_half);
  setOpt('pEarlyF', p.early_leave_minutes_full);
  const adlt = String(p.auto_deduct_leave_type || 'annual').toLowerCase();
  document.getElementById('pAutoLt').value = ['annual', 'casual', 'sick'].includes(adlt) ? adlt : 'annual';
  const adm = parseFloat(String(p.auto_deduct_max_per_day != null ? p.auto_deduct_max_per_day : '1'));
  document.getElementById('pAutoMax').value = Number.isFinite(adm) ? adm : 1;
  document.getElementById('pAutoCombine').checked = parseInt(String(p.auto_deduct_combine_late_early ?? 0), 10) === 1;
  document.getElementById('pAutoIgnore').value = p.auto_deduct_ignore_codes != null ? String(p.auto_deduct_ignore_codes) : '';
  document.getElementById('policyModalTitle').textContent = t('policy_name');
  openModal('policyModal');
}

async function savePolicy() {
  const id = document.getElementById('editPolicyId').value;
  const weekend = Array.from(document.querySelectorAll('input[name="pWeekend"]:checked')).map(cb => cb.value);
  const body = {
    policy_name: document.getElementById('pName').value.trim(),
    weekend_days: weekend,
    annual_leave_days: document.getElementById('pAnnual').value,
    casual_leave_days: document.getElementById('pCasual').value,
    sick_leave_days: document.getElementById('pSick').value,
    permission_monthly_limit: document.getElementById('pPermMonthLimit').value.trim() === '' ? null : parseInt(document.getElementById('pPermMonthLimit').value, 10),
    start_time: document.getElementById('pStart').value,
    end_time: document.getElementById('pEnd').value,
    deduct_full_day: document.getElementById('pDeductFull').value,
    deduct_half_day: document.getElementById('pDeductHalf').value,
    deduct_quarter_day: document.getElementById('pDeductQuarter').value,
    salary_deduct_enabled: document.getElementById('pSalaryDeductEnabled')?.checked ? 1 : 0,
    salary_deduct_allow_with_balance: document.getElementById('pSalaryDeductAllowBalance')?.checked ? 1 : 0,
    grace_late_minutes: parseInt(String(document.getElementById('pGraceLate').value || '5'), 10) || 0,
    grace_early_leave_minutes: parseInt(String(document.getElementById('pGraceEarly').value || '5'), 10) || 0,
    late_minutes_quarter: document.getElementById('pLateQ').value.trim() === '' ? null : parseInt(document.getElementById('pLateQ').value, 10),
    late_minutes_half: document.getElementById('pLateH').value.trim() === '' ? null : parseInt(document.getElementById('pLateH').value, 10),
    late_minutes_full: document.getElementById('pLateF').value.trim() === '' ? null : parseInt(document.getElementById('pLateF').value, 10),
    early_leave_minutes_quarter: document.getElementById('pEarlyQ').value.trim() === '' ? null : parseInt(document.getElementById('pEarlyQ').value, 10),
    early_leave_minutes_half: document.getElementById('pEarlyH').value.trim() === '' ? null : parseInt(document.getElementById('pEarlyH').value, 10),
    early_leave_minutes_full: document.getElementById('pEarlyF').value.trim() === '' ? null : parseInt(document.getElementById('pEarlyF').value, 10),
    auto_deduct_leave_type: document.getElementById('pAutoLt').value,
    auto_deduct_max_per_day: parseFloat(String(document.getElementById('pAutoMax').value || '1')) || 1,
    auto_deduct_combine_late_early: document.getElementById('pAutoCombine').checked ? 1 : 0,
    auto_deduct_ignore_codes: document.getElementById('pAutoIgnore').value.trim() || null
  };
  if (!body.policy_name) return showToast(t('fill_fields'), 'error');

  const method = id ? 'PUT' : 'POST';
  const url = id ? `leave-policies/${id}` : 'leave-policies';
  
  const res = await api(method, url, body);
  if (res.success) {
    closeModal('policyModal');
    showToast(res.message || 'Saved', 'success');
    loadPolicies();
  } else {
    showToast(res.message, 'error');
  }
}

async function deletePolicy(id) {
  if (!confirm('Are you sure? Employees on this policy will revert to default.')) return;
  const res = await api('DELETE', `leave-policies/${id}`);
  if (res.success) {
    showToast('Policy deleted', 'success');
    loadPolicies();
  } else {
    showToast(res.message, 'error');
  }
}

// ── OFFICIAL HOLIDAYS ─────────────────────────────────────────────
function ensureOfficialHolidayYearSelect() {
  const sel = document.getElementById('officialHolidayYear');
  if (!sel || sel.dataset.built) return;
  sel.dataset.built = '1';
  const y = new Date().getFullYear();
  for (let i = y - 3; i <= y + 5; i++) {
    const o = document.createElement('option');
    o.value = String(i);
    o.textContent = String(i);
    if (i === y) o.selected = true;
    sel.appendChild(o);
  }
}

async function loadOfficialHolidays() {
  ensureOfficialHolidayYearSelect();
  const yEl = document.getElementById('officialHolidayYear');
  const y = (yEl && yEl.value) ? yEl.value : String(new Date().getFullYear());
  const tbody = document.getElementById('officialHolidayTable');
  if (!tbody) return;
  tbody.innerHTML = `<tr><td colspan="4" style="text-align:center;padding:24px;color:var(--text-muted);">${t('loading')}</td></tr>`;
  const res = await api('GET', 'official-holidays', { year: y });
  if (!res.success) {
    tbody.innerHTML = `<tr><td colspan="4" style="text-align:center;padding:24px;color:var(--danger);">${String(res.message || 'Error').replace(/</g, '')}</td></tr>`;
    return;
  }
  const rows = res.data || [];
  if (!rows.length) {
    tbody.innerHTML = `<tr><td colspan="4" style="text-align:center;padding:24px;color:var(--textM);">${t('official_holidays_empty')}</td></tr>`;
    return;
  }
  const esc = (s) => String(s ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/"/g, '&quot;');
  tbody.innerHTML = rows.map((h) => `
    <tr class="emp-main-row">
      <td data-label="${t('date')}"><span style="font-family:var(--mono);">${esc(h.holiday_date)}</span></td>
      <td data-label="${t('official_holiday_name')}"><strong>${esc(h.title)}</strong></td>
      <td data-label="${t('notes')}" style="font-size:13px;color:var(--textM);">${esc(h.notes || '—')}</td>
      <td data-label="${t('actions')}">
        <div class="actions-flex">
          <button type="button" class="btn-icon-sm btn-accent" onclick='openOfficialHolidayModal(${JSON.stringify(h)})' title="${t('edit')}"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg></button>
          <button type="button" class="btn-icon-sm btn-del" onclick="deleteOfficialHoliday(${h.id})" title="${t('delete')}"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg></button>
        </div>
      </td>
    </tr>
  `).join('');
}

function openOfficialHolidayModal(row) {
  document.getElementById('editOfficialHolidayId').value = row && row.id != null ? String(row.id) : '';
  document.getElementById('ohDate').value = row && row.holiday_date ? row.holiday_date : '';
  document.getElementById('ohTitle').value = row && row.title ? row.title : '';
  document.getElementById('ohNotes').value = row && row.notes ? row.notes : '';
  const endWrap = document.getElementById('ohEndWrap');
  const ohEnd = document.getElementById('ohEndDate');
  if (row && row.id) {
    if (endWrap) endWrap.style.display = 'none';
    if (ohEnd) ohEnd.value = '';
  } else {
    if (endWrap) endWrap.style.display = '';
    if (ohEnd) ohEnd.value = row && row.holiday_date ? row.holiday_date : (document.getElementById('ohDate').value || '');
  }
  const titleEl = document.getElementById('officialHolidayModalTitle');
  if (titleEl) titleEl.textContent = row && row.id ? t('official_holiday_edit') : t('official_holiday_add');
  openModal('officialHolidayModal');
}

async function saveOfficialHoliday() {
  const id = document.getElementById('editOfficialHolidayId').value;
  const body = {
    holiday_date: document.getElementById('ohDate').value,
    title: document.getElementById('ohTitle').value.trim(),
    notes: document.getElementById('ohNotes').value.trim()
  };
  if (!body.holiday_date || !body.title) return showToast(t('fill_fields'), 'error');
  if (!id) {
    const endEl = document.getElementById('ohEndDate');
    const endVal = endEl && endEl.value ? endEl.value : body.holiday_date;
    body.end_date = endVal;
  }
  const res = id
    ? await api('PUT', `official-holidays/${id}`, body)
    : await api('POST', 'official-holidays', body);
  if (res.success) {
    closeModal('officialHolidayModal');
    let msg = res.message || 'OK';
    if (!id && res.data && res.data.inserted != null) {
      msg = t('official_holiday_range_saved', { n: String(res.data.inserted) });
      if (Array.isArray(res.data.skipped) && res.data.skipped.length) {
        msg += ' ' + (document.documentElement.lang === 'ar' ? '(تخطي تواريخ مكررة)' : '(Some dates skipped — already set.)');
      }
    }
    showToast(msg, 'success');
    loadOfficialHolidays();
  } else {
    showToast(res.message, 'error');
  }
}

async function deleteOfficialHoliday(id) {
  if (!confirm((t('delete') || 'Delete') + '?')) return;
  const res = await api('DELETE', `official-holidays/${id}`);
  if (res.success) {
    showToast(res.message || 'OK', 'success');
    loadOfficialHolidays();
  } else {
    showToast(res.message, 'error');
  }
}

// ── EMPLOYEE DOCUMENTS (admin) ───────────────────────────────────
function readFileAsDataURL(file) {
  return new Promise((resolve, reject) => {
    const r = new FileReader();
    r.onload = () => resolve(r.result);
    r.onerror = () => reject(new Error('read'));
    r.readAsDataURL(file);
  });
}

function formatEmpDocSize(bytes) {
  const n = Number(bytes);
  if (!Number.isFinite(n) || n <= 0) return '—';
  if (n >= 1048576) return (n / 1048576).toFixed(1) + ' MB';
  if (n >= 1024) return Math.max(1, Math.round(n / 1024)) + ' KB';
  return n + ' B';
}

function formatEmpDocDate(iso) {
  if (!iso) return '—';
  const s = String(iso);
  return s.length >= 10 ? s.substring(0, 10) : s;
}

function empDocTypeIcon(mime) {
  const m = String(mime || '').toLowerCase();
  if (m.indexOf('image/') === 0) return '🖼';
  if (m.indexOf('pdf') !== -1) return '📄';
  if (m.indexOf('word') !== -1 || m.indexOf('document') !== -1 || m.indexOf('msword') !== -1) return '📝';
  if (m.indexOf('excel') !== -1 || m.indexOf('spreadsheet') !== -1 || m.indexOf('ms-excel') !== -1) return '📊';
  return '📎';
}

/** Opens employee document via API (Bearer). Opens a tab immediately so popup blockers do not block after await fetch. */
async function openEmployeeDocumentDownload(empId, docId) {
  if (!authToken) return showToast(t('session_expired') || 'Session expired', 'error');
  const tab = window.open('about:blank', '_blank', 'noopener');
  if (!tab) {
    return showToast(t('emp_docs_popup_blocked') || 'Allow pop-ups for this site to open documents.', 'error');
  }
  const route = `employees/${Number(empId)}/documents/${Number(docId)}/download`;
  try {
    const r = await fetch(`${API}?route=${encodeURIComponent(route)}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + authToken },
      body: JSON.stringify({ _method: 'GET' }),
    });
    if (!r.ok) {
      let msg = r.statusText || 'Error';
      try {
        const j = await r.json();
        if (j && j.message) msg = j.message;
      } catch (e) { /* not JSON */ }
      try { tab.close(); } catch (e2) { /* ignore */ }
      return showToast(String(msg), 'error');
    }
    const ct = r.headers.get('Content-Type') || 'application/octet-stream';
    if (ct.indexOf('application/json') !== -1) {
      try {
        const j = await r.json();
        try { tab.close(); } catch (e2) { /* ignore */ }
        return showToast((j && j.message) ? j.message : 'Error', 'error');
      } catch (e) {
        try { tab.close(); } catch (e2) { /* ignore */ }
        return showToast('Error', 'error');
      }
    }
    const blob = await r.blob();
    const baseType = (ct.split(';')[0] || '').trim() || blob.type || 'application/octet-stream';
    const typed = new Blob([blob], { type: baseType });
    const url = URL.createObjectURL(typed);
    const isImg = baseType.toLowerCase().indexOf('image/') === 0;
    if (isImg && tab && tab.document) {
      try {
        tab.document.open();
        tab.document.write('<!DOCTYPE html><html><head><meta charset="utf-8"><title>' + (t('emp_docs_preview') || 'Preview').replace(/</g, '') + '</title><style>body{margin:0;display:flex;min-height:100vh;align-items:center;justify-content:center;background:#1a1a1f}img{max-width:100%;max-height:100vh;object-fit:contain}@media print{body{background:#fff}img{max-height:none;width:auto;height:auto}}</style></head><body><img src="' + url + '" alt=""></body></html>');
        tab.document.close();
      } catch (e) {
        tab.location.href = url;
      }
    } else {
      tab.location.href = url;
    }
    setTimeout(() => URL.revokeObjectURL(url), 120000);
  } catch (e) {
    try { tab.close(); } catch (e2) { /* ignore */ }
    showToast(t('network_error') || 'Network error', 'error');
  }
}

async function loadEmployeeDocuments(empId) {
  const tbody = document.getElementById('empDocTableBody');
  const btn = document.getElementById('btnEmpDocUpload');
  if (!tbody) return;
  if (!empId) {
    tbody.innerHTML = `<tr><td colspan="4" style="text-align:center;padding:14px;color:var(--text-muted);">${t('emp_docs_save_first')}</td></tr>`;
    if (btn) btn.disabled = true;
    updateUITranslations();
    return;
  }
  if (btn) btn.disabled = false;
  tbody.innerHTML = `<tr><td colspan="4" style="text-align:center;padding:14px;color:var(--text-muted);">${t('loading')}</td></tr>`;
  const res = await api('GET', `employees/${empId}/documents`);
  if (!res.success) {
    tbody.innerHTML = `<tr><td colspan="4" style="text-align:center;padding:14px;color:var(--danger);">${String(res.message || '').replace(/</g, '')}</td></tr>`;
    updateUITranslations();
    return;
  }
  const rows = res.data || [];
  if (!rows.length) {
    tbody.innerHTML = `<tr><td colspan="4" style="text-align:center;padding:14px;color:var(--text-muted);">${t('emp_docs_none')}</td></tr>`;
    updateUITranslations();
    return;
  }
  const esc = (s) => String(s ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/"/g, '&quot;');
  tbody.innerHTML = rows.map((d) => {
    const nameEnc = encodeURIComponent(String(d.original_name || ''));
    const sz = formatEmpDocSize(d.file_size);
    const dt = formatEmpDocDate(d.created_at);
    const did = Number(d.id);
    const mime = String(d.mime_type || '').toLowerCase();
    const isImg = mime.indexOf('image/') === 0;
    const openLabel = isImg ? t('emp_docs_preview') : t('emp_docs_download');
    const ic = empDocTypeIcon(d.mime_type);
    return `<tr>
      <td data-label="${t('emp_docs_col_doc')}"><span class="emp-doc-type-ic" title="${esc(mime)}" aria-hidden="true">${ic}</span><a href="#" rel="noopener" onclick="event.preventDefault();openEmployeeDocumentDownload(${Number(empId)},${did});" style="color:var(--accent);font-weight:600;word-break:break-word;cursor:pointer;">${esc(d.original_name)}</a> <span style="font-size:11px;color:var(--text-muted);">(${t('emp_docs_new_tab_hint')})</span></td>
      <td data-label="${t('emp_docs_col_size')}" style="font-family:var(--mono);font-size:12px;color:var(--text-muted);">${esc(sz)}</td>
      <td data-label="${t('emp_docs_col_date')}" style="font-family:var(--mono);font-size:12px;">${esc(dt)}</td>
      <td data-label="${t('actions')}">
        <div class="emp-docs-actions-row">
          <button type="button" class="btn btn-sm btn-outline" onclick="openEmployeeDocumentDownload(${Number(empId)},${did})">${esc(openLabel)}</button>
          <button type="button" class="btn btn-sm btn-secondary" data-emp-doc-emp="${Number(empId)}" data-emp-doc-id="${Number(d.id)}" data-emp-doc-name="${nameEnc}" onclick="openEmployeeDocumentEditFromBtn(this)">${t('edit')}</button>
          <button type="button" class="btn btn-sm btn-ghost btn-del" onclick="deleteEmployeeDocument(${Number(empId)},${Number(d.id)})" title="${t('delete')}">${t('delete')}</button>
        </div>
      </td>
    </tr>`;
  }).join('');
  updateUITranslations();
}

function openEmployeeDocumentEditFromBtn(btn) {
  if (!btn) return;
  const empId = btn.getAttribute('data-emp-doc-emp');
  const docId = btn.getAttribute('data-emp-doc-id');
  const name = decodeURIComponent(btn.getAttribute('data-emp-doc-name') || '');
  document.getElementById('editEmpDocEmpId').value = empId || '';
  document.getElementById('editEmpDocId').value = docId || '';
  document.getElementById('editEmpDocName').value = name;
  openModal('empDocEditModal');
  updateUITranslations();
}

async function saveEmployeeDocumentRename() {
  const empId = document.getElementById('editEmpDocEmpId').value;
  const docId = document.getElementById('editEmpDocId').value;
  const original_name = document.getElementById('editEmpDocName').value.trim();
  if (!empId || !docId || !original_name) return showToast(t('fill_fields'), 'error');
  const res = await api('PUT', `employees/${empId}/documents/${docId}`, { original_name });
  if (res.success) {
    closeModal('empDocEditModal');
    showToast(res.message || 'OK', 'success');
    loadEmployeeDocuments(empId);
  } else {
    showToast(res.message, 'error');
  }
}

async function uploadEmployeeDocuments() {
  const id = document.getElementById('editEmpId').value;
  if (!id) return showToast(t('emp_docs_save_first'), 'warning');
  const inp = document.getElementById('empDocFiles');
  if (!inp || !inp.files || !inp.files.length) return showToast(t('emp_docs_pick_files'), 'warning');
  const files = [];
  for (const f of inp.files) {
    files.push({ file_name: f.name, file_data: await readFileAsDataURL(f) });
  }
  const res = await api('POST', `employees/${id}/documents`, { files });
  if (res.success) {
    inp.value = '';
    showToast(res.message || 'OK', 'success');
    loadEmployeeDocuments(id);
  } else {
    showToast(res.message, 'error');
  }
}

async function deleteEmployeeDocument(empId, docId) {
  if (!confirm((t('delete') || 'Delete') + '?')) return;
  const res = await api('DELETE', `employees/${empId}/documents/${docId}`);
  if (res.success) {
    showToast(res.message || 'OK', 'success');
    loadEmployeeDocuments(empId);
  } else {
    showToast(res.message, 'error');
  }
}

// ── BRANCHES ──────────────────────────────────────────────────────
let branchesList = [];
let pendingBranchDeleteId = null;
let pendingBranchDeleteCode = null;

function generateBranchDeleteSixDigitCode() {
  return String(100000 + Math.floor(Math.random() * 900000));
}

async function loadBranches() {
  const res = await api('GET', 'branches');
  if (res.success) {
    branchesList = res.data || [];
    const tbody = document.getElementById('branchTable');
    if (tbody) {
      if (branchesList.length === 0) {
        tbody.innerHTML = `<tr><td colspan="5" style="text-align:center;padding:30px;color:var(--text-muted);">No branches found</td></tr>`;
      } else {
        tbody.innerHTML = branchesList.map(b => `
          <tr>
            <td data-label="${t('branch_name')}"><div style="font-weight:600;">${b.name}</div></td>
            <td data-label="${t('location')}" style="font-family:var(--mono);font-size:12px">${b.latitude}, ${b.longitude}</td>
            <td data-label="${t('branch_photo_col')}">
              ${b.photo_url ? `<img src="${_bmAttr(hrproMediaUrl(b.photo_url))}" alt="" style="width:40px;height:40px;object-fit:cover;border-radius:8px;" onerror="this.style.display='none'">` : '—'}
            </td>
            <td data-label="${t('radius')}" style="font-family:var(--mono);">${b.radius}m</td>
            <td data-label="${t('actions')}">
              <div class="actions-flex">
                <button class="btn-icon-sm btn-accent" onclick='openEditBranch(${JSON.stringify(b)})' title="${t('edit')}">
                  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
                </button>
                <button class="btn-icon-sm btn-del" onclick="deleteBranch(${b.id})" title="${t('delete')}">
                  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg>
                </button>
              </div>
            </td>
          </tr>
        `).join('');
      }
    }

    const eBranch = document.getElementById('eBranch');
    if (eBranch) {
      eBranch.innerHTML = `<option value="" data-i18n="no_branch">${t('no_branch')}</option>` + 
        branchesList.map(b => `<option value="${b.id}">${b.name}</option>`).join('');
    }

    const filterBranch = document.getElementById('filterBranch');
    const empFilterBranch = document.getElementById('empFilterBranch');
    const teamBranchFilter = document.getElementById('eTeamBranchFilter');
    const branchHtml = `<option value="all" data-i18n="all_branches">${t('all_branches')}</option>` + 
                       branchesList.map(b => `<option value="${b.id}">${b.name}</option>`).join('');
    if (filterBranch) filterBranch.innerHTML = branchHtml;
    if (empFilterBranch) empFilterBranch.innerHTML = branchHtml;
    if (teamBranchFilter) {
      const prev = String(teamBranchFilter.value || 'all');
      teamBranchFilter.innerHTML = branchHtml;
      teamBranchFilter.value = [...teamBranchFilter.options].some(o => o.value === prev) ? prev : 'all';
      renderTeamChecklist();
    }
  }
}

function handleBranchPhotoSelect(ev) {
  const f = ev.target.files && ev.target.files[0];
  if (!f) return;
  const r = new FileReader();
  r.onload = () => { document.getElementById('bPhotoData').value = r.result; };
  r.readAsDataURL(f);
}

function openCreateBranch() {
  document.getElementById('editBranchId').value = '';
  document.getElementById('bName').value = '';
  document.getElementById('bLat').value = '';
  document.getElementById('bLng').value = '';
  document.getElementById('bRadius').value = 100;
  document.getElementById('bPhotoData').value = '';
  const pf = document.getElementById('bPhotoFile');
  if (pf) pf.value = '';
  document.getElementById('branchModalTitle').textContent = t('add_branch');
  syncBranchGoogleMapsUrl();
  openModal('branchModal');
}

function openEditBranch(b) {
  document.getElementById('editBranchId').value = b.id;
  document.getElementById('bName').value = b.name;
  document.getElementById('bLat').value = b.latitude;
  document.getElementById('bLng').value = b.longitude;
  document.getElementById('bRadius').value = b.radius;
  document.getElementById('bPhotoData').value = '';
  const pf = document.getElementById('bPhotoFile');
  if (pf) pf.value = '';
  document.getElementById('branchModalTitle').textContent = t('branch_name');
  syncBranchGoogleMapsUrl();
  openModal('branchModal');
}

async function saveBranch() {
  const id = document.getElementById('editBranchId').value;
  const body = {
    name: document.getElementById('bName').value.trim(),
    latitude: document.getElementById('bLat').value,
    longitude: document.getElementById('bLng').value,
    radius: document.getElementById('bRadius').value,
    photo_data: document.getElementById('bPhotoData').value || null
  };
  if (!body.name || !body.latitude || !body.longitude) return showToast(t('fill_fields'), 'error');

  const method = id ? 'PUT' : 'POST';
  const url = id ? `branches/${id}` : 'branches';
  
  const res = await api(method, url, body);
  if (res.success) {
    closeModal('branchModal');
    showToast(res.message || 'Saved', 'success');
    loadBranches();
  } else {
    showToast(res.message, 'error');
  }
}

function closeBranchDeleteConfirmModal() {
  pendingBranchDeleteId = null;
  pendingBranchDeleteCode = null;
  const inp = document.getElementById('branchDeleteCodeInput');
  if (inp) inp.value = '';
  const codeEl = document.getElementById('branchDeleteVerificationCode');
  if (codeEl) codeEl.textContent = '------';
  closeModal('branchDeleteConfirmModal');
}

async function deleteBranch(id) {
  if (!confirm(t('confirm_delete_branch'))) return;
  pendingBranchDeleteId = id;
  pendingBranchDeleteCode = generateBranchDeleteSixDigitCode();
  const br = branchesList.find(x => Number(x.id) === Number(id));
  const intro = document.getElementById('branchDeleteConfirmIntro');
  if (intro) {
    const label = t('branch_delete_intro_name');
    intro.textContent = br && br.name ? `${label}: ${br.name}` : `${label} #${id}`;
  }
  const codeEl = document.getElementById('branchDeleteVerificationCode');
  if (codeEl) codeEl.textContent = pendingBranchDeleteCode;
  const inp = document.getElementById('branchDeleteCodeInput');
  if (inp) {
    inp.value = '';
    openModal('branchDeleteConfirmModal');
    setTimeout(() => inp.focus(), 150);
  } else {
    pendingBranchDeleteId = null;
    pendingBranchDeleteCode = null;
  }
}

async function submitBranchDeleteConfirm() {
  const id = pendingBranchDeleteId;
  const expected = pendingBranchDeleteCode;
  const inp = document.getElementById('branchDeleteCodeInput');
  if (!id || !expected) {
    closeBranchDeleteConfirmModal();
    return;
  }
  const typed = (inp && inp.value) ? inp.value.replace(/\D/g, '') : '';
  if (typed !== expected) {
    showToast(t('branch_delete_code_wrong'), 'error');
    return;
  }
  const res = await api('DELETE', `branches/${id}`);
  if (res.success) {
    closeBranchDeleteConfirmModal();
    showToast(res.message || t('branch_deleted_toast'), 'success');
    loadBranches();
  } else {
    showToast(res.message, 'error');
  }
}

function detectBranchLocation() {
  if (!navigator.geolocation) return showToast('Geolocation not supported', 'error');
  const btn = event.currentTarget;
  const oldHtml = btn.innerHTML;
  btn.disabled = true;
  btn.innerHTML = '<div class="spinner"></div>';

  navigator.geolocation.getCurrentPosition(pos => {
    document.getElementById('bLat').value = pos.coords.latitude.toFixed(8);
    document.getElementById('bLng').value = pos.coords.longitude.toFixed(8);
    syncBranchGoogleMapsUrl();
    btn.disabled = false;
    btn.innerHTML = oldHtml;
    showToast(t('loc_detected') || 'Location detected', 'success');
  }, (err) => {
    btn.disabled = false;
    btn.innerHTML = oldHtml;
    showToast('Could not get location', 'error');
  }, { enableHighAccuracy: true });
}

function syncBranchGoogleMapsUrl() {
  const lat = parseFloat(String(document.getElementById('bLat')?.value || ''));
  const lng = parseFloat(String(document.getElementById('bLng')?.value || ''));
  const link = document.getElementById('branchGoogleMapsLink');
  if (!link) return;
  if (Number.isFinite(lat) && Number.isFinite(lng)) {
    const q = `${lat},${lng}`;
    link.href = `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(q)}`;
    link.style.pointerEvents = '';
    link.style.opacity = '';
  } else {
    link.href = '#';
    link.style.opacity = '0.55';
  }
}

let _branchLeafletMap = null;
let _branchLeafletMarker = null;

function ensureLeafletLoaded(cb) {
  if (window.L && typeof window.L.map === 'function') {
    cb();
    return;
  }
  window._leafletLoadCallbacks = window._leafletLoadCallbacks || [];
  window._leafletLoadCallbacks.push(cb);
  if (window._leafletLoading) return;
  window._leafletLoading = true;
  const lk = document.createElement('link');
  lk.rel = 'stylesheet';
  lk.href = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css';
  lk.crossOrigin = '';
  document.head.appendChild(lk);
  const sc = document.createElement('script');
  sc.src = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js';
  sc.crossOrigin = '';
  sc.onload = () => {
    window._leafletLoading = false;
    const cbs = window._leafletLoadCallbacks || [];
    window._leafletLoadCallbacks = [];
    cbs.forEach((fn) => { try { fn(); } catch (e) { /* ignore */ } });
  };
  document.body.appendChild(sc);
}

function openBranchMapPicker() {
  ensureLeafletLoaded(() => {
    openModal('branchMapPickerModal');
    setTimeout(() => { initBranchMapPickerCanvas(); }, 150);
    updateUITranslations();
  });
}

function initBranchMapPickerCanvas() {
  const el = document.getElementById('branchMapPickerCanvas');
  if (!el || !window.L) return;
  const lat0 = parseFloat(String(document.getElementById('bLat')?.value || '')) || 24.7136;
  const lng0 = parseFloat(String(document.getElementById('bLng')?.value || '')) || 46.6753;
  if (_branchLeafletMap) {
    try { _branchLeafletMap.remove(); } catch (e) { /* ignore */ }
    _branchLeafletMap = null;
    _branchLeafletMarker = null;
  }
  el.innerHTML = '';
  _branchLeafletMap = L.map(el, { scrollWheelZoom: true }).setView([lat0, lng0], 13);
  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© OpenStreetMap' }).addTo(_branchLeafletMap);
  _branchLeafletMarker = L.marker([lat0, lng0], { draggable: true }).addTo(_branchLeafletMap);
  _branchLeafletMarker.on('dragend', () => updateBranchMapPickerGoogleLink());
  _branchLeafletMap.on('click', (e) => {
    _branchLeafletMarker.setLatLng(e.latlng);
    updateBranchMapPickerGoogleLink();
  });
  setTimeout(() => { try { _branchLeafletMap.invalidateSize(); } catch (e) { /* ignore */ } }, 250);
  updateBranchMapPickerGoogleLink();
}

function updateBranchMapPickerGoogleLink() {
  const a = document.getElementById('branchMapPickerGoogle');
  if (!_branchLeafletMarker || !a) return;
  const ll = _branchLeafletMarker.getLatLng();
  a.href = `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(`${ll.lat},${ll.lng}`)}`;
}

function applyBranchMapPicker() {
  if (!_branchLeafletMarker) {
    closeModal('branchMapPickerModal');
    return;
  }
  const ll = _branchLeafletMarker.getLatLng();
  document.getElementById('bLat').value = ll.lat.toFixed(8);
  document.getElementById('bLng').value = ll.lng.toFixed(8);
  syncBranchGoogleMapsUrl();
  closeModal('branchMapPickerModal');
}

let _editAttLeafletMap = null;
let _editAttLeafletMarker = null;

function refreshEditAttendanceTitle() {
  const h = document.getElementById('editAttModalTitle');
  if (!h) return;
  const typ = document.getElementById('editAttType')?.value;
  h.textContent = typ === 'check_out' ? t('edit_att_title_checkout') : t('edit_att_title_checkin');
}

function onEditAttBranchPick() {
  const sel = document.getElementById('editAttBranchPick');
  const id = sel && sel.value;
  if (!id) return;
  const b = (branchesList || []).find(x => String(x.id) === String(id));
  if (b && b.latitude != null && b.latitude !== '' && b.longitude != null && b.longitude !== '') {
    document.getElementById('editAttLat').value = String(b.latitude);
    document.getElementById('editAttLng').value = String(b.longitude);
  }
}

function openEditAttendanceGoogleMaps() {
  const lat = document.getElementById('editAttLat')?.value.trim();
  const lng = document.getElementById('editAttLng')?.value.trim();
  if (!lat || !lng) {
    showToast(t('att_coords_required'), 'error');
    return;
  }
  const q = `${lat},${lng}`;
  window.open(`https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(q)}`, '_blank', 'noopener,noreferrer');
}

function openEditAttendanceMapPicker() {
  ensureLeafletLoaded(() => {
    openModal('editAttMapPickerModal');
    setTimeout(() => { initEditAttMapPickerCanvas(); }, 150);
    updateUITranslations();
  });
}

function initEditAttMapPickerCanvas() {
  const el = document.getElementById('editAttMapPickerCanvas');
  if (!el || !window.L) return;
  const lat0 = parseFloat(String(document.getElementById('editAttLat')?.value || '')) || 24.7136;
  const lng0 = parseFloat(String(document.getElementById('editAttLng')?.value || '')) || 46.6753;
  if (_editAttLeafletMap) {
    try { _editAttLeafletMap.remove(); } catch (e) { /* ignore */ }
  }
  _editAttLeafletMap = null;
  _editAttLeafletMarker = null;
  el.innerHTML = '';
  _editAttLeafletMap = L.map(el, { scrollWheelZoom: true }).setView([lat0, lng0], 13);
  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© OpenStreetMap' }).addTo(_editAttLeafletMap);
  _editAttLeafletMarker = L.marker([lat0, lng0], { draggable: true }).addTo(_editAttLeafletMap);
  _editAttLeafletMarker.on('dragend', () => updateEditAttMapPickerGoogleLink());
  _editAttLeafletMap.on('click', (e) => {
    _editAttLeafletMarker.setLatLng(e.latlng);
    updateEditAttMapPickerGoogleLink();
  });
  setTimeout(() => { try { _editAttLeafletMap.invalidateSize(); } catch (e) { /* ignore */ } }, 250);
  updateEditAttMapPickerGoogleLink();
}

function updateEditAttMapPickerGoogleLink() {
  const a = document.getElementById('editAttMapPickerGoogle');
  if (!_editAttLeafletMarker || !a) return;
  const ll = _editAttLeafletMarker.getLatLng();
  a.href = `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(`${ll.lat},${ll.lng}`)}`;
}

function applyEditAttMapPicker() {
  if (!_editAttLeafletMarker) {
    closeModal('editAttMapPickerModal');
    return;
  }
  const ll = _editAttLeafletMarker.getLatLng();
  document.getElementById('editAttLat').value = ll.lat.toFixed(8);
  document.getElementById('editAttLng').value = ll.lng.toFixed(8);
  closeModal('editAttMapPickerModal');
}

function openCreateDepartment() {
  document.getElementById('editDeptId').value = '';
  document.getElementById('dName').value = '';
  document.getElementById('deptModalTitle').textContent = t('add_dept');
  openModal('deptModal');
}

function openEditDepartment(id) {
  const d = departmentsList.find((x) => String(x.id) === String(id));
  document.getElementById('editDeptId').value = id;
  document.getElementById('dName').value = d ? (d.department_name || '') : '';
  document.getElementById('deptModalTitle').textContent = t('department_name');
  openModal('deptModal');
}

async function saveDepartment() {
  const id = document.getElementById('editDeptId').value;
  const name = document.getElementById('dName').value.trim();
  if (!name) return showToast(t('fill_fields'), 'error');

  const method = id ? 'PUT' : 'POST';
  const url = id ? `departments/${id}` : 'departments';
  
  const res = await api(method, url, { department_name: name });
  if (res.success) {
    closeModal('deptModal');
    showToast(res.message || 'Saved', 'success');
    loadDepartments();
    loadEmployees();
  } else {
    showToast(res.message, 'error');
  }
}

function closeDeptDeleteConfirmModal() {
  pendingDeptDeleteId = null;
  pendingDeptDeleteCode = null;
  const inp = document.getElementById('deptDeleteCodeInput');
  if (inp) inp.value = '';
  const codeEl = document.getElementById('deptDeleteVerificationCode');
  if (codeEl) codeEl.textContent = '------';
  closeModal('deptDeleteConfirmModal');
}

async function deleteDepartment(id) {
  if (!confirm(t('confirm_delete_dept'))) return;
  pendingDeptDeleteId = id;
  pendingDeptDeleteCode = generateDeptDeleteSixDigitCode();
  const dept = departmentsList.find(x => Number(x.id) === Number(id));
  const intro = document.getElementById('deptDeleteConfirmIntro');
  if (intro) {
    const label = t('dept_delete_intro_name');
    intro.textContent = dept && dept.department_name ? `${label}: ${dept.department_name}` : `${label} #${id}`;
  }
  const codeEl = document.getElementById('deptDeleteVerificationCode');
  if (codeEl) codeEl.textContent = pendingDeptDeleteCode;
  const inp = document.getElementById('deptDeleteCodeInput');
  if (inp) {
    inp.value = '';
    openModal('deptDeleteConfirmModal');
    setTimeout(() => inp.focus(), 150);
  } else {
    pendingDeptDeleteId = null;
    pendingDeptDeleteCode = null;
  }
}

async function submitDeptDeleteConfirm() {
  const id = pendingDeptDeleteId;
  const expected = pendingDeptDeleteCode;
  const inp = document.getElementById('deptDeleteCodeInput');
  if (!id || !expected) {
    closeDeptDeleteConfirmModal();
    return;
  }
  const typed = (inp && inp.value) ? inp.value.replace(/\D/g, '') : '';
  if (typed !== expected) {
    showToast(t('dept_delete_code_wrong'), 'error');
    return;
  }
  const res = await api('DELETE', `departments/${id}`);
  if (res.success) {
    closeDeptDeleteConfirmModal();
    showToast(res.message || t('dept_deleted_toast'), 'success');
    loadDepartments();
    loadEmployees();
  } else {
    showToast(res.message, 'error');
  }
}

// ── EMPLOYEES ─────────────────────────────────────────────────────
let allEmployeesData = [];
let initialTeamMemberIds = new Set();
let explicitTeamRemovedIds = new Set();

function getAdminTimezone() {
  return localStorage.getItem('admin_timezone') || 'Africa/Cairo';
}

function ymdPartsInTz(date, tz) {
  const parts = new Intl.DateTimeFormat('en-CA', { timeZone: tz, year: 'numeric', month: '2-digit', day: '2-digit' }).formatToParts(date);
  const o = {};
  parts.forEach((p) => { if (p.type !== 'literal') o[p.type] = p.value; });
  return { year: +o.year, month: +o.month, day: +o.day };
}

function ymdToNum(y, m, d) {
  return y * 10000 + m * 100 + d;
}

function addDaysToYmd(y, m, d, delta) {
  const dt = new Date(Date.UTC(y, m - 1, d + delta));
  return { year: dt.getUTCFullYear(), month: dt.getUTCMonth() + 1, day: dt.getUTCDate() };
}

function getWeekdayInTz(date, tz) {
  const wd = new Intl.DateTimeFormat('en-US', { timeZone: tz, weekday: 'short' }).format(date);
  const map = { Sun: 0, Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6 };
  return map[wd] ?? 0;
}

function getEmpCalendarWeekRange(refDate, tz, lang) {
  const today = ymdPartsInTz(refDate, tz);
  const dow = getWeekdayInTz(refDate, tz);
  const weekStart = getEmpWeekStartDow(lang);
  const daysFromStart = (dow - weekStart + 7) % 7;
  const start = addDaysToYmd(today.year, today.month, today.day, -daysFromStart);
  const end = addDaysToYmd(start.year, start.month, start.day, 6);
  return {
    startNum: ymdToNum(start.year, start.month, start.day),
    endNum: ymdToNum(end.year, end.month, end.day),
    year: today.year
  };
}

function parseBirthMonthDay(birthDate) {
  const raw = String(birthDate ?? '').trim();
  if (!raw) return null;
  const m = raw.match(/^(\d{4})-(\d{2})-(\d{2})/);
  if (!m) return null;
  return { month: +m[2], day: +m[3] };
}

function isEmployeeActiveForBirthday(e) {
  const v = e?.is_active;
  return v === 1 || v === '1' || v === true;
}

function getEmpWeekStartDow(lang) {
  const isAr = String(lang || '').toLowerCase().startsWith('ar')
    || (document.documentElement.getAttribute('dir') || '').toLowerCase() === 'rtl';
  try {
    const locTag = isAr ? 'ar-EG' : 'en-US';
    const wi = new Intl.Locale(locTag).weekInfo;
    if (wi && typeof wi.firstDay === 'number') {
      const icuToJs = { 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 0 };
      if (icuToJs[wi.firstDay] !== undefined) return icuToJs[wi.firstDay];
    }
  } catch (_) { /* Intl.Locale.weekInfo not supported */ }
  return isAr ? 6 : 0;
}

function birthdayYmdForYear(birth, year) {
  let day = birth.day;
  if (birth.month === 2 && day === 29) {
    const leap = (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
    if (!leap) day = 28;
  }
  const mm = String(birth.month).padStart(2, '0');
  const dd = String(day).padStart(2, '0');
  return `${year}-${mm}-${dd}`;
}

function formatEmpBirthdayShort(ymd) {
  const m = String(ymd || '').match(/^(\d{4})-(\d{2})-(\d{2})$/);
  if (!m) return '';
  const isAr = (document.documentElement.lang || 'ar').toLowerCase().startsWith('ar');
  const date = new Date(Date.UTC(+m[1], +m[2] - 1, +m[3], 12, 0, 0));
  return new Intl.DateTimeFormat(isAr ? 'ar-EG' : 'en-US', { day: 'numeric', month: 'short', timeZone: 'UTC' }).format(date);
}

function getEmployeeBirthdaysThisWeek(employees) {
  const list = Array.isArray(employees) ? employees : [];
  const tz = getAdminTimezone();
  const lang = _cachedLang || document.documentElement.lang || 'ar';
  const week = getEmpCalendarWeekRange(new Date(), tz, lang);
  const startYear = Math.floor(week.startNum / 10000);
  const endYear = Math.floor(week.endNum / 10000);
  const yearsToCheck = [];
  for (let y = startYear; y <= endYear; y++) yearsToCheck.push(y);
  const out = [];
  const seen = new Set();

  list.forEach((e) => {
    if (!isEmployeeActiveForBirthday(e)) return;
    const birth = parseBirthMonthDay(e.birth_date);
    if (!birth) return;
    const empId = String(e.id ?? '');
    yearsToCheck.forEach((year) => {
      const bdayYmd = birthdayYmdForYear(birth, year);
      const parts = bdayYmd.match(/^(\d{4})-(\d{2})-(\d{2})$/);
      if (!parts) return;
      const bdayNum = ymdToNum(+parts[1], +parts[2], +parts[3]);
      if (bdayNum < week.startNum || bdayNum > week.endNum) return;
      const key = `${empId}:${bdayYmd}`;
      if (seen.has(key)) return;
      seen.add(key);
      out.push({
        id: e.id,
        name: e.name || '',
        birthYmd: bdayYmd,
        displayDate: formatEmpBirthdayShort(bdayYmd),
        sortKey: bdayNum
      });
    });
  });

  out.sort((a, b) => a.sortKey - b.sortKey);
  return out;
}

function renderEmployeeBirthdayWeekBar() {
  const bar = document.getElementById('empBirthdayWeekBar');
  if (!bar) return;
  const list = getEmployeeBirthdaysThisWeek(allEmployeesData);
  if (!list.length) {
    bar.innerHTML = '';
    bar.removeAttribute('aria-label');
    bar.setAttribute('hidden', '');
    return;
  }
  bar.removeAttribute('hidden');
  bar.setAttribute('aria-label', t('emp_birthday_week_aria'));
  bar.innerHTML = list.map((e) =>
    `<span class="emp-birthday-chip">${_bmEsc(e.name)} · ${_bmEsc(e.displayDate)}</span>`
  ).join('');
}

async function toggleEmployeeAccount(empId, empName, makeActive) {
  if (!canAdmin('update', 'employees')) return showToast(t('forbidden') || 'No permission', 'error');
  const key = makeActive ? 'emp_restore_confirm' : 'emp_suspend_confirm';
  const msg = (t(key) || '').replace(/\{name\}/g, empName || '');
  if (!confirm(msg)) return;
  const res = await api('PUT', `employees/${empId}`, { is_active: makeActive ? 1 : 0 });
  if (res.success) {
    showToast(res.message || (makeActive ? t('emp_restored_toast') : t('emp_suspended_toast')), 'success');
    loadEmployees();
  } else {
    showToast(res.message || 'Error', 'error');
  }
}

async function loadEmployees() {
  const res = await api('GET', 'employees');
  if (res.success) {
    allEmployeesData = res.data;
    renderEmployees();
    renderEmployeeBirthdayWeekBar();
    
    // Populate Final Report Dropdown
    const frEmp = document.getElementById('finalReportEmployee');
    if (frEmp) {
      frEmp.innerHTML = `<option value="">${t('select_employee')}</option>` + 
        allEmployeesData.map(e => `<option value="${e.id}">${e.name} (${e.employee_code})</option>`).join('');
    }
    const lsEmp = document.getElementById('leaveSettlementEmployee');
    if (lsEmp) {
      const prev = lsEmp.value;
      lsEmp.innerHTML = `<option value="">${t('select_employee')}</option>` +
        allEmployeesData.map(e => `<option value="${e.id}">${e.name} (${e.employee_code})</option>`).join('');
      if (prev && [...lsEmp.options].some(o => o.value === prev)) lsEmp.value = prev;
    }
    const drEmp = document.getElementById('deductionsReportEmployee');
    if (drEmp) {
      const prevDr = drEmp.value;
      drEmp.innerHTML = `<option value="">${t('select_employee')}</option>` +
        allEmployeesData.map(e => `<option value="${e.id}">${e.name} (${e.employee_code})</option>`).join('');
      if (prevDr && [...drEmp.options].some(o => o.value === prevDr)) drEmp.value = prevDr;
    }
    renderFinalDecisionEmployeePicker();

    // Team members picker (supervisor → employees)
    const teamSel = document.getElementById('eTeamMembers');
    if (teamSel) {
      teamSel.innerHTML = allEmployeesData.map(e => {
        const name = _bmEsc(e?.name || '');
        const code = _bmEsc(e?.employee_code || '');
        return `<option value="${e.id}">${name} (${code})</option>`;
      }).join('');
    }
    renderTeamChecklist();
  } else {
    document.getElementById('empTable').innerHTML = `<tr><td colspan="9"><div class="empty-state"><p>${res.message || 'Error loading employees'}</p></div></td></tr>`;
    renderEmployeeBirthdayWeekBar();
  }
}

function syncTeamSelectFromChecklist() {
  const teamSel = document.getElementById('eTeamMembers');
  const list = document.getElementById('eTeamChecklist');
  if (!teamSel || !list) return;
  // IMPORTANT: update only visible (filtered) checklist items.
  // Do NOT rebuild the full selection from visible rows only, otherwise
  // hidden already-selected employees get unintentionally unselected.
  const visibleChecks = Array.from(list.querySelectorAll('input[type="checkbox"][data-team-id]'));
  visibleChecks.forEach(ch => {
    const id = parseInt(ch.getAttribute('data-team-id') || '0', 10);
    if (!Number.isFinite(id) || id <= 0) return;
    const opt = Array.from(teamSel.options).find(o => (parseInt(o.value, 10) || 0) === id);
    if (!opt) return;
    opt.selected = !!ch.checked;
    if (opt.selected && explicitTeamRemovedIds.has(id)) {
      explicitTeamRemovedIds.delete(id);
    }
  });
  renderTeamChecklist();
}

function syncTeamChecklistFromSelect() {
  const teamSel = document.getElementById('eTeamMembers');
  const list = document.getElementById('eTeamChecklist');
  if (!teamSel || !list) return;
  const picked = new Set(
    Array.from(teamSel.selectedOptions)
      .map(o => parseInt(o.value, 10))
      .filter(v => Number.isFinite(v) && v > 0)
  );
  list.querySelectorAll('input[type="checkbox"][data-team-id]').forEach(ch => {
    const id = parseInt(ch.getAttribute('data-team-id') || '0', 10);
    ch.checked = picked.has(id);
  });
  renderSelectedTeamSummary();
}

function getTeamPickerSourceEmployees() {
  if (Array.isArray(allEmployeesData) && allEmployeesData.length) return allEmployeesData;
  const teamSel = document.getElementById('eTeamMembers');
  if (!teamSel) return [];
  return Array.from(teamSel.options).map(o => ({
    id: parseInt(o.value, 10) || 0,
    name: String(o.textContent || '').replace(/\s*\(.*?\)\s*$/, ''),
    employee_code: (String(o.textContent || '').match(/\((.*?)\)/)?.[1] || '')
  })).filter(e => e.id > 0);
}

function renderSelectedTeamSummary() {
  const box = document.getElementById('eTeamSelectedSummary');
  const teamSel = document.getElementById('eTeamMembers');
  if (!box || !teamSel) return;
  const selected = Array.from(teamSel.selectedOptions).map(o => ({
    id: parseInt(o.value, 10) || 0,
    label: String(o.textContent || '')
  })).filter(x => x.id > 0);
  const selectedIds = new Set(selected.map(s => s.id));
  const removed = Array.from(explicitTeamRemovedIds).filter(id => initialTeamMemberIds.has(id) && !selectedIds.has(id));

  if (!selected.length && !removed.length) {
    box.innerHTML = '<span style="font-size:12px;color:var(--text-muted);">لم يتم اختيار أعضاء بعد</span>';
    return;
  }

  const selHtml = selected.map(s => {
    const isExisting = initialTeamMemberIds.has(s.id);
    const bg = isExisting ? 'rgba(6,182,212,.12)' : 'rgba(34,197,94,.15)';
    const color = isExisting ? 'var(--accent)' : '#16a34a';
    const border = isExisting ? 'var(--accent)' : '#16a34a';
    const tag = isExisting ? 'حالي' : 'جديد';
    return `
      <span style="display:inline-flex;align-items:center;gap:6px;background:${bg};color:${color};border:1px solid ${border};border-radius:999px;padding:4px 8px;font-size:12px;">
        ${_bmEsc(s.label)} <small>(${tag})</small>
        <button type="button" data-team-remove="${s.id}" style="background:transparent;border:none;color:inherit;cursor:pointer;font-size:14px;line-height:1;">×</button>
      </span>
    `;
  }).join('');

  const removedHtml = removed.map(id => {
    const opt = Array.from(teamSel.options).find(o => (parseInt(o.value, 10) || 0) === id);
    const label = opt ? String(opt.textContent || '') : `موظف #${id}`;
    return `
      <span style="display:inline-flex;align-items:center;gap:6px;background:rgba(239,68,68,.12);color:var(--danger);border:1px solid var(--danger);border-radius:999px;padding:4px 8px;font-size:12px;">
        ${_bmEsc(label)} <small>(سيُحذف)</small>
        <button type="button" data-team-restore="${id}" style="background:transparent;border:none;color:inherit;cursor:pointer;font-size:12px;line-height:1;">تراجع</button>
      </span>
    `;
  }).join('');

  box.innerHTML = selHtml + removedHtml;
  box.querySelectorAll('button[data-team-remove]').forEach(btn => {
    btn.addEventListener('click', () => {
      const id = parseInt(btn.getAttribute('data-team-remove') || '0', 10);
      if (!id) return;
      if (initialTeamMemberIds.has(id)) {
        explicitTeamRemovedIds.add(id);
      }
      Array.from(teamSel.options).forEach(o => { if (parseInt(o.value, 10) === id) o.selected = false; });
      renderTeamChecklist();
    });
  });
  box.querySelectorAll('button[data-team-restore]').forEach(btn => {
    btn.addEventListener('click', () => {
      const id = parseInt(btn.getAttribute('data-team-restore') || '0', 10);
      if (!id) return;
      explicitTeamRemovedIds.delete(id);
      Array.from(teamSel.options).forEach(o => { if (parseInt(o.value, 10) === id) o.selected = true; });
      renderTeamChecklist();
    });
  });
}

function renderTeamChecklist() {
  const list = document.getElementById('eTeamChecklist');
  const qEl = document.getElementById('eTeamSearch');
  const deptEl = document.getElementById('eTeamDeptFilter');
  const branchEl = document.getElementById('eTeamBranchFilter');
  const teamSel = document.getElementById('eTeamMembers');
  if (!list) return;
  const idEdit = document.getElementById('editEmpId').value;
  const canManageTeam = idEdit ? canAdmin('update', 'employees') : canAdmin('create', 'employees');
  if (!canManageTeam) {
    list.innerHTML = '<div style="font-size:12px;color:var(--text-muted);padding:6px;">لا تملك صلاحية إدارة فريق العمل</div>';
    return;
  }
  const q = (qEl?.value || '').trim().toLowerCase();
  const deptId = String(deptEl?.value || 'all');
  const branchId = String(branchEl?.value || 'all');
  const selectedIds = new Set(
    Array.from(teamSel?.selectedOptions || [])
      .map(o => parseInt(o.value, 10))
      .filter(v => Number.isFinite(v) && v > 0)
  );
  const source = getTeamPickerSourceEmployees();
  const filtered = source.filter(e => {
    const empId = parseInt(e?.id, 10) || 0;
    if (selectedIds.has(empId)) return false;
    const n = String(e?.name || '').toLowerCase();
    const c = String(e?.employee_code || '').toLowerCase();
    const empDept = String(e?.department_id ?? '');
    const empBranch = String(e?.branch_id ?? '');
    const matchSearch = !q || n.includes(q) || c.includes(q);
    const matchDept = deptId === 'all' || empDept === deptId;
    const matchBranch = branchId === 'all' || empBranch === branchId;
    return matchSearch && matchDept && matchBranch;
  });
  if (!filtered.length) {
    list.innerHTML = '<div style="font-size:12px;color:var(--text-muted);padding:6px;">لا يوجد نتائج بحث</div>';
    return;
  }
  list.innerHTML = filtered.map(e => {
    const id = parseInt(e.id, 10) || 0;
    const name = _bmEsc(e?.name || '');
    const code = _bmEsc(e?.employee_code || '');
    const title = _bmEsc(e?.job_title || '');
    return `
      <label style="display:flex;align-items:flex-start;gap:8px;padding:8px;border:1px solid var(--border);border-radius:8px;background:var(--surface);cursor:pointer;">
        <input type="checkbox" data-team-id="${id}" style="margin-top:2px;width:16px;height:16px;flex-shrink:0;">
        <span style="min-width:0;">
          <span style="display:block;font-size:13px;color:var(--text);font-weight:700;">${name}</span>
          <span style="display:block;font-size:11px;color:var(--text-muted);">${code}${title ? ` • ${title}` : ''}</span>
        </span>
      </label>
    `;
  }).join('');

  list.querySelectorAll('input[type="checkbox"][data-team-id]').forEach(ch => {
    ch.addEventListener('change', syncTeamSelectFromChecklist);
  });
  syncTeamChecklistFromSelect();
}

if (!window.__teamChecklistBound) {
  window.__teamChecklistBound = true;
  setTimeout(() => {
    const qEl = document.getElementById('eTeamSearch');
    const deptEl = document.getElementById('eTeamDeptFilter');
    const branchEl = document.getElementById('eTeamBranchFilter');
    if (qEl) qEl.addEventListener('input', renderTeamChecklist);
    if (deptEl) deptEl.addEventListener('change', renderTeamChecklist);
    if (branchEl) branchEl.addEventListener('change', renderTeamChecklist);
  }, 0);
}

function renderEmployees() {
  const query = document.getElementById('empSearch').value.toLowerCase();
  const branchId = document.getElementById('empFilterBranch').value;
  const deptId = document.getElementById('empFilterDept').value;
  const nature = document.getElementById('empFilterNature').value;
  const status = document.getElementById('empFilterStatus').value;

  const filtered = allEmployeesData.filter(e => {
    const matchSearch = (e.name || '').toLowerCase().includes(query) || (e.employee_code || '').toLowerCase().includes(query) || (e.phone || '').includes(query);
    const matchBranch = branchId === 'all' || e.branch_id == branchId;
    const matchDept   = deptId === 'all' || e.department_id == deptId;
    const matchNature = nature === 'all' || e.work_nature == nature;
    const matchStatus = status === 'all' || e.is_active == status;
    return matchSearch && matchBranch && matchDept && matchNature && matchStatus;
  });

  const tbody = document.getElementById('empTable');
  if (!filtered.length) {
    tbody.innerHTML = `<tr><td colspan="10"><div class="empty-state"><p>No employees found matching filters</p></div></td></tr>`;
    return;
  }

  const canCreateEmp = canAdmin('create', 'employees');
  const canUpdateEmp = canAdmin('update', 'employees');
  const canDeleteEmp = canAdmin('delete', 'employees');
  const canMsgEmp = canAdminModule('create', 'employee_messages');
  /* Report link: any logged-in admin who loaded this table may open the report; API enforces access on policy/attendance. */
  const canOpenEmpReport = !!authToken;
  const reportBtnSvg = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><rect x="4" y="12" width="4" height="8" rx="1"/><rect x="10" y="8" width="4" height="12" rx="1"/><rect x="16" y="4" width="4" height="16" rx="1"/></svg>';
  tbody.innerHTML = filtered.map(e => `
    <tr>
      <td data-label="${t('employee')}">
        <div style="display:flex; align-items:center; gap:12px;">
          ${e.photo_url 
            ? `<img src="${_bmAttr(hrproMediaUrl(e.photo_url))}" onerror="handleImgError(this)" style="width:42px;height:42px;border-radius:50%;object-fit:cover;border:2px solid var(--accent);box-shadow:var(--shadow);">` 
            : `<div style="width:42px;height:42px;border-radius:50%;background:var(--surface2);display:flex;align-items:center;justify-content:center;font-size:16px;color:var(--textM);border:1px solid var(--border);">👤</div>`}
          <div>
            <strong style="font-size:15px; color:var(--text);">${e.name}</strong><br>
            <span class="device-chip" style="margin-top:4px; display:inline-block;">${e.employee_code}</span>
          </div>
        </div>
      </td>
      <td data-label="${t('code')}"><span style="font-family:var(--mono);font-size:12px;font-weight:600;">${e.employee_code}</span></td>
      <td data-label="${t('department')}"><span class="badge" style="background:var(--bg); border:1px solid var(--border); color:var(--accent); font-weight:700;">${e.department_name || '—'}</span></td>
      <td data-label="${t('branch')}">
        <div style="font-weight:700; color:var(--text);">${e.branch_name || t('no_branch')}</div>
        <div style="font-size:10px; color:var(--textM); text-transform:uppercase; font-weight:600;">${t(e.work_nature || 'internal')}</div>
      </td>
      <td data-label="${t('job_title')}">${e.job_title||'—'}</td>
      <td data-label="${t('phone')}" style="font-family:var(--mono);">${e.phone||'—'}</td>
      <td data-label="${t('email')}">${e.email||'—'}</td>
      <td data-label="${t('device')}">${e.device_id ? `<span class="badge badge-in" style="font-size:10px;">🔒 ${t('bound')}</span>` : `<span style="color:var(--text-muted);font-size:11px;">${t('unbound')}</span>`}</td>
      <td data-label="${t('status')}"><span class="badge ${e.is_active=='1'?'badge-ok':'badge-rejected'}">${e.is_active=='1'?t('status_active'):t('status_inactive')}</span></td>
      <td data-label="${t('actions')}"></td>
    </tr>
    <tr class="emp-actions-row">
      <td colspan="10">
        <div class="actions-flex emp-actions-toolbar" role="group" aria-label="${t('actions')}">
          ${canUpdateEmp ? `<button type="button" class="btn-icon-sm btn-accent" onclick='editEmployee(${JSON.stringify(e)})' title="${t('edit')}" aria-label="${t('edit')}"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg></button>` : ''}
          ${canOpenEmpReport ? `<a href="Reports/employee_report.html?employee_id=${encodeURIComponent(String(e.id))}&name=${encodeURIComponent(e.name || '')}" target="_blank" rel="noopener noreferrer" class="btn-icon-sm btn-info" title="${t('emp_att_report_review')}" aria-label="${t('emp_att_report_review')}">${reportBtnSvg}</a>` : ''}
          ${canUpdateEmp && parseInt(String(e.is_active), 10) === 1
            ? `<button type="button" class="btn-icon-sm btn-warning" onclick='toggleEmployeeAccount(${e.id}, ${JSON.stringify(e.name)}, 0)' title="${t('emp_suspend_account')}" aria-label="${t('emp_suspend_account')}"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="10" y1="15" x2="10" y2="9"/><line x1="14" y1="15" x2="14" y2="9"/></svg></button>`
            : (canUpdateEmp ? `<button type="button" class="btn-icon-sm btn-success" onclick='toggleEmployeeAccount(${e.id}, ${JSON.stringify(e.name)}, 1)' title="${t('emp_restore_account')}" aria-label="${t('emp_restore_account')}"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg></button>` : '')}
          ${canCreateEmp && parseInt(String(e.is_active), 10) === 1 ? `<button type="button" class="btn-icon-sm btn-success" onclick='openAdminLeaveForEmp(${e.id}, ${JSON.stringify(e.name)})' title="${t('admin_add_leave')}" aria-label="${t('admin_add_leave')}"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg></button>` : ''}
          ${canMsgEmp ? `<button type="button" class="btn-icon-sm btn-info" onclick='openMessageModal(${e.id}, ${JSON.stringify(e.name)})' title="${t('message')}" aria-label="${t('message')}"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg></button>` : ''}
          ${canUpdateEmp ? `<button type="button" class="btn-icon-sm btn-neutral" onclick='openResetPwd(${e.id}, ${JSON.stringify(e.name)})' title="${t('reset_pwd')}" aria-label="${t('reset_pwd')}"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3L15.5 7.5z"/></svg></button>` : ''}
          ${canCreateEmp ? `<button type="button" class="btn-icon-sm btn-neutral" onclick='openManualAtt(${e.id}, ${JSON.stringify(e.name)})' title="${t('manual_att')}" aria-label="${t('manual_att')}"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg></button>` : ''}
          ${canUpdateEmp && e.device_id ? `
          <button type="button" class="btn-icon-sm btn-warning" onclick='resetDevice(${e.id}, ${JSON.stringify(e.name)})' title="${t('reset_device')}" aria-label="${t('reset_device')}"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12.01" y2="18"/></svg></button>` : ''}
          ${canDeleteEmp ? `<button type="button" class="btn-icon-sm btn-del" onclick='deleteEmployee(${e.id}, ${JSON.stringify(e.name)})' title="${t('delete')}" aria-label="${t('delete')}"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg></button>` : ''}
        </div>
      </td>
    </tr>
  `).join('');
  updateUITranslations();
}

function clearEmployeeFilters() {
  document.getElementById('empSearch').value = '';
  document.getElementById('empFilterBranch').value = 'all';
  document.getElementById('empFilterDept').value = 'all';
  document.getElementById('empFilterNature').value = 'all';
  document.getElementById('empFilterStatus').value = 'all';
  renderEmployees();
}

function suggestEmpPassword() {
  const pwd = Math.floor(10000000 + Math.random() * 90000000).toString();
  const el = document.getElementById('ePassword');
  if (el) el.value = pwd;
}

function toggleEmployeePasswordVisibility() {
  const input = document.getElementById('ePassword');
  const btn = document.getElementById('btnToggleEmpPwd');
  if (!input) return;
  const show = input.type === 'password';
  input.type = show ? 'text' : 'password';
  if (btn) btn.textContent = show ? '🙈' : '👁️';
}

function suggestResetPassword() {
  const pwd = Math.floor(10000000 + Math.random() * 90000000).toString();
  const el = document.getElementById('resetPwdNew');
  if (el) el.value = pwd;
}

function autoFillUsername() {
  const idEdit = document.getElementById('editEmpId').value;
  if (idEdit) return;
  const fullName = (document.getElementById('eName').value || '').trim();
  const parts = fullName.split(/\s+/).filter(Boolean);
  const uEl = document.getElementById('eUsername');
  if (!uEl) return;
  if (parts.length >= 2) {
    uEl.value = parts[0] + '.' + parts[1];
  } else if (parts.length === 1) {
    uEl.value = parts[0];
  } else {
    uEl.value = '';
  }
}

function applyEmployeeTeamPermissions(isEditMode) {
  const canCreateEmp = canAdmin('create', 'employees');
  const canUpdateEmp = canAdmin('update', 'employees');
  const canManageTeam = isEditMode ? canUpdateEmp : canCreateEmp;
  const teamSearch = document.getElementById('eTeamSearch');
  const teamList = document.getElementById('eTeamChecklist');
  const teamSummary = document.getElementById('eTeamSelectedSummary');
  const teamSel = document.getElementById('eTeamMembers');
  if (teamSearch) {
    teamSearch.disabled = !canManageTeam;
    teamSearch.style.display = canManageTeam ? '' : 'none';
  }
  const teamDept = document.getElementById('eTeamDeptFilter');
  if (teamDept) {
    teamDept.disabled = !canManageTeam;
    teamDept.style.display = canManageTeam ? '' : 'none';
  }
  const teamBranch = document.getElementById('eTeamBranchFilter');
  if (teamBranch) {
    teamBranch.disabled = !canManageTeam;
    teamBranch.style.display = canManageTeam ? '' : 'none';
  }
  if (teamList) {
    teamList.style.display = canManageTeam ? '' : 'none';
    teamList.querySelectorAll('input[type="checkbox"][data-team-id]').forEach((ch) => {
      ch.disabled = !canManageTeam;
    });
  }
  if (teamSummary && !canManageTeam) {
    teamSummary.innerHTML = `<span style="font-size:12px;color:var(--text-muted);">${t('unknown_error') || 'No permission to manage team'}</span>`;
  }
  if (teamSel) teamSel.disabled = !canManageTeam;
  const saveBtn = document.querySelector('#empModal .btn.btn-primary[onclick="saveEmployee()"]');
  if (saveBtn) {
    const canSave = isEditMode ? canUpdateEmp : canCreateEmp;
    saveBtn.style.display = canSave ? '' : 'none';
  }
}

function openCreateEmployee() {
  if (!canAdmin('create', 'employees')) {
    showToast(t('forbidden') || 'No permission', 'error');
    return;
  }
  const clr = document.getElementById('btnEmpClearPhoto');
  if (clr) clr.style.display = 'none';
  const leaveBtn = document.getElementById('btnEmpSubmitLeave');
  if (leaveBtn) leaveBtn.style.display = 'none';
  const activeWrap = document.getElementById('eEmpActiveWrap');
  if (activeWrap) activeWrap.style.display = 'none';
  const screenWrap = document.getElementById('eEmpScreenFlagsWrap');
  if (screenWrap) screenWrap.style.display = 'none';
  document.getElementById('editEmpId').value = '';
  document.getElementById('empModalTitle').textContent = t('emp_modal_add');
  ['eName','eUsername','ePassword','eNatId','ePhone','eEmail','eTitle','eSalary','eHireDate','eBirthDate','eAddress','eDepartment','eBranch','ePolicy','ePhoto'].forEach(id => {
    const el = document.getElementById(id);
    if(el) el.value = '';
  });
  document.getElementById('eWorkNature').value = 'internal';
  const tm = document.getElementById('eTeamMembers');
  if (tm) Array.from(tm.options).forEach(o => { o.selected = false; });
  initialTeamMemberIds = new Set();
  explicitTeamRemovedIds = new Set();
  const tq = document.getElementById('eTeamSearch');
  if (tq) tq.value = '';
  const td = document.getElementById('eTeamDeptFilter');
  if (td) td.value = 'all';
  const tb = document.getElementById('eTeamBranchFilter');
  if (tb) tb.value = 'all';
  renderTeamChecklist();
  applyEmployeeTeamPermissions(false);
  document.getElementById('ePhotoData').value = '';
  document.getElementById('ePhotoUrl').value = '';
  document.getElementById('empPhotoPreview').innerHTML = `<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="opacity:0.3;"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>`;
  const finp = document.getElementById('empDocFiles');
  if (finp) finp.value = '';
  loadEmployeeDocuments(null);
  openModal('empModal');
  syncEmpModalRequiredHint();
}

function editEmployee(e) {
  if (!canAdmin('update', 'employees')) {
    showToast(t('forbidden') || 'No permission', 'error');
    return;
  }
  const clr = document.getElementById('btnEmpClearPhoto');
  if (clr) clr.style.display = e.photo_url ? 'inline-flex' : 'none';
  const leaveBtn = document.getElementById('btnEmpSubmitLeave');
  if (leaveBtn) leaveBtn.style.display = 'inline-flex';
  const activeWrap = document.getElementById('eEmpActiveWrap');
  if (activeWrap) activeWrap.style.display = '';
  const screenWrap = document.getElementById('eEmpScreenFlagsWrap');
  if (screenWrap) screenWrap.style.display = '';
  const empActive = document.getElementById('eEmpActive');
  if (empActive) empActive.checked = parseInt(String(e.is_active), 10) === 1;
  const chkRep = document.getElementById('eShowEmpReport');
  if (chkRep) chkRep.checked = parseInt(String(e.show_employee_report ?? 0), 10) === 1;
  const chkHol = document.getElementById('eShowPublicHolidays');
  if (chkHol) chkHol.checked = parseInt(String(e.show_public_holidays ?? 0), 10) === 1;
  document.getElementById('editEmpId').value = e.id;
  document.getElementById('empModalTitle').textContent = t('emp_modal_edit');
  document.getElementById('eName').value = e.name || '';
  document.getElementById('eUsername').value = e.username || '';
  document.getElementById('eDepartment').value = e.department_id || '';
  document.getElementById('eBranch').value = e.branch_id || '';
  document.getElementById('ePolicy').value = e.policy_id || '';
  document.getElementById('eWorkNature').value = e.work_nature || 'internal';
  document.getElementById('ePassword').value = '';
  document.getElementById('eNatId').value = e.national_id || '';
  document.getElementById('ePhone').value = e.phone || '';
  document.getElementById('eEmail').value = e.email || '';
  document.getElementById('eTitle').value = e.job_title || '';
  document.getElementById('eSalary').value = e.salary || '';
  document.getElementById('eHireDate').value = e.hire_date || '';
  document.getElementById('eBirthDate').value = e.birth_date || '';
  document.getElementById('eAddress').value = e.address || '';
  document.getElementById('ePhotoData').value = '';
  document.getElementById('ePhotoUrl').value = e.photo_url || '';
  if (e.photo_url) {
    document.getElementById('empPhotoPreview').innerHTML = `<img src="${_bmAttr(hrproMediaUrl(e.photo_url))}" onerror="handleImgError(this)">`;
  } else {
    document.getElementById('empPhotoPreview').innerHTML = `<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="opacity:0.3;"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>`;
  }

  // Apply supervisor team selection
  const tmSel = document.getElementById('eTeamMembers');
  if (tmSel) {
    const set = new Set(Array.isArray(e.team_member_ids) ? e.team_member_ids.map(x => Number(x)) : []);
    Array.from(tmSel.options).forEach(o => {
      const vid = parseInt(o.value, 10);
      o.selected = set.has(vid);
    });
    initialTeamMemberIds = new Set(Array.from(set.values()).filter(v => Number.isFinite(v) && v > 0));
    explicitTeamRemovedIds = new Set();
  }
  const tq = document.getElementById('eTeamSearch');
  if (tq) tq.value = '';
  const td = document.getElementById('eTeamDeptFilter');
  if (td) td.value = 'all';
  const tb = document.getElementById('eTeamBranchFilter');
  if (tb) tb.value = 'all';
  renderTeamChecklist();
  applyEmployeeTeamPermissions(true);
  const finp = document.getElementById('empDocFiles');
  if (finp) finp.value = '';
  loadEmployeeDocuments(e.id);
  openModal('empModal');
  syncEmpModalRequiredHint();
}

async function saveEmployee() {
  const id = document.getElementById('editEmpId').value;
  if (id && !canAdmin('update', 'employees')) return showToast(t('forbidden') || 'No permission', 'error');
  if (!id && !canAdmin('create', 'employees')) return showToast(t('forbidden') || 'No permission', 'error');
  const photoData = document.getElementById('ePhotoData').value;
  const photoUrl = document.getElementById('ePhotoUrl').value;
  const nameTrim = (document.getElementById('eName').value || '').trim();
  const usernameTrim = (document.getElementById('eUsername').value || '').trim();
  const passwordRaw = document.getElementById('ePassword').value;
  const passwordTrim = (passwordRaw || '').trim();
  if (!nameTrim) return showToast(t('emp_save_name_required'), 'error');
  if (!usernameTrim) return showToast(t('emp_save_username_required'), 'error');
  if (!id && !passwordTrim) return showToast(t('emp_save_password_required'), 'error');

  const body = {
    name: nameTrim,
    username: usernameTrim,
    password: passwordTrim,
    department_id: document.getElementById('eDepartment').value,
    branch_id: document.getElementById('eBranch').value,
    policy_id: document.getElementById('ePolicy').value,
    work_nature: document.getElementById('eWorkNature').value,
    national_id: document.getElementById('eNatId').value,
    phone: document.getElementById('ePhone').value,
    email: document.getElementById('eEmail').value,
    job_title: document.getElementById('eTitle').value,
    salary: document.getElementById('eSalary').value,
    hire_date: document.getElementById('eHireDate').value,
    birth_date: document.getElementById('eBirthDate').value,
    address: document.getElementById('eAddress').value,
    photo_data: photoData || null,
    photo_url: photoUrl || null,
  };

  const tmSel = document.getElementById('eTeamMembers');
  syncTeamSelectFromChecklist();
  if (tmSel) {
    const currentIds = Array.from(tmSel.selectedOptions)
      .map(o => parseInt(o.value, 10))
      .filter(v => Number.isFinite(v) && v > 0);
    if (id) {
      const addIds = currentIds.filter(v => !initialTeamMemberIds.has(v));
      const removeIds = Array.from(explicitTeamRemovedIds).filter(v => initialTeamMemberIds.has(v));
      body.team_member_add_ids = addIds;
      body.team_member_remove_ids = removeIds;
    } else {
      body.team_member_ids = currentIds;
    }
  }
  if (id) {
    const ac = document.getElementById('eEmpActive');
    if (ac) body.is_active = ac.checked ? 1 : 0;
    const sr = document.getElementById('eShowEmpReport');
    const sh = document.getElementById('eShowPublicHolidays');
    if (sr) body.show_employee_report = sr.checked ? 1 : 0;
    if (sh) body.show_public_holidays = sh.checked ? 1 : 0;
  }
  const res = id ? await api('PUT', `employees/${id}`, body) : await api('POST', 'employees', body);
  if (res.success) { 
    closeModal('empModal'); 
    showToast(res.message || 'Saved', 'success'); 
    loadEmployees(); 
    if (!id && passwordTrim) {
      const code = (res.data && (res.data.employee_code != null)) ? String(res.data.employee_code) : '';
      showInviteModal({ name: body.name, username: body.username, phone: body.phone, work_nature: body.work_nature, employee_code: code }, passwordTrim);
    }
  }
  else showToast(res.message || t('unknown_error'), 'error');
}

async function clearEmployeeProfilePhoto() {
  const id = document.getElementById('editEmpId').value;
  if (!id) return;
  if (!confirm(t('confirm_clear_profile_photo'))) return;
  const res = await api('PUT', `employees/${id}`, { clear_photo: 1 });
  if (res.success) {
    document.getElementById('ePhotoUrl').value = '';
    document.getElementById('ePhotoData').value = '';
    document.getElementById('empPhotoPreview').innerHTML = `<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" style="opacity:0.3;"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>`;
    const clr = document.getElementById('btnEmpClearPhoto');
    if (clr) clr.style.display = 'none';
    showToast(res.message || t('record_deleted'), 'success');
    loadEmployees();
  } else {
    showToast(res.message, 'error');
  }
}

// ── PHOTO UPLOAD HELPERS ──────────────────────────────────────────
function handleEmpFileSelect(event) {
  const file = event.target.files[0];
  if (!file) return;
  const reader = new FileReader();
  reader.onload = (e) => {
    document.getElementById('ePhotoData').value = e.target.result;
    document.getElementById('empPhotoPreview').innerHTML = `<img src="${e.target.result}">`;
    const clr = document.getElementById('btnEmpClearPhoto');
    if (clr && document.getElementById('editEmpId').value) clr.style.display = 'inline-flex';
  };
  reader.readAsDataURL(file);
}

let cameraStream = null;
function startCameraCapture() {
  openModal('cameraModal');
  navigator.mediaDevices.getUserMedia({ video: { facingMode: 'user' } })
    .then(stream => {
      cameraStream = stream;
      document.getElementById('captureVideo').srcObject = stream;
    })
    .catch(err => {
      showToast('Camera access denied', 'error');
      closeModal('cameraModal');
    });
}

function stopCameraCapture() {
  if (cameraStream) {
    cameraStream.getTracks().forEach(track => track.stop());
    cameraStream = null;
  }
  closeModal('cameraModal');
}

function takeSnapshot() {
  const video = document.getElementById('captureVideo');
  const canvas = document.getElementById('captureCanvas');
  const context = canvas.getContext('2d');
  canvas.width = 400;
  canvas.height = 400;
  context.drawImage(video, 0, 0, 400, 400);
  const data = canvas.toDataURL('image/jpeg');
  document.getElementById('ePhotoData').value = data;
  document.getElementById('empPhotoPreview').innerHTML = `<img src="${data}">`;
  const clr = document.getElementById('btnEmpClearPhoto');
  if (clr && document.getElementById('editEmpId').value) clr.style.display = 'inline-flex';
  stopCameraCapture();
}

async function resetDevice(id, name) {
  if (!canAdmin('update', 'employees')) return showToast(t('forbidden') || 'No permission', 'error');
  if (!confirm('هل تريد فعلاً إلغاء ربط هذا الموظف بجهازه الحالي؟')) return;
  const res = await api('POST', `employees/${id}/reset-device`);
  if (res.success) { showToast(res.message, 'success'); loadEmployees(); }
  else showToast(res.message, 'error');
}

async function deleteEmployee(id, name) {
  if (!canAdmin('delete', 'employees')) return showToast(t('forbidden') || 'No permission', 'error');
  if (!confirm('هل أنت متأكد من حذف هذا الموظف نهائياً؟')) return;
  const res = await api('DELETE', `employees/${id}`);
  if (res.success) { showToast('Employee deleted', 'success'); loadEmployees(); }
  else showToast(res.message, 'error');
}

function openMessageModal(id, name) {
  if (!canAdminModule('create', 'employee_messages')) return showToast(t('forbidden') || 'No permission', 'error');
  document.getElementById('msgEmpId').value = id;
  document.getElementById('msgEmpName').textContent = name;
  document.getElementById('msgContent').value = '';
  openModal('messageModal');
}

async function sendMessage() {
  if (!canAdminModule('create', 'employee_messages')) return showToast(t('forbidden') || 'No permission', 'error');
  const id = document.getElementById('msgEmpId').value;
  const msg = document.getElementById('msgContent').value.trim();
  if (!msg) return showToast('Please enter a message', 'error');
  
  const res = await api('POST', 'messages', { employee_id: id, message: msg });
  if (res.success) {
    closeModal('messageModal');
    showToast('Message sent successfully', 'success');
  } else {
    showToast(res.message, 'error');
  }
}

function openResetPwd(id, name) {
  if (!canAdmin('update', 'employees')) return showToast(t('forbidden') || 'No permission', 'error');
  document.getElementById('resetPwdEmpId').value = id;
  document.getElementById('resetPwdEmpName').textContent = name;
  document.getElementById('resetPwdNew').value = '';
  openModal('resetPwdModal');
  if (typeof updateUITranslations === 'function') updateUITranslations();
}

let currentInviteData = null;
let _companyWelcomeTemplate = null;

async function _ensureWelcomeTemplate() {
  if (_companyWelcomeTemplate !== null) return;
  try {
    const res = await api('GET', 'admin/welcome-email-template');
    if (res.success && res.data) {
      const raw = res.data.template != null ? String(res.data.template).trim() : '';
      _companyWelcomeTemplate = raw !== '' ? res.data.template : (res.data.default_template || '');
    }
  } catch(e) { _companyWelcomeTemplate = ''; }
}

function _buildWaMessage(d, pass) {
  const link = "https://hrpro.skinsion.com";
  const cname = localStorage.getItem('admin_company_name') || '';
  const empRecord = Array.isArray(allEmployeesData) ? allEmployeesData.find(e => e.username === d.username) : null;
  const empCode = d.employee_code || (empRecord ? empRecord.employee_code : '') || '';

  if (_companyWelcomeTemplate) {
    return _companyWelcomeTemplate
      .replace(/{employee_name}/g, d.name || '')
      .replace(/{username}/g, d.username || '')
      .replace(/{password}/g, pass || '')
      .replace(/{employee_code}/g, empCode)
      .replace(/{company_name}/g, cname)
      .replace(/{login_url}/g, link);
  }

  const instructions = d.work_nature === 'internal'
    ? "طريقة تسجيل الحضور للداخلي: يرجى التواجد داخل نطاق الفرع واستخدام الـ QR Code."
    : "طريقة تسجيل الحضور للموظف الخارجي: ضرورة كتابة سبب المأمورية بوضوح.";
  return t('wa_msg_template', { name: d.name, user: d.username, pass, link, instructions });
}

async function showInviteModal(emp, pass) {
  await _ensureWelcomeTemplate();
  currentInviteData = { ...emp, password: pass };
  document.getElementById('inviteUser').textContent = emp.username;
  document.getElementById('invitePass').textContent = pass;

  const instEl = document.getElementById('inviteInstructions');
  if (instEl) {
    const messageText = _buildWaMessage(emp, pass);
    instEl.style.whiteSpace = 'pre-wrap';
    instEl.textContent = messageText;
  }

  openModal('inviteModal');
  if (typeof updateUITranslations === 'function') updateUITranslations();
}

function copyInviteMessage() {
  const d = currentInviteData;
  if (!d) return;
  const msg = _buildWaMessage(d, d.password);
  navigator.clipboard.writeText(msg).then(() => showToast(t('btn_copy') + ' ✅', 'success'));
}

function sendInviteWhatsApp() {
  const d = currentInviteData;
  if (!d || !d.phone) return showToast('رقم الهاتف غير مسجل للموظف', 'error');
  const msg = _buildWaMessage(d, d.password);
  const waUrl = `https://wa.me/${d.phone.replace(/\D/g,'')}?text=${encodeURIComponent(msg)}`;
  window.open(waUrl, '_blank');
}

function copyPassword() {
  const pwd = document.getElementById('resetPwdNew').value;
  if (pwd) {
    navigator.clipboard.writeText(pwd).then(() => {
      showToast(t('pwd_copied_toast'), 'success');
    });
  }
}

async function submitResetPwd() {
  const id = document.getElementById('resetPwdEmpId').value;
  const newPwd = document.getElementById('resetPwdNew').value.trim();
  if (!newPwd) return showToast('Please enter a new password', 'error');
  const res = await api('POST', `employees/${id}/reset-password`, { new_password: newPwd });
  if (res.success) {
    closeModal('resetPwdModal');
    showToast(res.message || 'Password reset successfully', 'success');
    const emp = allEmployeesData.find(e => e.id == id);
    if (emp) {
      showInviteModal({
        name: emp.name,
        username: emp.username,
        phone: emp.phone,
        work_nature: emp.work_nature,
        employee_code: emp.employee_code != null ? String(emp.employee_code) : '',
      }, newPwd);
    }
  } else {
    showToast(res.message, 'error');
  }
}

// ── ATTENDANCE ────────────────────────────────────────────────────
let allAttendanceData = [];

async function loadAttendance() {
  const from = document.getElementById('filterFrom').value;
  const to   = document.getElementById('filterTo').value;
  
  const payload = {from, to};
  const res  = await api('GET', 'attendance', payload);
  if (res.success) {
    allAttendanceData = res.data;
    renderAttendance();
  } else {
    document.getElementById('attTable').innerHTML = `<tr><td colspan="11"><div class="empty-state"><p>${res.message || 'Error loading records'}</p></div></td></tr>`;
  }
}

function renderAttendance() {
  const query = document.getElementById('attSearch').value.toLowerCase();
  const branchId = document.getElementById('filterBranch').value;
  const deptId = document.getElementById('filterDept').value;
  const status = document.getElementById('filterStatus').value;

  const filtered = allAttendanceData.filter(r => {
    const matchSearch = (r.name || '').toLowerCase().includes(query) || (r.employee_code || '').toLowerCase().includes(query);
    const matchBranch = branchId === 'all' || r.branch_id == branchId;
    const matchDept   = deptId === 'all' || r.department_id == deptId;
    const matchStatus = status === 'all' || r.location_status === status;
    return matchSearch && matchBranch && matchDept && matchStatus;
  }).sort((a, b) => (Number(b.id) || 0) - (Number(a.id) || 0));

  const tbody = document.getElementById('attTable');
  if (!filtered.length) {
    tbody.innerHTML = `<tr><td colspan="11"><div class="empty-state"><p>No records found matching filters</p></div></td></tr>`;
    return;
  }
  
  const canUpdateAtt = canAdmin('update', 'attendance');
  const canDeleteAttGlobal = canAdmin('delete', 'attendance');
  tbody.innerHTML = filtered.map(r => {
    // Current month check for deletion
    const recordMonth = r.timestamp.substring(0, 7); // YYYY-MM
    const currentMonth = new Date().toISOString().substring(0, 7);
    const canDelete = recordMonth === currentMonth;

    return `
    <tr class="${r.location_status==='alert'?'row-alert':''}">
      <td data-label="${t('employee')}">
        <div style="display:flex; align-items:center; gap:10px;">
          ${r.photo_url 
            ? `<img src="${_bmAttr(hrproMediaUrl(r.photo_url))}" onerror="handleImgError(this)" style="width:32px;height:32px;border-radius:50%;object-fit:cover;">` 
            : `<div style="width:32px;height:32px;border-radius:50%;background:var(--surface2);display:flex;align-items:center;justify-content:center;font-size:12px;color:var(--textM);">👤</div>`}
          <div>
            <strong>${r.name}</strong><br><span style="font-size:11px;color:var(--textM)">${r.job_title||''}</span>
          </div>
        </div>
      </td>
      <td data-label="${t('code')}"><span style="font-family:var(--mono);font-size:12px">${r.employee_code}</span></td>
      <td data-label="${t('work_nature')}">
        <span class="badge" style="background:${r.work_nature==='external' ? 'rgba(245,158,11,0.1)' : 'rgba(59,130,246,0.1)'}; color:${r.work_nature==='external' ? '#f59e0b' : '#3b82f6'}; border:1px solid ${r.work_nature==='external' ? 'rgba(245,158,11,0.2)' : 'rgba(59,130,246,0.2)'};">
          ${r.work_nature==='external' ? t('external') : t('internal')}
        </span>
      </td>
      <td data-label="${t('branch')}">
        <div style="font-weight:700; color:var(--accent); font-size:12px;">${r.branch_name || '—'}</div>
        <div class="badge" style="background:var(--bg); border:1px solid var(--border); color:var(--text); font-size:10px;">${r.department_name || '—'}</div>
      </td>
      <td data-label="${t('type')}"><span class="badge ${r.type==='check_in'?'badge-in':'badge-out'}">${r.type==='check_in'?'↑ IN':'↓ OUT'}</span></td>
      <td data-label="${t('time')}" style="font-family:var(--mono);font-size:12px">${r.timestamp.replace('T',' ').substring(0,19)}</td>
      <td data-label="${t('distance')}" style="font-family:var(--mono); font-weight:600;">${r.distance_meters!=null ? Math.round(r.distance_meters)+'m' : '—'}</td>
      <td data-label="${t('status')}">
        <span class="badge badge-${r.location_status}">${r.location_status==='alert'?'⚠ Alert':'✓ OK'}</span>
        ${r.latitude ? `<a href="https://www.google.com/maps?q=${r.latitude},${r.longitude}" target="_blank" title="View Location" style="margin-left:4px; color:var(--info);">📍</a>` : ''}
      </td>
      <td data-label="${t('work_note')}" style="font-weight:600; color:var(--accent); font-size:11px;">
        <span>${[r.notes, r.visit_details].filter(Boolean).join(' | ') || (r.logged_work_nature === 'external' ? t('external_note') : '—')}</span>
      </td>
      <td data-label="${t('selfie')}">
        ${r.selfie_path
          ? `<img class="selfie-thumb" src="${_bmAttr(hrproMediaUrl(r.selfie_path))}" onerror="handleImgError(this)" onclick="openLightbox(this.getAttribute('data-selfie'))" data-selfie="${_bmAttr(r.selfie_path)}" style="border-radius:50%; width:36px; height:36px;cursor:pointer;" title="View">`
          : '<span class="no-selfie" title="No selfie">—</span>'}
      </td>
      <td data-label="${t('actions')}">
        <div class="actions-flex">
          ${canUpdateAtt ? `<button type="button" class="btn-icon-sm btn-accent" onclick="openEditAttendanceModal(${r.id})" title="${t('edit_att_record')}">
            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
          </button>` : ''}
          ${canDelete && canDeleteAttGlobal ? `
          <button class="btn-icon-sm btn-del" onclick="deleteAttendance(${r.id})" title="${t('delete')}">
            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg>
          </button>` : ''}
        </div>
      </td>
    </tr>
    `;
  }).join('');
  updateUITranslations();
}

async function openEditAttendanceModal(id) {
  if (!canAdmin('update', 'attendance')) return showToast(t('forbidden') || 'No permission', 'error');
  const r = allAttendanceData.find(x => Number(x.id) === Number(id));
  if (!r) return showToast(t('att_not_found'), 'error');
  if (!branchesList || branchesList.length === 0) {
    await loadBranches();
  }
  document.getElementById('editAttId').value = String(r.id);
  const empEl = document.getElementById('editAttEmpLabel');
  if (empEl) empEl.textContent = `${r.name || ''} (${r.employee_code || ''})`;

  const brSel = document.getElementById('editAttBranchPick');
  if (brSel) {
    const esc = (s) => String(s || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/"/g, '&quot;');
    brSel.innerHTML = `<option value="">${esc(t('att_branch_none'))}</option>` +
      (branchesList || []).map(b => `<option value="${b.id}">${esc(b.name)}</option>`).join('');
    brSel.value = '';
  }

  document.getElementById('editAttType').value = r.type === 'check_out' ? 'check_out' : 'check_in';
  const typEl = document.getElementById('editAttType');
  if (typEl && !typEl.dataset.attTitleHook) {
    typEl.dataset.attTitleHook = '1';
    typEl.addEventListener('change', refreshEditAttendanceTitle);
  }
  const ts = String(r.timestamp || '').replace('T', ' ');
  document.getElementById('editAttDate').value = ts.length >= 10 ? ts.substring(0, 10) : '';
  document.getElementById('editAttTime').value = ts.length >= 16 ? ts.substring(11, 16) : '';

  document.getElementById('editAttLat').value = (r.latitude != null && r.latitude !== '') ? String(r.latitude) : '';
  document.getElementById('editAttLng').value = (r.longitude != null && r.longitude !== '') ? String(r.longitude) : '';
  if (r.distance_meters != null && r.distance_meters !== '') {
    const dm = Number(r.distance_meters);
    document.getElementById('editAttDist').value = Number.isFinite(dm) ? String(dm) : '';
  } else {
    document.getElementById('editAttDist').value = '';
  }
  document.getElementById('editAttLocStatus').value = r.location_status === 'alert' ? 'alert' : 'ok';
  document.getElementById('editAttVisit').value = r.visit_details || '';
  document.getElementById('editAttNotes').value = r.notes || '';
  refreshEditAttendanceTitle();
  openModal('editAttendanceModal');
  updateUITranslations();
}

async function submitEditAttendance() {
  if (!canAdmin('update', 'attendance')) return showToast(t('forbidden') || 'No permission', 'error');
  const id = document.getElementById('editAttId').value;
  const date = document.getElementById('editAttDate').value;
  const time = document.getElementById('editAttTime').value;
  if (!date || !time) {
    showToast(t('manual_att_date') + ' / ' + t('manual_att_time'), 'error');
    return;
  }
  const latRaw = document.getElementById('editAttLat').value.trim();
  const lngRaw = document.getElementById('editAttLng').value.trim();
  const distRaw = document.getElementById('editAttDist').value.trim();
  const payload = {
    type: document.getElementById('editAttType').value,
    date,
    time,
    latitude: latRaw === '' ? null : latRaw,
    longitude: lngRaw === '' ? null : lngRaw,
    distance_meters: distRaw === '' ? null : distRaw,
    location_status: document.getElementById('editAttLocStatus').value,
    visit_details: document.getElementById('editAttVisit').value,
    notes: document.getElementById('editAttNotes').value
  };
  const btn = document.querySelector('#editAttendanceModal .btn-primary');
  const oldHtml = btn ? btn.innerHTML : '';
  if (btn) { btn.disabled = true; btn.innerHTML = '<div class="spinner"></div>'; }
  const res = await api('PUT', `attendance/${id}`, payload);
  if (btn) { btn.disabled = false; btn.innerHTML = oldHtml; }
  if (res.success) {
    closeModal('editAttendanceModal');
    showToast(res.message || t('att_edit_success'), 'success');
    loadAttendance();
  } else {
    showToast(res.message || 'Error', 'error');
  }
}

async function deleteAttendance(id) {
  if (!canAdmin('delete', 'attendance')) return showToast(t('forbidden') || 'No permission', 'error');
  if (!confirm(t('confirm_delete_record'))) return;
  const res = await api('DELETE', `attendance/${id}`);
  if (res.success) {
    showToast(t('record_deleted'), 'success');
    loadAttendance();
  } else {
    showToast(res.message, 'error');
  }
}

function clearAttendanceFilters() {
  document.getElementById('attSearch').value = '';
  document.getElementById('filterBranch').value = 'all';
  document.getElementById('filterDept').value = 'all';
  document.getElementById('filterStatus').value = 'all';
  setDefaultDates();
  loadAttendance();
}

function openManualAtt(id, name) {
  if (!canAdmin('create', 'employees')) return showToast(t('forbidden') || 'No permission', 'error');
  // Hide employee selector — employee is already known (called from Employees tab)
  const selectorWrap = document.getElementById('manualAttEmpSelectorWrap');
  if (selectorWrap) selectorWrap.style.display = 'none';

  document.getElementById('manualAttEmpId').value = id;
  const nameEl = document.getElementById('manualAttEmpName');
  if (nameEl) nameEl.textContent = name;

  const now = new Date();
  document.getElementById('manualAttDate').value = now.toLocaleDateString('en-CA');
  document.getElementById('manualAttTime').value = now.toTimeString().substring(0, 5);
  openModal('manualAttModal');
}

async function openManualAttFromTab() {
  if (!canAdmin('create', 'employees')) return showToast(t('forbidden') || 'No permission', 'error');
  // Show selector section
  const selectorWrap = document.getElementById('manualAttEmpSelectorWrap');
  if (selectorWrap) selectorWrap.style.display = '';

  // Clear any previously selected employee
  document.getElementById('manualAttEmpId').value = '';
  const nameEl = document.getElementById('manualAttEmpName');
  if (nameEl) nameEl.textContent = '';

  // Reset search field
  const searchEl = document.getElementById('manualAttEmpSearch');
  if (searchEl) searchEl.value = '';

  // Clear list (it will be populated after modal opens)
  const list = document.getElementById('manualAttEmpList');
  if (list) { list.innerHTML = ''; list.className = 'man-att-emp-list'; }

  const now = new Date();
  document.getElementById('manualAttDate').value = now.toLocaleDateString('en-CA');
  document.getElementById('manualAttTime').value = now.toTimeString().substring(0, 5);
  openModal('manualAttModal');

  // Render / fetch AFTER modal is open so the list is visible
  if (!allEmployeesData || !allEmployeesData.length) {
    if (list) {
      list.className = 'man-att-emp-list open';
      list.innerHTML = `<div class="man-att-emp-loading"><div class="spinner" style="width:16px;height:16px;border-width:2px"></div> ${t('loading_employees')}</div>`;
    }
    try {
      const res = await api('GET', 'employees');
      if (res.success && res.data) allEmployeesData = res.data;
    } catch (e) { /* ignore */ }
  }
  _renderManualAttEmpList('');

  // Auto-focus search so user can type immediately
  setTimeout(() => { if (searchEl) searchEl.focus(); }, 150);
}

function filterManualAttEmps() {
  const q = (document.getElementById('manualAttEmpSearch').value || '').trim();
  _renderManualAttEmpList(q);
}

function _renderManualAttEmpList(q) {
  const list = document.getElementById('manualAttEmpList');
  if (!list) return;

  const data = allEmployeesData || [];
  if (!data.length) { list.className = 'man-att-emp-list'; return; }

  const ql = q.toLowerCase();
  const filtered = ql
    ? data.filter(e =>
        (e.name || '').toLowerCase().includes(ql) ||
        (e.employee_code || '').toLowerCase().includes(ql))
    : data.slice(0, 60); // show first 60 when search is empty

  if (!filtered.length) {
    list.className = 'man-att-emp-list open';
    list.innerHTML = `<div class="man-att-emp-empty">${t('no_results')}</div>`;
    return;
  }

  list.className = 'man-att-emp-list open';
  list.innerHTML = filtered.map(e => {
    const safeName = (e.name || '').replace(/&/g,'&amp;').replace(/"/g,'&quot;').replace(/</g,'&lt;');
    const safeCode = (e.employee_code || '').replace(/&/g,'&amp;').replace(/</g,'&lt;');
    return `
    <div class="man-att-emp-item" tabindex="0" role="button"
         data-empid="${e.id}" data-empname="${safeName}">
      <div>
        <div class="man-att-emp-item-name">${safeName}</div>
        ${safeCode ? `<div class="man-att-emp-item-code">${safeCode}</div>` : ''}
      </div>
    </div>`;
  }).join('');

  // Use event delegation — avoids inline onclick quoting issues
  list.onclick = function (ev) {
    const item = ev.target.closest('.man-att-emp-item');
    if (item) selectManualAttEmp(item.dataset.empid, item.dataset.empname);
  };
  list.onkeydown = function (ev) {
    if (ev.key === 'Enter') {
      const item = ev.target.closest('.man-att-emp-item');
      if (item) selectManualAttEmp(item.dataset.empid, item.dataset.empname);
    }
  };
}

function selectManualAttEmp(id, name) {
  document.getElementById('manualAttEmpId').value = id;
  const nameEl = document.getElementById('manualAttEmpName');
  if (nameEl) nameEl.textContent = name;
  const searchEl = document.getElementById('manualAttEmpSearch');
  if (searchEl) searchEl.value = name;
  const list = document.getElementById('manualAttEmpList');
  if (list) { list.innerHTML = ''; list.className = 'man-att-emp-list'; list.onclick = null; list.onkeydown = null; }
}

async function submitManualAtt() {
  if (!canAdmin('create', 'employees')) return showToast(t('forbidden') || 'No permission', 'error');
  const payload = {
    employee_id: document.getElementById('manualAttEmpId').value,
    type: document.getElementById('manualAttType').value,
    date: document.getElementById('manualAttDate').value,
    time: document.getElementById('manualAttTime').value
  };
  if (!payload.employee_id) {
    showToast(t('select_employee'), 'error');
    return;
  }
  if (!payload.date || !payload.time) {
    showToast(t('manual_att_date') + ' / ' + t('manual_att_time'), 'error');
    return;
  }

  const btn = document.querySelector('#manualAttModal .btn-primary');
  let oldText = '';
  if (btn) { oldText = btn.innerHTML; btn.disabled = true; btn.innerHTML = '<div class="spinner"></div>'; }

  const res = await api('POST', 'admin/attendance/manual', payload);

  if (btn) { btn.disabled = false; btn.innerHTML = oldText; }

  if (res.success) {
    closeModal('manualAttModal');
    showToast(t('manual_att_success'), 'success');
    const tabDash = document.getElementById('tab-dashboard');
    const tabAtt  = document.getElementById('tab-attendance');
    if (tabDash && tabDash.style.display !== 'none') {
      loadDashboard();
    } else if (tabAtt && tabAtt.style.display !== 'none') {
      loadAttendance();
    }
  } else {
    showToast(res.message, 'error');
  }
}

async function exportCSV() {
  if (!canAdmin('read', 'attendance')) return showToast(t('forbidden') || 'No permission', 'error');
  const tok = authToken || '';
  if (!tok) return showToast(t('unknown_error') || 'Session expired. Please sign in again.', 'error');
  const from = document.getElementById('filterFrom').value;
  const to   = document.getElementById('filterTo').value;
  const deptRaw = document.getElementById('filterDept') ? document.getElementById('filterDept').value : '';
  const dept = deptRaw && deptRaw !== 'all' ? deptRaw : '';
  let url = `${API}?route=${encodeURIComponent('reports/export')}&from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`;
  if (dept) url += `&department_id=${encodeURIComponent(dept)}`;
  try {
    const r = await fetch(url, {
      method: 'GET',
      headers: { Authorization: 'Bearer ' + tok }
    });
    const buf = await r.arrayBuffer();
    const u8 = new Uint8Array(buf);
    let jsonOff = 0;
    if (u8.length >= 3 && u8[0] === 0xef && u8[1] === 0xbb && u8[2] === 0xbf) {
      jsonOff = 3;
    }
    if (u8.length > jsonOff && u8[jsonOff] === 0x7b) {
      let msg = t('unknown_error') || 'Export failed';
      try {
        const j = JSON.parse(new TextDecoder('utf-8').decode(u8.slice(jsonOff)));
        if (j.message) msg = j.message;
      } catch (e) { /* keep msg */ }
      return showToast(msg, 'error');
    }
    if (!r.ok) {
      return showToast(r.statusText || 'Export failed', 'error');
    }
    let csvMime = 'text/csv;charset=utf-8';
    if (u8.length >= 2 && u8[0] === 0xff && u8[1] === 0xfe) {
      csvMime = 'text/csv;charset=utf-16le';
    }
    const blob = new Blob([buf], { type: csvMime });
    const disp = r.headers.get('Content-Disposition') || '';
    let fname = 'attendance_' + new Date().toISOString().slice(0, 10).replace(/-/g, '') + '.csv';
    const m = /filename\*?=(?:UTF-8'')?["']?([^"'\s;]+)/i.exec(disp);
    if (m && m[1]) {
      try {
        fname = decodeURIComponent(m[1].replace(/"/g, ''));
      } catch (e) {
        fname = m[1];
      }
    }
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = fname;
    document.body.appendChild(a);
    a.click();
    a.remove();
    URL.revokeObjectURL(a.href);
  } catch (e) {
    showToast(t('unknown_error') || 'Network error', 'error');
  }
}

// ── COMPANY NEWS (employee app ticker) ────────────────────────────
async function loadCompanyNewsAdmin() {
  const res = await api('GET', 'admin/company-news');
  const tbody = document.getElementById('companyNewsTable');
  if (!tbody) return;
  if (!res.success) {
    tbody.innerHTML = `<tr><td colspan="4" style="padding:20px;">${(res.message || '—').replace(/</g, '&lt;')}</td></tr>`;
    return;
  }
  const rows = res.data || [];
  if (!rows.length) {
    tbody.innerHTML = `<tr><td colspan="4" style="text-align:center;padding:24px;color:var(--text-muted);">${t('company_news_empty')}</td></tr>`;
    return;
  }
  tbody.innerHTML = rows.map(r => {
    const msg = String(r.message || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
    const active = r.is_active == 1 || r.is_active === true;
    return `<tr>
      <td data-label="${t('company_news_message')}" style="max-width:min(520px,92vw);white-space:pre-wrap;font-size:13px;line-height:1.45;">${msg}</td>
      <td data-label="${t('company_news_sort')}">${r.sort_order}</td>
      <td data-label="${t('status')}">${active ? t('status_active') : t('status_inactive')}</td>
      <td data-label="${t('actions')}">
        <button type="button" class="btn btn-sm btn-ghost" onclick="toggleCompanyNews(${r.id}, ${active ? 0 : 1})">${active ? t('company_news_hide') : t('company_news_show')}</button>
        <button type="button" class="btn btn-sm btn-del" onclick="deleteCompanyNews(${r.id})">${t('delete')}</button>
      </td>
    </tr>`;
  }).join('');
  updateUITranslations();
}

async function addCompanyNewsItem() {
  const ta = document.getElementById('companyNewsNewMsg');
  const so = document.getElementById('companyNewsSort');
  const active = document.getElementById('companyNewsActive');
  if (!ta) return;
  const message = ta.value.trim();
  if (!message) return showToast(t('fill_fields'), 'error');
  const sortVal = so && so.value !== '' ? parseInt(so.value, 10) : 0;
  const res = await api('POST', 'admin/company-news', {
    message,
    sort_order: Number.isFinite(sortVal) ? sortVal : 0,
    is_active: active ? (active.checked ? 1 : 0) : 1
  });
  if (res.success) {
    showToast(res.message || 'OK', 'success');
    ta.value = '';
    if (so) so.value = '0';
    loadCompanyNewsAdmin();
  } else {
    showToast(res.message, 'error');
  }
}

async function toggleCompanyNews(id, toActive) {
  const res = await api('PUT', 'admin/company-news/' + id, { is_active: toActive ? 1 : 0 });
  if (res.success) {
    showToast(res.message || 'OK', 'success');
    loadCompanyNewsAdmin();
  } else {
    showToast(res.message, 'error');
  }
}

async function deleteCompanyNews(id) {
  if (!confirm(t('confirm_delete_record'))) return;
  const res = await api('DELETE', 'admin/company-news/' + id);
  if (res.success) {
    showToast(res.message || 'OK', 'success');
    loadCompanyNewsAdmin();
  } else {
    showToast(res.message, 'error');
  }
}

// ── LEAVE ─────────────────────────────────────────────────────────
let allLeaveData = [];
let _leaveReviewCurrent = null;
let _leaveReviewDecision = null;
let _editLeaveCurrent = null;

function leaveRowIsPermission(r) {
  if (!r) return false;
  const lt = String(r.leave_type || '').toLowerCase();
  if (lt === 'permission') return true;
  const pf = String(r.permission_from || '').trim();
  const pt = String(r.permission_to || '').trim();
  return pf.length > 0 && pt.length > 0;
}

function leaveTypeBadgeHtml(r) {
  if (leaveRowIsPermission(r)) {
    const extra = (r.permission_from && r.permission_to)
      ? (' · ' + formatPermissionTimeRange(r.permission_from, r.permission_to))
      : '';
    const isMission = String(r.permission_kind || '').toLowerCase() === 'mission';
    const label = isMission ? t('permission_mission_label') : t('permission');
    return label + extra;
  }
  const lt = String(r.leave_type || 'annual').toLowerCase();
  return t(lt) || lt;
}

function leaveRequestTypeBadgeStyle(r) {
  if (!leaveRowIsPermission(r)) return 'background:var(--bg); border:1px solid var(--border); color:var(--accent); font-weight:700;';
  const isMission = String(r.permission_kind || '').toLowerCase() === 'mission';
  if (isMission) {
    return 'background:rgba(14,165,233,.14); border:1px solid rgba(14,165,233,.55); color:var(--accent); font-weight:700;';
  }
  return 'background:var(--bg); border:1px solid var(--border); color:var(--accent); font-weight:700;';
}

function findNavTriggerForTab(tabName) {
  if (tabName === 'leave') {
    const el = document.getElementById('navLeave');
    if (el) return el;
  }
  return document.querySelector('.sidebar-nav .nav-item[onclick*="showTab(\'' + tabName + '\'"]')
    || document.querySelector('.bottom-nav .bottom-nav-item[onclick*="showTab(\'' + tabName + '\'"]');
}

function adminOpenLeaveQuickReview() {
  const nav = findNavTriggerForTab('leave');
  showTab('leave', nav || null);
  const statusEl = document.getElementById('leaveFilterStatus');
  if (statusEl) statusEl.value = 'pending';
  const searchEl = document.getElementById('leaveSearch');
  if (searchEl) searchEl.value = '';
  loadLeave().catch(() => {});
  const table = document.getElementById('leaveTable');
  if (table && typeof table.scrollIntoView === 'function') {
    table.scrollIntoView({ behavior: 'smooth', block: 'start' });
  }
}

function adminNavigateFromActionUrl(url) {
  closeModal('adminNotifModal');
  const raw = String(url || '').trim();
  if (!raw) return;
  try {
    const abs = new URL(raw, location.origin);
    const tab = abs.searchParams.get('open_tab') || abs.searchParams.get('tab');
    if (tab && document.getElementById('tab-' + tab)) {
      const nav = findNavTriggerForTab(tab);
      showTab(tab, nav || null);
      return;
    }
  } catch (e) { /* ignore */ }
  window.location.href = raw;
}

function applyAdminDeepLinkFromUrl() {
  if (!authToken) return;
  try {
    const p = new URLSearchParams(window.location.search);
    const tab = p.get('open_tab') || p.get('tab');
    if (!tab || !document.getElementById('tab-' + tab)) return;
    const nav = findNavTriggerForTab(tab);
    showTab(tab, nav || null);
    const clean = new URL(location.href);
    clean.searchParams.delete('open_tab');
    clean.searchParams.delete('tab');
    history.replaceState({}, '', clean.pathname + clean.search);
  } catch (e) { /* ignore */ }
}

(function setupAdminNotifListDelegation() {
  const box = document.getElementById('adminNotifList');
  if (!box || box.dataset.hrproClick === '1') return;
  box.dataset.hrproClick = '1';
  box.addEventListener('click', async (e) => {
    const markBtn = e.target && e.target.closest ? e.target.closest('.notif-mark-read-btn') : null;
    if (markBtn) {
      e.preventDefault();
      e.stopPropagation();
      await markAdminNotifRead(markBtn.getAttribute('data-id'));
      return;
    }
    const delBtn = e.target && e.target.closest ? e.target.closest('.notif-delete-btn') : null;
    if (delBtn) {
      e.preventDefault();
      e.stopPropagation();
      await deleteAdminNotif(delBtn.getAttribute('data-id'));
      return;
    }
    const hardDelBtn = e.target && e.target.closest ? e.target.closest('.notif-hard-delete-btn') : null;
    if (hardDelBtn) {
      e.preventDefault();
      e.stopPropagation();
      await hardDeleteAdminNotif(hardDelBtn.getAttribute('data-id'));
      return;
    }
    const row = e.target && e.target.closest ? e.target.closest('.admin-notif-row') : null;
    if (!row) return;
    const notifId = Number(row.getAttribute('data-id') || 0);
    const isRead = row.getAttribute('data-is-read') === '1';
    const notifType = String(row.getAttribute('data-type') || '').toLowerCase();
    if (notifId > 0 && !isRead) {
      await markAdminNotifRead(notifId);
      row.setAttribute('data-is-read', '1');
    }
    const enc = row.getAttribute('data-action-url');
    if (!enc) {
      if (notifType === 'leave_request' || notifType === 'permission_request') {
        adminOpenLeaveQuickReview();
      }
      return;
    }
    try {
      const rawUrl = decodeURIComponent(enc);
      const abs = new URL(rawUrl, location.origin);
      const tab = String(abs.searchParams.get('open_tab') || abs.searchParams.get('tab') || '').toLowerCase();
      if (notifType === 'leave_request' || notifType === 'permission_request' || tab === 'leave') {
        adminOpenLeaveQuickReview();
        return;
      }
      adminNavigateFromActionUrl(rawUrl);
    } catch (err) {
      adminNavigateFromActionUrl(enc);
    }
  });
})();

async function loadLeave() {
  const res = await api('GET', 'leave');
  if (res.success) {
    allLeaveData = res.data || [];
    const pendingCount = allLeaveData.filter(r => r.status === 'pending').length;
    const badge = document.getElementById('leaveBadge');
    if (pendingCount > 0) { badge.textContent = pendingCount; badge.style.display = 'inline'; }
    else badge.style.display = 'none';
    renderLeave();
  } else {
    document.getElementById('leaveTable').innerHTML = `<tr><td colspan="8"><div class="empty-state"><p>${res.message || 'Error loading records'}</p></div></td></tr>`;
  }
}

function renderLeave() {
  const query = document.getElementById('leaveSearch').value.toLowerCase();
  const status = document.getElementById('leaveFilterStatus').value;
  const from = document.getElementById('leaveFilterFrom').value;
  const to = document.getElementById('leaveFilterTo').value;

  const filtered = allLeaveData.filter(r => {
    const matchSearch = (r.name || '').toLowerCase().includes(query) || (r.employee_code || '').toLowerCase().includes(query);
    const matchStatus = status === 'all' || r.status === status;
    
    let matchDate = true;
    if (from && r.start_date < from) matchDate = false;
    if (to && r.end_date > to) matchDate = false;
    
    return matchSearch && matchStatus && matchDate;
  });

  const tbody = document.getElementById('leaveTable');
  if (!filtered.length) {
    const emptyMessage = status === 'pending'
      ? (t('leave_no_new_requests') || 'No new requests')
      : (t('no_records') || 'No records found matching filters');
    tbody.innerHTML = `<tr><td colspan="8"><div class="empty-state"><p>${emptyMessage}</p></div></td></tr>`;
    return;
  }

  tbody.innerHTML = filtered.map(r => {
    // 3-Month Rule Check: current month + 2 previous
    const recordDate = new Date(r.start_date);
    const firstDayAllowed = new Date();
    firstDayAllowed.setMonth(firstDayAllowed.getMonth() - 2);
    firstDayAllowed.setDate(1);
    firstDayAllowed.setHours(0,0,0,0);
    const canEdit = recordDate >= firstDayAllowed;

    return `
    <tr>
      <td data-label="${t('employee')}">
        <div style="display:flex; align-items:center; gap:10px;">
          ${r.photo_url 
            ? `<img src="${_bmAttr(hrproMediaUrl(r.photo_url))}" onerror="handleImgError(this)" style="width:32px;height:32px;border-radius:50%;object-fit:cover;">` 
            : `<div style="width:32px;height:32px;border-radius:50%;background:var(--surface2);display:flex;align-items:center;justify-content:center;font-size:12px;color:var(--textM);">👤</div>`}
          <div>
            <strong>${r.name}</strong><br><span style="font-size:11px;color:var(--textM)">${r.employee_code}</span>
          </div>
        </div>
      </td>
      <td data-label="${t('type')}"><span class="badge leave-type-badge" style="${leaveRequestTypeBadgeStyle(r)}">${leaveTypeBadgeHtml(r)}</span></td>
      <td data-label="${t('leave_start')}"><span style="font-family:var(--mono);">${r.start_date}</span></td>
      <td data-label="${t('leave_end')}"><span style="font-family:var(--mono);">${r.end_date}</span></td>
      <td data-label="${t('reason')}"><div class="leave-reason-preview" style="max-width:280px;font-weight:600;color:var(--accent);">${String(r.reason || '').replace(/</g,'&lt;')}</div></td>
      <td data-label="${t('status')}"><span class="badge badge-${r.status}">${t('status_' + r.status).toUpperCase()}</span></td>
      <td data-label="${t('leave_created')}"><span style="font-family:var(--mono);">${r.created_at.substring(0,10)}</span></td>
      <td data-label="${t('actions')}">
        <div class="actions-flex">
          ${r.status==='pending' ? `<button class="btn-icon-sm btn-info" onclick='openLeaveReview(${JSON.stringify(r)})' title="${t('review')}"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg></button>` : ''}
          ${canEdit ? `<button class="btn-icon-sm btn-accent" onclick='openEditLeave(${JSON.stringify(r)})' title="${t('edit')}"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg></button>` : ''}
          ${r.status==='pending' ? `<button class="btn-icon-sm btn-del" onclick='deleteLeaveRequest(${Number(r.id)})' title="${t('delete')}"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg></button>` : ''}
          ${!canEdit && r.status!=='pending' ? '—' : ''}
        </div>
      </td>
    </tr>
    `;
  }).join('');
  updateUITranslations();
}

function clearLeaveFilters() {
  document.getElementById('leaveSearch').value = '';
  document.getElementById('leaveFilterStatus').value = 'all';
  document.getElementById('leaveFilterFrom').value = '';
  document.getElementById('leaveFilterTo').value = '';
  renderLeave();
}

/** Same-day permission: if end time is before start in DB, show chronologically when gap ≤ 12h (likely reversed HH:MM). */
function formatTime12Hour(v) {
  const m = String(v || '').trim().match(/^(\d{1,2}):(\d{2})$/);
  if (!m) return String(v || '');
  let h = parseInt(m[1], 10);
  const mi = parseInt(m[2], 10);
  if (h > 23 || mi > 59) return String(v || '');
  const isPm = h >= 12;
  h = h % 12;
  if (h === 0) h = 12;
  const mm = String(mi).padStart(2, '0');
  if ((document.documentElement.lang || 'ar').toLowerCase().startsWith('ar')) {
    return `${h}:${mm} ${isPm ? 'م' : 'ص'}`;
  }
  return `${h}:${mm} ${isPm ? 'PM' : 'AM'}`;
}

function parseHHMMToMinutes(v) {
  const m = String(v || '').trim().match(/^(\d{1,2}):(\d{2})$/);
  if (!m) return null;
  const h = parseInt(m[1], 10);
  const mi = parseInt(m[2], 10);
  if (h > 23 || mi > 59) return null;
  return h * 60 + mi;
}

function normalizeDayInterval(startMin, endMin) {
  if (!Number.isFinite(startMin) || !Number.isFinite(endMin)) return null;
  if (endMin > startMin) return [startMin, endMin];
  // Handle likely swapped values for same-day permission rows.
  if (startMin - endMin <= 12 * 60) return [endMin, startMin];
  return null;
}

function clipIntervalToShift(interval, shiftStart, shiftEnd) {
  if (!interval || !Number.isFinite(shiftStart) || !Number.isFinite(shiftEnd) || shiftEnd <= shiftStart) return null;
  const st = Math.max(interval[0], shiftStart);
  const en = Math.min(interval[1], shiftEnd);
  return en > st ? [st, en] : null;
}

function mergeIntervals(intervals) {
  if (!Array.isArray(intervals) || intervals.length === 0) return [];
  const sorted = intervals
    .filter((x) => Array.isArray(x) && Number.isFinite(x[0]) && Number.isFinite(x[1]) && x[1] > x[0])
    .sort((a, b) => a[0] - b[0]);
  if (!sorted.length) return [];
  const merged = [sorted[0].slice()];
  for (let i = 1; i < sorted.length; i++) {
    const cur = sorted[i];
    const last = merged[merged.length - 1];
    if (cur[0] <= last[1]) {
      last[1] = Math.max(last[1], cur[1]);
    } else {
      merged.push(cur.slice());
    }
  }
  return merged;
}

function intervalsTotalMinutes(intervals) {
  return mergeIntervals(intervals).reduce((sum, itv) => sum + (itv[1] - itv[0]), 0);
}

function calcEffectiveWorkPermissionMinutes(dayRow) {
  const shiftStart = parseHHMMToMinutes(dayRow && dayRow.sch_in ? String(dayRow.sch_in).substring(0, 5) : '');
  const shiftEnd = parseHHMMToMinutes(dayRow && dayRow.sch_out ? String(dayRow.sch_out).substring(0, 5) : '');
  if (shiftStart == null || shiftEnd == null || shiftEnd <= shiftStart) return 0;

  const intervals = [];
  const ciMin = parseHHMMToMinutes(dayRow && dayRow.check_in ? String(dayRow.check_in).substring(11, 16) : '');
  const coMin = parseHHMMToMinutes(dayRow && dayRow.check_out ? String(dayRow.check_out).substring(11, 16) : '');
  const attInterval = normalizeDayInterval(ciMin, coMin);
  const clippedAtt = clipIntervalToShift(attInterval, shiftStart, shiftEnd);
  if (clippedAtt) intervals.push(clippedAtt);

  const perms = Array.isArray(dayRow && dayRow.approved_permissions) ? dayRow.approved_permissions : [];
  perms.forEach((p) => {
    if (String(p && p.permission_kind ? p.permission_kind : '').toLowerCase() === 'mission') {
      return;
    }
    const pf = parseHHMMToMinutes(p && p.permission_from ? String(p.permission_from) : '');
    const pt = parseHHMMToMinutes(p && p.permission_to ? String(p.permission_to) : '');
    const pInt = normalizeDayInterval(pf, pt);
    const clippedPerm = clipIntervalToShift(pInt, shiftStart, shiftEnd);
    if (clippedPerm) intervals.push(clippedPerm);
  });

  const unionMinutes = intervalsTotalMinutes(intervals);
  const shiftMinutes = shiftEnd - shiftStart;
  return Math.max(0, Math.min(unionMinutes, shiftMinutes));
}

function formatDurationMinutes(totalMinutes) {
  const mins = Math.max(0, Math.round(Number(totalMinutes) || 0));
  if (mins <= 0) return '—';
  const h = Math.floor(mins / 60);
  const m = mins % 60;
  const isAr = (document.documentElement.lang || 'ar').toLowerCase().startsWith('ar');
  if (isAr) return `${h}س ${String(m).padStart(2, '0')}د`;
  return `${h}h ${String(m).padStart(2, '0')}m`;
}

function formatPermissionTimeRange(pf, pt) {
  const a = parseHHMMToMinutes(pf), b = parseHHMMToMinutes(pt);
  if (a == null || b == null) return `${formatTime12Hour(pf)} → ${formatTime12Hour(pt)}`;
  if (a <= b) return `${formatTime12Hour(pf)} → ${formatTime12Hour(pt)}`;
  if (a - b <= 12 * 60) return `${formatTime12Hour(pt)} → ${formatTime12Hour(pf)}`;
  return `${formatTime12Hour(pf)} → ${formatTime12Hour(pt)}`;
}

function openLeaveReview(r) {
  _leaveReviewCurrent = r || null;
  _leaveReviewDecision = null;
  const waBtn = document.getElementById('leaveWaBtn');
  if (waBtn) waBtn.style.display = 'none';
  document.getElementById('reviewLeaveId').value = r.id;
  document.getElementById('adminNote').value = '';
  document.getElementById('leaveBalanceSummary').innerHTML = `<div style="text-align:center; padding:10px; opacity:0.5;">${t('loading')}</div>`;

  if (leaveRowIsPermission(r)) {
    const pf = r.permission_from || '—';
    const pt = r.permission_to || '—';
    const isMission = String(r.permission_kind || '').toLowerCase() === 'mission';
    const typeBadges = isMission
      ? `<span class="badge" style="background:rgba(14,165,233,.22);color:var(--accent);font-weight:700;">${t('permission_mission_label')}</span>`
      : `<span class="badge" style="background:var(--accent); color:#fff; font-weight:700;">${t('permission')}</span>`;
    document.getElementById('leaveDetails').innerHTML = `
    <div style="display:flex; justify-content:space-between; margin-bottom:8px;">
        <span style="color:var(--text-muted)">${t('employee')}:</span>
        <strong style="color:var(--text)">${r.name} (${r.employee_code})</strong>
    </div>
    <div style="display:flex; justify-content:space-between; margin-bottom:8px;align-items:center;gap:8px;flex-wrap:wrap;">
        <span style="color:var(--text-muted)">${t('type')}:</span>
        <span style="display:inline-flex;flex-wrap:wrap;gap:6px;justify-content:flex-end;">${typeBadges}</span>
    </div>
    <div style="display:flex; justify-content:space-between; margin-bottom:8px;">
        <span style="color:var(--text-muted)">${t('date')}:</span>
        <span style="font-family:var(--mono);">${r.start_date}</span>
    </div>
    <div style="display:flex; justify-content:space-between; margin-bottom:8px;">
        <span style="color:var(--text-muted)">${t('permission_time_range') || 'Time'}:</span>
        <span style="font-family:var(--mono);font-weight:700;">${formatPermissionTimeRange(pf, pt)}</span>
    </div>
    <div style="border-top:1px solid var(--border); margin-top:10px; padding-top:10px;">
        <div style="color:var(--text-muted); font-size:12px; margin-bottom:4px;">${t('reason')}:</div>
        <div style="font-weight:600; color:var(--text);">${String(r.reason || '').replace(/</g, '&lt;')}</div>
    </div>`;
    document.getElementById('leaveBalanceSummary').innerHTML = `<p style="font-size:12px;color:var(--textM);line-height:1.45;">${t('loading')}</p>`;
    openModal('leaveModal');
    updateUITranslations();
    const balYear = (r.start_date && String(r.start_date).length >= 4) ? parseInt(String(r.start_date).slice(0, 4), 10) : new Date().getFullYear();
    if (isMission) {
      document.getElementById('leaveBalanceSummary').innerHTML =
        `<p style="font-size:12px;color:var(--textM);line-height:1.55;margin:0;">${t('permission_mission_quota_exempt')}</p>`;
      return;
    }
    api('GET', `leave/balance/${r.employee_id}`, { year: balYear, permission_date: r.start_date }).then((res) => {
      if (!res.success) {
        document.getElementById('leaveBalanceSummary').innerHTML = `<p style="font-size:12px;color:var(--danger);line-height:1.45;">${String(res.message || t('unknown_error') || 'Error').replace(/</g, '&lt;')}</p>`;
        return;
      }
      const d = res.data || {};
      const limit = d.permission_limit;
      const used = Number(d.permission_used_month || 0);
      const remaining = d.permission_remaining_month;
      const monthRef = String(d.permission_ref_month || '').trim();
      const isUnlimited = limit == null || String(limit) === '';
      const summary = isUnlimited
        ? t('permission_usage_month_unlimited', { used: String(used) })
        : t('permission_usage_month_summary', { allowed: String(limit), used: String(used), remaining: String(remaining != null ? remaining : 0) });
      const warn = !isUnlimited && Number(remaining) <= 0
        ? `<div style="margin-top:8px;font-size:12px;color:var(--danger);font-weight:700;">⚠ ${String(t('permission_usage_warn_limit_reached')).replace(/</g, '&lt;')}</div>`
        : '';
      const monthLine = monthRef
        ? `<div style="font-size:11px;color:var(--textM);margin-top:4px;font-family:var(--mono);">${monthRef}</div>`
        : '';
      document.getElementById('leaveBalanceSummary').innerHTML =
        `<p style="font-size:12px;color:var(--textM);line-height:1.45;margin:0 0 8px;">${t('permission_no_balance')}</p>`
        + `<div style="font-size:13px;line-height:1.5;color:var(--text);font-weight:700;">${String(summary).replace(/</g, '&lt;')}</div>`
        + monthLine + warn;
    });
    return;
  }

  document.getElementById('leaveDetails').innerHTML = `
    <div style="display:flex; justify-content:space-between; margin-bottom:8px;">
        <span style="color:var(--text-muted)">${t('employee')}:</span>
        <strong style="color:var(--text)">${r.name} (${r.employee_code})</strong>
    </div>
    <div style="display:flex; justify-content:space-between; margin-bottom:8px;">
        <span style="color:var(--text-muted)">${t('type')}:</span>
        <span class="badge" style="background:var(--accent); color:#fff; font-weight:700;">${t(r.leave_type)}</span>
    </div>
    <div style="display:flex; justify-content:space-between; margin-bottom:8px;">
        <span style="color:var(--text-muted)">${t('date')}:</span>
        <span style="font-family:var(--mono);">${r.start_date} → ${r.end_date}</span>
    </div>
    <div style="border-top:1px solid var(--border); margin-top:10px; padding-top:10px;">
        <div style="color:var(--text-muted); font-size:12px; margin-bottom:4px;">${t('reason')}:</div>
        <div style="font-weight:600; color:var(--text);">${r.reason}</div>
    </div>
  `;
  
  openModal('leaveModal');
  updateUITranslations();

  // Fetch Balance and Policy
  const balYear = (r.start_date && String(r.start_date).length >= 4) ? parseInt(String(r.start_date).slice(0, 4), 10) : new Date().getFullYear();
  api('GET', `leave/balance/${r.employee_id}`, { year: balYear }).then(res => {
    if (res.success) {
        const d = res.data || {};
        const balances = d.balances || {};
        const policy = d.policy || { weekend_days: [] };
        const policyMissing = !!d.policy_missing;
        const weekendDays = policy.weekend_days || [];
        
        // Calculate Net Days
        let netDays = 0;
        let curr = new Date(r.start_date);
        const last = new Date(r.end_date);
        while (curr <= last) {
            const dayName = curr.toLocaleDateString('en-US', { weekday: 'long' });
            if (!weekendDays.includes(dayName)) netDays++;
            curr.setDate(curr.getDate() + 1);
        }
        
        const currentBalance = balances[r.leave_type] || { total: 0, used: 0, rem: 0 };
        const exceeds = netDays > currentBalance.rem;
        
        // Update Details with Net Days
        const detailsDiv = document.getElementById('leaveDetails');
        detailsDiv.innerHTML += `
            <div style="display:flex; justify-content:space-between; margin-top:12px; padding:10px; background:var(--surface2); border-radius:8px; border:1px solid ${exceeds ? 'var(--danger)' : 'var(--border)'};">
                <span style="font-weight:700; font-size:13px;">${t('net_days')}:</span>
                <span style="font-weight:800; color:${exceeds ? 'var(--danger)' : 'var(--accent)'}; font-size:15px;">
                    ${netDays} ${exceeds ? t('exceeds_balance') : ''}
                </span>
            </div>
        `;
        
        // Render Balance Table
        const policyNote = policyMissing
          ? `<div style="font-size:12px;color:var(--warning, #d97706);margin-bottom:10px;line-height:1.45;padding:8px;background:rgba(217,119,6,0.08);border-radius:8px;border:1px solid rgba(217,119,6,0.25);">${t('leave_policy_missing_hint')}</div>`
          : '';
        document.getElementById('leaveBalanceSummary').innerHTML = policyNote + `
            <table style="width:100%; font-size:12px; border-collapse:collapse;">
                <thead>
                    <tr style="border-bottom:1px solid var(--border); color:var(--text-muted);">
                        <th style="text-align:right; padding:6px 0;">${t('type')}</th>
                        <th style="text-align:center;">${t('total')}</th>
                        <th style="text-align:center;">${t('used')}</th>
                        <th style="text-align:center;">${t('rem')}</th>
                    </tr>
                </thead>
                <tbody>
                    ${Object.entries(balances).map(([type, b]) => `
                        <tr style="border-bottom:1px solid var(--border); ${type === r.leave_type ? 'background:rgba(var(--accent-rgb), 0.1); font-weight:700;' : ''}">
                            <td style="padding:8px 0; color:var(--text);">${t(type)}</td>
                            <td style="text-align:center;">${b.total}</td>
                            <td style="text-align:center; color:var(--danger);">${b.used}</td>
                            <td style="text-align:center; color:var(--success); font-weight:800;">${b.rem}</td>
                        </tr>
                    `).join('')}
                </tbody>
            </table>
        `;
    } else {
        document.getElementById('leaveBalanceSummary').innerHTML = `<div style="color:var(--danger); font-size:12px;">${res.message}</div>`;
    }
  });
}

async function reviewLeave(status) {
  const id = document.getElementById('reviewLeaveId').value;
  const note = document.getElementById('adminNote').value;
  const res = await api('PUT', `leave/${id}`, {status, admin_note: note});
  if (res.success) {
    showToast(status === 'approved' ? t('leave_toast_approved') : t('leave_toast_rejected'), 'success');
    _leaveReviewDecision = status;
    const waBtn = document.getElementById('leaveWaBtn');
    if (waBtn) waBtn.style.display = '';
    await loadLeave();
    return;
  }
  showToast(res.message || t('unknown_error') || 'Error', 'error');
}

function sendLeaveDecisionWhatsapp() {
  const r = _leaveReviewCurrent;
  const st = _leaveReviewDecision;
  if (!r || !st) return showToast(t('unknown_error') || 'Error', 'error');
  const wa = adminContactsWaNumber(r.phone || '');
  if (!wa) return showToast('رقم الهاتف غير مسجل للموظف', 'error');
  const statusText = st === 'approved' ? 'تمت الموافقة' : 'تم الرفض';
  const isPermission = leaveRowIsPermission(r);
  const typeText = isPermission ? (t('permission') || 'إذن') : (t(r.leave_type || 'annual') || String(r.leave_type || 'إجازة'));
  const periodLine = isPermission
    ? `الوقت: من ${formatTime12Hour(r.permission_from || '')} إلى ${formatTime12Hour(r.permission_to || '')}`
    : `التاريخ: من ${String(r.start_date || '')} إلى ${String(r.end_date || '')}`;
  const adminReason = String(document.getElementById('adminNote')?.value || '').trim();
  const reqReason = String(r.reason || '').trim();
  const reasonLine = adminReason ? `السبب: ${adminReason}` : (reqReason ? `السبب: ${reqReason}` : 'السبب: غير مذكور');
  const msg = `مرحباً ${String(r.name || '').trim() || 'زميلنا'}\n\nتمت مراجعة طلب ${typeText}.\nالقرار: ${statusText}\n${periodLine}\n${reasonLine}`;
  const waUrl = `https://wa.me/${wa}?text=${encodeURIComponent(msg)}`;
  window.open(waUrl, '_blank', 'noopener');
}

async function deleteLeaveRequest(id) {
  if (!id) return;
  if (!confirm(t('confirm_delete_record') || 'Are you sure?')) return;
  const res = await api('DELETE', `leave/${id}`);
  if (res.success) {
    showToast(res.message || t('record_deleted'), 'success');
    await loadLeave();
    return;
  }
  showToast(res.message || t('unknown_error') || 'Error', 'error');
}

function openEditLeave(r) {
  _editLeaveCurrent = r || null;
  document.getElementById('editLeaveId').value = r.id;
  document.getElementById('editLeaveType').value = leaveRowIsPermission(r) ? 'permission' : (r.leave_type || 'annual');
  document.getElementById('editLeaveStart').value = r.start_date;
  document.getElementById('editLeaveEnd').value = r.end_date;
  document.getElementById('editLeavePermFrom').value = toAdminHHMM(r.permission_from || '');
  document.getElementById('editLeavePermTo').value = toAdminHHMM(r.permission_to || '');
  document.getElementById('editLeaveReason').value = r.reason;
  document.getElementById('editLeaveStatus').value = r.status;
  document.getElementById('editLeaveAdminNote').value = r.admin_note || '';
  toggleEditLeavePermissionFields();
  openModal('editLeaveModal');
}

function toggleEditLeavePermissionFields() {
  const typeEl = document.getElementById('editLeaveType');
  const wrap = document.getElementById('editLeavePermFields');
  const endEl = document.getElementById('editLeaveEnd');
  const startEl = document.getElementById('editLeaveStart');
  if (!typeEl || !wrap) return;
  const isPermission = typeEl.value === 'permission';
  wrap.style.display = isPermission ? 'grid' : 'none';
  if (isPermission && startEl && endEl) {
    endEl.value = startEl.value;
  }
}

function sendEditLeaveWhatsapp() {
  const r = _editLeaveCurrent;
  if (!r) return showToast(t('unknown_error') || 'Error', 'error');
  const wa = adminContactsWaNumber(r.phone || '');
  if (!wa) return showToast('رقم الهاتف غير مسجل للموظف', 'error');

  const leaveType = String(document.getElementById('editLeaveType')?.value || r.leave_type || '').trim();
  const status = String(document.getElementById('editLeaveStatus')?.value || r.status || '').trim().toLowerCase();
  const startDate = String(document.getElementById('editLeaveStart')?.value || r.start_date || '').trim();
  const endDate = String(document.getElementById('editLeaveEnd')?.value || r.end_date || '').trim();
  const permFrom = toAdminHHMM(document.getElementById('editLeavePermFrom')?.value || r.permission_from || '');
  const permTo = toAdminHHMM(document.getElementById('editLeavePermTo')?.value || r.permission_to || '');
  const adminReason = String(document.getElementById('editLeaveAdminNote')?.value || '').trim();
  const reqReason = String(document.getElementById('editLeaveReason')?.value || r.reason || '').trim();
  const reasonLine = adminReason ? `السبب: ${adminReason}` : (reqReason ? `السبب: ${reqReason}` : 'السبب: غير مذكور');

  const isPermission = leaveType === 'permission';
  const typeText = isPermission ? (t('permission') || 'إذن') : (t(leaveType || 'annual') || (leaveType || 'إجازة'));
  const statusText = status === 'approved'
    ? 'تمت الموافقة'
    : (status === 'rejected' ? 'تم الرفض' : 'قيد المراجعة');
  const periodLine = isPermission
    ? `الوقت: من ${formatTime12Hour(permFrom)} إلى ${formatTime12Hour(permTo)}`
    : `التاريخ: من ${startDate} إلى ${endDate}`;

  const msg = `مرحباً ${String(r.name || '').trim() || 'زميلنا'}\n\nتم تحديث حالة طلب ${typeText}.\nالقرار: ${statusText}\n${periodLine}\n${reasonLine}`;
  const waUrl = `https://wa.me/${wa}?text=${encodeURIComponent(msg)}`;
  window.open(waUrl, '_blank', 'noopener');
}

async function saveEditLeave() {
  const id = document.getElementById('editLeaveId').value;
  const leaveType = document.getElementById('editLeaveType').value;
  const startDate = document.getElementById('editLeaveStart').value;
  const endDate = document.getElementById('editLeaveEnd').value;
  const body = {
    leave_type: leaveType,
    start_date: startDate,
    end_date: endDate,
    reason: document.getElementById('editLeaveReason').value,
    status: document.getElementById('editLeaveStatus').value,
    admin_note: document.getElementById('editLeaveAdminNote').value
  };
  if (leaveType === 'permission') {
    const pf = toAdminHHMM(document.getElementById('editLeavePermFrom').value);
    const pt = toAdminHHMM(document.getElementById('editLeavePermTo').value);
    if (!startDate) return showToast(t('date_required') || 'Date is required', 'error');
    if (!pf || !pt) return showToast(t('permission_time_invalid') || 'Invalid permission time range', 'error');
    const t1 = Date.parse(`${startDate}T${pf}:00`);
    const t2 = Date.parse(`${startDate}T${pt}:00`);
    if (!(t2 > t1)) return showToast(t('permission_time_invalid') || 'Invalid permission time range', 'error');
    body.start_date = startDate;
    body.end_date = startDate;
    body.permission_from = pf;
    body.permission_to = pt;
  } else {
    body.permission_from = null;
    body.permission_to = null;
  }
  
  const res = await api('PUT', `leave/${id}`, body);
  if (res.success) {
    closeModal('editLeaveModal');
    showToast(res.message || 'Leave request updated', 'success');
    loadLeave();
  } else {
    showToast(res.message, 'error');
  }
}

let _qrCodeLoadPromise = null;
function ensureQrCodeLib() {
  if (typeof window.QRCode === 'function') return Promise.resolve();
  if (_qrCodeLoadPromise) return _qrCodeLoadPromise;
  _qrCodeLoadPromise = new Promise((resolve, reject) => {
    const sc = document.createElement('script');
    sc.src = 'https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js';
    sc.async = true;
    sc.onload = () => resolve();
    sc.onerror = () => {
      _qrCodeLoadPromise = null;
      reject(new Error('QRCode script failed'));
    };
    document.head.appendChild(sc);
  });
  return _qrCodeLoadPromise;
}

// ── QR ────────────────────────────────────────────────────────────
async function generateQR() {
  clearInterval(qrCountdown);
  try {
    await ensureQrCodeLib();
  } catch (e) {
    return showToast(t('unknown_error') || 'Could not load QR library', 'error');
  }
  if (typeof window.QRCode !== 'function') {
    return showToast(t('unknown_error') || 'QR library unavailable', 'error');
  }
  const res = await api('POST', 'qr/generate');
  if (!res.success) return showToast(res.message, 'error');

  const canvas = document.getElementById('qrCanvas');
  canvas.innerHTML = '';
  new QRCode(canvas, {
    text: JSON.stringify({token: res.data.token, company_id: companyId}),
    width: 200, height: 200,
    colorDark: '#000000', colorLight: '#ffffff'
  });

  let remaining = res.data.lifetime_seconds;
  document.getElementById('qrStatus').textContent = 'Scan to record attendance';

  qrCountdown = setInterval(() => {
    remaining--;
    const m = Math.floor(remaining / 60);
    const s = remaining % 60;
    document.getElementById('qrTimer').textContent = `${m}:${s.toString().padStart(2,'0')}`;
    if (remaining <= 0) {
      clearInterval(qrCountdown);
      document.getElementById('qrTimer').textContent = 'EXPIRED';
      document.getElementById('qrStatus').textContent = 'Token expired. Generate a new one.';
      canvas.innerHTML = `<div style="width:200px;height:200px;display:flex;align-items:center;justify-content:center;background:#f5f5f5;color:#999;font-size:14px;">Expired</div>`;
    }
  }, 1000);
}

// ── UI ────────────────────────────────────────────────────────────
// ── FINAL REPORT ──────────────────────────────────────────────────
async function ensureFrBulkDeptsLoaded() {
  const sel = document.getElementById('frBulkDept');
  if (!sel || sel.dataset.deptsLoaded === '1') return;
  const res = await api('GET', 'departments');
  if (!res.success || !Array.isArray(res.data)) return;
  sel.dataset.deptsLoaded = '1';
  sel.innerHTML = '';
  const o0 = document.createElement('option');
  o0.value = '';
  o0.setAttribute('data-i18n', 'all_dept');
  o0.textContent = t('all_dept');
  sel.appendChild(o0);
  res.data.forEach(d => {
    const name = d.department_name || d.name || ('#' + d.id);
    sel.add(new Option(name, String(d.id)));
  });
  updateUITranslations();
}

function frUpdateTableDensity() {
  const tab = document.getElementById('tab-final-report');
  const v = document.getElementById('frTableDensity')?.value || 'full';
  if (tab) tab.classList.toggle('fr-density-compact', v === 'compact');
}
window.frUpdateTableDensity = frUpdateTableDensity;

function finalReportStatusLabel(st) {
  if (st === 'present') return t('status_present');
  if (st === 'absent') return t('status_absent');
  if (st === 'leave') return t('status_leave');
  if (st === 'weekend') return t('weekend_days');
  if (st === 'official_holiday') return t('status_official_holiday');
  return st || '—';
}

window._leaveSettlementPreview = null;
window._leaveSettlementEdits = {};

async function loadLeaveSettlementTab() {
  await loadEmployees();
  const fromEl = document.getElementById('leaveSettlementFrom');
  if (fromEl && !fromEl.dataset.inited) {
    fromEl.dataset.inited = '1';
    const m = document.getElementById('leaveSettlementMonth');
    if (m) m.value = 'current';
    setLeaveSettlementDatesByMonth();
  }
}

function setLeaveSettlementDatesByMonth() {
  const mode = document.getElementById('leaveSettlementMonth').value;
  const fromEl = document.getElementById('leaveSettlementFrom');
  const toEl = document.getElementById('leaveSettlementTo');
  if (!fromEl || !toEl) return;
  const now = new Date();
  if (mode === 'current') {
    const from = new Date(now.getFullYear(), now.getMonth(), 1);
    const to = new Date(now.getFullYear(), now.getMonth() + 1, 0);
    fromEl.value = getLocalISO(from);
    toEl.value = getLocalISO(to);
  } else if (mode === 'last') {
    const from = new Date(now.getFullYear(), now.getMonth() - 1, 1);
    const to = new Date(now.getFullYear(), now.getMonth(), 0);
    fromEl.value = getLocalISO(from);
    toEl.value = getLocalISO(to);
  }
  loadLeaveSettlementPreview();
}

function lsFracsFromPreview(data) {
  const p = data.policy || {};
  let f = p.deduct_options;
  if (!Array.isArray(f) || !f.length) f = [1, 0.5, 0.25];
  return f.map((x) => Number(x)).filter((n) => Number.isFinite(n));
}

function leaveSettlementFmtTime(iso) {
  if (!iso) return '—';
  const s = String(iso);
  return s.length >= 16 ? s.substring(11, 16) : '—';
}

async function loadLeaveSettlementPreview() {
  const eid = document.getElementById('leaveSettlementEmployee')?.value;
  const from = document.getElementById('leaveSettlementFrom')?.value;
  const to = document.getElementById('leaveSettlementTo')?.value;
  const tbody = document.getElementById('leaveSettlementTableBody');
  const banner = document.getElementById('leaveSettlementBanner');
  const sumCard = document.getElementById('leaveSettlementSummary');
  if (!tbody) return;
  if (!eid) {
    tbody.innerHTML = `<tr><td colspan="10" style="text-align:center;padding:24px;color:var(--textM);">${t('ls_empty_hint')}</td></tr>`;
    if (banner) { banner.style.display = 'none'; banner.innerHTML = ''; }
    if (sumCard) sumCard.style.display = 'none';
    window._leaveSettlementPreview = null;
    window._leaveSettlementEdits = {};
    return;
  }
  tbody.innerHTML = `<tr><td colspan="10" style="text-align:center;padding:24px;color:var(--textM);">${t('loading')}</td></tr>`;
  const res = await api('GET', 'reports/leave-settlement-preview', { employee_id: eid, from, to });
  if (!res.success) {
    tbody.innerHTML = `<tr><td colspan="10" style="text-align:center;padding:24px;color:var(--danger);">${String(res.message || '').replace(/</g, '')}</td></tr>`;
    if (banner) banner.style.display = 'none';
    if (sumCard) sumCard.style.display = 'none';
    window._leaveSettlementPreview = null;
    window._leaveSettlementEdits = {};
    return;
  }
  window._leaveSettlementPreview = res.data;
  window._leaveSettlementEdits = {};
  (res.data.days || []).forEach((d) => {
    const ed = HRProFinalReport.lsBuildEditForDay(d);
    if (ed) window._leaveSettlementEdits[d.date] = ed;
  });
  if (banner) {
    if (res.data.policy_missing) {
      banner.style.display = 'block';
      banner.style.border = '1px solid rgba(245,158,11,0.45)';
      banner.style.background = 'rgba(245,158,11,0.08)';
      banner.textContent = t('ls_policy_missing');
    } else {
      banner.style.display = 'none';
      banner.innerHTML = '';
    }
  }
  renderLeaveSettlementTable(res.data);
  leaveSettlementUpdateSummary(res.data);
  if (sumCard) sumCard.style.display = 'block';
}

function leaveSettlementUpdateSummary(data) {
  const body = document.getElementById('leaveSettlementSummaryBody');
  if (!body || !data) return;
  const ed = window._leaveSettlementEdits || {};
  const draft = { annual: 0, casual: 0, sick: 0 };
  Object.keys(ed).forEach((ds) => {
    const x = ed[ds];
    const d = x.days === '' || x.days == null ? NaN : Number(x.days);
    if (!Number.isFinite(d) || d <= 0) return;
    if (draft[x.leave_type] != null) draft[x.leave_type] = Math.round((draft[x.leave_type] + d) * 100) / 100;
  });
  const bal = data.balances || {};
  const lines = ['annual', 'casual', 'sick'].map((lt) => {
    const b = bal[lt] || { rem: 0, total: 0 };
    const rem = Number(b.rem);
    const draftLt = draft[lt] || 0;
    const after = Math.round((rem - draftLt) * 100) / 100;
    const typeLabel = t(lt);
    return t('ls_balance_line', { type: typeLabel, rem: String(rem), after: String(after), draft: String(draftLt) });
  });
  body.innerHTML = lines.map((line) => `<div>${line}</div>`).join('');
}

function renderLeaveSettlementTable(data) {
  const tbody = document.getElementById('leaveSettlementTableBody');
  const fracs = lsFracsFromPreview(data);
  const days = data.days || [];
  if (!days.length) {
    tbody.innerHTML = `<tr><td colspan="10" style="text-align:center;padding:24px;color:var(--textM);">${t('no_records')}</td></tr>`;
    return;
  }
  const esc = (x) => String(x ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/"/g, '&quot;');
  tbody.innerHTML = days.map((row) => {
    const dateStr = row.date;
    const edit = window._leaveSettlementEdits[dateStr];
    const sug = Array.isArray(row.suggestions) && row.suggestions.length ? row.suggestions.join(', ') : '—';
    let sysCell = '—';
    if (row.system_settlement) {
      const ss = row.system_settlement;
      const ltKey = ss.leave_type === 'casual' ? 'casual' : ss.leave_type === 'sick' ? 'sick' : 'annual';
      const ltLabel = t(ltKey);
      const br = ss.breakdown || {};
      const mins = (br.late_minutes != null || br.early_leave_minutes != null)
        ? ` [L${br.late_minutes || 0} / E${br.early_leave_minutes || 0}]`
        : '';
      sysCell = `${esc(ltLabel)} ${esc(String(ss.days))} — ${esc(ss.reason_text || ss.reason_code || '')}${esc(mins)}`;
    }
    const schIn = row.sch_in ? String(row.sch_in).substring(0, 5) : '';
    const schOut = row.sch_out ? String(row.sch_out).substring(0, 5) : '';
    const times = `${schIn}→${schOut} / ${leaveSettlementFmtTime(row.check_in)}→${leaveSettlementFmtTime(row.check_out)}`;
    const md = row.manual_deductions || [];
    const exist = md.length ? md.map((m) => `${m.leave_type} ${m.days}`).join(' + ') : '—';

    if (!edit) {
      return `<tr>
        <td data-label="${esc(t('date'))}"><span style="font-family:var(--mono);font-weight:600;">${esc(dateStr)}</span></td>
        <td data-label="${esc(t('status'))}">${esc(finalReportStatusLabel(row.status))}</td>
        <td data-label="${esc(t('ls_col_times'))}" style="font-size:12px;color:var(--textM);">${esc(times)}</td>
        <td data-label="${esc(t('fr_issues_suggestions_col'))}" style="font-size:12px;">${esc(sug)}</td>
        <td data-label="${esc(t('ls_col_system'))}" style="font-size:12px;">${sysCell}</td>
        <td colspan="5" style="color:var(--textM);font-size:12px;">—</td>
      </tr>`;
    }

    const ltSel = ['annual', 'casual', 'sick'].map((lt) =>
      `<option value="${lt}" ${edit.leave_type === lt ? 'selected' : ''}>${esc(t(lt))}</option>`
    ).join('');

    const dayOpts = ['<option value="">—</option>'].concat(fracs.map((f) => {
      const fv = (Math.abs(f - Math.round(f)) < 0.001) ? String(Math.round(f)) : String(f);
      const cur = edit.days === '' || edit.days == null ? NaN : Number(edit.days);
      const sel = Number.isFinite(cur) && Math.abs(cur - f) < 0.001 ? 'selected' : '';
      return `<option value="${fv}" ${sel}>${fv}</option>`;
    })).join('');

    const resetBtn = row.system_settlement
      ? `<button type="button" class="btn btn-sm btn-ghost" onclick="lsResetRow('${esc(dateStr)}')">${esc(t('ls_reset_row'))}</button>`
      : '';

    return `<tr data-ls-date="${esc(dateStr)}">
      <td data-label="${esc(t('date'))}"><span style="font-family:var(--mono);font-weight:600;">${esc(dateStr)}</span></td>
      <td data-label="${esc(t('status'))}">${esc(finalReportStatusLabel(row.status))}</td>
      <td data-label="${esc(t('ls_col_times'))}" style="font-size:12px;color:var(--textM);">${esc(times)}</td>
      <td data-label="${esc(t('fr_issues_suggestions_col'))}" style="font-size:12px;">${esc(sug)}</td>
      <td data-label="${esc(t('ls_col_system'))}" style="font-size:12px;">${sysCell}</td>
      <td data-label="${esc(t('type'))}"><select class="input" style="min-width:100px;padding:8px;" onchange="lsSetEdit('${esc(dateStr)}','leave_type',this.value)">${ltSel}</select></td>
      <td data-label="${esc(t('ls_col_days'))}"><select class="input" style="min-width:88px;padding:8px;" onchange="lsSetEdit('${esc(dateStr)}','days',this.value)">${dayOpts}</select></td>
      <td data-label="${esc(t('notes'))}"><input type="text" class="input" style="padding:8px;" value="${esc(edit.reason || '')}" maxlength="500" oninput="lsSetEdit('${esc(dateStr)}','reason',this.value)"></td>
      <td data-label="${esc(t('ls_existing_manual'))}" style="font-size:12px;">${esc(exist)}</td>
      <td data-label="${esc(t('actions'))}">${resetBtn}</td>
    </tr>`;
  }).join('');
}

function lsSetEdit(dateStr, field, value) {
  const g = window._leaveSettlementEdits[dateStr];
  if (!g) return;
  if (field === 'days') {
    g.days = value === '' ? '' : Number(value);
  } else if (field === 'leave_type') {
    g.leave_type = value;
  } else {
    g.reason = value;
  }
  if (window._leaveSettlementPreview) leaveSettlementUpdateSummary(window._leaveSettlementPreview);
}

function lsResetRow(dateStr) {
  const data = window._leaveSettlementPreview;
  if (!data) return;
  const row = (data.days || []).find((x) => x.date === dateStr);
  if (!row || !row.system_settlement) return;
  window._leaveSettlementEdits[dateStr] = {
    leave_type: row.system_settlement.leave_type,
    days: Number(row.system_settlement.days),
    reason: ''
  };
  renderLeaveSettlementTable(data);
  leaveSettlementUpdateSummary(data);
}

async function openLeaveSettlementFromFinalReport() {
  const eid = document.getElementById('finalReportEmployee')?.value;
  const from = document.getElementById('finalReportFrom')?.value;
  const to = document.getElementById('finalReportTo')?.value;
  if (!eid) {
    showToast(t('select_employee'), 'error');
    return;
  }
  await loadEmployees();
  const lsEmp = document.getElementById('leaveSettlementEmployee');
  if (lsEmp) lsEmp.value = String(eid);
  const lf = document.getElementById('leaveSettlementFrom');
  const lt = document.getElementById('leaveSettlementTo');
  const lm = document.getElementById('leaveSettlementMonth');
  if (lf) lf.value = from;
  if (lt) lt.value = to;
  if (lm) lm.value = 'custom';
  if (lf) lf.dataset.inited = '1';
  const nav = document.querySelector('.sidebar-nav .nav-item[onclick*="leave-settlement"]')
    || document.querySelector('.bottom-nav .bottom-nav-item[onclick*="leave-settlement"]');
  showTab('leave-settlement', nav || null, { skipLoader: true });
  await loadLeaveSettlementPreview();
  window.scrollTo({ top: 0, behavior: 'smooth' });
}

async function applyLeaveSettlement() {
  const data = window._leaveSettlementPreview;
  if (!data) {
    showToast(t('ls_empty_hint'), 'error');
    return;
  }
  const eid = data.employee_id;
  const from = data.from;
  const to = data.to;
  const resChk = await api('GET', 'reports/leave-settlement-preview', { employee_id: eid, from, to });
  if (resChk.success && resChk.data && resChk.data.preview_fingerprint !== data.preview_fingerprint) {
    if (!confirm(t('ls_stale_warning'))) return;
  }
  const fracs = lsFracsFromPreview(data);
  const rows = [];
  const clearDates = [];
  const edits = window._leaveSettlementEdits || {};
  let badFrac = false;
  (data.days || []).forEach((day) => {
    const ds = day.date;
    const ed = edits[ds];
    const hadManual = !!(day.manual_deductions && day.manual_deductions.length);
    if (!ed) {
      return;
    }
    const d = ed.days === '' || ed.days == null ? NaN : Number(ed.days);
    if (!Number.isFinite(d) || d <= 0) {
      if (hadManual) clearDates.push(ds);
      return;
    }
    let ok = false;
    for (let i = 0; i < fracs.length; i++) {
      if (Math.abs(d - fracs[i]) < 0.001) {
        ok = true;
        break;
      }
    }
    if (!ok) {
      badFrac = true;
      return;
    }
    rows.push({
      deduction_date: ds,
      leave_type: ed.leave_type,
      days: d,
      reason: ed.reason || ''
    });
  });
  if (badFrac) {
    showToast(t('fill_fields'), 'error');
    return;
  }
  const clearDatesU = [...new Set(clearDates)];
  if (!rows.length && !clearDatesU.length) {
    showToast(t('ls_no_valid_rows'), 'error');
    return;
  }
  const draft = { annual: 0, casual: 0, sick: 0 };
  rows.forEach((r) => {
    if (draft[r.leave_type] != null) draft[r.leave_type] = Math.round((draft[r.leave_type] + r.days) * 100) / 100;
  });
  const msg = t('ls_confirm_apply', {
    rows: String(rows.length),
    a: String(draft.annual),
    c: String(draft.casual),
    s: String(draft.sick)
  });
  if (!confirm(msg)) return;
  const res = await api('POST', 'reports/leave-settlement-apply', {
    employee_id: eid,
    from,
    to,
    rows,
    clear_dates: clearDatesU
  });
  if (res.success) {
    showToast(res.message || t('btn_save'), 'success');
    await loadLeaveSettlementPreview();
  } else {
    showToast(res.message || 'Error', 'error');
  }
}

function frSettleSet(dateStr, field, value) {
  const g = window._frSettlementEdits[dateStr];
  if (!g) return;
  if (field === 'days') {
    g.days = value === '' ? '' : Number(value);
  } else if (field === 'leave_type') {
    g.leave_type = value;
  } else {
    g.reason = value;
  }
  if (field === 'reason') {
    clearTimeout(window._frSettleReasonDebounce);
    window._frSettleReasonDebounce = setTimeout(() => {
      if (window._lastFinalReportRaw) renderFinalReport(window._lastFinalReportRaw);
    }, 400);
    return;
  }
  if (window._lastFinalReportRaw) renderFinalReport(window._lastFinalReportRaw);
}

function frResetSettlementRow(dateStr) {
  const data = window._lastFinalReportRaw;
  if (!data || !data.report) return;
  const row = data.report.find((x) => x.date === dateStr);
  if (!row || !row.system_settlement) return;
  window._frSettlementEdits[dateStr] = {
    leave_type: row.system_settlement.leave_type,
    days: Number(row.system_settlement.days),
    reason: ''
  };
  renderFinalReport(data);
}

async function applyFinalReportLeaveSettlement() {
  const raw = window._lastFinalReportRaw;
  if (!raw || !raw.report) {
    showToast(t('fr_need_report'), 'error');
    return;
  }
  const eid = document.getElementById('finalReportEmployee')?.value;
  const from = document.getElementById('finalReportFrom')?.value;
  const to = document.getElementById('finalReportTo')?.value;
  if (!eid || !from || !to) {
    showToast(t('fill_fields'), 'error');
    return;
  }
  const resChk = await api('GET', 'reports/final', { employee_id: eid, from, to });
  if (resChk.success && resChk.data && raw.settlement_fingerprint && resChk.data.settlement_fingerprint !== raw.settlement_fingerprint) {
    if (!confirm(t('ls_stale_warning'))) return;
  }
  const fracs = lsFracsFromPreview({ policy: raw.policy || {} });
  const rows = [];
  const clearDates = [];
  const edits = window._frSettlementEdits || {};
  let badFrac = false;
  (raw.report || []).forEach((day) => {
    const ds = day.date;
    const ed = edits[ds];
    const hadManual = !!(day.manual_deductions && day.manual_deductions.length);
    if (!ed) return;
    const d = ed.days === '' || ed.days == null ? NaN : Number(ed.days);
    if (!Number.isFinite(d) || d <= 0) {
      if (hadManual) clearDates.push(ds);
      return;
    }
    let ok = false;
    for (let i = 0; i < fracs.length; i++) {
      if (Math.abs(d - fracs[i]) < 0.001) {
        ok = true;
        break;
      }
    }
    if (!ok) {
      badFrac = true;
      return;
    }
    rows.push({
      deduction_date: ds,
      leave_type: ed.leave_type,
      days: d,
      reason: ed.reason || ''
    });
  });
  if (badFrac) {
    showToast(t('fill_fields'), 'error');
    return;
  }
  const clearDatesU = [...new Set(clearDates)];
  if (!rows.length && !clearDatesU.length) {
    showToast(t('ls_no_valid_rows'), 'error');
    return;
  }
  const draft = { annual: 0, casual: 0, sick: 0 };
  rows.forEach((r) => {
    if (draft[r.leave_type] != null) draft[r.leave_type] = Math.round((draft[r.leave_type] + r.days) * 100) / 100;
  });
  const msg = t('ls_confirm_apply', {
    rows: String(rows.length),
    a: String(draft.annual),
    c: String(draft.casual),
    s: String(draft.sick)
  });
  if (!confirm(msg)) return;
  const res = await api('POST', 'reports/leave-settlement-apply', {
    employee_id: parseInt(String(eid), 10),
    from,
    to,
    rows,
    clear_dates: clearDatesU
  });
  if (res.success) {
    showToast(res.message || t('btn_save'), 'success');
    await loadFinalReport();
  } else {
    showToast(res.message || 'Error', 'error');
  }
}

async function applyFrLateDeductionsThisEmployee() {
  const eid = document.getElementById('finalReportEmployee')?.value;
  const from = document.getElementById('finalReportFrom')?.value;
  const to = document.getElementById('finalReportTo')?.value;
  if (!eid || !from || !to) {
    showToast(t('fr_need_report'), 'error');
    return;
  }
  if (!confirm(t('fr_late_bulk_confirm'))) return;
  const res = await api('POST', 'reports/leave-deduct-late-bulk', {
    employee_id: parseInt(String(eid), 10),
    from,
    to
  });
  if (res.success && res.data) {
    const d = res.data;
    showToast(t('fr_late_bulk_done', {
      a: String(d.applied ?? 0),
      n: String(d.skipped_no_preview ?? 0),
      m: String(d.skipped_has_manual ?? 0)
    }), 'success');
    await loadFinalReport();
  } else {
    showToast(res.message || 'Error', 'error');
  }
}

async function applyFinalReportLateDeductionDay(dateStr) {
  const eid = document.getElementById('finalReportEmployee')?.value;
  if (!eid || !dateStr) {
    showToast(t('fr_need_report'), 'error');
    return;
  }
  const res = await api('POST', 'reports/leave-deduct-late-one', {
    employee_id: parseInt(String(eid), 10),
    deduction_date: dateStr
  });
  if (res.success) {
    showToast(res.message || t('btn_save'), 'success');
    await loadFinalReport();
  } else {
    showToast(res.message || 'Error', 'error');
  }
}

window._deductionsReportLast = null;

async function loadDeductionsReportTab() {
  await loadEmployees();
  const fromEl = document.getElementById('deductionsReportFrom');
  if (fromEl && !fromEl.dataset.inited) {
    fromEl.dataset.inited = '1';
    const m = document.getElementById('deductionsReportMonth');
    if (m) m.value = 'current';
    setDeductionsReportDatesByMonth();
  }
}

function setDeductionsReportDatesByMonth() {
  const mode = document.getElementById('deductionsReportMonth').value;
  const fromEl = document.getElementById('deductionsReportFrom');
  const toEl = document.getElementById('deductionsReportTo');
  if (!fromEl || !toEl) return;
  const now = new Date();
  if (mode === 'current') {
    const from = new Date(now.getFullYear(), now.getMonth(), 1);
    const to = new Date(now.getFullYear(), now.getMonth() + 1, 0);
    fromEl.value = getLocalISO(from);
    toEl.value = getLocalISO(to);
  } else if (mode === 'last') {
    const from = new Date(now.getFullYear(), now.getMonth() - 1, 1);
    const to = new Date(now.getFullYear(), now.getMonth(), 0);
    fromEl.value = getLocalISO(from);
    toEl.value = getLocalISO(to);
  }
  loadDeductionsReportSummary();
}

async function loadDeductionsReportSummary() {
  const eid = document.getElementById('deductionsReportEmployee')?.value;
  const from = document.getElementById('deductionsReportFrom')?.value;
  const to = document.getElementById('deductionsReportTo')?.value;
  const tbody = document.getElementById('deductionsReportTableBody');
  const meta = document.getElementById('deductionsReportMeta');
  const tot = document.getElementById('deductionsReportTotals');
  if (!tbody) return;
  if (!eid) {
    tbody.innerHTML = `<tr><td colspan="6" style="text-align:center;padding:24px;color:var(--textM);">${t('dr_empty')}</td></tr>`;
    if (meta) meta.style.display = 'none';
    if (tot) tot.style.display = 'none';
    window._deductionsReportLast = null;
    return;
  }
  tbody.innerHTML = `<tr><td colspan="6" style="text-align:center;padding:24px;color:var(--textM);">${t('loading')}</td></tr>`;
  const res = await api('GET', 'reports/employee-deductions-summary', { employee_id: eid, from, to });
  if (!res.success) {
    tbody.innerHTML = `<tr><td colspan="6" style="text-align:center;padding:24px;color:var(--danger);">${String(res.message || '').replace(/</g, '')}</td></tr>`;
    if (meta) meta.style.display = 'none';
    if (tot) tot.style.display = 'none';
    return;
  }
  window._deductionsReportLast = res.data;
  const emp = res.data.employee || {};
  if (meta) {
    meta.style.display = 'block';
    const mb = document.getElementById('deductionsReportMetaBody');
    if (mb) {
      const esc = (s) => String(s ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/"/g, '&quot;');
      mb.innerHTML = `
        <div><strong>${esc(t('employee'))}:</strong> ${esc(emp.name)} <span style="font-family:var(--mono);color:var(--textM);">(${esc(emp.employee_code)})</span></div>
        <div><strong>${esc(t('department'))}:</strong> ${esc(emp.department) || '—'} &nbsp;·&nbsp; <strong>${esc(t('branch'))}:</strong> ${esc(emp.branch) || '—'}</div>
        <div><strong>${esc(t('job_title'))}:</strong> ${esc(emp.job_title) || '—'} &nbsp;·&nbsp; <strong>${esc(t('date'))}:</strong> ${esc(res.data.from)} → ${esc(res.data.to)}</div>`;
    }
  }
  const totals = res.data.totals || {};
  const lt = totals.leave_by_type || {};
  if (tot) {
    tot.style.display = 'block';
    const tb = document.getElementById('deductionsReportTotalsBody');
    if (tb) {
      tb.innerHTML = `
        <div>${t('annual')}: ${lt.annual ?? 0} &nbsp;|&nbsp; ${t('casual')}: ${lt.casual ?? 0} &nbsp;|&nbsp; ${t('sick')}: ${lt.sick ?? 0}</div>
        <div>${t('dr_leave_sum')}: <strong>${totals.leave_days_sum ?? 0}</strong> — ${t('dr_salary_sum')}: <strong>${totals.salary_days_sum ?? 0}</strong></div>`;
    }
  }
  const rows = res.data.rows_merged || [];
  if (!rows.length) {
    tbody.innerHTML = `<tr><td colspan="6" style="text-align:center;padding:24px;color:var(--textM);">${t('dr_no_rows')}</td></tr>`;
    return;
  }
  const esc = (x) => String(x ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/"/g, '&quot;');
  tbody.innerHTML = rows.map((r) => {
    const isLeave = r.kind === 'leave_balance';
    const kindLabel = isLeave ? t('dr_type_leave') : t('dr_type_salary');
    const detail = isLeave ? (t(r.leave_type === 'annual' ? 'annual' : r.leave_type === 'casual' ? 'casual' : 'sick')) : t('fr_salary_short');
    const reasonRaw = r.reason || '';
    const reason = esc(reasonRaw === 'bulk_settlement' ? t('fr_reason_bulk') : (reasonRaw || '—'));
    const daysStr = String(r.days != null ? r.days : '');
    const forceTag = !isLeave && r.force_despite_balance ? ` <span style="font-size:11px;color:var(--warning);">${esc(t('fr_salary_force_tag'))}</span>` : '';
    const btn = isLeave
      ? `<button type="button" class="btn btn-sm btn-outline-danger" onclick="revertDeductionLeaveBalance(${r.id})">${esc(t('dr_revert'))}</button>`
      : `<button type="button" class="btn btn-sm btn-outline-danger" onclick="revertDeductionSalary(${r.id})">${esc(t('dr_revert'))}</button>`;
    return `<tr data-dr-kind="${isLeave ? 'leave' : 'salary'}" data-dr-id="${r.id}">
      <td data-label="${esc(t('dr_col_kind'))}">${esc(kindLabel)}</td>
      <td data-label="${esc(t('date'))}" style="font-family:var(--mono);font-weight:600;">${esc(r.deduction_date)}</td>
      <td data-label="${esc(t('dr_col_detail'))}">${esc(detail)}${forceTag}</td>
      <td data-label="${esc(t('dr_col_days'))}">${esc(daysStr)}</td>
      <td data-label="${esc(t('notes'))}" style="font-size:13px;max-width:240px;">${reason}</td>
      <td data-label="${esc(t('actions'))}">${btn}</td>
    </tr>`;
  }).join('');
}

async function revertDeductionLeaveBalance(id) {
  if (!confirm(t('dr_revert_confirm'))) return;
  const res = await api('DELETE', 'reports/leave-deduct/' + id, {});
  if (res.success) {
    showToast(res.message || t('record_deleted'), 'success');
    await loadDeductionsReportSummary();
  } else {
    showToast(res.message || 'Error', 'error');
  }
}

async function revertDeductionSalary(id) {
  if (!confirm(t('dr_revert_confirm'))) return;
  const res = await api('DELETE', 'reports/salary-deduct/' + id, {});
  if (res.success) {
    showToast(res.message || t('record_deleted'), 'success');
    await loadDeductionsReportSummary();
  } else {
    showToast(res.message || 'Error', 'error');
  }
}

function setFinalReportDatesByMonth() {
  const mode = document.getElementById('finalReportMonth').value;
  const fromEl = document.getElementById('finalReportFrom');
  const toEl = document.getElementById('finalReportTo');
  const now = new Date();

  if (mode === 'current') {
    const from = new Date(now.getFullYear(), now.getMonth(), 1);
    const to = new Date(now.getFullYear(), now.getMonth() + 1, 0);
    fromEl.value = getLocalISO(from);
    toEl.value = getLocalISO(to);
  } else if (mode === 'last') {
    const from = new Date(now.getFullYear(), now.getMonth() - 1, 1);
    const to = new Date(now.getFullYear(), now.getMonth(), 0);
    fromEl.value = getLocalISO(from);
    toEl.value = getLocalISO(to);
  }
  loadFinalReport();
}

async function loadFinalReport() {
  void ensureFrBulkDeptsLoaded();
  const eid = document.getElementById('finalReportEmployee').value;
  const from = document.getElementById('finalReportFrom').value;
  const to = document.getElementById('finalReportTo').value;

  if (!eid) {
    const stripEl = document.getElementById('frScreenContextStrip');
    if (stripEl) stripEl.innerHTML = '';
    return;
  }

  const tbody = document.getElementById('finalReportTable');
  tbody.innerHTML = `<tr><td colspan="14" style="text-align:center;padding:30px;color:var(--text-muted);">${t('loading')}</td></tr>`;

  const res = await api('GET', 'reports/final', { employee_id: eid, from: from, to: to });
  if (res.success) {
    window._lastFinalReportRaw = res.data;
    renderFinalReport(res.data);
  } else {
    tbody.innerHTML = `<tr><td colspan="14" style="text-align:center;padding:30px;color:var(--danger);">${res.message || 'Error'}</td></tr>`;
  }
}

function renderFinalReport(data) {
  window.hrproFinalReportT = t;
  HRProFinalReport.render(data, { mode: 'admin', omitSettlementColumn: false });
}

function openFinalReportDeductModal(dateStr) {
  const eid = document.getElementById('finalReportEmployee').value;
  if (!eid) {
    showToast(t('select_employee'), 'error');
    return;
  }
  document.getElementById('frDeductEmpId').value = eid;
  document.getElementById('frDeductDate').value = dateStr;
  document.getElementById('frDeductDateLabel').textContent = dateStr;
  document.getElementById('frDeductReason').value = '';
  const fr = window._frDeductFractions || [1, 0.5, 0.25];
  const daySel = document.getElementById('frDeductDays');
  if (!daySel) return;
  daySel.innerHTML = fr.map(f => {
    const v = String(f);
    let lab = v;
    if (Math.abs(f - 1) < 0.001) lab = t('fr_deduct_opt_full');
    else if (Math.abs(f - 0.5) < 0.001) lab = t('fr_deduct_opt_half');
    else if (Math.abs(f - 0.25) < 0.001) lab = t('fr_deduct_opt_quarter');
    return `<option value="${v}">${lab}</option>`;
  }).join('');
  openModal('finalReportDeductModal');
  updateUITranslations();
}

async function submitFinalReportDeduct() {
  const employee_id = document.getElementById('frDeductEmpId').value;
  const deduction_date = document.getElementById('frDeductDate').value;
  const leave_type = document.getElementById('frDeductLeaveType').value;
  const days = parseFloat(String(document.getElementById('frDeductDays').value), 10);
  const reason = document.getElementById('frDeductReason').value.trim();
  const res = await api('POST', 'reports/leave-deduct', { employee_id, deduction_date, leave_type, days, reason });
  if (res.success) {
    closeModal('finalReportDeductModal');
    showToast(res.message || t('record_deleted'), 'success');
    loadFinalReport();
  } else {
    showToast(res.message || 'Error', 'error');
  }
}

async function deleteFinalReportDeduct(id) {
  if (!confirm(t('dr_revert_confirm'))) return;
  const res = await api('DELETE', 'reports/leave-deduct/' + id, {});
  if (res.success) {
    showToast(res.message || t('record_deleted'), 'success');
    loadFinalReport();
  } else {
    showToast(res.message || 'Error', 'error');
  }
}

function openFinalReportSalaryModal(dateStr) {
  const eid = document.getElementById('finalReportEmployee').value;
  if (!eid) {
    showToast(t('select_employee'), 'error');
    return;
  }
  document.getElementById('frSalaryEmpId').value = eid;
  document.getElementById('frSalaryDate').value = dateStr;
  const dl = document.getElementById('frSalaryDateLabel');
  if (dl) dl.textContent = dateStr;
  const reasonEl = document.getElementById('frSalaryReason');
  if (reasonEl) reasonEl.value = '';
  const fr = window._frDeductFractions || [1, 0.5, 0.25];
  const daySel = document.getElementById('frSalaryDays');
  if (!daySel) return;
  daySel.innerHTML = fr.map(f => {
    const v = String(f);
    let lab = v;
    if (Math.abs(f - 1) < 0.001) lab = t('fr_deduct_opt_full');
    else if (Math.abs(f - 0.5) < 0.001) lab = t('fr_deduct_opt_half');
    else if (Math.abs(f - 0.25) < 0.001) lab = t('fr_deduct_opt_quarter');
    return `<option value="${v}">${lab}</option>`;
  }).join('');
  const pol = window._frPolicySalary || { allow_with_balance: false, balance_exhausted: true };
  const forceWrap = document.getElementById('frSalaryForceWrap');
  const forceCb = document.getElementById('frSalaryForce');
  const needForce = pol.allow_with_balance && !pol.balance_exhausted;
  if (forceWrap) forceWrap.style.display = needForce ? '' : 'none';
  if (forceCb) forceCb.checked = false;
  openModal('finalReportSalaryModal');
  updateUITranslations();
}

async function submitFinalReportSalary() {
  const employee_id = document.getElementById('frSalaryEmpId').value;
  const deduction_date = document.getElementById('frSalaryDate').value;
  const days = parseFloat(String(document.getElementById('frSalaryDays').value), 10);
  const reason = document.getElementById('frSalaryReason').value.trim();
  const pol = window._frPolicySalary || { allow_with_balance: false, balance_exhausted: true };
  const needForce = pol.allow_with_balance && !pol.balance_exhausted;
  const force_despite_balance = needForce && document.getElementById('frSalaryForce')?.checked ? 1 : 0;
  const res = await api('POST', 'reports/salary-deduct', { employee_id, deduction_date, days, reason, force_despite_balance });
  if (res.success) {
    closeModal('finalReportSalaryModal');
    showToast(res.message || t('record_deleted'), 'success');
    loadFinalReport();
  } else {
    showToast(res.message || 'Error', 'error');
  }
}

async function deleteFinalReportSalary(id) {
  if (!confirm(t('dr_revert_confirm'))) return;
  const res = await api('DELETE', 'reports/salary-deduct/' + id, {});
  if (res.success) {
    showToast(res.message || t('record_deleted'), 'success');
    loadFinalReport();
  } else {
    showToast(res.message || 'Error', 'error');
  }
}

let _lovSearchDebounce = null;
function debounceLeaveOverviewSearch() {
  clearTimeout(_lovSearchDebounce);
  _lovSearchDebounce = setTimeout(() => { loadLeaveOverview(); }, 400);
}

async function ensureLeaveOverviewFilters() {
  if (window._lovFiltersBuilt) return;
  window._lovFiltersBuilt = true;
  const escOpt = (s) => String(s || '').replace(/</g, '').replace(/&/g, '');
  const [br, de, po] = await Promise.all([
    api('GET', 'branches'),
    api('GET', 'departments'),
    api('GET', 'leave-policies')
  ]);
  const bSel = document.getElementById('leaveOverviewBranch');
  if (bSel) {
    bSel.innerHTML = `<option value="all">${t('all_branches')}</option><option value="none">${t('lov_no_branch')}</option>` +
      (br.success && Array.isArray(br.data) ? br.data.map(b => `<option value="${b.id}">${escOpt(b.name)}</option>`).join('') : '');
  }
  const dSel = document.getElementById('leaveOverviewDept');
  if (dSel) {
    dSel.innerHTML = `<option value="all">${t('all_depts')}</option><option value="none">${t('lov_no_dept')}</option>` +
      (de.success && Array.isArray(de.data) ? de.data.map(d => `<option value="${d.id}">${escOpt(d.department_name)}</option>`).join('') : '');
  }
  const pSel = document.getElementById('leaveOverviewPolicy');
  if (pSel) {
    pSel.innerHTML = `<option value="all">${t('lov_all_policies')}</option><option value="none">${t('lov_no_policy')}</option>` +
      (po.success && Array.isArray(po.data) ? po.data.map(p => `<option value="${p.id}">${escOpt(p.policy_name)}</option>`).join('') : '');
  }
  updateUITranslations();
}

function clearLeaveOverviewFilters() {
  const s = document.getElementById('leaveOverviewSearch');
  if (s) s.value = '';
  const b = document.getElementById('leaveOverviewBranch'); if (b) b.value = 'all';
  const d = document.getElementById('leaveOverviewDept'); if (d) d.value = 'all';
  const p = document.getElementById('leaveOverviewPolicy'); if (p) p.value = 'all';
  const n = document.getElementById('leaveOverviewNature'); if (n) n.value = 'all';
  const st = document.getElementById('leaveOverviewStatus'); if (st) st.value = 'active';
  const pp = document.getElementById('leaveOverviewPolicyPresence'); if (pp) pp.value = 'all';
  const so = document.getElementById('leaveOverviewSort'); if (so) so.value = 'name_asc';
  loadLeaveOverview();
}

function leaveOverviewSelectedLabel(selectId) {
  const el = document.getElementById(selectId);
  if (!el || el.selectedIndex < 0) return '';
  const opt = el.options[el.selectedIndex];
  return opt && opt.textContent ? opt.textContent.trim() : '';
}

function printLeaveOverview() {
  const meta = document.getElementById('leaveOverviewPrintMeta');
  if (meta) {
    const cname = localStorage.getItem('admin_company_name') || '';
    const sel = document.getElementById('leaveOverviewYear');
    const y = sel ? sel.value : '';
    const lines = [
      `${t('lov_print_company')}: ${cname}`,
      `${t('lov_print_year')}: ${y}`,
      `${t('branch')}: ${leaveOverviewSelectedLabel('leaveOverviewBranch')} · ${t('department')}: ${leaveOverviewSelectedLabel('leaveOverviewDept')} · ${t('emp_leave_policy_label')}: ${leaveOverviewSelectedLabel('leaveOverviewPolicy')}`,
      `${t('work_nature')}: ${leaveOverviewSelectedLabel('leaveOverviewNature')} · ${t('status')}: ${leaveOverviewSelectedLabel('leaveOverviewStatus')} · ${t('lov_policy_presence')}: ${leaveOverviewSelectedLabel('leaveOverviewPolicyPresence')} · ${t('lov_sort')}: ${leaveOverviewSelectedLabel('leaveOverviewSort')}`
    ];
    const q = document.getElementById('leaveOverviewSearch');
    if (q && q.value.trim()) lines.push(`${t('lov_search')}: ${q.value.trim()}`);
    meta.textContent = lines.join('\n');
  }
  const tab = document.getElementById('tab-leave-overview');
  const prevDisp = tab ? tab.style.display : '';
  if (tab) tab.style.display = 'block';
  document.body.classList.add('hrpro-print-leave-overview');
  const onAfter = () => {
    document.body.classList.remove('hrpro-print-leave-overview');
    if (tab) tab.style.display = prevDisp || '';
    window.removeEventListener('afterprint', onAfter);
  };
  window.addEventListener('afterprint', onAfter);
  requestAnimationFrame(() => {
    try {
      window.print();
    } catch (e) {
      document.body.classList.remove('hrpro-print-leave-overview');
      if (tab) tab.style.display = prevDisp || '';
      window.removeEventListener('afterprint', onAfter);
    }
  });
}

function openLeaveLedgerModal(empId, empName) {
  window._leaveLedgerEmpId = Number(empId);
  const line = document.getElementById('leaveLedgerEmpLine');
  if (line) line.textContent = (empName || '') + ' · ID ' + String(empId);
  const ySel = document.getElementById('leaveOverviewYear');
  const y = ySel && ySel.value ? parseInt(ySel.value, 10) : new Date().getFullYear();
  const fy = document.getElementById('leaveLedgerFromYear');
  const ty = document.getElementById('leaveLedgerToYear');
  if (ty) ty.value = String(y);
  if (fy) fy.value = String(Math.max(1990, y - 3));
  const lt = document.getElementById('leaveLedgerType');
  if (lt) lt.value = 'annual';
  const tb = document.getElementById('leaveLedgerBody');
  if (tb) tb.innerHTML = `<tr><td colspan="5" style="text-align:center;padding:20px;color:var(--text-muted);">${t('ledger_empty_hint')}</td></tr>`;
  openModal('leaveLedgerModal');
  updateUITranslations();
  void loadLeaveLedgerData();
}

function leaveLedgerKindLabel(detail) {
  if (detail === 'year_accrual') return t('ledger_kind_accrual');
  if (detail === 'approved_leave') return t('ledger_kind_leave_day');
  if (detail === 'manual_deduction') return t('ledger_kind_manual');
  return String(detail || '');
}

async function loadLeaveLedgerData() {
  const eid = window._leaveLedgerEmpId;
  const tb = document.getElementById('leaveLedgerBody');
  if (!eid || !tb) return;
  const lt = (document.getElementById('leaveLedgerType')?.value || 'annual').toLowerCase();
  const fy = parseInt(String(document.getElementById('leaveLedgerFromYear')?.value || ''), 10);
  const ty = parseInt(String(document.getElementById('leaveLedgerToYear')?.value || ''), 10);
  if (!Number.isFinite(fy) || !Number.isFinite(ty)) {
    return showToast(t('fill_fields') || 'Invalid years', 'warning');
  }
  const esc = (s) => String(s ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/"/g, '&quot;');
  tb.innerHTML = `<tr><td colspan="5" style="text-align:center;padding:20px;color:var(--text-muted);">${t('loading')}</td></tr>`;
  const res = await api('GET', 'reports/leave-ledger', {
    employee_id: eid,
    leave_type: lt,
    from_year: fy,
    to_year: ty,
  });
  if (!res.success) {
    tb.innerHTML = `<tr><td colspan="5" style="text-align:center;padding:20px;color:var(--danger);">${esc(res.message || 'Error')}</td></tr>`;
    return;
  }
  const evs = res.data.events || [];
  const line = document.getElementById('leaveLedgerEmpLine');
  if (line && res.data.name) {
    line.textContent = `${res.data.name}${res.data.employee_code ? ' · ' + res.data.employee_code : ''}${res.data.policy_name ? ' · ' + res.data.policy_name : ''}`;
  }
  if (!evs.length) {
    tb.innerHTML = `<tr><td colspan="5" style="text-align:center;padding:20px;color:var(--text-muted);">${t('no_records')}</td></tr>`;
    updateUITranslations();
    return;
  }
  tb.innerHTML = evs.map((r) => `
    <tr>
      <td data-label="${t('date')}" style="font-family:var(--mono);">${esc(r.date)}</td>
      <td data-label="${t('ledger_col_kind')}">${esc(leaveLedgerKindLabel(r.detail))}</td>
      <td data-label="${t('ledger_col_delta')}" style="font-family:var(--mono);">${r.delta > 0 ? '+' : ''}${esc(String(r.delta))}</td>
      <td data-label="${t('ledger_col_balance')}" style="font-family:var(--mono);font-weight:600;">${esc(String(r.balance))}</td>
      <td data-label="${t('notes')}">${esc(r.note || '')}</td>
    </tr>
  `).join('');
  updateUITranslations();
}

async function loadLeaveOverview() {
  const esc = (s) => {
    if (s == null || s === '') return '';
    return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/"/g, '&quot;');
  };
  const escAttr = (s) => String(s ?? '')
    .replace(/&/g, '&amp;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;')
    .replace(/</g, '&lt;');
  const sel = document.getElementById('leaveOverviewYear');
  const tbody = document.getElementById('leaveOverviewBody');
  if (!sel || !tbody) return;
  if (!sel.options.length) {
    const y = new Date().getFullYear();
    for (let i = 0; i < 8; i++) {
      const yy = y - i;
      sel.add(new Option(String(yy), String(yy)));
    }
  }
  await ensureLeaveOverviewFilters();

  const year = sel.value;
  const COL = 13;
  tbody.innerHTML = `<tr><td colspan="${COL}" style="text-align:center;padding:24px;color:var(--text-muted);">${t('loading')}</td></tr>`;

  const body = {
    year: parseInt(year, 10),
    branch_id: document.getElementById('leaveOverviewBranch')?.value || 'all',
    department_id: document.getElementById('leaveOverviewDept')?.value || 'all',
    policy_id: document.getElementById('leaveOverviewPolicy')?.value || 'all',
    work_nature: document.getElementById('leaveOverviewNature')?.value || 'all',
    status: document.getElementById('leaveOverviewStatus')?.value || 'active',
    policy_presence: document.getElementById('leaveOverviewPolicyPresence')?.value || 'all',
    search: (document.getElementById('leaveOverviewSearch')?.value || '').trim(),
    sort: document.getElementById('leaveOverviewSort')?.value || 'name_asc'
  };

  const res = await api('GET', 'reports/leave-overview', body);
  if (!res.success) {
    tbody.innerHTML = `<tr><td colspan="${COL}" style="text-align:center;padding:24px;color:var(--danger);">${esc(res.message || 'Error')}</td></tr>`;
    return;
  }
  const rows = res.data.employees || [];
  if (!rows.length) {
    tbody.innerHTML = `<tr><td colspan="${COL}" style="text-align:center;padding:24px;color:var(--text-muted);">${t('no_records')}</td></tr>`;
    return;
  }
  const typeCell = (x) => `
    <div style="font-weight:700;">${HRProFinalReport.formatLeaveBalanceBox({ rem: x.rem, total: x.total })}</div>
    <div style="font-size:10px;color:var(--textM);margin-top:4px;line-height:1.4;">
      ${t('leave_overview_appr')}: ${x.used_approved} · ${t('leave_overview_man')}: ${x.used_manual}
    </div>
  `;
  const natLabel = (wn) => (wn === 'external' ? t('external') : t('internal'));
  const stLabel = (ia) => (parseInt(String(ia), 10) === 1 ? t('status_active') : t('status_inactive'));
  tbody.innerHTML = rows.map(row => `
    <tr>
      <td data-label="${t('employee')}">${esc(row.name)}</td>
      <td data-label="${t('code')}" style="font-family:var(--mono);">${esc(row.employee_code)}</td>
      <td data-label="${t('department')}">${esc(row.department_name || '—')}</td>
      <td data-label="${t('branch')}">${esc(row.branch_name || '—')}</td>
      <td data-label="${t('emp_leave_policy_label')}">${esc(row.policy_name || '—')}</td>
      <td data-label="${t('work_nature')}">${esc(natLabel(row.work_nature))}</td>
      <td data-label="${t('status')}">${esc(stLabel(row.is_active))}</td>
      <td data-label="${t('leave_overview_absent')}">${row.absent_workdays != null ? row.absent_workdays : '—'}</td>
      <td data-label="${t('leave_overview_manual_total')}">${row.manual_days_total != null ? row.manual_days_total : '—'}</td>
      <td data-label="${t('annual')}">${typeCell(row.annual)}</td>
      <td data-label="${t('casual')}">${typeCell(row.casual)}</td>
      <td data-label="${t('sick')}">${typeCell(row.sick)}</td>
      <td class="lov-no-print" data-label="${t('ledger_col_actions')}"><button type="button" class="btn btn-sm btn-outline js-leave-ledger-open" data-emp-id="${Number(row.employee_id)}" data-emp-name="${escAttr(row.name)}" data-i18n="ledger_open">Ledger</button></td>
    </tr>
  `).join('');
  updateUITranslations();
}

function printReport() {
  const eid = document.getElementById('finalReportEmployee').value;
  if (!eid) return showToast(t('select_employee'), 'warning');
  const from = document.getElementById('finalReportFrom').value;
  const to = document.getElementById('finalReportTo').value;
  const hide = document.getElementById('finalReportHideDays')?.value || 'all';
  const q = new URLSearchParams({ employee_id: eid, from, to, hide });
  const url = 'Reports/employee-final-report-print.html?' + q.toString();
  window.open(url, '_blank', 'noopener');
}

/** Opens the same standalone final report print page as Final Report, using FD tab employee + period. */
function fdPrintReport() {
  const eid = getFinalDecisionEmployeeId();
  if (!eid) return showToast(t('select_employee'), 'warning');
  const from = document.getElementById('fdFrom')?.value;
  const to = document.getElementById('fdTo')?.value;
  if (!from || !to) return showToast(t('fd_print_need_period'), 'warning');
  const q = new URLSearchParams({ employee_id: eid, from, to, hide: 'all' });
  const url = 'Reports/employee-final-report-print.html?' + q.toString();
  window.open(url, '_blank', 'noopener');
}

async function fdPrintShortReport() {
  const eid = getFinalDecisionEmployeeId();
  if (!eid) return showToast(t('select_employee'), 'warning');
  const from = document.getElementById('fdFrom')?.value;
  const to = document.getElementById('fdTo')?.value;
  if (!from || !to) return showToast(t('fd_print_need_period'), 'warning');
  const tk = await api('POST', 'reports/final-link-token', { employee_id: eid, from, to, hide: 'all' });
  if (!tk?.success || !tk?.data?.token) {
    return showToast(tk?.message || 'Unable to secure report link', 'warning');
  }
  const url = 'Reports/emp_short_report.html?rt=' + encodeURIComponent(String(tk.data.token));
  window.open(url, '_blank', 'noopener');
}

function openModal(id) {
  const el = document.getElementById(id);
  if (!el) return;
  el.classList.add('open');
  // PERF: scroll backdrop to top so modal is always visible on mobile
  el.scrollTop = 0;
  if (id === 'policyModal' || id === 'empModal' || id === 'leaveLedgerModal' || id === 'branchDeleteConfirmModal' || id === 'deptDeleteConfirmModal') updateUITranslations();
}
function closeModal(id) {
  const el = document.getElementById(id);
  if (el) el.classList.remove('open');
}

document.getElementById('leaveOverviewBody')?.addEventListener('click', (ev) => {
  const btn = ev.target.closest('.js-leave-ledger-open');
  if (!btn) return;
  ev.preventDefault();
  const id = btn.getAttribute('data-emp-id');
  const name = btn.getAttribute('data-emp-name') || '';
  if (id) openLeaveLedgerModal(id, name);
});
function openLightbox(src) {
  document.getElementById('lightboxImg').src = hrproMediaUrl(src || '');
  document.getElementById('lightbox').style.display = 'flex';
}
function showToast(msg, type='success') {
  const t = document.getElementById('toast');
  t.textContent = msg; t.className = `toast show ${type}`;
  setTimeout(() => t.classList.remove('show'), 3500);
}
document.querySelectorAll('.modal-backdrop').forEach(bd => {
  bd.addEventListener('click', e => { if (e.target === bd) closeModal(bd.id); });
});
document.getElementById('loginPass').addEventListener('keydown', e => { if (e.key==='Enter') doLogin(); });

(function setupFinalReportTableClickDelegation() {
  const tb = document.getElementById('finalReportTable');
  if (!tb || tb.dataset.frDelegInit === '1') return;
  tb.dataset.frDelegInit = '1';
  tb.addEventListener('change', (e) => {
    const tgt = e.target;
    if (!tgt || !tgt.classList) return;
    const ds = tgt.getAttribute('data-fr-slt');
    if (!ds) return;
    if (tgt.classList.contains('fr-slt-type')) frSettleSet(ds, 'leave_type', tgt.value);
    else if (tgt.classList.contains('fr-slt-days')) frSettleSet(ds, 'days', tgt.value);
  });
  tb.addEventListener('input', (e) => {
    const tgt = e.target;
    if (tgt && tgt.classList && tgt.classList.contains('fr-slt-reason')) {
      const ds2 = tgt.getAttribute('data-fr-slt');
      if (ds2) frSettleSet(ds2, 'reason', tgt.value);
    }
  });
  tb.addEventListener('click', (e) => {
    const raw = e.target;
    const el = raw && raw.nodeType === 1 ? raw : (raw && raw.parentElement);
    if (!el || typeof el.closest !== 'function') return;
    const rst = el.closest('[data-fr-slt-reset]');
    if (rst) {
      e.preventDefault();
      e.stopPropagation();
      const ds = rst.getAttribute('data-fr-slt-reset');
      if (ds) frResetSettlementRow(ds);
      return;
    }
    const delBtn = el.closest('[data-fr-deduct-del]');
    if (delBtn) {
      e.preventDefault();
      e.stopPropagation();
      const id = parseInt(String(delBtn.getAttribute('data-fr-deduct-del') || ''), 10);
      if (id) void deleteFinalReportDeduct(id);
      return;
    }
    const deductBtn = el.closest('[data-fr-deduct-date]');
    if (deductBtn) {
      e.preventDefault();
      e.stopPropagation();
      const ds = deductBtn.getAttribute('data-fr-deduct-date');
      if (ds) openFinalReportDeductModal(ds);
      return;
    }
    const salDel = el.closest('[data-fr-salary-del]');
    if (salDel) {
      e.preventDefault();
      e.stopPropagation();
      const sid = parseInt(String(salDel.getAttribute('data-fr-salary-del') || ''), 10);
      if (sid) void deleteFinalReportSalary(sid);
      return;
    }
    const salBtn = el.closest('[data-fr-salary-date]');
    if (salBtn) {
      e.preventDefault();
      e.stopPropagation();
      const ds2 = salBtn.getAttribute('data-fr-salary-date');
      if (ds2) openFinalReportSalaryModal(ds2);
      return;
    }
    const lateOne = el.closest('[data-fr-late-apply]');
    if (lateOne) {
      e.preventDefault();
      e.stopPropagation();
      const ds3 = lateOne.getAttribute('data-fr-late-apply');
      if (ds3) void applyFinalReportLateDeductionDay(ds3);
    }
  });
})();

// Init
if (authToken) {
  try {
    const payload = JSON.parse(atob(authToken.split('.')[1] + '=='));
    if (payload.exp > Date.now()/1000) {
      void (async () => {
        const live = await fetchAdminLiveSession();
        if (!live) {
          localStorage.removeItem('admin_token');
          showToast(t('session_invalid') || 'Session expired', 'error');
          return;
        }
        persistAdminSessionData(live);
        await initApp(live);
      })();
    } else { localStorage.removeItem('admin_token'); }
  } catch (e) {
    localStorage.removeItem('admin_token');
  }
}
updateUITranslations();
refreshAdminInstallButtons();

function applyLanguage(lang) {
  _cachedLang = lang; // PERF: keep cache in sync
  document.documentElement.lang = lang;
  document.documentElement.dir = lang === 'ar' ? 'rtl' : 'ltr';
  const sel = document.getElementById('adminLangSelect');
  if (sel) sel.value = lang;
  updateUITranslations();
  const bmOpen = document.getElementById('branchActivityModal');
  if (bmOpen && bmOpen.classList.contains('open') && window._bmDetail && typeof renderBranchMonitorDetailView === 'function') {
    renderBranchMonitorDetailView();
    if (typeof syncBranchMonitorFilterButtons === 'function') syncBranchMonitorFilterButtons();
  }
  if (window._fdRawData && typeof renderFinalDecision === 'function') {
    renderFinalDecision(window._fdRawData);
  }
  const tabContacts = document.getElementById('tab-contacts');
  if (tabContacts && tabContacts.style.display === 'block') {
    if (typeof adminContactsFillFilterOptions === 'function') adminContactsFillFilterOptions();
    if (typeof adminContactsApplyFilter === 'function') adminContactsApplyFilter();
  }
  refreshAdminInstallButtons();
  updateAdminSidebarToggleUi(document.body.classList.contains('admin-sidebar-collapsed'));
  document.dispatchEvent(new CustomEvent('hrpro-admin-lang-changed', { detail: lang }));
}

async function changeAdminPassword() {
  const oldPwd = document.getElementById('adminOldPwd').value;
  const newPwd = document.getElementById('adminNewPwd').value;
  const confPwd = document.getElementById('adminConfirmPwd').value;
  if (!oldPwd || !newPwd || !confPwd) return showToast(t('fill_fields'), 'error');
  if (newPwd !== confPwd) return showToast(t('pwd_mismatch'), 'error');
  
  const res = await api('POST', 'admin/change-password', { old_password: oldPwd, new_password: newPwd });
  if (res.success) {
    showToast(t('pwd_updated'), 'success');
    closeModal('settingsModal');
    document.getElementById('adminOldPwd').value = '';
    document.getElementById('adminNewPwd').value = '';
    document.getElementById('adminConfirmPwd').value = '';
  } else {
    showToast(res.message, 'error');
  }
}

async function updateLanguagePreference(lang) {
  applyLanguage(lang);
  localStorage.setItem('admin_lang', lang);
  const res = await api('POST', 'user/language', { language: lang });
  if (res.success) {
    showToast(lang === 'ar' ? 'تم تحديث اللغة' : 'Language updated', 'success');
  } else {
    showToast(res.message, 'error');
  }
}

async function saveWelcomeEmailTemplate() {
  const ta = document.getElementById('adminWelcomeEmailTemplate');
  if (!ta) return;
  const res = await api('PUT', 'admin/welcome-email-template', { template: ta.value });
  if (res.success) {
    _companyWelcomeTemplate = null; // reset cache — next invite will load fresh
    showToast(t('welcome_template_saved'), 'success');
  } else {
    showToast(res.message, 'error');
  }
}

async function saveMobileAppUrl() {
  const el = document.getElementById('adminMobileAppUrl');
  if (!el) return;
  const res = await api('PUT', 'admin/mobile-app-url', { mobile_app_url: el.value.trim() });
  if (res.success) {
    const saved = res.data && res.data.mobile_app_url != null ? String(res.data.mobile_app_url) : el.value.trim();
    if (saved) localStorage.setItem('admin_mobile_app_url', saved);
    else localStorage.removeItem('admin_mobile_app_url');
    if (res.data && res.data.mobile_app_url != null) el.value = String(res.data.mobile_app_url);
    showToast(t('mobile_app_url_saved'), 'success');
  } else {
    showToast(res.message, 'error');
  }
}

function ensureQrCodeLib() {
  if (typeof QRCode === 'function') return Promise.resolve();
  return new Promise((resolve, reject) => {
    const s = document.createElement('script');
    s.src = 'https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js';
    s.crossOrigin = 'anonymous';
    s.onload = () => resolve();
    s.onerror = () => reject(new Error('qrcode'));
    document.head.appendChild(s);
  });
}

async function printAdminMobileAppQr() {
  const el = document.getElementById('adminMobileAppUrl');
  if (!el) return;
  let raw = String(el.value || '').trim();
  if (!raw) {
    const fromStore = localStorage.getItem('admin_mobile_app_url') || '';
    if (fromStore) raw = fromStore;
  }
  if (!raw) return showToast(t('mobile_app_url_empty'), 'error');
  let href;
  try {
    href = new URL(/^https?:\/\//i.test(raw) ? raw : `https://${raw}`).href;
  } catch (e) {
    return showToast(t('mobile_app_url_empty'), 'error');
  }
  try {
    await ensureQrCodeLib();
  } catch (e) {
    return showToast(t('unknown_error') || 'Failed to load QR', 'error');
  }
  const holder = document.createElement('div');
  holder.style.cssText = 'position:fixed;left:-9999px;top:0;';
  document.body.appendChild(holder);
  const qrSize = 520;
  const qrOpts = { text: href, width: qrSize, height: qrSize };
  if (typeof QRCode !== 'undefined' && QRCode.CorrectLevel) {
    qrOpts.correctLevel = QRCode.CorrectLevel.M;
  }
  try {
    new QRCode(holder, qrOpts);
  } catch (e) {
    document.body.removeChild(holder);
    return showToast(t('mobile_app_url_empty'), 'error');
  }
  setTimeout(() => {
    const img = holder.querySelector('img');
    const canvas = holder.querySelector('canvas');
    const qrDataUrl = (img && img.src) ? img.src : (canvas ? canvas.toDataURL('image/png') : '');
    try { document.body.removeChild(holder); } catch (e2) { /* ignore */ }
    if (!qrDataUrl) return showToast(t('mobile_app_url_empty'), 'error');
    const logo = localStorage.getItem('admin_company_logo') || '';
    const logoSrc = logo ? hrproMediaUrl(logo) : '';
    const esc = (s) => String(s).replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;');
    const companyName = (localStorage.getItem('admin_company_name') || '').trim();
    const lang = _cachedLang || document.documentElement.lang || 'ar';
    const dir = lang === 'ar' ? 'rtl' : 'ltr';
    const posterTitle = esc(t('mobile_app_qr_poster_title'));
    const posterNote = esc(t('mobile_app_qr_poster_note'));
    const companyBlock = companyName
      ? `<p class="company-name">${esc(companyName)}</p>`
      : '';
    const logoBlock = logoSrc
      ? `<div class="logo"><img src="${esc(logoSrc)}" alt=""></div>`
      : '';
    const html = `<!DOCTYPE html><html lang="${esc(lang)}" dir="${esc(dir)}"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>${posterTitle}</title>
<style>
@page { size: A4 portrait; margin: 12mm; }
* { box-sizing: border-box; -webkit-print-color-adjust: exact; print-color-adjust: exact; }
body { margin: 0; min-height: 100vh; font-family: "IBM Plex Sans Arabic", "Segoe UI", Tahoma, system-ui, sans-serif; background: #f1f5f9; color: #0f172a; }
.poster-outer { padding: 12px; max-width: 210mm; margin: 0 auto; }
.poster { background: #fff; border: 3px solid #0f172a; border-radius: 16px; box-shadow: 0 4px 24px rgba(15,23,42,0.08); padding: 20px 18px 28px; }
.headline { font-size: 26px; font-weight: 800; margin: 0 0 6px; letter-spacing: -0.02em; }
.company-name { font-size: 16px; color: #475569; font-weight: 600; margin: 0 0 18px; }
.logo { margin: 0 auto 16px; text-align: center; }
.logo img { max-height: 100px; max-width: 280px; object-fit: contain; vertical-align: middle; }
.qr-wrap { margin: 0 auto 22px; text-align: center; line-height: 0; }
.qr-wrap img { display: inline-block; vertical-align: top; width: 14cm; height: 14cm; max-width: 92%; object-fit: contain; }
.note { font-size: 19px; line-height: 1.65; color: #1e293b; text-align: center; max-width: 40em; margin: 0 auto; padding: 16px 8px 0; border-top: 1px solid #e2e8f0; }
@media print {
  body { background: #fff; }
  .poster-outer { padding: 0; max-width: none; }
  .poster { box-shadow: none; border-width: 2.5pt; }
  .qr-wrap img { width: 14cm; height: 14cm; }
}
@media (max-width: 520px) {
  .headline { font-size: 22px; }
  .note { font-size: 16px; }
  .qr-wrap img { width: min(88vw, 14cm); height: auto; aspect-ratio: 1; }
}
</style></head><body>
<div class="poster-outer">
  <div class="poster">
    <h1 class="headline">${posterTitle}</h1>
    ${companyBlock}
    ${logoBlock}
    <div class="qr-wrap"><img src="${String(qrDataUrl).replace(/"/g, '&quot;')}" alt="QR" width="${qrSize}" height="${qrSize}"></div>
    <p class="note">${posterNote}</p>
  </div>
</div>
</body></html>`;
    const w = window.open('', '_blank');
    if (!w) return showToast(t('mobile_app_url_print_popup'), 'warning');
    w.document.write(html);
    w.document.close();
    w.focus();
    setTimeout(() => { try { w.print(); } catch (e3) { /* ignore */ } }, 450);
  }, 220);
}

async function saveTimezone(tz) {
  const res = await api('POST', 'admin/timezone', { timezone: tz });
  if (res.success) {
    localStorage.setItem('admin_timezone', tz);
    showToast(t('timezone_saved'), 'success');
  } else {
    showToast(res.message, 'error');
  }
}

// ══════════════════════════════════════════════════════════════════════════════
// FINAL DECISION SCREEN
// ══════════════════════════════════════════════════════════════════════════════

let _fdRawData    = null;
let _fdHiddenCols = new Set();
let _fdSummaryVisible = true;
let _fdSummaryCompact = false;
var _fdEmployeeSource = [];

function fdEmployeeDisplayName(e) {
  return `${e.name || ''}`.trim();
}

function getFinalDecisionEmployeeId() {
  const hid = document.getElementById('fdEmployeeId');
  if (hid) return hid.value || '';
  const sel = document.getElementById('fdEmployee');
  return sel ? (sel.value || '') : '';
}

function renderFinalDecisionEmployeePicker() {
  const input = document.getElementById('fdEmployee');
  const list = document.getElementById('fdEmployeeList');
  const hid = document.getElementById('fdEmployeeId');
  if (!input || !list || !hid) return;
  const src = Array.isArray(allEmployeesData) ? allEmployeesData : [];
  _fdEmployeeSource = src.slice();
  list.innerHTML = src.map((e) => `<option value="${fdEmployeeDisplayName(e)}"></option>`).join('');
  if (hid.value) {
    const match = src.find((e) => String(e.id) === String(hid.value));
    if (match) input.value = fdEmployeeDisplayName(match);
    else {
      hid.value = '';
      input.value = '';
    }
  }
}

function onFinalDecisionEmployeeInput(triggerLoad) {
  const input = document.getElementById('fdEmployee');
  const hid = document.getElementById('fdEmployeeId');
  if (!input || !hid) return;
  const txt = String(input.value || '').trim().toLowerCase();
  const src = (Array.isArray(_fdEmployeeSource) && _fdEmployeeSource.length)
    ? _fdEmployeeSource
    : (Array.isArray(allEmployeesData) ? allEmployeesData : []);
  const exact = src.find((e) => {
    const disp = fdEmployeeDisplayName(e).toLowerCase();
    return disp === txt || String(e.employee_code || '').toLowerCase() === txt;
  });
  hid.value = exact ? String(exact.id) : '';
  if (triggerLoad && hid.value) loadFinalDecision();
}

function loadFinalDecisionTab() {
  const proceed = () => {
    const hid = document.getElementById('fdEmployeeId');
    if (!hid) return;
    renderFinalDecisionEmployeePicker();
    // Mirror employee from Final Report tab if nothing selected
    if (!hid.value) {
      const frSel = document.getElementById('finalReportEmployee');
      if (frSel && frSel.value) {
        hid.value = frSel.value;
        renderFinalDecisionEmployeePicker();
      }
    }
    // Ensure dates are always set
    const fromEl = document.getElementById('fdFrom');
    if (!fromEl || !fromEl.value) {
      setFinalDecisionDatesByMonth();
    } else if (hid.value) {
      loadFinalDecision();
    }
  };

  if (allEmployeesData && allEmployeesData.length) {
    proceed();
  } else {
    void loadEmployees().then(proceed);
  }
}

function setFinalDecisionDatesByMonth() {
  const mSel = document.getElementById('fdMonth');
  const fromEl = document.getElementById('fdFrom');
  const toEl   = document.getElementById('fdTo');
  if (!mSel || !fromEl || !toEl) return;
  const v = mSel.value;
  const now = new Date();
  let y = now.getFullYear(), m = now.getMonth();
  if (v === 'last') {
    m--;
    if (m < 0) { m = 11; y--; }
  }
  if (v === 'custom') return;
  const pad = (n) => String(n).padStart(2, '0');
  const lastDay = new Date(y, m + 1, 0).getDate();
  fromEl.value = `${y}-${pad(m + 1)}-01`;
  toEl.value   = `${y}-${pad(m + 1)}-${pad(lastDay)}`;
  loadFinalDecision();
}

async function loadFinalDecision() {
  const eid  = getFinalDecisionEmployeeId();
  const from = document.getElementById('fdFrom')?.value;
  const to   = document.getElementById('fdTo')?.value;
  const tbody = document.getElementById('fdTableBody');
  if (!eid) {
    if (tbody) tbody.innerHTML = `<tr><td colspan="8" style="text-align:center;padding:36px;color:var(--textM);">${t('select_employee')}</td></tr>`;
    fdApplySummaryVisibility(false);
    const balBar = document.getElementById('fdBalanceBar');
    if (balBar) balBar.style.display = 'none';
    return;
  }
  // If dates are missing, try to set them automatically
  if (!from || !to) {
    setFinalDecisionDatesByMonth();
    return;
  }
  if (tbody) tbody.innerHTML = `<tr><td colspan="8" style="text-align:center;padding:36px;color:var(--textM);">${t('loading')}</td></tr>`;
  try {
    const res = await api('GET', 'reports/final', { employee_id: eid, from, to });
    if (res.success) {
      _fdRawData = res.data;
      window._fdRawData = res.data;
      renderFinalDecision(res.data);
    } else {
      if (tbody) tbody.innerHTML = `<tr><td colspan="8" style="text-align:center;padding:36px;color:var(--danger);">${_fdEsc(res.message || t('unknown_error'))}</td></tr>`;
      fdApplySummaryVisibility(false);
    }
  } catch (err) {
    if (tbody) tbody.innerHTML = `<tr><td colspan="8" style="text-align:center;padding:36px;color:var(--danger);">${_fdEsc(String(err))}</td></tr>`;
    fdApplySummaryVisibility(false);
  }
}

function _fdEsc(s) {
  return s == null ? '' : String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/"/g, '&quot;');
}

function fdGetRowSuggestion(row, balances) {
  const hasMd  = (row.manual_deductions  || []).length > 0;
  const hasSal = (row.salary_deductions  || []).length > 0;
  if (hasMd || hasSal) {
    return { state: 'done', md: row.manual_deductions || [], sal: row.salary_deductions || [] };
  }
  const status = row.status || '';
  if (status === 'weekend' || status === 'official_holiday' || status === 'leave') {
    return { state: 'skip' };
  }

  // ── LATE: primary = salary, secondary = leave ───────────────────────────
  const pv = row.late_leave_deduct_preview;
  if (pv && pv.days) {
    const lateMin = pv.late_minutes || 0;
    const schIn   = row.sch_in ? String(row.sch_in).substring(0, 5) : '09:00';
    const tierKey = `fd_tier_${pv.tier || 'full'}`;
    const tierLabel = t(tierKey);
    const reason = t('fd_reason_late', { mins: String(lateMin), schIn, tier: tierLabel });
    return {
      state: 'pending',
      kind: 'late',
      primaryType: 'salary',
      secondaryType: 'leave',
      primaryLabel: t('fd_exec_salary'),
      secondaryLabel: t('fd_exec_leave'),
      days: pv.days,
      leaveType: pv.leave_type,
      reason,
    };
  }

  // ── ABSENT: primary = leave, secondary = salary ─────────────────────────
  if (status === 'absent') {
    const ss = row.system_settlement;
    if (ss) {
      const lt = ss.leave_type || 'annual';
      const leaveName = t(lt) || lt;
      const rem = balances && balances[lt] ? Number(balances[lt].rem) : 0;
      const reason = t('fd_reason_absent_leave', {
        leave: leaveName,
        rem: rem % 1 === 0 ? String(Math.round(rem)) : rem.toFixed(2),
      });
      return {
        state: 'pending',
        kind: 'absent_leave',
        primaryType: 'leave',
        secondaryType: 'salary',
        primaryLabel: t('fd_exec_leave'),
        secondaryLabel: t('fd_exec_salary'),
        days: ss.days,
        leaveType: lt,
        reason,
      };
    }
    // absent but no system_settlement (policy missing)
    return {
      state: 'pending',
      kind: 'absent_leave',
      primaryType: 'leave',
      secondaryType: 'salary',
      primaryLabel: t('fd_exec_leave'),
      secondaryLabel: t('fd_exec_salary'),
      days: 1,
      leaveType: 'annual',
      reason: t('fd_reason_absent_leave', { leave: t('annual'), rem: '?' }),
    };
  }

  // ── PRESENT (no late issue detected) — allow manager override ─────────
  if (status === 'present') {
    return {
      state: 'pending',
      kind: 'manual',
      primaryType: 'salary',
      secondaryType: 'leave',
      primaryLabel: t('fd_exec_salary'),
      secondaryLabel: t('fd_exec_leave'),
      days: null,
      leaveType: null,
      reason: t('fd_reason_no_policy'),
    };
  }

  return { state: 'none' };
}

function renderFinalDecision(data) {
  const escH = _fdEsc;
  const report   = data.report   || [];
  const balances = data.balances || {};
  const policy   = data.policy   || {};
  const totals   = data.totals   || {};
  const tbody  = document.getElementById('fdTableBody');

  // Render balance bar at the top of the screen
  fdRenderBalanceBar(balances, policy);
  if (!tbody) return;

  // Pre-cache all translations used in the loop (avoids repeated lookups per row)
  const TR = {
    date_day:     t('fd_col_date_day'),
    status:       t('status'),
    first_in:     t('first_in'),
    last_out:     t('last_out'),
    fd_col_total: t('fd_col_total_work_permission'),
    fd_col_issue: t('fd_col_issue'),
    fd_col_sug:   t('fd_col_suggestion'),
    actions:      t('actions'),
    present:      t('status_present'),
    absent:       t('status_absent'),
    leave:        t('status_leave'),
    weekend:      t('weekend_days'),
    official:     t('status_official_holiday'),
    fr_sug_late:  t('fr_sug_late'),
    fr_sug_early: t('fr_sug_early_leave'),
    fd_min:       t('fd_min'),
    fd_no_sug:    t('fd_no_sug'),
    fd_nothing:   t('fd_nothing'),
    fd_stat_done: t('fd_stat_done'),
    fd_exec_sal:  t('fd_exec_salary'),
    fd_exec_lv:   t('fd_exec_leave'),
    fd_apply:     t('fd_apply_btn'),
    fr_sal_short: t('fr_salary_short'),
    permission:   t('permission'),
    mission:      t('permission_mission_label'),
    perm_time:    t('permission_time_range'),
    annual:       t('annual'),
    casual:       t('casual'),
    sick:         t('sick'),
  };
  const dayNames = ['weekday_sunday','weekday_monday','weekday_tuesday','weekday_wednesday','weekday_thursday','weekday_friday','weekday_saturday'].map(k => t(k));
  const delTitle = escH(t('fd_delete_aria'));
  const trashSvg = `<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6M14 11v6"/></svg>`;
  const badgePresent  = `<span class="badge badge-ok">${TR.present}</span>`;
  const badgeAbsent   = `<span class="badge badge-rejected">${TR.absent}</span>`;
  const badgeLeave    = `<span class="badge badge-pending">${TR.leave}</span>`;
  const badgeWeekend  = `<span class="badge" style="background:var(--surface2);color:var(--textM);">${TR.weekend}</span>`;
  const badgeOfficial = `<span class="badge" style="background:rgba(6,182,212,.12);color:var(--accent);">${TR.official}</span>`;

  let statLate = 0, statAbsent = 0, statPending = 0, statDone = 0;

  if (!report.length) {
    tbody.innerHTML = `<tr><td colspan="8" style="text-align:center;padding:36px;color:var(--textM);">${t('no_records')}</td></tr>`;
    fdApplySummaryVisibility(false);
    const bar1 = document.getElementById('fdBalanceBar');
    if (bar1) bar1.style.display = 'none';
    const tsd = document.getElementById('printSalaryDeductDays');
    if (tsd) tsd.textContent = '0';
    return;
  }

  tbody.innerHTML = report.map((r) => {
    const d        = new Date(r.date + 'T12:00:00');
    const dayName  = dayNames[d.getDay()];
    const status   = r.status || '';
    const isWeekend = status === 'weekend' || status === 'official_holiday';

    const ciTime = r.check_in  ? r.check_in.substring(11,16)  : null;
    const coTime = r.check_out ? r.check_out.substring(11,16) : null;
    const schIn  = r.sch_in  ? String(r.sch_in).substring(0,5)  : '09:00';
    const schOut = r.sch_out ? String(r.sch_out).substring(0,5) : '17:00';
    const isLate  = ciTime && ciTime > schIn;
    const isEarly = coTime && coTime < schOut;
    const approvedPermissions = Array.isArray(r.approved_permissions) ? r.approved_permissions : [];
    const totalWorkPermission = formatDurationMinutes(calcEffectiveWorkPermissionMinutes(r));

    // Status badge (use pre-built strings)
    let statusBadge;
    if      (status === 'present')          statusBadge = badgePresent;
    else if (status === 'absent')           statusBadge = badgeAbsent;
    else if (status === 'leave')            statusBadge = badgeLeave;
    else if (status === 'weekend')          statusBadge = badgeWeekend;
    else if (status === 'official_holiday') statusBadge = badgeOfficial;
    else                                    statusBadge = `<span class="badge">${escH(status)}</span>`;

    // Issue badge
    let issueBadge = '<span style="color:var(--textM);font-size:12px;">—</span>';
    if (status === 'absent') {
      statAbsent++;
      issueBadge = `<span class="fd-issue-badge fd-issue-absent">${TR.absent}</span>`;
    } else if (status === 'present') {
      const pv = r.late_leave_deduct_preview;
      const lateMin = pv ? (pv.late_minutes || 0) : 0;
      if (isLate && lateMin > 0) {
        statLate++;
        issueBadge = `<span class="fd-issue-badge fd-issue-late">${TR.fr_sug_late} ${lateMin}${TR.fd_min}</span>`;
      } else if (isLate) {
        statLate++;
        issueBadge = `<span class="fd-issue-badge fd-issue-late">${TR.fr_sug_late}</span>`;
      }
      if (isEarly) {
        issueBadge += ` <span class="fd-issue-badge fd-issue-early">${TR.fr_sug_early}</span>`;
      }
    }

    // Suggestion + action (two-button layout)
    const sug = fdGetRowSuggestion(r, balances);
    let sugCell    = '';
    let actionCell = '';

    if (sug.state === 'skip') {
      sugCell    = `<span class="fd-sug-none">—</span>`;
      actionCell = `<span style="color:var(--textM);font-size:12px;">—</span>`;

    } else if (sug.state === 'none') {
      sugCell    = `<span class="fd-sug-none">${TR.fd_no_sug}</span>
                    <div style="font-size:11px;color:var(--textM);margin-top:3px;">${escH(sug.reason||'')}</div>`;
      actionCell = `<span style="color:var(--textM);font-size:12px;">${TR.fd_nothing}</span>`;

    } else if (sug.state === 'done') {
      statDone++;
      const appliedParts = [
        ...(sug.md  || []).map(m => `${TR[m.leave_type] || t(m.leave_type) || m.leave_type} −${m.days}`),
        ...(sug.sal || []).map(s => `${TR.fr_sal_short} −${s.days}`),
      ].join(' · ');
      const chips = [
        ...(sug.md || []).map(m =>
          `<button type="button" class="fd-action-chip" title="${delTitle}" onclick="fdDeleteDeduct(${m.id},'leave')">
            ${escH(TR[m.leave_type] || t(m.leave_type) || m.leave_type)} −${m.days} ${trashSvg}
          </button>`),
        ...(sug.sal || []).map(s =>
          `<button type="button" class="fd-action-chip" title="${delTitle}" onclick="fdDeleteDeduct(${s.id},'salary')">
            ${TR.fr_sal_short} −${s.days} ${trashSvg}
          </button>`),
      ].join('');
      sugCell    = `<span class="fd-sug-leave" style="font-size:12px;">${escH(appliedParts)}</span>
                    <div style="font-size:11px;color:var(--success);margin-top:3px;">✓ ${escH(TR.fd_stat_done)}</div>`;
      actionCell = `<div style="display:flex;flex-wrap:wrap;gap:6px;">${chips}</div>`;

    } else if (sug.state === 'pending') {
      statPending++;
      const dateEsc = escH(r.date);
      const sugJsonSafe = escH(JSON.stringify(sug));

      if (sug.kind === 'manual') {
        sugCell = `<span class="fd-sug-none" style="font-size:12px;">${TR.fd_no_sug}</span>
                   <div style="font-size:11px;color:var(--textM);line-height:1.5;margin-top:3px;">${escH(sug.reason||'')}</div>`;
      } else {
        const isPrimarySalary = sug.primaryType === 'salary';
        const sugClass = isPrimarySalary ? 'fd-sug-salary' : 'fd-sug-leave';
        const daysFmt  = sug.days != null ? ` (${sug.days})` : '';
        const sugLabel = isPrimarySalary ? TR.fd_exec_sal : TR.fd_exec_lv;
        sugCell = `<span class="${sugClass}" style="font-size:13px;font-weight:700;">${escH(sugLabel)}${escH(daysFmt)}</span>
                   <div style="font-size:11px;color:var(--textM);line-height:1.5;margin-top:4px;">${escH(sug.reason||'')}</div>`;
      }

      actionCell = `<button type="button" class="btn btn-sm btn-primary"
        onclick="fdOpenDeductModal('${dateEsc}', ${sugJsonSafe})"
        style="white-space:nowrap;">${TR.fd_apply}</button>`;
    }

    const trStyle = isWeekend ? 'opacity:.55;' : (status==='absent'?'background:rgba(239,68,68,.04);':'');
    const permissionInline = approvedPermissions.length
      ? `<div style="margin-top:10px;padding-top:10px;border-top:1px dashed var(--border);display:flex;flex-direction:column;gap:8px;">${approvedPermissions.map((p) => {
        const pf = p && p.permission_from ? String(p.permission_from) : '';
        const pt = p && p.permission_to ? String(p.permission_to) : '';
        const range = (pf && pt) ? formatPermissionTimeRange(pf, pt) : '—';
        const isMissionP = String(p && p.permission_kind ? p.permission_kind : '').toLowerCase() === 'mission';
        const headLabel = isMissionP ? escH(TR.mission) : escH(TR.permission);
        const rs = p && p.reason != null ? String(p.reason).trim() : '';
        const reasonLine = rs
          ? `<div style="font-size:11px;color:var(--textM);line-height:1.4;max-width:min(240px,42vw);white-space:pre-wrap;">${escH(rs)}</div>`
          : '';
        const badgeBg = isMissionP ? 'rgba(14,165,233,.2)' : 'rgba(6,182,212,.16)';
        return `<div style="display:flex;flex-direction:column;gap:4px;">
          <span style="display:inline-flex;flex-wrap:wrap;align-items:baseline;gap:6px 10px;align-self:flex-start;background:${badgeBg};color:var(--accent);font-size:11px;font-weight:700;padding:5px 11px;border-radius:10px;border:1px solid rgba(6,182,212,.22);">
            <span>${headLabel}</span>
            <span style="font-family:var(--mono);font-weight:600;opacity:.92;">${escH(TR.perm_time)} ${escH(range)}</span>
          </span>${reasonLine}</div>`;
      }).join('')}</div>`
      : '';
    return `<tr style="${trStyle}">
      <td data-label="${TR.date_day}" style="font-family:var(--mono);font-weight:700;">
        <div>${escH(r.date)}</div>
        <div style="font-family:var(--font);font-weight:600;color:var(--textM);font-size:12px;margin-top:2px;">${escH(dayName)}</div>
        ${permissionInline}
      </td>
      <td data-fd-col="status" data-label="${TR.status}">${statusBadge}</td>
      <td data-fd-col="in" data-label="${TR.first_in}" style="font-family:var(--mono);color:${isLate?'var(--danger)':'var(--success)'};font-weight:600;">${ciTime||'—'}${isLate?' ⚠':''}</td>
      <td data-fd-col="out" data-label="${TR.last_out}" style="font-family:var(--mono);color:${isEarly?'var(--danger)':'var(--accent)'};font-weight:600;">${coTime||'—'}${isEarly?' ⚠':''}</td>
      <td data-fd-col="total" data-label="${TR.fd_col_total}" style="font-family:var(--mono);font-weight:700;">${totalWorkPermission}</td>
      <td data-fd-col="issue" data-label="${TR.fd_col_issue}">${issueBadge}</td>
      <td data-fd-col="suggestion" data-label="${TR.fd_col_sug}" style="min-width:160px;">${sugCell}</td>
      <td data-fd-col="actions" data-label="${TR.actions}" style="min-width:120px;">${actionCell}</td>
    </tr>`;
  }).join('');

  // Restore hidden columns
  _fdHiddenCols.forEach((col) => {
    document.querySelectorAll(`#fdTable [data-fd-col="${col}"]`).forEach(el => el.classList.add('fd-hidden'));
  });

  // Update summary bar
  const bar = document.getElementById('fdSummaryBar');
  if (bar) bar.style.display = _fdSummaryVisible ? 'flex' : 'none';
  fdApplySummaryVisibility(true);
  bar?.classList.toggle('is-compact', _fdSummaryCompact);
  const _s = (id, v) => { const el = document.getElementById(id); if (el) el.textContent = v; };
  _s('fdStatLate',    statLate);
  _s('fdStatAbsent',  statAbsent);
  _s('fdStatPending', statPending);
  _s('fdStatDone',    statDone);
  const tsd = document.getElementById('printSalaryDeductDays');
  if (tsd) tsd.textContent = totals.salary_deduction_days != null ? String(totals.salary_deduction_days) : '0';

  // Show/hide execute-all button
  const execBtn = document.getElementById('fdExecAllBtn');
  if (execBtn) execBtn.style.display = statPending > 0 ? '' : 'none';
}

function fdApplySummaryVisibility(hasData) {
  const tools = document.getElementById('fdSummaryTools');
  const bar = document.getElementById('fdSummaryBar');
  if (tools) tools.style.display = hasData ? 'flex' : 'none';
  if (bar) {
    bar.style.display = (hasData && _fdSummaryVisible) ? 'flex' : 'none';
    bar.classList.toggle('is-compact', !!_fdSummaryCompact);
  }
  const toggleBtn = document.getElementById('fdSummaryToggleBtn');
  const compactBtn = document.getElementById('fdSummaryCompactBtn');
  const toggleSpan = toggleBtn?.querySelector('span');
  const compactSpan = compactBtn?.querySelector('span');
  if (toggleSpan) {
    const key = _fdSummaryVisible ? 'fd_stats_hide' : 'fd_stats_show';
    toggleSpan.setAttribute('data-i18n', key);
    toggleSpan.textContent = t(key);
  }
  if (compactSpan) {
    const key = _fdSummaryCompact ? 'fd_stats_expand' : 'fd_stats_compact';
    compactSpan.setAttribute('data-i18n', key);
    compactSpan.textContent = t(key);
  }
  if (compactBtn) compactBtn.style.display = (hasData && _fdSummaryVisible) ? '' : 'none';
}

function fdToggleSummaryVisible() {
  _fdSummaryVisible = !_fdSummaryVisible;
  fdApplySummaryVisibility(true);
}

function fdToggleSummaryCompact() {
  _fdSummaryCompact = !_fdSummaryCompact;
  fdApplySummaryVisibility(true);
}

async function fdExecuteAll() {
  const eid  = getFinalDecisionEmployeeId();
  const from = document.getElementById('fdFrom')?.value;
  const to   = document.getElementById('fdTo')?.value;
  if (!eid || !from || !to) { showToast(t('fr_need_report'), 'error'); return; }
  if (!confirm(t('fd_confirm_exec_all'))) return;
  const res = await api('POST', 'reports/final-execute-all', {
    employee_id: parseInt(String(eid), 10), from, to
  });
  if (res.success && res.data) {
    const d = res.data;
    showToast(t('fd_toast_bulk', {
      a: String(d.applied ?? 0),
      n: String(d.skipped_existing ?? 0),
      m: String(d.skipped_no_suggestion ?? 0),
    }), 'success');
    await loadFinalDecision();
  } else {
    showToast(res.message || t('unknown_error'), 'error');
  }
}

async function fdExecuteSingle(dateStr, deductionType, leaveType, days) {
  const eid = getFinalDecisionEmployeeId();
  if (!eid || !dateStr) { showToast(t('fr_need_report'), 'error'); return; }
  const body = { employee_id: parseInt(String(eid), 10), date: dateStr };
  if (deductionType) body.deduction_type = deductionType;
  if (leaveType)     body.leave_type = leaveType;
  if (days != null)  body.days = days;
  const res = await api('POST', 'reports/final-execute-single', body);
  if (res.success) {
    showToast(res.message || t('fd_done'), 'success');
    await loadFinalDecision();
  } else {
    showToast(res.message || t('unknown_error'), 'error');
  }
}

async function fdApplyFromModal(dateStr, deductionType, leaveType, days) {
  closeModal('fdDeductModal');
  await fdExecuteSingle(dateStr, deductionType, leaveType, days);
}

function fdRenderBalanceBar(balances, policy) {
  const bar = document.getElementById('fdBalanceBar');
  if (!bar) return;

  const renderBal = (elId, valElId, lt) => {
    const el    = document.getElementById(elId);
    const valEl = document.getElementById(valElId);
    if (!el || !valEl) return;
    const b      = balances && balances[lt];
    const rem    = b ? Number(b.rem)   : 0;
    const tot    = b ? Number(b.total) : 0;
    const remStr = rem % 1 === 0 ? String(Math.round(rem)) : rem.toFixed(2);
    const totStr = tot % 1 === 0 ? String(Math.round(tot)) : tot.toFixed(2);
    valEl.textContent = `${remStr} / ${totStr}`;
    valEl.style.color = rem <= 0 ? 'var(--danger)' : 'var(--success)';
    el.title = rem <= 0
      ? t('fd_bal_exhausted')
      : `${t(lt)}: ${remStr} ${t('fd_bal_remaining', { rem: remStr, total: totStr })}`;
    if (rem <= 0) {
      el.style.borderColor = 'rgba(239,68,68,.4)';
      el.style.background  = 'rgba(239,68,68,.06)';
    } else {
      el.style.borderColor = '';
      el.style.background  = '';
    }
    // Per-type warning badge (add/remove dynamically)
    let warnEl = el.querySelector('[data-fd-bal-warn]');
    if (rem <= 0) {
      if (!warnEl) {
        warnEl = document.createElement('div');
        warnEl.setAttribute('data-fd-bal-warn', '1');
        warnEl.style.cssText = 'font-size:10px;color:var(--danger);font-weight:700;margin-top:4px;';
        el.appendChild(warnEl);
      }
      warnEl.textContent = '⚠ ' + t('fd_bal_warn_single');
    } else if (warnEl) {
      warnEl.remove();
    }
  };

  renderBal('fdBalAnnual', 'fdBalAnnualVal', 'annual');
  renderBal('fdBalCasual', 'fdBalCasualVal', 'casual');
  renderBal('fdBalSick',   'fdBalSickVal',   'sick');

  const allExhausted = ['annual', 'casual', 'sick'].every(lt => {
    const b = balances && balances[lt];
    return !b || Number(b.rem) <= 0;
  });
  const alertEl = document.getElementById('fdBalExhaustedAlert');
  if (alertEl) alertEl.style.display = allExhausted ? 'flex' : 'none';

  bar.style.display = 'block';
}

function fdOpenDeductModal(dateStr, sug) {
  const raw      = _fdRawData || window._fdRawData || {};
  const balances = raw.balances || {};
  const policy   = raw.policy   || {};
  const report   = Array.isArray(raw.report) ? raw.report : [];
  const dayRow   = report.find((r) => r && r.date === dateStr) || null;

  // Use fractions from ALL company policies (sent by API); falls back to current policy then standard
  const allFractions = (() => {
    if (raw.all_policy_fractions && raw.all_policy_fractions.length) return raw.all_policy_fractions;
    if (policy.deduct_options    && policy.deduct_options.length)    return policy.deduct_options;
    return [0.25, 0.5, 1];
  })();

  const escH = _fdEsc;
  const labelEl = document.getElementById('fdDeductModalDateLabel');
  if (labelEl) labelEl.textContent = dateStr;

  // Fraction label helper
  const fracLabel = (f) => {
    if (Math.abs(f - 1)    < 0.001) return t('fd_deduct_full');
    if (Math.abs(f - 0.5)  < 0.001) return t('fd_deduct_half');
    if (Math.abs(f - 0.25) < 0.001) return t('fd_deduct_quarter');
    return String(f);
  };

  // ── 1. System suggestion block with direct "Apply" button ─────────────────
  let sugBlock = '';
  if (sug && sug.state === 'pending' && sug.reason) {
    const hasSugApply = sug.primaryType && sug.days != null;
    const leaveTypeArg = sug.leaveType ? `'${escH(sug.leaveType)}'` : 'null';
    const sugApplyBtn = hasSugApply
      ? `<button type="button" class="btn btn-primary" style="width:100%;margin-top:10px;font-size:14px;"
           onclick="fdApplyFromModal('${escH(dateStr)}','${sug.primaryType}',${leaveTypeArg},${sug.days})">
           ✓ ${escH(t('fd_apply_suggested'))} — ${escH(sug.primaryLabel || '')}${sug.days ? ' (' + sug.days + ')' : ''}
         </button>`
      : '';
    sugBlock = `
      <div style="background:rgba(16,185,129,.07);border:1px solid rgba(16,185,129,.3);border-radius:12px;padding:12px 14px;margin-bottom:16px;">
        <div style="font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--textM);margin-bottom:4px;">${escH(t('fd_modal_suggestion'))}</div>
        <div style="font-weight:600;color:var(--accent);">${escH(sug.primaryLabel || '')}${sug.days ? ' (' + sug.days + ')' : ''}</div>
        <div style="font-size:11px;color:var(--textM);margin-top:2px;">${escH(sug.reason)}</div>
        ${sugApplyBtn}
      </div>`;
  }

  // ── 2. Balance summary — 3-column grid with per-type warnings ─────────────
  const allExhausted = ['annual', 'casual', 'sick'].every(lt => {
    const b = balances[lt]; return !b || Number(b.rem) <= 0;
  });
  const salaryEnabled = Number(policy.salary_deduct_enabled ?? 1) === 1;
  const salaryAllowWithBalance = Number(policy.salary_deduct_allow_with_balance ?? 0) === 1;
  let salaryDisabledReason = '';
  let salaryEnabledForDay = true;
  if (!salaryEnabled) {
    salaryEnabledForDay = false;
    salaryDisabledReason = t('fd_salary_disabled_policy');
  } else if (dayRow && ['weekend', 'official_holiday', 'leave'].includes(String(dayRow.status || ''))) {
    salaryEnabledForDay = false;
    salaryDisabledReason = t('fd_salary_disabled_day');
  } else if (
    dayRow &&
    dayRow.status === 'absent' &&
    dayRow.can_mark_salary_deduct === false &&
    !salaryAllowWithBalance &&
    !allExhausted
  ) {
    salaryEnabledForDay = false;
    salaryDisabledReason = t('fd_salary_disabled_balance');
  }
  const exhaustedBanner = allExhausted
    ? `<div style="padding:8px 12px;border-radius:8px;background:rgba(239,68,68,.1);border:1px solid rgba(239,68,68,.35);color:var(--danger);font-size:12px;font-weight:700;margin-bottom:12px;">
        ⚠ ${escH(t('fd_all_exhausted_warn'))}
       </div>`
    : '';

  const balTypeKeys = { annual: 'fd_section_annual', casual: 'fd_section_casual', sick: 'fd_section_sick' };
  const balCards = ['annual', 'casual', 'sick'].map(lt => {
    const b      = balances[lt];
    const rem    = b ? Number(b.rem)   : 0;
    const tot    = b ? Number(b.total) : 0;
    const remStr = rem % 1 === 0 ? String(Math.round(rem)) : rem.toFixed(2);
    const totStr = tot % 1 === 0 ? String(Math.round(tot)) : tot.toFixed(2);
    const col        = rem <= 0 ? 'var(--danger)' : 'var(--success)';
    const borderCol  = rem <= 0 ? 'rgba(239,68,68,.4)' : 'var(--border)';
    const bgCol      = rem <= 0 ? 'rgba(239,68,68,.06)' : 'var(--surface)';
    const warnHtml   = rem <= 0
      ? `<div style="font-size:10px;color:var(--danger);font-weight:700;margin-top:3px;">⚠ ${escH(t('fd_bal_warn_single'))}</div>`
      : '';
    return `<div style="text-align:center;padding:10px 6px;border-radius:10px;background:${bgCol};border:1px solid ${borderCol};">
      <div style="font-size:10px;color:var(--textM);margin-bottom:4px;">${escH(t(balTypeKeys[lt]))}</div>
      <div style="font-size:18px;font-weight:800;color:${col};line-height:1.1;">${remStr}</div>
      <div style="font-size:10px;color:var(--textM);">/ ${totStr}</div>
      ${warnHtml}
    </div>`;
  }).join('');

  const balSummaryHtml = `
    <div style="margin-bottom:16px;">
      <div style="font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--textM);margin-bottom:8px;">${escH(t('fd_balance_summary'))}</div>
      ${exhaustedBanner}
      <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:8px;">${balCards}</div>
    </div>`;

  // ── 3. Divider — "Or choose a different decision" ─────────────────────────
  const divider = `
    <div style="display:flex;align-items:center;gap:8px;margin:16px 0 14px;">
      <div style="flex:1;border-top:1px solid var(--border);"></div>
      <span style="font-size:11px;font-weight:700;color:var(--textM);white-space:nowrap;">${escH(t('fd_override_section'))}</span>
      <div style="flex:1;border-top:1px solid var(--border);"></div>
    </div>`;

  // ── 4. Salary section ─────────────────────────────────────────────────────
  const salaryBtns = allFractions.map(f => {
    const fStr = String(f);
    return `<button type="button" class="btn btn-sm ${salaryEnabledForDay ? 'btn-outline' : 'btn-outline-danger'}"
      onclick="${salaryEnabledForDay ? `fdApplyFromModal('${escH(dateStr)}','salary',null,${fStr})` : ''}"
      ${salaryEnabledForDay ? '' : 'disabled'}
      style="min-width:auto;${salaryEnabledForDay ? 'border-color:rgba(16,185,129,.45);color:var(--success);' : ''}">
      ${escH(fracLabel(f))} (${fStr})
    </button>`;
  }).join('');
  const salaryReason = salaryEnabledForDay ? '' : `<div style="font-size:11px;color:var(--danger);margin-top:6px;">⚠ ${escH(salaryDisabledReason)}</div>`;
  const salarySection = `
    <div style="margin-bottom:16px;">
      <div style="font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--textM);margin-bottom:8px;">${escH(t('fd_section_salary'))}</div>
      <div style="display:flex;flex-wrap:wrap;gap:6px;">${salaryBtns}</div>
      ${salaryReason}
    </div>`;

  // ── 5. Leave type sections (annual / casual / sick) ───────────────────────
  const leaveTypes = [
    { lt: 'annual', key: 'fd_section_annual' },
    { lt: 'casual', key: 'fd_section_casual' },
    { lt: 'sick',   key: 'fd_section_sick'   },
  ];

  const leaveSections = leaveTypes.map(({ lt, key }) => {
    const b      = balances[lt];
    const rem    = b ? Number(b.rem)   : 0;
    const totN   = b ? Number(b.total) : 0;
    const remStr = rem % 1 === 0 ? String(Math.round(rem)) : rem.toFixed(2);
    const isZero = rem <= 0;
    const remLabel = isZero
      ? `<span style="color:var(--danger);font-size:11px;font-weight:700;">⚠ ${escH(t('fd_bal_zero_warn'))}</span>`
      : `<span style="color:var(--textM);font-size:11px;">${escH(t('fd_bal_remaining', { rem: remStr, total: String(totN) }))}</span>`;

    const btns = allFractions.map(f => {
      const fStr     = String(f);
      const warnStyle = isZero ? 'border-color:rgba(239,68,68,.4);color:var(--danger);' : '';
      return `<button type="button" class="btn btn-sm btn-outline"
        onclick="fdApplyFromModal('${escH(dateStr)}','leave','${lt}',${fStr})"
        style="min-width:auto;${warnStyle}">
        ${escH(fracLabel(f))}${isZero ? ' ⚠' : ''}
      </button>`;
    }).join('');

    return `<div style="margin-bottom:14px;">
      <div style="display:flex;align-items:center;gap:8px;margin-bottom:6px;">
        <span style="font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--textM);">${escH(t(key))}</span>
        ${remLabel}
      </div>
      <div style="display:flex;flex-wrap:wrap;gap:6px;">${btns}</div>
    </div>`;
  }).join('');

  // ── Assemble modal body ───────────────────────────────────────────────────
  const body = document.getElementById('fdDeductModalBody');
  if (body) {
    body.innerHTML = sugBlock
      + balSummaryHtml
      + divider
      + salarySection
      + `<hr style="border:none;border-top:1px solid var(--border);margin:16px 0;">`
      + leaveSections
      + `<div style="text-align:end;margin-top:16px;">
           <button type="button" class="btn btn-ghost" onclick="closeModal('fdDeductModal')" data-i18n="fd_modal_close">${t('fd_modal_close')}</button>
         </div>`;
  }

  openModal('fdDeductModal');
}

async function fdDeleteDeduct(id, type) {
  if (!confirm(t('fd_confirm_remove_deduct'))) return;
  const route = type === 'salary' ? `reports/salary-deduct/${id}` : `reports/leave-deduct/${id}`;
  const res = await api('DELETE', route, {});
  if (res.success) {
    showToast(t('dr_revert'), 'success');
    await loadFinalDecision();
  } else {
    showToast(res.message || t('unknown_error'), 'error');
  }
}

function fdToggleColumn(colName) {
  const checked = document.getElementById(`fdCol${colName.charAt(0).toUpperCase()+colName.slice(1)}`)?.checked ?? true;
  const cells   = document.querySelectorAll(`#fdTable [data-fd-col="${colName}"]`);
  if (checked) {
    _fdHiddenCols.delete(colName);
    cells.forEach(el => el.classList.remove('fd-hidden'));
  } else {
    _fdHiddenCols.add(colName);
    cells.forEach(el => el.classList.add('fd-hidden'));
  }
}

function fdToggleColMenu() {
  const menu = document.getElementById('fdColMenu');
  if (!menu) return;
  const willOpen = !menu.classList.contains('open');
  if (willOpen) {
    menu.classList.add('open');
    menu.classList.remove('open-up');
    // Decide direction based on viewport space.
    const rect = menu.getBoundingClientRect();
    const belowSpace = window.innerHeight - rect.top;
    const need = Math.min(rect.height || 320, Math.round(window.innerHeight * 0.6));
    if (belowSpace < need) {
      menu.classList.add('open-up');
    }
  } else {
    menu.classList.remove('open', 'open-up');
  }
  const close = (e) => {
    if (!menu.contains(e.target) && !e.target.closest('.fd-col-toggle-wrap')) {
      menu.classList.remove('open', 'open-up');
      document.removeEventListener('click', close);
    }
  };
  if (menu.classList.contains('open')) {
    setTimeout(() => document.addEventListener('click', close), 0);
  }
}