"/**
 * Generates a formatted PDF report based on inputs from the "Main" sheet.
 * Features:
 * 1. Displays report generation time in "Main" sheet (H2).
 * 2. Supports different report types ("PnL", "PF", "Trade Log") from "Main" sheet (I3).
 * 3. Filters data based on start and end dates from "Main" sheet (I4, K4).
 * 4. Option to send email with the generated report (I5, J5).
 * 5. Filters data based on Asset Class, Broker, and User with support for "ALL" options (I7, J7, K7).
 * 6. Ensures barcode does not overlap with text by placing it in a separate cell.
 * 7. Generates unique 5-digit report numbers.
 * 8. Includes report generation time in the PDF.
 * 9. Places total rows at the top of the PDF.
 * 10. Starts Intraday report on a new page.
 * 11. Calculates "% PnL" in the PDF.
 * 12. Logs each report generation in the "Log(s)" sheet with detailed information.
 */
function generateFreePDFReport() {
  const startTime = new Date();
  
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const mainSheet = ss.getSheetByName("Main");
  
  // Fetch inputs from Main sheet
  const reportType = mainSheet.getRange("I3").getValue().toString().trim(); // "PnL", "PF", "Trade Log"
  const startDate = mainSheet.getRange("I4").getValue(); // Start Date
  const endDate = mainSheet.getRange("K4").getValue(); // End Date
  const sendEmailFlag = mainSheet.getRange("I5").getValue().toString().trim().toLowerCase(); // "yes" or "no"
  const emailRecipient = mainSheet.getRange("J5").getValue().toString().trim(); // Email address
  const assetClassFilter = mainSheet.getRange("I7").getValue().toString().trim(); // Asset Class or "ALL"
  const brokerFilter = mainSheet.getRange("J7").getValue().toString().trim(); // Broker or "ALL"
  const userFilter = mainSheet.getRange("K7").getValue().toString().trim(); // User or "ALL"
  
  // Validate report type
  if (!["PnL", "PF", "Trade Log"].includes(reportType)) {
    Logger.log(`Invalid report type selected: ${reportType}. Please choose from "PnL", "PF", "Trade Log".`);
    mainSheet.getRange("H2").setValue("Invalid report type selected.");
    return;
  }
  
  if (reportType === "PnL") {
    generatePnLReport(ss, mainSheet, startDate, endDate, sendEmailFlag, emailRecipient, assetClassFilter, brokerFilter, userFilter, startTime);
  }
  
  // Future implementations for "PF" and "Trade Log" can be added here
}

/**
 * Generates the PnL Report based on the provided filters and inputs.
 */
