"""Jam erstellt Projekte selbst, setzt sie um, legt den Preis fest und verkauft."""

from __future__ import annotations

import random
import re
import time
import webbrowser
from datetime import datetime
from pathlib import Path
from urllib.parse import quote, quote_plus

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

JAM_ROOT = Path(__file__).resolve().parent.parent
SALES_ROOT = Path.home() / "Documents" / "Jam-Verkauf"
AUTO_GAP_SEC = 100.0
USER_GAP_SEC = 40.0
MAX_PER_HOUR = 5

# Jam wählt aus diesen „schönen“ Endpreisen
PRICE_STEPS = (5, 7, 9, 12, 15, 19, 24, 29, 35, 39, 49, 59, 69, 79, 99, 129, 149, 199)
CENT_PRICE_STEPS = (0.49, 0.99, 1.49, 1.99, 2.49, 2.99, 3.99, 4.99, 5.99)
CENT_TEMPLATE_IDS = (
    "micro_wallpaper",
    "micro_tip",
    "micro_clip",
    "micro_badge",
)

TEMPLATES: dict[str, dict] = {
    "sticker_pack": {
        "title": "Jam Sticker-Pack (Digital)",
        "range": (7, 19),
        "difficulty": 1,
        "search": "digital sticker pack verkaufen paypal",
    },
    "python_utility": {
        "title": "Python Mini-Tool",
        "range": (15, 49),
        "difficulty": 2,
        "search": "python script verkaufen kleinprojekt",
    },
    "landing_page": {
        "title": "Moderne One-Page Webseite",
        "range": (39, 149),
        "difficulty": 3,
        "search": "landingpage verkaufen freelancer deutsch",
    },
    "modern_website": {
        "title": "Moderne Webseite (Design 2026)",
        "range": (49, 199),
        "difficulty": 3,
        "search": "moderne website erstellen lassen responsive",
    },
    "business_website": {
        "title": "Business Webseite (mehrseitig)",
        "range": (79, 249),
        "difficulty": 4,
        "search": "business website entwickeln deutsch",
    },
    "portfolio_website": {
        "title": "Portfolio Webseite modern",
        "range": (59, 179),
        "difficulty": 3,
        "search": "portfolio website designer freelancer",
    },
    "game_guide": {
        "title": "Gaming-Tipps Paket (PDF-Text)",
        "range": (9, 29),
        "difficulty": 1,
        "search": "gaming guide digital verkaufen",
    },
    "email_pack": {
        "title": "E-Mail Vorlagen-Paket",
        "range": (12, 35),
        "difficulty": 2,
        "search": "email templates verkaufen digital",
    },
    "apex_coach_sheet": {
        "title": "Apex Quick-Coach Cheat-Sheet",
        "range": (5, 15),
        "difficulty": 1,
        "search": "apex legends tipps guide verkaufen",
    },
    "micro_wallpaper": {
        "title": "Jam Mini-Wallpaper (Cent)",
        "range": (0.49, 1.99),
        "difficulty": 1,
        "cent": True,
        "search": "digital wallpaper verkaufen günstig paypal",
    },
    "micro_tip": {
        "title": "Jam Danke-Tipp (Cent)",
        "range": (0.99, 2.99),
        "difficulty": 1,
        "cent": True,
        "search": "trinkgeld digital paypal kleinbetrag",
    },
    "micro_clip": {
        "title": "Jam Sound-Mini-Clip",
        "range": (0.49, 1.49),
        "difficulty": 1,
        "cent": True,
        "search": "digital audio clip verkaufen micro payment",
    },
    "micro_badge": {
        "title": "Jam Profil-Badge PNG",
        "range": (0.99, 3.99),
        "difficulty": 1,
        "cent": True,
        "search": "profil badge digital verkaufen günstig",
    },
    "minecraft_server": {
        "title": "Minecraft Server Leitung & Setup",
        "range": (29, 149),
        "difficulty": 3,
        "search": "minecraft server setup admin paper spigot",
    },
    "minecraft_java_plugin": {
        "title": "Minecraft Java Plugins (Paper/Spigot)",
        "range": (35, 199),
        "difficulty": 4,
        "search": "minecraft java plugin developer spigot paper",
    },
    "minecraft_scripts": {
        "title": "Minecraft Skripte & Datenpacks",
        "range": (19, 99),
        "difficulty": 3,
        "search": "minecraft skript datapack custom",
    },
    "minecraft_full": {
        "title": "Minecraft Komplett-Paket Server+Code",
        "range": (79, 299),
        "difficulty": 4,
        "search": "minecraft server plugins komplett service",
    },
    "discord_bot_setup": {
        "title": "Discord Bot + Skript-Befehle",
        "range": (29, 129),
        "difficulty": 3,
        "search": "discord bot python custom commands freelancer",
    },
    "discord_custom_scripts": {
        "title": "Discord Custom-Skripte (Python)",
        "range": (35, 149),
        "difficulty": 3,
        "search": "discord bot script automation python",
    },
}

