#!/usr/bin/env python3
"""
Accordance Port CLI -- comprehensive Bible study from the command line.

Usage:
  accordance study "Genesis 12:1"
  accordance verse "Genesis 12:1" --translation kjv
  accordance strongs "John 3:16"
  accordance lexicon "logos" --lexicon BDAG
  accordance commentary "John 3:16" --module JFB
  accordance compare "John 3:16" --translations KJV,ESV,NASB
  accordance interlinear "Genesis 1:1"
  accordance search "faith" --type all
  accordance daily
  accordance plan --type chronological
"""

from __future__ import annotations

import argparse
import datetime as _dt
import sys
from typing import Dict, List, Optional, Tuple

from .canon import CANON_PROTESTANT, CANON_CATHOLIC, chapter_count

# ---------------------------------------------------------------------------
# Lazy-loaded module caches (loaded on first use, same strategy as MCP server)
# ---------------------------------------------------------------------------

_text_modules: Dict[str, object] = {}
_lexicon_modules: Dict[str, object] = {}
_commentary_modules: Dict[str, object] = {}
_available_lexicons: Dict[str, dict] = {}
_available_commentaries: Dict[str, str] = {}
_modules_loaded: bool = False
_search_index: object = None  # SearchIndex instance (lazy-loaded on first search)


def _load_modules():
    """Lazy-load all Accordance modules (text, lexicon, commentary)."""
    global _modules_loaded
    if _modules_loaded:
        return

    try:
        from accordance_parser.text_module import TextModule
        from accordance_parser.lexicon_module import LexiconModule
        from accordance_parser.commentary_module import CommentaryModule
        from accordance_parser.discovery import discover_modules

        all_mods = discover_modules()
        text_mods = {k: v for k, v in all_mods.items() if v["type"] == 1}

        loaded = 0
        for abbr, info in text_mods.items():
            try:
                _text_modules[abbr] = TextModule(info["path"], abbr)
                loaded += 1
            except Exception:
                pass

        # Lexicon modules (types 3 and 4)
        lex_mods = {k: v for k, v in all_mods.items() if v["type"] in (3, 4)}
        for abbr, info in lex_mods.items():
            try:
                mod = LexiconModule(info["path"])
                _lexicon_modules[abbr] = mod
                _available_lexicons[abbr] = {
                    "name": info["human_name"],
                    "language": mod.language,
                    "format": mod.format,
                    "entry_count": len(mod.headwords()),
                }
            except Exception:
                pass

        # Commentary modules (type 5)
        com_mods = {k: v for k, v in all_mods.items() if v["type"] == 5}
        for abbr, info in com_mods.items():
            try:
                cm = CommentaryModule(info["path"])
                if cm.skipped:
                    cm.close()
                    continue
                books = cm.list_books()
                entry_count = sum(c for _, c in books)
                if entry_count > 0 or len(books) > 2:
                    _commentary_modules[abbr] = cm
                    _available_commentaries[abbr] = (
                        f"{info['human_name']} ({entry_count} sections, "
                        f"{len(books)} books, fmt={cm.format})"
                    )
                else:
                    cm.close()
            except Exception:
                pass

    except Exception as e:
        print(f"Warning: Could not load local modules: {e}", file=sys.stderr)

    _modules_loaded = True


# ---------------------------------------------------------------------------
# Helper: fetch verse text via bolls.life API (stdlib only)
# ---------------------------------------------------------------------------


def _fetch_verse_bolls(reference: str, translation: str = "kjv") -> Optional[object]:
    """Fetch verse from bolls.life API; returns VerseResult or None."""
    try:
        from accordance_parser.bible_api import get_verse

        return get_verse(reference, translation)
    except Exception:
        return None


def _fetch_translations(
    reference: str, translations: List[str]
) -> List[Tuple[str, str]]:
    """Fetch a verse in multiple translations; returns [(code, text), ...]."""
    results = []
    for t in translations:
        t_clean = t.strip()
        vr = _fetch_verse_bolls(reference, t_clean.lower())
        if vr:
            results.append((vr.translation, vr.text))
        else:
            results.append((t_clean.upper(), "[unavailable]"))
    return results


# ---------------------------------------------------------------------------
# Subcommand: verse
# ---------------------------------------------------------------------------


def cmd_verse(args):
    """Get a single verse's text."""
    _load_modules()

    # Try local text modules first
    for abbr, mod in _text_modules.items():
        for test_trans in [args.translation.lower(), args.translation.upper()]:
            if abbr.upper() == test_trans.upper():
                try:
                    text = mod.get_verse(args.reference)
                    if text and len(text.strip()) > 10:
                        print(text)
                        return
                except Exception:
                    pass

    # Fall back to bolls.life API
    vr = _fetch_verse_bolls(args.reference, args.translation)
    if vr:
        print(vr.text)
    else:
        print(
            f"Could not retrieve '{args.reference}' in {args.translation}.",
            file=sys.stderr,
        )
        sys.exit(1)


# ---------------------------------------------------------------------------
# Subcommand: strongs
# ---------------------------------------------------------------------------


