/* ============================================================
   EATME PORTÁL – DASHBOARD SERVICE
   Optimalizovaná verze

   Cíle:
   - zachovat stejné veřejné funkce
   - zrychlit opakované čtení stabilnějších tabulek
   - necacheovat živé SHIFTS
   - předindexovat PAY_RATES podle zaměstnance
============================================================ */


/* ============================================================
   KRÁTKODOBÁ CACHE PRO STABILNĚJŠÍ TABULKY

   SHIFTS záměrně necachujeme, protože dashboard má ukazovat
   příchody/odchody co nejčerstvěji.
============================================================ */

const DASHBOARD_CACHE_TTL_SECONDS = 20;


/* ============================================================
   CACHE V RÁMCI JEDNOHO APPS SCRIPT BĚHU

   Zapíná se pouze pro agregované read-only requesty.
   Díky tomu může více služeb ve stejném requestu použít stejná
   data bez dalšího čtení Google Sheets.
============================================================ */

let REQUEST_ROW_CACHE_ENABLED =
  false;


let REQUEST_ROW_CACHE =
  {};


function beginRequestRowCache_() {

  REQUEST_ROW_CACHE_ENABLED =
    true;


  REQUEST_ROW_CACHE =
    {};

}


function endRequestRowCache_() {

  REQUEST_ROW_CACHE_ENABLED =
    false;


  REQUEST_ROW_CACHE =
    {};

}


function clearRequestRowCache_(
  sheetName
) {

  if (
    !sheetName
  ) {

    REQUEST_ROW_CACHE =
      {};

    return;

  }


  delete REQUEST_ROW_CACHE[
    String(
      sheetName
    )
  ];

}


/* ============================================================
   ADMIN DASHBOARD
============================================================ */

