// Main app: sections, navigation, review, submission, tweaks
const { useState, useEffect, useMemo, useRef } = React;

const STORAGE_KEY = "x-sec-3cx-questionnaire-v3";
const CONTACT_EMAIL = "support@360sec.de";
const CONTACT_PHONE = "02104 493770";
const CONTACT_PHONE_RAW = "+492104493770";

// Reparatur älterer Speicherstände: Zeiten wurden als "08 : 00" und Nutzer-Verweise als "user : Name" gespeichert.
function fixHours(h) {
 if (!h || typeof h !== "object") return h;
 Object.values(h).forEach(d => {
 if (!d || typeof d !== "object") return;
 ["from", "to", "pause_from", "pause_to"].forEach(k => {
 if (typeof d[k] === "string") d[k] = d[k].replace(/^(\d{1,2})\s*:\s*(\d{2})$/, "$1:$2");
 });
 });
 return h;
}
function fixEntity(v) {
 if (Array.isArray(v)) return v.map(fixEntity);
 return typeof v === "string" ? v.replace(/^user : /, "user:") : v;
}
// Optionstexte wurden früher mit Gedankenstrich gespeichert: auf die heutigen Texte abbilden
const RENAMED_OPTIONS = {
 "Nein \u2013 nur App / kein Tischtelefon": "Nein, nur App / kein Tischtelefon",
 "Anderes Modell \u2013 siehe Kommentar": "Anderes Modell (siehe Kommentar)",
 "Anders \u2013 siehe Freitext": "Anders (siehe Freitext)",
 "KI-Ansage (kostenpflichtig \u2013 Preis nach Aufwand)": "KI-Ansage (kostenpflichtig, Preis nach Aufwand)",
 "Anderer Anbieter \u2013 siehe Notiz": "Anderer Anbieter (siehe Notiz)"
};
function fixOption(v) {
 return typeof v === "string" && Object.prototype.hasOwnProperty.call(RENAMED_OPTIONS, v) ? RENAMED_OPTIONS[v] : v;
}

function loadInitial() {
 try {
 const s = localStorage.getItem(STORAGE_KEY);
 if (s) {
 const parsed = JSON.parse(s);
 if (!parsed.trunks) parsed.trunks = [blankTrunk()];
 if (!parsed.hours) parsed.hours = defaultHours();
 if (!parsed.unsure) parsed.unsure = {};
 fixHours(parsed.hours);
 if (parsed.data) {
 const th = parsed.data.team_ausnahmen_hours;
 if (th && typeof th === "object") Object.values(th).forEach(fixHours);
 Object.keys(parsed.data).forEach(k => { parsed.data[k] = fixOption(fixEntity(parsed.data[k])); });
 }
 (parsed.users || []).forEach(u => { if (u && u.phone) u.phone = fixOption(u.phone); });
 parsed.trunks.forEach(t => { if (t && t.anbieter) t.anbieter = fixOption(t.anbieter); });
 (parsed.ivr || []).forEach(m => { if (m) m.target = fixEntity(m.target); });
 // Ländervorwahl wurde als " + 49" gespeichert
 parsed.trunks.forEach(t => ((t && t.nummern) || []).forEach(n => {
 if (n && typeof n.country === "string") n.country = n.country.replace(/^\s*\+\s+/, "+");
 }));
 return parsed;
 }
 } catch (e) {}
 const urlPrefill = (window.EASY_MODE && window.EASY_MODE.readUrlPrefill()) || {};
 return {
 data: { ...urlPrefill },
 users: [blankUser(), blankUser(), blankUser()],
 departments: [],
 trunks: [blankTrunk()],
 hours: defaultHours(),
 ivr: [],
 unsure: {},
 currentIdx: 0,
 visited: {},
 isFirstRun: true
 };
}

function computeErrors(section, data, unsure) {
 const errs = {};
 if (!section.fields) return errs;
 for (const f of section.fields) {
 if (f.showIf && !f.showIf(data)) continue;
 // "Weiß nicht, X-sec entscheidet" gilt als beantwortet
 if (f.required && !data[f.id] && !(unsure && unsure[f.id])) { errs[f.id] = "Pflichtfeld"; continue; }
 if (f.validate && data[f.id] && window.validateField) {
 const m = window.validateField(f, data[f.id]);
 if (m) errs[f.id] = m;
 }
 }
 return errs;
}

function sectionFilled(section, state) {
 if (section.kind === "users") return state.users.some(u => u.name);
 if (section.kind === "departments") return state.departments.length > 0;
 if (section.kind === "trunks") return (state.trunks || []).some(t => t.name || (t.nummern || []).some(n => n.base));
 if (section.kind === "hours") return Object.values(state.hours || {}).some(d => d.open);
 if (section.kind === "diagram") return false;
 if (!section.fields) return true;
 return section.fields.some(f => state.data[f.id]);
}

// --- Section renderers ---
function GenericSection({ section, state, setData, errors, wizard, setUnsure, isUnsure }) {
 return (
 <div className="fields">
 {section.fields?.map(f => (
 <Field
 key={f.id}
 field={f}
 data={state.data}
 errors={errors}
 onChange={(k, v) => setData(k, v)}
 state={state}
 wizard={wizard}
 sectionId={section.id}
 setUnsure={setUnsure}
 isUnsure={isUnsure}
 />
 ))}
 </div>
 );
}

function UsersSection({ section, state, setUsers }) {
 const deptOptions = (state.departments || []).map(d => d.name).filter(Boolean);
 return (
 <>
 <div className="legend">
 <h4>Hinweise zu den Spalten</h4>
 <ul>
 {section.legend.map(([k, v]) => (
 <li key={k}><strong>{k}</strong><span>{v}</span></li>
 ))}
 </ul>
 </div>
 {deptOptions.length === 0 && (
 <div className="warn-box">
 Sie haben noch keine Abteilungen angelegt. Ohne Abteilungen können Sie Mitarbeitende in der Spalte „Abteilung(en)“ nicht zuordnen. Gehen Sie bei Bedarf einen Abschnitt zurück.
 </div>
 )}
 <UsersList users={state.users} onChange={setUsers} departmentOptions={deptOptions} />
 </>
 );
}