def cmd_strongs(args):
    """Show word-by-word Strong's numbers and definitions."""
    _load_modules()

    # Get Strong's via bolls.life KJV
    vr = _fetch_verse_bolls(args.reference, "kjv")
    if vr is None or not vr.strongs:
        print(
            f"Could not retrieve Strong's data for '{args.reference}'.", file=sys.stderr
        )
        sys.exit(1)

    from .bible_api import parse_with_strongs

    is_ot = vr.strongs[0].startswith("H") if vr.strongs else False
    pairs = parse_with_strongs(vr.raw_text, is_ot=is_ot)

    print(f"=== {vr.reference} ({vr.translation}) ===")
    print()

    for word, strongs in pairs:
        if strongs:
            definition = _lookup_strongs_definition(strongs)
            print(f"  [{strongs}] {word}")
            if definition:
                print(f"       {definition}")
        else:
            print(f"  {word} (no Strong's)")
        print()


def _lookup_strongs_definition(strongs_number: str) -> str:
    """Quick lookup of a Strong's definition from loaded lexicons."""
    _load_modules()
    prefix = strongs_number[0].upper()
    try:
        num = int(strongs_number[1:])
    except ValueError:
        return ""

    candidates = [strongs_number, f"{prefix}{num:04d}"]
    seen = set()
    candidates = [c for c in candidates if not (c in seen or seen.add(c))]

    for abbr, mod in _lexicon_modules.items():
        if prefix == "H" and mod.language != "Hebrew":
            continue
        if prefix == "G" and mod.language != "Greek":
            continue
        for key in candidates:
            try:
                entry = mod.lookup(key)
                if entry:
                    first_line = entry.split("\n")[0].strip()
                    if len(first_line) > 120:
                        first_line = first_line[:120] + "..."
                    return first_line
            except Exception:
                pass
    return ""


# ---------------------------------------------------------------------------
# Subcommand: lexicon
# ---------------------------------------------------------------------------


def cmd_lexicon(args):
    """Search lexicons for a headword."""
    _load_modules()

    if not _lexicon_modules:
        print("No lexicon modules available.", file=sys.stderr)
        sys.exit(1)

    # If a specific lexicon is requested
    if args.lexicon:
        if args.lexicon not in _lexicon_modules:
            print(f"Lexicon '{args.lexicon}' not found. Available:", file=sys.stderr)
            for a in sorted(_available_lexicons):
                print(f"  {a}", file=sys.stderr)
            sys.exit(1)

        mod = _lexicon_modules[args.lexicon]
        results = mod.find(args.headword, max_results=args.max_results)
        if not results:
            print(f"No headwords matching '{args.headword}' in {args.lexicon}.")
            return

        info = _available_lexicons[args.lexicon]
        print(
            f"=== {args.lexicon} ({info['language']}, {info['entry_count']} entries) ==="
        )
        print()
        for hw in results:
            entry = mod.lookup(hw)
            if entry:
                preview = entry.split("\n")[0][:200]
                print(f"  {hw}")
                print(f"    {preview}")
                print()
            else:
                print(f"  {hw}")
    else:
        # Search all lexicons
        for abbr, mod in sorted(_lexicon_modules.items()):
            results = mod.find(args.headword, max_results=5)
            if results:
                info = _available_lexicons[abbr]
                print(f"--- {abbr} ({info['language']}) ---")
                for hw in results:
                    print(f"  {hw}")
                print()


# ---------------------------------------------------------------------------
# Subcommand: commentary
# ---------------------------------------------------------------------------


def cmd_commentary(args):
    """Get commentary for a verse."""
    _load_modules()

    if not _commentary_modules:
        print("No commentary modules available.", file=sys.stderr)
        sys.exit(1)

    if args.module:
        if args.module not in _commentary_modules:
            print(f"Commentary '{args.module}' not found. Available:", file=sys.stderr)
            for a in sorted(_available_commentaries):
                print(f"  {a}", file=sys.stderr)
            sys.exit(1)

        cm = _commentary_modules[args.module]
        try:
            text = cm.get_section(args.reference)
            if text and not text.startswith("[No commentary found"):
                print(f"=== {args.module} ===")
                print(text)
            else:
                print(f"No commentary found for '{args.reference}' in {args.module}.")
        except Exception as e:
            print(f"Error reading commentary: {e}", file=sys.stderr)
            sys.exit(1)
    else:
        # Search all commentaries
        for abbr, cm in sorted(_commentary_modules.items()):
            try:
                text = cm.get_section(args.reference)
                if text and not text.startswith("[No commentary found"):
                    preview = text[:500].replace("\n", " ").strip()
                    print(f"--- {abbr} ---")
                    print(preview)
                    print()
            except Exception:
                pass


# ---------------------------------------------------------------------------
# Subcommand: compare
# ---------------------------------------------------------------------------


