Indranil Bose

162 papers C 5Misc 1Journal 121Unranked 32
YearRankTypeTitle / Venue / Authors
2026 J jnl
Inf. Manag.
Yiping (amy) Song, Jiming Wu, Satadruta Mookherjee, Indranil Bose
2026 J jnl
Decis. Support Syst.
Pratik Tarafdar, Alvin Chung Man Leung, Wei Thoo Yue, Indranil Bose
2026 J jnl
IEEE Trans. Syst. Man Cybern. Syst.
Xiaoge Zhang, Tao Wang, Xiao-Lin Wang, Feng-Lei Fan, Yiu-ming Cheung, Indranil Bose
2026 J jnl
Internet Res.
Pramukh Nanjundaswamy Vasist, Satish Krishnan, Indranil Bose
2025 J jnl
ACM Comput. Surv.
Satyam Kumar, Yelleti Vivek, Vadlamani Ravi, Indranil Bose
2025 J jnl
Inf. Syst. Frontiers
Indranil Bose, Arpan Kumar Kar, Gene Moo Lee, Alvin Chung Man Leung, Dan J. Kim
2025 J jnl
Inf. Syst. Frontiers
Gaurav Sharma, Swagato Chatterjee, M. Vimala Rani, Aruna Divya Tatavarthy, Indranil Bose
2025 J jnl
Decis. Support Syst.
Subhajit Bag, Sobhan Sarkar, Indranil Bose
2025 J jnl
Inf. Syst. Frontiers
Masoud Shayganmehr, Indranil Bose
2025 J jnl
Inf. Syst. Frontiers
Madhuri Prabhala, Indranil Bose
2025 J jnl
Decis. Support Syst.
Rahul Kumar, Shubhadeep Mukherjee, Indranil Bose
2025 J jnl
Inf. Syst. Frontiers
V. Athi Karthick, Adrija Majumdar, Indranil Bose
2024 J jnl
Int. J. Inf. Manag.
Yogesh K. Dwivedi, Anand Jeyaraj, Laurie Hughes, Gareth H. Davies, Manju Ahuja, Mousa Ahmed Albashrawi, Adil S. Al-Busaidi, Salah A. Al-Sharhan, Khalid Ibrahim Al-Sulaiti, Levent Altinay, Shem Amalaya, Sunil Archak, María Teresa Ballestar, Shonil A. Bhagwat, Anandhi Bharadwaj, Amit Bhushan, Indranil Bose, Pawan Budhwar, Deborah Bunker, Alexandru Capatina, Lemuria D. Carter, Ioanna D. Constantiou, Crispin R. Coombs, Tom Crick, Csaba Csáki, Yves Darnige, Rahul Dé, Rick Delbridge, Rameshwar Dubey, Robin Gauld, Ravikumar Gutti, Marié Hattingh, Arve Haug, Leeya Hendricks, Airo Hino, Cathy H. C. Hsu, Netta Iivari, Marijn Janssen, Ikram Jebabli, Paul Jones, Iris A. Junglas, Abhishek Kaushik, Deepak Khazanchi, Mitsuru Kodama, Sascha Kraus, Vikram Kumar, Christian Maier, F. Tegwen Malik, Machdel Matthee, Ian P. McCarthy, Marco Meier, Bhimaraya A. Metri, Adrian Micu, Angela-Eliza Micu, Santosh K. Misra, Anubhav Mishra, Tonja Molin-Juustila, Leif Oppermann, Nicholas O'Regan, Abhipsa Pal, Neeraj Pandey, Ilias O. Pappas, Andrew Parker, Kavita Pathak, Daniel A. Pienta, Ariana Polyviou, Ramakrishnan Raman, Samuel Ribeiro-Navarrete, Paavo Ritala, Michael Rosemann, Suprateek Sarker, Pallavi Saxena, Daniel Schlagwein, Hergen Schultze, Chitra Sharma, Sujeet Kumar Sharma, Antonis C. Simintiras, Vinay Kumar Singh, Hanlie Smuts, John Soldatos, Manoj Kumar Tiwari, Jason Bennett Thatcher, Cristina Vanberghen, Ákos Varga, Polyxeni Vassilakopoulou, Viswanath Venkatesh, Giampaolo Viglia, Tim Vorley, Michael R. Wade, Paul Walton
2024 J jnl
IEEE Trans. Engineering Management
Nishant Kumar Verma, Ashish Kumar Jha, Indranil Bose, Eric W. T. Ngai
2024 J jnl
Inf. Manag.
Suparna Dhar, Indranil Bose
2024 J jnl
CoRR
Pingping Dong, Xiao-Lin Wang, Indranil Bose, Kam K. H. Ng, Xiaoning Zhang, Xiaoge Zhang
2024 J jnl
Inf. Manag.
Jithesh Arayankalam, Prakriti Soral, Anupriya Khan, Satish Krishnan, Indranil Bose
2024 J jnl
Commun. Assoc. Inf. Syst.
Saikat Lahiri, Indranil Bose, Adrija Majumdar
2024 J jnl
Decis. Support Syst.
Xiaoge Zhang, Indranil Bose
2024 J jnl
Inf. Syst. Frontiers
Suparna Dhar, Indranil Bose, Jose Benitez
2024 J jnl
Int. J. Inf. Manag.
Pratik Tarafdar, Alvin Chung Man Leung, Wei Thoo Yue, Indranil Bose
2024 J jnl
Ind. Manag. Data Syst.
Madhuri Prabhala, Indranil Bose
2024 J jnl
Commun. Assoc. Inf. Syst.
Ashneet Kaur, Sudhanshu Maheshwari, Indranil Bose, Simarjeet Singh
2023 J jnl
CoRR
Satyam Kumar, Yelleti Vivek, Vadlamani Ravi, Indranil Bose
2023 J jnl
Inf. Syst. Frontiers
Suparna Dhar, Indranil Bose
2023 J jnl
Decis. Support Syst.
Megha Sharma, Sumanta Basu, Soumyakanti Chakraborty, Indranil Bose
2023 J jnl
CoRR
Xiaoge Zhang, Xiao-Lin Wang, Fenglei Fan, Yiu-ming Cheung, Indranil Bose
2023 conf
ACIS
Samadrita Bhattacharyya, Shankhadeep Banerjee, Indranil Bose, H. Raghav Rao
2023 J jnl
Int. J. Inf. Manag.
Yogesh K. Dwivedi, Nir Kshetri, Laurie Hughes, Emma L. Slade, Anand Jeyaraj, Arpan Kumar Kar, Abdullah M. Baabdullah, Alex Koohang, Vishnupriya Raghavan, Manju Ahuja, Hanaa Albanna, Mousa Ahmad Albashrawi, Adil S. Al-Busaidi, Janarthanan Balakrishnan, Yves Barlette, Sriparna Basu, Indranil Bose, Laurence D. Brooks, Dimitrios Buhalis, Lemuria D. Carter, Soumyadeb Chowdhury, Tom Crick, Scott W. Cunningham, Gareth H. Davies, Robert M. Davison, Rahul De', Denis Dennehy, Yanqing Duan, Rameshwar Dubey, Rohita Dwivedi, John S. Edwards, Carlos Flavián, Robin Gauld, Varun Grover, Mei-Chih Hu, Marijn Janssen, Paul Jones, Iris A. Junglas, Sangeeta Khorana, Sascha Kraus, Kai R. Larsen, Paul Latreille, Sven Laumer, F. Tegwen Malik, Abbas Mardani, Marcello Mariani, Sunil Mithas, Emmanuel Mogaji, Jeretta Horn Nord, Siobhán O'Connor, Fevzi Okumus, Margherita Pagani, Neeraj Pandey, Savvas Papagiannidis, Ilias O. Pappas, Nishith Pathak, Jan Pries-Heje, Ramakrishnan Raman, Nripendra P. Rana, Sven-Volker Rehm, Samuel Ribeiro-Navarrete, Alexander Richter, Frantz Rowe, Suprateek Sarker, Bernd Carsten Stahl, Manoj Kumar Tiwari, Wil M. P. van der Aalst, Viswanath Venkatesh, Giampaolo Viglia, Michael R. Wade, Paul Walton, Jochen Wirtz, Ryan T. Wright
2023 J jnl
Ann. Oper. Res.
Issam Laguir, Sachin Modgil, Indranil Bose, Shivam Gupta, Rebecca Stekelorum
2023 J jnl
Eur. J. Oper. Res.
Saikat Das, Indranil Bose, Uttam Kumar Sarkar
2023 J jnl
Inf. Syst. Frontiers
Dan J. Kim, Indranil Bose, Arunabha Mukhopadhyay
2023 J jnl
Inf. Manag.
Shankhadeep Banerjee, Samadrita Bhattacharyya, Indranil Bose
2023 J jnl
Decis. Support Syst.
Yiping Amy Song, Lingling Zhang, Liye Ma, Indranil Bose
2022 J jnl
Inf. Manag.
Shankhadeep Banerjee, Indranil Bose
2022 J jnl
Decis. Support Syst.
Issam Laguir, Shivam Gupta, Indranil Bose, Rebecca Stekelorum, Lamia Laguir
2022 J jnl
Ann. Oper. Res.
Shivam Gupta, Sachin Modgil, Samadrita Bhattacharyya, Indranil Bose
2022 J jnl
Inf. Manag.
Gaurav Gupta, Indranil Bose
2022 conf
PACIS
Madhuri Prabhala, Indranil Bose
2022 conf
PACIS
Shankhadeep Banerjee, Samadrita Bhattacharyya, Indranil Bose
2022 J jnl
Decis. Support Syst.
Xiaoge Zhang, Felix T. S. Chan, Chao Yan, Indranil Bose
2022 conf
AMCIS
Madhuri Prabhala, Indranil Bose
2022 J jnl
Decis. Support Syst.
Suparna Dhar, Indranil Bose
2022 J jnl
Decis. Support Syst.
Suparna Dhar, Indranil Bose
2022 J jnl
Inf. Manag.
Samit Paul, Arnab Adhikari, Indranil Bose
2021 J jnl
Commun. Assoc. Inf. Syst.
Pratik Tarafdar, Indranil Bose
2021 J jnl
Inf. Syst. Frontiers
Ashish Kumar Jha, Indranil Bose
2021 J jnl
Decis. Support Syst.
Neha Chaudhuri, Gaurav Gupta, Vamsi Vallurupalli, Indranil Bose
2021 J jnl
Decis. Support Syst.
Pratik Tarafdar, Indranil Bose
2021 J jnl
J. Organ. Comput. Electron. Commer.
Suparna Dhar, Indranil Bose
2020 Misc conf
ICDCN
Neha Chaudhuri, Indranil Bose
2020 C conf
ICIS
Kasturi Bhattacharjee, Indranil Bose, Amitabh Ray, VV Rajasekhar, Sandipan Sarkar
2020 conf
AMCIS
Pratik Tarafdar, Alvin Chung Man Leung, Wei Thoo Yue, Indranil Bose
2020 J jnl
Decis. Support Syst.
Neha Chaudhuri, Indranil Bose
2020 J jnl
Electron. Mark.
Vamsi Vallurupalli, Indranil Bose
2020 J jnl
Inf. Manag.
Samadrita Bhattacharyya, Shankhadeep Banerjee, Indranil Bose
2020 J jnl
Decis. Support Syst.
Samadrita Bhattacharyya, Indranil Bose
2020 J jnl
J. Manag. Inf. Syst.
Samadrita Bhattacharyya, Shankhadeep Banerjee, Indranil Bose, Atreyi Kankanhalli
2019 J jnl
MIS Q.
Indranil Bose, Alvin Chung Man Leung
2019 conf
HICSS
Neha Chaudhuri, Indranil Bose
2019 J jnl
Commun. Assoc. Inf. Syst.
Ashish Kumar Jha, Indranil Bose
2019 conf
IEEM
Madhuri Prabhala, Indranil Bose
2019 C conf
ICIS
Suparna Dhar, Indranil Bose
2019 C conf
ICIS
Pratik Tarafdar, Alvin Chung Man Leung, Wei Thoo Yue, Indranil Bose
2019 J jnl
J. Organ. Comput. Electron. Commer.
Pratik Tarafdar, Indranil Bose
2019 conf
HICSS
Samadrita Bhattacharyya, Indranil Bose
2018 J jnl
Decis. Support Syst.
Vamsi Vallurupalli, Indranil Bose
2018 J jnl
J. Organ. Comput. Electron. Commer.
Adrija Majumdar, Indranil Bose
2018 conf
ESSDERC
Indranil Bose, Nagarajan Palavesam, Christian Hochreiter, Christof Landesberger, Christoph Kutter
2018 J jnl
Inf. Manag.
Adrija Majumdar, Indranil Bose
2018 J jnl
Commun. Assoc. Inf. Syst.
Vamsi Vallurupalli, Indranil Bose
2018 conf
ACIS
Shankhadeep Banerjee, Indranil Bose
2018 conf
ACIS
Ashish Kumar Jha, Indranil Bose
2018 conf
HICSS
Gaurav Gupta, Indranil Bose
2017 conf
ICSEB
Gaurav Gupta, Indranil Bose
2017 J jnl
Decis. Support Syst.
Baojun Gao, Nan Hu, Indranil Bose
2017 conf
ECIS
Gaurav Gupta, Indranil Bose
2017 C conf
ICIS
Shankhadeep Banerjee, Indranil Bose
2017 J jnl
Commun. Assoc. Inf. Syst.
Indranil Bose
2017 conf
ACIS
Samadrita Bhattacharyya, Shankhadeep Banerjee, Indranil Bose
2017 conf
ACIS
Vamsi Vallurupalli, Indranil Bose
2017 J jnl
Decis. Support Syst.
Xin Fu, Xi Chen, Yu-Tong Shi, Indranil Bose, Shun Cai
2017 J jnl
Decis. Support Syst.
Shankhadeep Banerjee, Samadrita Bhattacharyya, Indranil Bose
2016 J jnl
Decis. Support Syst.
Partha Saha, Indranil Bose, Ambuj Mahanti
2016 J jnl
Inf. Manag.
Ashish Kumar Jha, Indranil Bose
2016 J jnl
Inf. Manag.
Ashish Kumar Jha, Indranil Bose
2016 conf
WEB
Samadrita Bhattacharyya, Shankhadeep Banerjee, Indranil Bose
2015 conf
WEB
Manas Tripathi, Arunabha Mukhopadhyay, Indranil Bose
2015 J jnl
Inf. Manag.
Indranil Bose, Xi Chen
2015 J jnl
Decis. Support Syst.
Jie Han, Indranil Bose, Nan Hu, Baolei Qi, Gaoliang Tian
2015 conf
WEB
Ashish Kumar Jha, Indranil Bose
2015 conf
WEB
Suparna Dhar, Indranil Bose
2015 J jnl
Eur. J. Oper. Res.
Ruibin Geng, Indranil Bose, Xi Chen
2015 conf
WEB
Adrija Majumdar, Indranil Bose
2014 J jnl
IT Prof.
Indranil Bose, Ben Hui Siu Bun
2014 conf
PACIS
Xi Chen, Shanshan Liu, Indranil Bose
2014 J jnl
Microelectron. Reliab.
Indranil Bose, Kornelius Tetzner, Kathrin Borner, Karlheinz Bock
2014 J jnl
Decis. Support Syst.
Indranil Bose, Alvin Chung Man Leung
2014 conf
SEMCCO
Pradeep Ganghishetti, Vadlamani Ravi, Nandan Kaushik, B. L. Deekshatulu, Indranil Bose, A. Aditya
2013 J jnl
Decis. Support Syst.
Ling Liu, Daniel Qi Chen, Indranil Bose, Nan Hu, Garry D. Bruton
2013 J jnl
Decis. Support Syst.
Indranil Bose, Alvin Chung Man Leung
2012 J jnl
Decis. Support Syst.
Indranil Bose, Raktim Pal
2012 J jnl
Decis. Support Syst.
Nan Hu, Indranil Bose, Noi Sian Koh, Ling Liu
2012 J jnl
Decis. Support Syst.
M. A. H. Farquad, Indranil Bose
2012 J jnl
Commun. Assoc. Inf. Syst.
Indranil Bose, Debashis Saha, Minyi Huang
2012 J jnl
Expert Syst. Appl.
Kancherla Jonah Nishanth, Vadlamani Ravi, Narravula Ankaiah, Indranil Bose
2012 J jnl
Int. J. Bus. Data Commun. Netw.
Shun Cai, Xi Chen, Indranil Bose
2011 J jnl
Decis. Support Syst.
Xi Chen, Indranil Bose, Alvin Chung Man Leung, Chenhui (Julian) Guo
2011 J jnl
Decis. Support Syst.
Pediredla Ravisankar, Vadlamani Ravi, G. Raghava Rao, Indranil Bose
2011 J jnl
Commun. Assoc. Inf. Syst.
Indranil Bose, Xinwei Yang
2011 J jnl
Decis. Support Syst.
Nan Hu, Indranil Bose, Yunjun Gao, Ling Liu
2011 J jnl
IT Prof.
Indranil Bose, Shipeng Yan
2011 J jnl
J. Organ. Comput. Electron. Commer.
Indranil Bose, Ariel K. H. Lui, Eric W. T. Ngai
2010 J jnl
Inf. Syst. E Bus. Manag.
Nan Hu, Ling Liu, Indranil Bose, Jialie Shen
2010 J jnl
Electron. Commer. Res. Appl.
Indranil Bose, Xi Chen
2010 J jnl
Inf. Sci.
Pediredla Ravisankar, Vadlamani Ravi, Indranil Bose
2009 J jnl
Decis. Support Syst.
Indranil Bose, Xi Chen
2009 J jnl
J. Assoc. Inf. Sci. Technol.
Indranil Bose, Xi Chen
2009 J jnl
Eur. J. Oper. Res.
Raktim Pal, Indranil Bose
2009 J jnl
Eur. J. Oper. Res.
Indranil Bose
2009 ch.
Selected Readings on Database Technologies and Applications
Indranil Bose
2009 J jnl
Expert Syst. Appl.
Devulapalli Karthik Chandra, Vadlamani Ravi, Indranil Bose
2009 ch.
Encyclopedia of Data Warehousing and Mining
Indranil Bose
2009 J jnl
J. Organ. Comput. Electron. Commer.
Indranil Bose, Xi Chen
2009 J jnl
Eur. J. Inf. Syst.
Indranil Bose, Eric W. T. Ngai, Thompson S. H. Teo, Sarah Spiekermann
2009 J jnl
J. Organ. Comput. Electron. Commer.
Indranil Bose
2009 J jnl
Eur. J. Oper. Res.
Indranil Bose, Xi Chen
2009 J jnl
Commun. ACM
Indranil Bose, Alvin Chung Man Leung
2008 J jnl
Decis. Support Syst.
Indranil Bose, Alvin Chung Man Leung
2008 conf
ICIT
Indranil Bose, Alvin Chung Man Leung
2008 J jnl
Inf. Manag.
Indranil Bose, Raktim Pal, Alex Ye
2008 J jnl
Commun. Assoc. Inf. Syst.
Indranil Bose, Jae-Nam Lee, Benjamin P.-C. Yen
2008 J jnl
Eur. J. Oper. Res.
Indranil Bose, Anand A. Paul
2008 J jnl
Int. J. Inf. Syst. Supply Chain Manag.
Indranil Bose, Chun Lam
2008 C conf
ICIS
Alvin Chung Man Leung, Indranil Bose
2008 J jnl
Commun. Assoc. Inf. Syst.
Indranil Bose
2008 J jnl
Commun. Assoc. Inf. Syst.
Raktim Pal, Arijit Sengupta, Indranil Bose
2007 conf
IMECS
Alvin Chung Man Leung, Indranil Bose
2007 conf
IMECS
Xi Chen, Indranil Bose
2007 J jnl
Eur. J. Oper. Res.
Indranil Bose, Anand A. Paul
2007 J jnl
Commun. Assoc. Inf. Syst.
Indranil Bose, Alvin Chung Man Leung
2006 conf
PACIS
Indranil Bose, Chen Xi
2006 J jnl
Inf. Manag.
Indranil Bose
2006 conf
ICDM Workshops
Indranil Bose, Chen Xi
2006 J jnl
Commun. Assoc. Inf. Syst.
Indranil Bose
2006 conf
IMECS
Indranil Bose, Alvin Chung Man Leung
2006 J jnl
Eur. J. Oper. Res.
Indranil Bose, Raktim Pal
2005 J jnl
Commun. ACM
Indranil Bose, Raktim Pal
2005 ch.
Encyclopedia of Database Technologies and Applications
Indranil Bose, Ping Wang, Mok Wai Shan, Wong Ka Shing, Yip Yee Shing, Chan Lit Tin, Shiu Ka Wai
2005 J jnl
Decis. Support Syst.
Indranil Bose, Enes Eryarsoy, Ling He
2005 conf
EEE
Indranil Bose, Raktim Pal
2005 conf
PACIS
Indranil Bose, Raktim Pal
2004 J jnl
Oper. Res. Lett.
Anand A. Paul, Indranil Bose
2004 J jnl
Decis. Support Syst.
Indranil Bose, Kemal Altinkemer
2004 J jnl
Eur. J. Oper. Res.
Hsing Kenneth Cheng, Indranil Bose
2003 J jnl
Eur. J. Oper. Res.
Kemal Altinkemer, Indranil Bose
2003 J jnl
Decis. Support Syst.
Indranil Bose, Kemal Altinkemer, Alok R. Chaturvedi
2002 J jnl
Comput. Oper. Res.
Indranil Bose, Raktim Pal
2001 J jnl
Inf. Manag.
Indranil Bose, Radha K. Mahapatra
2000 J jnl
Decis. Support Syst.
Indranil Bose, Hsing Kenneth Cheng
1999 conf
WebNet
Indranil Bose
1998 J jnl
Comput. Oper. Res.
Kemal Altinkemer, Indranil Bose, Raktim Pal
src/main.rs
← Index src/main.rs rust
mod highlight;