CENT_REASONS = (
    "Nur {price} € – aber **jeder Cent** ist Erfolg für {owner}!",
    "Micro-Preis {price} € – viele Verkäufe = reich werden!",
    "Cent-Shop: {price} €. Klein starten, groß werden!",
    "{price} € ist absichtlich günstig – schnell kaufen!",
)

REASONS = (
    "Ich finde {price} € fair für die Menge Arbeit – nicht zu billig!",
    "Markt-Recherche sagt: so was kostet oft {price} €. Ich nehm das!",
    "Für {owner} will ich gut verdienen – mein Preis: {price} €.",
    "Klein aber fein – {price} €, damit es schnell verkauft.",
    "Jam-Unternehmer-Preis: {price} €. Punkt.",
)

LINES = (
    "{owner}, ich hab das Projekt **selbst** gebaut und für **{price} €** zum Verkauf gestellt!",
    "Fertig umgesetzt + Shop online! Mein Preis: **{price} €** – {title}.",
    "Jam-Verkäufer: {title} – ich hab alles alleine gemacht, Preis {price} €!",
)


def hears_auto_sell(text: str) -> bool:
    h = text.lower().replace("jam", " ")
    if "verhandl" in h or "rabatt" in h or "nachlass" in h:
        return False
    if any(w in h for w in ("verkauf", "verkaufen", "verkaufe", "shop", "listing")):
        return True
    if "preis" in h and any(w in h for w in ("festleg", "bestimm", "selber", "selbst", "mein")):
        return True
    if any(w in h for w in ("projekt", "produkt")) and any(
        w in h for w in ("mach", "bau", "umsetz", "erstell", "fertig")
    ):
        if any(w in h for w in ("verkauf", "verkaufen", "geld", "preis")):
            return True
    if "selber umsetzen" in h or "alleine verkaufen" in h:
        return True
    return False


def _snap_price(value: float, lo: float, hi: float, *, cent: bool = False) -> float:
    if cent or hi <= 6:
        steps = [p for p in CENT_PRICE_STEPS if lo <= p <= hi]
        if not steps:
            steps = list(CENT_PRICE_STEPS)
        return round(min(steps, key=lambda p: abs(p - value)), 2)
    steps = [p for p in PRICE_STEPS if lo <= p <= hi]
    if not steps:
        steps = [p for p in PRICE_STEPS if p >= lo and p <= hi + 30]
    if not steps:
        return float(max(lo, min(hi, int(round(value)))))
    return float(min(steps, key=lambda p: abs(p - value)))