function getAdminDashboard(token) {

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


  /*
   * EMPLOYEES, PAY_RATES a žádosti se nemění každou sekundu,
   * proto mohou mít krátkou cache.
   *
   * SHIFTS vždy čteme čerstvě.
   */

  const employees =
    displayRowsCached_(
      CFG.SHEETS.EMPLOYEES,
      DASHBOARD_CACHE_TTL_SECONDS
    );


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


  const payRates =
    displayRowsCached_(
      CFG.SHEETS.PAY_RATES,
      DASHBOARD_CACHE_TTL_SECONDS
    );


  const changeRequests =
    displayRowsCached_(
      CFG.SHEETS.SHIFT_CHANGE_REQUESTS,
      10
    );


  const requests =
    displayRowsCached_(
      CFG.SHEETS.REQUESTS,
      10
    );


  /* =========================================
     AKTIVNÍ ZAMĚSTNANCI
  ========================================= */

  const activeEmployees =
    employees.filter(
      function(employee) {

        return isActiveValue_(
          employee.active
        );

      }
    );


  /* =========================================
     MAPA ZAMĚSTNANCŮ PODLE ID
  ========================================= */

  const employeeMap =
    {};


  employees.forEach(
    function(employee) {

      employeeMap[
        String(
          employee.employee_id
        )
      ] =
        employee;

    }
  );


  /* =========================================
     INDEX SAZEB PODLE ZAMĚSTNANCE

     Původní verze pro každou směnu znovu
     filtrovala celý PAY_RATES. Tady to
     připravíme jednou.
  ========================================= */

  const rateIndex =
    buildRateIndex_(
      payRates
    );


  /* =========================================
     AKTUÁLNÍ MĚSÍC
  ========================================= */

  const now =
    new Date();


  const currentPeriod =
    Utilities.formatDate(
      now,
      CFG.TZ,
      'yyyy-MM'
    );


  /* =========================================
     OTEVŘENÉ SMĚNY
  ========================================= */

  const openShifts =
    [];


  const monthShifts =
    [];


  /*
   * Jediný průchod přes SHIFTS.
   * Původně se tabulka filtrovala vícekrát.
   */

  shifts.forEach(
    function(shift) {

      const status =
        String(
          shift.status ||
          ''
        ).trim();


      if (
        status ===
        'OPEN'
      ) {

        openShifts.push(
          shift
        );

      }


      const date =
        parseSheetDate_(
          shift.clock_in
        );


      if (
        !date
      ) {

        return;

      }


      const shiftPeriod =
        Utilities.formatDate(
          date,
          CFG.TZ,
          'yyyy-MM'
        );


      if (
        shiftPeriod ===
        currentPeriod
      ) {

        monthShifts.push(
          shift
        );

      }

    }
  );


  /* =========================================
     SOUČTY
  ========================================= */

  let totalWorkedMinutes =
    0;


  let estimatedPayroll =
    0;


  monthShifts.forEach(
    function(shift) {

      const workedMinutes =
        numberFromSheet_(
          shift.worked_minutes
        );


      totalWorkedMinutes +=
        workedMinutes;


      if (
        workedMinutes <=
        0
      ) {

        return;

      }


      const shiftDate =
        parseSheetDate_(
          shift.clock_in
        );


      if (
        !shiftDate
      ) {

        return;

      }


      const hourlyRate =
        getIndexedRateForDate_(
          rateIndex,
          shift.employee_id,
          shiftDate
        );


      estimatedPayroll +=
        (
          workedMinutes /
          60
        ) *
        hourlyRate;

    }
  );


  /* =========================================
     KDO JE PRÁVĚ V PRÁCI
  ========================================= */

  const nowMs =
    Date.now();


  const openPeople =
    openShifts.map(
      function(shift) {

        const employee =
          employeeMap[
            String(
              shift.employee_id
            )
          ];


        const clockIn =
          parseSheetDate_(
            shift.clock_in
          );


        let currentMinutes =
          0;


        if (
          clockIn
        ) {

          currentMinutes =
            Math.max(
              0,
              Math.floor(
                (
                  nowMs -
                  clockIn.getTime()
                ) /
                60000
              )
            );

        }


        let employeeName =
          String(
            shift.employee_id ||
            ''
          );


        if (
          employee
        ) {

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

        }


        return {

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

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

          name:
            employeeName,

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

          clock_in:
            clockIn
              ? clockIn.toISOString()
              : '',

          current_minutes:
            currentMinutes

        };

      }
    );


  /* =========================================
     ČEKAJÍCÍ OPRAVY
  ========================================= */

  let pendingChangeRequests =
    0;


  changeRequests.forEach(
    function(request) {

      if (
        String(
          request.status ||
          ''
        ).trim() ===
        'PENDING'
      ) {

        pendingChangeRequests++;

      }

    }
  );


  /* =========================================
     OSTATNÍ ČEKAJÍCÍ ŽÁDOSTI
  ========================================= */

  let pendingRequests =
    0;


  requests.forEach(
    function(request) {

      if (
        String(
          request.status ||
          ''
        ).trim() ===
        'PENDING'
      ) {

        pendingRequests++;

      }

    }
  );


  /* =========================================
     PODEZŘELE DLOUHÉ SMĚNY
  ========================================= */

  let staleOpenShifts =
    0;


  openShifts.forEach(
    function(shift) {

      const clockIn =
        parseSheetDate_(
          shift.clock_in
        );


      if (
        !clockIn
      ) {

        return;

      }


      const hours =
        (
          nowMs -
          clockIn.getTime()
        ) /
        3600000;


      if (
        hours >
        14
      ) {

        staleOpenShifts++;

      }

    }
  );


  /* =========================================
     VÝSLEDEK PRO FRONTEND
  ========================================= */

  return {

    current_period:
      currentPeriod,


    stats: {

      active_employees:
        activeEmployees.length,

      open_shifts:
        openShifts.length,

      worked_minutes:
        Math.round(
          totalWorkedMinutes
        ),

      estimated_payroll:
        Math.round(
          estimatedPayroll
        ),

      pending_change_requests:
        pendingChangeRequests,

      pending_requests:
        pendingRequests,

      stale_open_shifts:
        staleOpenShifts

    },


    open_people:
      openPeople

  };

}



/* ============================================================
   ČTENÍ TABULKY JAKO TEXTŮ

   Tato funkce zůstává bez cache, protože ji používá celý projekt.
   Tím minimalizujeme riziko, že nějaký zápis nebude okamžitě vidět.
============================================================ */

function displayRows_(sheetName) {

  const cacheKey =
    String(
      sheetName
    );


  if (
    REQUEST_ROW_CACHE_ENABLED &&
    Object.prototype.hasOwnProperty.call(
      REQUEST_ROW_CACHE,
      cacheKey
    )
  ) {

    return REQUEST_ROW_CACHE[
      cacheKey
    ];

  }


  const sheet =
    sh_(
      sheetName
    );


  const lastRow =
    sheet.getLastRow();


  const lastColumn =
    sheet.getLastColumn();


  if (
    lastRow <
    2 ||
    lastColumn <
    1
  ) {

    if (
      REQUEST_ROW_CACHE_ENABLED
    ) {

      REQUEST_ROW_CACHE[
        cacheKey
      ] =
        [];

    }


    return [];

  }


  const values =
    sheet
      .getRange(
        1,
        1,
        lastRow,
        lastColumn
      )
      .getDisplayValues();


  const headers =
    values[0]
      .map(
        function(header) {

          return String(
            header
          ).trim();

        }
      );


  const result =
    [];


  for (
    let rowIndex = 1;
    rowIndex < values.length;
    rowIndex++
  ) {

    const row =
      values[
        rowIndex
      ];


    let hasValue =
      false;


    for (
      let i = 0;
      i < row.length;
      i++
    ) {

      if (
        String(
          row[i]
        ).trim() !==
        ''
      ) {

        hasValue =
          true;

        break;

      }

    }


    if (
      !hasValue
    ) {

      continue;

    }


    const object =
      {};


    for (
      let columnIndex = 0;
      columnIndex < headers.length;
      columnIndex++
    ) {

      const header =
        headers[
          columnIndex
        ];


      if (
        !header
      ) {

        continue;

      }


      object[
        header
      ] =
        row[
          columnIndex
        ];

    }


    result.push(
      object
    );

  }


  if (
    REQUEST_ROW_CACHE_ENABLED
  ) {

    REQUEST_ROW_CACHE[
      cacheKey
    ] =
      result;

  }


  return result;

}