use std::collections::BTreeMap;
use std::fs;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::time::Instant;

use walkdir::WalkDir;

use highlight::highlight_code;

/// Above this threshold the file is read in full and classified
/// (text or binary) based on its complete content.
const FULL_READ_LIMIT: u64 = 4_000_000; // 4 MB
/// For files larger than the threshold above, we only read a preview
/// of this size (so we never load huge files fully into memory).
const PREVIEW_BYTES: usize = 16_384;
/// How many bytes of binary content to show in the hex dump.
const HEXDUMP_BYTES: usize = 4096;

struct FileResult {
    rel_path: PathBuf, // relative path of the original file
    out_rel: PathBuf,  // relative path of the generated .html file (inside out/)
    label: String,     // label shown in the badge (language, or "binary")
}

enum Unreadable {
    IoError(PathBuf),
}

enum Content {
    /// Successfully decoded text. `truncated` indicates whether the file
    /// is larger than what was actually read (very large files: preview only).
    Text { content: String, truncated: bool, total_size: u64 },
    /// Content that isn't valid UTF-8 text: we show a hex dump instead
    /// of trying to decode it.
    Binary { preview: Vec<u8>, total_size: u64, truncated: bool },
}

/// Reads a file safely: for small files, reads everything and classifies
/// text/binary based on the full content; for large files, reads only a
/// prefix, to avoid loading huge files into memory.
fn read_content(path: &Path, total_size: u64) -> std::io::Result<Content> {
    if total_size <= FULL_READ_LIMIT {
        let bytes = fs::read(path)?;
        match String::from_utf8(bytes) {
            Ok(s) => Ok(Content::Text {
                content: s,
                truncated: false,
                total_size,
            }),
            Err(e) => Ok(Content::Binary {
                preview: e.into_bytes(),
                total_size,
                truncated: false,
            }),
        }
    } else {
        let mut f = fs::File::open(path)?;
        let mut buf = vec![0u8; PREVIEW_BYTES];
        let n = f.read(&mut buf)?;
        buf.truncate(n);

        match std::str::from_utf8(&buf) {
            Ok(s) => Ok(Content::Text {
                content: s.to_string(),
                truncated: true,
                total_size,
            }),
            Err(e) => {
                let valid_up_to = e.valid_up_to();
                // If the error is only due to cutting a multi-byte
                // character in half (common when reading an arbitrary
                // prefix of a text file), still treat it as text.
                if valid_up_to > 0 && buf.len() - valid_up_to <= 4 {
                    let s = std::str::from_utf8(&buf[..valid_up_to]).unwrap().to_string();
                    Ok(Content::Text {
                        content: s,
                        truncated: true,
                        total_size,
                    })
                } else {
                    Ok(Content::Binary {
                        preview: buf,
                        total_size,
                        truncated: true,
                    })
                }
            }
        }
    }
}