def cmd_compare(args):
    """Compare translations side by side."""
    trans_list = [t.strip() for t in args.translations.split(",")]
    results = _fetch_translations(args.reference, trans_list)

    # Also get Strong's from KJV
    kjv_vr = _fetch_verse_bolls(args.reference, "kjv")

    print(f"=== Translation Comparison: {args.reference} ===")
    print()
    print(f"{'Translation':<12} Text")
    print("-" * 72)
    for code, text in results:
        print(f"  {code:<10} {text}")

    if kjv_vr and kjv_vr.strongs:
        print()
        strongs_str = " ".join(f"[{s}]" for s in kjv_vr.strongs)
        print(f"Strong's (KJV): {strongs_str}")


# ---------------------------------------------------------------------------
# Subcommand: interlinear
# ---------------------------------------------------------------------------


def cmd_interlinear(args):
    """Interlinear display with Strong's + definitions."""
    _load_modules()

    translation = args.translation.lower()
    vr = _fetch_verse_bolls(args.reference, translation)
    if vr is None:
        print(f"Could not retrieve '{args.reference}'.", file=sys.stderr)
        sys.exit(1)

    if not vr.strongs:
        print(f"{vr.text}")
        print(
            f"(No Strong's numbers available for {translation.upper()}. Try 'kjv'.)",
            file=sys.stderr,
        )
        sys.exit(1)

    from .bible_api import parse_with_strongs

    is_ot = vr.strongs[0].startswith("H") if vr.strongs else False
    pairs = parse_with_strongs(vr.raw_text, is_ot=is_ot)

    print(f"=== Interlinear: {vr.reference} ({vr.translation}) ===")
    print()

    for word, strongs in pairs:
        if strongs:
            definition = _lookup_strongs_definition(strongs)
            short_def = definition
            if len(short_def) > 80:
                short_def = short_def[:80] + "..."
            print(f"  {word}")
            print(f"    Strong's: {strongs}")
            if short_def:
                print(f"    Meaning:  {short_def}")
        else:
            print(f"  {word} (no Strong's)")
        print()


# ---------------------------------------------------------------------------
# Subcommand: study
# ---------------------------------------------------------------------------


def _is_topic_word(text: str) -> bool:
    """Return True if text looks like a topic word, not a verse reference."""
    stripped = text.strip()
    # Verse references contain digits and/or colons (e.g. "John 3:16", "1 Peter 2:5")
    return not any(c.isdigit() or c == ":" for c in stripped)


def cmd_study(args):
    """Comprehensive study: verse + Strong's + lexicon definitions + commentary."""
    _load_modules()

    vr = _fetch_verse_bolls(args.reference, "kjv")
    if vr is None:
        # Check if input is a topic word (no digits, no colon)
        if _is_topic_word(args.reference):
            _topic_study(args.reference)
            return
        print(f"Could not retrieve '{args.reference}'.", file=sys.stderr)
        sys.exit(1)

    print(f"{'=' * 60}")
    print(f"  STUDY: {vr.reference}")
    print(f"{'=' * 60}")
    print()

    # Verse text
    print(f"[VERSE TEXT — {vr.translation}]")
    print(f"  {vr.text}")
    print()

    # Word-by-word analysis
    if vr.strongs:
        from .bible_api import parse_with_strongs

        is_ot = vr.strongs[0].startswith("H") if vr.strongs else False
        pairs = parse_with_strongs(vr.raw_text, is_ot=is_ot)

        tagged_pairs = [(w, s) for w, s in pairs if s is not None]
        lookup_count = min(len(tagged_pairs), 20)

        print("[WORD-BY-WORD ANALYSIS]")
        print()
        for i in range(lookup_count):
            word, s = tagged_pairs[i]
            definition = _lookup_strongs_definition(s)
            print(f"  {word}  ->  [{s}]")
            if definition:
                print(f"           {definition}")
            else:
                print("           (no definition found)")
            print()

        if len(tagged_pairs) > lookup_count:
            print(f"  (+{len(tagged_pairs) - lookup_count} more words not shown)")
            print()

    # Commentary
    print("[COMMENTARY]")
    print()
    if _commentary_modules:
        found_any = False
        for abbr, cm in sorted(_commentary_modules.items()):
            try:
                text = cm.get_section(args.reference)
                if text and not text.startswith("[No commentary found"):
                    preview = text[:400].replace("\n", " ").strip()
                    print(f"  --- {abbr} ---")
                    print(f"  {preview}")
                    print()
                    found_any = True
            except Exception:
                pass
        if not found_any:
            print("  (No commentary found for this verse)")
    else:
        print("  (No commentary modules available)")
    print()

    print(f"{'=' * 60}")


def _topic_study(topic: str):
    """Fallback study for topic words (non-verse inputs).

    Searches Strong's definitions and commentaries for the topic word.
    """
    topic_clean = topic.strip()
    print(f"{'=' * 60}")
    print(f"  TOPIC STUDY: {topic_clean}")
    print(f'  (use `accordance search "{topic_clean}"` for more options)')
    print(f"{'=' * 60}")
    print()

    # Strong's definitions
    print("[STRONG'S DEFINITIONS]")
    print()
    _search_strongs_by_text(topic_clean, max_results=10)
    print()

    # Commentaries
    print("[COMMENTARIES]")
    print()
    if _commentary_modules:
        _search_commentaries_fulltext(topic_clean, max_hits=10)
    else:
        print("  (No commentary modules available)")
    print()

    print(f"{'=' * 60}")


