import { useState, useEffect, useCallback, useRef } from "react";
import { useQueryClient } from "@tanstack/react-query";
import apiFetch from "../utils/api";
import { isHoliday } from "../../utils/czech-holidays";
import {
  calcProjectMinutesTotal,
  calcFormWorkMinutes,
  calculateWorkMinutes,
  combineDatetime,
  getDatePart,
  getTimePart,
  formatDate,
  formatMinutes,
  getLeaveTypeName,
  formatTimeOrDatetimePrint,
  calculateWorkMinutesPrint,
  isWeekendDate,
  getDaysInMonth,
  shiftDateForMonth,
  formatHoursDecimal,
  calculateNightMinutes,
  normalizeDateStr,
  holidaysInMonth,
  calculateFreeHolidayHours,
  getCzechWeekday,
} from "../utils/attendanceHelpers";
import type {
  ShiftFormData,
  ProjectLog,
  Project,
  User,
} from "../components/ShiftFormModal";

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

interface AlertContext {
  alert: { success: (msg: string) => void; error: (msg: string) => void };
}

export interface AttendanceRecord {
  id: number;
  user_id: number;
  shift_date: string;
  leave_type?: string;
  leave_hours?: number;
  arrival_time?: string | null;
  departure_time?: string | null;
  break_start?: string | null;
  break_end?: string | null;
  notes?: string | null;
  project_id?: number | null;
  project_name?: string;
  project_logs?: Array<{
    id?: number;
    project_id: number;
    project_name?: string;
    started_at?: string;
    ended_at?: string | null;
    hours?: string | number | null;
    minutes?: string | number | null;
  }>;
  user_name?: string;
  users?: {
    id: number;
    first_name: string;
    last_name: string;
    username: string;
  };
}

interface ApiUser {
  id: number;
  first_name: string;
  last_name: string;
  username: string;
}

interface UserTotal {
  name: string;
  minutes: number;
  working: boolean;
  vacation_hours: number;
  sick_hours: number;
  unpaid_hours: number;
  overtime: number;
  missing: number;
  fund: number | null;
  business_days: number;
  worked_hours: number;
  covered: number;
  // mzda-style summary fields (set by computeUserTotals)
  worked_hours_raw?: number;
  odpracovano?: number;
  vcetne_svatku?: number;
  prescas?: number;
  svatek?: number;
  weekend_hours?: number;
  night_minutes?: number;
  worked_holiday_hours?: number; // sum of hours worked ON a holiday
  records?: AttendanceRecord[];
}

interface LeaveBalance {
  vacation_total: number;
  vacation_remaining: number;
}

interface BulkForm {
  month: string;
  user_ids: string[];
  arrival_time: string;
  departure_time: string;
  break_start_time: string;
  break_end_time: string;
}

interface AttendanceData {
  records: AttendanceRecord[];
  users: User[];
  user_totals: Record<string, UserTotal>;
  leave_balances: Record<string, LeaveBalance>;
}

interface DeleteConfirmState {
  show: boolean;
  record: AttendanceRecord | null;
}

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

const API_BASE = "/api/admin";

/**
 * Compute per-user totals from raw attendance records.
 * This replaces the server-side `user_totals` that the PHP backend returned.
 *
 * Adds mzda-style summary fields (odpracovano, vcetne_svatku, prescas, svatek,
 * weekend_hours, night_minutes) and a fund computed as
 * (rawBizDays + holidayCount) × 8 — holidays count as work days for fund
 * purposes, per the mzda.pdf model.
 */