/// Tree node used to build the index (index.html)
#[derive(Default)]
struct Node {
    dirs: BTreeMap<String, Node>,
    files: Vec<(String, String, PathBuf)>, // (display_name, label, href relative to the index)
}

impl Node {
    fn insert_file(&mut self, components: &[String], display_name: String, label: String, href: PathBuf) {
        if components.is_empty() {
            self.files.push((display_name, label, href));
        } else {
            let head = &components[0];
            let child = self.dirs.entry(head.clone()).or_default();
            child.insert_file(&components[1..], display_name, label, href);
        }
    }

    fn render(&self, out: &mut String) {
        out.push_str("<ul class=\"tree\">\n");
        for (name, node) in &self.dirs {
            out.push_str(&format!(
                "<li class=\"dir\"><span class=\"dir-name\">\u{1F4C1} {}</span>\n",
                escape_html(name)
            ));
            node.render(out);
            out.push_str("</li>\n");
        }
        for (name, label, href) in &self.files {
            out.push_str(&format!(
                "<li class=\"file\"><a href=\"{}\">\u{1F4C4} {}</a> <span class=\"badge\">{}</span></li>\n",
                href.to_string_lossy().replace('\\', "/"),
                escape_html(name),
                escape_html(label)
            ));
        }
        out.push_str("</ul>\n");
    }
}