function DepartmentsSection({ section, state, setDepartments }) {
 return (
 <>
 <div className="info-box">
 <h4>{section.helperBox.title}</h4>
 <p>{section.helperBox.body}</p>
 <div className="rule">{section.helperBox.rule}</div>
 </div>
 <DepartmentsList items={state.departments} onChange={setDepartments} />
 </>
 );
}

function TrunksSection({ section, state, setTrunks }) {
 return (
 <>
 <div className="info-box">
 <h4>{section.helperBox.title}</h4>
 <p>{section.helperBox.body}</p>
 <div className="rule">{section.helperBox.rule}</div>
 </div>
 <TrunksList trunks={state.trunks || []} onChange={setTrunks} />
 </>
 );
}

function HoursSection({ section, state, setHours, setData, errors, wizard, setUnsure, isUnsure }) {
 return (
 <>
 <WeekHours hours={state.hours} onChange={setHours} />
 <div className="fields" style={{ marginTop: 28 }}>
 {section.fields?.map(f => (
 <Field key={f.id} field={f} data={state.data} errors={errors} onChange={(k, v) => setData(k, v)} state={state} wizard={wizard} sectionId={section.id} setUnsure={setUnsure} isUnsure={isUnsure} />
 ))}
 </div>
 </>
 );
}

function resolveEntity(val, state) {
 if (val == null) return "";
 if (Array.isArray(val)) return val.map(v => resolveEntity(v, state)).filter(Boolean).join(", ");
 if (typeof val === "object") return JSON.stringify(val);
 const s = String(val);
 if (s.startsWith("user:")) {
 const name = s.slice(5);
 const u = (state.users || []).find(x => x.name === name);
 return u ? u.name + (u.durchwahl ? " (" + u.durchwahl + ")" : "") : name;
 }
 if (s.startsWith("dept:")) return s.slice(5) + " (Abteilung)";
 if (s.startsWith("queue:")) return"Warteschlange " + s.slice(6);
 if (s.startsWith("vm:")) {
 const k = s.slice(3);
 return k === "main" ? "Voicemail Hauptnummer" : k === "personal" ? "Persönliche Voicemail" : "Voicemail";
 }
 if (s.startsWith("ivr:")) return"Sprachmenü / IVR";
 if (s.startsWith("special:")) {
 const k = s.slice(8);
 return { hangup: "Auflegen", "ext-input": "Durchwahl eingeben (DDI)", external:"Externe Rufnummer" }[k] || k;
 }
 if (s.startsWith("free:")) return s.slice(5);
 return s;
}

function IvrSection({ section, state, setData, setIvr, errors, wizard, setUnsure, isUnsure }) {
 const aktiv = state.data.ivr_aktiv === "Ja";
 const fProps = { data: state.data, errors, onChange: (k, v) => setData(k, v), state, wizard, sectionId: section.id, setUnsure, isUnsure };
 return (
 <div className="fields">
 <Field field={section.fields[0]} {...fProps} />
 {aktiv && (
 <>
 <Field field={section.fields[1]} {...fProps} />
 <div className="field">
 <label className="field-label">
 <span className="field-label-main">Menüpunkte</span>
 <span className="field-hint">Taste und Ziel pro Auswahlmöglichkeit. Nutzen Sie Ziffern 0 bis 9 oder * / #.</span>
 </label>
 <IVRTree items={state.ivr} onChange={setIvr} state={state} />
 </div>
 <Field field={section.fields[2]} {...fProps} />
 <Field field={section.fields[3]} {...fProps} />
 </>
 )}
 </div>
 );
}

function DiagramSection({ state, onJump }) {
 return (
 <>
 <div className="info-box">
 <h4>So sieht Ihre Anlage aus</h4>
 <p>Dieses Diagramm wird live aus Ihren Angaben erzeugt. Klicken Sie auf einen Knoten für Details. Stimmt etwas nicht, springen Sie direkt in den passenden Abschnitt zurück.</p>
 </div>
 <FlowDiagram state={state} onJump={onJump} />
 </>
 );
}

// --- Review ---
const SEND_LABELS = { idle: "An X-sec senden", sending: "Wird gesendet …", sent: "Nochmals senden", error: "Erneut senden" };

function SendStatus({ send, onRetry }) {
 if (send.status === "idle") return null;
 if (send.status === "sending") return (
 <div className="info-box send-status" role="status"><p>Ihre Angaben werden an X-sec gesendet …</p></div>
 );
 if (send.status === "sent") return (
 <div className="info-box send-status is-ok" role="status">
 <h4>Vielen Dank! Ihre Angaben sind bei uns angekommen.</h4>
 <p>Wir melden uns in Kürze.</p>
 </div>
 );
 return (
 <div className="info-box send-status is-error" role="alert">
 <h4>Senden fehlgeschlagen</h4>
 <p>{send.error}</p>
 {onRetry && <button className="btn" style={{marginTop: 12}} onClick={onRetry}>Erneut senden</button>}
 </div>
 );
}

function Review({ state, send, onSend }) {
 const sections = window.FORM_SCHEMA.filter(s => s.kind !== "diagram");
 return (
 <div className="review">
 <SendStatus send={send} onRetry={onSend} />
 <div className="info-box">
 <h4>Zusammenfassung</h4>
 <p>Bitte prüfen Sie Ihre Angaben. Unten können Sie sie direkt an X-sec senden, das Formular drucken oder als JSON-Datei speichern.</p>
 </div>
 {sections.map(s => <ReviewCard key={s.id} section={s} state={state} />)}

 <div className="submit-zone">
 <h3>Fertig ausgefüllt? So senden Sie uns das Dokument zurück.</h3>
 <p>Ein Klick auf „An X-sec senden“ genügt, Ihre Angaben kommen direkt bei uns an. PDF und JSON sind für Ihre eigenen Unterlagen.</p>
 <div className="submit-actions">
 <button className="btn btn-primary" onClick={onSend} disabled={send.status === "sending"}>{SEND_LABELS[send.status]}</button>
 <button className="btn" onClick={() => window.print()}>Als PDF drucken / speichern</button>
 <button className="btn" onClick={() => downloadJSON(state)}>Als JSON herunterladen</button>
 <button className="btn" onClick={() => downloadPromptPackage(state, false)} title="ZIP mit System-Prompt, Task-Prompts und JSON-Payload für den 3CX Configurator Skill">Nur Skill-Prompt-Paket (.zip)</button>
 </div>
 <SendStatus send={send} />
 </div>
 </div>
 );
}