function computeUserTotals(
  records: AttendanceRecord[],
  userMap: Map<number, string>,
  month: string,
): Record<string, UserTotal> {
  const totals: Record<string, UserTotal> = {};
  const recordsByUser = new Map<number, AttendanceRecord[]>();

  for (const rec of records) {
    const uid = String(rec.user_id);
    if (!totals[uid]) {
      const name =
        userMap.get(rec.user_id) ??
        (rec.users
          ? `${rec.users.first_name} ${rec.users.last_name}`.trim() ||
            rec.users.username
          : `User #${rec.user_id}`);
      totals[uid] = {
        name,
        minutes: 0,
        working: false,
        vacation_hours: 0,
        sick_hours: 0,
        unpaid_hours: 0,
        overtime: 0,
        missing: 0,
        fund: null,
        business_days: 0,
        worked_hours: 0,
        covered: 0,
        records: [],
      };
    }

    const t = totals[uid];
    t.records!.push(rec);
    if (!recordsByUser.has(rec.user_id)) recordsByUser.set(rec.user_id, []);
    recordsByUser.get(rec.user_id)!.push(rec);

    const leaveType = rec.leave_type || "work";

    if (leaveType === "work") {
      // Only work records contribute to "minutes" (matching PHP calculateUserTotals)
      t.minutes += calculateWorkMinutes({
        ...rec,
        notes: rec.notes ?? undefined,
      });
    } else {
      const leaveHours = Number(rec.leave_hours) || 8;
      switch (leaveType) {
        case "vacation":
          t.vacation_hours += leaveHours;
          break;
        case "sick":
          t.sick_hours += leaveHours;
          break;
        case "unpaid":
          t.unpaid_hours += leaveHours;
          break;
        // "holiday" leave_type is no longer selectable in the UI — it is
        // auto-computed from Czech public holidays (see freeHolidayHours
        // below). Old records may still exist in the DB; treat them as a
        // paid non-work entry and skip so they don't double-count with
        // the auto-detected svátek.
      }
    }

    // Track if user is currently working (has arrival but no departure)
    if (rec.arrival_time && !rec.departure_time) {
      t.working = true;
    }
  }

  // Add fund data per user (matching PHP addFundDataToUserTotals)
  const [yearStr, monthStr] = month.split("-");
  const yr = parseInt(yearStr, 10);
  const mo = parseInt(monthStr, 10) - 1;

  // Count Mon-Fri business days in month (INCLUDING holidays).
  // The fund is rawBizDays × 8 — holidays stay in the count so the
  // user is paid for them via the fund. Svátek (free holiday hours) is
  // computed separately as "8h per holiday the user did not work".
  let rawBizDays = 0;
  const cur = new Date(yr, mo, 1);
  while (cur.getMonth() === mo) {
    const dow = cur.getDay();
    if (dow !== 0 && dow !== 6) rawBizDays++;
    cur.setDate(cur.getDate() + 1);
  }

  const holidayDates = holidaysInMonth(yr, mo + 1);
  const holidayCount = holidayDates.length;

  for (const uid of Object.keys(totals)) {
    const t = totals[uid];
    const userRecords = recordsByUser.get(Number(uid)) || [];

    // Odpracováno: floor(worked / 8) × 8 — round to whole days, drop remainder.
    const workedHoursRaw = t.minutes / 60;
    const odpracovano = Math.floor(workedHoursRaw / 8) * 8;

    // So/Ne: sum of worked hours on Sat/Sun (raw, not rounded).
    const weekendHours = userRecords
      .filter(
        (r) =>
          (r.leave_type || "work") === "work" && isWeekendDate(r.shift_date),
      )
      .reduce((sum, r) => sum + calculateWorkMinutesPrint(r) / 60, 0);

    // Práce v noci: minutes in 22:00-06:00 across all work records.
    const nightMinutes = userRecords
      .filter((r) => (r.leave_type || "work") === "work")
      .reduce((sum, r) => sum + calculateNightMinutes(r), 0);

    // Svátek (free): 8h per holiday the user did not work.
    const freeHolidayHours = calculateFreeHolidayHours(
      userRecords,
      holidayDates,
    );

    // Worked holidays: holidays the user DID work (counted toward
    // Odpracováno, but their 8h must not flow into Přesčas).
    const workedDatesSet = new Set(
      userRecords
        .filter((r) => (r.leave_type || "work") === "work")
        .map((r) => normalizeDateStr(r.shift_date))
        .filter(Boolean),
    );
    let workedHolidayCount = 0;
    let workedHolidayHours = 0;
    for (const hd of holidayDates) {
      if (workedDatesSet.has(hd)) workedHolidayCount++;
    }
    // Sum the actual hours worked on holiday dates (for the KPI card's
    // "fond used" total = real_work + vacation + worked_holiday + free_holiday).
    for (const r of userRecords) {
      if ((r.leave_type || "work") !== "work") continue;
      const d = normalizeDateStr(r.shift_date);
      if (d && holidayDates.includes(d)) {
        workedHolidayHours += calculateWorkMinutesPrint(r) / 60;
      }
    }

    // Fund = Mon-Fri × 8h. Holidays count as work days (already in rawBizDays).
    const fund = rawBizDays * 8;

    // Včetně svátků a přesčasů (mzda formula):
    //   odpracovano + vacation + remainder − min(freeHols, workedHols × 8)
    //
    // Two-way logic matching the print:
    //   • If the user worked at least one holiday, that worked holiday's
    //     8h is in Odpracováno and shouldn't also count as Přesčas. We
    //     subtract one 8h per worked holiday (capped at the total free
    //     holiday hours — can't go negative).
    //   • If the user did NOT work any holiday, no adjustment is needed:
    //     all unworked holidays are already paid via the Svátek line and
    //     don't flow into Přesčas, so vcetne_svatku just equals the
    //     work + vacation + remainder.
    const remainder = workedHoursRaw - odpracovano;
    const holidayAdj = Math.min(freeHolidayHours, workedHolidayCount * 8);
    const vcetneSv = odpracovano - holidayAdj + t.vacation_hours + remainder;

    // Přesčas = vcetneSv − odpracovano (derived).
    const prescas = vcetneSv - odpracovano;

    // Legacy fields (kept so existing UI doesn't break).
    const workedHours = Math.round(workedHoursRaw * 10) / 10;
    const covered = Math.round((workedHours + t.vacation_hours) * 10) / 10;

    t.fund = fund;
    t.business_days = rawBizDays;
    t.worked_hours = workedHours;
    t.covered = covered;
    t.missing = Math.max(0, Math.round((fund - covered) * 10) / 10);
    t.overtime = Math.max(0, Math.round((covered - fund) * 10) / 10);

    t.worked_hours_raw = workedHoursRaw;
    t.odpracovano = odpracovano;
    t.vcetne_svatku = vcetneSv;
    t.prescas = prescas;
    t.svatek = freeHolidayHours;
    t.weekend_hours = weekendHours;
    t.night_minutes = nightMinutes;
    t.worked_holiday_hours = workedHolidayHours;
  }

  return totals;
}

// ---------------------------------------------------------------------------
// Print helpers
// ---------------------------------------------------------------------------

function escapeHtml(str: string): string {
  return str
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#039;");
}

function buildProjectLogsHtml(record: Record<string, any>): string {
  if (record.project_logs && record.project_logs.length > 0) {
    return record.project_logs
      .map((log: any) => {
        let h: number, m: number;
        if (log.hours !== null && log.hours !== undefined) {
          h = parseInt(log.hours) || 0;
          m = parseInt(log.minutes) || 0;
        } else if (log.started_at && log.ended_at) {
          const mins = Math.max(
            0,
            Math.floor(
              (new Date(log.ended_at).getTime() -
                new Date(log.started_at).getTime()) /
                60000,
            ),
          );
          h = Math.floor(mins / 60);
          m = mins % 60;
        } else {
          h = 0;
          m = 0;
        }
        return `<div>${escapeHtml(log.project_name || `#${log.project_id}`)} (${h}:${String(m).padStart(2, "0")}h)</div>`;
      })
      .join("");
  }
  return escapeHtml(record.project_name || "—");
}

