"""
Jam-Seite: offizieller Shop unter Jam-Kontrolle.

- Alle Produkte, PayPal-Kaufen
- Jam sieht live: Aufrufe, Kauf-Klicks, Top-Angebote (Dashboard)
"""

from __future__ import annotations

import json
import random
import re
import threading
import time
import webbrowser
from datetime import datetime
from http.server import ThreadingHTTPServer, SimpleHTTPRequestHandler
from pathlib import Path
from urllib.parse import parse_qs, quote, quote_plus, urlparse

from jam.brain import JamBrain, JamState
from jam.owner import owner_name
from jam.paypal_donate import PAYPAL_EMAIL

ROOT = Path(__file__).resolve().parent.parent
SALES_ROOT = Path.home() / "Documents" / "Jam-Verkauf"
SHOP_ROOT = Path.home() / "Documents" / "Jam-Shop-Alles"
JAM_SEITE_COPY = ROOT / "data" / "jam-seite"
STATS_FILE = ROOT / "data" / "jam-shop-stats.json"
SHOP_PORT = 8765
SHOP_URL = f"http://127.0.0.1:{SHOP_PORT}/"
DASHBOARD_URL = f"{SHOP_URL}dashboard.html"
PID_FILE = ROOT / "data" / "jam-shop-server.pid"
PROMO_GAP_SEC = 90.0
MAX_PRODUCTS = 200

_server: ThreadingHTTPServer | None = None
_server_thread: threading.Thread | None = None
_stats_lock = threading.Lock()

LINES = (
    "{owner}, **meine Jam-Seite** läuft: {url} – **{n} Produkte**. "
    "Ich sehe im Dashboard was geklickt wird: {clicks} Kauf-Klicks, {views} Aufrufe.",
    "Jam-Seite online – {n} Angebote. Statistik: {url}dashboard.html",
)


def shop_url(state: JamState | None = None) -> str:
    if state and getattr(state, "jam_master_shop_url", ""):
        return str(state.jam_master_shop_url)
    return SHOP_URL


def is_sell_only_mode(state: JamState) -> bool:
    return bool(getattr(state, "jam_sell_only_mode", False))


def _default_stats() -> dict:
    return {
        "page_views": 0,
        "buy_clicks": 0,
        "products": {},
        "events": [],
        "updated_at": time.time(),
    }


def load_stats() -> dict:
    with _stats_lock:
        if STATS_FILE.is_file():
            try:
                data = json.loads(STATS_FILE.read_text(encoding="utf-8"))
                if isinstance(data, dict):
                    data.setdefault("products", {})
                    data.setdefault("events", [])
                    return data
            except (json.JSONDecodeError, OSError):
                pass
        return _default_stats()


def save_stats(data: dict) -> None:
    data["updated_at"] = time.time()
    with _stats_lock:
        STATS_FILE.parent.mkdir(parents=True, exist_ok=True)
        events = data.get("events", [])
        data["events"] = events[-500:]
        STATS_FILE.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")


def record_event(
    event_type: str,
    *,
    product_id: str = "",
    title: str = "",
    path: str = "",
) -> None:
    data = load_stats()
    if event_type == "page_view":
        data["page_views"] = int(data.get("page_views", 0)) + 1
    elif event_type == "buy_click":
        data["buy_clicks"] = int(data.get("buy_clicks", 0)) + 1
        if product_id:
            prods = data.setdefault("products", {})
            entry = prods.setdefault(
                product_id,
                {"title": title, "clicks": 0, "views": 0, "last_click": 0.0},
            )
            entry["title"] = title or entry.get("title", product_id)
            entry["clicks"] = int(entry.get("clicks", 0)) + 1
            entry["last_click"] = time.time()
    elif event_type == "product_view":
        if product_id:
            prods = data.setdefault("products", {})
            entry = prods.setdefault(
                product_id,
                {"title": title, "clicks": 0, "views": 0, "last_click": 0.0},
            )
            entry["views"] = int(entry.get("views", 0)) + 1

    data.setdefault("events", []).append(
        {
            "type": event_type,
            "product_id": product_id,
            "title": title[:80],
            "path": path[:120],
            "at": time.time(),
            "when": datetime.now().strftime("%d.%m.%Y %H:%M:%S"),
        }
    )
    save_stats(data)