function ReviewCard({ section, state }) {
 const items = collectReview(section, state);
 return (
 <div className="review-card">
 <h3>{section.title} <span className="num">{section.num !== "0" ? "§ " + section.num : "Kontakt"}</span></h3>
 {items.length === 0 ? (
 <div className="review-empty">Keine Angaben.</div>
 ) : (
 <dl className="review-list">
 {items.map(([k, v], i) => (
 <React.Fragment key={i}>
 <dt>{k}</dt>
 <dd>{v}</dd>
 </React.Fragment>
 ))}
 </dl>
 )}
 </div>
 );
}

function collectReview(section, state) {
 const items = [];
 if (section.kind === "users") {
 state.users.forEach((u, i) => {
 if (!u.name && !u.email) return;
 const abt = Array.isArray(u.abteilungen) ? u.abteilungen.join(", ") : u.abteilungen;
 const parts = [
 u.durchwahl && "DW " + u.durchwahl,
 u.phone && u.phone !== "Nein, nur App / kein Tischtelefon" ? u.phone : (u.phone === "Nein, nur App / kein Tischtelefon" ? "nur App" : ""),
 u.apps,
 abt && "Abt.: " + abt,
 u.sonderrufe && "Sonderrufe: " + u.sonderrufe
 ].filter(Boolean);
 items.push([u.name || "Nutzer " + (i+1), [u.email, ...parts].filter(Boolean).join(" · ")]);
 });
 return items;
 }
 if (section.kind === "departments") {
 state.departments.forEach(d => {
 if (!d.name && !d.members) return;
 items.push([d.name || "", [d.members, d.owner && "verantw.: " + d.owner].filter(Boolean).join(" · ")]);
 });
 return items;
 }
 if (section.kind === "trunks") {
 // Gewählter Weg mit denselben Texten wie die Auswahl im Bogen
 const PATH_LABELS = { credentials: "Ich habe Zugangsdaten", contract: "Ich habe den Vertrag / Übernahmeschein", port: "Ich möchte portieren", unknown: "Ich weiß es nicht" };
 (state.trunks || []).forEach((t, i) => {
 if (!t.name && !t.path && !(t.nummern || []).some(n => n.base)) return;
 const nums = (t.nummern || []).map(formatNumber).filter(Boolean).join(" / ");
 const c = t.path === "credentials" ? (t.credentials || {}) : {};
 const p = t.path === "port" ? (t.portierung || {}) : {};
 const docs = (t.docs || []).map(d => d.name).filter(Boolean);
 // Alles außer dem Passwort
 const parts = [
 t.path && "Weg: " + (PATH_LABELS[t.path] || t.path),
 t.anbieter, t.typ, nums,
 t.unsure && "Achtung: Kunde unsicher",
 c.server && "Server: " + c.server + (c.port ? ", Port " + c.port : ""),
 c.outbound && "Outbound-Proxy: " + c.outbound,
 c.username && "Benutzer: " + c.username,
 p.aktueller_anbieter && "Aktueller Anbieter: " + p.aktueller_anbieter,
 p.kuendigung_bis && "Kündigung bis: " + p.kuendigung_bis,
 p.wunschtermin && "Wunschtermin Portierung: " + p.wunschtermin,
 t.path === "unknown" && "Rückruf erwünscht: " + (t.kontakt_gewuenscht ? "Ja" : "Nein"),
 docs.length > 0 && "Dokumente: " + docs.join(", "),
 t.notiz && "Notiz: " + t.notiz
 ].filter(Boolean);
 items.push([t.name || `Trunk ${i+1}`, parts.join(" · ")]);
 });
 return items;
 }
 if (section.kind === "hours") {
 WEEKDAYS.forEach(({k, l}) => {
 const d = (state.hours || {})[k];
 if (!d) return;
 if (!d.open) { items.push([l, "geschlossen"]); return; }
 let s = `${d.from} bis ${d.to}`;
 if (d.pause && d.pause_from && d.pause_to) s += ` · Pause ${d.pause_from} bis ${d.pause_to}`;
 items.push([l, s]);
 });
 // Die Felder darunter laufen durch dieselbe Typbehandlung wie unten (dept-hours, entity usw.)
 }
 if (section.id === "ivr") {
 if (state.data.ivr_aktiv) items.push(["Sprachmenü aktiv", state.data.ivr_aktiv]);
 if (state.data.ivr_begruessung) items.push(["Begrüßungstext", state.data.ivr_begruessung]);
 state.ivr.forEach(m => {
 const tgt = m.target ? resolveEntity(m.target, state) : "";
 items.push(["Taste " + (m.key || "?"), [m.label, tgt && "→ " + tgt].filter(Boolean).join(" ")]);
 });
 if (state.data.ivr_timeout) items.push(["Bei keiner Eingabe", state.data.ivr_timeout]);
 if (state.data.ivr_ungueltig) items.push(["Bei ungültiger Eingabe", state.data.ivr_ungueltig]);
 return items;
 }
 section.fields?.forEach(f => {
 if (f.showIf && !f.showIf(state.data)) return;
 if (f.type === "info") return;
 const v = state.data[f.id];
 if (v == null || v === "" || (Array.isArray(v) && v.length === 0)) {
 if (state.unsure && state.unsure[f.id]) items.push([f.label, "Weiß nicht, X-sec entscheidet"]);
 return;
 }
 if (f.type === "clip-map"&& typeof v === "object") {
 Object.entries(v).forEach(([k, vv]) => { if (vv) items.push([f.label + " · " + k, vv]); });
 return;
 }
 if (f.type === "shortcut-map" && Array.isArray(v)) {
 v.forEach(r => {
 if (r.code && r.target) {
 const label = r.name ? `${r.name} (${r.code})` : r.code;
 items.push([f.label + " · " + label, resolveEntity(r.target, state)]);
 }
 });
 return;
 }
 if (f.type === "ext-shortcut-map" && Array.isArray(v)) {
 v.forEach(r => {
 if (r.name || r.number) {
 const parts = [r.number, r.code && `Taste ${r.code}`].filter(Boolean).join(" · ");
 items.push([f.label + " · " + (r.name || "ohne Namen"), parts]);
 }
 });
 return;
 }
 if (f.type === "dept-hours"&& typeof v === "object" && !Array.isArray(v)) {
 Object.entries(v).forEach(([dept, hrs]) => {
 if (!hrs) return;
 const lines = WEEKDAYS.map(({k, l}) => {
 const d = hrs[k];
 if (!d || !d.open) return `${l}: geschlossen`;
 let s = `${l}: ${d.from} bis ${d.to}`;
 if (d.pause && d.pause_from && d.pause_to) s += ` (Pause ${d.pause_from} bis ${d.pause_to})`;
 return s;
 }).join(" · ");
 items.push([f.label + " · " + dept, lines]);
 });
 return;
 }
 if (f.type === "entity"|| f.type === "entity-multi") {
 items.push([f.label, resolveEntity(v, state)]);
 return;
 }
 // Objekte nie roh an React geben (sonst weiße Seite)
 items.push([f.label, typeof v === "object" ? resolveEntity(v, state) : v]);
 });
 return items;
}