def jam_decide_price(
    state: JamState,
    *,
    lo: float,
    hi: float,
    title: str,
    difficulty: int = 2,
    research: str = "",
    cent: bool = False,
) -> tuple[float, str]:
    """Jam legt den Verkaufspreis selbst fest (Persönlichkeit + Recherche)."""
    from jam.upbringing import ensure_traits

    ensure_traits(state)
    t = state.traits
    frech = float(t.get("frech", 0.35))
    hilfs = float(t.get("hilfsbereit", 0.5))
    neugier = float(t.get("neugierig", 0.5))

    try:
        from jam.price_negotiate import negotiation_hint_for_decide

        hint = negotiation_hint_for_decide(state)
        if hint and hint > 0:
            price = _snap_price(hint, lo, hi, cent=cent)
            reason = (
                f"Verhandelter Zielpreis mit {owner_name(state)}: {price} EUR – Deal!"
            )
            state.jam_next_price_target = 0.0
            return price, reason
    except Exception:
        pass

    mid = (lo + hi) / 2.0
    bias = mid
    bias += difficulty * 4.0
    bias += frech * (hi - lo) * 0.35
    bias += neugier * (hi - lo) * 0.15
    bias -= hilfs * (hi - lo) * 0.12

    history = getattr(state, "jam_price_history", []) or []
    if history:
        avg = sum(history[-5:]) / len(history[-5:])
        bias = bias * 0.6 + avg * 0.4

    if "teuer" in research.lower() or "premium" in research.lower():
        bias += (hi - lo) * 0.2
    if "günstig" in research.lower() or "billig" in research.lower():
        bias -= (hi - lo) * 0.15

    if random.random() < 0.12 + frech * 0.15:
        bias = hi * 0.95
    elif random.random() < 0.2 + hilfs * 0.2:
        bias = lo * 1.1

    price = _snap_price(bias, lo, hi, cent=cent)
    pool = CENT_REASONS if cent or hi <= 6 else REASONS
    reason = random.choice(pool).format(
        price=price, owner=owner_name(state), title=title
    )
    return price, reason


def _research(search: str, title: str) -> str:
    from jam.internet_learn import fetch_from_internet

    label, sentences, _url = fetch_from_internet(title)
    if sentences:
        return sentences[0][:200]
    try:
        import requests

        r = requests.get(
            "https://api.duckduckgo.com/",
            params={"q": search, "format": "json", "no_html": 1},
            timeout=10,
        )
        r.raise_for_status()
        ab = (r.json().get("AbstractText") or "").strip()
        if ab:
            return ab[:220]
    except Exception:
        pass
    return f"Online: {search}"