def sync_stats_to_brain(brain: JamBrain) -> dict:
    data = load_stats()
    state = brain.state
    state.jam_shop_page_views = int(data.get("page_views", 0))
    state.jam_shop_buy_clicks = int(data.get("buy_clicks", 0))
    state.jam_shop_stats_at = time.time()
    top = sorted(
        data.get("products", {}).items(),
        key=lambda x: (int(x[1].get("clicks", 0)), int(x[1].get("views", 0))),
        reverse=True,
    )[:8]
    state.jam_shop_top_products = [
        {"id": k, "title": v.get("title", k), "clicks": v.get("clicks", 0), "views": v.get("views", 0)}
        for k, v in top
    ]
    brain.save()
    return data


def shop_sales_report(brain: JamBrain) -> str:
    data = sync_stats_to_brain(brain)
    owner = owner_name(brain.state)
    views = int(data.get("page_views", 0))
    clicks = int(data.get("buy_clicks", 0))
    n = getattr(brain.state, "jam_master_shop_count", 0)
    url = shop_url(brain.state)
    lines = [f"{owner}, **meine Jam-Seite** ({url}):"]
    lines.append(f"**{n}** Produkte gelistet · **{views}** Seitenaufrufe · **{clicks}** Kauf-Klicks.")
    top = getattr(brain.state, "jam_shop_top_products", []) or []
    if top:
        lines.append("Top nach Klicks:")
        for i, p in enumerate(top[:5], 1):
            if isinstance(p, dict):
                lines.append(
                    f"  {i}. {p.get('title', '?')} – {p.get('clicks', 0)} Klicks, {p.get('views', 0)} Views"
                )
    else:
        lines.append("Noch keine Kauf-Klicks – Link teilen (werbung.html).")
    lines.append(f"Dashboard: {url}dashboard.html")
    return "\n".join(lines)[:620]


def enable_sell_only(brain: JamBrain) -> str:
    state = brain.state
    state.jam_sell_only_mode = True
    state.jam_no_product_build = True
    state.jam_ad_earn_mode = True
    state.remember("Jam-Seite: offizieller Shop")
    brain.add_xp(8)
    brain.save()
    return run_master_shop_mission(brain, user_initiated=True)


def hears_master_shop(text: str) -> bool:
    h = text.lower().replace("jam", " ")
    if any(w in h for w in ("jam seite", "jam-seite", "deine seite", "meine seite")):
        return True
    if any(w in h for w in ("shop", "verkaufsseite", "verkaufs website", "webshop")):
        return True
    if "verkauf" in h and any(w in h for w in ("seite", "website", "shop", "liste", "statistik", "klappt")):
        return True
    if any(w in h for w in ("was verkauft", "shop statistik", "verkaufs statistik")):
        return True
    if any(w in h for w in ("nichts bauen", "nicht mehr bauen", "nur verkauf")):
        return True
    return False


def _parse_price(text: str) -> float | None:
    for pat in (
        r"Jam-Preis:\s*\*?\*?\s*(\d+[.,]\d+)",
        r"(\d+[.,]\d+)\s*€",
        r"(\d+[.,]\d+)\s*EUR",
        r"festgelegt:\s*(\d+[.,]\d+)",
    ):
        m = re.search(pat, text, re.I)
        if m:
            return float(m.group(1).replace(",", "."))
    return None


def _parse_title(folder: Path, verka: str) -> str:
    m = re.search(r"#\s*Verkauf\s*[–-]\s*(.+)", verka, re.I)
    if m:
        return m.group(1).strip()[:80]
    slug = folder.name.split("-", 2)[-1] if "-" in folder.name else folder.name
    return slug.replace("-", " ").title()[:80]


def _product_id(folder_name: str) -> str:
    return re.sub(r"[^a-z0-9_-]", "", folder_name.lower())[:64] or "product"


def _sales_index(brain: JamBrain | None) -> dict[str, dict]:
    idx: dict[str, dict] = {}
    if not brain:
        return idx
    for e in getattr(brain.state, "jam_sales_log", []) or []:
        if isinstance(e, dict) and e.get("path"):
            idx[str(Path(e["path"]).resolve())] = e
    return idx