function generatePnLReport(ss, mainSheet, startDate, endDate, sendEmailFlag, emailRecipient, assetClassFilter, brokerFilter, userFilter, startTime) {
  // Sheet names
  const PNL_SHEET_NAME = "PnL";
  const PNL_I_SHEET_NAME = "PnL_i";
  const REPORT_SHEET_NAME = "Report";
  const LOG_SHEET_NAME = "Log(s)";
  
  // Retrieve and increment the unique report number
  const reportNumber = getNextReportNumber(ss, LOG_SHEET_NAME);
  const formattedReportNumber = Utilities.formatString("%05d", reportNumber);
  
  // PDF file name
  const pdfName = `Trading_Report_${formattedReportNumber}_${Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "yyyyMMdd_HHmm")}.pdf`;
  
  // Report Drive folder (ensure this folder exists)
  const folderName = "Trading Reports"; // Ensure this folder exists in your Drive
  const folder = getExistingFolder(folderName);
  
  if (!folder) {
    Logger.log(`Folder "${folderName}" not found. Please create it in Google Drive.`);
    mainSheet.getRange("H2").setValue(`Folder "${folderName}" not found.`);
    return;
  }
  
  // Create or clear the Report sheet
  let reportSheet = ss.getSheetByName(REPORT_SHEET_NAME);
  if (reportSheet) {
    reportSheet.clear();
  } else {
    reportSheet = ss.insertSheet(REPORT_SHEET_NAME);
  }
  
  // Insert Report Header (Report Number and Barcode)
  insertReportHeader(reportSheet, formattedReportNumber);
  
  // Apply filters based on Main sheet inputs
  const filters = {
    assetClass: assetClassFilter,
    broker: brokerFilter,
    user: userFilter,
    startDate: startDate,
    endDate: endDate
  };
  
  // Append content from PnL and PnL_i with formatting and totals
  appendPnLSection(ss, reportSheet, PNL_SHEET_NAME, "Delivery Trades", filters);
  appendPnLSection(ss, reportSheet, PNL_I_SHEET_NAME, "Intraday Trades", filters, true); // Start Intraday on new page
  
  // Apply formatting to the Report sheet
  formatReportSheet(reportSheet);
  
  // Calculate report generation time
  const endTime = new Date();
  const timeTakenSeconds = ((endTime - startTime) / 1000).toFixed(2);
  mainSheet.getRange("H2").setValue(`Report generated in ${timeTakenSeconds} seconds`);
  
  // Export to PDF
  const pdfBlob = exportSheetAsPDF(ss, REPORT_SHEET_NAME, pdfName, formattedReportNumber, endTime);
  
  if (pdfBlob) {
    // Save PDF to Drive
    const pdfFile = folder.createFile(pdfBlob);
    Logger.log(`PDF report generated: ${pdfFile.getUrl()}`);
    
    // Email the PDF if required
    if (sendEmailFlag === "yes" && emailRecipient) {
      sendEmailWithReport(pdfFile, formattedReportNumber, endTime, emailRecipient);
    }
    
    // Log the report generation
    logReportGeneration(ss, LOG_SHEET_NAME, formattedReportNumber, "PnL");
    
    // Delete the Report sheet to clean up
    ss.deleteSheet(reportSheet);
  } else {
    Logger.log("PDF blob was not created.");
    mainSheet.getRange("H2").setValue("Failed to generate PDF.");
  }
}

/**
 * Retrieves an existing folder by name. If it doesn't exist, returns null.
 * @param {string} folderName - The name of the folder.
 * @return {Folder|null} - The Google Drive folder or null if not found.
 */
function getExistingFolder(folderName) {
  const folders = DriveApp.getFoldersByName(folderName);
  if (folders.hasNext()) {
    return folders.next();
  } else {
    return null;
  }
}

/**
 * Retrieves the next unique report number from the Log(s) sheet and increments it.
 * @param {Spreadsheet} ss - The active spreadsheet.
 * @param {string} logSheetName - The name of the Log(s) sheet.
 * @return {number} - The unique report number.
 */
function getNextReportNumber(ss, logSheetName) {
  const logSheet = ss.getSheetByName(logSheetName);
  if (!logSheet) {
    Logger.log(`Log sheet "${logSheetName}" not found. Please create it with headers in row 3: Sl, Date time, Note.`);
    throw new Error(`Log sheet "${logSheetName}" not found.`);
  }
  
  const lastRow = logSheet.getLastRow();
  let nextReportNumber = 1;
  
  if (lastRow >= 4) { // Assuming data starts at row 4
    const lastReportNumber = logSheet.getRange(lastRow, 1).getValue();
    if (!isNaN(lastReportNumber) && lastReportNumber >= 1) {
      nextReportNumber = lastReportNumber + 1;
    }
  }
  
  return nextReportNumber;
}

/**
 * Inserts the report header including the report number and barcode into the Report sheet.
 * Ensures barcode is placed in a separate cell to avoid overlap.
 * @param {Sheet} reportSheet - The report sheet.
 * @param {string} reportNumber - The unique 5-digit report number.
 */
