<?php
class Upload_model extends CI_Model {

	private $holiday_model = null;

	public function __construct()
	{
		parent::__construct();
		// Load Holiday model for holiday and Sunday adjustments
		$this->_loadHolidayModel();
	}

	/**
	 * Load Holiday model for due date adjustments
	 */
	private function _loadHolidayModel()
	{
		try {
			$CI =& get_instance();
			$CI->load->model('Holiday_model');
			$this->holiday_model = $CI->Holiday_model;
		} catch (Exception $e) {
			// If Holiday_model fails to load, continue without it
			$this->holiday_model = null;
			log_message('error', 'Failed to load Holiday_model: ' . $e->getMessage());
		}
	}

	/**
	 * Get adjusted due date considering holidays and Sundays
	 */
	public function getAdjustedDueDate($dueDay, $month, $year)
	{
		// 🛡️ HARD SAFETY CASTS
		$dueDay = (int)$dueDay;
		$month  = (int)$month;
		$year   = (int)$year;

		// Prevent impossible values
		if ($month < 1)  $month = 1;
		if ($month > 12) $month = 12;
		if ($year < 2000) $year = (int)date('Y');

		// If due day is missing or invalid, default to 1
		if ($dueDay <= 0) {
			$dueDay = 1;
		}

		// If holiday model exists, still pass SAFE values
		if ($this->holiday_model) {
			return $this->holiday_model->getAdjustedDueDate($dueDay, $month, $year);
		}

		// Fallback logic (no holiday adjustments)
		$lastDay = (int)date('t', mktime(0, 0, 0, $month, 1, $year));

		// Prevent day overflow like 31 Feb
		$adjustedDay = min($dueDay, $lastDay);

		return [
			'original_day' => $dueDay,
			'day'          => $adjustedDay,
			'date'         => sprintf('%04d-%02d-%02d', $year, $month, $adjustedDay),
			'formatted'    => date('d M Y', mktime(0, 0, 0, $month, $adjustedDay, $year)),
			'adjusted'     => ($dueDay > $lastDay),
			'reason'       => ($dueDay > $lastDay) ? 'Adjusted to last day of month' : ''
		];
	}

	public function getAuthorities($company_id = null)
	{
		// Use raw SQL to avoid CodeIgniter quoting boolean values
		if (!empty($company_id)) {
			$sql = "SELECT a.id as authority_id, a.authority_name, a.alias_name 
				FROM authority a
				INNER JOIN company_authorities ca 
					ON ca.authority_id = a.id 
					AND ca.company_id = ? 
					AND ca.is_enabled = true
				WHERE a.status = '1' AND a.is_delete = '0'
				GROUP BY a.id, a.authority_name, a.alias_name
				ORDER BY a.authority_name ASC";
			return $this->db->query($sql, array((int)$company_id))->result_array();
		}
		
		// No company filter - return all active authorities
		$sql = "SELECT a.id as authority_id, a.authority_name, a.alias_name 
			FROM authority a
			WHERE a.status = '1' AND a.is_delete = '0'
			GROUP BY a.id, a.authority_name, a.alias_name
			ORDER BY a.authority_name ASC";
		return $this->db->query($sql)->result_array();
	}
	
	/**
	 * Get authorities by specific IDs (for permission-based filtering)
	 */
	public function getAuthoritiesByIds($authority_ids)
	{
		if (empty($authority_ids)) {
			return [];
		}
		
		return $this->db->select('a.id as authority_id, a.authority_name, a.alias_name')
			->from('authority a')
			->where('a.status', '1')
			->where('a.is_delete', '0')
			->where_in('a.id', $authority_ids)
			->order_by('a.authority_name', 'asc')
			->get()
			->result_array();
	}

