/* ============================================================
   EATME PORTÁL – SHIFT RECONCILIATION SERVICE

   Porovnává:
   - plánované směny: SHIFT_SLOTS + SHIFT_SIGNUPS
   - skutečnou docházku: SHIFTS

   Nic nepřepisuje. Jde o read-only analytickou vrstvu.
============================================================ */

const SHIFT_RECONCILIATION = Object.freeze({
  LATE_TOLERANCE_MINUTES: 10,
  EARLY_LEAVE_TOLERANCE_MINUTES: 10,
  MATCH_WINDOW_HOURS: 8
});


/* ============================================================
   ZAMĚSTNANEC – PLÁN VS DOCHÁZKA
============================================================ */

function getMyShiftReconciliation(
  token,
  period
) {

  const user =
    requireUser_(
      token,
      [
        'EMPLOYEE',
        'MANAGER',
        'ADMIN'
      ]
    );


  if (
    !user.employee_id
  ) {

    throw new Error(
      'Účet není propojen se zaměstnancem.'
    );

  }


  period =
    normalizeReconciliationPeriod_(
      period
    );


  const context =
    buildReconciliationContext_();


  const rows =
    reconcileEmployeePeriod_(
      user.employee_id,
      period,
      context
    );


  return {

    period:
      period,

    stats:
      reconciliationStats_(
        rows
      ),

    rows:
      rows

  };

}



/* ============================================================
   ADMIN / MANAGER – PLÁN VS DOCHÁZKA
============================================================ */

function getAdminShiftReconciliation(
  token,
  period
) {

  requireUser_(
    token,
    [
      'ADMIN',
      'MANAGER'
    ]
  );


  period =
    normalizeReconciliationPeriod_(
      period
    );


  const context =
    buildReconciliationContext_();


  const result =
    [];


  context.employees
    .filter(
      function(employee) {

        return (
          String(
            employee.active
          ).toUpperCase() ===
          'TRUE'
        );

      }
    )
    .forEach(
      function(employee) {

        const rows =
          reconcileEmployeePeriod_(
            employee.employee_id,
            period,
            context
          );


        const employeeName =
          (
            String(
              employee.first_name ||
              ''
            ) +
            ' ' +
            String(
              employee.last_name ||
              ''
            )
          ).trim();


        rows.forEach(
          function(row) {

            row.employee_id =
              String(
                employee.employee_id ||
                ''
              );


            row.employee_name =
              employeeName;


            result.push(
              row
            );

          }
        );

      }
    );


  result.sort(
    function(a,b) {

      if (
        a.date !==
        b.date
      ) {

        return String(
          a.date
        ).localeCompare(
          String(
            b.date
          )
        );

      }


      if (
        a.planned_start !==
        b.planned_start
      ) {

        return String(
          a.planned_start ||
          a.actual_start ||
          ''
        ).localeCompare(
          String(
            b.planned_start ||
            b.actual_start ||
            ''
          )
        );

      }


      return String(
        a.employee_name ||
        ''
      ).localeCompare(
        String(
          b.employee_name ||
          ''
        ),
        'cs'
      );

    }
  );


  return {

    period:
      period,

    stats:
      reconciliationStats_(
        result
      ),

    rows:
      result

  };

}



/* ============================================================
   KONTEXT – KAŽDÝ LIST NAČTEME JEN JEDNOU
============================================================ */

function buildReconciliationContext_() {

  const slots =
    displayRows_(
      'SHIFT_SLOTS'
    );


  const signups =
    displayRows_(
      'SHIFT_SIGNUPS'
    );


  const shifts =
    displayRows_(
      CFG.SHEETS.SHIFTS
    );


  const employees =
    displayRows_(
      CFG.SHEETS.EMPLOYEES
    );


  const slotMap =
    {};


  slots.forEach(
    function(slot) {

      slotMap[
        String(
          slot.slot_id
        )
      ] =
        slot;

    }
  );


  const plannedByEmployee =
    {};


  signups.forEach(
    function(signup) {

      if (
        String(
          signup.status
        ) !==
        'APPROVED'
      ) {

        return;

      }


      const slot =
        slotMap[
          String(
            signup.slot_id
          )
        ];


      if (
        !slot
      ) {

        return;

      }


      const employeeId =
        String(
          signup.employee_id ||
          ''
        );


      if (
        !plannedByEmployee[
          employeeId
        ]
      ) {

        plannedByEmployee[
          employeeId
        ] =
          [];

      }


      plannedByEmployee[
        employeeId
      ].push(
        {

          signup:
            signup,

          slot:
            slot

        }
      );

    }
  );


  const actualByEmployee =
    {};


  shifts.forEach(
    function(shift) {

      const employeeId =
        String(
          shift.employee_id ||
          ''
        );


      if (
        !actualByEmployee[
          employeeId
        ]
      ) {

        actualByEmployee[
          employeeId
        ] =
          [];

      }


      actualByEmployee[
        employeeId
      ].push(
        shift
      );

    }
  );


  return {

    employees:
      employees,

    plannedByEmployee:
      plannedByEmployee,

    actualByEmployee:
      actualByEmployee

  };

}