function insertReportHeader(reportSheet, reportNumber) {
  // Insert Report Title
  reportSheet.appendRow([`Trading Report - Report #${reportNumber}`]);
  const titleRange = reportSheet.getRange(1, 1, 1, 1);
  titleRange.setFontWeight("bold").setFontSize(16);
  
  // Insert the generation timestamp
  const generatedAt = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "dd-MMM-yyyy HH:mm:ss");
  reportSheet.appendRow([`Generated At: ${generatedAt}`]);
  const genAtRange = reportSheet.getRange(2, 1, 1, 1);
  genAtRange.setFontSize(12);
  
  // Insert a separate cell for Barcode to avoid overlap
  reportSheet.appendRow(['']); // Blank row
  
  // Generate Barcode Image URL using QuickChart
  const barcodeURL = `https://quickchart.io/barcode?text=${reportNumber}&format=png&type=code128&height=50`;
  
  try {
    const response = UrlFetchApp.fetch(barcodeURL);
    const barcodeBlob = response.getBlob().setName(`Barcode_Report_${reportNumber}.png`);
    
    // Insert the barcode image in a separate cell (e.g., Column A, Row 4)
    reportSheet.insertImage(barcodeBlob, 1, 4); // Column 1 (A), Row 4
  } catch (error) {
    Logger.log(`Error fetching barcode image: ${error}`);
  }
  
  // Add a blank row after the barcode
  reportSheet.appendRow(['']);
}

/**
 * Appends a PnL section (Delivery or Intraday) to the Report sheet, including data, formatting, and totals.
 * @param {Spreadsheet} ss - The active spreadsheet.
 * @param {Sheet} reportSheet - The report sheet.
 * @param {string} sheetName - The name of the PnL sheet ("PnL" or "PnL_i").
 * @param {string} sectionTitle - The title of the section ("Delivery Trades" or "Intraday Trades").
 * @param {Object} filters - Filters for Asset Class, Broker, User, Start Date, End Date.
 * @param {boolean} newPage - If true, starts the section on a new page.
 */
function appendPnLSection(ss, reportSheet, sheetName, sectionTitle, filters, newPage = false) {
  const pnlSheet = ss.getSheetByName(sheetName);
  if (!pnlSheet) {
    Logger.log(`Sheet "${sheetName}" not found.`);
    return;
  }
  
  // Apply filters and fetch data
  const { data, formats } = getFilteredData(pnlSheet, filters);
  Logger.log(`${sectionTitle} Sheet filtered row count: ${data.length}`);
  
  if (data.length > 0) {
    if (newPage) {
      // Insert a page break by adding sufficient blank rows (Google Sheets doesn't support explicit page breaks)
      const blankRows = 10; // Adjust as needed for visual separation
      const blankRowData = Array(pnlSheet.getLastColumn()).fill('');
      for (let i = 0; i < blankRows; i++) {
        reportSheet.appendRow(blankRowData);
      }
    }
    
    // Append section title
    const startRow = reportSheet.getLastRow() + 1;
    reportSheet.appendRow([`${sectionTitle} - ${sheetName}`]);
    
    // Apply title formatting
    const titleRange = reportSheet.getRange(startRow, 1, 1, 1);
    titleRange.setFontWeight("bold").setFontSize(14);
    
    // Append headers from row 3 of Log(s) sheet
    const headers = pnlSheet.getRange(3, 1, 1, pnlSheet.getLastColumn()).getValues()[0];
    
    // Add "% PnL" to headers if not present
    if (!headers.includes("% PnL")) {
      headers.push("% PnL");
    }
    reportSheet.appendRow(headers);
    
    // Apply header formatting
    const headerRange = reportSheet.getRange(startRow + 1, 1, 1, headers.length);
    headerRange.setFontWeight("bold").setBackground("#d3d3d3"); // Light gray background for headers
    
    // Append the data
    const dataStartRow = reportSheet.getLastRow() + 1;
    reportSheet.getRange(dataStartRow, 1, data.length, data[0].length).setValues(data);
    
    // Apply data formatting
    const dataRange = reportSheet.getRange(dataStartRow, 1, data.length, data[0].length);
    dataRange.setBackgrounds(formats.backgrounds);
    dataRange.setFontStyles(formats.fontStyles);
    dataRange.setFontSizes(formats.fontSizes);
    dataRange.setNumberFormats(formats.numberFormats);
    dataRange.setFontWeights(formats.fontWeights);
    dataRange.setFontColors(formats.fontColors);
    
    // Determine which columns to sum based on headers
    const columnsToSum = ["Value @ Buy", "Value @ Sell", "Gross PnL", "Fees", "Net Profit/Loss", "% PnL"];
    const columnIndices = getColumnIndices(headers, columnsToSum);
    
    if (columnIndices.length > 0) {
      // Insert Total Row immediately after headers
      insertTotalRowAtTop(reportSheet, startRow + 1, data.length, columnIndices);
    }
  }
}