function safeName(state) {
 return (state.data.firma || "checkliste").replace(/[^a-z0-9]+/gi, "-").toLowerCase();
}

function downloadJSON(state) {
 const blob = new Blob([JSON.stringify({ meta: { exported: new Date().toISOString(), schema: "3cx-checklist-v2"}, ...state }, null, 2)], { type:"application/json" });
 const url = URL.createObjectURL(blob);
 const a = document.createElement("a");
 a.href = url;
 a.download = `3cx-checkliste-${safeName(state)}.json`;
 a.click();
 URL.revokeObjectURL(url);
}

async function downloadPromptPackage(state, silent) {
 if (!window.PromptGen || !window.JSZip) {
 if (!silent) alert("Prompt-Generator ist nicht geladen.");
 return;
 }
 const { files } = window.PromptGen.buildPackage(state);
 const zip = new JSZip();
 Object.entries(files).forEach(([path, content]) => zip.file(path, content));
 const blob = await zip.generateAsync({ type: "blob" });
 const url = URL.createObjectURL(blob);
 const a = document.createElement("a");
 a.href = url;
 a.download = `3cx-configurator-prompts-${safeName(state)}.zip`;
 a.click();
 URL.revokeObjectURL(url);
}

function buildPlainText(state) {
 const lines = ["3CX Konfigurations-Checkliste", "=============================", ""];
 window.FORM_SCHEMA.filter(s => s.kind !== "diagram").forEach(s => {
 const items = collectReview(s, state);
 if (items.length === 0) return;
 lines.push(`## ${s.num !== "0" ? s.num + ". " : ""}${s.title}`);
 items.forEach(([k, v]) => lines.push(`- ${k}: ${v}`));
 lines.push("");
 });
 return lines.join("\n");
}

// --- Support Card (always visible) ---
function SupportCard() {
 return (
 <aside className="support-card" aria-label="Kontakt zu X-sec">
 <div className="support-title">Wir helfen beim Ausfüllen</div>
 <div className="support-body">
 <p>
 Unsicher bei einer Frage? Rufen Sie uns an und erwähnen Sie,
 dass Sie gerade den <strong>3CX-Bogen</strong> ausfüllen. Wir begleiten
 Sie Schritt für Schritt.
 </p>
 <a className="support-phone" href={"tel:" + CONTACT_PHONE_RAW}>
 <span className="support-phone-icon" aria-hidden="true"><XsecIcon name="phone" /></span>
 <span>
 <span className="support-phone-label">X-sec Hotline</span>
 <span className="support-phone-num">{CONTACT_PHONE}</span>
 </span>
 </a>
 <a className="support-mail" href={"mailto:" + CONTACT_EMAIL}>
 oder per E-Mail: {CONTACT_EMAIL}
 </a>
 </div>
 </aside>
 );
}

// --- Main app ---
const STORY_KEY = "x-sec-3cx-story-v1";
const VIEW_KEY = "x-sec-3cx-view-mode"; //"story" | "form"

function loadStory() {
 try { const s = localStorage.getItem(STORY_KEY); if (s) { const st = JSON.parse(s); if (st) fixHours(st.hours); return st; } } catch (e) {}
 return null;
}

