<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
ini_set('max_execution_time', '0');
ini_set('memory_limit', '1024M');

defined('BASEPATH') or exit('No direct script access allowed');

include_once APPPATH . 'core/Admin_Controller.php';

class Report extends Admin_Controller
{
    public function __construct()
    {
        parent::__construct();
        $this->load->model('report_m');
        $this->load->database();
        $this->load->helper(array('report', 'rbac'));
        $this->load->library('pdf_export');
        $this->load->library('pdf_table_export');
        $this->load->library('excel_export');
        $this->load->library('word_export');
        date_default_timezone_set('Asia/Kolkata');
    }

    /**
     * Enforce view/export permission for the current report method.
     */
    protected function gate_report($module_key = null)
    {
        if ($module_key === null) {
            $module_key = $this->router->fetch_method();
        }
        $this->data['permission_module'] = $module_key;
        $action = $this->is_export() ? 'export' : 'view';
        $this->require_permission($module_key, $action);
    }

    /**
     * Stream a PDF via Dompdf. Large table reports are auto-chunked to avoid Cellmap OOM.
     * Returns false when multi-part PDF cannot be merged into one file (caller should show Excel message).
     *
     * @param string $view
     * @param array  $data
     * @param string $filename
     * @param string $orientation
     * @param string $paper
     * @param array  $extra
     * @param array  $options rows_key, chunk_size, no_chunk, fetch_batch (callable), total_rows
     * @return bool|void false on merge failure; exits on successful download
     */
    protected function stream_pdf($view, $data, $filename, $orientation = 'landscape', $paper = 'A4', $extra = array(), $options = array())
    {
        @ini_set('memory_limit', '2048M');
        @set_time_limit(0);

        $rows_key = isset($options['rows_key']) ? $options['rows_key'] : 'report';
        $chunk_size = isset($options['chunk_size']) ? max(10, (int) $options['chunk_size']) : 35;
        $no_chunk = !empty($options['no_chunk']);
        $fetch_batch = isset($options['fetch_batch']) && is_callable($options['fetch_batch'])
            ? $options['fetch_batch']
            : null;

        $token = (string) $this->input->post('download_token');
        if ($token === '') {
            $token = (string) $this->input->get('download_token');
        }
        $extra['progress_token'] = $token;
        $extra['progress_chunk_size'] = $chunk_size;

        try {
            // Document-style PDFs (SO/CRV/etc.) or explicit opt-out
            if ($no_chunk) {
                $rows = isset($data[$rows_key]) && is_array($data[$rows_key]) ? $data[$rows_key] : array();
                $extra['progress_rows'] = count($rows);
                $html = $this->load->view($view, $data, true);
                $this->pdf_export->stream($html, $filename, $orientation, $paper, $extra);
                return true;
            }

            // Batched fetch from DB (LAC / very large sets) — never load full result into memory
            if ($fetch_batch) {
                $total_rows = isset($options['total_rows']) ? (int) $options['total_rows'] : 0;
                $offset = 0;
                $chunk_index = 0;
                $known_total_chunks = ($total_rows > 0) ? (int) ceil($total_rows / $chunk_size) : null;
                $extra['progress_rows'] = $total_rows;
                $extra['progress_total_chunks'] = $known_total_chunks ? $known_total_chunks : 0;

                $this->pdf_export->stream_chunks_callback(function ($i) use (
                    $view, $data, $rows_key, $fetch_batch, &$offset, $chunk_size, $total_rows, $known_total_chunks, &$chunk_index
                ) {
                    $rows = call_user_func($fetch_batch, $offset, $chunk_size);
                    if (!is_array($rows) || empty($rows)) {
                        return null;
                    }
                    $view_data = $data;
                    $view_data[$rows_key] = $rows;
                    $view_data['pdf_total_rows'] = $total_rows > 0 ? $total_rows : $offset + count($rows);
                    $view_data['pdf_sno_start'] = $offset + 1;
                    $view_data['show_title'] = ($chunk_index === 0);
                    $is_last = (count($rows) < $chunk_size)
                        || ($known_total_chunks !== null && ($chunk_index + 1) >= $known_total_chunks);
                    $view_data['show_sign'] = $is_last;
                    $view_data['repeat_header'] = true;
                    $view_data['pdf_chunk_index'] = $chunk_index;
                    $view_data['pdf_chunk_count'] = $known_total_chunks;
                    $offset += count($rows);
                    $chunk_index++;
                    $html = $this->load->view($view, $view_data, true);
                    unset($view_data, $rows);
                    return $html;
                }, $filename, $orientation, $paper, $extra);
                return true;
            }

            $rows = isset($data[$rows_key]) && is_array($data[$rows_key]) ? $data[$rows_key] : array();
            $total = count($rows);
            $extra['progress_rows'] = $total;

            // Small sets: single Dompdf render
            if ($total <= $chunk_size) {
                $data['pdf_total_rows'] = $total;
                $data['show_title'] = true;
                $data['show_sign'] = true;
                $data['repeat_header'] = true;
                $html = $this->load->view($view, $data, true);
                $this->pdf_export->stream($html, $filename, $orientation, $paper, $extra);
                return true;
            }

            // In-memory row list — chunk without holding all HTML at once
            $chunks = array_chunk($rows, $chunk_size);
            unset($rows);
            $total_chunks = count($chunks);
            $chunk_i = 0;
            $extra['progress_total_chunks'] = $total_chunks;

            $this->pdf_export->stream_chunks_callback(function ($i) use (
                $view, $data, $rows_key, &$chunks, &$chunk_i, $total, $total_chunks, $chunk_size
            ) {
                if ($chunk_i >= $total_chunks || !isset($chunks[$chunk_i])) {
                    return null;
                }
                $chunk_rows = $chunks[$chunk_i];
                $chunks[$chunk_i] = null;
                $idx = $chunk_i;
                $chunk_i++;

                $view_data = $data;
                $view_data[$rows_key] = $chunk_rows;
                $view_data['pdf_total_rows'] = $total;
                $view_data['pdf_sno_start'] = ($idx * $chunk_size) + 1;
                $view_data['show_title'] = ($idx === 0);
                $view_data['show_sign'] = ($idx === $total_chunks - 1);
                $view_data['repeat_header'] = true;
                $view_data['pdf_chunk_index'] = $idx;
                $view_data['pdf_chunk_count'] = $total_chunks;
                $html = $this->load->view($view, $view_data, true);
                unset($view_data, $chunk_rows);
                return $html;
            }, $filename, $orientation, $paper, $extra);
            return true;
        } catch (Throwable $e) {
            if ($e->getMessage() === 'PDF_MERGE_FAILED') {
                $this->data['export_error'] = 'Unable to create a single PDF from this large report. Please use Excel export instead.';
                return false;
            }
            throw $e;
        }
    }

    /**
     * JSON progress for PDF exports (polled by the browser overlay).
     */
    public function export_progress()
    {
        // Auth already checked in Admin_Controller; release lock for concurrent PDF build
        if (session_status() === PHP_SESSION_ACTIVE) {
            session_write_close();
        }

        header('Content-Type: application/json; charset=utf-8');
        header('Cache-Control: no-store, no-cache, must-revalidate');

        require_once APPPATH . 'libraries/Pdf_progress.php';
        $token = (string) $this->input->get_post('token');
        $token = Pdf_progress::sanitize_token($token);
        if ($token === '') {
            echo json_encode(array('status' => 'missing', 'percent' => 0, 'message' => 'Missing export token.'));
            return;
        }

        $data = Pdf_progress::read($token);
        if (!$data) {
            echo json_encode(array(
                'status' => 'waiting',
                'percent' => 0,
                'message' => 'Waiting for export to start…',
                'eta_seconds' => null,
                'finish_at' => null,
            ));
            return;
        }

        echo json_encode($data);
    }

    protected function export_action()
    {
        return (string) $this->input->post('submitForm');
    }

    protected function is_pdf()
    {
        return $this->export_action() === 'pdf';
    }

    protected function is_excel()
    {
        return $this->export_action() === 'excel';
    }

    protected function is_word()
    {
        return $this->export_action() === 'word';
    }

    protected function is_export()
    {
        return in_array($this->export_action(), array('pdf', 'excel', 'word'), true);
    }

    protected function is_posted()
    {
        return strtoupper($this->input->method()) === 'POST';
    }

    /**
     * Dispatch PDF / Excel / Word after report data is loaded.
     *
     * Config keys:
     * - pdf_view, filename_base, orientation, paper, extra
     * - columns (label=>key) for Excel/Word tables
     * - word_as_pdf_html (bool) use PDF HTML for Word (SO/CRV)
     * - excel_as_pdf_html (bool) use PDF HTML for Excel
     * - rows_key (default report)
     * - pdf_no_chunk (bool) skip Dompdf row chunking (document layouts)
     * - pdf_chunk_size (int) rows per Dompdf part (default 35)
     * - fetch_batch, total_rows (optional batched PDF)
     */
    protected function dispatch_export($config)
    {
        if (!$this->is_export()) {
            return;
        }

        $action = $this->export_action();
        $today = isset($this->data['today']) ? $this->data['today'] : date('Y-m-d');
        $base = isset($config['filename_base']) ? $config['filename_base'] : 'Report';
        $orientation = isset($config['orientation']) ? $config['orientation'] : 'landscape';
        $paper = isset($config['paper']) ? $config['paper'] : 'A4';
        $extra = isset($config['extra']) ? $config['extra'] : array();
        $pdf_view = isset($config['pdf_view']) ? $config['pdf_view'] : '';
        $columns = isset($config['columns']) ? $config['columns'] : array();
        $rows_key = isset($config['rows_key']) ? $config['rows_key'] : 'report';
        $rows = isset($this->data[$rows_key]) ? $this->data[$rows_key] : array();
        $title = isset($this->data['report_title']) ? $this->data['report_title'] : $base;

        if ($action === 'pdf' && $pdf_view) {
            // Release session lock so progress polling can run while PDF is building
            if (session_status() === PHP_SESSION_ACTIVE) {
                session_write_close();
            }

            $no_chunk = !empty($config['pdf_no_chunk']);

            // Document layouts (SO/CRV) stay on Dompdf. Table reports use continuous FPDF
            // so rows fill each page without wasted space between batches.
            if (!$no_chunk && !empty($columns)) {
                $row_count = is_array($rows) ? count($rows) : 0;
                if (!empty($config['fetch_batch']) && is_callable($config['fetch_batch'])) {
                    $total_rows = isset($config['total_rows'])
                        ? (int) $config['total_rows']
                        : (isset($this->data['total_rows']) ? (int) $this->data['total_rows'] : 0);
                    $offset = 0;
                    $batch_size = isset($config['pdf_chunk_size']) ? max(50, (int) $config['pdf_chunk_size']) : 250;
                    $fetch = $config['fetch_batch'];
                    $this->pdf_table_export->stream_callback(
                        $title,
                        $columns,
                        function () use ($fetch, &$offset, $batch_size, $total_rows) {
                            if ($total_rows > 0 && $offset >= $total_rows) {
                                return null;
                            }
                            $batch = call_user_func($fetch, $offset, $batch_size);
                            if (!is_array($batch) || empty($batch)) {
                                return null;
                            }
                            $offset += count($batch);
                            return $batch;
                        },
                        $base . ' - ' . $today . '.pdf',
                        array(
                            'orientation' => $orientation,
                            'paper' => $paper,
                            'today' => $today,
                            'subtitle' => isset($this->data['report_subtitle']) ? $this->data['report_subtitle'] : '',
                            'progress_rows' => $total_rows,
                        )
                    );
                    return;
                }

                $this->pdf_table_export->stream(
                    $title,
                    $columns,
                    $rows,
                    $base . ' - ' . $today . '.pdf',
                    array(
                        'orientation' => $orientation,
                        'paper' => $paper,
                        'today' => $today,
                        'subtitle' => isset($this->data['report_subtitle']) ? $this->data['report_subtitle'] : '',
                        'progress_rows' => $row_count,
                    )
                );
                return;
            }

            $pdf_opts = array(
                'rows_key' => $rows_key,
                'chunk_size' => isset($config['pdf_chunk_size']) ? (int) $config['pdf_chunk_size'] : 35,
                'no_chunk' => $no_chunk,
            );
            if (!empty($config['fetch_batch']) && is_callable($config['fetch_batch'])) {
                $pdf_opts['fetch_batch'] = $config['fetch_batch'];
                $pdf_opts['total_rows'] = isset($config['total_rows'])
                    ? (int) $config['total_rows']
                    : (isset($this->data['total_rows']) ? (int) $this->data['total_rows'] : 0);
            }

            $ok = $this->stream_pdf(
                $pdf_view,
                $this->data,
                $base . ' - ' . $today . '.pdf',
                $orientation,
                $paper,
                $extra,
                $pdf_opts
            );
            if ($ok === false) {
                return;
            }
        }

        if ($action === 'excel') {
            if (!empty($config['excel_as_pdf_html']) && $pdf_view) {
                $html = $this->load->view($pdf_view, $this->data, true);
                $this->excel_export->stream_html($html, $base . ' - ' . $today . '.xls');
            }
            $this->excel_export->stream_table($title, $columns, $rows, $base . ' - ' . $today . '.xls');
        }

        if ($action === 'word') {
            if (!empty($config['word_as_pdf_html']) && $pdf_view) {
                $html = $this->load->view($pdf_view, $this->data, true);
                $this->word_export->stream_html($html, $base . ' - ' . $today . '.doc');
            }
            $this->word_export->stream_table($title, $columns, $rows, $base . ' - ' . $today . '.doc');
        }
    }