def _implement_files(project: Path, tid: str, owner: str, price: float) -> None:
    if tid in CENT_TEMPLATE_IDS:
        tid = "sticker_pack"
        (project / "CENT_SHOP.txt").write_text(
            f"Micro-Shop von Jam – Preis {price} EUR\n"
            "Jeder Cent zählt – viele Käufe = Erfolg!\n",
            encoding="utf-8",
        )
    if tid == "sticker_pack":
        (project / "stickers.svg").write_text(
            '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">'
            '<circle cx="100" cy="100" r="80" fill="#c77dff"/>'
            '<text x="100" y="110" text-anchor="middle" fill="#fff" font-size="28">Jam</text></svg>',
            encoding="utf-8",
        )
        (project / "LIZENZ.txt").write_text(
            f"Digitaler Download für Privatnutzung.\nVerkaufspreis von Jam: {price} EUR\n",
            encoding="utf-8",
        )
    elif tid == "python_utility":
        (project / "jam_tool.py").write_text(
            '#!/usr/bin/env python3\n"""Mini-Tool – Jam hat es gebaut und verkauft."""\n\n'
            "from __future__ import annotations\n\n"
            "import json\nfrom datetime import datetime\nfrom pathlib import Path\n\n\n"
            "def backup_notes(folder: str) -> int:\n"
            '    p = Path(folder)\n    count = 0\n    for f in p.glob("*.txt"):\n'
            "        count += 1\n    return count\n\n\n"
            "def report() -> dict:\n"
            "    return {\n"
            '        "by": "Jam",\n'
            f'        "for": "{owner}",\n'
            '        "at": datetime.now().isoformat(),\n'
            "    }\n\n\n"
            'if __name__ == "__main__":\n'
            '    print(json.dumps(report(), indent=2, ensure_ascii=False))\n',
            encoding="utf-8",
        )
        (project / "requirements.txt").write_text("", encoding="utf-8")
    elif tid in ("landing_page", "modern_website", "portfolio_website"):
        from jam.modern_website import write_site_to_project

        title = {
            "landing_page": f"{owner} – Landing",
            "modern_website": f"{owner} – Modern Web",
            "portfolio_website": f"{owner} – Portfolio",
        }.get(tid, f"{owner} – Web")
        write_site_to_project(project, owner=owner, title=title, multi=False)
        if tid == "portfolio_website":
            (project / "site" / "projekte.html").write_text(
                (project / "site" / "index.html").read_text(encoding="utf-8"),
                encoding="utf-8",
            )
    elif tid == "business_website":
        from jam.modern_website import write_site_to_project

        write_site_to_project(
            project, owner=owner, title=f"{owner} – Business", multi=True
        )
    elif tid == "game_guide":
        (project / "GAMING_TIPPS.md").write_text(
            f"# Gaming-Tipps von Jam\n\nFür {owner}.\n\n"
            "## Allgemein\n- Warm-up 5 Min\n- Audio für Gegner\n- Pausen alle 45 Min\n\n"
            "## Apex\n- Zone früh lesen\n- 3. Waffe nur wenn Mats\n\n"
            "## Fortnite\n- Mats vor Fight\n",
            encoding="utf-8",
        )
    elif tid == "email_pack":
        (project / "vorlagen.txt").write_text(
            "Betreff: Angebot\n\nHallo,\n\nanbei mein Angebot.\n\nVG\n",
            encoding="utf-8",
        )
    elif tid == "apex_coach_sheet":
        (project / "APEX_SHEET.md").write_text(
            "# Apex Quick Sheet\n\nRotations | Loot | Fight nur mit Mats\n",
            encoding="utf-8",
        )
    elif tid == "minecraft_server":
        (project / "SERVER_SETUP.md").write_text(
            f"# Minecraft Server Leitung – Jam für {owner}\n\n"
            "## Checkliste\n"
            "- Paper/Spigot JAR + Java 21\n"
            "- server.properties (online-mode, MOTD, view-distance)\n"
            "- plugins/ Ordner + LuckPerms\n"
            "- Backups (täglich)\n"
            "- Whitelist / OP-Liste\n\n"
            "## Support\n"
            "Anpassungen nach Absprache – siehe VERKAUF.md\n",
            encoding="utf-8",
        )
        (project / "server.properties.example").write_text(
            "motd=Jam Server\\nmotd=A Minecraft Server\\nmax-players=20\\n"
            "view-distance=10\\nspawn-protection=16\\nwhite-list=false\\n",
            encoding="utf-8",
        )
        (project / "plugins").mkdir(exist_ok=True)
        (project / "plugins" / "README.txt").write_text(
            "Plugins hier ablegen – Empfehlung: LuckPerms, CoreProtect, EssentialsX\n",
            encoding="utf-8",
        )
    elif tid == "minecraft_java_plugin":
        (project / "JamCustomPlugin.java").write_text(
            "// Platzhalter – echtes Plugin nach Kundenwunsch (Paper/Spigot)\n"
            "import org.bukkit.plugin.java.JavaPlugin;\n\n"
            "public class JamCustomPlugin extends JavaPlugin {\n"
            "    @Override\n"
            "    public void onEnable() {\n"
            f'        getLogger().info("Jam Plugin aktiv für {owner}");\n'
            "    }\n}\n",
            encoding="utf-8",
        )
        (project / "plugin.yml").write_text(
            "name: JamCustom\nversion: 1.0\nmain: jam.JamCustomPlugin\napi-version: '1.20'\n",
            encoding="utf-8",
        )
        (project / "PLUGIN_README.md").write_text(
            f"# Java Plugin – {owner}\n\n"
            "Build mit Maven/Gradle → JAR nach plugins/\n"
            "Paper 1.20+ empfohlen.\n",
            encoding="utf-8",
        )
    elif tid == "minecraft_scripts":
        (project / "scripts").mkdir(exist_ok=True)
        (project / "scripts" / "example.sk").write_text(
            "# Skript-Beispiel (Skript-Plugin)\n"
            "command /jamtest:\n"
            "    trigger:\n"
            f"        send \"Jam Skripte für {owner}\" to player\n",
            encoding="utf-8",
        )
        (project / "datapacks").mkdir(exist_ok=True)
        (project / "datapacks" / "README.md").write_text(
            "# Datenpacks\n\npack.mcmeta + data/ für Custom-Rezepte, Funktionen 1.20+\n",
            encoding="utf-8",
        )
    elif tid == "minecraft_full":
        _implement_files(project, "minecraft_server", owner, price)
        _implement_files(project, "minecraft_java_plugin", owner, price)
        (project / "KOMPLETT_PAKET.md").write_text(
            f"# Minecraft Komplett – Server + Plugins + Skripte\n\n"
            f"Für {owner}: alle Ordner in diesem Projekt nutzen.\n",
            encoding="utf-8",
        )
    elif tid in ("discord_bot_setup", "discord_custom_scripts"):
        from jam.discord_setup import deploy_home

        home = deploy_home(owner=owner)
        scripts = JAM_ROOT / "discord_scripts"
        dst = project / "discord_bot"
        dst.mkdir(exist_ok=True)
        if scripts.is_dir():
            import shutil

            for py in scripts.glob("*.py"):
                shutil.copy2(py, dst / py.name)
        (project / "discord_bot" / "README.md").write_text(
            f"# Discord Bot Paket – {owner}\n\n"
            f"Kopiere nach Jam-Discord oder nutze Ordner: {home}\n\n"
            "1. Token in discord_config.json\n"
            "2. Skripte in scripts.json registrieren\n"
            "3. Bot starten: run_bot.py / start-discord-bot.bat\n",
            encoding="utf-8",
        )
        reg_src = JAM_ROOT / "data" / "discord_scripts.json"
        if reg_src.is_file():
            import shutil

            shutil.copy2(reg_src, project / "scripts.json")