# ---------------------------------------------------------------------------
# Subcommand: search
# ---------------------------------------------------------------------------


def cmd_search(args):
    """Search across lexicons, commentaries, and Strong's definitions."""
    _load_modules()

    query = args.query.strip()
    search_type = args.type.lower()

    if search_type in ("all", "lexicon", "lexicons"):
        print("[LEXICON HEADWORD SEARCH]")
        print()
        if _lexicon_modules:
            for abbr, mod in sorted(_lexicon_modules.items()):
                results = mod.find(query, max_results=5)
                if results:
                    info = _available_lexicons[abbr]
                    print(f"  --- {abbr} ({info['language']}) ---")
                    for hw in results:
                        print(f"    {hw}")
                    print()
        else:
            print("  (No lexicons available)")
        print()

    if search_type in ("all", "commentary", "commentaries"):
        print("[COMMENTARY FULL-TEXT SEARCH]")
        print()
        if _commentary_modules:
            _search_commentaries_fulltext(query, max_hits=10)
        else:
            print("  (No commentaries available)")
        print()

    if search_type in ("all", "strongs", "definition", "definitions"):
        print("[STRONG'S DEFINITION SEARCH]")
        print()
        _search_strongs_by_text(query, max_results=10)
        print()


def _get_search_index():
    """Return the module-level SearchIndex singleton, creating it on first call."""
    global _search_index
    if _search_index is None:
        from accordance_parser.search_index import SearchIndex

        _search_index = SearchIndex(db_path=None)
        _search_index.ensure_index()
    return _search_index


def _search_commentaries_fulltext(query: str, max_hits: int = 10):
    """Search commentary text using the FTS5 search index."""
    try:
        si = _get_search_index()
    except Exception as e:
        print(f"  (Search index unavailable: {e})", file=sys.stderr)
        return

    results = si.search(
        query,
        source_type="commentary",
        language="any",
        max_results=max_hits,
        fuzzy=False,
        snippet=True,
    )

    if not results:
        print("  (No commentary matches found)")
        return

    # Group results by source_name, keeping rank ordering
    seen_sources = set()
    printed = 0
    for r in results:
        source_type, source_name, source_id, snippet, citation, rank = r
        if printed >= max_hits:
            break
        if source_name in seen_sources:
            # Already printed this source; show at most one per source
            continue
        seen_sources.add(source_name)
        printed += 1

        print(f"  --- {source_name} ---")
        if snippet:
            # Strip FTS5 <mark> tags for clean CLI output
            clean_snippet = snippet.replace("<mark>", "").replace("</mark>", "")
            print(f"    {clean_snippet}")
        elif citation:
            print(f"    {citation}")
        print()


def _search_strongs_by_text(query: str, max_results: int = 10):
    """Search Strong's definitions for text matches."""
    query_lower = query.lower()
    results = []
    strongs_lexicons = {
        abbr: mod
        for abbr, mod in _lexicon_modules.items()
        if mod._is_bdb or mod._is_numbered
    }
    if not strongs_lexicons:
        print("  (No Strong's-numbered lexicons available)")
        return

    for abbr, mod in sorted(strongs_lexicons.items()):
        lang = mod.language
        prefix = "H" if lang == "Hebrew" else "G"
        name = _available_lexicons.get(abbr, {}).get("name", abbr)
        seen = set()

        for key in mod._entries:
            if key.startswith(prefix) and key[1:].isdigit():
                normalized = f"{prefix}{int(key[1:]):04d}"
            elif key.isdigit():
                normalized = f"{prefix}{int(key):04d}"
            else:
                continue
            if normalized in seen:
                continue
            seen.add(normalized)

            try:
                entry = mod.lookup(key)
                if entry and query_lower in entry.lower():
                    idx = entry.lower().find(query_lower)
                    start = max(0, idx - 40)
                    end = min(len(entry), idx + len(query) + 80)
                    snippet = (
                        entry[start:end].replace("\r", " ").replace("\n", " ").strip()
                    )
                    if start > 0:
                        snippet = "..." + snippet
                    if end < len(entry):
                        snippet = snippet + "..."

                    print(f"  [{normalized}] ({name})")
                    print(f"    {snippet}")
                    print()
                    results.append(normalized)

                    if len(results) >= max_results:
                        break
            except Exception:
                pass

        if len(results) >= max_results:
            break


# ---------------------------------------------------------------------------
# Subcommand: daily
# ---------------------------------------------------------------------------