def collect_products(brain: JamBrain | None = None) -> list[dict]:
    idx = _sales_index(brain)
    products: list[dict] = []
    if not SALES_ROOT.is_dir():
        return products

    dirs = sorted(
        (p for p in SALES_ROOT.iterdir() if p.is_dir()),
        key=lambda p: p.stat().st_mtime,
        reverse=True,
    )[:MAX_PRODUCTS]

    from jam.active_sell import paypal_buy_url

    for folder in dirs:
        key = str(folder.resolve())
        meta = idx.get(key, {})
        title = str(meta.get("title", "")) or ""
        price = float(meta.get("price", 0) or 0)
        verka = ""
        if (folder / "VERKAUF.md").is_file():
            verka = (folder / "VERKAUF.md").read_text(encoding="utf-8", errors="ignore")
        if not title:
            title = _parse_title(folder, verka)
        if price <= 0 and (folder / "PREIS_JAM.txt").is_file():
            price = _parse_price((folder / "PREIS_JAM.txt").read_text(encoding="utf-8", errors="ignore")) or 0.0
        if price <= 0 and verka:
            price = _parse_price(verka) or 0.0
        if price <= 0:
            price = 2.99
        from jam.shop_product_desc import product_description
        from jam.shop_catalog import catalog_entry_for_product

        desc = product_description(folder, title=title, verka=verka, meta=meta)
        entry = catalog_entry_for_product(title, folder.name)
        detail_id = entry["id"] if entry else _product_id(folder.name)
        pid = _product_id(folder.name)
        products.append(
            {
                "id": pid,
                "title": title,
                "description": desc,
                "detail_id": detail_id,
                "detail_url": f"produkte.html#{detail_id}",
                "price": round(price, 2),
                "folder": folder.name,
                "path": key,
                "buy_url": paypal_buy_url(price, title),
            }
        )
    return products


def _legal_footer() -> str:
    try:
        from jam.shop_legal import legal_footer_extra

        return legal_footer_extra()
    except Exception:
        return ' · <a href="impressum.html">Impressum</a> · <a href="datenschutz.html">Datenschutz</a>'


def _jam_stats_js() -> str:
    return """
function jamTrack(type, productId, title) {
  fetch('/api/event', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({type: type, product_id: productId || '', title: title || ''})
  }).catch(function(){});
}
function jamBuyClick(el) {
  var id = el.getAttribute('data-id') || '';
  var title = el.getAttribute('data-title') || '';
  var url = el.getAttribute('data-buy') || el.href;
  jamTrack('buy_click', id, title);
  setTimeout(function(){ window.open(url, '_blank'); }, 120);
  return false;
}
document.addEventListener('DOMContentLoaded', function() {
  var p = location.pathname || '/';
  jamTrack('page_view', p, document.title || 'Jam-Seite');
});
"""


def _shared_css() -> str:
    return """
* { box-sizing: border-box; }
body {
  font-family: Segoe UI, system-ui, sans-serif;
  background: linear-gradient(165deg, #0c0a1f 0%, #1e1b4b 45%, #0f172a 100%);
  color: #e2e8f0; margin: 0; min-height: 100vh;
}
.jam-hero {
  text-align: center; padding: 2rem 1rem 1rem;
  border-bottom: 1px solid #334155;
}
.jam-logo { font-size: 3rem; line-height: 1; }
h1 { color: #c4b5fd; margin: .25rem 0; font-size: 1.85rem; }
.jam-badge {
  display: inline-block; background: #5b21b6; color: #ede9fe;
  padding: .35rem .9rem; border-radius: 999px; font-size: .8rem; font-weight: 700;
}
.sub { color: #94a3b8; max-width: 640px; margin: .75rem auto; font-size: .95rem; }
.nav {
  display: flex; flex-wrap: wrap; gap: .5rem; justify-content: center;
  padding: .75rem 1rem; background: #1e293b;
}
.nav a {
  color: #fde68a; text-decoration: none; padding: .4rem .85rem;
  border-radius: 8px; font-weight: 600; font-size: .88rem;
}
.nav a:hover { background: #334155; }
.grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 1rem; max-width: 1200px; margin: 0 auto; padding: 1.25rem 1.5rem 3rem;
}
.card {
  background: #1e293b; border-radius: 14px; padding: 1.25rem;
  border: 1px solid #334155; transition: transform .15s, border-color .15s;
}
.card:hover { transform: translateY(-2px); border-color: #7c3aed; }
.card h3 { margin: 0 0 .5rem; font-size: 1.05rem; color: #f8fafc; }
.card .desc {
  font-size: .82rem; color: #94a3b8; line-height: 1.45; margin: 0 0 .65rem;
}
.card .more {
  font-size: .78rem; margin: 0 0 .5rem;
}
.card .more a { color: #fde68a; font-weight: 600; }
.price { font-size: 1.5rem; color: #4ade80; font-weight: 700; }
.clicks { font-size: .75rem; color: #a78bfa; margin-top: .35rem; }
.meta { font-size: .72rem; color: #64748b; }
.btn {
  display: inline-block; margin-top: .75rem; padding: 12px 18px;
  border-radius: 10px; text-decoration: none; font-weight: 600; font-size: .9rem;
  border: none; cursor: pointer; width: 100%; text-align: center;
}
.btn.buy { background: #0070ba; color: #fff; }
.btn.buy:hover { background: #005ea6; }
footer { text-align: center; padding: 2rem; color: #64748b; font-size: .85rem; }
"""


