"""
=============================================================================
第 9 讲 · 工具模块：一批虚构的长文档 —— 季度财报（10-Q 风格）
=============================================================================

对应讲义：lectures/lecture09_sliders.md 第 0.4 节

-----------------------------------------------------------------------------
【这个模块做什么？】
-----------------------------------------------------------------------------
幻灯片的例子都来自真实的长文档：SEC 的季度报告（10-Q）、保险备案、学术论文。这里生成一批虚构的
季度报告：每家公司一份，4,000~9,000 个词（真实的 10-Q 约 10 万个 token），结构模仿真实的财报：

    封面 → 资产负债表（表格）→ 利润表（表格）→ 附注（借款、应付账款、股东权益）→
    管理层讨论与分析（MD&A，叙述性的文字）→ 风险因素、法律诉讼、内部控制（很长，和问题无关）

并刻意埋进幻灯片讲到的几类困难：

    · 重复：收入在利润表里写成 "2,979,692"（千美元），在 MD&A 里写成 "$2.98 billion"；
    · 单位在别处："(in thousands, except share data)" 只写在表头 —— 表格被切开后，后半截的数字就没有单位了；
    · 冲突：股东权益附注写"已发行并流通的股份 50,251,933 股"，利润表和 MD&A 写"加权平均流通股数 49,458,023 股"
      （70% 的公司有这条附注；其余的只在资产负债表的一行里写了流通股数）；
    · 歧义：一半的公司在资产负债表里只有合并的一行 "Accounts payable and accrued expenses"，
      单独的"应付账款"写在附注里（幻灯片 BIOLARGO 的例子）；
    · 残缺：一半的公司没有直接写"借款总额"，只在附注里分别写了短期借款和长期借款；
    · 名字的写法：封面写 "ARBOR BIOTECH, INC."，正文写 "Arbor Biotech" 或 "the Company"。

公司、数字、人名全部是虚构的。

-----------------------------------------------------------------------------
【用法】
-----------------------------------------------------------------------------
    from doc_world import make_corpus
    corpus = make_corpus(n=30, seed=0)
    doc = corpus.docs[0]
    print(doc.title, doc.n_tokens, len(doc.blocks))
    print(doc.truth)            # 这家公司的真实数字（用来出题和判分）
"""

import random
from dataclasses import dataclass, field

NAME_A = ["Arbor", "Beacon", "Cobalt", "Driftwood", "Ember", "Fulcrum", "Granite", "Halcyon", "Ironclad", "Juniper",
          "Keystone", "Lumen", "Meridian", "Northwind", "Onyx", "Pinnacle", "Quarry", "Redwood", "Sable", "Tidewater",
          "Umbra", "Vantage", "Willow", "Zephyr"]
NAME_B = ["Biotech", "Payments", "Holdings", "Energy", "Robotics", "Foods", "Logistics", "Therapeutics", "Minerals",
          "Software", "Textiles", "Aerospace"]
SUFFIX = ["Inc.", "Corp.", "LLC", "Ltd."]

# 和问题无关的长段落（风险因素、法律诉讼、内部控制）：用一些常见的句子随机拼成
FILLER = [
    "Our business is subject to numerous risks and uncertainties that could materially affect our operations.",
    "We may be unable to obtain additional financing on acceptable terms, if at all.",
    "Changes in laws or regulations could adversely affect our business and results of operations.",
    "We depend on a limited number of suppliers for certain components of our products.",
    "Our management concluded that our disclosure controls and procedures were effective as of the end of the period.",
    "From time to time, we may become involved in legal proceedings arising in the ordinary course of business.",
    "Cybersecurity incidents could disrupt our operations and expose us to liability.",
    "Fluctuations in interest rates may affect the fair value of our investments.",
    "We rely on key personnel, and the loss of any of them could harm our business.",
    "Competition in our industry is intense, and many of our competitors have greater resources.",
    "Inflationary pressures may increase our costs and reduce our margins.",
    "There were no changes in our internal control over financial reporting during the quarter.",
    "Our stock price may be volatile, and investors could lose all or part of their investment.",
    "We have identified certain matters that may require additional disclosure in future periods.",
]


