/* === Itahuka Hotfix Pack vA (shims + dblclick + stream token query fallback + skin proof) === */
(function(){
  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', addBadge);
  else
function openBasicModal(title, html){
    try{
      if (window.PM && typeof window.PM.openModal === 'function'){
        window.PM.openModal({ title: title, body: html, buttons: [{label:'Close', action:'close'}] });
        return true;
      }
    }catch(e){}
    try{
      if (typeof window.openHtmlModal === 'function'){
        window.openHtmlModal("basic", String(title||""), String(html||""));
        return true;
      }
    }catch(e2){}
    return false;
  }


  // Lightweight HTML modal (independent of PM) so we can mount interactive editors reliably.
  function openHtmlModal(modalId, title, bodyHtml, onMount){
    try{ window.openHtmlModal = openHtmlModal; }catch(_){ }
    try{
      // Close existing with same id
      const existing = document.querySelector(`.lkModal[data-id="${modalId}"]`);
      if (existing) existing.remove();

      const overlay = document.createElement("div");
      overlay.className = "lkModal";
      overlay.dataset.id = modalId;
      overlay.innerHTML = `
        <div class="lkModal__backdrop"></div>
        <div class="lkModal__panel" role="dialog" aria-modal="true">
          <div class="lkModal__hdr">
            <div class="lkModal__title"></div>
            <button class="lkModal__x" title="Close">×</button>
          </div>
          <div class="lkModal__body"></div>
          <div class="lkModal__ftr">
            <button class="lkBtn lkBtn--secondary" data-act="close">Close</button>
          </div>
        </div>
      `;
      overlay.querySelector(".lkModal__title").textContent = String(title||"");
      overlay.querySelector(".lkModal__body").innerHTML = String(bodyHtml||"");

      function close(){ try{ overlay.remove(); }catch(_){ } }
      overlay.querySelector(".lkModal__backdrop").addEventListener("click", close);
      overlay.querySelector(".lkModal__x").addEventListener("click", close);
      overlay.querySelector('[data-act="close"]').addEventListener("click", close);
      document.body.appendChild(overlay);

      // basic esc close
      const onKey = (e)=>{ if (e.key === "Escape"){ close(); document.removeEventListener("keydown", onKey); } };
      document.addEventListener("keydown", onKey);

      try{ onMount && onMount(overlay); }catch(_){}
      return true;
    }catch(e){
      console.warn("openHtmlModal failed", e);
      return false;
    }
  }

  // Ensure openFxSettings exists and is global
  if (typeof window.openFxSettings !== 'function'){
    window.openFxSettings = function(kind, payload){
      var k = String(kind||'settings');
      var p = payload || {};

      
      if (k === "guides"){ try{ openGuidesSettingsModal(); return; }catch(_){ /* fallback below */ } }
// If guides passed as a string, expand to real guides object when available
      if (k === 'guides' && (typeof p === 'string' || typeof p === 'number')) {
        if (window.__GUIDES_SETTINGS && typeof window.__GUIDES_SETTINGS === 'object') p = window.__GUIDES_SETTINGS;
        else p = { preset: String(p), safeArea: true, ruler: true, grid: true, aspect: "16:9", note: "Define window.__GUIDES_SETTINGS to persist real guide settings." };
      }
      var pretty = '';
      try { pretty = JSON.stringify(p, null, 2); } catch(e){ pretty = String(p); }
      openBasicModal(k.toUpperCase() + ' Settings',
        '<pre style="margin:0;padding:12px;white-space:pre-wrap;word-break:break-word;">' +
        pretty.replace(/[&<>]/g, function(c){ return ({'&':'&amp;','<':'&lt;','>':'&gt;'}[c]); }) +
        '</pre>');
    };
  }


  // ===== v18: Real Guides Settings Modal (persistent + applies to canvas guides) =====
  function __loadGuidesSettings(){
    try{
      const raw = localStorage.getItem('itahuka_guides_settings');
      if (raw){
        const j = JSON.parse(raw);
        if (j && typeof j === 'object') return j;
      }
    }catch(_){}
    return null;
  }

  function __saveGuidesSettings(s){
    try{ localStorage.setItem('itahuka_guides_settings', JSON.stringify(s||{})); }catch(_){}
  }

  function __ensureGuidesDefaults(s){
    const d = { standard:'16:9', safe:0.10, grid:true, ruler:true, pixels:false };
    s = (s && typeof s==='object') ? s : {};
    if (!s.standard) s.standard = d.standard;
    if (typeof s.safe !== 'number') s.safe = d.safe;
    if (typeof s.grid !== 'boolean') s.grid = d.grid;
    if (typeof s.ruler !== 'boolean') s.ruler = d.ruler;
    if (typeof s.pixels !== 'boolean') s.pixels = d.pixels;
    return s;
  }

  function applyGuidesSettings(s){
    try{
      window.__GUIDES_SETTINGS = s;
      // guidesState is used by drawGuides(); keep both in sync if present
      if (typeof window.guidesState === 'object' && window.guidesState){
        window.guidesState.standard = s.standard;
        window.guidesState.safe = s.safe;
        window.guidesState.grid = s.grid;
        window.guidesState.ruler = s.ruler;
        window.guidesState.pixels = s.pixels;
      } else if (typeof guidesState === 'object'){
        guidesState.standard = s.standard;
        guidesState.safe = s.safe;
        guidesState.grid = s.grid;
        guidesState.ruler = s.ruler;
        guidesState.pixels = s.pixels;
      }
      if (typeof updatePreviewGuides === 'function') updatePreviewGuides();
    }catch(_){}
  }

  function openGuidesSettingsModal(){
    const cur = __ensureGuidesDefaults(window.__GUIDES_SETTINGS || __loadGuidesSettings());
    // build modal HTML
    const safePct = Math.round(Math.max(0, Math.min(0.20, Number(cur.safe||0))) * 100);
    const html = `
      <div class="fxForm">
        <div class="fxRow">
          <label>Standard</label>
          <select id="gs_standard">
            <option value="16:9">16:9</option>
            <option value="9:16">9:16</option>
            <option value="1:1">1:1</option>
            <option value="4:3">4:3</option>
          </select>
        </div>

        <div class="fxRow">
          <label>Safe area</label>
          <div style="display:flex;align-items:center;gap:10px;">
            <input id="gs_safe" type="range" min="0" max="20" value="${safePct}" style="flex:1;">
            <span id="gs_safe_v" style="min-width:44px;text-align:right;">${safePct}%</span>
          </div>
        </div>

        <div class="fxRow fxChecks">
          <label><input id="gs_grid" type="checkbox"> Grid (10×10)</label>
          <label><input id="gs_ruler" type="checkbox"> Ruler ticks</label>
          <label><input id="gs_pixels" type="checkbox"> Pixel box</label>
        </div>

        <div class="fxRow" style="justify-content:flex-end; gap:10px;">
          <button class="miniBtn" id="gs_cancel" type="button">Cancel</button>
          <button class="miniBtn danger" id="gs_reset" type="button">Reset</button>
          <button class="miniBtn" id="gs_save" type="button">Save</button>
        </div>
      </div>
    `;

    openBasicModal('GUIDES Settings', html);

    // init
    const std = document.getElementById('gs_standard');
    const safe = document.getElementById('gs_safe');
    const safeV = document.getElementById('gs_safe_v');
    const grid = document.getElementById('gs_grid');
    const ruler = document.getElementById('gs_ruler');
    const pixels = document.getElementById('gs_pixels');

    if (std) std.value = String(cur.standard||'16:9');
    if (grid) grid.checked = !!cur.grid;
    if (ruler) ruler.checked = !!cur.ruler;
    if (pixels) pixels.checked = !!cur.pixels;

    if (safe){
      safe.addEventListener('input', ()=>{ if(safeV) safeV.textContent = safe.value + '%'; });
    }

    const close = ()=>{ try{ closeBasicModal(); }catch(_){} };

    const build = ()=>{
      const s = {
        standard: std ? std.value : '16:9',
        safe: (safe ? (Number(safe.value)/100) : 0),
        grid: !!(grid && grid.checked),
        ruler: !!(ruler && ruler.checked),
        pixels: !!(pixels && pixels.checked),
      };
      return __ensureGuidesDefaults(s);
    };

    const btnSave = document.getElementById('gs_save');
    const btnCancel = document.getElementById('gs_cancel');
    const btnReset = document.getElementById('gs_reset');

    if (btnCancel) btnCancel.onclick = close;
    if (btnReset) btnReset.onclick = ()=>{
      const s = __ensureGuidesDefaults(null);
      __saveGuidesSettings(s);
      applyGuidesSettings(s);
      close();
    };
    if (btnSave) btnSave.onclick = ()=>{
      const s = build();
      __saveGuidesSettings(s);
      applyGuidesSettings(s);
      close();
    };
  }

  // Make sure guides settings are applied on load if stored
  try{
    const stored = __loadGuidesSettings();
    if (stored) applyGuidesSettings(__ensureGuidesDefaults(stored));
  }catch(_){}


  // Guard missing overlay updater
  if (typeof window.updateOverlaysAfterToggle !== 'function'){
    window.updateOverlaysAfterToggle = function(){};
  }
  // Guard missing stream modal opener
  if (typeof window.openStreamModal !== 'function'){
    window.openStreamModal = function(){
      openBasicModal('STREAM', 'Stream UI is not wired in this build.');
    };
  }

  // STREAM API token fallback: if your host strips Authorization, add token query to fetch.
  // We monkeypatch window.fetch to append token ONLY for stream_api.php calls.
  try{
    var origFetch = window.fetch;
    window.fetch = function(input, init){
      try{
        var url = (typeof input === 'string') ? input : (input && input.url) ? input.url : '';
        if (url && url.indexOf('stream_api.php') !== -1){
          var tok = '';
          try{
            tok = (window.STREAM_API_TOKEN || window.streamApiToken || '').toString().trim();
          }catch(e){}
          if (!tok){
            // attempt to read from inline debug object if present
            if (window.__STREAM_CFG && window.__STREAM_CFG.token) tok = String(window.__STREAM_CFG.token).trim();
          }
          if (tok && url.indexOf('token=') === -1){
            url += (url.indexOf('?')>=0 ? '&' : '?') + 'token=' + encodeURIComponent(tok);
            if (typeof input === 'string') input = url;
            else input = new Request(url, input);
          }
        }
      }catch(e){}
      return origFetch(input, init);
    };
  }catch(e){}
})();
;
(function(){
  // --------- Helpers ----------
  const $ = (s, r=document) => r.querySelector(s);
  const esc = (s) => (s ?? '').toString().replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));

  // Prefer server-backed storage, but keep a local fallback.
  const LS_KEY = "stream_ui_v3_cfg";
  const LS_CH = "stream_ui_v3_channels";
  const defaultCfg = {
    endpoint: (window.STREAM_API_URL || "https://studio.itahukamedia.com/stream_api.php"),
    token: (window.STREAM_API_TOKEN || ""),
    input: (window.STREAM_INPUT_URL || "rtmp://mtx.itahukamedia.com:1935"),
    pollMs: 2000,
    timeoutMs: 12000
  };
  function loadJson(k, fb){ try{ return JSON.parse(localStorage.getItem(k) || "") ?? fb; }catch(_){ return fb; } }
  function saveJson(k, v){ try{ localStorage.setItem(k, JSON.stringify(v)); }catch(_){ } }

  let cfg = Object.assign({}, defaultCfg, loadJson(LS_KEY, {}));
  let localChannels = loadJson(LS_CH, []);

  // --------- DOM ----------
  const modal = $("#streamModalV3");
  const meta = $("#stv3Meta");
  const list = $("#stv3List");
  const logEl = $("#stv3Log");

  const endpointEl = $("#stv3Endpoint");
  const tokenEl = $("#stv3Token");
  const inputEl = $("#stv3Input");
  const pollEl = $("#stv3Poll");
  const timeoutEl = $("#stv3Timeout");

  const btnClose = $("#stv3Close");
  const btnSave = $("#stv3Save");
  const btnTestApi = $("#stv3TestApi");
  const btnRefresh = $("#stv3Refresh");
  const btnAdd = $("#stv3Add");
  const btnStopAll = $("#stv3StopAll");
  const btnCopy = $("#stv3Copy");
  const btnClear = $("#stv3Clear");

  let pollTimer = null;
  let busy = new Set();

  function nowStamp(){
    const d = new Date();
    const p=(n)=>String(n).padStart(2,"0");
    return `${d.getFullYear()}-${p(d.getMonth()+1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
  }
  function log(level, msg){
    const line = `[${nowStamp()}] ${level} ${msg}`;
    logEl.textContent += (logEl.textContent ? "\n" : "") + line;
    logEl.scrollTop = logEl.scrollHeight;
    try{ (level==="ERR" ? console.error : console.log)(line); }catch(_){}
  }
  // ---------------------------------------------------------
  // STV3 real modal (replaces prompt/alert/confirm)
  // ---------------------------------------------------------
  function stv3CopyText(txt){
    try{
      if(navigator.clipboard && navigator.clipboard.writeText){
        navigator.clipboard.writeText(String(txt||""));
        return true;
      }
    }catch(e){}
    try{
      const ta = document.createElement("textarea");
      ta.value = String(txt||"");
      ta.style.position = "fixed";
      ta.style.left = "-9999px";
      ta.style.top = "-9999px";
      document.body.appendChild(ta);
      ta.select();
      document.execCommand("copy");
      ta.remove();
      return true;
    }catch(e){}
    return false;
  }

  function stv3OpenModal({title, subtitle, bodyHTML, onMount}){
    return new Promise((resolve)=>{
      const back = document.createElement("div");
      back.className = "stv3Backdrop";
      back.innerHTML = `
        <div class="stv3Modal" role="dialog" aria-modal="true">
          <div class="stv3ModalHdr">
            <div class="stv3ModalHdrText">
              <div class="stv3ModalTitle"></div>
              <div class="stv3ModalSub"></div>
            </div>
            <button class="stv3IconBtn" type="button" data-x aria-label="Close">✕</button>
          </div>
          <div class="stv3ModalBody"></div>
        </div>
      `;
      const modal = back.querySelector(".stv3Modal");
      back.querySelector(".stv3ModalTitle").textContent = title || "Dialog";
      const sub = back.querySelector(".stv3ModalSub");
      if(subtitle){ sub.textContent = subtitle; sub.style.display="block"; } else { sub.style.display="none"; }
      back.querySelector(".stv3ModalBody").innerHTML = bodyHTML || "";
      const close = (v)=>{ try{ back.remove(); }catch(e){} resolve(v); };
      back.addEventListener("click",(e)=>{ if(e.target===back) close(null); });
      back.querySelector("[data-x]").addEventListener("click",()=>close(null));
      document.body.appendChild(back);
      if(typeof onMount === "function"){ onMount({back, modal, close}); }
    });
  }

  async function stv3ConfirmModal({title, message, okText, cancelText}){
    return await stv3OpenModal({
      title: title || "Confirm",
      bodyHTML: `
        <div class="stv3P">${escapeHtml(message||"Are you sure?")}</div>
        <div class="stv3RowBtns">
          <button class="stv3Btn" data-cancel type="button">${escapeHtml(cancelText||"Cancel")}</button>
          <button class="stv3Btn stv3BtnPrimary" data-ok type="button">${escapeHtml(okText||"OK")}</button>
        </div>
      `,
      onMount: ({back, close})=>{
        back.querySelector("[data-cancel]").addEventListener("click",()=>close(false));
        back.querySelector("[data-ok]").addEventListener("click",()=>close(true));
        setTimeout(()=>{ back.querySelector("[data-ok]")?.focus?.(); },0);
      }
    });
  }

  async function stv3ActionModal({title, subtitle, name, canReveal}){
    return await stv3OpenModal({
      title: title || "Destination",
      subtitle,
      bodyHTML: `
        <div class="stv3RowBtns stv3RowBtnsWrap">
          <button class="stv3Btn stv3BtnPrimary" data-act="test" type="button">Test</button>
          <button class="stv3Btn" data-act="rename" type="button">Rename</button>
          <button class="stv3Btn stv3BtnDanger" data-act="delete" type="button">Delete</button>
          <button class="stv3Btn" data-act="reveal" type="button"${canReveal? "" : " disabled"}>Reveal</button>
        </div>
        <div class="stv3Field">
          <label class="stv3Lbl">New name (for Rename)</label>
          <input class="stv3Input" type="text" data-rename value="${escapeAttr(name||"")}" placeholder="Type new name…" />
          <div class="stv3Hint">Use the Rename button above after editing the name.</div>
        </div>
        <div class="stv3RowBtns">
          <button class="stv3Btn" data-close type="button">Close</button>
        </div>
      `,
      onMount: ({back, close})=>{
        const inp = back.querySelector("[data-rename]");
        back.querySelectorAll("[data-act]").forEach((b)=>{
          b.addEventListener("click", ()=>{
            const a = b.getAttribute("data-act");
            if(a==="rename"){
              const v = (inp?.value||"").trim();
              close({action:"rename", value:v});
            }else{
              close({action:a});
            }
          });
        });
        back.querySelector("[data-close]").addEventListener("click",()=>close(null));
        setTimeout(()=>{ inp?.focus?.(); },0);
      }
    });
  }

  async function stv3RevealModal({title, key}){
    const k = String(key||"");
    return await stv3OpenModal({
      title: title || "Stream key",
      bodyHTML: `
        <div class="stv3Field">
          <label class="stv3Lbl">Key</label>
          <textarea class="stv3Textarea" readonly data-key>${escapeHtml(k)}</textarea>
          <div class="stv3RowBtns">
            <button class="stv3Btn stv3BtnPrimary" data-copy type="button">Copy</button>
            <button class="stv3Btn" data-close type="button">Close</button>
          </div>
        </div>
      `,
      onMount: ({back, close})=>{
        back.querySelector("[data-copy]").addEventListener("click",()=>{
          stv3CopyText(k);
        });
        back.querySelector("[data-close]").addEventListener("click",()=>close(true));
      }
    });
  }

  async function stv3AddDestModal(){
    return await stv3OpenModal({
      title: "Add destination",
      bodyHTML: `
        <div class="stv3Field">
          <label class="stv3Lbl">Name</label>
          <input class="stv3Input" type="text" data-name placeholder="e.g. YOLOCAST" />
        </div>
        <div class="stv3Field">
          <label class="stv3Lbl">RTMP URL</label>
          <input class="stv3Input" type="text" data-url placeholder="rtmp://host/app" />
          <div class="stv3Hint">Must start with rtmp:// or rtmps://</div>
        </div>
        <div class="stv3Field">
          <label class="stv3Lbl">Stream key</label>
          <input class="stv3Input" type="text" data-key placeholder="optional" />
        </div>
        <div class="stv3Err" data-err style="display:none"></div>
        <div class="stv3RowBtns">
          <button class="stv3Btn" data-cancel type="button">Cancel</button>
          <button class="stv3Btn stv3BtnPrimary" data-add type="button">Add</button>
        </div>
      `,
      onMount: ({back, close})=>{
        const q = (sel)=>back.querySelector(sel);
        const err = q("[data-err]");
        const showErr=(msg)=>{ if(!err) return; err.textContent = msg||""; err.style.display = msg? "block":"none"; };
        q("[data-cancel]").addEventListener("click",()=>close(null));
        q("[data-add]").addEventListener("click",()=>{
          const name = (q("[data-name]")?.value||"").trim();
          const url  = (q("[data-url]")?.value||"").trim();
          const key  = (q("[data-key]")?.value||"").trim();
          if(!name){ showErr("Name is required."); return; }
          if(!url || !/^rtmps?:\/\//i.test(url)){ showErr("RTMP URL must start with rtmp:// or rtmps://"); return; }
          close({name, rtmp_url:url, stream_key:key});
        });
        setTimeout(()=>{ q("[data-name]")?.focus?.(); },0);
      }
    });
  }

  function escapeHtml(s){
    return String(s??"")
      .replaceAll("&","&amp;")
      .replaceAll("<","&lt;")
      .replaceAll(">","&gt;")
      .replaceAll('"',"&quot;")
      .replaceAll("'","&#039;");
  }
  function escapeAttr(s){ return escapeHtml(s).replaceAll("`","&#096;"); }


  function saveCfg(){
    cfg.endpoint = (endpointEl.value || "").trim() || defaultCfg.endpoint;
    cfg.token = (tokenEl.value || "").trim();
    cfg.input = (inputEl.value || "").trim() || defaultCfg.input;
    cfg.pollMs = Math.max(0, Number(pollEl.value)||0);
    cfg.timeoutMs = Math.max(1000, Number(timeoutEl.value)||defaultCfg.timeoutMs);
    saveJson(LS_KEY, cfg);
  }
  
  function currentRoomId(){
    try{
      if (typeof ROOM === 'string' && ROOM.trim()) return ROOM.trim();
    }catch(e){}
    const w = (typeof window !== 'undefined') ? window : {};
    const cand = [
      w.ROOM, w.room, w.room_id,
      w.project_room, w.projectRoom, w.PROJECT_ROOM,
      w.CURRENT_ROOM, w.currentRoom,
      w.project_id, w.projectId
    ].find(v => typeof v === 'string' && v.trim());
    return (cand && cand.trim()) ? cand.trim() : 'program';
  }

  function computeDefaultInput(){
    const room = currentRoomId();
    // Prefer docker DNS name by default; override by setting window.MTX_RTMP_HOST
    const w = (typeof window !== 'undefined') ? window : {};
    const host = (typeof w.MTX_RTMP_HOST === 'string' && w.MTX_RTMP_HOST.trim()) ? w.MTX_RTMP_HOST.trim() : 'mediamtx';
    // If host is already an rtmp URL, append /room
    if (/^rtmps?:\/\//i.test(host)) return host.replace(/\/+$/,'') + '/' + room;
    return 'rtmp://' + host + ':1935/' + room;
  }

  function isLegacyOrWrongInput(input){
    if (!input || typeof input !== 'string') return true;
    const room = currentRoomId();
    const s = input.trim();
    // legacy hardcoded program
    if (/\/(program)\s*$/i.test(s)) return true;
    // points to another path (project switched)
    const m = s.match(/:1935\/(.+)$/i);
    if (m && m[1] && m[1] !== room) return true;
    return false;
  }

function loadCfgToUI(){
    endpointEl.value = cfg.endpoint || defaultCfg.endpoint;
    tokenEl.value = cfg.token || "";
    inputEl.value = cfg.input || defaultCfg.input;
    pollEl.value = String(Number(cfg.pollMs)||2000);
    timeoutEl.value = String(Number(cfg.timeoutMs)||12000);
  }

  function getUserId(){
    try{
      if (window.ITAHUKA_PROJECT && ITAHUKA_PROJECT.user_id) return ITAHUKA_PROJECT.user_id;
    }catch(_){}
    return null;
  }
  function getProjectId(){
    try{
      if (window.ITAHUKA_PROJECT && ITAHUKA_PROJECT.id) return ITAHUKA_PROJECT.id;
    }catch(_){}
    return null;
  }

  // Always call stream_api.php with ?action=... for shared hosting compatibility.
  async function api(action, payload){
    payload = payload || {};
    if (!payload.user_id) payload.user_id = getUserId();
    if (!payload.project_id) payload.project_id = getProjectId();
    if (!payload.room) payload.room = (typeof ROOM !== "undefined" ? ROOM : "");
    if (!payload.input_url) payload.input_url = (cfg.input || defaultCfg.input);

    const ep = (cfg.endpoint || defaultCfg.endpoint).trim();
    const url = ep.includes("?") ? `${ep}&action=${encodeURIComponent(action)}` : `${ep}?action=${encodeURIComponent(action)}`;

    const headers = { "Content-Type":"application/json" };
    const tok = (cfg.token || "").trim();
    if (tok) headers["Authorization"] = "Bearer " + tok;

    const ctl = new AbortController();
    const to = setTimeout(()=>ctl.abort(), Math.max(1000, Number(cfg.timeoutMs)||12000));
    try{
      const res = await fetch(url, { method:"POST", headers, body: JSON.stringify(payload), signal: ctl.signal });
      const txt = await res.text();
      let data = null;
      try{ data = JSON.parse(txt); }catch(_){ data = { ok:false, raw: txt }; }
      if (!res.ok || data?.ok === false){
        const msg = data?.message || data?.error || ("HTTP " + res.status);
        throw new Error(msg);
      }
      return data;
    } finally {
      clearTimeout(to);
    }
  }

  // Icons (inline SVG)
  function iconFor(platform){
    const p = String(platform||"generic").toLowerCase();
    if (p.includes("youtube")) return `<svg viewBox="0 0 24 24"><path d="M10 15.5V8.5l6 3.5-6 3.5z"/><path d="M21.6 7.2s-.2-1.5-.8-2.2c-.8-.8-1.7-.8-2.1-.9C15.9 3.8 12 3.8 12 3.8h0s-3.9 0-6.7.3c-.4 0-1.3.1-2.1.9-.6.7-.8 2.2-.8 2.2S2 9 2 10.8v2.4c0 1.8.4 3.6.4 3.6s.2 1.5.8 2.2c.8.8 1.9.8 2.4.9 1.7.2 6.4.3 6.4.3s3.9 0 6.7-.3c.4 0 1.3-.1 2.1-.9.6-.7.8-2.2.8-2.2s.4-1.8.4-3.6v-2.4c0-1.8-.4-3.6-.4-3.6z" fill="none" stroke="currentColor" stroke-width="1.2"/></svg>`;
    if (p.includes("facebook")) return `<svg viewBox="0 0 24 24"><path d="M13.5 22v-8h2.7l.4-3H13.5V9.2c0-.9.3-1.5 1.6-1.5h1.6V5.1c-.8-.1-1.8-.2-2.8-.2-2.8 0-4.7 1.7-4.7 4.8V11H6v3h3v8h4.5z"/></svg>`;
    if (p.includes("twitch")) return `<svg viewBox="0 0 24 24"><path d="M4 3h17v10l-4 4h-4l-2 2H8v-2H4V3zm3 4v6h3V7H7zm7 0v6h3V7h-3z"/></svg>`;
    return `<svg viewBox="0 0 24 24"><path d="M12 3c-3.9 0-7 3.1-7 7 0 5.2 7 11 7 11s7-5.8 7-11c0-3.9-3.1-7-7-7zm0 9.5c-1.4 0-2.5-1.1-2.5-2.5S10.6 7.5 12 7.5s2.5 1.1 2.5 2.5S13.4 12.5 12 12.5z"/></svg>`;
  }

  function badge(state){
    const s = String(state||"IDLE").toUpperCase();
    if (s === "RUNNING") return `<span class="stv3Badge run">LIVE</span>`;
    if (s === "ERROR") return `<span class="stv3Badge err">ERROR</span>`;
    if (s === "STARTING" || s === "STOPPING") return `<span class="stv3Badge idle">${esc(s)}</span>`;
    return `<span class="stv3Badge idle">IDLE</span>`;
  }

  // Pull channels from DB (preferred). Fallback to local.
  async function fetchChannels(){
    try{
      const r = await api("channels", {});
      if (Array.isArray(r?.channels)) return r.channels;
    }catch(err){
      // If API doesn't support channels yet, fallback.
      log("INFO", "Channels endpoint not available, using local list.");
    }
    return localChannels;
  }

  async function fetchStatus(ids){
    try{
      const r = await api("status", { channels: ids.map(id=>({id})) });
      const map = {};
      const arr = Array.isArray(r?.channels) ? r.channels : [];
      for (const it of arr){
        if (!it || !it.id) continue;
        map[it.id] = { state: it.state || "IDLE", message: it.message || "" };
      }
      return map;
    }catch(err){
      log("ERR", "STATUS: " + (err?.message || err));
      return {};
    }
  }

  function renderList(channels, statusMap){
    list.innerHTML = "";
    if (!channels || !channels.length){
      const d = document.createElement("div");
      d.style.opacity = ".65";
      d.textContent = "No destinations yet. Click + Add.";
      list.appendChild(d);
      return;
    }

    for (const ch of channels){
      const st = statusMap && statusMap[ch.id] ? statusMap[ch.id] : { state:"IDLE", message:"" };
      const state = String(st.state||"IDLE").toUpperCase();
      const isLive = (state === "RUNNING" || state === "STARTING");

      const item = document.createElement("div");
      item.className = "stv3Item";
      item.innerHTML = `
        <div class="stv3Left">
          <div class="stv3Icon">${iconFor(ch.platform || ch.name || "generic")}</div>
          <div class="stv3Txt">
            <div class="stv3Name">${esc(ch.name || "Channel")}</div>
            <div class="stv3Sub">${esc(ch.platform ? ch.platform : "Destination")}</div>
          </div>
        </div>

        <div class="stv3Right">
          ${badge(state)}
          <label class="stv3Switch" title="Toggle Live">
            <input type="checkbox" data-id="${esc(ch.id)}" ${isLive ? "checked":""} />
            <span class="track"><span class="knob"></span></span>
          </label>
          <button class="stv3More" data-act="more" data-id="${esc(ch.id)}">•••</button>
        </div>
      `;
      list.appendChild(item);
    }

    // Switch handler
    list.querySelectorAll('.stv3Switch input[type="checkbox"]').forEach(inp=>{
      inp.addEventListener("change", async ()=>{
        const id = inp.getAttribute("data-id");
        if (!id) return;
        if (busy.has(id)){ inp.checked = !inp.checked; return; }
        busy.add(id);
        try{
          const turnOn = !!inp.checked;
          if (turnOn){
            log("INFO", "START " + id);
            await api("start", { channel_id: id });
            log("INFO", "STARTED " + id);
          } else {
            log("INFO", "STOP " + id);
            await api("stop", { channel_id: id });
            log("INFO", "STOPPED " + id);
          }
        } catch(err){
          log("ERR", (inp.checked ? "START":"STOP") + " failed: " + (err?.message || err));
          inp.checked = !inp.checked;
        } finally {
          busy.delete(id);
          await refresh(); // refresh status
        }
      });
    });

    // More menu (keeps URL/KEY hidden by default)
    list.querySelectorAll('button[data-act="more"]').forEach(btn=>{
      btn.addEventListener("click", async ()=>{
        const id = btn.getAttribute("data-id");
        if (!id) return;
        
        const res = await stv3ActionModal({
          title: (ch?.name || ch?.platform || "Destination"),
          subtitle: (ch?.platform ? String(ch.platform).toUpperCase() : ""),
          name: (ch?.name || ""),
          canReveal: !!(ch && (ch.rtmp_key || ch.stream_key))
        });
        if(!res) return;

        const action = res.action;

        if(action==="test"){
          const r = await api("test",{id});
          log(r.ok ? `TEST OK: ${ch.name}` : `ERR TEST: ${r.err||"failed"}`, r.ok? "ok":"err");
        }

        if(action==="rename"){
          const newName = String(res.value||"").trim();
          if(!newName){ log("ERR rename: missing name","err"); return; }
          const r = await api("rename",{id, name:newName});
          log(r.ok ? `RENAMED: ${newName}` : `ERR rename: ${r.err||"failed"}`, r.ok? "ok":"err");
        }

        if(action==="delete"){
          const ok = await stv3ConfirmModal({
            title: "Delete destination",
            message: `Delete "${ch.name||ch.platform||"destination"}"?`,
            okText: "Delete",
            cancelText: "Cancel"
          });
          if(!ok) return;
          const r = await api("delete",{id});
          log(r.ok ? `DELETED: ${ch.name}` : `ERR delete: ${r.err||"failed"}`, r.ok? "ok":"err");
        }

        if(action==="reveal"){
          const secret = (ch && (ch.rtmp_key || ch.stream_key)) ? (ch.rtmp_key || ch.stream_key) : "";
          if(!secret){ log("ERR reveal: not found","err"); return; }
          await stv3RevealModal({title:`${ch.name||"Key"}`, key: secret});
        }

        await refresh();
});
            await stv3OpenModal({title:"Reveal", bodyHTML:`<div class="stv3Field"><label class="stv3Lbl">RTMP URL</label><input class="stv3Input" readonly value="${escapeAttr(r?.rtmp_url||"(none)")}" /><label class="stv3Lbl" style="margin-top:10px">KEY</label><textarea class="stv3Textarea" readonly>${escapeHtml(r?.stream_key||r?.key||"(none)")}</textarea><div class="stv3RowBtns"><button class="stv3Btn stv3BtnPrimary" type="button" data-copy>Copy Key</button><button class="stv3Btn" type="button" data-close>Close</button></div></div>`, onMount:({back,close})=>{back.querySelector("[data-close]").addEventListener("click",()=>close(true));back.querySelector("[data-copy]").addEventListener("click",()=>stv3CopyText(r?.stream_key||r?.key||""));}});
          }catch(err){
            log("ERR", "Reveal failed: " + (err?.message || err));
          }
          return;
        }
      });
    });
  }

  async function refresh(){
    saveCfg();
    const channels = await fetchChannels();
    // persist fallback list if server isn't available
    localChannels = Array.isArray(channels) ? channels : [];
    saveJson(LS_CH, localChannels);

    const ids = localChannels.map(c=>c.id).filter(Boolean);
    const statusMap = ids.length ? await fetchStatus(ids) : {};
    renderList(localChannels, statusMap);
  }

  function open(){
    loadCfgToUI();
    meta.textContent = "ROOM " + String((typeof ROOM !== "undefined" ? ROOM : "—"));
    modal.classList.add("show");
    modal.setAttribute("aria-hidden","false");
    refresh().catch(err=>log("ERR", err?.message || err));
    startPolling();
  }
  function close(){
    modal.classList.remove("show");
    modal.setAttribute("aria-hidden","true");
    stopPolling();
  }

  function startPolling(){
    stopPolling();
    const ms = Math.max(0, Number(cfg.pollMs)||0);
    if (!ms) return;
    pollTimer = setInterval(()=>refresh().catch(()=>{}), ms);
  }
  function stopPolling(){
    if (pollTimer){ clearInterval(pollTimer); pollTimer = null; }
  }

  // Wire buttons
  btnClose.addEventListener("click", close);
  modal.addEventListener("click", (e)=>{ if (e.target === modal) close(); });
  document.addEventListener("keydown", (e)=>{ if (e.key === "Escape" && modal.classList.contains("show")) close(); });

  btnSave.addEventListener("click", ()=>{
    saveCfg();
    log("INFO", "Saved stream config.");
    refresh().catch(()=>{});
    startPolling();
  });
  btnTestApi.addEventListener("click", async ()=>{
    saveCfg();
    try{
      log("INFO","TEST API");
      const r = await api("healthz", {});
      log("INFO", "API OK");
    }catch(err){
      log("ERR", "API FAIL: " + (err?.message || err));
    }
  });
  btnRefresh.addEventListener("click", ()=>refresh().catch(()=>{}));

  btnAdd.addEventListener("click", async ()=>{
    // user-friendly add flow (does not show URL/key unless filling)
    
        const d = await stv3AddDestModal();
        if(!d) return;
        const r = await api("add", {
          name: d.name,
          rtmp_url: d.rtmp_url,
          stream_key: d.stream_key || ""
        });
        log(r.ok ? `ADDED: ${d.name}` : `ERR add: ${r.err||"failed"}`, r.ok? "ok":"err");
        await refresh();
;


localStorage.removeItem("coproducer_stream_cfg_v2");
localStorage.removeItem("coproducer_stream_channels_v2");