/* ============================================================
   KRÁTKODOBĚ CACHOVANÉ ČTENÍ

   Používá se pouze tam, kde malé zpoždění nevadí.
============================================================ */

function displayRowsCached_(
  sheetName,
  ttlSeconds
) {

  ttlSeconds =
    Number(
      ttlSeconds ||
      DASHBOARD_CACHE_TTL_SECONDS
    );


  const cache =
    CacheService
      .getScriptCache();


  const key =
    'DISPLAY_ROWS_V1_' +
    String(
      sheetName
    );


  try {

    const cached =
      cache.get(
        key
      );


    if (
      cached
    ) {

      return JSON.parse(
        cached
      );

    }

  }

  catch(error) {

    /*
     * Cache nesmí nikdy rozbít aplikaci.
     * Při chybě pokračujeme normálním čtením.
     */

  }


  const rows =
    displayRows_(
      sheetName
    );


  /*
   * CacheService má limit velikosti jedné hodnoty.
   * Když je tabulka moc velká, zápis jen přeskočíme.
   */

  try {

    const json =
      JSON.stringify(
        rows
      );


    if (
      json.length <
      90000
    ) {

      cache.put(
        key,
        json,
        ttlSeconds
      );

    }

  }

  catch(error) {

    /*
     * Opět ignorujeme pouze chybu cache.
     */

  }


  return rows;

}



/* ============================================================
   RUČNÍ SMAZÁNÍ DASHBOARD CACHE

   Lze spustit ručně při testování.
============================================================ */

function clearDashboardCache() {

  const cache =
    CacheService
      .getScriptCache();


  [
    CFG.SHEETS.EMPLOYEES,
    CFG.SHEETS.PAY_RATES,
    CFG.SHEETS.SHIFT_CHANGE_REQUESTS,
    CFG.SHEETS.REQUESTS
  ]
    .forEach(
      function(sheetName) {

        cache.remove(
          'DISPLAY_ROWS_V1_' +
          String(
            sheetName
          )
        );

      }
    );


  Logger.log(
    'Dashboard cache cleared'
  );

}



/* ============================================================
   INDEX SAZEB
============================================================ */

function buildRateIndex_(rates) {

  const index =
    {};


  rates.forEach(
    function(rate) {

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


      if (
        !employeeId
      ) {

        return;

      }


      if (
        !index[
          employeeId
        ]
      ) {

        index[
          employeeId
        ] =
          [];

      }


      const fromDate =
        parseSheetDate_(
          rate.valid_from
        );


      const toDate =
        parseSheetDate_(
          rate.valid_to
        );


      index[
        employeeId
      ].push(
        {

          from:
            fromDate
              ? Utilities.formatDate(
                  fromDate,
                  CFG.TZ,
                  'yyyy-MM-dd'
                )
              : '0000-00-00',

          to:
            toDate
              ? Utilities.formatDate(
                  toDate,
                  CFG.TZ,
                  'yyyy-MM-dd'
                )
              : '9999-12-31',

          rate:
            numberFromSheet_(
              rate.hourly_rate
            )

        }
      );

    }
  );


  Object.keys(
    index
  )
    .forEach(
      function(employeeId) {

        index[
          employeeId
        ].sort(
          function(a,b) {

            return b.from.localeCompare(
              a.from
            );

          }
        );

      }
    );


  return index;

}



/* ============================================================
   SAZBA Z INDEXU
============================================================ */

function getIndexedRateForDate_(
  rateIndex,
  employeeId,
  targetDate
) {

  if (
    !targetDate
  ) {

    return 0;

  }


  const employeeRates =
    rateIndex[
      String(
        employeeId
      )
    ] ||
    [];


  if (
    !employeeRates.length
  ) {

    return 0;

  }


  const target =
    Utilities.formatDate(
      targetDate,
      CFG.TZ,
      'yyyy-MM-dd'
    );


  for (
    let i = 0;
    i < employeeRates.length;
    i++
  ) {

    const rate =
      employeeRates[
        i
      ];


    if (
      rate.from <=
        target &&
      rate.to >=
        target
    ) {

      return rate.rate;

    }

  }


  return 0;

}