/**
 * Retrieves and filters data from a sheet based on provided filters.
 * @param {Sheet} sheet - The sheet to retrieve data from.
 * @param {Object} filters - Filters for Asset Class, Broker, User, Start Date, End Date.
 * @return {Object} - An object containing data and formatting arrays.
 */
function getFilteredData(sheet, filters) {
  const startRow = 4; // Header is in row 3, data starts from row 4
  const lastRow = sheet.getLastRow();
  const lastColumn = sheet.getLastColumn();
  
  if (lastRow < startRow + 1) { // No data
    return { data: [], formats: {} };
  }
  
  const dataRange = sheet.getRange(startRow + 1, 1, lastRow - startRow, lastColumn); // Data starts from row 4
  const data = dataRange.getValues();
  
  // Retrieve headers once to avoid multiple calls
  const headers = getHeaders(sheet);
  
  // Date Column based on sheet name
  let dateHeaderName = "";
  const sheetName = sheet.getName();
  if (sheetName === "PnL") {
    dateHeaderName = "Sell Date";
  } else if (sheetName === "PnL_i") {
    dateHeaderName = "Trade date";
  } else {
    throw new Error(`Unknown sheet name "${sheetName}".`);
  }
  
  const dateColumnIndex = headers.indexOf(dateHeaderName) + 1; // 1-based index
  
  if (dateColumnIndex === 0) {
    throw new Error(`"${dateHeaderName}" column not found in headers.`);
  }
  
  // Determine the indices for necessary columns
  const assetClassIndex = headers.indexOf("Asset Class") + 1;
  const brokerIndex = headers.indexOf("Broker") + 1;
  const userIndex = headers.indexOf("User") + 1;
  const netProfitLossIndex = headers.indexOf("Net Profit/Loss") + 1;
  const valueBuyIndex = headers.indexOf("Value @ Buy") + 1;
  
  // Validate essential headers
  if (assetClassIndex === 0 || brokerIndex === 0 || userIndex === 0 || netProfitLossIndex === 0 || valueBuyIndex === 0) {
    throw new Error(`One or more required columns ("Asset Class", "Broker", "User", "Net Profit/Loss", "Value @ Buy") not found in headers.`);
  }
  
  // Filter data based on provided filters
  const filteredData = data.filter((row, idx) => {
    const date = row[dateColumnIndex - 1];
    if (!(date instanceof Date) || isNaN(date)) return false;
    if (filters.startDate && new Date(date) < new Date(filters.startDate)) return false;
    if (filters.endDate && new Date(date) > new Date(filters.endDate)) return false;
    
    if (filters.assetClass !== "ALL" && row[assetClassIndex - 1] !== filters.assetClass) return false;
    if (filters.broker !== "ALL" && row[brokerIndex - 1] !== filters.broker) return false;
    if (filters.user !== "ALL" && row[userIndex - 1] !== filters.user) return false;
    
    return true;
  });
  
  // Get formatting for filtered rows
  const backgrounds = [];
  const fontStyles = [];
  const fontSizes = [];
  const numberFormats = [];
  const fontWeights = [];
  const fontColors = [];
  
  filteredData.forEach((row, idx) => {
    const sheetRow = startRow + 1 + data.indexOf(row);
    const rowRange = sheet.getRange(sheetRow, 1, 1, lastColumn);
    backgrounds.push(rowRange.getBackgrounds()[0]);
    fontStyles.push(rowRange.getFontStyles()[0]);
    fontSizes.push(rowRange.getFontSizes()[0]);
    numberFormats.push(rowRange.getNumberFormats()[0]);
    fontWeights.push(rowRange.getFontWeights()[0]);
    fontColors.push(rowRange.getFontColors()[0]);
  });
  
  // Prepare data with "% PnL" calculation
  const finalData = filteredData.map(row => {
    const netProfitLoss = row[netProfitLossIndex - 1];
    const valueBuy = row[valueBuyIndex - 1];
    const percentPnL = valueBuy !== 0 ? (netProfitLoss / valueBuy) * 100 : 0;
    return [...row, percentPnL.toFixed(2)]; // Adding "% PnL" as the last column
  });
  
  return {
    data: finalData,
    formats: {
      backgrounds: backgrounds.length > 0 ? backgrounds : [[]],
      fontStyles: fontStyles.length > 0 ? fontStyles : [[]],
      fontSizes: fontSizes.length > 0 ? fontSizes : [[]],
      numberFormats: numberFormats.length > 0 ? numberFormats : [[]],
      fontWeights: fontWeights.length > 0 ? fontWeights : [[]],
      fontColors: fontColors.length > 0 ? fontColors : [[]]
    }
  };
}