function App() {
 const initial = loadInitial();
 const [state, setState] = useState(initial);
 const [showReview, setShowReview] = useState(false);
 const [sideOpen, setSideOpen] = useState(false); // Abschnittsliste mobil aufgeklappt
 const [errors, setErrors] = useState({});
 const [saved, setSaved] = useState(true);
 const [tweaksOpen, setTweaksOpen] = useState(false);
 const [tweaks, setTweaks] = useState(window.TWEAK_DEFAULTS || { theme: "minimal", density:"comfortable", accent:"pink" });
 const [view, setView] = useState(() => {
 const m = new URLSearchParams(location.search).get("modus");
 const map = { neu: "story", profi: "form", aenderung: "change", nachricht: "message" };
 return (m && map[m]) || "start";
 });
 const [story, setStory] = useState(() => loadStory());
 const [wizard, setWizard] = useState(() => {
 let stored = null;
 try { stored = localStorage.getItem("x-sec-3cx-wizard-mode"); } catch (e) {}
 return stored === null ? true : stored === "1";
 });
 const [saveFailed, setSaveFailed] = useState(false);
 // Stand, der bewusst nicht gespeichert wird (nach erfolgreichem Versand oder "Alles zurücksetzen")
 const skipSaveRef = useRef(null);
 const [showWelcome, setShowWelcome] = useState(initial.isFirstRun === true && view === "form");
 const [appliedDefaults, setAppliedDefaults] = useState([]);
 const [send, setSend] = useState({ status: "idle", error: "" }); // idle | sending | sent | error
 const sendingRef = useRef(false);
 const mainRef = useRef(null);

 // Checkliste an X-sec senden (Server erzeugt JSON- und ZIP-Anhang selbst)
 const sendChecklist = async (s) => {
 if (sendingRef.current) return;
 sendingRef.current = true;
 setSend({ status: "sending", error: "" });
 const res = await window.sendToXsec({ type: "checkliste", firma: s.data.firma, name: s.data.ansprechpartner, kontakt: s.data.kontakt_email, text: buildPlainText(s), state: s });
 sendingRef.current = false;
 if (res.ok) {
 // Gespeicherte Angaben aufräumen; "Nochmals senden" nutzt den Stand im Speicher der Seite
 skipSaveRef.current = s;
 window.clearXsecStorage();
 }
 setSend(res.ok ? { status: "sent", error: "" } : { status: "error", error: res.error });
 };

 const setUnsure = (fieldId, val) => {
 setState(s => {
 const u = { ...(s.unsure || {}) };
 if (val) u[fieldId] = true; else delete u[fieldId];
 return { ...s, unsure: u };
 });
 };
 const isUnsure = (fieldId) => !!(state.unsure && state.unsure[fieldId]);

 useEffect(() => {
 const t = setTimeout(() => {
 // Nach Versand oder Zurücksetzen nichts zurückschreiben, solange sich nichts ändert
 if (skipSaveRef.current !== state) {
 try {
 localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
 setSaveFailed(false);
 } catch (e) {
 setSaveFailed(true); // z. B. Speicher voll (große Uploads) oder gesperrt
 }
 }
 setSaved(true);
 }, 400);
 setSaved(false);
 return () => clearTimeout(t);
 }, [state]);

 useEffect(() => {
 // Hell/Dunkel kommt vom Umschalter in der Navigation (<html data-theme>), nicht aus den Tweaks
 document.body.dataset.density = tweaks.density || "comfortable";
 }, [tweaks]);

 useEffect(() => {
 try { localStorage.setItem("x-sec-3cx-wizard-mode", wizard ? "1" : "0"); } catch (e) {}
 document.body.dataset.wizard = wizard ? "1" : "0";
 }, [wizard]);

 useEffect(() => {
 const handler = (e) => {
 if (!e.data || typeof e.data !== "object") return;
 if (e.data.type === "__activate_edit_mode") setTweaksOpen(true);
 if (e.data.type === "__deactivate_edit_mode") setTweaksOpen(false);
 };
 window.addEventListener("message", handler);
 window.parent.postMessage({ type: "__edit_mode_available"}, "*");
 return () => window.removeEventListener("message", handler);
 }, []);

 const allSections = window.FORM_SCHEMA;
 const sections = useMemo(() => {
 if (!wizard || !window.EASY_MODE) return allSections;
 return allSections.filter(s => !window.EASY_MODE.isSectionAdvanced(s.id));
 }, [wizard, allSections]);
 const total = sections.length;
 const idx = Math.min(state.currentIdx, total - 1);
 const current = sections[idx];

 // Eine Zählbasis für Seitenleiste, Fortschritt und "Abschnitt x von y": die nummerierten Abschnitte,
 // ohne den Start ("Bearbeitet durch") und ohne das Diagramm, das nichts abfragt
 const counted = sections.filter(s => s.num !== "0" && s.kind !== "diagram");
 const filledCount = counted.filter(s => sectionFilled(s, state)).length;
 const progress = Math.round((filledCount / counted.length) * 100);
 const currentPos = counted.indexOf(current);

 const setData = (k, v) => setState(s => ({ ...s, data: { ...s.data, [k]: v } }));
 const setUsers = (users) => setState(s => ({ ...s, users }));
 const setDepartments = (departments) => setState(s => ({ ...s, departments }));
 const setTrunks = (trunks) => setState(s => ({ ...s, trunks }));
 const setHours = (hours) => setState(s => ({ ...s, hours }));
 const setIvr = (ivr) => setState(s => ({ ...s, ivr }));

 const goto = (i) => {
 setShowReview(false);
 setState(s => ({ ...s, currentIdx: i, visited: { ...s.visited, [s.currentIdx]: true } }));
 setErrors({});
 if (mainRef.current) mainRef.current.scrollTop = 0;
 window.scrollTo({ top: 0, behavior: "smooth" });
 };
 const gotoById = (id) => {
 const i = sections.findIndex(s => s.id === id);
 if (i >= 0) goto(i);
 };

 const onNext = () => {
 if (!["users", "departments", "trunks", "hours", "diagram"].includes(current.kind)) {
 const errs = computeErrors(current, state.data, state.unsure);
 if (Object.keys(errs).length) {
 setErrors(errs);
 return;
 }
 }
 setErrors({});
 if (idx < total - 1) goto(idx + 1);
 else setShowReview(true);
 };
 const onPrev = () => {
 if (showReview) { setShowReview(false); return; }
 if (idx > 0) goto(idx - 1);
 };

 const resetAll = () => {
 if (!confirm("Alle Angaben löschen? Diese Aktion kann nicht rückgängig gemacht werden.")) return;
 // Alle x-sec-3cx-* Keys (Bogen, Story, Änderung, Ansicht) und auch den Story-Stand zurücksetzen
 window.clearXsecStorage();
 const fresh = { data: {}, users: [blankUser(), blankUser(), blankUser()], departments: [], trunks: [blankTrunk()], hours: defaultHours(), ivr: [], currentIdx: 0, visited: {} };
 skipSaveRef.current = fresh;
 setState(fresh);
 setStory(null);
 setShowReview(false);
 };

 const lastIdx = total - 1;

 useEffect(() => {
 try { localStorage.setItem(VIEW_KEY, view); } catch (e) {}
 window.scrollTo(0, 0);
 if (view === "form" && state.isFirstRun) setShowWelcome(true);
 }, [view]);

 // Gemeinsame Hauptnavigation; im Formular ist "Neue Anlage" aktiv
 const nav = <XsecNav active={view === "form" ? "story" : view} onNavigate={setView} phone={CONTACT_PHONE} phoneRaw={CONTACT_PHONE_RAW} />;

 if (["start", "change", "message"].includes(view) && window.StartMode) {
 const SM = window.StartMode;
 const home = () => setView("start");
 return (
 <>
 {nav}
 {view === "start" && <SM.StartScreen hasDraft={!!story} onNew={() => setView("story")} onProfi={() => setView("form")} onChange={() => setView("change")} onMessage={() => setView("message")} />}
 {view === "change" && <SM.ChangeRequest baseData={state.data} onHome={home} />}
 {view === "message" && <SM.MessageForm baseData={state.data} onHome={home} />}
 {tweaksOpen && <TweaksPanel tweaks={tweaks} setTweaks={setTweaks} onClose={() => setTweaksOpen(false)} />}
 </>
 );
 }

 if (view === "story" && window.StoryMode) {
 const persistStory = (s) => {
 if (s === story) return; // unverändert (z. B. beim erneuten Öffnen nach dem Versand): nichts zurückschreiben
 try { localStorage.setItem(STORY_KEY, JSON.stringify(s)); } catch (e) {}
 setStory(s);
 };
      // Story nur übernehmen, wenn seit der letzten Übernahme etwas beantwortet wurde.
      // Danach gilt sie als übernommen, damit spätere Änderungen im Profi-Bogen nicht überschrieben werden.
 const takeStory = (s, mappedState) => {
 if (!s || s.dirty === false || !window.StoryMode.isStoryAnswered(s, state)) return null;
 const mapped = mappedState || window.StoryMode.mapStoryToSchema(s, state);
 const { taken, dirty, ...snapshot } = s; // Stand dieser Übernahme merken (siehe mapStoryToSchema)
 persistStory({ ...s, dirty: false, taken: JSON.parse(JSON.stringify(snapshot)) });
 return mapped;
 };
 const handleStorySubmit = (mappedState, storyData) => {
      // Merge mapped state into form state and switch to review
 const merged = { ...state, ...takeStory(storyData, mappedState), currentIdx: 0, isFirstRun: false };
 setState(merged);
 setShowReview(true);
 setView("form");
      // Vollständigen, zusammengeführten State an X-sec senden
 sendChecklist(merged);
 };
 const handleStoryPdf = (mappedState, storyData) => {
 const taken = takeStory(storyData, mappedState);
 setState(prev => ({ ...prev, ...taken, isFirstRun: false }));
 setShowReview(true);
 setView("form");
 setTimeout(() => window.print(), 400);
 };
 return (
 <>
 {nav}
 <window.StoryMode.StoryApp
 initialStory={story}
 baseState={state}
 onSubmitStory={handleStorySubmit}
 onPdfPrint={handleStoryPdf}
 onJumpProfi={() => {
            // On switch to Profi: map current story → state so user keeps their answers (Nutzer und Zeiten werden zusammengeführt)
 const taken = takeStory(story);
 if (taken) setState(prev => ({ ...prev, ...taken, isFirstRun: false }));
 setView("form");
 }}
 onHome={() => setView("start")}
 onSaveDraft={() => alert("Ihre Antworten sind automatisch im Browser gespeichert. Sie können diesen Tab schließen und später zurückkehren.")}
 persistStory={persistStory}
 />
 {tweaksOpen && <TweaksPanel tweaks={tweaks} setTweaks={setTweaks} onClose={() => setTweaksOpen(false)} />}
 </>
 );
 }

 return (
 <div className="app">
 {nav}
 <aside className="sidebar">
 <div className="brand">
 <div className="brand-badge">3CX Onboarding</div>
 <div>
 <h1 className="doc-title">Konfigurations-Checkliste</h1>
 <div className="doc-sub">Nutzer, Abteilungen, Anrufflüsse und Erreichbarkeit.</div>
 </div>
 </div>

 <div className="progress">
 <div className="progress-bar"><div className="progress-fill" style={{ width: progress +"%" }} /></div>
 <div className="progress-meta">
 <span>{filledCount} / {counted.length} Abschnitte</span>
 <span>{progress}%</span>
 </div>
 </div>

 {/* Mobil eingeklappt, damit das Formular nicht erst nach der ganzen Liste beginnt */}
 <button type="button" className="side-toggle" aria-expanded={sideOpen} aria-controls="side-sections" onClick={() => setSideOpen(o => !o)}>
 <span>Abschnitte und Hilfe</span>
 <XsecIcon name="chevron-down" />
 </button>
 <div id="side-sections" className={`side-sections ${sideOpen ? "is-open" : ""}`}>
 <nav className="nav" aria-label="Abschnitte" onClick={() => setSideOpen(false)}>
 {sections.map((s, i) => {
 const done = sectionFilled(s, state);
 const active = !showReview && i === idx;
 return (
 <button key={s.id} className={`nav-item ${active ? "is-active" : ""} ${done ? "is-done" : ""}`} onClick={() => goto(i)}>
 <span className="nav-check" aria-hidden="true">{done && <XsecIcon name="check" size={14} />}</span>
 <span>{s.title}</span>
 <span className="nav-num">{counted.includes(s) ? counted.indexOf(s) + 1 : "•"}</span>
 </button>
 );
 })}
 <button className={`nav-item ${showReview ? "is-active" : ""}`} onClick={() => setShowReview(true)}>
 <span className="nav-check" aria-hidden="true" />
 <span>Übersicht & Senden</span>
 <span className="nav-num"><XsecIcon name="arrow-down" size={14} /></span>
 </button>
 </nav>

 <SupportCard />
 </div>

 <div className="side-foot">
 {saveFailed ? (
 <span className="save-state" role="alert" style={{ color: "var(--danger)" }} title="Ihre Angaben bleiben bis zum Schließen der Seite erhalten. Große Uploads ggf. entfernen.">
 Nicht gespeichert: Browser-Speicher voll oder gesperrt
 </span>
 ) : (
 <span className="save-state">{saved ? "Automatisch gespeichert" : "Speichert…"}</span>
 )}
 <span>Fragen? <a href={"mailto:" + CONTACT_EMAIL}>{CONTACT_EMAIL}</a></span>
 <button className="btn btn-ghost" style={{alignSelf:"flex-start"}} onClick={resetAll}>Alles zurücksetzen</button>
 </div>
 </aside>

 <main className="main" ref={mainRef}>
 <div className="topbar">
 <div className="crumbs">
 <span>Abfragebogen</span>
 <span className="sep">/</span>
 <span>{showReview ? "Übersicht" : current.title}</span>
 </div>
 <div className="topbar-actions">
 <button className="mode-tab is-active" onClick={() => setView("story")} title="Zurück zum geführten Einfach-Modus mit 6 Fragen">
 <span className="mode-tab-icon" aria-hidden="true"><XsecIcon name="arrow-left" /></span>
 <span>Einfach-Modus</span>
 </button>
 <div className="mode-toggle" role="tablist" aria-label="Detailtiefe">
 <button role="tab" aria-selected={wizard} className={`mode-tab ${wizard ? "is-active" : ""}`} onClick={() => setWizard(true)}>
 <span className="mode-tab-icon" aria-hidden="true"><XsecIcon name="sparkles" /></span>
 <span>Wizard</span>
 </button>
 <button role="tab" aria-selected={!wizard} className={`mode-tab ${!wizard ? "is-active" : ""}`} onClick={() => setWizard(false)}>
 <span className="mode-tab-icon" aria-hidden="true"><XsecIcon name="settings" /></span>
 <span>Profi</span>
 </button>
 </div>
 {!showReview && (
 <button className="btn" onClick={() => setShowReview(true)}>Übersicht ansehen</button>
 )}
 </div>
 </div>

 <div className="content">
 {showReview ? (
 <>
 <div className="section-head">
 <h2 className="section-title">Prüfen und zurücksenden</h2>
 <p className="section-intro">Alle Ihre Angaben auf einen Blick. Unten können Sie sie direkt an X-sec senden.</p>
 </div>
 <Review state={state} send={send} onSend={() => sendChecklist(state)} />
 <div className="step-nav">
 <button className="btn" onClick={onPrev}><XsecIcon name="arrow-left" /> Zurück zum Formular</button>
 </div>
 </>
 ) : (
 <>
 <div className="section-head">
 {current.num === "0" && <div className="section-eyebrow">Start</div>}
 {currentPos >= 0 && <div className="section-eyebrow">Abschnitt {currentPos + 1} von {counted.length}</div>}
 <h2 className="section-title">{current.title}</h2>
 {current.intro && <p className="section-intro">{current.intro}</p>}
 </div>

 {current.kind === "users" && <UsersSection section={current} state={state} setUsers={setUsers} />}
 {current.kind === "departments" && <DepartmentsSection section={current} state={state} setDepartments={setDepartments} />}
 {current.kind === "trunks" && <TrunksSection section={current} state={state} setTrunks={setTrunks} />}
 {current.kind === "hours" && <HoursSection section={current} state={state} setHours={setHours} setData={setData} errors={errors} wizard={wizard} setUnsure={setUnsure} isUnsure={isUnsure} />}
 {current.kind === "ivr" && <IvrSection section={current} state={state} setData={setData} setIvr={setIvr} errors={errors} wizard={wizard} setUnsure={setUnsure} isUnsure={isUnsure} />}
 {current.kind === "diagram" && <DiagramSection state={state} onJump={gotoById} />}
 {(!current.kind || current.kind === "meta") && <GenericSection section={current} state={state} setData={setData} errors={errors} wizard={wizard} setUnsure={setUnsure} isUnsure={isUnsure} />}

 <div className="step-nav">
 <button className="btn" onClick={onPrev} disabled={idx === 0}><XsecIcon name="arrow-left" /> Zurück</button>
 {idx === lastIdx ? (
 <button className="btn btn-primary" onClick={() => setShowReview(true)}>Zur Übersicht <XsecIcon name="arrow-right" /></button>
 ) : (
 <button className="btn btn-primary" onClick={onNext}>Weiter: {sections[idx+1].title} <XsecIcon name="arrow-right" /></button>
 )}
 </div>
 </>
 )}
 </div>
 </main>

 {tweaksOpen && <TweaksPanel tweaks={tweaks} setTweaks={setTweaks} onClose={() => setTweaksOpen(false)} />}
 {showWelcome && (
 <WelcomeModal
 onAcceptDefaults={() => {
 const result = window.EASY_MODE.applySmartDefaults(state);
 setState(s => ({ ...result.state, isFirstRun: false }));
 setAppliedDefaults(result.applied);
 setShowWelcome(false);
 }}
 onSkip={() => {
 setState(s => ({ ...s, isFirstRun: false }));
 setShowWelcome(false);
 }}
 defaults={window.EASY_MODE ? window.EASY_MODE.SMART_DEFAULTS : []}
 />
 )}
 {appliedDefaults.length > 0 && (
 <DefaultsBanner items={appliedDefaults} onDismiss={() => setAppliedDefaults([])} />
 )}
 </div>
 );
}

