import * as cheerio from "cheerio";

/**
 * Extract text blocks from an SEC filing HTML body.
 * Walks block-level elements, skipping those with block children (avoids double-counting).
 * Excludes `<span>` to prevent duplicate extraction (spans are inline; their text
 * is captured by their parent block element).
 */
function extractTextBlocks($: cheerio.CheerioAPI): string[] {
  const body = $("body");
  if (!body.length) return [];

  $("script, style, noscript, ix\\:header, ix\\:references").remove();

  const blocks: string[] = [];
  const seen = new Set<string>();

  body.find("p, div, td, th, li, h1, h2, h3, h4, h5, h6").each((_, el) => {
    const $el = $(el);
    // Skip if this element has child block elements (avoid double-counting)
    if ($el.children("p, div, td, th, li, h1, h2, h3, h4, h5, h6").length > 0) return;

    const text = $el.text().replace(/\s+/g, " ").trim();
    if (text.length > 0 && !seen.has(text)) {
      seen.add(text);
      blocks.push(text);
    }
  });

  return blocks;
}

/**
 * Strip "Table of Contents" page headers that get merged into content blocks.
 * Handles patterns like:
 *   "Table of Contents Our privacy..."
 *   "33 Table of Contents Our privacy..."
 *   "Table of Contents Glossary of Terms external legal advisors..."
 *   "Table of Contents Item 1B through Item 4 | Unresolved Staff Comments through Mine Safety consistent approach..."
 *   "312 Table of Contents ENTERGY ARKANSAS, LLC AND SUBSIDIARIES..."
 *
 * After stripping the ToC prefix, removes navigation breadcrumbs (section references,
 * glossary labels, company boilerplate) that commonly sit between the ToC header
 * and actual paragraph content.
 */
