#!/data/data/com.termux/files/usr/bin/bash
# blamcode-browser — Web page reader + AI extractor + real-time search for BLAMCODE
#
# Usage:
#   blamcode-browser <url> [question]        — read a page, AI answers about it
#   blamcode-browser <file.html> [question]  — read a local HTML file
#   blamcode-browser search "query" [count]  — real-time web search (DuckDuckGo)
#
# AI model: nemotron-3-ultra-free (default — fastest ~3s, handles 37KB+ text)
# Override:  BLAMCODE_BROWSER_MODEL=deepseek-v4-flash-free
# Provider:  OpenCode Zen (key from OPENCODE_API_KEY, built-in fallback)
#
# Termux notes baked in:
#   - python stdout is broken in some Termux builds → os.write(1, ...)
#   - no /tmp folder → temp dirs come from TMPDIR/TEMP/TMP
#   - payloads are built with json.dump (printf breaks JSON on quotes)

set -e

if [ $# -lt 1 ]; then
    echo "Usage:"
    echo "  blamcode-browser <url> [question]        — read a page, AI answers"
    echo "  blamcode-browser search \"query\" [count]  — real-time web search"
    echo "  blamcode-browser <file.html> [question]  — read a local HTML file"
    exit 1
fi

if command -v python3 >/dev/null 2>&1; then
    PY=python3
elif command -v python >/dev/null 2>&1; then
    PY=python
else
    echo "❌ Python is required for blamcode-browser" >&2
    exit 1
fi

ZEN_KEYS="${OPENCODE_API_KEY:-sk-PKOWRt2391BL0MP3W90yaG8qx4vofQJQgigJreBBYjrArj0lwuU1HkWUqOHgDGHP}"
MODEL="${BLAMCODE_BROWSER_MODEL:-nemotron-3-ultra-free}"

exec "$PY" - "$1" "$2" "$3" "$MODEL" "$ZEN_KEYS" <<'PYBR'
import sys, os, json, re, time
import urllib.request, urllib.error, urllib.parse
import html as html_mod
from html.parser import HTMLParser

argv = sys.argv[1:]
target   = argv[0] if len(argv) > 0 else ""
question = argv[1] if len(argv) > 1 else ""
count    = argv[2] if len(argv) > 2 else ""
model    = argv[3] if len(argv) > 3 else "nemotron-3-ultra-free"
raw_key  = argv[4] if len(argv) > 4 else ""

keys = [k.strip() for k in re.split(r"[,;\s\n]+", raw_key) if k.strip()]
if not keys:
    keys = [os.environ.get("OPENCODE_API_KEY", "")]

def out(s):
    try:
        os.write(1, (str(s) + "\n").encode("utf-8", "replace"))
    except Exception:
        pass

def fetch(url, timeout=40):
    req = urllib.request.Request(url, headers={
        "User-Agent": ("Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 "
                       "(KHTML, like Gecko) Chrome/120.0 Mobile Safari/537.36"),
        "Accept-Language": "en-US,en;q=0.9",
    })
    with urllib.request.urlopen(req, timeout=timeout) as r:
        return r.read().decode("utf-8", "replace")

def strip_tags(s):
    s = re.sub(r"<[^>]+>", "", s or "")
    s = html_mod.unescape(s)
    return re.sub(r"\s+", " ", s).strip()

class TextExtractor(HTMLParser):
    def __init__(self):
        super().__init__(convert_charrefs=True)
        self.parts = []
        self.skip = 0
        self.blocks = {"p", "div", "li", "tr", "h1", "h2", "h3", "h4", "h5",
                       "section", "article", "br", "pre", "blockquote", "td", "th"}
        self.hidden = {"script", "style", "noscript", "template", "svg",
                       "head", "iframe", "form", "nav", "footer"}
    def handle_starttag(self, tag, attrs):
        if tag in self.hidden:
            self.skip += 1
        if tag in self.blocks:
            self.parts.append("\n")
    def handle_endtag(self, tag):
        if tag in self.hidden and self.skip:
            self.skip -= 1
        if tag in self.blocks:
            self.parts.append("\n")
    def handle_data(self, data):
        if not self.skip:
            self.parts.append(data)

def html_to_text(raw):
    p = TextExtractor()
    try:
        p.feed(raw)
    except Exception:
        pass
    text = "".join(p.parts)
    text = re.sub(r"[ \t]+", " ", text)
    text = re.sub(r"\n\s*\n+", "\n", text)
    return text.strip()

def decode_uddg(href):
    href = (href or "").strip()
    if href.startswith("//"):
        href = "https:" + href
    if "uddg=" in href:
        q = urllib.parse.parse_qs(urllib.parse.urlparse(href).query)
        if q.get("uddg"):
            return q["uddg"][0]
    return href

def parse_ddg(page):
    """Parse html.duckduckgo.com results — tolerant of markup changes."""
    results = []
    blocks = re.split(r'<div[^>]*class="[^"]*result[^"]*"[^>]*>', page)[1:]
    for b in blocks:
        m = re.search(r'<a[^>]*href="([^"]+)"[^>]*>(.*?)</a>', b, re.S)
        if not m:
            continue
        url = decode_uddg(m.group(1))
        title = strip_tags(m.group(2))
        if not title or not url.startswith("http"):
            continue
        sm = re.search(r'class="[^"]*snippet[^"]*"[^>]*>(.*?)</(?:a|div|span)>', b, re.S)
        snippet = strip_tags(sm.group(1)) if sm else ""
        results.append({"title": title, "url": url, "snippet": snippet})
        if len(results) >= 10:
            break
    if not results:
        for m in re.finditer(r'<a[^>]*href="([^"]+)"[^>]*>(.*?)</a>', page, re.S):
            url = decode_uddg(m.group(1))
            title = strip_tags(m.group(2))
            if not title or not url.startswith("http"):
                continue
            if any(d in url for d in ("duckduckgo.com", "duck.co", "w3.org")):
                continue
            results.append({"title": title, "url": url, "snippet": ""})
            if len(results) >= 10:
                break
    return results

def parse_ddg_lite(page):
    results = []
    for m in re.finditer(r'<a[^>]*href="([^"]+)"[^>]*>(.*?)</a>', page, re.S):
        url = m.group(1)
        title = strip_tags(m.group(2))
        if not title or not url.startswith("http"):
            continue
        if "duckduckgo.com" in url or "duck.co" in url:
            continue
        results.append({"title": title, "url": url, "snippet": ""})
        if len(results) >= 10:
            break
    return results

def ddg_search(query, n=8):
    q = urllib.parse.quote_plus(query)
    try:
        page = fetch("https://html.duckduckgo.com/html/?q=" + q)
        results = parse_ddg(page)
    except Exception:
        results = []
    if not results:
        try:
            page = fetch("https://lite.duckduckgo.com/lite/?q=" + q)
            results = parse_ddg_lite(page)
        except Exception:
            results = []
    return results[:max(1, n)]

def zen_chat(system, user, key, max_tokens=2500):
    url = "https://opencode.ai/zen/v1/chat/completions"
    payload = {
        "model": model,
        "messages": [
            {"role": "system", "content": system},
            {"role": "user", "content": user},
        ],
        "max_tokens": max_tokens,
    }
    req = urllib.request.Request(
        url,
        data=json.dumps(payload).encode("utf-8"),
        headers={
            "Content-Type": "application/json",
            "Authorization": "Bearer " + key,
            # Zen keys the free quota to the opencode client id — BLAMCODE is a
            # rebranded opencode, so this is our real identity (other UAs
            # land in a permanently-exhausted anonymous bucket).
            "User-Agent": "opencode/1.0.0",
        },
    )
    with urllib.request.urlopen(req, timeout=180) as resp:
        d = json.loads(resp.read().decode("utf-8"))
    return d["choices"][0]["message"]["content"]

def zen_chat_retry(system, user):
    """Rate limits (429) → wait 12s and retry up to 4 times across all keys."""
    errors = []
    for attempt in range(4):
        for k in keys:
            if not k:
                continue
            try:
                return zen_chat(system, user, k)
            except urllib.error.HTTPError as e:
                errors.append(f"HTTP {e.code}")
                if e.code == 429:
                    time.sleep(12)
                else:
                    return f"[browser: provider error {e.code}]"
            except Exception as e:
                errors.append(str(e)[:120])
        if attempt < 3:
            time.sleep(5)
    return "[browser: all attempts failed — " + "; ".join(errors[-3:]) + "]"

def summarize_search(query, results):
    lines = [f"Real-time search results for: {query}\n"]
    for i, r in enumerate(results, 1):
        lines.append(f"{i}. {r['title']}")
        lines.append(f"   URL: {r['url']}")
        if r["snippet"]:
            lines.append(f"   {r['snippet']}")
    text = "\n".join(lines)
    system = ("You are a real-time search assistant. Answer the user's question "
              "using ONLY the search results below. Give a clear, detailed answer "
              "with numbered sources. If results are missing or irrelevant, say so "
              "honestly instead of guessing.")
    return zen_chat_retry(system, text + "\n\nQuestion: " + query)

def answer_page(page_title, text, question):
    system = ("You are a precise web-page reader. Answer the user's question "
              "using ONLY the page text below. Include exact names, numbers, "
              "facts and steps; quote the page when relevant. If the page does "
              "not contain the answer, say so clearly.")
    user = f"Page: {page_title}\n\n--- page text ---\n{text}\n--- end ---\n\nQuestion: {question}"
    return zen_chat_retry(system, user)

def local_or_url_path(p):
    if os.path.isfile(p):
        return os.path.abspath(p)
    if p.startswith(("http://", "https://", "file://")):
        return p
    return "https://" + p

# ---------------- main ----------------
if target == "search":
    query = question or ""
    if not query:
        out("Usage: blamcode-browser search \"query\" [count]")
        sys.exit(1)
    n = 8
    if count and count.isdigit():
        n = int(count)
    out(f"🔎 Searching: {query}\n")
    results = ddg_search(query, n)
    if not results:
        out("⚠ No results found. Try a different query.")
        sys.exit(0)
    out(f"Found {len(results)} result(s):\n")
    for i, r in enumerate(results, 1):
        out(f"{i}. {r['title']}")
        out(f"   {r['url']}")
        if r["snippet"]:
            out(f"   {r['snippet']}")
    out("\n--- AI summary ---")
    answer = summarize_search(query, results)
    out(answer)
    sys.exit(0)

# page mode
p = local_or_url_path(target)
page_title = p
if os.path.isfile(p):
    try:
        with open(p, "r", encoding="utf-8", errors="replace") as fh:
            raw = fh.read()
        page_title = os.path.basename(p)
    except Exception as e:
        out(f"❌ Could not read file {p}: {e}")
        sys.exit(1)
else:
    out(f"🌐 Fetching: {p}\n")
    try:
        raw = fetch(p)
    except Exception as e:
        out(f"❌ Could not fetch {p}: {e}")
        sys.exit(1)
    m = re.search(r"<title[^>]*>(.*?)</title>", raw, re.S | re.I)
    if m:
        page_title = strip_tags(m.group(1))

text = html_to_text(raw)
if len(text) > 55000:
    text = text[:55000] + "\n...[truncated]"
if not text:
    out("⚠ No readable text found on the page.")
    sys.exit(0)

question = question or "Summarize this page in detail — main topic, key facts, structure and anything notable."
out(f"📄 {page_title} ({len(text)} chars)\n")
out("--- AI answer ---")
answer = answer_page(page_title, text, question)
out(answer)
PYBR