function WelcomeModal({ onAcceptDefaults, onSkip, defaults }) {
 return (
 <div className="welcome-overlay" role="dialog" aria-modal="true" aria-labelledby="welcome-title">
 <div className="welcome-card">
 <div className="welcome-eyebrow">Willkommen beim 3CX-Abfragebogen</div>
 <h2 id="welcome-title" className="welcome-title">Sie müssen nichts wissen, wir schlagen alles vor.</h2>
 <p className="welcome-lead">
 Damit Sie nicht bei jedem Detail rätseln müssen, haben wir
 <strong> sinnvolle Standardeinstellungen </strong>
 vorbereitet, die für die meisten Unternehmen passen. Sie können alles ändern,
 aber wahrscheinlich brauchen Sie das gar nicht.
 </p>
 <div className="welcome-defaults">
 <div className="welcome-defaults-head">Diese Standardeinstellungen schlagen wir vor:</div>
 <ul className="welcome-defaults-list">
 {defaults.slice(0, 8).map((d, i) => (
 <li key={i}><span className="welcome-defaults-check" aria-hidden="true"><XsecIcon name="check" /></span>{d[2]}</li>
 ))}
 {defaults.length > 8 && <li className="welcome-defaults-more">… und {defaults.length - 8} weitere</li>}
 </ul>
 </div>
 <div className="welcome-modes">
 <div className="welcome-mode">
 <div className="welcome-mode-icon" aria-hidden="true"><XsecIcon name="sparkles" /></div>
 <div>
 <strong>Einfach-Modus (empfohlen)</strong>
 <p>Nur die wichtigsten Fragen. Alles Technische blenden wir aus oder kümmern uns selbst darum.</p>
 </div>
 </div>
 <div className="welcome-mode">
 <div className="welcome-mode-icon" aria-hidden="true"><XsecIcon name="circle-help" /></div>
 <div>
 <strong>„Weiß nicht“-Knopf an jedem Feld</strong>
 <p>Bei Unsicherheit einfach klicken, wir entscheiden das für Sie.</p>
 </div>
 </div>
 <div className="welcome-mode">
 <div className="welcome-mode-icon" aria-hidden="true"><XsecIcon name="phone" /></div>
 <div>
 <strong>Hotline auf Standby</strong>
 <p>Festhängen? Anrufen, wir gehen den Bogen mit Ihnen durch.</p>
 </div>
 </div>
 </div>
 <div className="welcome-actions">
 <button className="btn btn-primary btn-lg" onClick={onAcceptDefaults}>Mit Standardeinstellungen starten <XsecIcon name="arrow-right" /></button>
 <button className="btn btn-ghost" onClick={onSkip}>Komplett leer starten</button>
 </div>
 <p className="welcome-foot">Standardeinstellungen lassen sich jederzeit überschreiben, nichts ist endgültig.</p>
 </div>
 </div>
 );
}