def cmd_daily(args):
    """Show today's Mass readings (Catholic lectionary)."""
    from accordance_parser.bible_api import get_verse_range
    from accordance_parser.lectionary import get_lectionary_readings

    # Parse date
    if args.date and args.date.strip():
        try:
            target = _dt.date.fromisoformat(args.date.strip())
        except ValueError:
            print(
                f"Invalid date format: '{args.date}'. Use YYYY-MM-DD.", file=sys.stderr
            )
            sys.exit(1)
    else:
        target = _dt.date.today()

    display_date = target.strftime("%A, %B %d, %Y")

    season, reading_refs = get_lectionary_readings(target)

    print(f"Daily Mass Readings: {display_date}")
    print(f"{'=' * 50}")
    print(f"Season: {season}")
    print()

    reading_labels = {
        "first": "First Reading",
        "psalm": "Responsorial Psalm",
        "second": "Second Reading",
        "gospel": "Gospel",
    }

    for key, label in reading_labels.items():
        ref = reading_refs.get(key, "")
        if not ref:
            if key == "second":
                continue
            continue

        print(f"[{label}]")
        print(f"  {ref}")
        print()

        # Fetch reading text (handles verse ranges like "Acts 20:17-27")
        verses = get_verse_range(ref, "nabre")
        if verses:
            combined = " ".join(v.text for v in verses)
            print(f"  {combined}")
        elif ref.startswith("("):
            print(f"  {ref}")
        else:
            print("  (NABRE text not available)")
        print()


# ---------------------------------------------------------------------------
# Subcommand: plan
# ---------------------------------------------------------------------------


def cmd_plan(args):
    """Show next 7 days of a reading plan."""
    from accordance_parser.bible_api import BOOK_ID_TO_NAME

    plan_type = args.type.lower().strip()
    schedule = _get_plan_schedule(plan_type)
    plan_name = _get_plan_name(plan_type)

    start_date = _dt.date(2026, 1, 1)
    today = _dt.date.today()
    day_number = (today - start_date).days

    print(f"Daily Reading Plan: {plan_name}")
    print(f"{'=' * 50}")
    print(f"Total days in plan: {len(schedule)}")
    print()
    print(f"{'Day':<6} {'Date':<14} Reading")
    print("-" * 72)

    for offset in range(7):
        day = (day_number + offset) % len(schedule)
        reading = schedule[day]
        date = today + _dt.timedelta(days=offset)
        date_str = date.strftime("%a %b %d")
        parts = []
        for book_id, start_ch, end_ch in reading:
            name = BOOK_ID_TO_NAME.get(book_id, f"Book {book_id}")
            if start_ch == end_ch:
                parts.append(f"{name} {start_ch}")
            else:
                parts.append(f"{name} {start_ch}-{end_ch}")
        reading_str = "; ".join(parts)
        print(f"  {day + 1:<4} {date_str:<14} {reading_str}")


def _get_plan_name(plan_type: str) -> str:
    names = {
        "chronological": "Chronological",
        "canonical": "Canonical (Genesis -> Revelation)",
        "catholic": "Catholic (73 books, deuterocanonical included)",
        "nt-first": "New Testament First",
        "psalms-proverbs": "Psalms & Proverbs",
    }
    return names.get(plan_type, "Chronological")