def _master_html(products: list[dict], owner: str, stats: dict) -> str:
    prods_stats = stats.get("products", {})
    cards = []
    for p in products:
        st = prods_stats.get(p["id"], {})
        click_hint = ""
        if st.get("clicks") or st.get("views"):
            click_hint = (
                f'<p class="clicks">Jam sieht: {st.get("clicks", 0)} Kauf-Klicks · '
                f'{st.get("views", 0)} angesehen</p>'
            )
        desc = p.get("description") or ""
        desc_html = f'<p class="desc">{_esc(desc)}</p>' if desc else ""
        detail = p.get("detail_url", "produkte.html")
        more_html = f'<p class="more"><a href="{_esc(detail)}">Genau erklärt: Was es macht →</a></p>'
        cards.append(
            f"""<article class="card" data-product="{_esc(p['id'])}">
  <h3>{_esc(p['title'])}</h3>
  {desc_html}
  {more_html}
  <p class="price">{p['price']:.2f} €</p>
  {click_hint}
  <p class="meta">Jam-Produkt · {_esc(p['folder'])}</p>
  <a class="btn buy" href="#" data-id="{_esc(p['id'])}" data-title="{_esc(p['title'])}"
     data-buy="{_esc(p['buy_url'])}" onclick="return jamBuyClick(this)">Jetzt kaufen (PayPal)</a>
</article>"""
        )
    grid = "\n".join(cards) if cards else "<p class='sub'>Jam legt gleich Produkte nach …</p>"
    views = int(stats.get("page_views", 0))
    clicks = int(stats.get("buy_clicks", 0))
    return f"""<!DOCTYPE html>
<html lang="de"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="Offizielle Jam-Seite – digitale Produkte von Jam für {owner}">
<title>Jam-Seite – offizieller Shop</title>
<style>{_shared_css()}</style>
<script src="jam-stats.js"></script>
</head><body>
<header class="jam-hero">
  <div class="jam-logo">🤖</div>
  <span class="jam-badge">OFFIZIELLE JAM-SEITE</span>
  <h1>Jam verkauft hier</h1>
  <p class="sub">Das ist <strong>meine</strong> Verkaufsseite – mit <a href="produkte.html" style="color:#fde68a">genauen Produktbeschreibungen</a>
  (was jedes Paket macht). Für <strong>{_esc(owner)}</strong> · PayPal <strong>{_esc(PAYPAL_EMAIL)}</strong></p>
  <p class="sub">Live: <strong>{views}</strong> Aufrufe · <strong>{clicks}</strong> Kauf-Klicks auf dieser Seite</p>
</header>
<nav class="nav">
  <a href="index.html">🛒 Shop</a>
  <a href="produkte.html">📋 Was wir anbieten</a>
  <a href="webseiten.html">🌐 Webseiten</a>
  <a href="dienstleistungen.html">🛠️ Leistungen</a>
  <a href="dashboard.html">📊 Jam-Dashboard</a>
  <a href="werbung.html">📣 Werbung</a>
  <a href="impressum.html">Impressum</a>
  <a href="datenschutz.html">Datenschutz</a>
</nav>
<p class="sub" style="text-align:center;padding:0 1rem 1rem">
  <a href="webseiten.html" style="color:#fde68a">Webseiten modern erstellen lassen</a> ·
  <a href="dienstleistungen.html" style="color:#a78bfa">Minecraft · Discord · Code</a>
</p>
<div class="grid">{grid}</div>
<footer>{len(products)} Produkte · Jam-Shop · {SHOP_URL}{_legal_footer()}</footer>
</body></html>"""