/* ============================================================
   PÁROVÁNÍ JEDNOHO ZAMĚSTNANCE
============================================================ */

function reconcileEmployeePeriod_(
  employeeId,
  period,
  context
) {

  const planned =
    (
      context.plannedByEmployee[
        String(
          employeeId
        )
      ] ||
      []
    )
      .filter(
        function(item) {

          return (
            String(
              item.slot.date ||
              ''
            ).substring(
              0,
              7
            ) ===
            period
          );

        }
      )
      .map(
        function(item) {

          return makePlannedItem_(
            item
          );

        }
      )
      .filter(
        Boolean
      )
      .sort(
        function(a,b) {

          return (
            a.start.getTime() -
            b.start.getTime()
          );

        }
      );


  const actual =
    (
      context.actualByEmployee[
        String(
          employeeId
        )
      ] ||
      []
    )
      .map(
        function(shift) {

          return makeActualItem_(
            shift
          );

        }
      )
      .filter(
        function(item) {

          if (
            !item ||
            !item.start
          ) {

            return false;

          }


          return (
            Utilities.formatDate(
              item.start,
              CFG.TZ,
              'yyyy-MM'
            ) ===
            period
          );

        }
      )
      .sort(
        function(a,b) {

          return (
            a.start.getTime() -
            b.start.getTime()
          );

        }
      );


  const usedActual =
    {};


  const rows =
    [];


  planned.forEach(
    function(plan) {

      let bestIndex =
        -1;


      let bestDistance =
        Infinity;


      actual.forEach(
        function(attendance,index) {

          if (
            usedActual[
              index
            ]
          ) {

            return;

          }


          const distance =
            Math.abs(
              attendance.start.getTime() -
              plan.start.getTime()
            );


          if (
            distance >
            SHIFT_RECONCILIATION.MATCH_WINDOW_HOURS *
            3600000
          ) {

            return;

          }


          /*
           * Přednost má stejný pracovní den.
           */

          const planDate =
            Utilities.formatDate(
              plan.start,
              CFG.TZ,
              'yyyy-MM-dd'
            );


          const actualDate =
            Utilities.formatDate(
              attendance.start,
              CFG.TZ,
              'yyyy-MM-dd'
            );


          const penalty =
            planDate ===
              actualDate
              ? 0
              : 24 *
                3600000;


          const score =
            distance +
            penalty;


          if (
            score <
            bestDistance
          ) {

            bestDistance =
              score;

            bestIndex =
              index;

          }

        }
      );


      if (
        bestIndex >=
        0
      ) {

        usedActual[
          bestIndex
        ] =
          true;


        rows.push(
          reconciliationRow_(
            plan,
            actual[
              bestIndex
            ]
          )
        );

      } else {

        rows.push(
          reconciliationRow_(
            plan,
            null
          )
        );

      }

    }
  );


  actual.forEach(
    function(attendance,index) {

      if (
        usedActual[
          index
        ]
      ) {

        return;

      }


      rows.push(
        reconciliationRow_(
          null,
          attendance
        )
      );

    }
  );


  return rows.sort(
    function(a,b) {

      const aKey =
        String(
          a.date ||
          ''
        ) +
        ' ' +
        String(
          a.planned_start ||
          a.actual_start ||
          ''
        );


      const bKey =
        String(
          b.date ||
          ''
        ) +
        ' ' +
        String(
          b.planned_start ||
          b.actual_start ||
          ''
        );


      return aKey.localeCompare(
        bKey
      );

    }
  );

}



/* ============================================================
   PLÁNOVANÁ SMĚNA
============================================================ */