    protected function parse_ui_date($value, $fallback = null)
    {
        if ($value === null || $value === '') {
            return $fallback !== null ? $fallback : date('Y-m-d');
        }
        $ts = strtotime(str_replace('/', '-', $value));
        return $ts ? date('Y-m-d', $ts) : ($fallback !== null ? $fallback : date('Y-m-d'));
    }

    /**
     * Autogenerate document numbers with a static prefix (SO / CRV).
     * When $consume is true (export), the counter is incremented and saved.
     * When false (preview), returns the next number without consuming it.
     *
     * @param string $prefix
     * @param bool   $consume
     * @param int    $pad
     * @return string e.g. SO0001
     */
    protected function next_document_number($prefix, $consume = false, $pad = 4)
    {
        $prefix = strtoupper(preg_replace('/[^A-Z0-9]/i', '', (string) $prefix));
        if ($prefix === '') {
            $prefix = 'DOC';
        }
        $file = APPPATH . 'logs/last_' . strtolower($prefix) . '_number.txt';
        $last = file_exists($file) ? (int) file_get_contents($file) : 0;
        $next = $last + 1;
        if ($consume) {
            if (!is_dir(APPPATH . 'logs')) {
                @mkdir(APPPATH . 'logs', 0755, true);
            }
            file_put_contents($file, (string) $next);
        }
        return $prefix . str_pad((string) $next, $pad, '0', STR_PAD_LEFT);
    }

    /* -------------------- Overdue (auto-load) -------------------- */
    public function overdue_report_details()
    {
        $this->gate_report();
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['has_filters'] = false;
        $this->data['report_title'] = 'Overdue Report';
        $this->data['report'] = $this->report_m->get_overdue_report_details() ?: array();
        $this->data['searched'] = true;

        $cols = array(
            'S.No' => 'S_No',
            'Id.No' => 'ID_No',
            'Name' => 'Name',
            'Designation' => 'Designation',
            'Acc No' => 'Acc_No',
            'Title' => 'Title',
            'Author' => 'Author',
            'Issue Date' => 'Issue_Date',
            'Due Date' => 'Due_Date',
            'Overdue Days' => 'Overdue_Days',
        );
        $this->dispatch_export(array(
            'pdf_view' => 'report_pdf/pdf_overdue_report_details',
            'filename_base' => 'Overdue Collection Report',
            'orientation' => 'landscape',
            'columns' => $cols,
        ));

        $this->render_report('report/overdue_report_details', $this->data);
    }

    /* -------------------- Accession (filter by status, server-side pages) -------------------- */
    protected function normalize_status_post($status)
    {
        if ($status === null || $status === '') {
            $status = array('ALL');
        } elseif (!is_array($status)) {
            $status = array($status);
        }
        $status = array_values(array_filter(array_map('strval', $status), function ($v) {
            return $v !== '';
        }));
        if (empty($status)) {
            $status = array('ALL');
        }
        return $status;
    }

    public function accession_register()
    {
        $this->gate_report();
        $this->data['today'] = $today = date('Y-m-d');
        $status = $this->normalize_status_post($this->input->post('status'));
        $this->data['status'] = $status;
        $this->data['status_options'] = $this->report_m->get_accession_status_options() ?: array();
        $this->data['has_filters'] = true;
        $this->data['report_title'] = 'Accession Register';
        $this->data['report'] = array();
        $this->data['searched'] = false;
        $this->data['server_side'] = true;
        $this->data['ajax_url'] = base_url('index.php/report/accession_register_data');

        $this->load->helper('report');
        $cols = report_catalogue_columns('book');

        // Export: load ALL matching rows (may take time for large sets).
        if ($this->is_export()) {
            $this->data['report'] = $this->report_m->get_accession_register_report($status) ?: array();
            $this->data['searched'] = true;
            $this->dispatch_export(array(
                'pdf_view' => 'report_pdf/pdf_accession_register',
                'filename_base' => 'Accession Register',
                'orientation' => 'landscape',
                'paper' => 'A3',
                'columns' => $cols,
            ));
        }

        // Search submit: show table shell; pages load via AJAX (no full dump).
        if ($this->is_posted() && !$this->is_export()) {
            $this->data['searched'] = true;
        }

        $this->render_report('report/accession_register', $this->data);
    }