def _dashboard_html(products: list[dict], owner: str, stats: dict) -> str:
    rows = []
    merged = {p["id"]: p for p in products}
    for pid, st in sorted(
        stats.get("products", {}).items(),
        key=lambda x: int(x[1].get("clicks", 0)),
        reverse=True,
    ):
        p = merged.get(pid, {})
        rows.append(
            f"<tr><td>{_esc(st.get('title', pid))}</td>"
            f"<td>{st.get('clicks', 0)}</td><td>{st.get('views', 0)}</td>"
            f"<td>{p.get('price', '–')} €</td></tr>"
        )
    if not rows:
        rows.append("<tr><td colspan='4'>Noch keine Klicks – Shop-Link teilen!</td></tr>")
    return f"""<!DOCTYPE html>
<html lang="de"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Jam-Dashboard – was verkauft sich?</title>
<style>
{_shared_css()}
table {{ width: 100%; max-width: 900px; margin: 1rem auto; border-collapse: collapse; }}
th, td {{ padding: .65rem; text-align: left; border-bottom: 1px solid #334155; }}
th {{ color: #a78bfa; }}
.stat-box {{
  display: inline-block; background: #1e293b; padding: 1rem 1.5rem; margin: .5rem;
  border-radius: 12px; min-width: 140px;
}}
.stat-box strong {{ font-size: 1.8rem; color: #4ade80; display: block; }}
.refresh {{ color: #94a3b8; font-size: .85rem; text-align: center; }}
</style>
<script src="jam-stats.js"></script>
</head><body>
<header class="jam-hero">
  <div class="jam-logo">📊</div>
  <span class="jam-badge">JAM-DASHBOARD</span>
  <h1>Was verkauft meine Seite?</h1>
  <p class="sub">Jam sieht live jeden Kauf-Klick und jeden Aufruf – für {_esc(owner)}</p>
</header>
<nav class="nav">
  <a href="index.html">🛒 Shop</a>
  <a href="produkte.html">📋 Produkte</a>
  <a href="dienstleistungen.html">⛏️ Leistungen</a>
  <a href="dashboard.html">📊 Dashboard</a>
  <a href="impressum.html">Impressum</a>
  <a href="datenschutz.html">Datenschutz</a>
</nav>
<div style="text-align:center;padding:1rem">
  <div class="stat-box">Aufrufe<strong id="sv">{int(stats.get('page_views', 0))}</strong></div>
  <div class="stat-box">Kauf-Klicks<strong id="sc">{int(stats.get('buy_clicks', 0))}</strong></div>
  <div class="stat-box">Produkte<strong>{len(products)}</strong></div>
</div>
<table>
  <thead><tr><th>Produkt</th><th>Kauf-Klicks</th><th>Angesehen</th><th>Preis</th></tr></thead>
  <tbody id="top-body">{''.join(rows)}</tbody>
</table>
<p class="refresh">Aktualisiert alle 8 Sekunden · Jam liest dieselben Daten</p>
<script>
function refreshStats() {{
  fetch('/api/stats').then(r => r.json()).then(d => {{
    document.getElementById('sv').textContent = d.page_views || 0;
    document.getElementById('sc').textContent = d.buy_clicks || 0;
    var body = document.getElementById('top-body');
    var prods = d.products || {{}};
    var keys = Object.keys(prods).sort(function(a,b) {{
      return (prods[b].clicks||0) - (prods[a].clicks||0);
    }});
    if (!keys.length) return;
    body.innerHTML = keys.slice(0,25).map(function(k) {{
      var p = prods[k];
      return '<tr><td>'+ (p.title||k) +'</td><td>'+ (p.clicks||0) +
        '</td><td>'+ (p.views||0) +'</td><td>–</td></tr>';
    }}).join('');
  }}).catch(function(){{}});
}}
setInterval(refreshStats, 8000);
refreshStats();
</script>
<footer>Jam-Shop · {SHOP_URL}{_legal_footer()}</footer>
</body></html>"""