function makePlannedItem_(item) {

  const slot =
    item.slot;


  const date =
    String(
      slot.date ||
      ''
    ).substring(
      0,
      10
    );


  if (
    !date
  ) {

    return null;

  }


  const start =
    reconciliationDateTime_(
      date,
      slot.start_time
    );


  let end =
    reconciliationDateTime_(
      date,
      slot.end_time
    );


  if (
    !start ||
    !end
  ) {

    return null;

  }


  if (
    end <=
    start
  ) {

    end =
      new Date(
        end.getTime() +
        86400000
      );

  }


  return {

    slot_id:
      String(
        slot.slot_id ||
        ''
      ),

    date:
      date,

    start:
      start,

    end:
      end,

    start_time:
      reconciliationHHMM_(
        start
      ),

    end_time:
      reconciliationHHMM_(
        end
      )

  };

}



/* ============================================================
   SKUTEČNÁ SMĚNA
============================================================ */

function makeActualItem_(shift) {

  const start =
    reconciliationParseDate_(
      shift.clock_in
    );


  if (
    !start
  ) {

    return null;

  }


  const end =
    reconciliationParseDate_(
      shift.clock_out
    );


  return {

    shift_id:
      String(
        shift.shift_id ||
        ''
      ),

    start:
      start,

    end:
      end,

    status:
      String(
        shift.status ||
        ''
      ),

    worked_minutes:
      reconciliationNumber_(
        shift.worked_minutes
      )

  };

}



/* ============================================================
   VÝSLEDNÝ ŘÁDEK
============================================================ */

function reconciliationRow_(
  plan,
  attendance
) {

  const now =
    new Date();


  let status =
    'OK';


  let label =
    'V pořádku';


  let lateMinutes =
    0;


  let earlyLeaveMinutes =
    0;


  let differenceMinutes =
    0;


  if (
    plan &&
    !attendance
  ) {

    if (
      now <
      plan.end
    ) {

      status =
        'PLANNED';

      label =
        'Plánováno';

    } else {

      status =
        'ABSENT';

      label =
        'Bez docházky';

    }

  }


  if (
    !plan &&
    attendance
  ) {

    status =
      'EXTRA';

    label =
      'Směna navíc';

  }


  if (
    plan &&
    attendance
  ) {

    lateMinutes =
      Math.max(
        0,
        Math.round(
          (
            attendance.start.getTime() -
            plan.start.getTime()
          ) /
          60000
        )
      );


    if (
      attendance.end
    ) {

      earlyLeaveMinutes =
        Math.max(
          0,
          Math.round(
            (
              plan.end.getTime() -
              attendance.end.getTime()
            ) /
            60000
          )
        );

    }


    const plannedMinutes =
      Math.round(
        (
          plan.end.getTime() -
          plan.start.getTime()
        ) /
        60000
      );


    differenceMinutes =
      attendance.worked_minutes -
      plannedMinutes;


    if (
      attendance.status ===
      'OPEN'
    ) {

      status =
        'OPEN';

      label =
        'Právě probíhá';

    } else if (
      lateMinutes >
      SHIFT_RECONCILIATION.LATE_TOLERANCE_MINUTES
    ) {

      status =
        'LATE';

      label =
        'Pozdní příchod';

    } else if (
      earlyLeaveMinutes >
      SHIFT_RECONCILIATION.EARLY_LEAVE_TOLERANCE_MINUTES
    ) {

      status =
        'EARLY_LEAVE';

      label =
        'Předčasný odchod';

    }

  }


  const date =
    plan
      ? plan.date
      : Utilities.formatDate(
          attendance.start,
          CFG.TZ,
          'yyyy-MM-dd'
        );


  return {

    date:
      date,

    slot_id:
      plan
        ? plan.slot_id
        : '',

    shift_id:
      attendance
        ? attendance.shift_id
        : '',

    planned_start:
      plan
        ? plan.start_time
        : '',

    planned_end:
      plan
        ? plan.end_time
        : '',

    actual_start:
      attendance
        ? reconciliationHHMM_(
            attendance.start
          )
        : '',

    actual_end:
      attendance &&
      attendance.end
        ? reconciliationHHMM_(
            attendance.end
          )
        : '',

    worked_minutes:
      attendance
        ? attendance.worked_minutes
        : 0,

    late_minutes:
      lateMinutes,

    early_leave_minutes:
      earlyLeaveMinutes,

    difference_minutes:
      differenceMinutes,

    status:
      status,

    status_label:
      label

  };

}



/* ============================================================
   STATISTIKY
============================================================ */