	public function getTypesByAuthority($authority_id, $company_id, $month, $year)
	{
		// Use raw SQL to avoid CodeIgniter quoting boolean values
		$sql = "SELECT DISTINCT st.id, st.type_name, st.frequency, st.frequency_start_date, st.alias_name, 
				COALESCE(cdh.custom_start_date, st.document_start_date) as document_start_date, st.authority_id
			FROM sub_type st
			INNER JOIN company_document_heads cdh 
				ON cdh.type_id = st.id 
				AND cdh.company_id = ? 
				AND cdh.is_enabled = true
			INNER JOIN mandatory_documents md ON st.id = md.type_id
			WHERE st.authority_id = ? AND st.status = '1'
			ORDER BY st.type_name ASC";
		return $this->db->query($sql, array((int)$company_id, $authority_id))->result();
	}

    public function getDocumentsByType($type_id)
    {
        // Get document sequence from sub_type.document_name
        $sub_type = $this->db->select('document_name')
                             ->where('id', $type_id)
                             ->get('sub_type')
                             ->row();
        $doc_sequence = [];
        if ($sub_type && !empty($sub_type->document_name)) {
            $doc_sequence = array_map('intval', array_filter(explode(',', $sub_type->document_name)));
        }

        $this->db->select('d.id, d.document_name, md.mandatory');
        $this->db->from('mandatory_documents md');
        $this->db->join('documents d', 'd.id = md.document_id', 'left');
        $this->db->where('md.type_id', $type_id);
        $docs = $this->db->get()->result_array();

        // Sort according to sequence from sub_type.document_name
        if (!empty($doc_sequence)) {
            usort($docs, function($a, $b) use ($doc_sequence) {
                $posA = array_search((int)$a['id'], $doc_sequence);
                $posB = array_search((int)$b['id'], $doc_sequence);
                if ($posA === false) $posA = PHP_INT_MAX;
                if ($posB === false) $posB = PHP_INT_MAX;
                return $posA - $posB;
            });
        }

        return $docs;
    }

    public function saveUpload($data)
    {
        return $this->db->insert('uploaded_documents', $data);
    }
	public function get_missing_documents_by_company($user_id, $month = null, $year = null)
	{
		$params = [$user_id];
		$monthYearFilter = '';

		// Build month/year condition used inside CASE WHEN
		if (!empty($month) && !empty($year)) {
			$monthYearFilter = "AND LPAD(u.document_month::text, 2, '0') = ? AND u.document_year::text = ?";
			$params[] = $month;
			$params[] = $year;
		} elseif (!empty($year)) {
			$monthYearFilter = "AND u.document_year::text = ?";
			$params[] = $year;
		} elseif (!empty($month)) {
			$monthYearFilter = "AND LPAD(u.document_month::text, 2, '0') = ?";
			$params[] = $month;
		}

		$sql = "
			WITH user_companies AS (
				SELECT 
					unnest(string_to_array(user_company, ','))::int AS company_id,
					unnest(string_to_array(user_type, ','))::int AS type_id
				FROM \"users\"
				WHERE id = ?
			),
			all_docs AS (
				SELECT 
					uc.company_id,
					uc.type_id,
					md.document_id,
					d.document_name,
					c.company_name,
					t.type_name,
					
					CASE 
						WHEN u.document_id IS NULL THEN 1
						ELSE 0
					END AS is_missing,

					CASE 
						WHEN u.document_id IS NOT NULL 
							$monthYearFilter
						THEN 1
						ELSE 0
					END AS is_uploaded

				FROM 
					user_companies uc
				JOIN mandatory_documents md 
					ON uc.type_id = md.type_id
				JOIN documents d 
					ON md.document_id = d.id
				LEFT JOIN uploaded_documents u 
					ON u.company_id = uc.company_id 
					AND u.type_id = uc.type_id 
					AND u.document_id = md.document_id
				LEFT JOIN company c 
					ON uc.company_id = c.id
				LEFT JOIN sub_type t 
					ON uc.type_id = t.id
			)

			SELECT 
				company_id,
				company_name,
				type_id,
				type_name,
				string_agg(d.document_name, ', ' ORDER BY d.document_name) FILTER (WHERE is_missing = 1) AS missing_documents,
				COUNT(*) FILTER (WHERE is_missing = 1) AS missing_count,
				COUNT(*) FILTER (WHERE is_uploaded = 1) AS uploaded_count
			FROM all_docs d
			GROUP BY company_id, company_name, type_id, type_name
			ORDER BY company_name, type_name;
		";

		$query = $this->db->query($sql, $params);
		return $query->result_array();
	}