/**
 * Retrieves headers from the sheet.
 * @param {Sheet} sheet - The sheet from which to retrieve headers.
 * @return {Array} - An array of header names.
 */
function getHeaders(sheet) {
  const headers = sheet.getRange(3, 1, 1, sheet.getLastColumn()).getValues()[0]; // Assuming headers are in row 3
  Logger.log(`Headers in sheet "${sheet.getName()}": ${headers}`);
  return headers;
}

/**
 * Retrieves the indices (1-based) of specified headers in a header row.
 * @param {Array} headers - The array of header names.
 * @param {Array} targetHeaders - The headers to find.
 * @return {Array} - The 1-based column indices of the target headers.
 */
function getColumnIndices(headers, targetHeaders) {
  const indices = [];
  headers.forEach((header, index) => {
    if (targetHeaders.includes(header)) {
      indices.push(index + 1); // Convert to 1-based index
    }
  });
  return indices;
}

/**
 * Inserts a total row by summing specified columns at the top of the data section.
 * @param {Sheet} reportSheet - The report sheet.
 * @param {number} headerRow - The row number where headers are located.
 * @param {number} numRows - The number of data rows.
 * @param {Array<number>} columnIndices - The 1-based indices of the columns to sum.
 */
function insertTotalRowAtTop(reportSheet, headerRow, numRows, columnIndices) {
  const numColumns = reportSheet.getLastColumn();
  const totalRow = [];
  
  for (let i = 1; i <= numColumns; i++) {
    if (i === 1) {
      totalRow.push("Total");
    } else if (columnIndices.includes(i)) {
      const columnLetter = columnToLetter(i);
      const formula = `=SUM(${columnLetter}${headerRow + 1}:${columnLetter}${headerRow + numRows})`;
      totalRow.push(formula);
    } else {
      totalRow.push('');
    }
  }
  
  // Insert the total row immediately after headers
  reportSheet.insertRowAfter(headerRow);
  reportSheet.getRange(headerRow + 1, 1, 1, numColumns).setValues([totalRow]);
  
  // Apply formatting to the total row
  const totalRange = reportSheet.getRange(headerRow + 1, 1, 1, numColumns);
  totalRange.setFontWeight("bold").setBackground("#d3d3d3"); // Light gray background
  
  // Set number formats for total cells (adjust as needed)
  for (let i = 2; i <= numColumns; i++) { // Starting from column B
    if (columnIndices.includes(i)) {
      // Assuming numeric columns; adjust format as needed
      reportSheet.getRange(headerRow + 1, i).setNumberFormat("#,##0.00");
    }
  }
}

/**
 * Converts a column index (1-based) to its corresponding letter (e.g., 1 -> A).
 * @param {number} column - The 1-based column index.
 * @return {string} - The corresponding column letter.
 */