function reconciliationStats_(rows) {

  const stats =
    {

      total:
        rows.length,

      ok:
        0,

      late:
        0,

      early_leave:
        0,

      absent:
        0,

      extra:
        0,

      open:
        0,

      planned:
        0

    };


  rows.forEach(
    function(row) {

      switch(
        String(
          row.status
        )
      ) {

        case 'OK':

          stats.ok++;
          break;


        case 'LATE':

          stats.late++;
          break;


        case 'EARLY_LEAVE':

          stats.early_leave++;
          break;


        case 'ABSENT':

          stats.absent++;
          break;


        case 'EXTRA':

          stats.extra++;
          break;


        case 'OPEN':

          stats.open++;
          break;


        case 'PLANNED':

          stats.planned++;
          break;

      }

    }
  );


  return stats;

}



/* ============================================================
   HELPERS
============================================================ */

function normalizeReconciliationPeriod_(
  period
) {

  period =
    String(
      period ||
      ''
    ).trim();


  if (
    !/^\d{4}-\d{2}$/.test(
      period
    )
  ) {

    return Utilities.formatDate(
      new Date(),
      CFG.TZ,
      'yyyy-MM'
    );

  }


  return period;

}


function reconciliationDateTime_(
  date,
  time
) {

  const dateParts =
    String(
      date ||
      ''
    ).split(
      '-'
    );


  const timeParts =
    String(
      time ||
      '00:00'
    ).substring(
      0,
      5
    ).split(
      ':'
    );


  if (
    dateParts.length !==
      3 ||
    timeParts.length <
      2
  ) {

    return null;

  }


  const result =
    new Date(
      Number(
        dateParts[0]
      ),
      Number(
        dateParts[1]
      ) - 1,
      Number(
        dateParts[2]
      ),
      Number(
        timeParts[0]
      ),
      Number(
        timeParts[1]
      ),
      0
    );


  return isNaN(
    result.getTime()
  )
    ? null
    : result;

}


function reconciliationParseDate_(
  value
) {

  if (
    !value
  ) {

    return null;

  }


  if (
    value instanceof Date
  ) {

    return value;

  }


  const text =
    String(
      value
    ).trim();


  let match;


  match =
    text.match(
      /^(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{1,2}):(\d{2})(?::(\d{2}))?)?$/
    );


  if (
    match
  ) {

    return new Date(
      Number(
        match[1]
      ),
      Number(
        match[2]
      ) - 1,
      Number(
        match[3]
      ),
      Number(
        match[4] ||
        0
      ),
      Number(
        match[5] ||
        0
      ),
      Number(
        match[6] ||
        0
      )
    );

  }


  match =
    text.match(
      /^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:\s+(\d{1,2}):(\d{2})(?::(\d{2}))?)?$/
    );


  if (
    match
  ) {

    return new Date(
      Number(
        match[3]
      ),
      Number(
        match[1]
      ) - 1,
      Number(
        match[2]
      ),
      Number(
        match[4] ||
        0
      ),
      Number(
        match[5] ||
        0
      ),
      Number(
        match[6] ||
        0
      )
    );

  }


  match =
    text.match(
      /^(\d{1,2})\.\s*(\d{1,2})\.\s*(\d{4})(?:\s+(\d{1,2}):(\d{2})(?::(\d{2}))?)?$/
    );


  if (
    match
  ) {

    return new Date(
      Number(
        match[3]
      ),
      Number(
        match[2]
      ) - 1,
      Number(
        match[1]
      ),
      Number(
        match[4] ||
        0
      ),
      Number(
        match[5] ||
        0
      ),
      Number(
        match[6] ||
        0
      )
    );

  }


  const date =
    new Date(
      text
    );


  return isNaN(
    date.getTime()
  )
    ? null
    : date;

}


function reconciliationHHMM_(
  date
) {

  if (
    !date
  ) {

    return '';

  }


  return Utilities.formatDate(
    date,
    CFG.TZ,
    'HH:mm'
  );

}


function reconciliationNumber_(
  value
) {

  const number =
    Number(
      String(
        value == null
          ? 0
          : value
      )
        .replace(
          ',',
          '.'
        )
        .replace(
          /\s/g,
          ''
        )
    );


  return isFinite(
    number
  )
    ? number
    : 0;

}


/* ============================================================
   DIAGNOSTIKA
============================================================ */

function testShiftReconciliation() {

  const period =
    Utilities.formatDate(
      new Date(),
      CFG.TZ,
      'yyyy-MM'
    );


  const context =
    buildReconciliationContext_();


  Logger.log(
    'Employees: ' +
    context.employees.length
  );


  Logger.log(
    'Period: ' +
    period
  );


  Logger.log(
    'Shift reconciliation OK'
  );

}