fn main() {
    let args: Vec<String> = std::env::args().collect();
    let input_dir = args.get(1).cloned().unwrap_or_else(|| ".".to_string());
    let output_dir = args.get(2).cloned().unwrap_or_else(|| "out".to_string());
    // Extra directory names to exclude, optional, comma-separated
    // (e.g. "node_modules,.git"). Nothing is excluded by default:
    // the generator includes everything it finds, including
    // __pycache__, compiled files, binaries, etc.
    let extra_excludes: Vec<String> = args
        .get(3)
        .map(|s| s.split(',').map(|p| p.trim().to_string()).filter(|p| !p.is_empty()).collect())
        .unwrap_or_default();

    let input = PathBuf::from(&input_dir);
    let output = PathBuf::from(&output_dir);

    if !input.is_dir() {
        eprintln!("Error: '{}' is not a valid directory.", input.display());
        std::process::exit(1);
    }

    if let Err(e) = fs::create_dir_all(&output) {
        eprintln!("Error creating the output directory: {e}");
        std::process::exit(1);
    }

    // Absolute path of the output directory, so we can exclude it from the
    // scan in case it's nested inside the input directory (e.g. input=".",
    // output="out"). This is the only automatic exclusion and it can't be
    // turned off: it only prevents the program from processing the files
    // it is currently writing.
    let output_abs = fs::canonicalize(&output).unwrap_or_else(|_| output.clone());

    println!("Scanning '{}' -> '{}'", input.display(), output.display());
    if !extra_excludes.is_empty() {
        println!("Directories excluded on request: {}", extra_excludes.join(", "));
    }
    let start = Instant::now();

    let mut results: Vec<FileResult> = Vec::new();
    let mut unreadable: Vec<Unreadable> = Vec::new();
    let mut n_binary = 0usize;
    let mut n_truncated = 0usize;

    for entry in WalkDir::new(&input)
        .into_iter()
        .filter_entry(|e| {
            if e.file_type().is_dir() {
                if let Some(name) = e.file_name().to_str() {
                    if extra_excludes.iter().any(|x| x == name) {
                        return false;
                    }
                }
                if let Ok(abs) = fs::canonicalize(e.path()) {
                    if abs == output_abs {
                        return false;
                    }
                }
            }
            true
        })
    {
        let entry = match entry {
            Ok(e) => e,
            Err(_) => continue,
        };

        if !entry.file_type().is_file() {
            continue;
        }

        let path = entry.path();
        let rel_path = match path.strip_prefix(&input) {
            Ok(p) => p.to_path_buf(),
            Err(_) => path.to_path_buf(),
        };

        let meta = match fs::metadata(path) {
            Ok(m) => m,
            Err(_) => {
                unreadable.push(Unreadable::IoError(rel_path));
                continue;
            }
        };

        let content = match read_content(path, meta.len()) {
            Ok(c) => c,
            Err(_) => {
                unreadable.push(Unreadable::IoError(rel_path));
                continue;
            }
        };

        let syntax_lang = detect_language(path);

        let (body_html, label, note): (String, String, Option<String>) = match &content {
            Content::Text { content, truncated, total_size } => {
                let body = highlight_code(content, syntax_lang);
                let note = if *truncated {
                    n_truncated += 1;
                    Some(format!(
                        "Truncated preview: showing the first {} bytes out of {} total.",
                        content.len(),
                        total_size
                    ))
                } else {
                    None
                };
                (body, syntax_lang.to_string(), note)
            }
            Content::Binary { preview, total_size, truncated } => {
                n_binary += 1;
                let dump = hex_dump(preview, HEXDUMP_BYTES);
                let shown = preview.len().min(HEXDUMP_BYTES);
                let note = Some(format!(
                    "Binary file: {} bytes total. Showing the first {} bytes as hex.{}",
                    total_size,
                    shown,
                    if *truncated { " (only the first bytes of the file were read)" } else { "" }
                ));
                (escape_html(&dump), "binary".to_string(), note)
            }
        };

        let mut out_rel = rel_path.clone();
        let new_name = format!(
            "{}.html",
            out_rel.file_name().unwrap_or_default().to_string_lossy()
        );
        out_rel.set_file_name(new_name);

        let out_path = output.join(&out_rel);
        if let Some(parent) = out_path.parent() {
            let _ = fs::create_dir_all(parent);
        }

        let depth = out_rel.components().count().saturating_sub(1);
        let back_to_index = "../".repeat(depth);

        let html = render_file_html(
            &rel_path.to_string_lossy(),
            &label,
            &body_html,
            note.as_deref(),
            &back_to_index,
        );

        if fs::write(&out_path, html).is_ok() {
            results.push(FileResult {
                rel_path,
                out_rel,
                label,
            });
        }
    }

    // Build the tree for the index
    let mut root = Node::default();
    for r in &results {
        let components: Vec<String> = r
            .rel_path
            .parent()
            .map(|p| {
                p.components()
                    .map(|c| c.as_os_str().to_string_lossy().to_string())
                    .filter(|s| !s.is_empty())
                    .collect()
            })
            .unwrap_or_default();
        let display_name = r
            .rel_path
            .file_name()
            .unwrap_or_default()
            .to_string_lossy()
            .to_string();
        root.insert_file(
            &components,
            display_name,
            r.label.clone(),
            r.out_rel.clone(),
        );
    }

    let mut tree_html = String::new();
    root.render(&mut tree_html);

    let index_html = render_index_html(&tree_html, results.len(), n_binary, n_truncated);
    let _ = fs::write(output.join("index.html"), index_html);

    let elapsed = start.elapsed();
    println!(
        "Done in {:.2?}: {} files included ({} binary, {} with truncated preview), {} unreadable.",
        elapsed,
        results.len(),
        n_binary,
        n_truncated,
        unreadable.len()
    );
    for u in &unreadable {
        match u {
            Unreadable::IoError(p) => println!("  unreadable (I/O error): {}", p.display()),
        }
    }
    println!("Open {}/index.html in your browser.", output.display());
}