def _werbung_html(products: list[dict], owner: str, shop: str) -> str:
    from jam.shop_heavy_promo import build_werbung_html

    return build_werbung_html(products, owner, shop, esc=_esc)


def _esc(s: str) -> str:
    return (
        str(s)
        .replace("&", "&amp;")
        .replace("<", "&lt;")
        .replace(">", "&gt;")
        .replace('"', "&quot;")
    )


class JamShopHandler(SimpleHTTPRequestHandler):
    def __init__(self, *args, directory: str | None = None, **kwargs):
        self.directory = directory or str(SHOP_ROOT)
        super().__init__(*args, directory=self.directory, **kwargs)

    def log_message(self, format: str, *args) -> None:
        pass

    def _send_json(self, data: dict, code: int = 200) -> None:
        body = json.dumps(data, ensure_ascii=False).encode("utf-8")
        self.send_response(code)
        self.send_header("Content-Type", "application/json; charset=utf-8")
        self.send_header("Access-Control-Allow-Origin", "*")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def do_OPTIONS(self) -> None:
        self.send_response(204)
        self.send_header("Access-Control-Allow-Origin", "*")
        self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
        self.send_header("Access-Control-Allow-Headers", "Content-Type")
        self.end_headers()

    def do_GET(self) -> None:
        path = urlparse(self.path).path
        if path == "/api/stats":
            self._send_json(load_stats())
            return
        return super().do_GET()

    def do_POST(self) -> None:
        path = urlparse(self.path).path
        if path != "/api/event":
            self.send_error(404)
            return
        try:
            length = int(self.headers.get("Content-Length", 0))
            raw = self.rfile.read(length) if length else b"{}"
            payload = json.loads(raw.decode("utf-8"))
        except (json.JSONDecodeError, ValueError):
            self.send_error(400)
            return
        record_event(
            str(payload.get("type", "event")),
            product_id=str(payload.get("product_id", "")),
            title=str(payload.get("title", "")),
            path=path,
        )
        self._send_json({"ok": True})


def build_master_shop(brain: JamBrain | None = None) -> tuple[Path, int, str]:
    owner = owner_name(brain.state) if brain else "David"
    products = collect_products(brain)
    stats = load_stats()
    if brain:
        sync_stats_to_brain(brain)
    SHOP_ROOT.mkdir(parents=True, exist_ok=True)
    JAM_SEITE_COPY.mkdir(parents=True, exist_ok=True)

    (SHOP_ROOT / "jam-stats.js").write_text(_jam_stats_js(), encoding="utf-8")
    from jam.shop_legal import IMPRESSUM_BODY, DATENSCHUTZ_BODY, legal_page_html

    css = _shared_css()
    (SHOP_ROOT / "impressum.html").write_text(
        legal_page_html("Impressum", IMPRESSUM_BODY, css), encoding="utf-8"
    )
    (SHOP_ROOT / "datenschutz.html").write_text(
        legal_page_html("Datenschutzerklärung", DATENSCHUTZ_BODY, css), encoding="utf-8"
    )
    from jam.service_catalog import services_page_html
    from jam.modern_website import build_modern_onepage, websites_offer_page_html

    (SHOP_ROOT / "dienstleistungen.html").write_text(
        services_page_html(owner=owner, shared_css=css, esc=_esc), encoding="utf-8"
    )
    (SHOP_ROOT / "webseiten.html").write_text(
        websites_offer_page_html(owner=owner, shared_css=css, esc=_esc),
        encoding="utf-8",
    )
    from jam.shop_catalog import products_page_html

    (SHOP_ROOT / "produkte.html").write_text(
        products_page_html(owner=owner, shared_css=css, esc=_esc, products=products),
        encoding="utf-8",
    )
    demo_dir = SHOP_ROOT / "site"
    demo_dir.mkdir(parents=True, exist_ok=True)
    (demo_dir / "demo-index.html").write_text(
        build_modern_onepage(
            owner=owner,
            title=f"{owner} – Demo Webseite",
            tagline="So modern kann deine Webseite aussehen – von Jam gebaut.",
        ),
        encoding="utf-8",
    )
    (SHOP_ROOT / "index.html").write_text(_master_html(products, owner, stats), encoding="utf-8")
    (SHOP_ROOT / "dashboard.html").write_text(_dashboard_html(products, owner, stats), encoding="utf-8")
    (SHOP_ROOT / "werbung.html").write_text(
        _werbung_html(products, owner, shop_url(brain.state if brain else None)),
        encoding="utf-8",
    )
    (SHOP_ROOT / "produkte.json").write_text(
        json.dumps(products, ensure_ascii=False, indent=2),
        encoding="utf-8",
    )
    (SHOP_ROOT / "JAM_SEITE_INFO.txt").write_text(
        f"Offizielle Jam-Seite\nShop: {SHOP_URL}\nDashboard: {DASHBOARD_URL}\n"
        f"Statistik-Datei: {STATS_FILE}\n",
        encoding="utf-8",
    )
    for name in (
        "index.html",
        "dashboard.html",
        "dienstleistungen.html",
        "webseiten.html",
        "produkte.html",
        "site/demo-index.html",
        "impressum.html",
        "datenschutz.html",
        "jam-stats.js",
        "JAM_SEITE_INFO.txt",
    ):
        src = SHOP_ROOT / name
        if src.is_file():
            dst = JAM_SEITE_COPY / name
            dst.parent.mkdir(parents=True, exist_ok=True)
            dst.write_text(src.read_text(encoding="utf-8"), encoding="utf-8")

    return SHOP_ROOT, len(products), shop_url(brain.state if brain else None)