# 封面上的样板文字（真实的 10-Q 封面有一大段勾选框和说明；这里是改写过的通用句子）
COVER = [
    "Indicate by check mark whether the registrant has filed all reports required to be filed during the preceding twelve months.",
    "Indicate by check mark whether the registrant has submitted electronically every interactive data file required to be submitted.",
    "Indicate by check mark whether the registrant is a large accelerated filer, an accelerated filer, a non-accelerated filer,",
    "a smaller reporting company, or an emerging growth company, as defined in the applicable rules of the exchange act.",
    "If an emerging growth company, indicate by check mark if the registrant has elected not to use the extended transition period",
    "for complying with any new or revised financial accounting standards provided pursuant to the exchange act.",
    "Indicate by check mark whether the registrant is a shell company. Securities registered pursuant to the act are listed below,",
    "including the title of each class, the trading symbol, and the name of each exchange on which registered.",
    "This quarterly report contains forward-looking statements that involve risks and uncertainties. Words such as anticipate,",
    "believe, estimate, expect, intend, may, plan, project, should, and similar expressions identify forward-looking statements.",
    "These statements are based on current expectations and are subject to risks that could cause actual results to differ materially.",
    "Readers are cautioned not to place undue reliance on these forward-looking statements, which speak only as of the date hereof.",
    "We undertake no obligation to update any forward-looking statement, except as required by law. Unless the context requires",
    "otherwise, references in this report to the Company, we, us, and our refer to the registrant and its consolidated subsidiaries.",
]


@dataclass
class Block:
    """
    文档里的一个"块"：一个标题、一段文字或一张表格。分块时，语义分块不会把一个块切开。

    【字段】
        kind    : "heading" / "para" / "table"
        section : 所在的章节标题（例如 "Notes to Condensed Financial Statements"）
        lines   : 文字的各行（表格每行一行）
    """
    kind: str
    section: str
    lines: list

    @property
    def text(self):
        return "\n".join(self.lines)

    @property
    def n_tokens(self):
        """词数（这里把一个词当作一个 token）。"""
        return sum(len(line.split()) for line in self.lines)


@dataclass
class Doc:
    """一份季度报告。truth 里是真实的数字（判分用，SLIDERS 看不到）。"""
    doc_id: int
    title: str                   # 封面上的正式名称，例如 "ARBOR BIOTECH, INC."
    company: str                 # 规范的名称，例如 "Arbor Biotech Inc."
    blocks: list
    truth: dict = field(default_factory=dict)

    @property
    def n_tokens(self):
        return sum(b.n_tokens for b in self.blocks)

    @property
    def text(self):
        return "\n\n".join(b.text for b in self.blocks)


@dataclass
class Corpus:
    """一批文档。"""
    docs: list

    @property
    def n_tokens(self):
        return sum(d.n_tokens for d in self.docs)

    def by_company(self, name):
        return next(d for d in self.docs if d.company == name)


def _fmt(n):
    """把整数写成带千位分隔符的形式：2979692 → '2,979,692'。"""
    return f"{n:,}"