function stripTableOfContents(text: string): string {
  const tocPattern = /^\d*\s*table\s+of\s+contents\s*/i;
  if (!tocPattern.test(text)) return text;

  let stripped = text.replace(tocPattern, "");

  // If the remaining text after ToC stripping is entirely a short navigation
  // breadcrumb (no sentence-ending punctuation, under 120 chars), discard it.
  // Examples: "Item 1B through Item 4 | Unresolved Staff Comments through Mine Safety"
  //           "Glossary of Terms"
  //           "Part I Item 1A, 1B, and 1C"
  if (stripped.length < 120 && !/[.!?]/.test(stripped)) {
    return "";
  }

  // Iteratively strip known breadcrumb patterns that precede real content.
  // These are short navigation labels prepended to actual paragraph text.
  let prev = "";
  while (prev !== stripped) {
    prev = stripped;
    stripped = stripped
      // "Glossary of Terms" label
      .replace(/^glossary\s+of\s+terms\s*/i, "")
      // "Item 1B through Item 4 | Unresolved Staff Comments through Mine Safety"
      .replace(/^item\s+\d+\w?\s+through\s+item\s+\d+\w?\s*\|.*?(?=\s[A-Z][a-z])/i, "")
      // "Part I Item 1A, 1B, and 1C" or "Part I Item 1A | Business"
      .replace(/^part\s+[IV]+\s+item[^.]*?(?:\s+(?=[A-Z][a-z])|\s*$)/i, "")
      // Company boilerplate: "Entergy Corporation, Utility operating companies, and System Energy"
      .replace(/^[A-Z][A-Za-z\s,.'&-]+(?:corporation|company|companies|energy|subsidiaries)\s*/i, "");
  }

  return stripped.trim();
}

/** Detect page footer/header lines like "Weatherford International plc – 2024 Form 10-K | 20" */
function isPageFooter(text: string): boolean {
  if (text.length > 120) return false;
  // Matches: "Company Name – YYYY Form 10-K | page" or just "page number" lines
  if (/form\s+10-[kq]\s*[|–—-]\s*\d+\s*$/i.test(text)) return true;
  if (/^\s*\d{1,4}\s*$/i.test(text)) return true;
  if (/^page\s+\d+/i.test(text)) return true;
  return false;
}

// ─── Heading / section boundary patterns ───

// Matches "Item 1C" heading — must be near START of line (not a mid-sentence cross-reference)
const ITEM_1C_PATTERN = /^\s*(\u2022\s*)?item\s*1c[\.\s\u00a0—–-]/i;

// Matches ANY subsequent Item heading — broad catch to prevent over-extraction
const NEXT_ITEM_PATTERN = /^[\s\u00a0]*item\s*(\d+[a-z]?\.?\d*)/i;

// Matches section-level boundaries that always end Item 1C.
// Includes subsidiary/registrant financial sections in combined 10-K filings
// (e.g., "ENTERGY ARKANSAS, LLC AND SUBSIDIARIES MANAGEMENT'S FINANCIAL DISCUSSION AND ANALYSIS").
const SECTION_END_PATTERN =
  /^\s*(signatures?|part\s*(ii|2|iii|3|iv|4)|exhibit\s*index|financial\s+statements|management['']?s?\s+financial\s+discussion)/i;

// Detects combined-filing subsidiary headers like "ENTERGY ARKANSAS, LLC AND SUBSIDIARIES"
// Must be ALL-CAPS and short — case-sensitive to avoid matching body text
const SUBSIDIARY_HEADER_PATTERN =
  /^[A-Z][A-Z\s,.'&-]{5,}(?:LLC|INC|CORP|COMPANY|L\.?P\.?)\b.*\bAND\s+SUBSIDIARIES\b/;

/** Check if a text block is a section boundary that ends Item 1C content. */
function isSectionEnd(text: string, isAfterItem1C: boolean): boolean {
  if (text.length > 300) return false;

  if (SECTION_END_PATTERN.test(text)) return true;
  if (text.length < 120 && SUBSIDIARY_HEADER_PATTERN.test(text)) return true;

  const itemMatch = NEXT_ITEM_PATTERN.exec(text);
  if (!itemMatch || !isAfterItem1C) return false;

  // Any Item that isn't 1C itself is a section end
  const itemNum = itemMatch[1]!.toLowerCase();
  if (itemNum === "1c") return false;
  return true;
}

// ─── Max extraction sanity cap ───
// p99 of real Item 1C sections is ~33 paragraphs. Cap at 40 to catch runaway extraction
// while allowing legitimately long sections from large companies.
const MAX_PARAGRAPHS = 40;
const MAX_WORDS = 15000;

/**
 * Extract Item 1C (Cybersecurity) section from a 10-K HTML filing.
 *
 * Strategy:
 * 1. Find the LAST "Item 1C" heading (skips Table of Contents references)
 * 2. Capture content until the next Item heading or section boundary
 * 3. Sanity-cap at MAX_PARAGRAPHS/MAX_WORDS to prevent runaway extraction
 *
 * Returns null if Item 1C is not found.
 */
export function extractItem1C(html: string): string | null {
  const $ = cheerio.load(html);
  const textBlocks = extractTextBlocks($);
  if (textBlocks.length === 0) return null;

  // Find Item 1C start — use the LAST match to skip Table of Contents references
  let startIdx = -1;
  for (let i = 0; i < textBlocks.length; i++) {
    const block = textBlocks[i]!;
    if (ITEM_1C_PATTERN.test(block) && block.length < 300) {
      startIdx = i + 1;
    }
  }

  if (startIdx === -1) return null;

  // Find end: next Item heading or section boundary
  let endIdx = -1;
  for (let i = startIdx; i < textBlocks.length; i++) {
    if (isSectionEnd(textBlocks[i]!, true)) {
      endIdx = i;
      break;
    }
  }

  if (endIdx === -1) endIdx = textBlocks.length;

  // Extract and filter content blocks
  const contentBlocks: string[] = [];
  let totalWords = 0;

  for (let i = startIdx; i < endIdx; i++) {
    let text = textBlocks[i]!;
    if (text.length < 5) continue;

    // Strip "Table of Contents" headers (with optional page numbers or trailing nav text)
    text = stripTableOfContents(text);
    if (text.length < 5) continue;

    // Skip page footers like "Weatherford International plc – 2024 Form 10-K | 20"
    if (isPageFooter(text)) continue;

    contentBlocks.push(text);
    totalWords += text.split(/\s+/).length;

    // Sanity cap
    if (contentBlocks.length >= MAX_PARAGRAPHS || totalWords >= MAX_WORDS) break;
  }

  if (contentBlocks.length === 0) return null;
  return contentBlocks.join("\n\n");
}

// ─── 8-K section boundary patterns ───

// Matches any 8-K Item heading: "Item 1.05", "Item 9.01", etc.
const ITEM_8K_PATTERN = /^\s*item\s*(\d+\.\d+)/i;

// Forward-looking statement headers — treat as section boundary (stop extraction).
// Matches both standalone headings ("Forward-Looking Statements") and inline
// headings merged with body ("Forward-Looking Statements. This report contains...").
const FORWARD_LOOKING_HEADER =
  /^\s*(forward[- ]looking\s+statements?|cautionary\s+(note|statement)\s+(regarding|concerning|about)\s+forward|safe\s+harbor\s+statement)/i;

// Cross-reference stubs: "See Item 1.05 above" or "Refer to Item 1.05"
const CROSS_REF_STUB = /^\s*(see|refer\s+to|the\s+information\s+(set\s+forth|contained|disclosed|provided)\s+(in|under))\s+item\s+\d+\.\d+/i;

// 8-K sanity caps (shorter than 10-K: typical cybersecurity 8-K is 1-10 paragraphs)
const MAX_8K_BLOCKS = 30;
const MAX_8K_WORDS = 10000;

/**
 * Extract Item 1.05 (Material Cybersecurity Incidents) from an 8-K filing.
 * Also checks Items 8.01 and 7.01 (alternate disclosure locations).
 *
 * Strategy (applying 10-K extraction learnings):
 * 1. Try items in priority order: 1.05 → 8.01 → 7.01
 * 2. Use LAST heading match per item (skips Table of Contents references)
 * 3. End at: next Item heading, Signatures, forward-looking disclaimer, or section end
 * 4. Skip cross-reference stubs ("See Item 1.05 above")
 * 5. Strip page artifacts, ToC headers
 * 6. Sanity-cap at MAX_8K_BLOCKS/MAX_8K_WORDS
 */
export function extractItem105(html: string): { item: string; text: string } | null {
  const $ = cheerio.load(html);
  const textBlocks = extractTextBlocks($);
  if (textBlocks.length === 0) return null;

  const targets = [
    { pattern: /item\s*1\.05/i, name: "Item 1.05" },
    { pattern: /item\s*8\.01/i, name: "Item 8.01" },
    { pattern: /item\s*7\.01/i, name: "Item 7.01" },
  ];

  for (const target of targets) {
    // Use LAST match to skip Table of Contents references (10-K lesson #1)
    let startIdx = -1;
    for (let i = 0; i < textBlocks.length; i++) {
      if (target.pattern.test(textBlocks[i]!) && textBlocks[i]!.length < 300) {
        startIdx = i + 1;
      }
    }

    if (startIdx === -1) continue;

    // Find section end
    let endIdx = textBlocks.length;
    for (let i = startIdx; i < textBlocks.length; i++) {
      const block = textBlocks[i]!;

      // Forward-looking statement header — hard stop (check before length guard:
      // these blocks are often heading + body merged into 300+ chars)
      if (FORWARD_LOOKING_HEADER.test(block)) { endIdx = i; break; }

      if (block.length > 300) continue;

      // Signatures — hard stop
      if (/^\s*signatures?\s*$/i.test(block)) { endIdx = i; break; }

      // 10-K-style section boundaries (Part II, Exhibit Index, etc.)
      if (SECTION_END_PATTERN.test(block)) { endIdx = i; break; }

      // Another Item heading (Item X.XX where X.XX != current item)
      const itemMatch = ITEM_8K_PATTERN.exec(block);
      if (itemMatch && block.length < 200) {
        if (!target.pattern.test(block)) {
          endIdx = i;
          break;
        }
      }
    }

    // Extract and filter content blocks
    const contentBlocks: string[] = [];
    let totalWords = 0;

    for (let i = startIdx; i < endIdx; i++) {
      let text = textBlocks[i]!;
      if (text.length < 5) continue;

      // Strip "Table of Contents" headers (same as 10-K)
      text = stripTableOfContents(text);
      if (text.length < 5) continue;

      // Skip page footers/headers
      if (isPageFooter(text)) continue;

      contentBlocks.push(text);
      totalWords += text.split(/\s+/).length;

      // Sanity cap
      if (contentBlocks.length >= MAX_8K_BLOCKS || totalWords >= MAX_8K_WORDS) break;
    }

    // Skip cross-reference stubs like "See Item 1.05 above" (no real content)
    if (contentBlocks.length <= 2) {
      const allText = contentBlocks.join(" ");
      if (CROSS_REF_STUB.test(allText) && allText.split(/\s+/).length < 30) {
        continue; // Try next target item
      }
    }

    if (contentBlocks.length > 0) {
      return { item: target.name, text: contentBlocks.join("\n\n") };
    }
  }

  return null;
}
