function clockAction(token, action) {
  const user = requireUser_(token,['EMPLOYEE','MANAGER','ADMIN']);
  if (!user.employee_id) throw new Error('Účet není propojený se zaměstnancem.');
  return clockForEmployee_(user.employee_id, action, 'PORTAL', user.user_id);
}

function terminalClock(employeeId, pin, action, locationId) {
  const emp = findOne_(CFG.SHEETS.EMPLOYEES, r => String(r.employee_id) === String(employeeId));
  if (!emp || String(emp.terminal_pin) !== String(pin)) throw new Error('Neplatný PIN.');
  if (!(emp.active === true || String(emp.active).toUpperCase() === 'TRUE')) throw new Error('Zaměstnanec není aktivní.');
  return clockForEmployee_(employeeId, action, 'TERMINAL', 'TERMINAL:'+String(locationId || emp.location_id));
}

function clockForEmployee_(employeeId, action, source, createdBy) {
  const allowed = ['CLOCK_IN','BREAK_START','BREAK_END','CLOCK_OUT'];
  if (!allowed.includes(action)) throw new Error('Neplatná akce.');

  const lock = LockService.getScriptLock();
  lock.waitLock(10000);
  try {
    const open = getOpenShift_(employeeId);
    const t = now_();

    if (action === 'CLOCK_IN') {
      if (open) throw new Error('Směna už probíhá.');
      const emp = findOne_(CFG.SHEETS.EMPLOYEES, r => String(r.employee_id) === String(employeeId));
      const shift = {
        shift_id:uuid_('SHIFT'),
        employee_id:employeeId,
        work_date:isoDate_(t),
        clock_in:t,
        clock_out:'',
        break_minutes:0,
        worked_minutes:0,
        night_minutes:0,
        weekend_minutes:0,
        holiday_minutes:0,
        status:'OPEN',
        location_id:emp ? emp.location_id : '',
        created_at:t,
        updated_at:t
      };
      append_(CFG.SHEETS.SHIFTS,shift);
      attendanceEvent_(employeeId,t,action,shift.location_id,source,createdBy);
      return {ok:true,status:'OPEN',shift};
    }

    if (!open) throw new Error('Žádná otevřená směna.');

    const events = rows_(CFG.SHEETS.ATTENDANCE_EVENTS)
      .filter(e => String(e.employee_id) === String(employeeId) && new Date(e.timestamp) >= new Date(open.clock_in))
      .sort((a,b) => new Date(a.timestamp)-new Date(b.timestamp));
    const last = events[events.length-1];

    if (action === 'BREAK_START' && last && String(last.event_type) === 'BREAK_START') throw new Error('Pauza už běží.');
    if (action === 'BREAK_END' && (!last || String(last.event_type) !== 'BREAK_START')) throw new Error('Pauza neběží.');
    if (action === 'CLOCK_OUT' && last && String(last.event_type) === 'BREAK_START') throw new Error('Nejdřív ukonči pauzu.');

    attendanceEvent_(employeeId,t,action,open.location_id,source,createdBy);

    if (action === 'CLOCK_OUT') {
      const allEvents = events.concat([{timestamp:t,event_type:'CLOCK_OUT'}]);
      const calc = calculateShift_(new Date(open.clock_in),t,allEvents);
      updateBy_(CFG.SHEETS.SHIFTS,'shift_id',open.shift_id,{
        clock_out:t,
        break_minutes:calc.breakMinutes,
        worked_minutes:calc.workedMinutes,
        night_minutes:calc.nightMinutes,
        weekend_minutes:calc.weekendMinutes,
        status:'COMPLETED',
        updated_at:t
      });
      return {ok:true,status:'COMPLETED',shift_id:open.shift_id,worked_minutes:calc.workedMinutes};
    }

    return {ok:true,status:action,shift_id:open.shift_id};
  } finally {
    lock.releaseLock();
  }
}

function attendanceEvent_(employeeId,timestamp,type,locationId,source,createdBy) {
  append_(CFG.SHEETS.ATTENDANCE_EVENTS,{
    event_id:uuid_('EVT'),
    employee_id:employeeId,
    timestamp,
    event_type:type,
    location_id:locationId || '',
    source:source || '',
    created_by:createdBy || ''
  });
}

function getOpenShift_(employeeId) {
  const list = rows_(CFG.SHEETS.SHIFTS)
    .filter(s => String(s.employee_id) === String(employeeId) && String(s.status) === 'OPEN')
    .sort((a,b) => new Date(b.clock_in)-new Date(a.clock_in));
  return list[0] || null;
}

function calculateShift_(clockIn, clockOut, events) {
  let breakMinutes = 0, breakStart = null;
  (events || []).forEach(e => {
    if (String(e.event_type) === 'BREAK_START') breakStart = new Date(e.timestamp);
    if (String(e.event_type) === 'BREAK_END' && breakStart) {
      breakMinutes += Math.max(0, Math.round((new Date(e.timestamp)-breakStart)/60000));
      breakStart = null;
    }
  });
  const total = Math.max(0, Math.round((clockOut-clockIn)/60000));
  const worked = Math.max(0,total-breakMinutes);

  // Základní výpočet nočních minut 22:00–06:00 po jednotlivých minutách.
  let night = 0, weekend = 0;
  let p = new Date(clockIn);
  while (p < clockOut) {
    const n = new Date(Math.min(p.getTime()+60000, clockOut.getTime()));
    const hh = Number(Utilities.formatDate(p,CFG.TZ,'H'));
    const dow = Number(Utilities.formatDate(p,CFG.TZ,'u'));
    if (hh >= 22 || hh < 6) night += (n-p)/60000;
    if (dow >= 6) weekend += (n-p)/60000;
    p = n;
  }

  return {
    breakMinutes:Math.round(breakMinutes),
    workedMinutes:Math.round(worked),
    nightMinutes:Math.round(night),
    weekendMinutes:Math.round(weekend)
  };
}