function DefaultsBanner({ items, onDismiss }) {
 return (
 <div className="defaults-banner" role="status">
 <div className="defaults-banner-icon" aria-hidden="true"><XsecIcon name="sparkles" size={14} /></div>
 <div className="defaults-banner-body">
 <strong>{items.length} Standardeinstellungen vorausgewählt.</strong>
 <span> Sie können jede davon im Bogen anpassen.</span>
 </div>
 <button className="defaults-banner-close" onClick={onDismiss} aria-label="Schließen"><XsecIcon name="x" size={18} /></button>
 </div>
 );
}

function TweaksPanel({ tweaks, setTweaks, onClose }) {
 const update = (k, v) => {
 const next = { ...tweaks, [k]: v };
 setTweaks(next);
 window.parent.postMessage({ type: "__edit_mode_set_keys", edits: next }, "*");
 };
 return (
 <div className="tweaks-panel">
 <h4>Tweaks <button className="icon-btn" onClick={onClose} aria-label="Schließen"><XsecIcon name="x" size={14} /></button></h4>
 <div className="tweaks-row">
 <span className="tw-label">Theme</span>
 <div className="tw-chips">
 {["minimal", "bold", "dark"].map(t => (
 <button key={t} className={`tw-chip ${tweaks.theme === t ? "is-active" : ""}`} onClick={() => update("theme", t)}>
 {t === "minimal" ? "Zurückhaltend" : t === "bold" ? "Markant" : "Dunkel"}
 </button>
 ))}
 </div>
 <p className="tw-desc">
 {tweaks.theme === "bold" ? "Dunkle Seitenleiste, kräftige Pink-Flächen, größere Überschriften."
 : tweaks.theme === "dark" ? "Dunkler Hintergrund, angenehm bei wenig Licht."
 : "Hell und ruhig, Pink nur als Akzent."}
 </p>
 </div>
 </div>
 );
}