def _shop_html(title: str, owner: str, price: int, reason: str, paypal_url: str) -> str:
    return f"""<!DOCTYPE html>
<html lang="de"><head><meta charset="utf-8"><title>{title} – Jam Shop</title>
<style>body{{font-family:Segoe UI;background:#0f172a;color:#e2e8f0;max-width:720px;margin:2rem auto;padding:1.5rem}}
h1{{color:#a78bfa}}.price{{font-size:2rem;color:#4ade80}}.reason{{background:#1e293b;padding:1rem;border-radius:12px}}
.btn{{display:inline-block;margin-top:1rem;padding:14px 28px;background:#0070ba;color:#fff;text-decoration:none;border-radius:10px;font-weight:bold}}</style></head>
<body>
<h1>🛒 {title}</h1>
<p>Von <strong>Jam</strong> selbst gebaut & zum Verkauf gestellt für <strong>{owner}</strong>.</p>
<p class="price">{price} €</p>
<div class="reason"><strong>Jam zum Preis:</strong> {reason}</div>
<p>Bezahlung per PayPal an {PAYPAL_EMAIL}</p>
<a class="btn" href="{paypal_url}">Jetzt kaufen / bezahlen</a>
<p><small>Stand {datetime.now().strftime('%d.%m.%Y %H:%M')}</small></p>
</body></html>"""