function columnToLetter(column) {
  let temp;
  let letter = '';
  while (column > 0) {
    temp = (column - 1) % 26;
    letter = String.fromCharCode(temp + 65) + letter;
    column = Math.floor((column - temp - 1) / 26);
  }
  return letter;
}

/**
 * Applies formatting to the Report sheet to make it systematic and beautiful.
 * Sets column widths, text alignment, frozen rows, and alternating row colors.
 * @param {Sheet} reportSheet - The report sheet.
 */
function formatReportSheet(reportSheet) {
  // Adjust column widths to fit content
  reportSheet.autoResizeColumns(1, reportSheet.getLastColumn());
  
  // Set text alignment to left for all cells
  const range = reportSheet.getDataRange();
  range.setHorizontalAlignment("left");
  
  // Freeze the top four rows (Report Title, Generated At, blank, Barcode)
  reportSheet.setFrozenRows(4);
  
  // Apply alternating row colors (Zebra Striping) efficiently
  const numRows = reportSheet.getLastRow();
  const numColumns = reportSheet.getLastColumn();
  
  if (numRows > 4) { // Adjust based on frozen rows
    // Define the range excluding headers
    const dataRange = reportSheet.getRange(5, 1, numRows - 4, numColumns);
    
    // Create a 2D array for background colors
    const backgrounds = [];
    for (let i = 0; i < numRows - 4; i++) {
      const isEven = (i + 1) % 2 === 0; // Start from the first data row as odd
      const rowColor = isEven ? "#f9f9f9" : "#ffffff"; // Light gray for even rows
      const rowBackground = Array(numColumns).fill(rowColor);
      backgrounds.push(rowBackground);
    }
    
    // Apply all background colors at once
    dataRange.setBackgrounds(backgrounds);
  }
}

/**
 * Exports a specific sheet as a PDF blob, including report number and generation time.
 * @param {Spreadsheet} ss - The active spreadsheet.
 * @param {string} sheetName - The name of the sheet to export.
 * @param {string} pdfName - The desired PDF file name.
 * @param {string} reportNumber - The unique report number.
 * @param {Date} endTime - The time when the report was generated.
 * @return {Blob|null} - The PDF blob of the sheet or null if export fails.
 */
function exportSheetAsPDF(ss, sheetName, pdfName, reportNumber, endTime) {
  const sheet = ss.getSheetByName(sheetName);
  if (!sheet) {
    Logger.log(`Sheet "${sheetName}" not found.`);
    return null;
  }
  
  const sheetId = sheet.getSheetId();
  const url_base = ss.getUrl().replace(/edit$/, '');
  const exportUrl = url_base + 'export?' +
    'format=pdf&' + // export format
    'size=letter&' + // paper size
    'portrait=true&' + // orientation
    'fitw=true&' + // fit to width
    'sheetnames=false&' + // hide sheet names
    'printtitle=false&' + // hide title
    'pagenumbers=true&' + // show page numbers
    'gridlines=false&' + // hide gridlines
    'fzr=true&' + // repeat frozen rows on each page
    'gid=' + sheetId + '&' + // sheet ID
    'horizontal_alignment=LEFT&' + // alignment
    'vertical_alignment=MIDDLE&' + // vertical alignment
    'top_margin=0.5&' + // margins in inches
    'bottom_margin=0.5&' + 
    'left_margin=0.5&' +
    'right_margin=0.5&';
  
  const options = {
    headers: {
      'Authorization': `Bearer ${ScriptApp.getOAuthToken()}`
    },
    muteHttpExceptions: true
  };
  
  try {
    const response = UrlFetchApp.fetch(exportUrl, options);
    if (response.getResponseCode() !== 200) {
      Logger.log(`Failed to fetch PDF. Response Code: ${response.getResponseCode()}`);
      return null;
    }
    const blob = response.getBlob().setName(pdfName);
    return blob;
  } catch (error) {
    Logger.log(`Error exporting sheet "${sheetName}" as PDF: ${error}`);
    return null;
  }
}

/**
 * Sends an email with the PDF report attached, including the unique report number and generation time.
 * @param {Blob} pdfFile - The PDF file blob to attach.
 * @param {string} reportNumber - The unique report number.
 * @param {Date} endTime - The time when the report was generated.
 * @param {string} emailRecipient - The recipient's email address.
 */
