"""Count FDA-approved drugs at several levels from official FDA downloads (ProteinIQ analysis, October 24, 2026).

Inputs (keep the downloads outside the repository and pass their paths):
  1. Drugs@FDA data files, https://www.fda.gov/media/89850/download (dafdata20261023.zip, HTTP Last-Modified
     Fri, 23 Oct 2026 13:28:46 GMT; table files dated October 22, 2026). Pass the unzipped directory.
  2. Orange Book data files, https://www.fda.gov/media/76860/download (EOBZIP_2026_09.zip, HTTP Last-Modified
     Tue, 13 Oct 2026 16:09:04 GMT; products.txt dated October 9, 2026). Pass the unzipped directory.
  3. Compilation of CDER NME and New Biologic Approvals 1985-2025, https://www.fda.gov/media/177921/download
     (2026 Compilation_of_CDER_NME_and_New_Biologic_Approvals_1985-2025.xlsx, HTTP Last-Modified
     Tue, 12 May 2026 19:32:10 GMT). Needs openpyxl.
  4. FDA Purple Book September 2026 data download,
     https://www.accessdata.fda.gov/drugsatfda_docs/PurpleBook/2026/purplebook-search-September-data-download.csv
     (HTTP Last-Modified Wed, 30 Sep 2026 17:32:54 GMT; SHA-256
     b9d04a8b1ea9c6605fdeff01127f0de32cb092534a40441b036e39c9665974e6), the release used by
     content/guides/how-many-monoclonal-antibodies-are-fda-approved.mdx.
All files were downloaded on October 24, 2026.

The article's ChEMBL figure is not computed here. On October 24, 2026 the ChEMBL web services reported release
ChEMBL_37 (2026-05-01); https://www.ebi.ac.uk/chembl/api/data/molecule.json?max_phase=4 returned 4,225 molecule
records, of which 2,996 are parent molecules (molecule_hierarchy.parent_chembl_id equal to their own ID): 2,306 small
molecules, and 291 with withdrawn_flag set. DrugBank's approved small-molecule browse
(https://go.drugbank.com/drugs?approved=1) showed 3,054 drugs on the same day.

Run from this directory (Python 3.13):
  python count_fda_drugs.py <drugsatfda-dir> <orange-book-dir> <compilation.xlsx> <purplebook.csv>
Writes summary.json and novel-approvals-by-year.csv.

Definitions
- A product record is one application product number (one strength and dosage form of one application).
- Active means a current marketing status of prescription or over-the-counter. Drugs@FDA also holds
  discontinued records and tentative approvals (marketing status "None (Tentative Approval)").
- Ingredient entries are the exact ActiveIngredient (Drugs@FDA) or Ingredient (Orange Book) strings, so salts
  and combinations are separate entries. Ingredient components split combination entries on ";", which
  counts each named component once but still keeps salt forms apart.
- Novel approvals are the rows of CDER's compilation (Type 1 or 1/4 NDAs and new therapeutic BLAs).
- Purple Book products are distinct BLA numbers, split by reviewing center and by 351(a) versus 351(k).
"""
import collections
import csv
import json
import os
import sys

import openpyxl


def read_tsv(directory, name):
    with open(os.path.join(directory, name), encoding='latin-1', newline='') as f:
        return list(csv.DictReader(f, delimiter='\t'))


def components(entries):
    return {part.strip() for entry in entries for part in entry.split(';') if part.strip()}


def drugs_at_fda(directory):
    products = read_tsv(directory, 'Products.txt')
    status_names = {r['MarketingStatusID']: r['MarketingStatusDescription'] for r in read_tsv(directory, 'MarketingStatus_Lookup.txt')}
    status = {(r['ApplNo'], r['ProductNo']): status_names[r['MarketingStatusID']] for r in read_tsv(directory, 'MarketingStatus.txt')}
    appl_type = {r['ApplNo']: r['ApplType'] for r in read_tsv(directory, 'Applications.txt')}
    for p in products:
        p['status'] = status.get((p['ApplNo'], p['ProductNo']), 'Missing')
        p['type'] = appl_type.get(p['ApplNo'], 'Missing')
    active = [p for p in products if p['status'] in ('Prescription', 'Over-the-counter')]
    ingredients = {p['ActiveIngredient'].strip() for p in active if p['ActiveIngredient'].strip()}
    return {
        'product_records_total': len(products),
        'product_records_by_status': dict(collections.Counter(p['status'] for p in products)),
        'active_product_records': len(active),
        'active_product_records_by_application_type': dict(collections.Counter(p['type'] for p in active)),
        'active_applications': len({p['ApplNo'] for p in active}),
        'active_applications_by_type': dict(collections.Counter(appl_type.get(a, 'Missing') for a in {p['ApplNo'] for p in active})),
        'active_drug_names': len({p['DrugName'].strip() for p in active}),
        'active_ingredient_entries': len(ingredients),
        'active_ingredient_components': len(components(ingredients)),
        'active_combination_entries': sum(';' in i for i in ingredients),
    }