    /**
     * DataTables server-side JSON for Accession Register.
     */
    public function accession_register_data()
    {
        $this->gate_report('accession_register');

        $status = $this->normalize_status_post($this->input->post('status'));
        $draw = (int) $this->input->post('draw');
        $start = (int) $this->input->post('start');
        $length = (int) $this->input->post('length');
        if ($length < 1) {
            $length = 25;
        }

        $search = '';
        $search_arr = $this->input->post('search');
        if (is_array($search_arr) && isset($search_arr['value'])) {
            $search = trim((string) $search_arr['value']);
        }

        $total = $this->report_m->get_accession_register_count($status, '');
        $filtered = ($search === '')
            ? $total
            : $this->report_m->get_accession_register_count($status, $search);

        $rows = $this->report_m->get_accession_register_page($status, $start, $length, $search) ?: array();

        $this->load->helper('report');
        $cols = report_catalogue_columns('book');
        $keys = array_values($cols);

        $data = array();
        foreach ($rows as $row) {
            $item = array();
            foreach ($keys as $key) {
                $item[$key] = isset($row->$key) ? $row->$key : '';
            }
            $data[] = $item;
        }

        $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode(array(
                'draw' => $draw,
                'recordsTotal' => $total,
                'recordsFiltered' => $filtered,
                'data' => $data,
            )));
    }

    protected function normalize_category_post($category)
    {
        if ($category === null || $category === '') {
            $category = array('All');
        } elseif (!is_array($category)) {
            $category = array($category);
        }
        $category = array_values(array_filter(array_map('strval', $category), function ($v) {
            return $v !== '';
        }));
        if (empty($category)) {
            $category = array('All');
        }
        return $category;
    }

    /* -------------------- Issue / Checkout (from–to) -------------------- */
    public function issue_report()
    {
        $this->gate_report();
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['fdate'] = $this->input->post('fdate') ?: date('d-m-Y', strtotime('-7 days'));
        $this->data['tdate'] = $this->input->post('tdate') ?: date('d-m-Y');
        $this->data['categories'] = $this->report_m->get_categories() ?: array();
        $category = $this->normalize_category_post($this->input->post('category'));
        $this->data['category'] = $category;

        $this->data['report'] = array();
        $this->data['searched'] = false;
        $this->data['has_filters'] = true;
        $this->data['report_title'] = 'Checkout Report';

        $cols = array(
            'S.No' => 'S_No',
            'ID No' => 'ID_No',
            'Name' => 'Name',
            'Designation' => 'Designation',
            'Acc No' => 'Acc_No',
            'Issue Date' => 'Issue_Date',
            'Due Date' => 'Due_Date',
        );

        if ($this->is_posted()) {
            $fdate = $this->parse_ui_date($this->input->post('fdate'));
            $tdate = $this->parse_ui_date($this->input->post('tdate'));
            $this->data['fdate'] = $this->input->post('fdate');
            $this->data['tdate'] = $this->input->post('tdate');
            $this->data['report'] = $this->report_m->get_issue_report($fdate, $tdate, $category) ?: array();
            $this->data['searched'] = true;
            $this->dispatch_export(array(
                'pdf_view' => 'report_pdf/pdf_book_issue_report',
                'filename_base' => 'Checkout Report',
                'orientation' => 'landscape',
                'columns' => $cols,
            ));
        }

        $this->render_report('report/book_issue', $this->data);
    }

    /* -------------------- Return (from–to) -------------------- */
    public function return_report()
    {
        $this->gate_report();
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['fdate'] = $this->input->post('fdate') ?: date('d-m-Y', strtotime('-7 days'));
        $this->data['tdate'] = $this->input->post('tdate') ?: date('d-m-Y');
        $this->data['categories'] = $this->report_m->get_categories() ?: array();
        $category = $this->normalize_category_post($this->input->post('category'));
        $this->data['category'] = $category;

        $this->data['report'] = array();
        $this->data['searched'] = false;
        $this->data['has_filters'] = true;
        $this->data['report_title'] = 'Return Report';

        $cols = array(
            'S.No' => 'S_No',
            'ID No' => 'ID_No',
            'Name' => 'Name',
            'Designation' => 'Designation',
            'Acc No' => 'Acc_No',
            'Due Date' => 'Due_Date',
            'Return Date' => 'Return_Date',
        );

        if ($this->is_posted()) {
            $fdate = $this->parse_ui_date($this->input->post('fdate'));
            $tdate = $this->parse_ui_date($this->input->post('tdate'));
            $this->data['fdate'] = $this->input->post('fdate');
            $this->data['tdate'] = $this->input->post('tdate');
            $this->data['report'] = $this->report_m->get_return_report($fdate, $tdate, $category) ?: array();
            $this->data['searched'] = true;
            $this->dispatch_export(array(
                'pdf_view' => 'report_pdf/pdf_book_return_report',
                'filename_base' => 'Return Report',
                'orientation' => 'landscape',
                'columns' => $cols,
            ));
        }

        $this->render_report('report/book_return', $this->data);
    }

    /* -------------------- Current Checkout (category filter) -------------------- */
    public function current_checkout()
    {
        $this->gate_report();
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['categories'] = $this->report_m->get_categories() ?: array();
        $category = $this->normalize_category_post($this->input->post('category'));
        $this->data['category'] = $category;

        $this->data['report'] = array();
        $this->data['searched'] = false;
        $this->data['has_filters'] = true;
        $this->data['report_title'] = 'Current Checkout Report';

        $cols = array(
            'S.No' => 'S_No',
            'ID No' => 'ID_No',
            'Name' => 'Name',
            'Designation' => 'Designation',
            'Acc No' => 'Acc_No',
            'Issue Date' => 'Issue_Date',
            'Due Date' => 'Due_Date',
        );

        if ($this->is_posted()) {
            $this->data['report'] = $this->report_m->get_current_checkout_report($category) ?: array();
            $this->data['searched'] = true;
            $this->dispatch_export(array(
                'pdf_view' => 'report_pdf/pdf_current_checkout',
                'filename_base' => 'Current Checkout',
                'orientation' => 'landscape',
                'columns' => $cols,
            ));
        }

        $this->render_report('report/current_checkout', $this->data);
    }

    /* -------------------- New Arrivals (auto-load) -------------------- */
    public function new_arrivals()
    {
        $this->gate_report();
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['has_filters'] = false;
        $this->data['report_title'] = 'New Arrivals Report';
        $this->data['report'] = $this->report_m->get_new_arrivals() ?: array();
        $this->data['searched'] = true;

        $cols = array(
            'S.No' => 'S_No',
            'Biblio No' => 'biblionumber',
            'Acc No' => 'Acc_No',
            'ISBN' => 'isbn',
            'Title' => 'Title',
            'Author' => 'Author',
            'Class No' => 'Class_No',
            'Publisher' => 'Publisher',
            'Subject' => 'Subject',
        );
        $this->dispatch_export(array(
            'pdf_view' => 'report_pdf/pdf_new_arrivals',
            'filename_base' => 'New Arrivals',
            'orientation' => 'landscape',
            'columns' => $cols,
        ));

        $this->render_report('report/new_arrivals', $this->data);
    }

    /* -------------------- LAC New Titles (Annexure-A) -------------------- */
    public function lac_new_titles_report()
    {
        $this->gate_report();
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['has_filters'] = true;
        $this->data['report_title'] = 'List of New Titles for LAC Recommendations';
        $this->data['report'] = array();
        $this->data['searched'] = false;
        $this->data['lac_member_count'] = '';
        $this->data['lac_members'] = array();
        $this->data['lac_from_date'] = date('d-m-Y', strtotime('-30 days'));
        $this->data['lac_to_date'] = date('d-m-Y');
        $this->data['preview_limit'] = 200;
        $this->data['period_label'] = date('d/m/Y', strtotime('-30 days')) . ' – ' . date('d/m/Y');

        if ($this->is_posted()) {
            @ini_set('memory_limit', '512M');
            @set_time_limit(0);

            $dates = $this->parse_lac_dates_from_post();
            $this->data['lac_from_date'] = $dates['from_display'];
            $this->data['lac_to_date'] = $dates['to_display'];

            if (!empty($dates['error'])) {
                $this->data['lac_member_count'] = (int) $this->input->post('lac_member_count');
                $this->data['lac_members'] = array();
                $this->data['lac_error'] = $dates['error'];
                $this->render_report('report/lac_new_titles_report', $this->data);
                return;
            }

            $members = $this->parse_lac_members_from_post();
            if (empty($members)) {
                $this->data['lac_member_count'] = (int) $this->input->post('lac_member_count');
                $this->data['lac_members'] = array();
                $this->data['lac_error'] = 'Enter LAC member count and short forms before submitting.';
                $this->render_report('report/lac_new_titles_report', $this->data);
                return;
            }

            $from_date = $dates['from'];
            $to_date = $dates['to'];
            $this->data['lac_members'] = $members;
            $this->data['lac_member_count'] = count($members);
            $this->data['lac_from_ymd'] = $from_date;
            $this->data['lac_to_ymd'] = $to_date;
            $this->data['period_label'] = $this->lac_period_label($from_date, $to_date);
            $this->data['lac_columns'] = $this->build_lac_export_columns($members);
            $this->data['total_rows'] = $this->report_m->count_lac_new_titles_report($from_date, $to_date);
            $this->data['report_subtitle'] = $this->data['period_label'];
            $this->data['searched'] = true;
            $this->data['server_side'] = true;
            $this->data['ajax_url'] = base_url('index.php/report/lac_new_titles_data');

            $cols = $this->data['lac_columns'];
            $total_rows = (int) $this->data['total_rows'];
            $filename_base = 'LAC New Titles ' . preg_replace('/[\\\\\/\:\*\?\"\<\>\|]+/', '-', $this->data['period_label']);
            $fetch_lac = function ($offset, $limit) use ($members, $from_date, $to_date) {
                return $this->attach_lac_member_columns(
                    $this->report_m->get_lac_new_titles_report($limit, $offset, $from_date, $to_date) ?: array(),
                    $members
                );
            };

            // PDF / Excel / Word: same annexure HTML layout + brand header
            if ($this->is_export()) {
                $all = array();
                $offset = 0;
                $batch_size = 2000;
                while ($total_rows <= 0 || $offset < $total_rows) {
                    $batch = $fetch_lac($offset, $batch_size);
                    if (!is_array($batch) || empty($batch)) {
                        break;
                    }
                    $all = array_merge($all, $batch);
                    $offset += count($batch);
                    if (count($batch) < $batch_size) {
                        break;
                    }
                }
                $this->data['report'] = $all;
                $this->data['show_title'] = true;
                $this->data['show_annexure'] = true;
                $this->data['show_sign'] = true;
                $this->data['repeat_header'] = true;
                $this->dispatch_export(array(
                    'pdf_view' => 'report_pdf/pdf_lac_new_titles_report',
                    'filename_base' => $filename_base,
                    'orientation' => 'landscape',
                    'columns' => array(),
                    'word_as_pdf_html' => true,
                    'excel_as_pdf_html' => true,
                    'pdf_no_chunk' => true,
                    'extra' => array(
                        'page_numbers' => true,
                        'page_text' => 'Page {PAGE_NUM} of {PAGE_COUNT}',
                        'page_x' => 380,
                        'page_y' => 575,
                        'page_size' => 9,
                    ),
                ));
            }

            // Screen: table shell only — pages load via AJAX (200 rows at a time)
        }

        $this->render_report('report/lac_new_titles_report', $this->data);
    }

    /**
     * DataTables server-side JSON for LAC New Titles (200 rows per page).
     */
    public function lac_new_titles_data()
    {
        $this->gate_report('lac_new_titles_report');

        $dates = $this->parse_lac_dates_from_post();
        $members = $this->parse_lac_members_from_post();
        if (!empty($dates['error']) || empty($members)) {
            $this->output
                ->set_content_type('application/json')
                ->set_output(json_encode(array(
                    'draw' => (int) $this->input->post('draw'),
                    'recordsTotal' => 0,
                    'recordsFiltered' => 0,
                    'data' => array(),
                    'error' => !empty($dates['error']) ? $dates['error'] : 'Invalid LAC filters.',
                )));
            return;
        }

        $from_date = $dates['from'];
        $to_date = $dates['to'];
        $draw = (int) $this->input->post('draw');
        $start = max(0, (int) $this->input->post('start'));
        $length = (int) $this->input->post('length');
        if ($length < 1) {
            $length = 200;
        }
        if ($length > 500) {
            $length = 500;
        }

        $search = '';
        $search_arr = $this->input->post('search');
        if (is_array($search_arr) && isset($search_arr['value'])) {
            $search = trim((string) $search_arr['value']);
        }

        $total = $this->report_m->count_lac_new_titles_report($from_date, $to_date, '');
        $filtered = ($search === '')
            ? $total
            : $this->report_m->count_lac_new_titles_report($from_date, $to_date, $search);

        $rows = $this->attach_lac_member_columns(
            $this->report_m->get_lac_new_titles_report($length, $start, $from_date, $to_date, $search) ?: array(),
            $members
        );

        $cols = $this->build_lac_export_columns($members);
        $data = array();
        foreach ($rows as $row) {
            $item = array();
            foreach ($cols as $label => $key) {
                $item[$key] = isset($row->$key) ? $row->$key : '';
            }
            $data[] = $item;
        }

        $this->output
            ->set_content_type('application/json')
            ->set_output(json_encode(array(
                'draw' => $draw,
                'recordsTotal' => $total,
                'recordsFiltered' => $filtered,
                'data' => $data,
            )));
    }

    /**
     * Parse / validate LAC from–to date filters (created date).
     *
     * @return array{from:?string,to:?string,from_display:string,to_display:string,error:?string}
     */
    protected function parse_lac_dates_from_post()
    {
        // Prefer ISO dates (from DataTables table data-* attrs) — reliable for AJAX
        $from_ymd = trim((string) $this->input->post('lac_from_ymd'));
        $to_ymd = trim((string) $this->input->post('lac_to_ymd'));
        if (
            preg_match('/^\d{4}-\d{2}-\d{2}$/', $from_ymd)
            && preg_match('/^\d{4}-\d{2}-\d{2}$/', $to_ymd)
        ) {
            if ($from_ymd > $to_ymd) {
                return array(
                    'from' => $from_ymd,
                    'to' => $to_ymd,
                    'from_display' => date('d-m-Y', strtotime($from_ymd)),
                    'to_display' => date('d-m-Y', strtotime($to_ymd)),
                    'error' => 'From Date cannot be greater than To Date.',
                );
            }
            return array(
                'from' => $from_ymd,
                'to' => $to_ymd,
                'from_display' => date('d-m-Y', strtotime($from_ymd)),
                'to_display' => date('d-m-Y', strtotime($to_ymd)),
                'error' => null,
            );
        }

        $from_raw = trim((string) $this->input->post('lac_from_date'));
        $to_raw = trim((string) $this->input->post('lac_to_date'));

        if ($from_raw === '' || $to_raw === '') {
            return array(
                'from' => null,
                'to' => null,
                'from_display' => $from_raw,
                'to_display' => $to_raw,
                'error' => 'Enter both From Date and To Date.',
            );
        }

        $from = $this->parse_lac_ui_date($from_raw);
        $to = $this->parse_lac_ui_date($to_raw);
        if (!$from || !$to) {
            return array(
                'from' => null,
                'to' => null,
                'from_display' => $from_raw,
                'to_display' => $to_raw,
                'error' => 'Enter valid dates (dd-mm-yyyy).',
            );
        }
        if ($from > $to) {
            return array(
                'from' => $from,
                'to' => $to,
                'from_display' => $from_raw,
                'to_display' => $to_raw,
                'error' => 'From Date cannot be greater than To Date.',
            );
        }

        return array(
            'from' => $from,
            'to' => $to,
            'from_display' => $from_raw,
            'to_display' => $to_raw,
            'error' => null,
        );
    }

    /**
     * Parse dd-mm-yyyy / dd/mm/yyyy into Y-m-d (avoids US strtotime ambiguity).
     *
     * @param string $value
     * @return string|null
     */
    protected function parse_lac_ui_date($value)
    {
        $value = trim((string) $value);
        if ($value === '') {
            return null;
        }
        if (preg_match('/^(\d{1,2})[-\/](\d{1,2})[-\/](\d{4})$/', $value, $m)) {
            $d = (int) $m[1];
            $mo = (int) $m[2];
            $y = (int) $m[3];
            if (checkdate($mo, $d, $y)) {
                return sprintf('%04d-%02d-%02d', $y, $mo, $d);
            }
            return null;
        }
        if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) {
            $ts = strtotime($value);
            return $ts ? date('Y-m-d', $ts) : null;
        }
        return null;
    }

    /**
     * @param string $from_date Y-m-d
     * @param string $to_date   Y-m-d
     * @return string
     */
    protected function lac_period_label($from_date, $to_date)
    {
        $from_ts = strtotime($from_date);
        $to_ts = strtotime($to_date);
        if ($from_ts && $to_ts) {
            $from_label = date('d/m/Y', $from_ts);
            $to_label = date('d/m/Y', $to_ts);
            return $from_label === $to_label
                ? $from_label
                : ($from_label . ' – ' . $to_label);
        }
        return date('d/m/Y');
    }

    /**
     * Read LAC member short forms from POST (count + lac_member[]).
     *
     * @return array List of short-form labels
     */
    protected function parse_lac_members_from_post()
    {
        $count = (int) $this->input->post('lac_member_count');
        if ($count < 1) {
            return array();
        }
        if ($count > 20) {
            $count = 20;
        }

        $raw = $this->input->post('lac_member');
        if (!is_array($raw)) {
            $raw = array();
        }

        $members = array();
        for ($i = 0; $i < $count; $i++) {
            $code = isset($raw[$i]) ? trim((string) $raw[$i]) : '';
            $code = preg_replace('/\s+/', '', $code);
            $code = strtoupper($code);
            $code = preg_replace('/[^A-Z0-9._\-]/', '', $code);
            if ($code === '') {
                // Incomplete short form — reject whole set so user fills all boxes
                return array();
            }
            $members[] = $code;
        }

        return $members;
    }

    /**
     * @param array $members
     * @return array label => row key
     */
    protected function build_lac_export_columns(array $members)
    {
        $cols = array(
            'S.No' => 'S_No',
            'Author' => 'Author',
            'Title' => 'Title',
            'Publisher' => 'Publisher',
            'Year' => 'Year',
            'Subject' => 'Subject',
            'ISBN' => 'ISBN',
        );

        foreach ($members as $i => $label) {
            $cols[$label] = 'M' . ($i + 1);
        }

        return $cols;
    }

    /**
     * Attach recommendation cells M1..Mn from Created_By matching member short forms.
     *
     * @param array $rows
     * @param array $members
     * @return array
     */
    protected function attach_lac_member_columns(array $rows, array $members)
    {
        $map = array();
        foreach ($members as $i => $label) {
            $map[strtoupper(trim((string) $label))] = 'M' . ($i + 1);
        }

        foreach ($rows as $row) {
            foreach ($members as $i => $label) {
                $row->{'M' . ($i + 1)} = '';
            }
            $created = '';
            if (isset($row->Created_By)) {
                $created = strtoupper(trim((string) $row->Created_By));
            }
            if ($created !== '' && isset($map[$created])) {
                $row->{$map[$created]} = $created;
            }
        }
        return $rows;
    }

    /* -------------------- Subject Wise -------------------- */
    public function subject_wise_report()
    {
        $this->gate_report();
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['subject'] = $this->input->post('subject');
        $this->data['report'] = array();
        $this->data['searched'] = false;
        $this->data['has_filters'] = true;
        $this->data['report_title'] = 'Subject Wise Report';

        $cols = array(
            'S.No' => 'S_No',
            'Date' => 'Date',
            'Acc No' => 'Accession_Number',
            'Author 1' => 'Author_1',
            'Author 2' => 'Author_2',
            'Title' => 'Title',
            'Sub Title' => 'Sub_Title',
            'Edition' => 'Edition',
            'Subject' => 'Subject',
            'Publisher' => 'Publisher',
            'Place' => 'Place_of_Pub',
            'Year' => 'Year',
            'ISBN' => 'ISBN',
            'Pages' => 'Pages',
            'Vol' => 'Vol',
            'Enc Mat' => 'Enc_Mat',
            'Supplier' => 'Book_Supplier_Name',
            'Invoice No' => 'Invoice_No',
            'Invoice Date' => 'Invoice_Date',
            'Curr Code' => 'Curr_Code',
            'Price' => 'Price',
            'Price INR' => 'Price_INR',
            'Dis%' => 'Dis',
            'Location' => 'Location',
            'Nature' => 'Nature',
            'Status' => 'Status',
            'Remarks' => 'Remarks',
        );

        if ($this->is_posted()) {
            $subject = trim((string) $this->input->post('subject'));
            $this->data['subject'] = $subject;
            $this->data['report'] = $this->report_m->get_subject_wise_report($subject) ?: array();
            $this->data['searched'] = true;
            $this->dispatch_export(array(
                'pdf_view' => 'report_pdf/pdf_subject_wise_report',
                'filename_base' => 'Subject Wise Report',
                'orientation' => 'landscape',
                'paper' => 'A3',
                'columns' => $cols,
            ));
        }

        $this->render_report('report/subject_wise_report', $this->data);
    }

    /* -------------------- Book Report (itype BK) -------------------- */
    public function book_report()
    {
        $this->gate_report();
        $this->load->helper('report');
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['has_filters'] = false;
        $this->data['report_title'] = 'Book Report';
        $this->data['report'] = $this->report_m->get_book_report() ?: array();
        $this->data['searched'] = true;

        $cols = report_catalogue_columns('book');
        $this->dispatch_export(array(
            'pdf_view' => 'report_pdf/pdf_book_report',
            'filename_base' => 'Book Report',
            'orientation' => 'landscape',
            'paper' => 'A3',
            'columns' => $cols,
        ));

        $this->render_report('report/book_report', $this->data);
    }

    /* -------------------- Patent Report (itype PATENT) -------------------- */
    public function patent_report()
    {
        $this->gate_report();
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['has_filters'] = false;
        $this->data['report_title'] = 'Patent Report';
        $this->data['report'] = $this->report_m->get_patent_report() ?: array();
        $this->data['searched'] = true;

        $cols = array(
            'S.No' => 'S_No',
            'Acc No' => 'ACCNO',
            'Author' => 'AUTHOR',
            'Title' => 'TITLE',
            'Country' => 'COUNTRY',
            'Patent No' => 'PATENT_NO',
            'Subject' => 'SUBJECT',
        );
        $this->dispatch_export(array(
            'pdf_view' => 'report_pdf/pdf_patent_report',
            'filename_base' => 'Patent Report',
            'orientation' => 'landscape',
            'columns' => $cols,
        ));

        $this->render_report('report/patent_report', $this->data);
    }

    /* -------------------- Search Catalogue -------------------- */
    public function search_catalogue_report()
    {
        $this->gate_report();
        $this->load->helper('report');
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['barcode'] = $this->input->post('barcode');
        $this->data['report'] = array();
        $this->data['searched'] = false;
        $this->data['has_filters'] = true;
        $this->data['report_title'] = 'Search Catalogue Report';

        $all_cols = array(
            'S.No' => 'S_No',
            'Accession No' => 'Accession_No',
            'Item Type' => 'Item_Type',
            'Title' => 'Title',
            'Author' => 'Author',
            'Subtitle' => 'Subtitle',
            'Edition' => 'Edition',
            'Publisher' => 'Publisher',
            'Year' => 'Year',
            'Place' => 'Place',
            'ISBN' => 'ISBN',
            'Pages' => 'Pages',
            'Subject' => 'Subject',
            'File Path' => 'File_Path',
            'File Name' => 'File_Name',
            'Standard' => 'Standard',
            'Standard No' => 'Standard_No',
            'Period' => 'Period',
            'Country' => 'Country',
            'Patent No' => 'Patent_No',
            'Report No' => 'Report_No',
            'Report Type' => 'Report_Type',
            'Org ID' => 'Org_ID',
            'Sponsor' => 'Sponsor',
            'Invoice No' => 'Invoice_No',
            'Invoice Date' => 'Invoice_Date',
            'Currency Code' => 'Currency_Code',
            'Price (Foreign)' => 'Price_Foreign',
            'Currency' => 'Currency',
            'Price INR' => 'Price_INR',
            'Location' => 'Location',
            'Nature' => 'Nature',
            'Status' => 'Status',
            'Remarks' => 'Remarks',
            'Supplier' => 'Supplier',
        );

        $this->data['columns'] = $all_cols;

        if ($this->is_posted()) {
            $barcode = trim((string) $this->input->post('barcode'));
            $this->data['barcode'] = $barcode;
            $report = $this->report_m->get_search_catalogue_report($barcode) ?: array();
            $this->data['report'] = $report;
            $this->data['searched'] = true;
            $cols = report_filter_empty_columns($all_cols, $report, array('S_No', 'Accession_No'));
            $this->data['columns'] = $cols;
            $this->dispatch_export(array(
                'pdf_view' => 'report_pdf/pdf_search_catalogue_report',
                'filename_base' => 'Search Catalogue Report',
                'orientation' => 'landscape',
                'paper' => 'A3',
                'columns' => $cols,
            ));
        }

        $this->render_report('report/search_catalogue_report', $this->data);
    }

    /* -------------------- Top Books -------------------- */
    public function top_books_report()
    {
        $this->gate_report();
        $this->data['today'] = $today = date('Y-m-d');
        $default_fdate = date('d-m-Y', strtotime('-365 days'));
        $default_tdate = date('d-m-Y');
        $this->data['fdate'] = $this->input->post('fdate') ?: $default_fdate;
        $this->data['tdate'] = $this->input->post('tdate') ?: $default_tdate;
        $limit = (int) ($this->input->post('limit') ?: 10);
        if (!in_array($limit, array(10, 25, 50), true)) {
            $limit = 10;
        }
        $this->data['limit'] = $limit;
        $this->data['has_filters'] = true;
        $this->data['report_title'] = 'Top Books Report';
        $this->data['searched'] = true;

        $fdate = $this->parse_ui_date($this->data['fdate']);
        $tdate = $this->parse_ui_date($this->data['tdate']);
        $this->data['report'] = $this->report_m->get_top_books_report($fdate, $tdate, $limit) ?: array();

        $cols = array(
            'Rank' => 'RANK',
            'Barcode' => 'Barcode',
            'Title' => 'Title',
            'Author' => 'Author',
            'Issue Count' => 'Issue_Count',
        );

        $this->dispatch_export(array(
            'pdf_view' => 'report_pdf/pdf_top_books_report',
            'filename_base' => 'Top Books Report',
            'orientation' => 'landscape',
            'columns' => $cols,
        ));

        $this->render_report('report/top_books_report', $this->data);
    }

    /* -------------------- Top Users -------------------- */
    public function top_users_report()
    {
        $this->gate_report();
        $this->data['today'] = $today = date('Y-m-d');
        $default_fdate = date('d-m-Y', strtotime('-365 days'));
        $default_tdate = date('d-m-Y');
        $this->data['fdate'] = $this->input->post('fdate') ?: $default_fdate;
        $this->data['tdate'] = $this->input->post('tdate') ?: $default_tdate;
        $limit = (int) ($this->input->post('limit') ?: 10);
        if (!in_array($limit, array(10, 25, 50), true)) {
            $limit = 10;
        }
        $this->data['limit'] = $limit;
        $this->data['has_filters'] = true;
        $this->data['report_title'] = 'Top Users Report';
        $this->data['searched'] = true;

        $fdate = $this->parse_ui_date($this->data['fdate']);
        $tdate = $this->parse_ui_date($this->data['tdate']);
        $this->data['report'] = $this->report_m->get_top_users_report($fdate, $tdate, $limit) ?: array();

        $cols = array(
            'Rank' => 'RANK',
            'Id No' => 'ID_No',
            'Name' => 'Name',
            'Designation' => 'Designation',
            'Count' => 'Count',
        );

        $this->dispatch_export(array(
            'pdf_view' => 'report_pdf/pdf_top_users_report',
            'filename_base' => 'Top Users Report',
            'orientation' => 'landscape',
            'columns' => $cols,
        ));

        $this->render_report('report/top_users_report', $this->data);
    }

    /* -------------------- Single Catalogue History -------------------- */
    public function single_book_report()
    {
        $this->gate_report();
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['barcode'] = $this->input->post('barcode');
        $this->data['report'] = array();
        $this->data['searched'] = false;
        $this->data['has_filters'] = true;
        $this->data['report_title'] = 'Single Catalogue History Report';

        $cols = array(
            'S.No' => 'S.No.',
            'ID No' => 'ID_No',
            'Name' => 'Name',
            'Designation' => 'Designation',
            'Acc No' => 'Acc_No',
            'Title' => 'Title',
            'Issue Date' => 'Issue_Date',
            'Renewal Count' => 'Renewal_Count',
            'Last Renewed' => 'lastreneweddate',
            'Due Date' => 'Due_Date',
            'Return Date' => 'Return_Date',
            'Status' => 'Current_Status',
        );

        if ($this->is_posted()) {
            $barcode = $this->input->post('barcode');
            $this->data['barcode'] = $barcode;
            $this->data['report'] = $this->report_m->get_single_book_report($barcode) ?: array();
            $this->data['searched'] = true;
            $this->dispatch_export(array(
                'pdf_view' => 'report_pdf/pdf_single_book_report',
                'filename_base' => 'Single Catalogue History Report',
                'orientation' => 'landscape',
                'columns' => $cols,
            ));
        }

        $this->render_report('report/single_book_report', $this->data);
    }

    /* -------------------- Single User History -------------------- */
    public function single_user_report()
    {
        $this->gate_report();
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['cardnumber'] = $this->input->post('cardnumber');
        $this->data['report'] = array();
        $this->data['searched'] = false;
        $this->data['has_filters'] = true;
        $this->data['report_title'] = 'Single User History Report';

        $cols = array(
            'S.No' => 'S.No.',
            'ID No' => 'ID_No',
            'Name' => 'Name',
            'Designation' => 'Designation',
            'Acc No' => 'Acc_No',
            'Title' => 'Title',
            'Issue Date' => 'Issue_Date',
            'Renewal Count' => 'Renewal_Count',
            'Last Renewed' => 'lastreneweddate',
            'Due Date' => 'Due_Date',
            'Return Date' => 'Return_Date',
            'Status' => 'Current_Status',
        );

        if ($this->is_posted()) {
            $cardnumber = $this->input->post('cardnumber');
            $this->data['cardnumber'] = $cardnumber;
            $this->data['report'] = $this->report_m->get_single_user_report($cardnumber) ?: array();
            $this->data['searched'] = true;
            $this->dispatch_export(array(
                'pdf_view' => 'report_pdf/pdf_single_user_report',
                'filename_base' => 'Single User History Report',
                'orientation' => 'landscape',
                'columns' => $cols,
            ));
        }

        $this->render_report('report/single_user_report', $this->data);
    }

    /* -------------------- Due Clearance -------------------- */
    public function due_clearance()
    {
        $this->gate_report();
        $this->load->helper('number_to_word');
        $this->data['today'] = $today = date('d-m-Y');
        $this->data['cardnumber'] = $this->input->post('cardnumber');
        $this->data['report'] = array();
        $this->data['searched'] = false;
        $this->data['has_filters'] = true;
        $this->data['message'] = '';
        $this->data['printNumber'] = '';
        $this->data['preview_html'] = '';
        $this->data['show_export_preview'] = true;
        $this->data['report_title'] = 'Due Clearance';
        $this->data['report_subtitle'] = 'No Dues Certificate — preview and export';

        if ($this->is_posted()) {
            $cardnumber = $this->input->post('cardnumber');
            $this->data['cardnumber'] = $cardnumber;
            $this->data['searched'] = true;

            $query = $this->db->query('CALL CheckLibraryDues(?)', array($cardnumber));
            $report = $query->result_array();
            $query->free_result();
            $this->db->close();
            $this->db->initialize();

            $this->data['report'] = $report;

            // Always resolve Designation from DESIG attribute (replace Course in UI)
            $patron = $this->report_m->get_borrower_name_designation($cardnumber);
            $this->data['patron_photo'] = $this->report_m->get_borrower_photo_data_uri($cardnumber);
            if (!empty($report) && $patron) {
                foreach ($report as $idx => $row) {
                    if (!empty($patron->Name)) {
                        $report[$idx]['Name'] = $patron->Name;
                    }
                    $report[$idx]['Designation'] = isset($patron->Designation) ? $patron->Designation : '';
                    if (isset($report[$idx]['Course'])) {
                        unset($report[$idx]['Course']);
                    }
                    if (isset($report[$idx]['course'])) {
                        unset($report[$idx]['course']);
                    }
                }
                $this->data['report'] = $report;
            }

            if (!empty($report) && isset($report[0]['Message'])) {
                $message = trim($report[0]['Message']);
                $this->data['message'] = $message;

                if ($message === 'DUES IN THE LIBRARY CANNOT GENERATE FORM' || $message === 'Borrower not found') {
                    $this->data['alert'] = $message;
                } elseif ($message === 'NO DUES IN THE LIBRARY') {
                    $query1 = $this->db->query('CALL sp_update_debarment(?)', array($cardnumber));
                    $query1->free_result();
                    $this->db->close();
                    $this->db->initialize();

                    $file = APPPATH . 'logs/last_due_number.txt';
                    $lastNumber = file_exists($file) ? (int) file_get_contents($file) : 0;
                    $nextNumber = $lastNumber + 1;
                    $formattedNumber = str_pad($nextNumber, 4, '0', STR_PAD_LEFT);
                    file_put_contents($file, $nextNumber);
                    $this->data['printNumber'] = $formattedNumber;

                    $this->data['preview_html'] = $this->load->view('report_pdf/pdf_due_clearence', $this->data, true);

                    $this->dispatch_export(array(
                        'pdf_view' => 'report_pdf/pdf_due_clearence',
                        'filename_base' => 'No Dues Certificate',
                        'orientation' => 'portrait',
                        'paper' => 'A4',
                        'columns' => array(),
                        'word_as_pdf_html' => true,
                        'excel_as_pdf_html' => true,
                        'pdf_no_chunk' => true,
                    ));
                }
            } else {
                $this->data['alert'] = 'Unexpected response from the system. Please try again.';
            }
        }

        $this->render_report('report/due_clearence', $this->data);
    }

    /* -------------------- Material Statistics (auto-load) -------------------- */
    public function overall_material_statistics_report()
    {
        $this->gate_report();
        $this->data['today'] = $today = date('Y-m-d H:i:s');
        $this->data['has_filters'] = false;
        $this->data['report_title'] = 'Overall Material Statistics';
        $this->data = array_merge($this->data, $this->load_material_statistics());
        $this->data['searched'] = true;
        $this->data['today'] = $today;
        $this->data['report'] = isset($this->data['book_report']) ? $this->data['book_report'] : array();

        $this->dispatch_export(array(
            'pdf_view' => 'report_pdf/pdf_overall_material_statistics_report',
            'filename_base' => 'Overall Material Statistics Report',
            'orientation' => 'portrait',
            'columns' => array(),
            'word_as_pdf_html' => true,
            'excel_as_pdf_html' => true,
            'pdf_no_chunk' => true,
        ));

        $this->render_report('report/overall_material_statistics_report', $this->data);
    }

    protected function load_material_statistics()
    {
        // dataKey => [stored procedure, display label]
        $keys = array(
            'al_report' => array('STATISTICS_AL', 'Application Notes'),
            'book_report' => array('STATISTICS_BK', 'Books'),
            'ebook_report' => array('STATISTICS_EB', 'E-Books'),
            'ereport_report' => array('STATISTICS_ER', 'E-Reports'),
            'ir_report' => array('STATISTICS_IR', 'Institutional Repository'),
            'jrl_report' => array('STATISTICS_JRL', 'Journals'),
            'patent_report' => array('STATISTICS_PATENT', 'Patents'),
            'reports_report' => array('STATISTICS_REPORT', 'Reports'),
            'sd_report' => array('STATISTICS_SD', 'Standards Data'),
        );
        $out = array(
            'material_stat_sets' => array(),
        );
        foreach ($keys as $dataKey => $meta) {
            $sp = $meta[0];
            $label = $meta[1];
            $query = $this->db->query('CALL ' . $sp . '()');
            $rows = $query->result_array();
            $query->free_result();
            $this->db->close();
            $this->db->initialize();
            $out[$dataKey] = $rows;
            $out['material_stat_sets'][$label] = $rows;
        }
        return $out;
    }

    /* -------------------- User Report -------------------- */
    public function user_report()
    {
        $this->gate_report();
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['categories'] = $this->report_m->get_categories() ?: array();
        $this->data['rt_options'] = $this->report_m->get_retired_transfer_options() ?: array();

        $category = $this->input->post('category');
        if ($category === null || $category === '') {
            $category = array('All');
        } elseif (!is_array($category)) {
            $category = array($category);
        }
        $category = array_values(array_filter(array_map('strval', $category), function ($v) {
            return $v !== '';
        }));
        if (empty($category)) {
            $category = array('All');
        }

        $retired = $this->input->post('retired_transferred');
        if ($retired === null || $retired === '') {
            $retired = array('All');
        } elseif (!is_array($retired)) {
            $retired = array($retired);
        }
        $retired = array_values(array_filter(array_map('strval', $retired), function ($v) {
            return $v !== '';
        }));
        if (empty($retired)) {
            $retired = array('All');
        }

        $this->data['category'] = $category;
        $this->data['retired_transferred'] = $retired;
        $this->data['description'] = '';
        $this->data['report'] = array();
        $this->data['searched'] = false;
        $this->data['has_filters'] = true;
        $this->data['report_title'] = 'User Report';

        $cols = array(
            'Cardnumber' => 'cardnumber',
            'Name' => 'Name',
            'Designation' => 'Designation',
            'Phone' => 'INT_Phone',
            'Enrollment Date' => 'dateenrolled',
            'Expiry Date' => 'dateexpiry',
            'Division' => 'Division',
            'Retired Transferred' => 'Retired_Transferred',
            'Category' => 'Category',
        );

        if ($this->is_posted()) {
            $labels = array();
            if (in_array('All', $category, true)) {
                $labels[] = 'All Categories';
            } else {
                foreach ($this->data['categories'] as $cat) {
                    $code = isset($cat->categorycode) ? $cat->categorycode : '';
                    if ($code !== '' && in_array($code, $category, true)) {
                        $labels[] = isset($cat->description) ? $cat->description : $code;
                    }
                }
            }
            $this->data['description'] = !empty($labels) ? implode(', ', $labels) : 'Selected';
            $this->data['report'] = $this->report_m->get_user_report($category, $retired) ?: array();
            $this->data['searched'] = true;
            $this->dispatch_export(array(
                'pdf_view' => 'report_pdf/pdf_user_report',
                'filename_base' => 'User Report',
                'orientation' => 'landscape',
                'columns' => $cols,
            ));
        }

        $this->render_report('report/user_report', $this->data);
    }

    /* -------------------- Search User -------------------- */
    public function search_user_report()
    {
        $this->gate_report();
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['cardnumber'] = $this->input->post('cardnumber');
        $this->data['report'] = array();
        $this->data['searched'] = false;
        $this->data['has_filters'] = true;
        $this->data['report_title'] = 'Search User Report';

        $cols = array(
            'Cardnumber' => 'cardnumber',
            'Name' => 'Name',
            'Phone' => 'INT_Phone',
            'Enrollment Date' => 'dateenrolled',
            'Expiry Date' => 'dateexpiry',
            'Designation' => 'Designation',
            'Division' => 'Division',
            'Retired Transferred' => 'Retired_Transferred',
            'Category' => 'Category_Description',
        );

        if ($this->is_posted()) {
            $cardnumber = trim((string) $this->input->post('cardnumber'));
            $this->data['cardnumber'] = $cardnumber;
            $this->data['report'] = $this->report_m->get_search_user_report($cardnumber) ?: array();
            $this->data['searched'] = true;
            $this->dispatch_export(array(
                'pdf_view' => 'report_pdf/pdf_search_user_report',
                'filename_base' => 'Search User Report',
                'orientation' => 'landscape',
                'columns' => $cols,
            ));
        }

        $this->render_report('report/search_user_report', $this->data);
    }

    /* -------------------- Publisher Year -------------------- */
    public function publisher_year_wise_report()
    {
        $this->gate_report();
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['year'] = $this->input->post('year') ?: date('Y');
        $this->data['report'] = array();
        $this->data['searched'] = false;
        $this->data['has_filters'] = true;
        $this->data['report_title'] = 'Publication Year Wise Report';

        $cols = array(
            'Barcode' => 'barcode',
            'D.O.Entry' => 'Date_of_Entry',
            'Call No' => 'itemcallnumber',
            'Title' => 'title',
            'Author' => 'author',
            'Item Type' => 'itype',
            'Home Branch' => 'homebranch',
            'Publisher Year' => 'Publisher_Year',
        );

        if ($this->is_posted()) {
            $year = $this->input->post('year');
            $this->data['year'] = $year;
            $this->data['report'] = $this->report_m->get_publisher_year_wise_report($year) ?: array();
            $this->data['searched'] = true;
            $this->dispatch_export(array(
                'pdf_view' => 'report_pdf/pdf_publisher_year_wise_report',
                'filename_base' => 'Publication Year Wise Report',
                'orientation' => 'landscape',
                'columns' => $cols,
            ));
        }

        $this->render_report('report/publisher_year_wise_report', $this->data);
    }

    /* -------------------- Accession Year Wise -------------------- */
    public function accession_year_wise_report()
    {
        $this->gate_report();
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['year'] = $this->input->post('year') ?: date('Y');
        $this->data['report'] = array();
        $this->data['searched'] = false;
        $this->data['has_filters'] = true;
        $this->data['report_title'] = 'Accession Year Wise Report';

        $this->load->helper('report');
        // Same headers/columns as Barcode Range Book report
        $cols = report_catalogue_columns('book');

        if ($this->is_posted()) {
            $year = $this->input->post('year');
            $this->data['year'] = $year;
            $this->data['report'] = $this->report_m->get_accession_year_wise_report($year) ?: array();
            $this->data['searched'] = true;

            $this->data['report_title'] = 'Accession Register Report';
            $this->data['report_subtitle'] = 'Accession Year: ' . $year;
            $this->dispatch_export(array(
                'pdf_view' => 'report_pdf/pdf_accession_year_wise_report',
                'filename_base' => 'Accession Year Wise Report',
                'orientation' => 'landscape',
                'paper' => 'A3',
                'columns' => $cols,
            ));
            $this->data['report_title'] = 'Accession Year Wise Report';
            $this->data['report_subtitle'] = '';
        }

        $this->render_report('report/accession_year_wise_report', $this->data);
    }

    /* -------------------- Barcode Range -------------------- */
    public function barcode_range_report()
    {
        $this->gate_report();
        $this->load->helper('report');
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['from_barcode'] = $this->input->post('from_barcode');
        $this->data['to_barcode'] = $this->input->post('to_barcode');
        $this->data['itemtypes'] = $this->report_m->get_itemtypes() ?: array();
        $this->data['itype'] = $this->input->post('itype') ?: '';
        $this->data['report'] = array();
        $this->data['searched'] = false;
        $this->data['has_filters'] = true;
        $this->data['report_title'] = 'Barcode Range Report';
        $this->data['columns'] = array();

        if ($this->is_posted()) {
            $from_barcode = trim((string) $this->input->post('from_barcode'));
            $to_barcode = trim((string) $this->input->post('to_barcode'));
            $itype = trim((string) $this->input->post('itype'));
            if (strtoupper($itype) === 'ALL') {
                $itype = '';
            }
            $this->data['from_barcode'] = $from_barcode;
            $this->data['to_barcode'] = $to_barcode;
            $this->data['itype'] = $itype;

            $profile = $itype !== '' ? report_itype_profile($itype) : '';
            $all_cols = $profile !== '' ? report_catalogue_columns($profile) : array();

            if ($from_barcode !== '' && $to_barcode !== '' && $itype !== '') {
                $from_num = (int) preg_replace('/\D+/', '', $from_barcode);
                $to_num = (int) preg_replace('/\D+/', '', $to_barcode);
                if ($from_num > 0 && $to_num > 0 && $from_num > $to_num) {
                    $tmp = $from_barcode;
                    $from_barcode = $to_barcode;
                    $to_barcode = $tmp;
                    $this->data['from_barcode'] = $from_barcode;
                    $this->data['to_barcode'] = $to_barcode;
                }
                $report = $this->report_m->get_barcode_range_report($from_barcode, $to_barcode, $itype) ?: array();
                $this->data['report'] = $report;

                if ($profile === 'all') {
                    $always_keep = array('S_No', 'Accession_No', 'Item_Type', 'Title');
                    $this->data['columns'] = report_filter_empty_columns(
                        $all_cols,
                        $report,
                        $always_keep
                    );
                } else {
                    $this->data['columns'] = $all_cols;
                }
            }
            $this->data['searched'] = true;
            $cols = !empty($this->data['columns']) ? $this->data['columns'] : $all_cols;

            $itype_label = $itype;
            foreach ($this->data['itemtypes'] as $it) {
                $code = is_object($it) ? $it->itemtype : (isset($it['itemtype']) ? $it['itemtype'] : '');
                if ($code === $itype) {
                    $desc = is_object($it)
                        ? (isset($it->description) ? $it->description : $code)
                        : (isset($it['description']) ? $it['description'] : $code);
                    $itype_label = ($desc !== '' && $desc !== $code) ? ($desc . ' (' . $code . ')') : $code;
                    break;
                }
            }
            $this->data['itype_label'] = $itype_label;

            $subtitle_parts = array();
            if ($itype !== '') {
                $subtitle_parts[] = 'Item Type: ' . $itype_label;
            }
            if ($from_barcode !== '' && $to_barcode !== '') {
                $subtitle_parts[] = 'From ' . $from_barcode . ' To ' . $to_barcode;
            }
            $this->data['report_subtitle'] = implode(' · ', $subtitle_parts);

            $filename_base = 'Barcode Range Report';
            if ($itype !== '') {
                $filename_base .= ' - ' . $itype;
            }

            $export_title = 'Barcode Range Report';
            if ($itype !== '') {
                $export_title .= ' — Item Type: ' . $itype_label;
            }
            $saved_title = isset($this->data['report_title']) ? $this->data['report_title'] : 'Barcode Range Report';
            $this->data['report_title'] = $export_title;

            $this->dispatch_export(array(
                'pdf_view' => 'report_pdf/pdf_barcode_range_report',
                'filename_base' => $filename_base,
                'orientation' => 'landscape',
                'paper' => 'A3',
                'columns' => $cols,
            ));

            $this->data['report_title'] = $saved_title;
            $this->data['report_subtitle'] = '';
        }

        $this->render_report('report/barcode_range_report', $this->data);
    }

    /* -------------------- Publisher Wise -------------------- */
    public function publisher_person_wise_report()
    {
        $this->gate_report();
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['publishers'] = $this->report_m->get_publisher_code() ?: array();
        $this->data['publisher_name'] = $this->input->post('publisher_name');
        $this->data['report'] = array();
        $this->data['searched'] = false;
        $this->data['has_filters'] = true;
        $this->data['report_title'] = 'Publisher Wise Report';

        $cols = array(
            'Barcode' => 'barcode',
            'D.O.Entry' => 'Date_of_Entry',
            'Call No' => 'itemcallnumber',
            'Title' => 'title',
            'Author' => 'author',
            'Item Type' => 'itype',
            'Home Branch' => 'homebranch',
            'Publisher' => 'Publisher',
        );

        if ($this->is_posted()) {
            $publisher_name = $this->input->post('publisher_name');
            $this->data['publisher_name'] = $publisher_name;
            $this->data['report'] = $this->report_m->get_publisher_person_wise_report($publisher_name) ?: array();
            $this->data['searched'] = true;
            $this->dispatch_export(array(
                'pdf_view' => 'report_pdf/pdf_publisher_person_wise_report',
                'filename_base' => 'Publisher Wise Report',
                'orientation' => 'landscape',
                'columns' => $cols,
            ));
        }

        $this->render_report('report/publisher_person_wise_report', $this->data);
    }

    /* -------------------- Circulation -------------------- */
    public function circulation_report()
    {
        $this->gate_report();
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['fdate'] = $this->input->post('fdate') ?: date('d-m-Y', strtotime('-7 days'));
        $this->data['tdate'] = $this->input->post('tdate') ?: date('d-m-Y');
        $this->data['report'] = array();
        $this->data['searched'] = false;
        $this->data['has_filters'] = true;
        $this->data['report_title'] = 'Circulation Report';

        $cols = array(
            'S.No' => 'S_No',
            'ID No' => 'ID_No',
            'Name' => 'Name',
            'Designation' => 'Designation',
            'Acc No' => 'Acc_No',
            'Title' => 'Title',
            'Issue Date' => 'Issue_Date',
            'Due Date' => 'Due_Date',
            'Return Date' => 'Return_Date',
            'Status' => 'Current_Status',
        );

        if ($this->is_posted()) {
            $fdate = $this->parse_ui_date($this->input->post('fdate'));
            $tdate = $this->parse_ui_date($this->input->post('tdate'));
            $this->data['fdate'] = $this->input->post('fdate');
            $this->data['tdate'] = $this->input->post('tdate');
            $this->data['report'] = $this->report_m->get_circulation_report($fdate, $tdate) ?: array();
            $this->data['searched'] = true;
            $this->dispatch_export(array(
                'pdf_view' => 'report_pdf/pdf_circulation_report',
                'filename_base' => 'Circulation Report',
                'orientation' => 'landscape',
                'columns' => $cols,
            ));
        }

        $this->render_report('report/circulation_report', $this->data);
    }

    /**
     * Legacy URL alias — redirects old checkout_report links.
     */
    public function checkout_report()
    {
        redirect(base_url('index.php/report/circulation_report'));
    }

    /* -------------------- Reservation (from–to) -------------------- */
    public function reservation_report()
    {
        $this->gate_report();
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['fdate'] = $this->input->post('fdate') ?: date('d-m-Y', strtotime('-30 days'));
        $this->data['tdate'] = $this->input->post('tdate') ?: date('d-m-Y');
        $this->data['report'] = array();
        $this->data['searched'] = false;
        $this->data['has_filters'] = true;
        $this->data['report_title'] = 'Reservation Report';

        $cols = array(
            'Cardnumber' => 'cardnumber',
            'Name' => 'patron_name',
            'Designation' => 'Designation',
            'Title' => 'title',
            'Author' => 'author',
            'Reserve Date' => 'reservedate',
            'Expiration Date' => 'expirationdate',
            'Hold Status' => 'hold_status',
        );

        if ($this->is_posted()) {
            $fdate = $this->parse_ui_date($this->input->post('fdate'));
            $tdate = $this->parse_ui_date($this->input->post('tdate'));
            $this->data['fdate'] = $this->input->post('fdate');
            $this->data['tdate'] = $this->input->post('tdate');
            $this->data['report'] = $this->report_m->get_reservation_report($fdate, $tdate) ?: array();
            $this->data['searched'] = true;
            $this->dispatch_export(array(
                'pdf_view' => 'report_pdf/pdf_reservation_report',
                'filename_base' => 'Reservation Report',
                'orientation' => 'landscape',
                'columns' => $cols,
            ));
        }

        $this->render_report('report/reservation_report', $this->data);
    }

    /* -------------------- SO -------------------- */
    public function so_report()
    {
        $this->gate_report();
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['basket_name'] = $this->input->post('basket_name');
        $this->data['report'] = array();
        $this->data['searched'] = false;
        $this->data['has_filters'] = true;
        $this->data['show_export_preview'] = true;
        $this->data['preview_view'] = 'report_pdf/pdf_so_report';
        $this->data['report_title'] = 'SO Report';
        $this->data['report_subtitle'] = 'Supply Order — preview annexure items and export layout';

        $cols = array(
            'Order No' => 'Order_number',
            'Author' => 'Author',
            'Title' => 'Title',
            'Publisher' => 'Publisher',
            'Year' => 'Publication_year',
            'ISBN' => 'ISBN',
            'Qty' => 'Quantity',
            'RRP' => 'RRP',
            'Discount' => 'Discount',
            'Basket' => 'Basket_name',
        );

        if ($this->is_posted()) {
            $basket_name = $this->input->post('basket_name');
            $this->data['basket_name'] = $basket_name;
            $this->data['report'] = $this->report_m->get_so_report($basket_name) ?: array();
            $this->data['searched'] = true;
            $this->data['document_number'] = $this->next_document_number('SO', $this->is_export());
            if (!empty($this->data['report'])) {
                $this->data['preview_html'] = $this->load->view('report_pdf/pdf_so_report', $this->data, true);
            }
            $this->dispatch_export(array(
                'pdf_view' => 'report_pdf/pdf_so_report',
                'filename_base' => 'SO Report',
                'orientation' => 'portrait',
                'paper' => 'A4',
                'extra' => array(
                    'page_numbers' => true,
                    'page_x' => 550,
                    'page_y' => 820,
                ),
                'columns' => $cols,
                'word_as_pdf_html' => true,
                'pdf_no_chunk' => true,
            ));
        }

        $this->render_report('report/so_report', $this->data);
    }

    /* -------------------- CRV -------------------- */
    public function crv_report()
    {
        $this->gate_report();
        $this->load->helper('number_to_word');
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['basket_name'] = $this->input->post('basket_name');
        $this->data['report'] = array();
        $this->data['searched'] = false;
        $this->data['has_filters'] = true;
        $this->data['show_export_preview'] = true;
        $this->data['preview_view'] = 'report_pdf/pdf_crv_report';
        $this->data['report_title'] = 'CRV Report';
        $this->data['report_subtitle'] = 'Receipt / Issue and Expense Voucher — preview lines and export layout';

        $cols = array(
            'Acc No' => 'ACC_NO',
            'Author' => 'Author',
            'Title' => 'Title',
            'Publisher' => 'Publisher',
            'Year' => 'Publication_year',
            'ISBN' => 'ISBN',
            'Currency' => 'Currency_code',
            'Price' => 'RRP',
            'Discount' => 'Discount',
            'Basket' => 'Basket_Name',
        );

        if ($this->is_posted()) {
            $basket_name = $this->input->post('basket_name');
            $this->data['basket_name'] = $basket_name;
            $this->data['report'] = $this->report_m->get_crv_report($basket_name) ?: array();
            $this->data['searched'] = true;
            $this->load->helper('settings');
            $this->data['document_number'] = function_exists('app_crv_number') ? app_crv_number() : '';
            if (!empty($this->data['report'])) {
                $this->data['preview_html'] = $this->load->view('report_pdf/pdf_crv_report', $this->data, true);
            }
            $this->dispatch_export(array(
                'pdf_view' => 'report_pdf/pdf_crv_report',
                'filename_base' => 'CRV Report',
                'orientation' => 'landscape',
                'columns' => $cols,
                'word_as_pdf_html' => true,
                'pdf_no_chunk' => true,
            ));
        }

        $this->render_report('report/crv_report', $this->data);
    }

    /* -------------------- Institutional Repository (itype IR) -------------------- */
    public function institutional_repository()
    {
        $this->gate_report();
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['has_filters'] = false;
        $this->data['report_title'] = 'Institutional Repository Data';
        $this->data['report'] = $this->report_m->get_institutional_repository() ?: array();
        $this->data['searched'] = true;

        $cols = array(
            'Barcode' => 'Barcode',
            'Title' => 'Title',
            'Author' => 'Author',
            'Publication Year' => 'Publication_Year',
            'Publication Name' => 'Publication_Name',
            'Period' => 'Period',
            'Pages' => 'Pages',
            'PDF Name' => 'PDF_Name',
            'File Path' => 'File_Path',
        );
        $this->dispatch_export(array(
            'pdf_view' => 'report_pdf/pdf_institutional_repository',
            'filename_base' => 'Institutional Repository Data',
            'orientation' => 'landscape',
            'columns' => $cols,
        ));

        $this->render_report('report/institutional_repository', $this->data);
    }

    /* -------------------- Standards Data (itype SD) -------------------- */
    public function standards_data()
    {
        $this->gate_report();
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['has_filters'] = false;
        $this->data['report_title'] = 'Standards Data';
        $this->data['report'] = $this->report_m->get_standards_data() ?: array();
        $this->data['searched'] = true;

        $cols = array(
            'Accession No.' => 'Accession_No',
            'STANDARDIN' => 'STANDARDIN',
            'STD NO' => 'STD_NO',
            'Title' => 'Title',
            'Document No.' => 'Document_No',
        );
        $this->dispatch_export(array(
            'pdf_view' => 'report_pdf/pdf_standards_data',
            'filename_base' => 'Standards Data',
            'orientation' => 'landscape',
            'columns' => $cols,
        ));

        $this->render_report('report/standards_data', $this->data);
    }

    /* -------------------- EReport Books (itype ER) -------------------- */
    public function ereport_books()
    {
        $this->gate_report();
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['has_filters'] = false;
        $this->data['report_title'] = 'EReport Books';
        $this->data['report'] = $this->report_m->get_ereport_books() ?: array();
        $this->data['searched'] = true;

        $cols = array(
            'Acc Date' => 'Acc_Date',
            'Acc No' => 'Acc_No',
            'Title' => 'Title',
            'Author' => 'Author',
            'Copyright Year' => 'Copyright_Year',
            'Pages' => 'Pages',
            'Publisher' => 'Publisher',
            'File Path' => 'File_Path',
        );
        $this->dispatch_export(array(
            'pdf_view' => 'report_pdf/pdf_ereport_books',
            'filename_base' => 'EReport Books',
            'orientation' => 'landscape',
            'columns' => $cols,
        ));

        $this->render_report('report/ereport_books', $this->data);
    }

    /* -------------------- Application Notes (itype AL) -------------------- */
    public function application_notes_report()
    {
        $this->gate_report();
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['has_filters'] = false;
        $this->data['report_title'] = 'Application Notes Report';
        $this->data['report'] = $this->report_m->get_application_notes_report() ?: array();
        $this->data['searched'] = true;

        $cols = array(
            'S.No.' => 'S_No',
            'Accession No.' => 'Accession_No',
            'Publisher' => 'Publisher',
            'General Note' => 'General_Note',
            'Title' => 'Title',
            'Subject' => 'Subject',
        );
        $this->dispatch_export(array(
            'pdf_view' => 'report_pdf/pdf_application_notes_report',
            'filename_base' => 'Application Notes Report',
            'orientation' => 'landscape',
            'columns' => $cols,
        ));

        $this->render_report('report/application_notes_report', $this->data);
    }

    /* -------------------- Reports Book (itype REPORT) -------------------- */
    public function reports_book_report()
    {
        $this->gate_report();
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['has_filters'] = false;
        $this->data['report_title'] = 'Reports Book Report';
        $this->data['report'] = $this->report_m->get_reports_book_report() ?: array();
        $this->data['searched'] = true;

        $cols = array(
            'S.No.' => 'S_No',
            'Accession No.' => 'Accession_No',
            'REPORTNO' => 'REPORTNO',
            'TITLE' => 'TITLE',
            'SUBTITLE' => 'SUBTITLE',
            'NOOFPAGES' => 'NOOFPAGES',
            'PERORGREPN' => 'PERORGREPN',
            'REPORTTYPE' => 'REPORTTYPE',
            'PERORGID' => 'PERORGID',
            'SPONBODYID' => 'SPONBODYID',
            'LANGID' => 'LANGID',
            'PUBLISHER' => 'PUBLISHER',
            'REPDATE' => 'REPDATE',
        );
        $this->dispatch_export(array(
            'pdf_view' => 'report_pdf/pdf_reports_book_report',
            'filename_base' => 'Reports Book Report',
            'orientation' => 'landscape',
            'columns' => $cols,
        ));

        $this->render_report('report/reports_book_report', $this->data);
    }

    /* -------------------- Journals Book Report (itype JRL) -------------------- */
    public function journals_book_report()
    {
        $this->gate_report();
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['has_filters'] = false;
        $this->data['report_title'] = 'Journals Book Report';
        $this->data['report'] = $this->report_m->get_journals_book_report() ?: array();
        $this->data['searched'] = true;

        $cols = array(
            'S.No.' => 'S_No',
            'ACC_NO' => 'ACC_NO',
            'TITLE' => 'TITLE',
            'VOLUME' => 'VOLUME',
            'PERIOD' => 'PERIOD',
            'YEAR' => 'YEAR',
        );
        $this->dispatch_export(array(
            'pdf_view' => 'report_pdf/pdf_journals_book_report',
            'filename_base' => 'Journals Book Report',
            'orientation' => 'landscape',
            'columns' => $cols,
        ));

        $this->render_report('report/journals_book_report', $this->data);
    }

    /* -------------------- EBook Report (itype EB) -------------------- */
    public function ebook_report()
    {
        $this->gate_report();
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['has_filters'] = false;
        $this->data['report_title'] = 'EBook Report';
        $this->data['report'] = $this->report_m->get_ebook_report() ?: array();
        $this->data['searched'] = true;

        $cols = array(
            'Accession Date' => 'Accession_Date',
            'Accession No' => 'Accession_No',
            'Title' => 'Title',
            'Author' => 'Author',
            'Subject' => 'Subject',
            'Pages' => 'Pages',
            'ISBN' => 'ISBN',
            'Publisher' => 'Publisher',
            'Year' => 'Year',
            'File URL' => 'File_URL',
        );
        $this->dispatch_export(array(
            'pdf_view' => 'report_pdf/pdf_ebook_report',
            'filename_base' => 'EBook Report',
            'orientation' => 'landscape',
            'columns' => $cols,
        ));

        $this->render_report('report/ebook_report', $this->data);
    }

    /* -------------------- Weed Out Report -------------------- */
    public function weed_out_report()
    {
        $this->gate_report();
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['has_filters'] = true;
        $this->data['report_title'] = 'Weed Out Report';
        $this->data['report'] = array();
        $this->data['searched'] = false;
        $this->data['acc_numbers'] = '';
        $this->data['weed_member_count'] = '';
        $this->data['weed_members'] = array();

        $cols = array(
            'SNO' => 'S_No',
            'Acc No' => 'Acc_No',
            'Title' => 'Title',
            'Author' => 'Author',
            'Publisher Name' => 'Publisher_Name',
            'Publisher Year' => 'Publisher_Year',
            'Cost' => 'Cost',
            'Acc Date' => 'Acc_Date',
            'Item Type' => 'Item_Type',
        );

        if ($this->is_posted()) {
            $raw = (string) $this->input->post('acc_numbers');
            $this->data['acc_numbers'] = $raw;
            $this->data['weed_member_count'] = (int) $this->input->post('weed_member_count');
            $members = $this->parse_weed_out_members_from_post();
            $this->data['weed_members'] = $members;
            $this->data['weed_member_count'] = !empty($members) ? count($members) : (int) $this->input->post('weed_member_count');

            $barcodes = $this->parse_weed_out_acc_numbers($raw);
            if (empty($barcodes)) {
                $this->data['weed_error'] = 'Enter one or more accession numbers (comma or line separated).';
            } elseif (empty($members)) {
                $this->data['weed_error'] = 'Enter member count and short forms before submitting.';
            } else {
                $this->data['report'] = $this->report_m->get_weed_out_report($barcodes) ?: array();
                $this->data['searched'] = true;
                $this->data['show_sign'] = true;
                $this->dispatch_export(array(
                    'pdf_view' => 'report_pdf/pdf_weed_out_report',
                    'filename_base' => 'Weed Out Report',
                    'orientation' => 'landscape',
                    'columns' => $cols,
                    'word_as_pdf_html' => true,
                    'excel_as_pdf_html' => true,
                    'pdf_no_chunk' => true,
                    'extra' => array(
                        'page_numbers' => true,
                        'page_text' => 'Page {PAGE_NUM} of {PAGE_COUNT}',
                        'page_x' => 380,
                        'page_y' => 575,
                        'page_size' => 9,
                    ),
                ));
            }
        }

        $this->render_report('report/weed_out_report', $this->data);
    }

    /* -------------------- Price Range Report -------------------- */
    public function price_range_report()
    {
        $this->gate_report();
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['has_filters'] = true;
        $this->data['report_title'] = 'Price Range Report';
        $this->data['report'] = array();
        $this->data['searched'] = false;
        $this->data['itemtypes'] = $this->report_m->get_itemtypes() ?: array();
        $this->data['filter_mode'] = 'price';
        $this->data['start_price'] = '';
        $this->data['end_price'] = '';
        $this->data['from_barcode'] = '';
        $this->data['to_barcode'] = '';
        $this->data['itype'] = '';
        $this->data['row_limit'] = '100';
        $this->data['cost_total'] = 0;
        $this->data['price_error'] = '';

        $this->load->helper('report');
        $cols = report_catalogue_columns('book');

        if ($this->is_posted()) {
            $mode = strtolower(trim((string) $this->input->post('filter_mode')));
            if (!in_array($mode, array('all', 'accession', 'price'), true)) {
                $mode = 'price';
            }
            $start_raw = trim((string) $this->input->post('start_price'));
            $end_raw = trim((string) $this->input->post('end_price'));
            $from_barcode = trim((string) $this->input->post('from_barcode'));
            $to_barcode = trim((string) $this->input->post('to_barcode'));
            $itype = trim((string) $this->input->post('itype'));
            $limit_raw = trim((string) $this->input->post('row_limit'));

            $this->data['filter_mode'] = $mode;
            $this->data['start_price'] = $start_raw;
            $this->data['end_price'] = $end_raw;
            $this->data['from_barcode'] = $from_barcode;
            $this->data['to_barcode'] = $to_barcode;
            $this->data['itype'] = $itype;
            $this->data['row_limit'] = $limit_raw !== '' ? $limit_raw : '100';

            $limit = (int) $limit_raw;
            if ($limit < 1) {
                $limit = 100;
            }
            if ($limit > 100000) {
                $limit = 100000;
            }
            $this->data['row_limit'] = (string) $limit;
            $itype_filter = (strtoupper($itype) === 'ALL') ? '' : $itype;

            $error = '';
            $start = null;
            $end = null;
            if ($mode === 'price') {
                if ($start_raw === '' || $end_raw === '' || !is_numeric($start_raw) || !is_numeric($end_raw)) {
                    $error = 'Enter valid From Price and To Price.';
                } else {
                    $start = (float) $start_raw;
                    $end = (float) $end_raw;
                    if ($start > $end) {
                        $tmp = $start;
                        $start = $end;
                        $end = $tmp;
                        $this->data['start_price'] = (string) $start;
                        $this->data['end_price'] = (string) $end;
                    }
                }
            } elseif ($mode === 'accession') {
                if ($from_barcode === '' || $to_barcode === '') {
                    $error = 'Enter From Accession and To Accession.';
                }
            }

            if ($error !== '') {
                $this->data['price_error'] = $error;
            } else {
                $report = $this->report_m->get_price_range_report(
                    $mode === 'price' ? $start : '',
                    $mode === 'price' ? $end : '',
                    $itype_filter,
                    $limit,
                    $mode === 'accession' ? $from_barcode : '',
                    $mode === 'accession' ? $to_barcode : ''
                ) ?: array();
                $this->data['report'] = $report;
                $this->data['searched'] = true;

                $cost_total = 0.0;
                foreach ($report as $row) {
                    $cost = is_object($row)
                        ? (isset($row->Price_INR) ? $row->Price_INR : 0)
                        : (isset($row['Price_INR']) ? $row['Price_INR'] : 0);
                    $cost_total += (float) $cost;
                }
                $this->data['cost_total'] = $cost_total;

                $itype_label = ($itype === '' || strtoupper($itype) === 'ALL') ? 'All' : $itype;
                foreach ($this->data['itemtypes'] as $it) {
                    $code = is_object($it) ? $it->itemtype : (isset($it['itemtype']) ? $it['itemtype'] : '');
                    if ($code !== '' && $code === $itype) {
                        $desc = is_object($it)
                            ? (isset($it->description) ? $it->description : $code)
                            : (isset($it['description']) ? $it['description'] : $code);
                        $itype_label = ($desc !== '' && $desc !== $code) ? ($desc . ' (' . $code . ')') : $code;
                        break;
                    }
                }
                $this->data['itype_label'] = $itype_label;
                $this->data['columns'] = $cols;

                $subtitle_parts = array();
                if ($mode === 'all') {
                    $subtitle_parts[] = 'Filter: All';
                } elseif ($mode === 'accession') {
                    $subtitle_parts[] = 'Accession: ' . $from_barcode . ' – ' . $to_barcode;
                } else {
                    $subtitle_parts[] = 'Price: ' . number_format((float) $start, 2, '.', '') . ' – ' . number_format((float) $end, 2, '.', '');
                }
                $subtitle_parts[] = 'Item Type: ' . $itype_label;
                $subtitle_parts[] = 'Limit: ' . $limit;
                $this->data['report_subtitle'] = implode(' · ', $subtitle_parts);

                $saved_report = $report;
                $this->data['pdf_total_rows'] = count($saved_report);
                $total_row = (object) array(
                    'S_No' => '',
                    'Date' => '',
                    'Accession_Number' => '',
                    'Author_1' => '',
                    'Author_2' => '',
                    'Title' => 'TOTAL',
                    'Sub_Title' => '',
                    'Edition' => '',
                    'Subject' => '',
                    'Publisher' => '',
                    'Place_of_Pub' => '',
                    'Year' => '',
                    'ISBN' => '',
                    'Pages' => '',
                    'Vol' => '',
                    'Enc_Mat' => '',
                    'Book_Supplier_Name' => '',
                    'Invoice_No' => '',
                    'Invoice_Date' => '',
                    'Curr_Code' => '',
                    'Price' => '',
                    'Price_INR' => number_format($cost_total, 2, '.', ''),
                    'Dis' => '',
                    'Location' => '',
                    'Nature' => '',
                    'Status' => '',
                    'Remarks' => '',
                );
                $this->data['report'] = array_merge($saved_report, array($total_row));

                $this->dispatch_export(array(
                    'pdf_view' => 'report_pdf/pdf_price_range_report',
                    'filename_base' => 'Price Range Report',
                    'orientation' => 'landscape',
                    'paper' => 'A3',
                    'columns' => $cols,
                    'pdf_no_chunk' => true,
                    'word_as_pdf_html' => true,
                    'excel_as_pdf_html' => true,
                    'extra' => array(
                        'page_numbers' => true,
                        'page_text' => 'Page {PAGE_NUM} of {PAGE_COUNT}',
                        'page_x' => 560,
                        'page_y' => 820,
                        'page_size' => 9,
                    ),
                ));

                $this->data['report'] = $saved_report;
                $this->data['report_subtitle'] = '';
                unset($this->data['pdf_total_rows']);
            }
        }

        $this->data['columns'] = $cols;
        $this->render_report('report/price_range_report', $this->data);
    }

    /* -------------------- Accession Date Wise -------------------- */
    public function accession_date_wise_report()
    {
        $this->gate_report();
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['from_date'] = $this->input->post('from_date') ?: date('d-m-Y', strtotime('-30 days'));
        $this->data['to_date'] = $this->input->post('to_date') ?: date('d-m-Y');
        $this->data['report'] = array();
        $this->data['searched'] = false;
        $this->data['has_filters'] = true;
        $this->data['report_title'] = 'Accession Date Wise Report';
        $this->data['date_error'] = '';

        $this->load->helper('report');
        $cols = report_catalogue_columns('book');

        if ($this->is_posted()) {
            $from_ui = trim((string) $this->input->post('from_date'));
            $to_ui = trim((string) $this->input->post('to_date'));
            $this->data['from_date'] = $from_ui;
            $this->data['to_date'] = $to_ui;

            $from_ymd = $this->parse_ui_date($from_ui, '');
            $to_ymd = $this->parse_ui_date($to_ui, '');
            if ($from_ymd === '' || $to_ymd === '') {
                $this->data['date_error'] = 'Enter valid From Date and To Date (dd-mm-yyyy).';
            } else {
                if ($from_ymd > $to_ymd) {
                    $tmp = $from_ymd;
                    $from_ymd = $to_ymd;
                    $to_ymd = $tmp;
                }
                $this->data['report'] = $this->report_m->get_accession_date_wise_report($from_ymd, $to_ymd) ?: array();
                $this->data['searched'] = true;
                $this->data['report_title'] = 'Accession Register Report';
                $this->data['report_subtitle'] = 'Accession Date: ' . date('d/m/Y', strtotime($from_ymd)) . ' – ' . date('d/m/Y', strtotime($to_ymd));
                $this->data['from_ymd'] = $from_ymd;
                $this->data['to_ymd'] = $to_ymd;
                $this->dispatch_export(array(
                    'pdf_view' => 'report_pdf/pdf_accession_date_wise_report',
                    'filename_base' => 'Accession Date Wise Report',
                    'orientation' => 'landscape',
                    'paper' => 'A3',
                    'columns' => $cols,
                ));
                $this->data['report_title'] = 'Accession Date Wise Report';
                $this->data['report_subtitle'] = '';
            }
        }

        $this->data['columns'] = $cols;
        $this->render_report('report/accession_date_wise_report', $this->data);
    }

    /* -------------------- Budget Expenditure Report -------------------- */
    public function budget_expenditure_report()
    {
        $this->gate_report();
        $this->load->helper('settings');
        $this->data['today'] = $today = date('Y-m-d');
        $this->data['from_date'] = $this->input->post('from_date') ?: date('d-m-Y', strtotime(date('Y') . '-04-01'));
        $this->data['to_date'] = $this->input->post('to_date') ?: date('d-m-Y');
        $this->data['report'] = array();
        $this->data['searched'] = false;
        $this->data['has_filters'] = true;
        $this->data['report_title'] = 'Budget Expenditure Report';
        $this->data['date_error'] = '';
        $default_budget = function_exists('app_setting_text') ? app_setting_text('acquisition_budget_allotted') : '';
        $this->data['budget_allotted'] = $this->input->post('budget_allotted');
        if ($this->data['budget_allotted'] === null || $this->data['budget_allotted'] === '') {
            $this->data['budget_allotted'] = $default_budget !== '' ? $default_budget : '';
        }

        if ($this->is_posted()) {
            $from_ui = trim((string) $this->input->post('from_date'));
            $to_ui = trim((string) $this->input->post('to_date'));
            $budget_raw = trim((string) $this->input->post('budget_allotted'));
            $this->data['from_date'] = $from_ui;
            $this->data['to_date'] = $to_ui;
            $this->data['budget_allotted'] = $budget_raw;

            $from_ymd = $this->parse_ui_date($from_ui, '');
            $to_ymd = $this->parse_ui_date($to_ui, '');
            if ($from_ymd === '' || $to_ymd === '') {
                $this->data['date_error'] = 'Enter valid From Date and To Date (dd-mm-yyyy).';
            } elseif ($budget_raw === '' || !is_numeric($budget_raw)) {
                $this->data['date_error'] = 'Enter a valid Budget Allotted amount.';
            } else {
                if ($from_ymd > $to_ymd) {
                    $tmp = $from_ymd;
                    $from_ymd = $to_ymd;
                    $to_ymd = $tmp;
                }
                $summary = $this->report_m->get_budget_expenditure_report($from_ymd, $to_ymd, (float) $budget_raw);
                $this->data['report'] = array($summary);
                $this->data['searched'] = true;
                $this->data['from_ymd'] = $from_ymd;
                $this->data['to_ymd'] = $to_ymd;
                $this->data['period_label'] = date('d/m/Y', strtotime($from_ymd)) . ' to ' . date('d/m/Y', strtotime($to_ymd));
                $this->dispatch_export(array(
                    'pdf_view' => 'report_pdf/pdf_budget_expenditure_report',
                    'filename_base' => 'Budget Expenditure Report',
                    'orientation' => 'portrait',
                    'columns' => array(),
                    'pdf_no_chunk' => true,
                    'word_as_pdf_html' => true,
                    'excel_as_pdf_html' => true,
                ));
            }
        }

        $this->render_report('report/budget_expenditure_report', $this->data);
    }

    /**
     * Parse Weed Out member short forms from POST (count + weed_member[]).
     *
     * @return array
     */
    protected function parse_weed_out_members_from_post()
    {
        $count = (int) $this->input->post('weed_member_count');
        if ($count < 1) {
            return array();
        }
        if ($count > 20) {
            $count = 20;
        }

        $raw = $this->input->post('weed_member');
        if (!is_array($raw)) {
            $raw = array();
        }

        $members = array();
        for ($i = 0; $i < $count; $i++) {
            $code = isset($raw[$i]) ? trim((string) $raw[$i]) : '';
            $code = preg_replace('/\s+/', ' ', $code);
            $code = trim($code);
            if ($code === '') {
                return array();
            }
            $code = strtoupper($code);
            if (mb_strlen($code) > 40) {
                $code = mb_substr($code, 0, 40);
            }
            $members[] = $code;
        }

        return $members;
    }

    /**
     * Parse jumset accession numbers from textarea / comma list.
     *
     * @param string $raw
     * @return array
     */
    protected function parse_weed_out_acc_numbers($raw)
    {
        $parts = preg_split('/[\s,;]+/', trim((string) $raw));
        $out = array();
        $seen = array();
        foreach ($parts as $part) {
            $code = trim((string) $part);
            if ($code === '') {
                continue;
            }
            $key = strtoupper($code);
            if (isset($seen[$key])) {
                continue;
            }
            $seen[$key] = true;
            $out[] = $code;
        }
        return $out;
    }
}