/* ============================================================
   AKTIVNÍ HODNOTA
============================================================ */

function isActiveValue_(value) {

  const text =
    String(
      value ||
      ''
    )
      .trim()
      .toUpperCase();


  return (
    text ===
      'TRUE' ||

    text ===
      'ANO' ||

    text ===
      'YES' ||

    text ===
      '1'
  );

}



/* ============================================================
   ČÍSLO Z GOOGLE SHEETS
============================================================ */

function numberFromSheet_(value) {

  let text =
    String(
      value ||
      '0'
    )
      .trim()
      .replace(
        /\s/g,
        ''
      )
      .replace(
        ',',
        '.'
      );


  text =
    text.replace(
      /[^0-9.\-]/g,
      ''
    );


  const number =
    Number(
      text
    );


  return isNaN(
    number
  )
    ? 0
    : number;

}



/* ============================================================
   PARSOVÁNÍ DATUMU
============================================================ */

function parseSheetDate_(value) {

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


  if (
    !text
  ) {

    return null;

  }


  let match;


  /* =========================================
     ISO FORMÁT

     2026-08-11
     2026-08-11 23:44:56
  ========================================= */

  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
      )
    );

  }


  /* =========================================
     ČESKÝ FORMÁT

     11.8.2026
     11.8.2026 23:44:56
  ========================================= */

  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
      )
    );

  }


  /* =========================================
     GOOGLE / US FORMÁT

     8/11/2026
     8/11/2026 23:44:56

     měsíc / den / rok
  ========================================= */

  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
      )
    );

  }


  /* =========================================
     POSLEDNÍ POKUS
  ========================================= */

  const date =
    new Date(
      text
    );


  if (
    isNaN(
      date.getTime()
    )
  ) {

    return null;

  }


  return date;

}



/* ============================================================
   PŮVODNÍ VEŘEJNÁ FUNKCE PRO SAZBU

   Zachována kvůli kompatibilitě s ostatními moduly.
============================================================ */

function getDisplayRateForDate_(
  rates,
  employeeId,
  targetDate
) {

  if (
    !targetDate
  ) {

    return 0;

  }


  const rateIndex =
    buildRateIndex_(
      rates
    );


  return getIndexedRateForDate_(
    rateIndex,
    employeeId,
    targetDate
  );

}



/* ============================================================
   DIAGNOSTIKA DATABÁZE
============================================================ */

function testDashboardDatabase() {

  const started =
    Date.now();


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


  const afterEmployees =
    Date.now();


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


  const afterShifts =
    Date.now();


  Logger.log(
    'EMPLOYEES: ' +
    employees.length +
    ' | ' +
    (
      afterEmployees -
      started
    ) +
    ' ms'
  );


  Logger.log(
    'SHIFTS: ' +
    shifts.length +
    ' | ' +
    (
      afterShifts -
      afterEmployees
    ) +
    ' ms'
  );


  Logger.log(
    'TOTAL: ' +
    (
      afterShifts -
      started
    ) +
    ' ms'
  );


  return {

    employees:
      employees.length,

    shifts:
      shifts.length,

    employees_ms:
      afterEmployees -
      started,

    shifts_ms:
      afterShifts -
      afterEmployees,

    total_ms:
      afterShifts -
      started

  };

}



/* ============================================================
   DIAGNOSTIKA ADMIN DASHBOARDU
============================================================ */

function testAdminDashboardPerformance() {

  const started =
    Date.now();


  /*
   * Test vyžaduje validní token při volání přes frontend,
   * proto zde měříme pouze datovou část.
   */

  const employees =
    displayRowsCached_(
      CFG.SHEETS.EMPLOYEES,
      DASHBOARD_CACHE_TTL_SECONDS
    );


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


  const payRates =
    displayRowsCached_(
      CFG.SHEETS.PAY_RATES,
      DASHBOARD_CACHE_TTL_SECONDS
    );


  const rateIndex =
    buildRateIndex_(
      payRates
    );


  const finished =
    Date.now();


  Logger.log(
    'EMPLOYEES: ' +
    employees.length
  );


  Logger.log(
    'SHIFTS: ' +
    shifts.length
  );


  Logger.log(
    'PAY_RATES: ' +
    payRates.length
  );


  Logger.log(
    'RATE INDEX EMPLOYEES: ' +
    Object.keys(
      rateIndex
    ).length
  );


  Logger.log(
    'TOTAL: ' +
    (
      finished -
      started
    ) +
    ' ms'
  );


  return {

    total_ms:
      finished -
      started,

    employees:
      employees.length,

    shifts:
      shifts.length,

    pay_rates:
      payRates.length

  };

}