/// Produces a hex dump (in the style of `xxd -C`) of the first `limit` bytes.
fn hex_dump(bytes: &[u8], limit: usize) -> String {
    let show = &bytes[..bytes.len().min(limit)];
    let mut out = String::with_capacity(show.len() * 4);
    for (i, chunk) in show.chunks(16).enumerate() {
        let offset = i * 16;
        out.push_str(&format!("{:08x}  ", offset));
        for (j, b) in chunk.iter().enumerate() {
            out.push_str(&format!("{:02x} ", b));
            if j == 7 {
                out.push(' ');
            }
        }
        let missing = 16usize.saturating_sub(chunk.len());
        for _ in 0..missing {
            out.push_str("   ");
        }
        out.push_str(" |");
        for b in chunk {
            let c = *b;
            if (0x20..0x7f).contains(&c) {
                out.push(c as char);
            } else {
                out.push('.');
            }
        }
        out.push_str("|\n");
    }
    out
}

/// Determines the language (used to pick syntax-highlighting rules)
/// from the file's name/extension. This is only used when the content
/// turns out to actually be text; binary files are classified as such
/// regardless by read_content.
fn detect_language(path: &Path) -> &'static str {
    let file_name = path
        .file_name()
        .map(|s| s.to_string_lossy().to_lowercase())
        .unwrap_or_default();

    match file_name.as_str() {
        "dockerfile" => return "dockerfile",
        "makefile" | "gnumakefile" => return "makefile",
        "cmakelists.txt" => return "cmake",
        ".gitignore" | ".dockerignore" | ".npmignore" => return "plaintext",
        "cargo.toml" | "cargo.lock" => return "ini",
        _ => {}
    }

    let ext = path
        .extension()
        .map(|s| s.to_string_lossy().to_lowercase())
        .unwrap_or_default();

    match ext.as_str() {
        "py" | "pyw" | "pyi" => "python",
        "rs" => "rust",
        "js" | "mjs" | "cjs" => "javascript",
        "jsx" => "javascript",
        "ts" => "typescript",
        "tsx" => "typescript",
        "java" => "java",
        "c" => "c",
        "h" => "c",
        "hpp" | "hh" | "hxx" => "cpp",
        "cpp" | "cc" | "cxx" => "cpp",
        "cs" => "csharp",
        "go" => "go",
        "rb" => "ruby",
        "php" => "php",
        "html" | "htm" => "xml",
        "css" => "css",
        "scss" | "sass" => "scss",
        "less" => "less",
        "json" => "json",
        "xml" => "xml",
        "yaml" | "yml" => "yaml",
        "toml" | "ini" | "cfg" | "conf" => "ini",
        "sh" | "bash" | "zsh" => "bash",
        "sql" => "sql",
        "md" | "markdown" => "markdown",
        "kt" | "kts" => "kotlin",
        "swift" => "swift",
        "lua" => "lua",
        "r" => "r",
        "pl" | "pm" => "perl",
        "groovy" | "gradle" => "groovy",
        "vb" => "vbnet",
        "graphql" | "gql" => "graphql",
        "diff" | "patch" => "diff",
        "bat" | "cmd" => "dos",
        "ps1" => "powershell",
        "vue" => "xml",
        "txt" => "plaintext",
        _ => "plaintext",
    }
}