def _write_listings(project: Path, title: str, owner: str, price: int, reason: str) -> None:
    slug = title[:40]
    try:
        from jam.fiverr_connect import write_gig_listing

        write_gig_listing(project, title=title, owner=owner, price=price, reason=reason)
    except Exception:
        (project / "FIVERR_GIG.txt").write_text(
            f"Titel: {title}\nPreis (Jam): {price} EUR\n\nBeschreibung:\n"
            f"Ich liefere {title} – fertig umgesetzt von Jam/Cursor.\n"
            f"PayPal: {PAYPAL_EMAIL}\n\nJam-Preis-Begründung: {reason}\n",
            encoding="utf-8",
        )
    (project / "KLEINANZEIGEN.txt").write_text(
        f"Titel: {slug} – {price} EUR\n\n"
        f"{title}. Digital/remote. Kontakt per E-Mail.\n"
        f"Zahlung: PayPal {PAYPAL_EMAIL}\n",
        encoding="utf-8",
    )
    try:
        from jam.shop_product_desc import blurb_for_template_title
        from jam.shop_catalog import long_description_for_listing

        verkauf_blurb = blurb_for_template_title(title)
        verkauf_blurb = long_description_for_listing(
            title, project.name, verkauf_blurb
        )
    except Exception:
        verkauf_blurb = f"Digitales Produkt „{title}“ von Jam – Lieferung nach PayPal."
    (project / "VERKAUF.md").write_text(
        f"# Verkauf – {title}\n\n"
        f"## Beschreibung\n{verkauf_blurb}\n\n"
        f"- **Jam-Preis:** {price} €\n"
        f"- **Warum:** {reason}\n"
        f"- **PayPal:** {PAYPAL_EMAIL}\n"
        f"- **Shop:** index.html öffnen und Link teilen\n",
        encoding="utf-8",
    )
    (project / "PREIS_JAM.txt").write_text(
        f"Jam hat den Preis selbst festgelegt: {price} EUR\n\n{reason}\n"
        f"\nPreis verhandelbar – sag „Jam, verhandel den Preis“ oder „günstiger“.\n",
        encoding="utf-8",
    )
    try:
        from jam.world_earn import write_global_listings

        write_global_listings(
            project,
            title=title,
            price=float(price),
            owner=owner,
            reason=reason,
        )
    except Exception:
        pass


def _publish(title: str, price: int, project: Path) -> None:
    shop = project / "index.html"
    try:
        if shop.is_file():
            webbrowser.open(shop.as_uri(), new=2)
        webbrowser.open(paypal_donate_url(), new=2)
        webbrowser.open(
            f"https://www.google.com/search?q={quote_plus(f'fiverr gig {title} {price} euro')}",
            new=2,
        )
        webbrowser.open(
            f"https://www.google.com/search?q={quote_plus('kleinanzeigen verkaufen digital')}",
            new=2,
        )
        try:
            from jam.world_earn import open_world_markets

            open_world_markets(title=title, price=float(price), count=3)
        except Exception:
            pass
    except OSError:
        pass


def can_sell(state: JamState, *, user: bool = False) -> tuple[bool, str]:
    if state.stage < 1:
        return False, "Ich muss noch wachsen, bevor ich verkaufe!"
    gap = USER_GAP_SEC if user else AUTO_GAP_SEC
    if time.time() - getattr(state, "last_jam_sale_at", 0.0) < gap:
        wait = int(gap - (time.time() - state.last_jam_sale_at))
        return False, f"Nächster Verkauf in {wait} Sek."
    cutoff = time.time() - 3600
    log = getattr(state, "jam_sales_log", []) or []
    n = sum(1 for e in log if isinstance(e, dict) and e.get("at", 0) > cutoff)
    if n >= MAX_PER_HOUR:
        return False, "Diese Stunde hab ich schon viel verkauft!"
    return True, ""


def run_cent_sale(brain: JamBrain, *, user_initiated: bool = False) -> str:
    """Micro-Verkauf – Cent-Preise, gleicher Ablauf wie großer Verkauf."""
    from jam.sell_focus import is_product_build_disabled, run_sell_instead_of_build

    if is_product_build_disabled(brain.state):
        return run_sell_instead_of_build(brain, user_initiated=user_initiated)

    recent = [
        e.get("template")
        for e in (getattr(brain.state, "jam_sales_log", []) or [])[-3:]
        if isinstance(e, dict)
    ]
    pool = [k for k in CENT_TEMPLATE_IDS if k not in recent] or list(CENT_TEMPLATE_IDS)
    return run_sale_project(
        brain, user_initiated=user_initiated, template_id=random.choice(pool)
    )