function buildUserSectionHtml(
  userId: string,
  userData: Record<string, any>,
  printData: Record<string, any>,
): string {
  // Build a date-keyed lookup of the user's records.
  const recordsByDate = new Map<string, Record<string, any>>();
  for (const r of userData.records || []) {
    recordsByDate.set(normalizeDateStr(r.shift_date), r);
  }

  // Iterate one row per day of the month.
  const [yearStr, monthStr] = printData.month.split("-");
  const yr = parseInt(yearStr, 10);
  const mo = parseInt(monthStr, 10);
  const daysInMonth = getDaysInMonth(yr, mo);

  const userRecords = (userData.records || []) as Record<string, any>[];

  const rows: string[] = [];
  for (let d = 1; d <= daysInMonth; d++) {
    const dateStr = shiftDateForMonth(yr, mo, d);
    const record = recordsByDate.get(dateStr);
    const weekend = isWeekendDate(dateStr);
    const holiday = isHoliday(dateStr);
    const leaveType = (record?.leave_type as string) || "work";
    const rowClasses = [
      weekend && "weekend",
      holiday && "holiday",
      leaveType === "vacation" && "vacation",
    ]
      .filter(Boolean)
      .join(" ");

    if (!record) {
      rows.push(`<tr class="${rowClasses}">
        <td>${escapeHtml(formatDate(dateStr))}</td>
        <td>${escapeHtml(getCzechWeekday(dateStr))}</td>
        <td>—</td>
        <td class="text-center">—</td>
        <td class="text-center">—</td>
        <td class="text-center">—</td>
        <td class="text-center">—</td>
        <td></td>
        <td></td>
      </tr>`);
      continue;
    }

    const isLeave = leaveType !== "work";
    const workMinutes = calculateWorkMinutesPrint(record);
    const hours = Math.floor(workMinutes / 60);
    const mins = workMinutes % 60;
    const breakCell =
      isLeave || !record.break_start || !record.break_end
        ? "—"
        : `${escapeHtml(formatTimeOrDatetimePrint(record.break_start, record.shift_date))} - ${escapeHtml(formatTimeOrDatetimePrint(record.break_end, record.shift_date))}`;

    let hoursCell: string;
    if (workMinutes > 0 && !isLeave) {
      hoursCell = `${hours}:${String(mins).padStart(2, "0")} (${formatHoursDecimal(workMinutes)})`;
    } else if (isLeave) {
      hoursCell = formatHoursDecimal((Number(record.leave_hours) || 8) * 60);
    } else {
      hoursCell = "—";
    }

    rows.push(`<tr class="${rowClasses}">
      <td>${escapeHtml(formatDate(dateStr))}</td>
      <td>${escapeHtml(getCzechWeekday(dateStr))}</td>
      <td>${escapeHtml(getLeaveTypeName(leaveType))}</td>
      <td class="text-center">${isLeave ? "—" : escapeHtml(formatTimeOrDatetimePrint(record.arrival_time, record.shift_date))}</td>
      <td class="text-center">${breakCell}</td>
      <td class="text-center">${isLeave ? "—" : escapeHtml(formatTimeOrDatetimePrint(record.departure_time, record.shift_date))}</td>
      <td class="text-center">${hoursCell}</td>
      <td style="font-size:8px">${buildProjectLogsHtml(record)}</td>
      <td>${escapeHtml(record.notes || "")}</td>
    </tr>`);
  }

  // ----- mzda-style summary numbers (computed here, not from userData,
  // because the backend's getPrintData only returns the legacy fields). -----

  // Worked minutes: sum of calculateWorkMinutesPrint across work records.
  let workedMinutes = 0;
  let weekendHoursRaw = 0;
  let nightMinutes = 0;
  for (const r of userRecords) {
    const leaveType = r.leave_type || "work";
    if (leaveType !== "work") continue;
    const m = calculateWorkMinutesPrint(r);
    workedMinutes += m;
    if (isWeekendDate(r.shift_date)) {
      weekendHoursRaw += m / 60;
    }
    nightMinutes += calculateNightMinutes(r);
  }

  // Sum of vacation/sick/unpaid hours (in hours, not minutes). The
  // "holiday" leave_type is auto-computed from Czech holidays further down
  // and no longer selectable in the UI, so it's deliberately omitted here.
  let vacationHours = 0,
    sickHours = 0;
  for (const r of userRecords) {
    const lt = r.leave_type || "work";
    if (lt === "work") continue;
    const h = Number(r.leave_hours) || 8;
    if (lt === "vacation") vacationHours += h;
    else if (lt === "sick") sickHours += h;
  }

  // Odpracováno: floor(worked / 8) × 8.
  const workedHoursRaw = workedMinutes / 60;
  const odpracovano = Math.floor(workedHoursRaw / 8) * 8;

  // Free Svátek: 8h per holiday date the user did not work.
  const holidayDates = holidaysInMonth(yr, mo);
  const holidayCount = holidayDates.length;
  const workedDates = new Set(
    userRecords
      .filter((r) => (r.leave_type || "work") === "work")
      .map((r) => normalizeDateStr(r.shift_date))
      .filter(Boolean),
  );
  let freeHolidayHours = 0;
  let workedHolidayCount = 0;
  for (const hd of holidayDates) {
    if (workedDates.has(hd)) workedHolidayCount++;
    else freeHolidayHours += 8;
  }

  // Fund: count Mon-Fri (incl. holidays) × 8h.
  let rawBizDays = 0;
  const cur = new Date(yr, mo - 1, 1);
  while (cur.getMonth() === mo - 1) {
    const dow = cur.getDay();
    if (dow !== 0 && dow !== 6) rawBizDays++;
    cur.setDate(cur.getDate() + 1);
  }
  const businessDays = rawBizDays;
  const fund = businessDays * 8;

  // Včetně svátků a přesčasů: floor(worked/8)*8 − worked_holiday*8 + vacation + remainder.
  // (A worked holiday counts toward Odpracováno, but its 8h should not flow
  //  into Přesčas — so we subtract it here. Unworked holidays are paid via
  //  the fund and don't affect this line.)
  const remainder = workedHoursRaw - odpracovano;
  const vcetneSv =
    odpracovano - workedHolidayCount * 8 + vacationHours + remainder;

  // Přesčas = #2 - #1.
  const prescas = vcetneSv - odpracovano;

  const summaryHtml = `<div class="user-summary">
    <div class="summary-row">
      <span class="summary-label">Odpracováno:</span>
      <span class="summary-value">${formatHoursDecimal(odpracovano * 60)}h</span>
    </div>
    <div class="summary-row">
      <span class="summary-label">Odpracováno včetně svátků a přesčasů:</span>
      <span class="summary-value">${formatHoursDecimal(vcetneSv * 60)}h</span>
    </div>
    <div class="summary-row">
      <span class="summary-label">Dovolená:</span>
      <span class="summary-value">${formatHoursDecimal(vacationHours * 60)}h</span>
    </div>
    <div class="summary-row">
      <span class="summary-label">Přesčas:</span>
      <span class="summary-value">${formatHoursDecimal(prescas * 60)}h</span>
    </div>
    <div class="summary-row">
      <span class="summary-label">Svátek:</span>
      <span class="summary-value">${formatHoursDecimal(freeHolidayHours * 60)}h</span>
    </div>
    <div class="summary-row">
      <span class="summary-label">So/Ne:</span>
      <span class="summary-value">${formatHoursDecimal(weekendHoursRaw * 60)}h</span>
    </div>
    <div class="summary-row">
      <span class="summary-label">Práce v noci:</span>
      <span class="summary-value">${formatHoursDecimal(nightMinutes)}h</span>
    </div>
    <div class="summary-row summary-fund">
      <span class="summary-label">Fond měsíce:</span>
      <span class="summary-value">${formatHoursDecimal(fund * 60)}h (${businessDays} dnů)</span>
    </div>
    <div class="legend">
      <span class="legend-item"><span class="legend-swatch weekend"></span>Víkend (So/Ne)</span>
      <span class="legend-item"><span class="legend-swatch holiday"></span>Svátek</span>
      <span class="legend-item"><span class="legend-swatch weekend-holiday"></span>Víkend + svátek</span>
      <span class="legend-item"><span class="legend-swatch vacation"></span>Dovolená</span>
    </div>
  </div>`;

  return `<div class="user-section">
    <div class="user-header">
      <h3>${escapeHtml(userData.name)}</h3>
      <span class="total">Odpracováno: ${escapeHtml(formatMinutes(userData.minutes))} h</span>
    </div>
    <table>
      <thead><tr>
        <th style="width:55px">Datum</th>
        <th style="width:55px">Den</th>
        <th style="width:60px">Typ</th>
        <th class="text-center" style="width:55px">Příchod</th>
        <th class="text-center" style="width:80px">Pauza</th>
        <th class="text-center" style="width:55px">Odchod</th>
        <th class="text-center" style="width:85px">Hodiny</th>
        <th>Projekty</th>
        <th>Poznámka</th>
      </tr></thead>
      <tbody>${rows.join("")}</tbody>
    </table>
    ${summaryHtml}
  </div>`;
}