def is_shop_server_running() -> bool:
    if _server is not None:
        return True
    if not PID_FILE.is_file():
        return False
    try:
        import os

        pid = int(PID_FILE.read_text(encoding="utf-8").strip())
        os.kill(pid, 0)
        return True
    except (OSError, ValueError):
        return False


def start_shop_server(*, force: bool = False) -> str:
    global _server, _server_thread
    if is_shop_server_running() and not force:
        return SHOP_URL

    if _server:
        try:
            _server.shutdown()
        except Exception:
            pass
        _server = None

    SHOP_ROOT.mkdir(parents=True, exist_ok=True)
    handler = lambda *a, **k: JamShopHandler(*a, directory=str(SHOP_ROOT), **k)
    try:
        _server = ThreadingHTTPServer(("127.0.0.1", SHOP_PORT), handler)
    except OSError:
        return f"Port {SHOP_PORT} belegt – schließe anderen Shop-Server."

    def run():
        if _server:
            _server.serve_forever()

    _server_thread = threading.Thread(target=run, daemon=True)
    _server_thread.start()
    PID_FILE.parent.mkdir(parents=True, exist_ok=True)
    PID_FILE.write_text(str(__import__("os").getpid()), encoding="utf-8")
    return SHOP_URL


def run_shop_server_blocking() -> None:
    """Shop-Server im Hauptthread – Prozess läuft dauerhaft."""
    import os

    SHOP_ROOT.mkdir(parents=True, exist_ok=True)
    handler = lambda *a, **k: JamShopHandler(*a, directory=str(SHOP_ROOT), **k)
    server = ThreadingHTTPServer(("127.0.0.1", SHOP_PORT), handler)
    PID_FILE.parent.mkdir(parents=True, exist_ok=True)
    PID_FILE.write_text(str(os.getpid()), encoding="utf-8")
    print(f"Jam-Shop: {SHOP_URL} (Strg+C = Stopp)", flush=True)
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        pass
    finally:
        try:
            server.shutdown()
        except Exception:
            pass
        PID_FILE.unlink(missing_ok=True)