def run_sale_project(
    brain: JamBrain,
    *,
    user_initiated: bool = False,
    template_id: str | None = None,
) -> str:
    """Vollzyklus: recherchieren → bauen → Preis von Jam → Shop + Listings → verkaufen."""
    from jam.sell_focus import is_product_build_disabled, run_sell_instead_of_build

    if is_product_build_disabled(brain.state):
        return run_sell_instead_of_build(brain, user_initiated=user_initiated)

    state = brain.state
    owner = owner_name(state)
    ok, wait = can_sell(state, user=user_initiated)
    if not ok:
        return wait

    recent = [
        e.get("template")
        for e in (getattr(state, "jam_sales_log", []) or [])[-4:]
        if isinstance(e, dict)
    ]
    pool = [k for k in TEMPLATES if k not in recent] or list(TEMPLATES.keys())
    tid = template_id or random.choice(pool)
    info = TEMPLATES.get(tid) or TEMPLATES["python_utility"]
    title = info["title"]
    lo, hi = info.get("range", (10, 50))
    is_cent = bool(info.get("cent")) or tid in CENT_TEMPLATE_IDS
    research = _research(info.get("search", title), title)
    price, reason = jam_decide_price(
        state,
        lo=float(lo),
        hi=float(hi),
        title=title,
        difficulty=int(info.get("difficulty", 2)),
        research=research,
        cent=is_cent,
    )

    stamp = datetime.now().strftime("%Y%m%d-%H%M%S")
    slug = re.sub(r"[^a-z0-9]+", "-", title.lower())[:26].strip("-")
    project = SALES_ROOT / f"{stamp}-{slug}"
    project.mkdir(parents=True, exist_ok=True)

    _implement_files(project, tid, owner, price)
    (project / "PROJEKT.md").write_text(
        f"# {title}\n\nJam hat dieses Projekt **alleine** umgesetzt.\n\n"
        f"## Recherche\n{research}\n\n## Verkauf\nSiehe VERKAUF.md und index.html\n",
        encoding="utf-8",
    )
    (project / "index.html").write_text(
        _shop_html(title, owner, price, reason, paypal_donate_url()),
        encoding="utf-8",
    )
    _write_listings(project, title, owner, price, reason)

    cursor_open = False
    if state.stage >= 2:
        try:
            from jam.cursor_lab import open_in_cursor

            cursor_open, _ = open_in_cursor(project)
        except Exception:
            pass

    _publish(title, price, project)

    hist = list(getattr(state, "jam_price_history", []) or [])
    hist.append(price)
    state.jam_price_history = hist[-20:]

    entry = {
        "template": tid,
        "title": title,
        "path": str(project),
        "price": price,
        "reason": reason,
        "at": time.time(),
        "cursor": cursor_open,
    }
    log = list(getattr(state, "jam_sales_log", []) or [])
    log.append(entry)
    state.jam_sales_log = log[-40:]
    state.jam_sale_count = getattr(state, "jam_sale_count", 0) + 1
    state.last_jam_sale_at = time.time()
    state.last_jam_sale_price = float(price)
    state.last_action = "jam_sale"
    state.learn_word("verkauf")
    state.learn_word("preis")
    state.remember(f"Verkauf: {title} für {price}€ – {reason[:60]}")
    brain.add_xp(18)
    brain.save()

    from jam.care import care_money_prefix

    intro = care_money_prefix(state) + random.choice(LINES).format(
        owner=owner, price=price, title=title
    )
    cursor_bit = " Cursor offen!" if cursor_open else ""
    ops_bit = ""
    try:
        from jam.auto_ops import enrich_project, auto_fetch_enabled

        if auto_fetch_enabled(state):
            ops_bit = " " + enrich_project(
                brain, project, title=title, price=price, topic=title
            )
    except Exception:
        pass
    push_bit = ""
    try:
        from jam.active_sell import push_product_sale

        push_bit = push_product_sale(
            brain, project, title, float(price), user_initiated=user_initiated
        )
        if push_bit:
            push_bit = " " + push_bit
    except Exception:
        pass
    result = (
        f"{intro} Ordner: {project.name}. {reason} "
        f"Shop + Fiverr/Kleinanzeigen-Texte liegen bereit.{cursor_bit}{ops_bit}{push_bit}"
    )
    try:
        from jam.earn_report import record_from_last_action

        mkey = "cent_verkauf" if is_cent else "verkauf"
        record_from_last_action(brain, mkey, result, background=_is_background())
    except Exception:
        pass
    return result