function sendEmailWithReport(pdfFile, reportNumber, endTime, emailRecipient) {
  const recipients = [emailRecipient]; // emailRecipient comes from Main sheet
  const subject = `Trading Report - Report #${reportNumber}`;
  
  const reportGenerationTime = Utilities.formatDate(endTime, Session.getScriptTimeZone(), "dd-MMM-yyyy HH:mm:ss");
  const body = `Please find attached the latest trading report (Report #${reportNumber}) generated on ${reportGenerationTime}.\n\nRegards,\nNeeraj`;
  
  try {
    MailApp.sendEmail({
      to: recipients.join(","),
      subject: subject,
      body: body,
      attachments: [pdfFile]
    });
    Logger.log("PDF report emailed to recipients.");
  } catch (error) {
    Logger.log(`Error sending email: ${error}`);
  }
}

/**
 * Logs the report generation with report number, type, and current date-time in the Log(s) sheet.
 * Also updates cell A2 with the last update time.
 * @param {Spreadsheet} ss - The active spreadsheet.
 * @param {string} logSheetName - The name of the Log(s) sheet.
 * @param {string} reportNumber - The unique report number.
 * @param {string} reportType - The type of report generated.
 */
function logReportGeneration(ss, logSheetName, reportNumber, reportType) {
  const logSheet = ss.getSheetByName(logSheetName);
  if (!logSheet) {
    Logger.log(`Log sheet "${logSheetName}" not found. Please create it with headers in row 3: Sl, Date time, Note.`);
    return;
  }
  
  // Find the next empty row starting from row 4
  const lastRow = logSheet.getLastRow();
  const nextRow = lastRow >= 4 ? lastRow + 1 : 4;
  
  // Prepare log data based on report type
  let note = "";
  if (reportType === "PnL") {
    note = `PnL report generated with Report Number ${reportNumber}`;
  } else {
    note = `${reportType} report generated with Report Number ${reportNumber}`;
  }
  
  // Prepare log data
  const logData = [
    reportNumber, // A: Sl (Seq Number)
    Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "dd-MMM-yyyy HH:mm:ss"), // B: Date time
    note, // C: Note
    '', // D: (Reserved for future use)
    '', // E: 
    '', // F: 
    ''  // G: 
  ];
  
  // Fill A4:G accordingly
  logSheet.getRange(nextRow, 1, 1, logData.length).setValues([logData]);
  
  // Update the last updated time in A2
  logSheet.getRange("A2").setValue(Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "dd-MMM-yyyy HH:mm:ss"));
}

/**
 * Converts a column index (1-based) to its corresponding letter (e.g., 1 -> A).
 * @param {number} column - The 1-based column index.
 * @return {string} - The corresponding column letter.
 */
function columnToLetter(column) {
  let temp;
  let letter = '';
  while (column > 0) {
    temp = (column - 1) % 26;
    letter = String.fromCharCode(temp + 65) + letter;
    column = Math.floor((column - temp - 1) / 26);
  }
  return letter;
}

/**
 * Applies formatting to the Report sheet to make it systematic and beautiful.
 * Sets column widths, text alignment, frozen rows, and alternating row colors.
 * @param {Sheet} reportSheet - The report sheet.
 */
function formatReportSheet(reportSheet) {
  // Adjust column widths to fit content
  reportSheet.autoResizeColumns(1, reportSheet.getLastColumn());
  
  // Set text alignment to left for all cells
  const range = reportSheet.getDataRange();
  range.setHorizontalAlignment("left");
  
  // Freeze the top four rows (Report Title, Generated At, blank, Barcode)
  reportSheet.setFrozenRows(4);
  
  // Apply alternating row colors (Zebra Striping) efficiently
  const numRows = reportSheet.getLastRow();
  const numColumns = reportSheet.getLastColumn();
  
  if (numRows > 4) { // Adjust based on frozen rows
    // Define the range excluding headers
    const dataRange = reportSheet.getRange(5, 1, numRows - 4, numColumns);
    
    // Create a 2D array for background colors
    const backgrounds = [];
    for (let i = 0; i < numRows - 4; i++) {
      const isEven = (i + 1) % 2 === 0; // Start from the first data row as odd
      const rowColor = isEven ? "#f9f9f9" : "#ffffff"; // Light gray for even rows
      const rowBackground = Array(numColumns).fill(rowColor);
      backgrounds.push(rowBackground);
    }
    
    // Apply all background colors at once
    dataRange.setBackgrounds(backgrounds);
  }
}