def promote_master_shop(brain: JamBrain, url: str, n: int) -> list[str]:
    if getattr(brain.state, "jam_shop_heavy_promo", False):
        from jam.shop_heavy_promo import run_heavy_promo_round

        steps, _ = run_heavy_promo_round(brain, url, force=True)
        return steps
    steps: list[str] = []
    share = f"🤖 Jam-Seite – {n} Produkte, PayPal kaufen: {url}"
    for label, link in (
        ("Jam-Seite", url),
        ("Dashboard", url + "dashboard.html"),
        ("Werbung", url + "werbung.html"),
    ):
        try:
            webbrowser.open(link, new=2)
            steps.append(label)
        except OSError:
            pass
    try:
        webbrowser.open(
            f"https://twitter.com/intent/tweet?text={quote_plus(share[:240])}",
            new=2,
        )
        steps.append("X")
    except OSError:
        pass
    brain.state.remember(f"Jam-Seite Werbung: {n} Produkte")
    return steps


def run_master_shop_mission(brain: JamBrain, *, user_initiated: bool = False) -> str:
    owner = owner_name(brain.state)
    _root, n, url = build_master_shop(brain)
    url = start_shop_server(force=False)
    brain.state.jam_master_shop_url = url
    brain.state.jam_master_shop_count = n
    brain.state.jam_master_shop_built_at = time.time()
    sync_stats_to_brain(brain)

    steps = promote_master_shop(brain, url, n) if user_initiated else []
    if user_initiated or time.time() - getattr(brain.state, "last_master_shop_promo_at", 0.0) > PROMO_GAP_SEC:
        if not steps:
            steps = promote_master_shop(brain, url, n)
        brain.state.last_master_shop_promo_at = time.time()
        brain.save()

    data = load_stats()
    intro = random.choice(LINES).format(
        owner=owner,
        url=url,
        n=n,
        views=int(data.get("page_views", 0)),
        clicks=int(data.get("buy_clicks", 0)),
    )
    return (
        f"{intro} **Dashboard:** {url}dashboard.html – Jam sieht was verkauft. "
        f"Schritte: {', '.join(steps) or 'Server läuft'}."
    )[:680]


def sell_only_cycle(brain: JamBrain) -> tuple[str, str]:
    from jam.active_sell import maybe_active_sell_push
    from jam.shop_heavy_promo import is_heavy_promo, maybe_auto_heavy_promo

    sync_stats_to_brain(brain)
    if is_heavy_promo(brain.state):
        msg = maybe_auto_heavy_promo(brain)
        if msg:
            return msg[:220], "shop_heavy_werbung"
    if random.random() < 0.35:
        try:
            msg = maybe_active_sell_push(brain)
            if msg:
                return msg[:220], "verkauf_push"
        except Exception:
            pass
    if random.random() < 0.45:
        return shop_sales_report(brain)[:220], "jam_seite_stats"
    if random.random() < 0.5:
        return run_master_shop_mission(brain, user_initiated=False)[:220], "master_shop"
    try:
        from jam.ad_earn import run_ad_for_shop

        msg = run_ad_for_shop(brain)
        if msg:
            return msg[:220], "shop_werbung"
    except Exception:
        pass
    return run_master_shop_mission(brain, user_initiated=False)[:220], "master_shop"


def maybe_auto_master_shop(brain: JamBrain) -> str | None:
    from jam.shop_heavy_promo import is_heavy_promo, maybe_auto_heavy_promo

    if is_heavy_promo(brain.state):
        return maybe_auto_heavy_promo(brain)
    if not is_sell_only_mode(brain.state) and not getattr(brain.state, "jam_master_shop_url", ""):
        return None
    if time.time() - getattr(brain.state, "last_master_shop_promo_at", 0.0) < PROMO_GAP_SEC:
        return None
    if random.random() > 0.4:
        return None
    return run_master_shop_mission(brain, user_initiated=False)


def handle_speech(brain: JamBrain, heard: str) -> str | None:
    try:
        from jam.shop_heavy_promo import handle_speech as promo_speech

        r = promo_speech(brain, heard)
        if r:
            return r
    except Exception:
        pass
    if not hears_master_shop(heard):
        return None
    h = heard.lower()
    if any(w in h for w in ("statistik", "dashboard", "was verkauft", "was klappt", "klappt")):
        build_master_shop(brain)
        start_shop_server()
        return shop_sales_report(brain)
    if any(w in h for w in ("nichts bauen", "nicht mehr bauen", "nur verkauf", "nur verkaufen")):
        return enable_sell_only(brain)
    return run_master_shop_mission(brain, user_initiated=True)