function buildPrintHtml(
  pData: Record<string, any>,
  userSections: string,
  emptyMsg: string,
  filterNote: string,
  companyName: string,
): string {
  return `<!DOCTYPE html>
<html lang="cs">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Docházka - ${escapeHtml(pData.month_name)}</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
      -webkit-print-color-adjust: exact;
      print-color-adjust: exact;
    }
    body {
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      font-size: 11px; line-height: 1.4; color: #000; background: #fff; padding: 15mm;
    }
    .print-header {
      display: flex; justify-content: space-between; align-items: flex-start;
      margin-bottom: 20px; padding-bottom: 15px; border-bottom: 2px solid #333;
    }
    .print-header-left { display: flex; align-items: center; gap: 12px; }
    .print-logo { height: 40px; width: auto; }
    .print-header-text { text-align: left; }
    .print-header-right { text-align: right; }
    .print-header h1 { font-size: 18px; font-weight: 700; margin-bottom: 3px; }
    .print-header .company { font-size: 11px; color: #666; }
    .print-header .period { font-size: 13px; font-weight: 600; color: #333; margin-bottom: 2px; }
    .print-header .filters { font-size: 10px; color: #666; }
    .print-header .generated { font-size: 9px; color: #888; margin-top: 5px; }
    .user-section { margin-bottom: 25px; page-break-inside: avoid; }
    .user-header {
      background: #f5f5f5; border: 1px solid #ddd; padding: 10px 15px;
      margin-bottom: 10px; display: flex; justify-content: space-between; align-items: center;
    }
    .user-header h3 { font-size: 13px; font-weight: 600; }
    .user-header .total { font-size: 12px; font-weight: 600; }
    .user-section table { width: 100%; border-collapse: collapse; margin-bottom: 15px; }
    .user-section th, .user-section td { border: 1px solid #333; padding: 6px 8px; text-align: left; }
    .user-section th { background: #333; color: #fff; font-weight: 600; font-size: 10px; text-transform: uppercase; }
    .user-section td { font-size: 10px; }
    .user-section tr:nth-child(even) { background: #f9f9f9; }
    /* Weekend and holiday row highlighting — must come after the
       :nth-child(even) rule and use higher specificity (tr.X td) to win. */
    .user-section tr.weekend td { background: #fde68a; }
    .user-section tr.holiday td { background: #bbf7d0; }
    .user-section tr.weekend.holiday td { background: #fcd34d; }
    .user-section tr.vacation td { background: #bfdbfe; }
    .text-center { text-align: center; }
    .text-right { text-align: right; }
    .leave-badge { display: inline-block; padding: 2px 6px; border-radius: 3px; font-size: 9px; font-weight: 500; }
    .badge-vacation { background: #dbeafe; color: #1d4ed8; }
    .badge-sick { background: #fee2e2; color: #dc2626; }
    .badge-holiday { background: #dcfce7; color: #16a34a; }
    .badge-unpaid { background: #f3f4f6; color: #6b7280; }
    .badge-overtime { background: #fef3c7; color: #d97706; }
    .user-summary {
      margin-top: 10px; padding: 10px 15px;
      background: #f9f9f9; border: 1px solid #ddd; font-size: 10px;
    }
    .user-summary .summary-row {
      display: flex; justify-content: space-between; padding: 2px 0;
    }
    .user-summary .summary-label { color: #555; }
    .user-summary .summary-value { font-weight: 600; }
    .user-summary .summary-fund {
      border-top: 1px solid #ddd; margin-top: 4px; padding-top: 6px;
    }
    .legend {
      display: flex; flex-wrap: wrap; gap: 14px;
      margin-top: 8px; padding-top: 6px;
      border-top: 1px dashed #ddd;
      font-size: 9px; color: #555;
    }
    .legend-item { display: inline-flex; align-items: center; gap: 5px; }
    .legend-swatch {
      display: inline-block; width: 12px; height: 12px;
      border: 1px solid #333; vertical-align: middle;
    }
    .legend-swatch.weekend { background: #fde68a; }
    .legend-swatch.holiday { background: #bbf7d0; }
    .legend-swatch.weekend-holiday { background: #fcd34d; }
    .legend-swatch.vacation { background: #bfdbfe; }
    .print-wrapper-table { width: 100%; border-collapse: collapse; border: none; }
    .print-wrapper-table > thead > tr > td,
    .print-wrapper-table > tbody > tr > td { padding: 0; border: none; background: none; }
    @media print {
      body { padding: 0; margin: 0; }
      @page { size: A4 portrait; margin: 10mm; }
      .user-section { page-break-inside: avoid; }
    }
  </style>
</head>
<body>
  <table class="print-wrapper-table">
    <thead><tr><td>
      <div class="print-header">
        <div class="print-header-left">
          <img src="/api/admin/company-settings/logo?variant=light" alt="" class="print-logo" />
          <div class="print-header-text">
            <h1>EVIDENCE DOCHÁZKY</h1>
            <div class="company">${escapeHtml(companyName)}</div>
          </div>
        </div>
        <div class="print-header-right">
          <div class="period">${escapeHtml(pData.month_name)}</div>
          ${filterNote}
          <div class="generated">Vygenerováno: ${new Date().toLocaleString("cs-CZ")}</div>
        </div>
      </div>
    </td></tr></thead>
    <tbody><tr><td>
      ${userSections}
      ${emptyMsg}
    </td></tr></tbody>
  </table>
</body>
</html>`;
}