def orange_book(directory):
    with open(os.path.join(directory, 'products.txt'), encoding='latin-1', newline='') as f:
        products = list(csv.DictReader(f, delimiter='~'))
    active = [p for p in products if p['Type'] in ('RX', 'OTC')]
    ingredients = {p['Ingredient'].strip() for p in active}
    return {
        'product_records_total': len(products),
        'product_records_by_type': dict(collections.Counter(p['Type'] for p in products)),
        'active_product_records': len(active),
        'active_applications': len({(p['Appl_Type'], p['Appl_No']) for p in active}),
        'active_applications_by_type': dict(collections.Counter(t for t, _ in {(p['Appl_Type'], p['Appl_No']) for p in active})),
        'active_ingredient_entries': len(ingredients),
        'active_ingredient_components': len(components(ingredients)),
        'reference_listed_drugs_active': sum(p['RLD'] == 'Yes' for p in active),
    }


def novel_approvals(path):
    rows = list(openpyxl.load_workbook(path, read_only=True).worksheets[0].iter_rows(values_only=True))
    header = rows[0]
    records = [dict(zip(header, r)) for r in rows[1:] if r[0]]
    by_year = collections.defaultdict(collections.Counter)
    for r in records:
        by_year[int(r['Approval Year'])][r['NDA/BLA'].strip()] += 1
    return len(records), {year: dict(c) for year, c in sorted(by_year.items())}


def purple_book(path):
    with open(path, encoding='utf-8-sig', newline='') as f:
        rows = list(csv.reader(f))
    header_rows = [i for i, r in enumerate(rows) if r and r[0] == 'N/R/U']
    header = rows[header_rows[-1]]  # the full list follows the second header row; the first block is the month's changes
    records = [dict(zip(header, r)) for r in rows[header_rows[-1] + 1:] if len(r) == len(header)]
    blas = collections.defaultdict(list)
    for r in records:
        blas[r['BLA Number'].strip()].append(r)
    out = collections.Counter()
    for rs in blas.values():
        center = rs[0]['Center'].strip()
        pathway = '351(a)' if rs[0]['License Type'].strip() == '351(a)' else '351(k)'
        licensed = any(r['Licensure'].strip() == 'Licensed' for r in rs)
        out[f'{center} {pathway} {"licensed" if licensed else "revoked"}'] += 1
    return dict(sorted(out.items()))


def main():
    dfda_dir, ob_dir, compilation, purple = sys.argv[1:5]
    novel_total, novel_by_year = novel_approvals(compilation)
    summary = {
        'analysis_date': '2026-10-24',
        'drugs_at_fda_20261023': drugs_at_fda(dfda_dir),
        'orange_book_2026_09': orange_book(ob_dir),
        'cder_novel_approvals_1985_2025': {
            'total': novel_total,
            'last_ten_years_2016_2025': sum(sum(c.values()) for y, c in novel_by_year.items() if 2016 <= y <= 2025),
        },
        # Novel Drug Approvals for 2026 (FDA page, content current as of 10/20/2026): No. 47, Hibsago, 10/20/2026
        'cder_novel_approvals_2026_through_oct_20': 47,
        'purple_book_2026_09_bla_counts': purple_book(purple),
    }
    with open('summary.json', 'w', encoding='utf-8', newline='\n') as f:
        json.dump(summary, f, indent=2)
        f.write('\n')
    with open('novel-approvals-by-year.csv', 'w', encoding='utf-8', newline='') as f:
        w = csv.writer(f, lineterminator='\n')
        w.writerow(['year', 'nda', 'bla', 'total'])
        for year, c in novel_by_year.items():
            w.writerow([year, c.get('NDA', 0), c.get('BLA', 0), sum(c.values())])
    print(json.dumps(summary, indent=2))


if __name__ == '__main__':
    main()