def _get_plan_schedule(plan_type: str) -> List[List[Tuple[int, int, int]]]:
    """Return a reading plan schedule: list of [(book_id, start_ch, end_ch), ...] per day."""
    # --- Chronological ---
    _CHRON = [
        [(1, 1, 3)],
        [(1, 4, 7)],
        [(1, 8, 11)],
        [(18, 1, 5)],
        [(18, 6, 9)],
        [(18, 10, 13)],
        [(18, 14, 16)],
        [(18, 17, 20)],
        [(18, 21, 24)],
        [(18, 25, 28)],
        [(18, 29, 31)],
        [(18, 32, 34)],
        [(18, 35, 37)],
        [(18, 38, 42)],
        [(1, 12, 15)],
        [(1, 16, 18)],
        [(1, 19, 21)],
        [(1, 22, 24)],
        [(1, 25, 26)],
        [(1, 27, 29)],
        [(1, 30, 31)],
        [(1, 32, 34)],
        [(1, 35, 37)],
        [(1, 38, 40)],
        [(1, 41, 42)],
        [(1, 43, 45)],
        [(1, 46, 47)],
        [(1, 48, 50)],
        [(2, 1, 3)],
        [(2, 4, 6)],
        [(2, 7, 9)],
        [(2, 10, 12)],
        [(2, 13, 15)],
        [(2, 16, 18)],
        [(2, 19, 21)],
        [(2, 22, 24)],
        [(2, 25, 27)],
        [(2, 28, 29)],
        [(2, 30, 32)],
        [(2, 33, 35)],
        [(2, 36, 38)],
        [(2, 39, 40)],
        [(3, 1, 4)],
        [(3, 5, 7)],
        [(3, 8, 10)],
        [(3, 11, 13)],
        [(3, 14, 16)],
        [(3, 17, 19)],
        [(3, 20, 22)],
        [(3, 23, 25)],
        [(3, 26, 27)],
        [(4, 1, 3)],
        [(4, 4, 6)],
        [(4, 7, 9)],
        [(4, 10, 12)],
        [(4, 13, 15)],
        [(4, 16, 18)],
        [(4, 19, 21)],
        [(4, 22, 24)],
        [(4, 25, 27)],
        [(4, 28, 30)],
        [(4, 31, 33)],
        [(4, 34, 36)],
        [(19, 90, 95)],
        [(5, 1, 3)],
        [(5, 4, 6)],
        [(5, 7, 9)],
        [(5, 10, 12)],
        [(5, 13, 15)],
        [(5, 15, 18)],
        [(5, 19, 21)],
        [(5, 22, 24)],
        [(5, 25, 27)],
        [(5, 28, 30)],
        [(5, 31, 34)],
        [(6, 1, 4)],
        [(6, 5, 8)],
        [(6, 9, 12)],
        [(6, 13, 17)],
        [(6, 18, 21)],
        [(6, 22, 24)],
        [(7, 1, 3)],
        [(7, 4, 6)],
        [(7, 7, 9)],
        [(7, 10, 12)],
        [(7, 13, 16)],
        [(7, 17, 21)],
        [(8, 1, 4)],
        [(9, 1, 4)],
        [(9, 5, 8)],
        [(9, 9, 12)],
        [(9, 13, 17)],
        [(9, 18, 20)],
        [(9, 21, 24)],
        [(9, 25, 28)],
        [(9, 29, 31)],
        [(10, 1, 4)],
        [(10, 5, 8)],
        [(10, 9, 12)],
        [(10, 13, 16)],
        [(10, 17, 20)],
        [(10, 21, 24)],
        [(13, 1, 4)],
        [(13, 5, 8)],
        [(13, 9, 12)],
        [(13, 13, 17)],
        [(13, 18, 22)],
        [(13, 23, 26)],
        [(13, 27, 29)],
        [(19, 1, 8)],
        [(19, 9, 17)],
        [(19, 18, 24)],
        [(19, 25, 32)],
        [(19, 33, 38)],
        [(19, 39, 45)],
        [(19, 46, 52)],
        [(19, 53, 59)],
        [(19, 60, 67)],
        [(19, 68, 72)],
        [(11, 1, 4)],
        [(11, 5, 8)],
        [(11, 9, 12)],
        [(11, 13, 17)],
        [(11, 18, 22)],
        [(12, 1, 4)],
        [(12, 5, 8)],
        [(12, 9, 12)],
        [(12, 13, 15)],
        [(12, 16, 20)],
        [(12, 21, 25)],
        [(20, 1, 4)],
        [(20, 5, 8)],
        [(20, 9, 12)],
        [(20, 13, 17)],
        [(20, 18, 22)],
        [(20, 23, 27)],
        [(20, 28, 31)],
        [(21, 1, 6)],
        [(21, 7, 12)],
        [(22, 1, 4)],
        [(22, 5, 8)],
        [(19, 73, 78)],
        [(19, 79, 85)],
        [(19, 86, 89)],
        [(23, 1, 4)],
        [(23, 5, 8)],
        [(23, 9, 12)],
        [(23, 13, 17)],
        [(23, 18, 22)],
        [(23, 23, 27)],
        [(23, 28, 31)],
        [(23, 32, 36)],
        [(23, 37, 41)],
        [(23, 42, 45)],
        [(23, 46, 50)],
        [(23, 51, 55)],
        [(23, 56, 60)],
        [(23, 61, 66)],
        [(24, 1, 4)],
        [(24, 5, 8)],
        [(24, 9, 12)],
        [(24, 13, 17)],
        [(24, 18, 22)],
        [(24, 23, 27)],
        [(24, 28, 32)],
        [(24, 33, 37)],
        [(24, 38, 42)],
        [(24, 43, 47)],
        [(24, 48, 52)],
        [(26, 1, 5)],
        [(26, 6, 10)],
        [(26, 11, 15)],
        [(26, 16, 20)],
        [(26, 21, 25)],
        [(26, 26, 30)],
        [(26, 31, 35)],
        [(26, 36, 40)],
        [(26, 41, 44)],
        [(26, 45, 48)],
        [(27, 1, 4)],
        [(27, 5, 8)],
        [(27, 9, 12)],
        [(28, 1, 7)],
        [(28, 8, 14)],
        [(29, 1, 3)],
        [(30, 1, 5)],
        [(30, 6, 9)],
        [(31, 1, 1)],
        [(32, 1, 4)],
        [(33, 1, 4)],
        [(33, 5, 7)],
        [(34, 1, 3)],
        [(35, 1, 3)],
        [(36, 1, 3)],
        [(37, 1, 2)],
        [(38, 1, 7)],
        [(38, 8, 14)],
        [(39, 1, 4)],
        [(15, 1, 5)],
        [(15, 6, 10)],
        [(16, 1, 6)],
        [(16, 7, 13)],
        [(17, 1, 5)],
        [(17, 6, 10)],
        [(17, 11, 16)],
        [(19, 107, 118)],
        [(19, 119, 119)],
        [(40, 1, 3)],
        [(40, 4, 6)],
        [(40, 7, 9)],
        [(40, 10, 12)],
        [(40, 13, 15)],
        [(40, 16, 18)],
        [(40, 19, 21)],
        [(40, 22, 24)],
        [(40, 25, 28)],
        [(41, 1, 3)],
        [(41, 4, 6)],
        [(41, 7, 9)],
        [(41, 10, 12)],
        [(41, 13, 16)],
        [(42, 1, 3)],
        [(42, 4, 6)],
        [(42, 7, 9)],
        [(42, 10, 12)],
        [(42, 13, 16)],
        [(42, 17, 19)],
        [(42, 20, 22)],
        [(42, 23, 24)],
        [(43, 1, 4)],
        [(43, 5, 8)],
        [(43, 9, 12)],
        [(43, 13, 17)],
        [(43, 18, 21)],
        [(44, 1, 4)],
        [(44, 5, 8)],
        [(44, 9, 12)],
        [(44, 13, 16)],
        [(44, 17, 20)],
        [(44, 21, 24)],
        [(44, 25, 28)],
        [(45, 1, 4)],
        [(45, 5, 8)],
        [(45, 9, 12)],
        [(45, 13, 16)],
        [(46, 1, 5)],
        [(46, 6, 10)],
        [(46, 11, 16)],
        [(47, 1, 4)],
        [(47, 5, 9)],
        [(47, 10, 13)],
        [(48, 1, 3)],
        [(48, 4, 6)],
        [(49, 1, 4)],
        [(49, 5, 6)],
        [(50, 1, 2)],
        [(50, 3, 4)],
        [(51, 1, 4)],
        [(52, 1, 3)],
        [(52, 4, 5)],
        [(53, 1, 3)],
        [(54, 1, 3)],
        [(54, 4, 6)],
        [(55, 1, 4)],
        [(56, 1, 3)],
        [(57, 1, 1)],
        [(58, 1, 4)],
        [(58, 5, 8)],
        [(58, 9, 13)],
        [(59, 1, 3)],
        [(59, 4, 5)],
        [(60, 1, 3)],
        [(60, 4, 5)],
        [(61, 1, 3)],
        [(62, 1, 3)],
        [(62, 4, 5)],
        [(63, 1, 1)],
        [(64, 1, 1)],
        [(65, 1, 1)],
        [(66, 1, 3)],
        [(66, 4, 7)],
        [(66, 8, 11)],
        [(66, 12, 14)],
        [(66, 15, 18)],
        [(66, 19, 22)],
        [(19, 120, 130)],
        [(19, 131, 140)],
        [(19, 141, 150)],
        [(25, 1, 5)],
        [(14, 1, 4)],
        [(14, 5, 9)],
        [(14, 10, 14)],
        [(14, 15, 19)],
        [(14, 20, 24)],
        [(14, 25, 29)],
        [(14, 30, 36)],
    ]

    if plan_type == "chronological":
        return _CHRON

    # Canonical: Genesis -> Revelation, 3-4 chapters per day
    canonical = []
    books = [
        (b.bolls_id, chapter_count(b.osis, CANON_PROTESTANT))
        for b in CANON_PROTESTANT.books
        if b.bolls_id is not None
    ]
    for book_id, total_ch in books:
        ch = 1
        while ch <= total_ch:
            end = min(ch + 3, total_ch)
            canonical.append([(book_id, ch, end)])
            ch = end + 1

    if plan_type == "canonical":
        return canonical

    # Catholic: 73 books interspersed (deuterocanon in Septuagint order)
    if plan_type == "catholic":
        catholic = []
        cath_books = [
            (b.bolls_id, chapter_count(b.osis, CANON_CATHOLIC))
            for b in CANON_CATHOLIC.books
            if b.bolls_id is not None
        ]
        for book_id, total_ch in cath_books:
            ch = 1
            while ch <= total_ch:
                end = min(ch + 3, total_ch)
                catholic.append([(book_id, ch, end)])
                ch = end + 1
        return catholic

    # NT-first
    if plan_type == "nt-first":
        nt_first = []
        for book_id, total_ch in books:
            if book_id >= 40:
                ch = 1
                while ch <= total_ch:
                    end = min(ch + 3, total_ch)
                    nt_first.append([(book_id, ch, end)])
                    ch = end + 1
        for book_id, total_ch in books:
            if book_id < 40:
                ch = 1
                while ch <= total_ch:
                    end = min(ch + 3, total_ch)
                    nt_first.append([(book_id, ch, end)])
                    ch = end + 1
        return nt_first

    # Psalms-Proverbs
    if plan_type == "psalms-proverbs":
        pp = []
        ps_ch = 1
        for _ in range(150):
            ps_end = min(ps_ch + 4, 150)
            prov_ch = ((ps_ch - 1) // 5 % 31) + 1
            day = [(19, ps_ch, ps_end)]
            if prov_ch <= 31:
                day.append((20, prov_ch, prov_ch))
            pp.append(day)
            ps_ch = ps_end + 1
            if ps_ch > 150:
                ps_ch = 1
        return pp

    return _CHRON  # default: chronological


# ---------------------------------------------------------------------------
# Argument parser
# ---------------------------------------------------------------------------


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        prog="accordance",
        description="Accordance Port CLI -- Bible study from the command line",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  accordance verse "Genesis 1:1"
  accordance verse "Genesis 1:1" --translation esv
  accordance strongs "John 3:16"
  accordance interlinear "Genesis 12:1"
  accordance study "John 3:16"
  accordance lexicon "logos" --lexicon BDAG
  accordance commentary "John 3:16" --module JFB
  accordance compare "John 3:16" --translations KJV,ESV,NASB
  accordance search "covenant" --type all
  accordance daily
  accordance plan --type chronological
""",
    )

    sub = parser.add_subparsers(dest="command", help="Available commands")

    # --- verse ---
    p_verse = sub.add_parser("verse", help="Get a single verse's text")
    p_verse.add_argument(
        "reference", help="Bible reference (e.g. 'Genesis 1:1', 'John 3:16')"
    )
    p_verse.add_argument(
        "--translation",
        default="kjv",
        help="Translation code (default: kjv; see BOLLS_CODES)",
    )

    # --- strongs ---
    p_strongs = sub.add_parser(
        "strongs", help="Word-by-word Strong's numbers and definitions"
    )
    p_strongs.add_argument("reference", help="Bible reference (e.g. 'John 3:16')")

    # --- lexicon ---
    p_lex = sub.add_parser("lexicon", help="Search lexicons for a headword")
    p_lex.add_argument(
        "headword",
        help="Headword to search for (e.g. 'logos' for Greek, 'אָב' for Hebrew)",
    )
    p_lex.add_argument(
        "--lexicon",
        default="",
        help="Lexicon abbreviation (e.g. 'BDAG', 'HALOT'). Empty = search all.",
    )
    p_lex.add_argument(
        "--max-results", type=int, default=20, help="Max results (default: 20)"
    )

    # --- commentary ---
    p_com = sub.add_parser("commentary", help="Get commentary for a verse")
    p_com.add_argument("reference", help="Bible reference (e.g. 'John 3:16')")
    p_com.add_argument(
        "--module",
        default="",
        help="Commentary abbreviation (e.g. 'JFB'). Empty = search all.",
    )

    # --- compare ---
    p_cmp = sub.add_parser("compare", help="Compare translations side by side")
    p_cmp.add_argument("reference", help="Bible reference (e.g. 'John 3:16')")
    p_cmp.add_argument(
        "--translations",
        default="KJV,ESV,NASB",
        help="Comma-separated translation codes (default: KJV,ESV,NASB)",
    )

    # --- interlinear ---
    p_int = sub.add_parser(
        "interlinear", help="Interlinear display with Strong's + definitions"
    )
    p_int.add_argument("reference", help="Bible reference (e.g. 'Genesis 1:1')")
    p_int.add_argument(
        "--translation", default="kjv", help="Translation code (default: kjv)"
    )

    # --- study ---
    p_study = sub.add_parser(
        "study", help="Comprehensive study: verse + Strong's + lexicon + commentary"
    )
    p_study.add_argument("reference", help="Bible reference (e.g. 'Genesis 12:1')")

    # --- search ---
    p_search = sub.add_parser(
        "search", help="Search across lexicons, commentaries, and Strong's definitions"
    )
    p_search.add_argument("query", help="Search term (e.g. 'faith', 'covenant')")
    p_search.add_argument(
        "--type",
        default="all",
        choices=["all", "lexicon", "commentary", "strongs"],
        help="Search scope (default: all)",
    )

    # --- daily ---
    p_daily = sub.add_parser(
        "daily", help="Today's Mass readings (Catholic lectionary)"
    )
    p_daily.add_argument(
        "--date", default="", help="Date in YYYY-MM-DD format (default: today)"
    )

    # --- plan ---
    p_plan = sub.add_parser("plan", help="Daily reading plan for the next 7 days")
    p_plan.add_argument(
        "--type",
        default="chronological",
        choices=[
            "chronological",
            "canonical",
            "catholic",
            "nt-first",
            "psalms-proverbs",
        ],
        help="Plan type (default: chronological)",
    )

    return parser


# ---------------------------------------------------------------------------
# Main entry point
# ---------------------------------------------------------------------------

COMMAND_MAP = {
    "verse": cmd_verse,
    "strongs": cmd_strongs,
    "lexicon": cmd_lexicon,
    "commentary": cmd_commentary,
    "compare": cmd_compare,
    "interlinear": cmd_interlinear,
    "study": cmd_study,
    "search": cmd_search,
    "daily": cmd_daily,
    "plan": cmd_plan,
}


def main():
    parser = build_parser()
    args = parser.parse_args()

    if args.command is None:
        parser.print_help()
        sys.exit(1)

    fn = COMMAND_MAP.get(args.command)
    if fn is None:
        print(f"Unknown command: {args.command}", file=sys.stderr)
        sys.exit(1)

    fn(args)


if __name__ == "__main__":
    main()