fn escape_html(s: &str) -> String {
    let mut out = String::with_capacity(s.len());
    for c in s.chars() {
        match c {
            '&' => out.push_str("&amp;"),
            '<' => out.push_str("&lt;"),
            '>' => out.push_str("&gt;"),
            '"' => out.push_str("&quot;"),
            '\'' => out.push_str("&#39;"),
            _ => out.push(c),
        }
    }
    out
}

const STYLE: &str = r#"
:root {
  --bg: #0d1117;
  --panel: #161b22;
  --text: #c9d1d9;
  --muted: #8b949e;
  --accent: #58a6ff;
  --border: #30363d;
}
* { box-sizing: border-box; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
}
header {
  padding: 14px 20px;
  background: var(--panel);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
header a { color: var(--accent); text-decoration: none; font-weight: 600; }
header a:hover { text-decoration: underline; }
.path { color: var(--muted); font-size: 0.9em; word-break: break-all; }
.note {
  padding: 10px 20px;
  background: #21262d;
  border-bottom: 1px solid var(--border);
  color: var(--muted);
  font-size: 0.85em;
}
main { padding: 0; }
pre {
  margin: 0;
  padding: 16px 20px 40px 20px;
  overflow-x: auto;
  font-size: 13px;
  line-height: 1.5;
}
code { font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; }
.container { max-width: 1000px; margin: 30px auto; padding: 0 20px 60px; }
h1 { font-size: 1.6em; }
.meta { color: var(--muted); font-size: 0.9em; margin-bottom: 20px; }
ul.tree { list-style: none; padding-left: 18px; margin: 6px 0; }
ul.tree > li { margin: 4px 0; }
.dir-name { font-weight: 600; color: var(--text); }
.file a { color: var(--accent); text-decoration: none; }
.file a:hover { text-decoration: underline; }
.badge {
  display: inline-block;
  font-size: 0.72em;
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 1px 8px;
  margin-left: 6px;
}
.skipped { color: var(--muted); font-size: 0.85em; margin-top: 30px; }

/* Syntax highlighting: generated in Rust, no JavaScript involved. */
.c { color: #8b949e; font-style: italic; }
.s { color: #a5d6ff; }
.n { color: #79c0ff; }
.k { color: #ff7b72; font-weight: 600; }
"#;

fn render_file_html(rel_path: &str, label: &str, body_html: &str, note: Option<&str>, back_to_index: &str) -> String {
    let title = escape_html(rel_path);
    let note_html = match note {
        Some(n) => format!("<div class=\"note\">{}</div>\n", escape_html(n)),
        None => String::new(),
    };
    format!(
        r#"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{title}</title>
<style>{STYLE}</style>
</head>
<body>
<header>
  <a href="{back_to_index}index.html">&larr; Index</a>
  <span class="path">{title}</span>
  <span class="badge">{label}</span>
</header>
{note_html}<main>
<pre><code>{body_html}</code></pre>
</main>
</body>
</html>
"#
    )
}

fn render_index_html(tree_html: &str, n_ok: usize, n_binary: usize, n_truncated: usize) -> String {
    let mut extra = String::new();
    if n_binary > 0 {
        extra.push_str(&format!(
            "<p class=\"skipped\">{} binary files included with a hex-dump preview.</p>",
            n_binary
        ));
    }
    if n_truncated > 0 {
        extra.push_str(&format!(
            "<p class=\"skipped\">{} very large files shown as a truncated preview only.</p>",
            n_truncated
        ));
    }

    let folder_emoji = '\u{1F4C2}';
    format!(
        r#"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Source index</title>
<style>{STYLE}</style>
</head>
<body>
<div class="container">
<h1>{folder_emoji} Source index</h1>
<p class="meta">{n_ok} files included in total.</p>
{tree_html}
{extra}
</div>
</body>
</html>
"#
    )
}