	public function get_compliance_matrix($user_id, $year, $company_id = null, $authorityIds = null)
	{
		// First, get the user's assigned types from user_type field
		$user = $this->db->select('user_type')->where('id', $user_id)->get('users')->row();
		$userTypeIds = [];
		if ($user && !empty($user->user_type)) {
			$userTypeIds = array_map('intval', array_filter(explode(',', $user->user_type)));
		}
		
		// 1) Get active statutory types that have mandatory documents configured
		// AND are assigned to this user (via user_type field)
		/*$this->db->select('st.id, st.type_name, st.frequency, st.document_start_date')
			->from('sub_type st')
			->join('mandatory_documents md', 'md.type_id = st.id', 'inner') // Only types with mandatory docs
			->where('st.status', '1')
			->where('LOWER(TRIM(st.frequency)) !=', 'one time');*/
		
		$this->db->select("
			st.id,
			st.type_name,
			st.frequency,
			st.document_start_date,
		
			COALESCE(
				NULLIF(cdh.custom_frequency_start_date::text, ''),
				st.frequency_start_date
			) AS due_day,
		
			COALESCE(
				NULLIF(cdh.custom_due_in_same_next_month, '')::int,
				st.due_in_same_next_month::int,
				0
			) AS due_next_month
		", false)
		
		->from('sub_type st')
		->join('mandatory_documents md', 'md.type_id = st.id', 'inner')
		/*->join('company_document_heads cdh', 'cdh.type_id = st.id AND cdh.company_id = '.$this->db->escape($company_id), 'left')*/
		->join(
			'company_document_heads cdh',
			'cdh.type_id = st.id 
			 AND cdh.company_id = '.$this->db->escape($company_id).'
			 AND cdh.is_enabled = TRUE',
			'inner',
			false   // 👈 THIS prevents CI from adding quotes
		)
				
		->where('st.status', '1')
		->where("LOWER(TRIM(st.frequency)) !=", 'one time')
		
		->group_by([
			'st.id',
			'st.type_name',
			'st.frequency',
			'st.document_start_date',
			"COALESCE(NULLIF(cdh.custom_frequency_start_date::text, ''), st.frequency_start_date)",
			"COALESCE(NULLIF(cdh.custom_due_in_same_next_month, '')::int, st.due_in_same_next_month::int, 0)"
		])
		
		->order_by('st.type_name');
		
		// Filter by user's assigned types if available
		if (!empty($userTypeIds)) {
			$this->db->where_in('st.id', $userTypeIds);
		}
		
		// Filter by authority IDs for User role (role 3)
		if (!empty($authorityIds) && is_array($authorityIds)) {
			$this->db->where_in('CAST(st.authority_id AS INT)', $authorityIds, false);
		}
		
		$types = $this->db
			/*->group_by('st.id, st.type_name, st.frequency, st.document_start_date')*/
			->order_by('st.type_name')
			->get()
			->result_array();

		// Remove any "onetime" frequencies
		$types = array_filter($types, function($t) {
			$freq = isset($t['frequency']) ? strtolower(trim($t['frequency'])) : '';
			return $freq !== 'onetime';
		});
		$types = array_values($types);

		// 2) Get total mandatory documents per type
		$totals = $this->db
			->select('type_id, COUNT(*) AS total_docs')
			->from('mandatory_documents')
			->group_by('type_id')
			->get()
			->result_array();

		$totalDocsMap = [];
		foreach ($totals as $t) {
			$typeId = (int)$t['type_id'];
			$totalDocsMap[$typeId] = (int)$t['total_docs'];
		}

		// 3) Get uploaded counts per type & month for this company & year
		$this->db->select('type_id, document_month, COUNT(*) AS uploaded_docs');
		$this->db->from('uploaded_documents');
		
		if (!empty($company_id)) {
			$this->db->where('company_id', $company_id);
		} else {
			$this->db->where('user_id', (string)$user_id);
		}
		
		$this->db->where('document_year', (string)$year);
		$this->db->where('is_deleted', 0);
		$this->db->group_by(['type_id', 'document_month']);
		
		$uploads = $this->db->get()->result_array();

		// 4) Build a map: uploadedMap[type_id][monthInt] = uploaded_docs_count
		$uploadedMap = [];
		foreach ($uploads as $u) {
			$typeId = (int)$u['type_id'];
			$m      = (int)trim($u['document_month']);
			$y      = (int)$year;
		
			if ($m < 1 || $m > 12) continue;
		
			// Get due_next_month flag for this type
			$dueNextMonth = 0;
			foreach ($types as $t) {
				if ((int)$t['id'] === $typeId) {
					$dueNextMonth = (int)($t['due_next_month'] ?? 0);
					break;
				}
			}
		
			// 🔥 SHIFT BACK ONE MONTH IF DUE NEXT MONTH
			/*if ($dueNextMonth === 1) {
				$m = $m - 1;
				if ($m === 0) {
					$m = 12;
				}
			}*/
		
			$uploadedMap[$typeId][$m] = (int)$u['uploaded_docs'];
		}
		
		// 5) Build the matrix - only include types with mandatory documents
		$matrix = [];

		foreach ($types as $t) {

			$typeId   = (int)$t['id'];
			$totalReq = $totalDocsMap[$typeId] ?? 0;
			if ($totalReq === 0) continue;

			$freq = strtolower(trim($t['frequency'] ?? 'monthly'));
			$dueNextMonth = !empty($t['due_next_month']);

			// Document start date
			$docStartMonth = 1;
			$docStartYear  = 2020;
			if (!empty($t['document_start_date'])) {
				$startTs = strtotime($t['document_start_date']);
				if ($startTs) {
					$docStartMonth = (int)date('n', $startTs);
					$docStartYear  = (int)date('Y', $startTs);
				}
			}
			$docStartDate = mktime(0,0,0,$docStartMonth,1,$docStartYear);

			$row = [
				'type_id'   => $typeId,
				'name'      => $t['type_name'],
				'frequency' => $freq,
				'due_next_month' => (int)$t['due_next_month'],
				'due_day'        => (int)$t['due_day'],
				'months'    => []
			];

			for ($periodMonth = 1; $periodMonth <= 12; $periodMonth++) {

				$isApplicable = true;
			
				// Respect document start date
				$startDate = mktime(0,0,0,$docStartMonth,1,$docStartYear);
				$checkDate = mktime(0,0,0,$periodMonth,1,$year);
				if ($checkDate < $startDate) {
					$isApplicable = false;
				}
			
				// Frequency filtering
				if ($freq === 'quarterly' && !in_array($periodMonth, [3,6,9,12])) {
					$isApplicable = false;
				}
				if (in_array($freq, ['half yearly','half-yearly','half yearly']) && !in_array($periodMonth, [6,12])) {
					$isApplicable = false;
				}
				if (in_array($freq, ['yearly','annual']) && $periodMonth != 12) {
					$isApplicable = false;
				}
			
				// -------- DUE DATE CALCULATION (FIXED) --------
				$dueMonth = $periodMonth;
				$dueYear  = $year;
				$dueDay   = (int)($t['due_day'] ?? 1); // from DB

				if (!empty($t['due_next_month'])) {
					$dueMonth++;
					if ($dueMonth > 12) {
						$dueMonth = 1;
						$dueYear++;
					}
				}

				// Handle months with fewer days (Feb, Apr, etc.)
				$lastDay = cal_days_in_month(CAL_GREGORIAN, $dueMonth, $dueYear);
				if ($dueDay > $lastDay) {
					$dueDay = $lastDay;
				}

				// Final correct due date
				$dueDate = mktime(23,59,59, $dueMonth, $dueDay, $dueYear);

				// -------- UPLOAD COUNT FOR THAT DUE MONTH --------
				$uploaded = $uploadedMap[$typeId][$dueMonth] ?? 0;

				$row['months'][$periodMonth] = [
					'uploaded'   => $uploaded,
					'total'      => $totalReq,
					'applicable' => $isApplicable,
					'due_date'   => $dueDate   // ⭐ THIS WAS MISSING
				];
			}			

			$matrix[] = $row;
		}

		return $matrix;
	}

	/**
	 * Check if document upload is allowed based on frequency
	 * Considers holidays, Sundays, and month day limits
	 * Returns: ['allowed' => true/false, 'message' => '...', 'next_due' => '...', 'adjusted_due' => '...']
	 */
	public function checkFrequencyDue($type_id, $company_id, $month, $year)
	{
		try {

		$this->db->select('st.*, t.frequency,c.fiscal_year');
		$this->db->from('company_document_heads st');
		$this->db->join('company c', 'c.id = ' . $this->db->escape($company_id), 'left');
		$this->db->join('sub_type t', 't.id = ' . $type_id, 'left');
		$this->db->where('st.company_id', $company_id);
		$this->db->where('st.type_id', $type_id);
		$type = $this->db->get()->row();
	
		if (!$type) {
			return ['allowed' => false, 'message' => 'Document type not found.'];
		}
		
		$frequency = strtolower(trim($type->frequency ?? 'monthly'));
		$docStartDate = $type->custom_start_date;
		$fiscalYear = $type->fiscal_year ?? 'jan-dec';
		$dueDay = (int)($type->custom_frequency_start_date ?? 0);

		if ($dueDay <= 0) {
			// fallback to master frequency_start_date if custom empty
			$dueDay = (int)($type->frequency_start_date ?? 1);
		}

		if ($dueDay <= 0) {
			$dueDay = 1; // FINAL safety fallback
		}
		
		$selectedMonth = (int)$month;
		$selectedYear = (int)$year;
		
		// SPECIAL CHECK FOR ONE-TIME DOCUMENTS
		// One-time documents can only be uploaded once ever (in any period)
		if ($frequency === 'one time') {
			$oneTimeUploaded = $this->isOneTimeDocumentUploaded($type_id, $company_id);
			if ($oneTimeUploaded['uploaded']) {
				return [
					'allowed' => false,
					'message' => 'This is a one-time document and has already been uploaded for ' . $oneTimeUploaded['period'] . '.',
					'status' => 'one_time_uploaded'
				];
			}
		}

		$dueInNextMonth = (int)($type->custom_due_in_same_next_month ?? 0);

		// -------------------------------------------------
		// BLOCK FUTURE DOCUMENT PERIODS
		// Applies ONLY when custom_due_in_same_next_month = 1
		// -------------------------------------------------
		if ($dueInNextMonth === 1) {

			log_message('error', 'Due-in-next-month logic ACTIVE');
			// Upload opens in NEXT month
			$uploadMonth = $selectedMonth + 1;
			$uploadYear  = $selectedYear;
		
			if ($uploadMonth > 12) {
				$uploadMonth = 1;
				$uploadYear++;
			}
		
			$uploadOpenPeriod = ($uploadYear * 12) + $uploadMonth;
			$nowPeriod = ((int)date('Y') * 12) + (int)date('n');

			log_message('error', 'docPeriod: ' . $docPeriod);
			log_message('error', 'uploadOpenPeriod: ' . $uploadOpenPeriod);
			log_message('error', 'nowPeriod: ' . $nowPeriod);
					
			// ❌ Upload window not opened yet
			if ($nowPeriod < $uploadOpenPeriod) {
				return [
					'allowed' => false,
					'status'  => 'not_generated_yet',
					'message' => 'This document is not yet generated. It can be uploaded from '
						. date('F Y', mktime(0, 0, 0, $uploadMonth, 1, $uploadYear))
				];
			}
		}
		
		// Check upload status for this period
		$uploadStatus = $this->getUploadStatus($type_id, $company_id, $month, $year);
		
		// If ALL documents are uploaded, show "already uploaded" message
		if ($uploadStatus['all_uploaded']) {
			return [
				'allowed' => true, // Allow re-upload/modification
				'message' => 'All documents are already uploaded for this period.',
				'status' => 'all_uploaded',
				'uploaded_count' => $uploadStatus['uploaded'],
				'total_count' => $uploadStatus['total']
			];
		}
		
		// For monthly documents, if we reach here it means either:
		// 1. No document_start_date is set, OR
		// 2. Selected period is >= document_start_date
		// In both cases, monthly is always due
		
		// Check frequency-based due date
		$isDue = $this->_checkIfDueForPeriod($frequency, $docStartDate, $fiscalYear, $selectedMonth, $selectedYear);
		
		// -------------------------------------------------
		// FIX: Prevent next_due before document start date
		// -------------------------------------------------
		if (!empty($docStartDate)) {
			$startTs = strtotime($docStartDate);
			if ($startTs !== false) {
				$startPeriod = ((int)date('Y', $startTs) * 12) + (int)date('n', $startTs);
				$selectedPeriod = ($selectedYear * 12) + $selectedMonth;

				if ($selectedPeriod < $startPeriod) {
					return [
						'allowed' => false,
						'status'  => 'before_start',
						'message' => 'This document is not due yet.',
						'next_due' => date('F Y', $startTs)
					];
				}
			}
		}
		
		if (!$isDue['is_due']) {
			$nextDue = $this->_calculateNextDue($frequency, $docStartDate, $fiscalYear, $selectedMonth, $selectedYear);
			return [
				'allowed' => false,
				'message' => 'This document is not due for ' . date('F', mktime(0, 0, 0, $selectedMonth, 10)) . ' ' . $selectedYear . '.',
				'next_due' => $nextDue,
				'status' => 'not_due'
			];
		}
		
		// Get adjusted due date (considering holidays, Sundays, and month limits)
		$adjustedDue = $this->getAdjustedDueDate($dueDay, $selectedMonth, $selectedYear);
		$today = date('Y-m-d');
		
		// Build response with adjusted due info
		$response = [
			'allowed' => true,
			'message' => 'Document is due for this period. You can upload.',
			'status' => 'due',
			'due_day' => $dueDay,
			'adjusted_due_day' => $adjustedDue['day'],
			'adjusted_due_date' => $adjustedDue['date'],
			'adjusted_due_formatted' => $adjustedDue['formatted']
		];
		
		// Add reason if date was adjusted
		if ($adjustedDue['adjusted']) {
			$response['adjusted_reason'] = $adjustedDue['reason'];
			$response['message'] = 'Document is due for this period. Due date adjusted to ' . $adjustedDue['formatted'] . ' (' . $adjustedDue['reason'] . ')';
		}
		
		return $response;
		
		} catch (Exception $e) {
			return [
				'allowed' => false,
				'message' => 'Error checking frequency: ' . $e->getMessage(),
				'status' => 'error',
				'error' => true
			];
		}
	}
	
	/**
	 * Check if document is already uploaded for a period
	 */
	public function isAlreadyUploaded($type_id, $company_id, $month, $year)
	{
		$this->db->from('uploaded_documents');
		$this->db->where('type_id', $type_id);
		$this->db->where('company_id', $company_id);
		$this->db->where('document_year', (string)$year);
		$this->db->where('document_month', (string)$month);
		$this->db->where('is_deleted', 0);
		return $this->db->count_all_results() > 0;
	}

	/**
	 * Check if a one-time document has been uploaded in ANY period
	 */
	public function isOneTimeDocumentUploaded($type_id, $company_id)
	{
		$query = $this->db->query("SELECT document_month, document_year FROM uploaded_documents WHERE type_id = ? AND company_id = ? AND is_deleted = 0 LIMIT 1", 
			[$type_id, $company_id]);
		$result = $query->row();
		
		if ($result) {
			$monthName = date('F', mktime(0, 0, 0, (int)$result->document_month, 10));
			return [
				'uploaded' => true,
				'period' => $monthName . ' ' . $result->document_year
			];
		}
		
		return ['uploaded' => false];
	}

	/**
	 * Get upload status - returns total docs and uploaded count
	 */
	public function getUploadStatus($type_id, $company_id, $month, $year)
	{
		// Get total mandatory documents for this type - use separate query
		$query1 = $this->db->query("SELECT COUNT(*) as cnt FROM mandatory_documents WHERE type_id = ?", [$type_id]);
		$result1 = $query1->row();
		$totalDocs = $result1 ? (int)$result1->cnt : 0;
		
		// Get uploaded documents count - use separate query
		$query2 = $this->db->query("SELECT COUNT(*) as cnt FROM uploaded_documents WHERE type_id = ? AND company_id = ? AND document_year = ? AND document_month = ? AND is_deleted = 0", 
			[$type_id, $company_id, (string)$year, (string)$month]);
		$result2 = $query2->row();
		$uploadedCount = $result2 ? (int)$result2->cnt : 0;
		
		return [
			'total' => $totalDocs,
			'uploaded' => $uploadedCount,
			'all_uploaded' => ($totalDocs > 0 && $uploadedCount >= $totalDocs),
			'partial_uploaded' => ($uploadedCount > 0 && $uploadedCount < $totalDocs)
		];
	}
	
	/**
	 * Check if document is due for a specific period based on frequency
	 * Uses fiscal year to determine quarter/half-year/year end months
	 */
	private function _checkIfDueForPeriod($frequency, $docStartDate, $fiscalYear, $selectedMonth, $selectedYear)
	{
		// Parse document start date
		$startMonth = 1;
		$startYear = 2024;
		
		if (!empty($docStartDate) && trim($docStartDate) !== '') {
			$startTimestamp = strtotime($docStartDate);
			if ($startTimestamp !== false) {
				$startMonth = (int)date('n', $startTimestamp);
				$startYear = (int)date('Y', $startTimestamp);
			}
		}
		
		// Check if selected period is before document start
		$selectedPeriod = $selectedYear * 12 + $selectedMonth;
		$docStartPeriod = $startYear * 12 + $startMonth;
		
		if ($selectedPeriod < $docStartPeriod) {
			return ['is_due' => false, 'reason' => 'before_start'];
		}
		
		// Get fiscal year start month
		$fiscalStartMonth = $this->_getFiscalStartMonth($fiscalYear);
		
		switch ($frequency) {
			case 'one time':
				// One-time: only due in the document start month
				return ['is_due' => ($selectedMonth == $startMonth && $selectedYear == $startYear), 'reason' => 'one_time'];
				
			/*case 'monthly':
				// Monthly: always due every month from start
				return ['is_due' => true, 'reason' => 'monthly'];*/
				
			case 'quarterly':
				// Quarterly: due at end of each quarter based on fiscal year
				// For Jan-Dec fiscal: March, June, September, December
				// For Apr-Mar fiscal: June, September, December, March
				$quarterEndMonths = $this->_getQuarterEndMonths($fiscalStartMonth);
				return ['is_due' => in_array($selectedMonth, $quarterEndMonths), 'reason' => 'quarterly'];
				
			case 'half yearly':
				// Half yearly: due at end of each half based on fiscal year
				// For Jan-Dec fiscal: June, December
				// For Apr-Mar fiscal: September, March
				$halfYearEndMonths = $this->_getHalfYearEndMonths($fiscalStartMonth);
				return ['is_due' => in_array($selectedMonth, $halfYearEndMonths), 'reason' => 'half_yearly'];
				
			case 'yearly':
				// Yearly: due at end of fiscal year
				// For Jan-Dec fiscal: December
				// For Apr-Mar fiscal: March
				$yearEndMonth = $this->_getYearEndMonth($fiscalStartMonth);
				return ['is_due' => ($selectedMonth == $yearEndMonth), 'reason' => 'yearly'];
				
			default:
				// Unknown frequency - allow upload
				return ['is_due' => true, 'reason' => 'unknown'];
		}
	}
	
	/**
	 * Get fiscal year start month from fiscal_year string
	 */
	private function _getFiscalStartMonth($fiscalYear)
	{
		$fiscalYear = strtolower(trim($fiscalYear ?? 'jan-dec'));
		
		$monthMap = [
			'jan' => 1, 'january' => 1,
			'feb' => 2, 'february' => 2,
			'mar' => 3, 'march' => 3,
			'apr' => 4, 'april' => 4,
			'may' => 5,
			'jun' => 6, 'june' => 6,
			'jul' => 7, 'july' => 7,
			'aug' => 8, 'august' => 8,
			'sep' => 9, 'sept' => 9, 'september' => 9,
			'oct' => 10, 'october' => 10,
			'nov' => 11, 'november' => 11,
			'dec' => 12, 'december' => 12
		];
		
		foreach ($monthMap as $abbr => $monthNum) {
			if (strpos($fiscalYear, $abbr) === 0) {
				return $monthNum;
			}
		}
		
		return 1; // Default to January
	}
	
	/**
	 * Get quarter end months based on fiscal year start
	 */
	private function _getQuarterEndMonths($fiscalStartMonth)
	{
		$quarters = [];
		for ($i = 1; $i <= 4; $i++) {
			$endMonth = (($fiscalStartMonth - 1) + ($i * 3)) % 12;
			if ($endMonth == 0) $endMonth = 12;
			$quarters[] = $endMonth;
		}
		return $quarters;
	}
	
	/**
	 * Get half-year end months based on fiscal year start
	 */
	private function _getHalfYearEndMonths($fiscalStartMonth)
	{
		$halves = [];
		for ($i = 1; $i <= 2; $i++) {
			$endMonth = (($fiscalStartMonth - 1) + ($i * 6)) % 12;
			if ($endMonth == 0) $endMonth = 12;
			$halves[] = $endMonth;
		}
		return $halves;
	}
	
	/**
	 * Get year end month based on fiscal year start
	 */
	private function _getYearEndMonth($fiscalStartMonth)
	{
		$endMonth = $fiscalStartMonth - 1;
		if ($endMonth <= 0) $endMonth = 12;
		return $endMonth;
	}
	
	/**
	 * Calculate next due date based on frequency and fiscal year
	 */
	private function _calculateNextDue($frequency, $docStartDate, $fiscalYear, $selectedMonth, $selectedYear)
	{
		$fiscalStartMonth = $this->_getFiscalStartMonth($fiscalYear);
		
		switch ($frequency) {
			case 'one time':
				return 'One-time document';
				
			case 'monthly':
				// Next month
				$nextMonth = $selectedMonth + 1;
				$nextYear = $selectedYear;
				if ($nextMonth > 12) {
					$nextMonth = 1;
					$nextYear++;
				}
				return date('F', mktime(0, 0, 0, $nextMonth, 1)) . ' ' . $nextYear;
				
			case 'quarterly':
				// Find next quarter end month
				$quarterEndMonths = $this->_getQuarterEndMonths($fiscalStartMonth);
				return $this->_findNextDueMonth($quarterEndMonths, $selectedMonth, $selectedYear);
				
			case 'half yearly':
				// Find next half-year end month
				$halfYearEndMonths = $this->_getHalfYearEndMonths($fiscalStartMonth);
				return $this->_findNextDueMonth($halfYearEndMonths, $selectedMonth, $selectedYear);
				
			case 'yearly':
				// Find next year end month
				$yearEndMonth = $this->_getYearEndMonth($fiscalStartMonth);
				return $this->_findNextDueMonth([$yearEndMonth], $selectedMonth, $selectedYear);
				
			default:
				return 'Unknown frequency';
		}
	}
	
	/**
	 * Find the next due month from a list of valid months
	 */
	private function _findNextDueMonth($validMonths, $currentMonth, $currentYear)
	{
		sort($validMonths);
		
		// First check if there's a valid month later in the current year
		foreach ($validMonths as $month) {
			if ($month > $currentMonth) {
				return date('F', mktime(0, 0, 0, $month, 1)) . ' ' . $currentYear;
			}
		}
		
		// Otherwise, it's the first valid month of next year
		$nextMonth = $validMonths[0];
		return date('F', mktime(0, 0, 0, $nextMonth, 1)) . ' ' . ($currentYear + 1);
	}
}