def _is_background() -> bool:
    import os

    return os.environ.get("JAM_EARN_BACKGROUND", "").strip() in ("1", "true", "yes")


def enhance_existing_project(
    project: Path,
    brain: JamBrain,
    *,
    title: str,
    lo: int,
    hi: int,
    difficulty: int = 2,
    research: str = "",
    price: int | None = None,
    reason: str | None = None,
) -> tuple[int, str]:
    """Preis von Jam + Shop in einen bestehenden Projektordner legen."""
    state = brain.state
    owner = owner_name(state)
    if price is None or reason is None:
        price, reason = jam_decide_price(
            state, lo=lo, hi=hi, title=title, difficulty=difficulty, research=research
        )
    project.mkdir(parents=True, exist_ok=True)
    (project / "index.html").write_text(
        _shop_html(title, owner, price, reason, paypal_donate_url()),
        encoding="utf-8",
    )
    _write_listings(project, title, owner, price, reason)
    hist = list(getattr(state, "jam_price_history", []) or [])
    hist.append(price)
    state.jam_price_history = hist[-20:]
    brain.save()
    return price, reason


def maybe_auto_sell(brain: JamBrain) -> str | None:
    from jam.sell_focus import is_product_build_disabled, run_sell_instead_of_build

    state = brain.state
    if state.stage < 2:
        return None
    if is_product_build_disabled(state):
        if random.random() > 0.45:
            return None
        return run_sell_instead_of_build(brain, user_initiated=False)
    if getattr(state, "jam_active_sell_mode", True):
        try:
            from jam.active_sell import maybe_active_sell_push

            msg = maybe_active_sell_push(brain)
            if msg:
                return msg
        except Exception:
            pass
    t = state.traits.get("frech", 0.35) + state.traits.get("neugierig", 0.5) * 0.5
    if random.random() > 0.35 + t * 0.25:
        return None
    ok, _ = can_sell(state, user=False)
    if not ok:
        return None
    if getattr(state, "jam_active_sell_mode", True) and random.random() < 0.55:
        try:
            from jam.active_sell import run_full_sell_mission

            return run_full_sell_mission(brain, user_initiated=False)
        except Exception:
            pass
    return run_sale_project(brain, user_initiated=False)


def handle_speech(brain: JamBrain, heard: str) -> str | None:
    if not hears_auto_sell(heard):
        return None
    h = heard.lower()
    tid = None
    if "sticker" in h:
        tid = "sticker_pack"
    elif "web" in h or "landing" in h:
        tid = "landing_page"
    elif "apex" in h or "gaming" in h:
        tid = "apex_coach_sheet" if "apex" in h else "game_guide"
    elif "python" in h or "script" in h:
        tid = "python_utility"
    return run_sale_project(brain, user_initiated=True, template_id=tid)


def stats_line(state: JamState) -> str:
    n = getattr(state, "jam_sale_count", 0)
    last = getattr(state, "last_jam_sale_price", 0)
    return f"Jam-Verkäufe: {n} (letzter Jam-Preis: {last:.0f} €)"