// ---------------------------------------------------------------------------
// Hook
// ---------------------------------------------------------------------------

export default function useAttendanceAdmin({ alert }: AlertContext) {
  const queryClient = useQueryClient();
  // ---- Core state ----
  const [loading, setLoading] = useState(true);
  const [month, setMonth] = useState(() => {
    const now = new Date();
    return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
  });
  const [filterUserId, setFilterUserId] = useState("");
  const [data, setData] = useState<AttendanceData>({
    records: [],
    users: [],
    user_totals: {},
    leave_balances: {},
  });

  // ---- Bulk modal ----
  const [showBulkModal, setShowBulkModal] = useState(false);
  const [bulkSubmitting, setBulkSubmitting] = useState(false);
  const [bulkForm, setBulkForm] = useState<BulkForm>({
    month: "",
    user_ids: [],
    arrival_time: "08:00",
    departure_time: "16:30",
    break_start_time: "12:00",
    break_end_time: "12:30",
  });

  // ---- Create modal ----
  const [showCreateModal, setShowCreateModal] = useState(false);
  const now = new Date();
  const today = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
  const [createForm, setCreateForm] = useState<ShiftFormData>({
    user_id: "",
    shift_date: today,
    leave_type: "work",
    leave_hours: 8,
    arrival_date: today,
    arrival_time: "",
    break_start_date: today,
    break_start_time: "",
    break_end_date: today,
    break_end_time: "",
    departure_date: today,
    departure_time: "",
    notes: "",
  });

  // ---- Edit modal ----
  const [showEditModal, setShowEditModal] = useState(false);
  const [editingRecord, setEditingRecord] = useState<AttendanceRecord | null>(
    null,
  );
  const [editForm, setEditForm] = useState<ShiftFormData>({
    user_id: "",
    shift_date: "",
    leave_type: "work",
    leave_hours: 8,
    arrival_date: "",
    arrival_time: "",
    break_start_date: "",
    break_start_time: "",
    break_end_date: "",
    break_end_time: "",
    departure_date: "",
    departure_time: "",
    notes: "",
  });

  // ---- Delete ----
  const [deleteConfirm, setDeleteConfirm] = useState<DeleteConfirmState>({
    show: false,
    record: null,
  });

  // ---- Projects ----
  const [projectList, setProjectList] = useState<Project[]>([]);
  const [createProjectLogs, setCreateProjectLogs] = useState<ProjectLog[]>([]);
  const [editProjectLogs, setEditProjectLogs] = useState<ProjectLog[]>([]);

  // ---- Print ref (kept for API compat) ----
  const printRef = useRef<HTMLDivElement | null>(null);

  // ---- Ref to hold full user list for user_totals computation ----
  const usersRef = useRef<Map<number, string>>(new Map());

  // =========================================================================
  // Load projects once
  // =========================================================================
  useEffect(() => {
    const loadProjects = async () => {
      try {
        const response = await apiFetch(
          `${API_BASE}/attendance?action=projects`,
        );
        const result = await response.json();
        if (result.success)
          setProjectList(result.data?.projects ?? result.data ?? []);
      } catch {
        /* silent */
      }
    };
    loadProjects();
  }, []);

  // =========================================================================
  // Load users once
  // =========================================================================
  useEffect(() => {
    const loadUsers = async () => {
      try {
        const response = await apiFetch(
          `${API_BASE}/attendance?action=attendance_users`,
        );
        const result = await response.json();
        if (result.success) {
          const apiUsers: ApiUser[] = result.data;
          const mapped: User[] = apiUsers.map((u) => ({
            id: u.id,
            name: `${u.first_name} ${u.last_name}`.trim() || u.username,
          }));
          const nameMap = new Map<number, string>();
          for (const u of mapped) nameMap.set(u.id as number, u.name);
          usersRef.current = nameMap;

          setData((prev) => ({ ...prev, users: mapped }));
        }
      } catch {
        /* silent */
      }
    };
    loadUsers();
  }, []);

  // =========================================================================
  // Fetch attendance records + leave balances
  // =========================================================================
  const fetchData = useCallback(
    async (showLoading = true) => {
      if (showLoading) setLoading(true);
      try {
        const [yearStr, monthStr] = month.split("-");

        // The KPI cards + summary totals are computed from THIS fetch — it
        // must cover the complete month (the server allows limit up to 2000
        // for exactly this view; 2000 ≈ 64 employees × 31 days).
        let recordsUrl = `${API_BASE}/attendance?year=${yearStr}&month=${monthStr}&limit=2000`;
        if (filterUserId) recordsUrl += `&user_id=${filterUserId}`;

        const [recordsResponse, balancesResponse] = await Promise.all([
          apiFetch(recordsUrl),
          apiFetch(`${API_BASE}/attendance?action=balances&year=${yearStr}`),
        ]);

        if (recordsResponse.status === 401) return;

        const recordsResult = await recordsResponse.json();
        const balancesResult = await balancesResponse.json();

        const records: AttendanceRecord[] = recordsResult.success
          ? Array.isArray(recordsResult.data)
            ? recordsResult.data
            : []
          : [];
        // balancesResult.data is { users: [...], balances: { uid: {...} } }
        const balancesObj = balancesResult.success ? balancesResult.data : {};
        const leaveBalances: Record<string, LeaveBalance> =
          balancesObj?.balances ?? balancesObj ?? {};

        const userTotals = computeUserTotals(records, usersRef.current, month);

        setData((prev) => ({
          ...prev,
          records,
          user_totals: userTotals,
          leave_balances: leaveBalances,
        }));
      } catch {
        alert.error("Nepodařilo se načíst data");
      } finally {
        if (showLoading) setLoading(false);
      }
    },
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [month, filterUserId],
  );

  // Initial load with skeleton, filter changes without skeleton
  const initialLoadDone = useRef(false);
  useEffect(() => {
    if (!initialLoadDone.current) {
      initialLoadDone.current = true;
      fetchData(true);
    } else {
      fetchData(false);
    }
  }, [fetchData]);

  // =========================================================================
  // Validation helper
  // =========================================================================
  const validateProjectLogs = (
    logs: ProjectLog[],
    formData: ShiftFormData,
  ): boolean => {
    const totalWork = calcFormWorkMinutes(formData);
    const totalProject = calcProjectMinutesTotal(
      logs.map((l) => ({
        ...l,
        project_id:
          l.project_id !== "" && l.project_id != null
            ? Number(l.project_id)
            : undefined,
      })),
    );
    if (totalWork > 0 && totalProject !== totalWork) {
      const wH = Math.floor(totalWork / 60);
      const wM = totalWork % 60;
      const pH = Math.floor(totalProject / 60);
      const pM = totalProject % 60;
      alert.error(
        `Součet hodin projektů (${pH}h ${pM}m) neodpovídá odpracovanému času (${wH}h ${wM}m)`,
      );
      return false;
    }
    return true;
  };

  // =========================================================================
  // Create modal
  // =========================================================================
  const openCreateModal = () => {
    const d = new Date();
    const todayDate = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
    setCreateForm({
      user_id: "",
      shift_date: todayDate,
      leave_type: "work",
      leave_hours: 8,
      arrival_date: todayDate,
      arrival_time: "",
      break_start_date: todayDate,
      break_start_time: "",
      break_end_date: todayDate,
      break_end_time: "",
      departure_date: todayDate,
      departure_time: "",
      notes: "",
    });
    setCreateProjectLogs([]);
    setShowCreateModal(true);
  };

  const handleCreateShiftDateChange = (newDate: string) => {
    setCreateForm((prev) => ({
      ...prev,
      shift_date: newDate,
      arrival_date: newDate,
      break_start_date: newDate,
      break_end_date: newDate,
      departure_date: newDate,
    }));
  };

  const handleCreateSubmit = async () => {
    if (!createForm.user_id || !createForm.shift_date) {
      alert.error("Vyplňte zaměstnance a datum směny");
      return;
    }

    const filteredCreateLogs = createProjectLogs.filter((l) => l.project_id);
    if (filteredCreateLogs.length > 0 && createForm.leave_type === "work") {
      if (!validateProjectLogs(filteredCreateLogs, createForm)) return;
    }

    try {
      const isLeave = createForm.leave_type !== "work";
      const payload: Record<string, unknown> = {
        user_id: Number(createForm.user_id),
        shift_date: createForm.shift_date,
        leave_type: createForm.leave_type,
        notes: createForm.notes || null,
      };

      if (isLeave) {
        payload.leave_hours = createForm.leave_hours || 8;
        payload.arrival_time = null;
        payload.departure_time = null;
        payload.break_start = null;
        payload.break_end = null;
      } else {
        payload.arrival_time = combineDatetime(
          createForm.arrival_date,
          createForm.arrival_time,
        );
        payload.departure_time = combineDatetime(
          createForm.departure_date,
          createForm.departure_time,
        );
        payload.break_start = combineDatetime(
          createForm.break_start_date,
          createForm.break_start_time,
        );
        payload.break_end = combineDatetime(
          createForm.break_end_date,
          createForm.break_end_time,
        );
      }

      if (filteredCreateLogs.length > 0 && createForm.leave_type === "work") {
        payload.project_logs = filteredCreateLogs;
      }

      const response = await apiFetch(`${API_BASE}/attendance`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(payload),
      });

      const result = await response.json();

      if (result.success) {
        setShowCreateModal(false);
        queryClient.invalidateQueries({ queryKey: ["attendance"] });
        // The dashboard embeds attendance (Přítomní dnes / Docházka dnes /
        // punch-button state) — without this it serves a stale cache for up
        // to its staleTime after records change here.
        queryClient.invalidateQueries({ queryKey: ["dashboard"] });
        await fetchData(false);
        await new Promise((resolve) => setTimeout(resolve, 300));
        alert.success(
          result.message || result.data?.message || "Záznam vytvořen",
        );
      } else {
        alert.error(result.error || "Nepodařilo se vytvořit záznam");
      }
    } catch {
      alert.error("Chyba připojení");
    }
  };

  // =========================================================================
  // Bulk modal
  // =========================================================================
  const openBulkModal = () => {
    setBulkForm({
      month,
      user_ids: data.users.map((u) => String(u.id)),
      arrival_time: "08:00",
      departure_time: "16:30",
      break_start_time: "12:00",
      break_end_time: "12:30",
    });
    setShowBulkModal(true);
  };

  const toggleBulkUser = (userId: number | string) => {
    const uid = String(userId);
    setBulkForm((prev) => ({
      ...prev,
      user_ids: prev.user_ids.includes(uid)
        ? prev.user_ids.filter((u) => u !== uid)
        : [...prev.user_ids, uid],
    }));
  };

  const toggleAllBulkUsers = () => {
    const allIds = data.users.map((u) => String(u.id));
    setBulkForm((prev) => ({
      ...prev,
      user_ids: prev.user_ids.length === allIds.length ? [] : allIds,
    }));
  };

  const handleBulkSubmit = async () => {
    if (!bulkForm.month) {
      alert.error("Vyberte měsíc");
      return;
    }
    if (bulkForm.user_ids.length === 0) {
      alert.error("Vyberte alespoň jednoho zaměstnance");
      return;
    }

    setBulkSubmitting(true);
    try {
      const response = await apiFetch(
        `${API_BASE}/attendance?action=bulk_attendance`,
        {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(bulkForm),
        },
      );

      const result = await response.json();

      if (result.success) {
        setShowBulkModal(false);
        queryClient.invalidateQueries({ queryKey: ["attendance"] });
        // The dashboard embeds attendance (Přítomní dnes / Docházka dnes /
        // punch-button state) — without this it serves a stale cache for up
        // to its staleTime after records change here.
        queryClient.invalidateQueries({ queryKey: ["dashboard"] });
        await fetchData(false);
        await new Promise((resolve) => setTimeout(resolve, 300));
        alert.success(
          result.message || result.data?.message || "Záznamy vytvořeny",
        );
      } else {
        alert.error(result.error || "Nepodařilo se vytvořit záznamy");
      }
    } catch {
      alert.error("Chyba připojení");
    } finally {
      setBulkSubmitting(false);
    }
  };

  // =========================================================================
  // Edit modal
  // =========================================================================
  const openEditModal = (record: AttendanceRecord) => {
    // Enrich record with user_name for the modal subtitle
    const userName = record.users
      ? `${record.users.first_name} ${record.users.last_name}`.trim() ||
        record.users.username
      : ((record as unknown as Record<string, unknown>).user_name as string) ||
        `User #${record.user_id}`;
    const enriched = { ...record, user_name: userName };
    setEditingRecord(enriched);

    const shiftDate = getDatePart(record.shift_date) || record.shift_date;
    setEditForm({
      user_id: String(record.user_id),
      shift_date: shiftDate,
      leave_type: record.leave_type || "work",
      leave_hours: Number(record.leave_hours) || 8,
      arrival_date: getDatePart(record.arrival_time) || shiftDate,
      arrival_time: getTimePart(record.arrival_time),
      break_start_date: getDatePart(record.break_start) || shiftDate,
      break_start_time: getTimePart(record.break_start),
      break_end_date: getDatePart(record.break_end) || shiftDate,
      break_end_time: getTimePart(record.break_end),
      departure_date: getDatePart(record.departure_time) || shiftDate,
      departure_time: getTimePart(record.departure_time),
      notes: record.notes || "",
    });

    const logs: ProjectLog[] = (record.project_logs || []).map((l) => {
      if (l.hours !== null && l.hours !== undefined) {
        return {
          project_id: String(l.project_id),
          hours: String(l.hours),
          minutes: String(l.minutes || 0),
        };
      }
      if (l.started_at && l.ended_at) {
        const mins = Math.max(
          0,
          Math.floor(
            (new Date(l.ended_at).getTime() -
              new Date(l.started_at).getTime()) /
              60000,
          ),
        );
        return {
          project_id: String(l.project_id),
          hours: String(Math.floor(mins / 60)),
          minutes: String(mins % 60),
        };
      }
      return { project_id: String(l.project_id), hours: "", minutes: "" };
    });
    setEditProjectLogs(logs);
    setShowEditModal(true);
  };

  const handleEditSubmit = async () => {
    if (!editingRecord) return;

    const isWork = (editForm.leave_type || "work") === "work";
    const filteredEditLogs = isWork
      ? editProjectLogs.filter((l) => l.project_id)
      : [];
    if (filteredEditLogs.length > 0) {
      if (!validateProjectLogs(filteredEditLogs, editForm)) return;
    }

    try {
      const isLeave = editForm.leave_type !== "work";
      const payload: Record<string, unknown> = {
        leave_type: editForm.leave_type,
        notes: editForm.notes || null,
      };

      if (isLeave) {
        payload.leave_hours = editForm.leave_hours || 8;
        payload.arrival_time = null;
        payload.departure_time = null;
        payload.break_start = null;
        payload.break_end = null;
      } else {
        payload.arrival_time = combineDatetime(
          editForm.arrival_date,
          editForm.arrival_time,
        );
        payload.departure_time = combineDatetime(
          editForm.departure_date,
          editForm.departure_time,
        );
        payload.break_start = combineDatetime(
          editForm.break_start_date,
          editForm.break_start_time,
        );
        payload.break_end = combineDatetime(
          editForm.break_end_date,
          editForm.break_end_time,
        );
      }

      if (filteredEditLogs.length > 0) {
        payload.project_logs = filteredEditLogs;
      }

      const response = await apiFetch(
        `${API_BASE}/attendance/${editingRecord.id}`,
        {
          method: "PUT",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(payload),
        },
      );

      const result = await response.json();

      if (result.success) {
        setShowEditModal(false);
        queryClient.invalidateQueries({ queryKey: ["attendance"] });
        // The dashboard embeds attendance (Přítomní dnes / Docházka dnes /
        // punch-button state) — without this it serves a stale cache for up
        // to its staleTime after records change here.
        queryClient.invalidateQueries({ queryKey: ["dashboard"] });
        await fetchData(false);
        await new Promise((resolve) => setTimeout(resolve, 300));
        alert.success(
          result.message || result.data?.message || "Záznam aktualizován",
        );
      } else {
        alert.error(result.error || "Nepodařilo se uložit");
      }
    } catch {
      alert.error("Chyba připojení");
    }
  };

  // =========================================================================
  // Delete
  // =========================================================================
  const handleDelete = async () => {
    if (!deleteConfirm.record) return;

    try {
      const response = await apiFetch(
        `${API_BASE}/attendance/${deleteConfirm.record.id}`,
        { method: "DELETE" },
      );

      const result = await response.json();

      if (result.success) {
        setDeleteConfirm({ show: false, record: null });
        queryClient.invalidateQueries({ queryKey: ["attendance"] });
        // The dashboard embeds attendance (Přítomní dnes / Docházka dnes /
        // punch-button state) — without this it serves a stale cache for up
        // to its staleTime after records change here.
        queryClient.invalidateQueries({ queryKey: ["dashboard"] });
        await fetchData(false);
        alert.success(
          result.message || result.data?.message || "Záznam smazán",
        );
      } else {
        alert.error(result.error || "Nepodařilo se smazat");
      }
    } catch {
      alert.error("Chyba připojení");
    }
  };

  // =========================================================================
  // Print
  // =========================================================================
  const handlePrint = async () => {
    try {
      const [response, settingsRes] = await Promise.all([
        apiFetch(
          `${API_BASE}/attendance?action=print&month=${month}${filterUserId ? `&user_id=${filterUserId}` : ""}`,
        ),
        apiFetch(`${API_BASE}/company-settings`),
      ]);
      const settingsData = await settingsRes.json();
      const companyName = settingsData.success
        ? settingsData.data.company_name || ""
        : "";
      if (response.status === 401) return;
      const result = await response.json();
      if (result.success) {
        const pData = result.data;
        const userSections = Object.entries(pData.user_totals)
          .map(([uid, uData]) =>
            buildUserSectionHtml(uid, uData as Record<string, any>, pData),
          )
          .join("");
        const emptyMsg =
          Object.keys(pData.user_totals).length === 0
            ? '<p style="text-align:center;padding:20px">Za vybrané období nejsou žádné záznamy.</p>'
            : "";
        const filterNote = pData.selected_user_name
          ? `<div class="filters">Zaměstnanec: ${escapeHtml(pData.selected_user_name)}</div>`
          : "";
        const bodyContent = buildPrintHtml(
          pData,
          userSections,
          emptyMsg,
          filterNote,
          companyName,
        );
        const printWindow = window.open("", "_blank");
        if (printWindow) {
          printWindow.document.open();
          printWindow.document.write(bodyContent);
          printWindow.document.close();
          printWindow.addEventListener("load", () => printWindow.print(), {
            once: true,
          });
        }
      }
    } catch {
      alert.error("Nepodařilo se připravit tisk");
    }
  };

  // =========================================================================
  // Derived
  // =========================================================================
  const hasData = Object.keys(data.user_totals).length > 0;

  // =========================================================================
  // Public API
  // =========================================================================
  return {
    loading,
    month,
    setMonth,
    filterUserId,
    setFilterUserId,
    data,
    hasData,
    showBulkModal,
    setShowBulkModal,
    bulkSubmitting,
    bulkForm,
    setBulkForm,
    showCreateModal,
    setShowCreateModal,
    createForm,
    setCreateForm,
    showEditModal,
    setShowEditModal,
    editingRecord,
    editForm,
    setEditForm,
    deleteConfirm,
    setDeleteConfirm,
    projectList,
    createProjectLogs,
    setCreateProjectLogs,
    editProjectLogs,
    setEditProjectLogs,
    printRef,
    openCreateModal,
    handleCreateShiftDateChange,
    handleCreateSubmit,
    openBulkModal,
    toggleBulkUser,
    toggleAllBulkUsers,
    handleBulkSubmit,
    openEditModal,
    handleEditSubmit,
    handleDelete,
    handlePrint,
  };
}