// Fängt Darstellungsfehler ab, damit nie eine weiße Seite bleibt
class ErrorBoundary extends React.Component {
 constructor(props) {
 super(props);
 this.state = { failed: false };
 this.reset = this.reset.bind(this);
 }
 static getDerivedStateFromError() { return { failed: true }; }
 reset() {
 if (!confirm("Alle im Browser gespeicherten Angaben löschen und neu beginnen?")) return;
 window.clearXsecStorage();
 location.reload();
 }
 render() {
 if (!this.state.failed) return this.props.children;
 return (
 <div style={{ maxWidth: 640, margin: "64px auto", padding: "0 16px" }}>
 <div className="info-box send-status is-error" role="alert">
 <h4>Da ist leider etwas schiefgelaufen.</h4>
 <p>Die Seite konnte nicht richtig angezeigt werden. Rufen Sie uns gern an, wir helfen sofort weiter: X-sec Hotline <a href={"tel:" + CONTACT_PHONE_RAW}>{CONTACT_PHONE}</a></p>
 <p>Bleibt der Fehler bestehen, setzen Sie die im Browser gespeicherten Angaben zurück und beginnen neu.</p>
 <button className="btn" style={{ marginTop: 12 }} onClick={this.reset}>Gespeicherte Angaben zurücksetzen</button>
 </div>
 </div>
 );
 }
}

ReactDOM.createRoot(document.getElementById("root")).render(<ErrorBoundary><App /></ErrorBoundary>);