def make_doc(doc_id, company, rng):
    """
    生成一家公司的季度报告。

    【思路】先随机生成"真实的数字"，再按真实财报的结构把它们写进各个块：
        有的数字出现好几次（写法不同），有的只在附注里，有的需要两个数相加才能得到。
    【返回】Doc
    """
    base, suffix = company
    name = f"{base} {suffix}"
    title = f"{base.upper()}, {suffix.upper()}"
    shares = rng.randint(2_000_000, 200_000_000)
    weighted = int(shares * rng.uniform(0.9, 0.99))
    revenue_k = rng.randint(800, 900_000)                         # 千美元
    margin = rng.uniform(-0.6, 0.25)
    net_k = int(revenue_k * margin)
    ap_k = rng.randint(50, 60_000)
    accrued_k = rng.randint(30, 40_000)
    st_debt_k = rng.randint(0, 50_000)
    lt_debt_k = rng.randint(0, 300_000)
    cash_k = rng.randint(100, 500_000)
    combined_ap = rng.random() < 0.5                              # 资产负债表只有合并的一行
    explicit_debt = rng.random() < 0.5                            # 直接写了借款总额
    truth = {"company": name, "shares_outstanding": shares, "weighted_shares": weighted,
             "revenue": revenue_k * 1000, "net_income": net_k * 1000, "accounts_payable": ap_k * 1000,
             "accrued_expenses": accrued_k * 1000, "total_debt": (st_debt_k + lt_debt_k) * 1000,
             "cash": cash_k * 1000, "combined_ap": combined_ap, "explicit_debt": explicit_debt}
    blocks = []

    def add(kind, section, lines):
        blocks.append(Block(kind, section, lines))

    # ---- 封面 ----
    sec = "Cover Page"
    add("heading", sec, ["UNITED STATES SECURITIES AND EXCHANGE COMMISSION", "FORM 10-Q"])
    add("para", sec, [f"Quarterly report for the quarterly period ended March 31, 2024. Commission file number 001-{rng.randint(10000, 99999)}.",
                      f"{title} (Exact name of registrant as specified in its charter)."])
    add("para", sec, [" ".join(COVER)])                         # 封面上的一大段样板文字（勾选框、说明……）
    # ---- 资产负债表 ----
    sec = "Condensed Balance Sheets"
    add("heading", sec, ["PART I. FINANCIAL INFORMATION", "Item 1. Financial Statements", f"{title}", "CONDENSED BALANCE SHEETS"])
    rows = ["(in thousands, except share data) | March 31, 2024 | December 31, 2023",
            f"Cash and cash equivalents | {_fmt(cash_k)} | {_fmt(int(cash_k * rng.uniform(0.8, 1.2)))}",
            f"Accounts receivable, net | {_fmt(rng.randint(10, 90_000))} | {_fmt(rng.randint(10, 90_000))}",
            f"Prepaid expenses and other current assets | {_fmt(rng.randint(10, 9_000))} | {_fmt(rng.randint(10, 9_000))}",
            f"Inventory | {_fmt(rng.randint(10, 50_000))} | {_fmt(rng.randint(10, 50_000))}",
            f"Short-term investments | {_fmt(rng.randint(0, 90_000))} | {_fmt(rng.randint(0, 90_000))}",
            f"Restricted cash | {_fmt(rng.randint(0, 9_000))} | {_fmt(rng.randint(0, 9_000))}",
            f"Contract assets | {_fmt(rng.randint(0, 9_000))} | {_fmt(rng.randint(0, 9_000))}",
            f"Income taxes receivable | {_fmt(rng.randint(0, 9_000))} | {_fmt(rng.randint(0, 9_000))}",
            f"Total current assets | {_fmt(rng.randint(1_000, 900_000))} | {_fmt(rng.randint(1_000, 900_000))}",
            f"Property and equipment, net | {_fmt(rng.randint(10, 200_000))} | {_fmt(rng.randint(10, 200_000))}",
            f"Operating lease right-of-use assets | {_fmt(rng.randint(10, 30_000))} | {_fmt(rng.randint(10, 30_000))}",
            f"Goodwill | {_fmt(rng.randint(0, 80_000))} | {_fmt(rng.randint(0, 80_000))}",
            f"Intangible assets, net | {_fmt(rng.randint(0, 80_000))} | {_fmt(rng.randint(0, 80_000))}",
            f"Deferred tax assets | {_fmt(rng.randint(0, 30_000))} | {_fmt(rng.randint(0, 30_000))}",
            f"Long-term investments | {_fmt(rng.randint(0, 90_000))} | {_fmt(rng.randint(0, 90_000))}",
            f"Other assets | {_fmt(rng.randint(0, 9_000))} | {_fmt(rng.randint(0, 9_000))}",
            f"Total assets | {_fmt(rng.randint(5_000, 2_000_000))} | {_fmt(rng.randint(5_000, 2_000_000))}"]
    if combined_ap:
        rows.append(f"Accounts payable and accrued expenses | {_fmt(ap_k + accrued_k)} | {_fmt(int((ap_k + accrued_k) * rng.uniform(0.8, 1.2)))}")
    else:
        rows += [f"Accounts payable | {_fmt(ap_k)} | {_fmt(int(ap_k * rng.uniform(0.8, 1.2)))}",
                 f"Accrued expenses | {_fmt(accrued_k)} | {_fmt(int(accrued_k * rng.uniform(0.8, 1.2)))}"]
    rows += [f"Short-term borrowings | {_fmt(st_debt_k)} | {_fmt(int(st_debt_k * rng.uniform(0.8, 1.2)))}",
             f"Deferred revenue | {_fmt(rng.randint(0, 20_000))} | {_fmt(rng.randint(0, 20_000))}",
             f"Long-term debt, net of current portion | {_fmt(lt_debt_k)} | {_fmt(int(lt_debt_k * rng.uniform(0.8, 1.2)))}",
             f"Operating lease liabilities | {_fmt(rng.randint(10, 30_000))} | {_fmt(rng.randint(10, 30_000))}",
             f"Total liabilities | {_fmt(rng.randint(1_000, 900_000))} | {_fmt(rng.randint(1_000, 900_000))}",
             f"Common stock, $0.001 par value; {_fmt(shares * 2)} shares authorized; {_fmt(shares)} shares issued and outstanding | "
             f"{_fmt(shares // 1_000_000)} | {_fmt(int(shares * rng.uniform(0.95, 1.0)) // 1_000_000)}",
             f"Additional paid-in capital | {_fmt(rng.randint(1_000, 900_000))} | {_fmt(rng.randint(1_000, 900_000))}",
             f"Accumulated deficit | ({_fmt(rng.randint(1_000, 900_000))}) | ({_fmt(rng.randint(1_000, 900_000))})",
             f"Total stockholders' equity | {_fmt(rng.randint(1_000, 900_000))} | {_fmt(rng.randint(1_000, 900_000))}",
             f"Total liabilities and stockholders' equity | {_fmt(rng.randint(5_000, 2_000_000))} | {_fmt(rng.randint(5_000, 2_000_000))}"]
    add("table", sec, rows)
    # ---- 利润表 ----
    sec = "Condensed Statements of Operations"
    add("heading", sec, ["CONDENSED STATEMENTS OF OPERATIONS"])
    loss_word = "Net loss" if net_k < 0 else "Net income"
    net_str = f"({_fmt(-net_k)})" if net_k < 0 else _fmt(net_k)
    add("table", sec, ["(in thousands, except per share data) | Three months ended March 31, 2024 | 2023",
                       f"Revenue | {_fmt(revenue_k)} | {_fmt(int(revenue_k * rng.uniform(0.7, 1.3)))}",
                       f"Cost of revenue | {_fmt(int(revenue_k * rng.uniform(0.3, 0.8)))} | {_fmt(int(revenue_k * rng.uniform(0.3, 0.8)))}",
                       f"Research and development | {_fmt(rng.randint(10, 90_000))} | {_fmt(rng.randint(10, 90_000))}",
                       f"Selling, general and administrative | {_fmt(rng.randint(10, 90_000))} | {_fmt(rng.randint(10, 90_000))}",
                       f"Operating income (loss) | {net_str} | {_fmt(rng.randint(-90_000, 90_000))}",
                       f"{loss_word} | {net_str} | {_fmt(rng.randint(-90_000, 90_000))}",
                       f"Weighted average shares outstanding, basic | {_fmt(weighted)} | {_fmt(int(weighted * rng.uniform(0.9, 1.0)))}",
                       f"Net income (loss) per share, basic | {net_k * 1000 / weighted:.2f} | {rng.uniform(-2, 2):.2f}"])
    # ---- 附注 ----
    sec = "Notes to Condensed Financial Statements"
    add("heading", sec, ["NOTES TO CONDENSED FINANCIAL STATEMENTS (UNAUDITED)"])
    for _ in range(rng.randint(3, 6)):
        add("para", sec, [" ".join(rng.sample(FILLER, 5))])
    debt_text = (f"Note 5. Debt. As of March 31, 2024, the Company had short-term borrowings of ${_fmt(st_debt_k)} thousand "
                 f"and long-term debt of ${_fmt(lt_debt_k)} thousand.")
    if explicit_debt:
        debt_text += f" Total borrowings were ${_fmt(st_debt_k + lt_debt_k)} thousand."
    add("para", sec, [debt_text])
    if combined_ap:
        add("para", sec, [f"Note 7. Accounts Payable and Accrued Expenses. Accounts payable were ${_fmt(ap_k)} thousand and "
                          f"accrued expenses were ${_fmt(accrued_k)} thousand as of March 31, 2024."])
    for _ in range(rng.randint(2, 5)):
        add("para", sec, [" ".join(rng.sample(FILLER, 5))])
    if rng.random() < 0.7:                                        # 30% 的文档只在资产负债表里写了流通股数
        add("para", sec, [f"Note 11. Stockholders' Equity. (b) As of March 31, 2024, {_fmt(shares)} shares of common stock "
                          f"were issued and outstanding."])
    # ---- MD&A ----
    sec = "Management's Discussion and Analysis"
    add("heading", sec, ["Item 2. Management's Discussion and Analysis of Financial Condition and Results of Operations"])
    short = base
    add("para", sec, [f"{short} reported revenue of ${revenue_k / 1000:.2f} million for the quarter, "
                      f"{'an increase' if rng.random() < 0.5 else 'a decrease'} compared with the prior year period."])
    add("para", sec, [f"The weighted average number of shares outstanding was {_fmt(weighted)} for the quarter. "
                      f"{'Net loss' if net_k < 0 else 'Net income'} was ${abs(net_k) / 1000:.2f} million."])
    for _ in range(rng.randint(4, 9)):
        add("para", sec, [" ".join(rng.sample(FILLER, 6))])
    # ---- 风险因素等（很长，和问题无关）----
    for sec in ["Item 1A. Risk Factors", "Item 3. Quantitative and Qualitative Disclosures About Market Risk",
                "Item 4. Controls and Procedures", "Part II, Item 1. Legal Proceedings"]:
        add("heading", sec, [sec])
        for _ in range(rng.randint(8, 22)):
            add("para", sec, [" ".join(rng.sample(FILLER, rng.randint(5, 9)))])
    return Doc(doc_id, title, name, blocks, truth)


def make_corpus(n=30, seed=0):
    """
    生成 n 家公司的季度报告。同一个 seed 总是生成同一批文档。

    【参数】n：公司数（不超过 24 × 12 = 288）
    【返回】Corpus
    """
    rng = random.Random(f"corpus|{seed}")
    names = rng.sample([(a, b) for a in NAME_A for b in NAME_B], n)
    docs = []
    for i, (a, b) in enumerate(names):
        # 文档编号在所有批次里都唯一（seed × 10000 + i）：后面的模拟用它来决定"这一块会不会被读错"，
        # 如果不同批次的文档编号相同，同样的错误就会在每一批里重复出现
        docs.append(make_doc(seed * 10000 + i, (f"{a} {b}", rng.choice(SUFFIX)), rng))
    return Corpus(docs)


if __name__ == "__main__":
    c = make_corpus(n=30)
    print(f"{len(c.docs)} 份文档，共 {c.n_tokens:,} 个词；每份 {min(d.n_tokens for d in c.docs):,}~{max(d.n_tokens for d in c.docs):,} 个词")
    d = c.docs[0]
    print(d.title, "|", d.company, "|", d.truth)
    for b in d.blocks[:8]:
        print(f"--- [{b.kind}] {b.section}\n{b.text[:300]}")