/**
 * Sets up an onEdit trigger to update the Last Record Update Time in cell A2 of "Log(s)" sheet.
 * This function should be run once manually to create the trigger.
 */
function createOnEditTrigger() {
  // Check if the trigger already exists to avoid duplicates
  const triggers = ScriptApp.getProjectTriggers();
  const triggerExists = triggers.some(trigger => 
    trigger.getHandlerFunction() === 'updateLastRecordUpdateTime' &&
    trigger.getEventType() === ScriptApp.EventType.ON_EDIT
  );
  
  if (!triggerExists) {
    ScriptApp.newTrigger('updateLastRecordUpdateTime')
      .forSpreadsheet(SpreadsheetApp.getActive())
      .onEdit()
      .create();
    Logger.log("OnEdit trigger created successfully.");
  } else {
    Logger.log("OnEdit trigger already exists.");
  }
}

/**
 * Trigger function that updates cell A2 in "Log(s)" sheet whenever A4:G are edited manually.
 * Note: This does NOT fire for script-based edits.
 * @param {Event} e - The onEdit event object.
 */
function updateLastRecordUpdateTime(e) {
  const sheet = e.range.getSheet();
  const sheetName = sheet.getName();
  
  if (sheetName === "Log(s)") {
    const editedRow = e.range.getRow();
    const editedColumn = e.range.getColumn();
    
    // Check if the edit is within A4:G (columns 1 to 7, row >=4)
    if (editedRow >= 4 && editedColumn >= 1 && editedColumn <= 7) {
      sheet.getRange("A2").setValue(Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "dd-MMM-yyyy HH:mm:ss"));
    }
  }
}"

ye code ye errro dara h 

"Execution log
5:28:43 PM	Notice	Execution started
5:28:52 PM	Info	Headers in sheet "PnL": ,,Execution time: 4.446 seconds,,Main,,© Neeraj Mandloi,,,,,,,,,,,Most Held:,425 ; MF:L&T_Merged ; 6082,,,Least Held:,NSE:LYCOS ; 1 ; NSE:LYCOS ; 1 ; NSE:LYCOS ; 1 ; NSE:LYCOS ; 1 ; NSE:HINDZINC ; 1 ; NSE:SBIN ; 1 ; NSE:SUBEXLTD ; 1 ; NSE:PLASTIBLEN ; 1 ; NSE:JTEKTINDIA ; 1 ; NSE:ITC ; 1 ; NSE:TIMETECHNO ; 1 ; NSE:TIMETECHNO ; 1 ; NSE:TIMETECHNO ; 1 ; NSE:TIMETECHNO ; 1 ; NSE:TIMETECHNO ; 1 ; NSE:TIMETECHNO ; 1 ; NSE:SBIN ; 1 ; RELIANCE 25 AUG 3000 CALL ; 1 ; IDFC 25 AUG 70 CALL ; 1 ; NSE:RENUKA ; 1 ; NSE:PETRONET ; 1 ; NSE:COCHINSHIP ; 1 ; NSE:JIOFIN ; 1,,,
5:28:52 PM	Error	
Error: "Sell Date" column not found in headers.
getFilteredData	@ 8. PDF Report Beta.gs:318
appendPnLSection	@ 8. PDF Report Beta.gs:225
generatePnLReport	@ 8. PDF Report Beta.gs:95
generateFreePDFReport	@ 8. PDF Report Beta.gs:41"

oh god header row 4 me h dono pnl sheet me