<!doctype html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <%- include('partials/favicon') %>
    <title>프롬프트 라이브러리 - NCue</title>
    <link rel="stylesheet" href="/public/styles.css" />
  </head>
  <body>
    <div class="app-shell">
      <%- include('partials/nav', { activeMenu: typeof activeMenu !== 'undefined' ? activeMenu : 'prompts', adminMode: typeof adminMode !== 'undefined' ? adminMode : false, opsUserEmail: typeof opsUserEmail !== 'undefined' ? opsUserEmail : false }) %>
      <div class="content-area">
        <header class="topbar">
          <h1>프롬프트</h1>
          <a class="top-action-link" href="/ai-explore">AI 목록</a>
        </header>
        <main class="container container-ai-full prompt-lib">
          <a href="/ai-explore" class="prompts-back" aria-label="AI 탐색으로 돌아가기">← AI</a>

          <section class="prompts-hero">
            <h1>프롬프트 라이브러리</h1>
            <p class="prompts-lead">
              공식 템플릿을 복사하거나, 팀이 공유한 프롬프트를 고르고 좋아요로 인기를 표시하세요. 워크플로 도움으로 본인 업무에 맞는 지시문 초안을 만들 수 있습니다.
            </p>
            <% if (typeof openaiPolishAvailable !== "undefined" && !openaiPolishAvailable) { %>
            <p class="prompts-stats prompts-stats--info">「워크플로」의 일부 기능은 서버에 OpenAI 키가 있을 때 사용할 수 있습니다.</p>
            <% } else { %>
            <p class="prompts-stats">공식 <strong><%= (officialPrompts && officialPrompts.length) || 0 %></strong>종 · 팀 공유
              <% if (libData && libData.hasDb) { %><strong><%= (libData.community && libData.community.length) || 0 %></strong>건<% } else { %><span>DB 연결 시 활성</span><% } %>
            </p>
            <% } %>
          </section>

          <% if (!libData && (!officialPrompts || !officialPrompts.length)) { %>
          <section class="panel">
            <p class="empty">프롬프트 데이터를 불러오지 못했습니다.</p>
          </section>
          <% } else { %>
          <div class="prompt-lib-tabs" role="tablist" aria-label="프롬프트 메뉴">
            <button type="button" class="prompt-lib-tab is-active" data-tab="library" role="tab" aria-selected="true">라이브러리</button>
            <button type="button" class="prompt-lib-tab" data-tab="workflow" role="tab" aria-selected="false">워크플로 → 프롬프트</button>
            <button type="button" class="prompt-lib-tab" data-tab="submit" role="tab" aria-selected="false">공유하기</button>
            <button type="button" class="prompt-lib-tab" data-tab="mine" role="tab" aria-selected="false">내가 올린 것</button>
          </div>

          <p class="prompt-lib-login-hint" id="promptLibLoginHint" hidden>
            좋아요·공유·삭제는 <strong>회사 이메일(OPS) 로그인</strong> 후 사용할 수 있습니다.
          </p>

          <section class="panel prompt-lib-panel" data-panel="library">
            <div class="prompt-lib-toolbar">
              <div class="prompt-lib-filters">
                <label class="visually-hidden" for="promptFilterSource">출처</label>
                <select id="promptFilterSource" class="prompt-lib-select">
                  <option value="all">전체</option>
                  <option value="official">공식 템플릿만</option>
                  <option value="community" <%= libData && libData.hasDb ? "" : "disabled" %>>팀 공유만</option>
                </select>
                <label class="visually-hidden" for="promptSort">정렬</label>
                <select id="promptSort" class="prompt-lib-select">
                  <option value="popular">인기(좋아요)</option>
                  <option value="recent">최신</option>
                </select>
              </div>
              <div class="prompt-lib-search">
                <label class="visually-hidden" for="promptSearchQ">검색</label>
                <input type="search" id="promptSearchQ" class="prompt-lib-search-input" placeholder="제목·설명·태그 검색" autocomplete="off" />
              </div>
            </div>
            <% if (!libData || !libData.hasDb) { %>
            <p class="prompt-lib-db-hint">PostgreSQL이 연결되지 않은 환경입니다. 공식 템플릿과 복사만 사용할 수 있으며, 팀 공유·좋아요는 DB 연결 후 사용할 수 있습니다.</p>
            <% } %>

            <div class="prompts-layout prompt-lib-main">
              <div class="prompt-lib-list-wrap">
                <h2 class="prompts-grid-title">시나리오 · 공유</h2>
                <div class="prompts-grid prompt-lib-grid" id="promptCardList" role="list"></div>
                <p class="empty" id="promptListEmpty" hidden>조건에 맞는 항목이 없습니다.</p>
              </div>
              <div class="prompts-preview-panel prompt-lib-preview">
                <h2 id="previewTitle">프롬프트 미리보기</h2>
                <p class="prompts-preview-empty" id="previewEmpty">왼쪽에서 항목을 선택하세요.</p>
                <div id="previewActive" hidden>
                  <div class="prompts-preview-toolbar prompt-lib-preview-toolbar">
                    <button type="button" class="prompt-lib-like" id="promptLikeBtn" title="로그인 후 사용">
                      <span class="prompt-lib-like-icon" aria-hidden="true">♥</span>
                      <span id="promptLikeCount">0</span>
                    </button>
                    <button type="button" class="prompts-copy-btn" id="copyPromptBtn" disabled>클립보드에 복사</button>
                  </div>
                  <p class="prompt-lib-meta" id="previewMeta"></p>
                  <div class="prompt-lib-preview-files" id="previewFileWrap" hidden></div>
                  <label class="visually-hidden" for="promptBody">선택한 프롬프트 전문</label>
                  <textarea id="promptBody" class="prompts-body-textarea" readonly spellcheck="false" aria-describedby="previewHint"></textarea>
                  <p class="prompts-hint" id="previewHint">대괄호 [ ] 안은 상황에 맞게 바꾼 뒤 사용하세요.</p>
                </div>
              </div>
            </div>
          </section>

          <section class="panel prompt-lib-panel prompt-lib-panel--submit" data-panel="submit" hidden>
            <div class="prompt-lib-submit-hero">
              <div class="prompt-lib-submit-hero-icon" aria-hidden="true">✦</div>
              <h2>팀에 프롬프트 공개하기</h2>
              <p>동료들이 참고할 수 있도록 지시문과(선택) <strong>참고·결과 파일</strong>을 함께 올릴 수 있습니다.</p>
            </div>
            <p class="prompt-lib-policy">
              작성자는 로그인 이메일로 저장됩니다. <strong>민감·개인정보·기밀</strong>이 포함되지 않게 해 주세요. 첨부는 항목당 최대 5개, 파일당 20MB입니다.
            </p>
            <form id="promptSubmitForm" class="prompt-lib-form" novalidate enctype="multipart/form-data">
              <div class="prompt-lib-fieldset">
                <h3 class="prompt-lib-h3">기본 정보</h3>
                <div class="prompt-lib-input-row">
                  <label for="pcTitle" class="prompt-lib-label">제목 <span class="req">*</span></label>
                  <input type="text" id="pcTitle" name="title" class="prompt-lib-input" maxlength="500" required placeholder="예: 주간 CS 리포트 요약" />
                </div>
                <div class="prompt-lib-input-row prompt-lib-input-row--2col">
                  <div>
                    <label for="pcTag" class="prompt-lib-label">태그</label>
                    <input type="text" id="pcTag" name="tag" class="prompt-lib-input" maxlength="100" placeholder="예: CS, 요약" />
                  </div>
                  <div>
                    <label for="pcDesc" class="prompt-lib-label">한줄 설명</label>
                    <input type="text" id="pcDesc" name="description" class="prompt-lib-input" maxlength="2000" placeholder="이 프롬프트로 어떤 일을 하는지" />
                  </div>
                </div>
              </div>
              <div class="prompt-lib-fieldset">
                <h3 class="prompt-lib-h3">프롬프트 본문 <span class="req">*</span></h3>
                <p class="prompt-lib-hintline">채팅에 그대로 붙여 넣을 수 있는 <strong>지시문 전체</strong>를 적어 주세요.</p>
                <div class="prompt-lib-body-editor-wrap">
                  <div class="prompt-lib-body-toolbar" role="tablist" aria-label="프롬프트 본문 표시">
                    <button
                      type="button"
                      class="prompt-lib-body-mode-btn is-active"
                      id="pcBodyModeRaw"
                      role="tab"
                      aria-selected="true"
                      aria-controls="pcBody"
                    >
                      원문 보기
                    </button>
                    <button
                      type="button"
                      class="prompt-lib-body-mode-btn"
                      id="pcBodyModeMd"
                      role="tab"
                      aria-selected="false"
                      aria-controls="pcBodyMdPreview"
                    >
                      마크다운 보기
                    </button>
                  </div>
                  <textarea
                    id="pcBody"
                    name="body"
                    class="prompt-lib-textarea prompt-lib-textarea--body"
                    rows="14"
                    minlength="10"
                    required
                    placeholder="ChatGPT, Claude, 자사 채팅 등에 사용할 지시문을 작성합니다."
                    aria-label="프롬프트 본문"
                  ></textarea>
                  <div
                    id="pcBodyMdPreview"
                    class="prompt-lib-md-preview"
                    hidden
                    role="region"
                    aria-label="마크다운 미리보기"
                    tabindex="0"
                  >
                    <div class="prompt-lib-md-body" id="pcBodyMdInner"></div>
                  </div>
                </div>
              </div>
              <div class="prompt-lib-fieldset">
                <h3 class="prompt-lib-h3">첨부 (선택)</h3>
                <div class="prompt-lib-upload-grid">
                  <div class="prompt-lib-drop prompt-lib-drop--prompt">
                    <span class="prompt-lib-drop-eyebrow">PR</span>
                    <strong>프롬프트와 함께 쓰는 자료</strong>
                    <p>예: 참고 PDF, 사내 양식, 키워드 목록</p>
                    <input type="file" id="pcPromptFiles" name="promptFiles" class="prompt-lib-file-native" multiple />
                    <label for="pcPromptFiles" class="prompt-lib-file-btn">파일 선택</label>
                    <p class="prompt-lib-file-list" id="pcPromptFileNames" aria-live="polite"></p>
                  </div>
                  <div class="prompt-lib-drop prompt-lib-drop--result">
                    <span class="prompt-lib-drop-eyebrow">OUT</span>
                    <strong>결과(샘플) 파일</strong>
                    <p>예: AI로 만든 산출물 예시, 기대하는 출력 형식</p>
                    <input type="file" id="pcResultFiles" name="resultFiles" class="prompt-lib-file-native" multiple />
                    <label for="pcResultFiles" class="prompt-lib-file-btn">파일 선택</label>
                    <p class="prompt-lib-file-list" id="pcResultFileNames" aria-live="polite"></p>
                  </div>
                </div>
              </div>
              <div class="prompt-lib-submit-bar">
                <button type="submit" class="prompt-lib-submit-btn" id="pcSubmit">공개하기</button>
              </div>
              <p class="form-message" id="pcMsg" hidden></p>
            </form>
          </section>

          <section class="panel prompt-lib-panel prompt-lib-panel--workflow" data-panel="workflow" hidden>
            <div class="prompt-lib-wf-hero">
              <div class="prompt-lib-wf-hero-icon" aria-hidden="true">◇</div>
              <h2>워크플로 → 프롬프트</h2>
              <p>업무를 한눈에 정리한 뒤, <strong>한 덩어리 초안</strong>을 만듭니다. 필요하면 <strong>AI로 다듬기</strong>로 정리한 지시문을 얻을 수 있습니다.</p>
            </div>

            <aside class="prompt-lib-wf-api-note" role="note">
              <strong>동작 방식</strong>
              <ul>
                <li>
                  <span class="prompt-lib-wf-k">초안 합치기</span> — ①~④에 적은 내용을 이 브라우저에서만 이어 붙입니다. <em>인터넷 밖으로 보내지 않습니다</em> (OpenAI/ChatGPT API 호출 없음).<% if (typeof openaiPolishAvailable !== "undefined" && !openaiPolishAvailable) { %>
                  <br /><span class="prompt-lib-wf-warn">「AI로 다듬기」는 현재 환경에서 API 키가 없어 사용할 수 없습니다.</span><% } %>
                </li>
              </ul>
            </aside>

            <div class="prompt-lib-wf-body">
              <div class="prompt-lib-wf-fieldset">
                <h3 class="prompt-lib-wf-h3">①~④단계 입력</h3>
                <div class="prompt-lib-wf-row">
                  <label for="wfGoal" class="prompt-lib-wf-label"
                    >맡기려는 일 <span class="prompt-lib-wf-badge">1</span> <span class="req">· 한 문장</span></label
                  >
                  <input type="text" id="wfGoal" class="prompt-lib-wf-input" placeholder="예: 거래처 견적 이메일 초안" autocomplete="off" />
                </div>
                <div class="prompt-lib-wf-row">
                  <label for="wfIn" class="prompt-lib-wf-label"
                    >AI에게 줄 자료 <span class="prompt-lib-wf-badge">2</span></label
                  >
                  <textarea id="wfIn" class="prompt-lib-wf-textarea" rows="4" placeholder="붙일 메모, 표, 키워드…" spellcheck="false"></textarea>
                </div>
                <div class="prompt-lib-wf-row">
                  <label for="wfOut" class="prompt-lib-wf-label"
                    >원하는 출력·톤 <span class="prompt-lib-wf-badge">3</span></label
                  >
                  <textarea id="wfOut" class="prompt-lib-wf-textarea" rows="3" placeholder="불릿, 표, 격식체, 분량…" spellcheck="false"></textarea>
                </div>
                <div class="prompt-lib-wf-row">
                  <label for="wfRule" class="prompt-lib-wf-label"
                    >제약·주의 <span class="prompt-lib-wf-badge">4</span> <span class="opt">선택</span></label
                  >
                  <input type="text" id="wfRule" class="prompt-lib-wf-input" placeholder="개인정보 제외, 한국어, …" autocomplete="off" />
                </div>
              </div>

              <div class="prompt-lib-wf-fieldset prompt-lib-wf-fieldset--actions">
                <h3 class="prompt-lib-wf-h3">가공하기</h3>
                <p class="prompt-lib-wf-lead">먼저 <strong>초안 합치기</strong>로 아래에 텍스트를 만든 뒤, 필요할 때만 <strong>AI로 다듬기</strong>를 누르세요.</p>
                <div class="prompt-lib-wf-actions">
                  <div class="prompt-lib-wf-action">
                    <button
                      type="button"
                      class="prompt-lib-wf-btn prompt-lib-wf-btn--merge"
                      id="wfMerge"
                      title="1~4번에 적은 내용을 브라우저에서만 이어 붙여, 아래 결과 칸에 한 덩어리로 넣습니다. 서버·OpenAI로 전송하지 않습니다."
                      aria-describedby="wfHelpMerge"
                    >
                      초안 합치기
                    </button>
                    <p id="wfHelpMerge" class="prompt-lib-wf-action-desc">
                      로컬에서만 병합 · API 미사용
                      <span class="prompt-lib-wf-tip" title="PC에서만: 마우스를 버튼 위에 잠시 올리면 전체 설명이 툴팁으로 보입니다." tabindex="0" role="img" aria-label="툴팁 안내">ⓘ</span>
                    </p>
                  </div>
                  <div class="prompt-lib-wf-action">
                    <button
                      type="button"
                      class="prompt-lib-wf-btn prompt-lib-wf-btn--ai"
                      id="wfPolish"
                      <% if (typeof openaiPolishAvailable === "undefined" || !openaiPolishAvailable) { %>disabled<% } %>
                    >
                      AI로 다듬기
                    </button>
                  </div>
                </div>
              </div>

              <div class="prompt-lib-wf-fieldset">
                <h3 class="prompt-lib-wf-h3">결과(복사해 공유 탭에 붙여 넣을 수 있음)</h3>
                <textarea
                  id="wfResult"
                  class="prompt-lib-wf-textarea prompt-lib-wf-textarea--result"
                  rows="12"
                  placeholder="「초안 합치기」 또는 「AI로 다듬기」 후 여기에 표시됩니다."
                  spellcheck="false"
                ></textarea>
              </div>
            </div>
            <p class="form-message" id="wfMsg" hidden></p>
          </section>

          <section class="panel prompt-lib-panel" data-panel="mine" hidden>
            <h2>내가 올린 프롬프트</h2>
            <p class="field-hint" style="margin: 0 0 8px" id="mineEmptyHint">로그인 시 목록이 표시됩니다.</p>
            <ul class="prompt-lib-mine-list" id="mineList"></ul>
          </section>
          <% } %>
        </main>
      </div>
    </div>
    <script type="application/json" id="prompt-lib-boot">
      <%- JSON.stringify({
        official: officialPrompts || [],
        lib: libData || { hasDb: false, community: [], likeCountOfficial: {}, likeCountCommunity: {}, myOfficialLikes: [], myCommunityLikes: [], mySubmissions: [] },
        loggedIn: !!userEmail,
        userEmail: userEmail || "",
        polish: !!openaiPolishAvailable
      }).replace(/</g, "\\u003c") %>
    </script>
    <script src="/vendor/marked/marked.umd.js"></script>
    <script src="/vendor/dompurify/purify.min.js"></script>
    <script>
      (function () {
        var boot = document.getElementById("prompt-lib-boot");
        var C = { official: [], lib: { hasDb: false, community: [] }, loggedIn: false, userEmail: "", polish: false };
        try {
          C = JSON.parse(boot ? boot.textContent : "{}");
        } catch (e) {}

        var cardList = document.getElementById("promptCardList");
        if (!cardList) return;

        var filterEl = document.getElementById("promptFilterSource");
        var sortEl = document.getElementById("promptSort");
        var searchEl = document.getElementById("promptSearchQ");
        var listEmpty = document.getElementById("promptListEmpty");
        var titleEl = document.getElementById("previewTitle");
        var bodyEl = document.getElementById("promptBody");
        var copyBtn = document.getElementById("copyPromptBtn");
        var emptyEl = document.getElementById("previewEmpty");
        var activeWrap = document.getElementById("previewActive");
        var previewMeta = document.getElementById("previewMeta");
        var likeBtn = document.getElementById("promptLikeBtn");
        var likeCountEl = document.getElementById("promptLikeCount");
        var loginHint = document.getElementById("promptLibLoginHint");
        var previewFileWrap = document.getElementById("previewFileWrap");

        var byKey = {};
        var currentKey = null;
        var lo = (C.lib && C.lib.likeCountOfficial) || {};
        var lc = (C.lib && C.lib.likeCountCommunity) || {};
        var myO = new Set((C.lib && C.lib.myOfficialLikes) || []);
        var myC = new Set((C.lib && C.lib.myCommunityLikes) || []);

        function keyOf(src, id) {
          return src + ":" + id;
        }

        function buildMerged() {
          var list = [];
          (C.official || []).forEach(function (p, i) {
            if (!p || !p.id) return;
            list.push({
              source: "official",
              id: p.id,
              title: p.title,
              description: p.description,
              body: p.body,
              tag: p.tag,
              likeCount: lo[p.id] != null ? lo[p.id] : 0,
              myLike: myO.has(p.id),
              order: i,
              createdT: 0
            });
          });
          (C.lib.community || []).forEach(function (c) {
            list.push({
              source: "community",
              id: c.id,
              title: c.title,
              description: c.description,
              body: c.body,
              tag: c.tag,
              likeCount: c.likeCount != null ? c.likeCount : (lc[c.id] || 0),
              myLike: myC.has(c.id),
              authorLabel: c.authorLabel,
              promptFiles: c.promptFiles || [],
              resultFiles: c.resultFiles || [],
              order: 10000,
              createdT: c.createdAt ? new Date(c.createdAt).getTime() : 0
            });
          });
          return list;
        }

        function filterAndSort() {
          var all = buildMerged();
          var src = filterEl && filterEl.value;
          var q = (searchEl && searchEl.value ? searchEl.value : "").trim().toLowerCase();
          var sort = sortEl && sortEl.value;
          var out = all.filter(function (p) {
            if (src === "official" && p.source !== "official") return false;
            if (src === "community" && p.source !== "community") return false;
            if (!q) return true;
            var t =
              (p.title + " " + (p.description || "") + " " + (p.tag || "") + " " + (p.source === "community" ? p.authorLabel || "" : "")).toLowerCase();
            return t.indexOf(q) !== -1;
          });
          if (sort === "popular") {
            out.sort(function (a, b) {
              if (b.likeCount !== a.likeCount) return b.likeCount - a.likeCount;
              if (a.source !== b.source) return a.source === "official" ? -1 : 1;
              return String(a.title).localeCompare(String(b.title), "ko");
            });
          } else {
            out.sort(function (a, b) {
              if (a.source !== b.source) return a.source === "community" ? -1 : 1;
              if (a.source === "community" && b.source === "community") return b.createdT - a.createdT;
              return (a.order || 0) - (b.order || 0);
            });
          }
          return out;
        }

        function renderList() {
          var rows = filterAndSort();
          cardList.innerHTML = "";
          if (!rows.length) {
            if (listEmpty) listEmpty.hidden = false;
            return;
          }
          if (listEmpty) listEmpty.hidden = true;
          rows.forEach(function (p) {
            var btn = document.createElement("button");
            btn.type = "button";
            btn.className = "prompt-template-card";
            btn.setAttribute("data-key", keyOf(p.source, p.id));
            btn.setAttribute("role", "listitem");
            var srcLabel = p.source === "official" ? "공식" : "팀";
            var likes = p.likeCount != null ? p.likeCount : 0;
            var authorHtml =
              p.source === "community" && p.authorLabel
                ? "<span class=\"prompt-card-author\" title=\"작성\">" + escapeHtml(p.authorLabel) + "</span>"
                : "";
            btn.innerHTML =
              "<h3>" + escapeHtml(p.title) + "</h3><p>" + escapeHtml(p.description || "") + "</p>" +
              "<div class=\"prompt-card-footer\"><span class=\"prompt-mini-tag\">" + escapeHtml(p.tag || "") + "</span>" +
              authorHtml +
              "<span class=\"prompt-card-src\">" + srcLabel + "</span>" +
              "<span class=\"prompt-card-likes\" aria-label=\"좋아요\">♥ " + likes + "</span></div>";
            btn.addEventListener("click", function () {
              selectKey(btn.getAttribute("data-key"));
            });
            cardList.appendChild(btn);
          });
        }

        function escapeHtml(s) {
          if (s == null) return "";
          return String(s)
            .replace(/&/g, "&amp;")
            .replace(/</g, "&lt;")
            .replace(/>/g, "&gt;")
            .replace(/"/g, "&quot;");
        }

        function formatFileSize(n) {
          if (n == null || n <= 0) return "";
          if (n < 1024) return n + " B";
          if (n < 1024 * 1024) return (n / 1024).toFixed(1) + " KB";
          return (n / 1024 / 1024).toFixed(1) + " MB";
        }
        function renderPreviewFiles(p) {
          if (!previewFileWrap) return;
          previewFileWrap.innerHTML = "";
          if (p.source !== "community" || (!(p.promptFiles && p.promptFiles.length) && !(p.resultFiles && p.resultFiles.length))) {
            previewFileWrap.hidden = true;
            return;
          }
          previewFileWrap.hidden = false;
          function addBlock(title, files) {
            if (!files || !files.length) return;
            var d = document.createElement("div");
            d.className = "prompt-lib-preview-file-block";
            var h = document.createElement("h4");
            h.textContent = title;
            d.appendChild(h);
            var ul = document.createElement("ul");
            ul.className = "prompt-lib-file-link-list";
            files.forEach(function (f) {
              if (!f || !f.relativePath) return;
              var li = document.createElement("li");
              var a = document.createElement("a");
              a.href = f.relativePath;
              a.target = "_blank";
              a.rel = "noopener noreferrer";
              a.textContent = f.originalName || "파일";
              var z = f.size != null ? formatFileSize(f.size) : "";
              if (z) a.setAttribute("title", z);
              li.appendChild(a);
              if (z) {
                var sp = document.createElement("span");
                sp.className = "prompt-lib-file-size";
                sp.textContent = z;
                li.appendChild(sp);
              }
              ul.appendChild(li);
            });
            d.appendChild(ul);
            previewFileWrap.appendChild(d);
          }
          addBlock("프롬프트 관련 자료", p.promptFiles);
          addBlock("결과(샘플) 파일", p.resultFiles);
        }
        function selectKey(k) {
          currentKey = k;
          var p = byKey[k];
          if (!p) {
            if (emptyEl) emptyEl.hidden = false;
            if (activeWrap) activeWrap.hidden = true;
            if (copyBtn) copyBtn.disabled = true;
            if (titleEl) titleEl.textContent = "프롬프트 미리보기";
            if (likeBtn) likeBtn.classList.remove("is-liked");
            if (previewFileWrap) {
              previewFileWrap.hidden = true;
              previewFileWrap.innerHTML = "";
            }
            return;
          }
          document.querySelectorAll(".prompt-template-card").forEach(function (el) {
            el.classList.toggle("is-selected", el.getAttribute("data-key") === k);
            el.setAttribute("aria-pressed", el.getAttribute("data-key") === k ? "true" : "false");
          });
          if (emptyEl) emptyEl.hidden = true;
          if (activeWrap) activeWrap.hidden = false;
          if (titleEl) titleEl.textContent = p.title;
          if (bodyEl) bodyEl.value = p.body;
          if (copyBtn) copyBtn.disabled = false;
          if (likeCountEl) likeCountEl.textContent = String(p.likeCount != null ? p.likeCount : 0);
          if (likeBtn) {
            likeBtn.classList.toggle("is-liked", !!p.myLike);
            likeBtn.disabled = !C.loggedIn || !(C.lib && C.lib.hasDb);
          }
          if (previewMeta) {
            var srcLabel =
              p.source === "official" ? "공식 템플릿" : "팀 공유 · " + (p.authorLabel || "팀원");
            previewMeta.textContent = srcLabel;
          }
          renderPreviewFiles(p);
        }

        function rebuildIndex() {
          byKey = {};
          buildMerged().forEach(function (p) {
            byKey[keyOf(p.source, p.id)] = p;
          });
        }

        rebuildIndex();
        renderList();
        var firstKey = null;
        if (C.official && C.official[0] && C.official[0].id) {
          firstKey = keyOf("official", C.official[0].id);
        } else if (C.lib && C.lib.community && C.lib.community[0] && C.lib.community[0].id) {
          firstKey = keyOf("community", C.lib.community[0].id);
        }
        if (firstKey && byKey[firstKey]) {
          selectKey(firstKey);
        } else {
          if (emptyEl) emptyEl.hidden = false;
          if (activeWrap) activeWrap.hidden = true;
        }
        var urlParams = new URLSearchParams(window.location.search);
        var deepId = urlParams.get("id");
        if (deepId) {
          var ko = keyOf("official", deepId);
          var kc = keyOf("community", deepId);
          if (byKey[ko]) selectKey(ko);
          else if (byKey[kc]) selectKey(kc);
        }

        if (filterEl) filterEl.addEventListener("change", renderList);
        if (sortEl) sortEl.addEventListener("change", renderList);
        if (searchEl) searchEl.addEventListener("input", function () { renderList(); if (currentKey && byKey[currentKey]) selectKey(currentKey); });

        if (likeBtn) {
          likeBtn.addEventListener("click", function () {
            if (!C.loggedIn || !(C.lib && C.lib.hasDb) || !currentKey) return;
            var p = byKey[currentKey];
            if (!p) return;
            fetch("/api/prompts/likes/toggle", {
              method: "POST",
              headers: { "Content-Type": "application/json" },
              credentials: "same-origin",
              body: JSON.stringify({ kind: p.source, id: p.id })
            })
              .then(function (r) { return r.json().then(function (j) { return { ok: r.ok, j: j }; }); })
              .then(function (o) {
                if (o.ok && o.j && o.j.ok) {
                  p.likeCount = o.j.likeCount;
                  p.myLike = o.j.liked;
                  if (p.source === "official") {
                    lo[p.id] = o.j.likeCount;
                    if (p.myLike) myO.add(p.id); else myO.delete(p.id);
                  } else {
                    lc[p.id] = o.j.likeCount;
                    if (p.myLike) myC.add(p.id); else myC.delete(p.id);
                  }
                  if (likeCountEl) likeCountEl.textContent = String(p.likeCount);
                  if (likeBtn) likeBtn.classList.toggle("is-liked", p.myLike);
                  renderList();
                  selectKey(currentKey);
                }
              });
          });
        }

        if (copyBtn) {
          copyBtn.addEventListener("click", function () {
            if (!bodyEl || !bodyEl.value) return;
            navigator.clipboard.writeText(bodyEl.value).then(
              function () {
                var t = copyBtn.textContent;
                copyBtn.textContent = "복사됨";
                setTimeout(function () { copyBtn.textContent = t; }, 1600);
              },
              function () { bodyEl.select(); try { document.execCommand("copy"); } catch (e) {} }
            );
          });
        }

        if (loginHint) {
          loginHint.hidden = C.loggedIn;
        }

        // Tabs
        var tabs = document.querySelectorAll(".prompt-lib-tab");
        var panels = document.querySelectorAll(".prompt-lib-panel");
        function showTab(name) {
          tabs.forEach(function (t) {
            var on = t.getAttribute("data-tab") === name;
            t.classList.toggle("is-active", on);
            t.setAttribute("aria-selected", on ? "true" : "false");
          });
          panels.forEach(function (p) {
            p.hidden = p.getAttribute("data-panel") !== name;
          });
        }
        tabs.forEach(function (t) {
          t.addEventListener("click", function () {
            showTab(t.getAttribute("data-tab"));
            if (t.getAttribute("data-tab") === "mine") renderMine();
          });
        });
        showTab("library");

        function renderMine() {
          var ul = document.getElementById("mineList");
          if (!ul) return;
          ul.innerHTML = "";
          var hint = document.getElementById("mineEmptyHint");
          if (!C.loggedIn) {
            if (hint) { hint.textContent = "로그인 후에만 볼 수 있습니다."; }
            return;
          }
          var mine = (C.lib && C.lib.mySubmissions) || [];
          if (hint) { hint.textContent = mine.length ? "삭제 시 목록·라이브러리에서 사라집니다." : "아직 올린 프롬프트가 없습니다."; }
          mine.forEach(function (m) {
            var li = document.createElement("li");
            li.className = "prompt-lib-mine-item";
            li.innerHTML = "<span class=\"mine-title\">" + escapeHtml(m.title) + "</span>" +
              "<span class=\"mine-date\">" + escapeHtml((m.createdAt || "").slice(0, 10)) + "</span>" +
              "<button type=\"button\" class=\"btn-ghost mine-del\" data-id=\"" + escapeHtml(m.id) + "\">삭제</button>";
            li.querySelector(".mine-del").addEventListener("click", function () {
              if (!confirm("이 공유를 삭제할까요?")) return;
              fetch("/api/prompts/community/" + encodeURIComponent(m.id), { method: "DELETE", credentials: "same-origin" })
                .then(function (r) { return r.json().then(function (j) { return { ok: r.ok, j: j }; }); })
                .then(function (o) {
                  if (o.ok && o.j && o.j.ok) {
                    C.lib.mySubmissions = (C.lib.mySubmissions || []).filter(function (x) { return x.id !== m.id; });
                    C.lib.community = (C.lib.community || []).filter(function (x) { return x.id !== m.id; });
                    rebuildIndex();
                    renderList();
                    renderMine();
                    if (currentKey === keyOf("community", m.id)) {
                      if (emptyEl) emptyEl.hidden = false;
                      if (activeWrap) activeWrap.hidden = true;
                      if (titleEl) titleEl.textContent = "프롬프트 미리보기";
                      if (previewFileWrap) {
                        previewFileWrap.hidden = true;
                        previewFileWrap.innerHTML = "";
                      }
                    }
                  } else {
                    alert((o.j && o.j.error) || "삭제에 실패했습니다.");
                  }
                });
            });
            ul.appendChild(li);
          });
        }
        if (C.loggedIn) renderMine();

        // Submit
        var pcForm = document.getElementById("promptSubmitForm");
        if (pcForm) {
          pcForm.addEventListener("submit", function (ev) {
            ev.preventDefault();
            if (!C.loggedIn) {
              alert("로그인이 필요합니다.");
              return;
            }
            if (!(C.lib && C.lib.hasDb)) {
              alert("DB에 연결된 환경에서만 공유할 수 있습니다.");
              return;
            }
            var msg = document.getElementById("pcMsg");
            var body = (document.getElementById("pcBody") && document.getElementById("pcBody").value) || "";
            var title = (document.getElementById("pcTitle") && document.getElementById("pcTitle").value) || "";
            var descVal = (document.getElementById("pcDesc") && document.getElementById("pcDesc").value) || "";
            var tagVal = (document.getElementById("pcTag") && document.getElementById("pcTag").value) || "기타";
            if (body.length < 10) {
              if (msg) { msg.textContent = "본문은 10자 이상 입력해 주세요."; msg.hidden = false; msg.style.color = "#b91c1c"; }
              return;
            }
            if (msg) msg.hidden = true;
            var prIn = document.getElementById("pcPromptFiles");
            var reIn = document.getElementById("pcResultFiles");
            if (prIn && prIn.files && prIn.files.length > 5) {
              if (msg) { msg.textContent = "프롬프트 첨부는 5개 이하로 올려 주세요."; msg.hidden = false; msg.style.color = "#b91c1c"; }
              return;
            }
            if (reIn && reIn.files && reIn.files.length > 5) {
              if (msg) { msg.textContent = "결과(샘플) 첨부는 5개 이하로 올려 주세요."; msg.hidden = false; msg.style.color = "#b91c1c"; }
              return;
            }
            var fd = new FormData();
            fd.append("title", title);
            fd.append("tag", tagVal);
            fd.append("description", descVal);
            fd.append("body", body);
            if (prIn && prIn.files) {
              for (var pi = 0; pi < prIn.files.length; pi++) fd.append("promptFiles", prIn.files[pi]);
            }
            if (reIn && reIn.files) {
              for (var ri = 0; ri < reIn.files.length; ri++) fd.append("resultFiles", reIn.files[ri]);
            }
            var subBtn = document.getElementById("pcSubmit");
            if (subBtn) subBtn.disabled = true;
            fetch("/api/prompts/community", {
              method: "POST",
              credentials: "same-origin",
              body: fd
            })
              .then(function (r) { return r.json().then(function (j) { return { ok: r.ok, j: j }; }); })
              .then(function (o) {
                if (subBtn) subBtn.disabled = false;
                if (o.ok && o.j && o.j.ok) {
                  if (msg) { msg.textContent = "공개되었습니다. 라이브러리에서 확인하세요."; msg.hidden = false; msg.style.color = "#047857"; }
                  pcForm.reset();
                  if (typeof window.promptLibResetPcBodyView === "function") window.promptLibResetPcBodyView();
                  var pfn = document.getElementById("pcPromptFileNames");
                  var rfn = document.getElementById("pcResultFileNames");
                  if (pfn) pfn.textContent = "";
                  if (rfn) rfn.textContent = "";
                  C.lib.hasDb = true;
                  C.lib.community = C.lib.community || [];
                  C.lib.community.unshift({
                    id: o.j.id,
                    title: title.trim(),
                    description: descVal,
                    body: body,
                    tag: tagVal || "기타",
                    authorLabel: (function (em) {
                      if (!em) return "팀원";
                      var a = em.indexOf("@");
                      if (a < 1) return "팀원";
                      var loc = em.slice(0, a).trim();
                      return loc || "팀원";
                    })(C.userEmail),
                    likeCount: 0,
                    createdAt: o.j.createdAt || new Date().toISOString(),
                    promptFiles: o.j.promptFiles || [],
                    resultFiles: o.j.resultFiles || []
                  });
                  C.lib.mySubmissions = C.lib.mySubmissions || [];
                  C.lib.mySubmissions.unshift({ id: o.j.id, title: title.trim(), createdAt: o.j.createdAt || "" });
                  rebuildIndex();
                  renderList();
                  showTab("library");
                  selectKey("community:" + o.j.id);
                } else {
                  if (msg) { msg.textContent = (o.j && o.j.error) || "저장에 실패했습니다."; msg.hidden = false; msg.style.color = "#b91c1c"; }
                }
              })
              .catch(function () {
                if (subBtn) subBtn.disabled = false;
                if (msg) { msg.textContent = "요청에 실패했습니다."; msg.hidden = false; msg.style.color = "#b91c1c"; }
              });
          });
        }
        function wirePromptFileList(inputId, nameId) {
          var inp = document.getElementById(inputId);
          var out = document.getElementById(nameId);
          if (!inp || !out) return;
          inp.addEventListener("change", function () {
            out.textContent = "";
            if (!inp.files || !inp.files.length) return;
            var names = [];
            for (var i = 0; i < inp.files.length; i++) names.push(inp.files[i].name);
            out.textContent = names.join(" · ");
          });
        }
        wirePromptFileList("pcPromptFiles", "pcPromptFileNames");
        wirePromptFileList("pcResultFiles", "pcResultFileNames");

        // Workflow
        var wfMerge = document.getElementById("wfMerge");
        var wfPolish = document.getElementById("wfPolish");
        var wfRes = document.getElementById("wfResult");
        var wfMsg = document.getElementById("wfMsg");
        if (wfMerge) {
          wfMerge.addEventListener("click", function () {
            var g = (document.getElementById("wfGoal") && document.getElementById("wfGoal").value) || "";
            var i = (document.getElementById("wfIn") && document.getElementById("wfIn").value) || "";
            var o = (document.getElementById("wfOut") && document.getElementById("wfOut").value) || "";
            var r = (document.getElementById("wfRule") && document.getElementById("wfRule").value) || "";
            var t =
              "다음 조건에 따라 결과를 한국어로 생성해 주세요.\n\n" +
              "【맡기려는 일】\n" + g + "\n\n" +
              "【입력·자료】\n" + i + "\n\n" +
              "【원하는 출력·형식·톤】\n" + o + "\n\n" +
              (r ? "【제약·주의】\n" + r : "");
            if (wfRes) wfRes.value = t;
            if (wfMsg) wfMsg.hidden = true;
          });
        }
        if (wfPolish) {
          wfPolish.addEventListener("click", function () {
            if (!C.loggedIn) {
              alert("로그인이 필요합니다.");
              return;
            }
            if (!C.polish) {
              if (wfMsg) { wfMsg.textContent = "OpenAI API가 설정되지 않았습니다."; wfMsg.hidden = false; wfMsg.style.color = "#b91c1c"; }
              return;
            }
            var draft = (wfRes && wfRes.value) || "";
            if (draft.length < 5) {
              if (wfMsg) { wfMsg.textContent = "먼저 아래에 초안을 넣거나 「초안 합치기」를 누르세요."; wfMsg.hidden = false; wfMsg.style.color = "#b91c1c"; }
              return;
            }
            wfPolish.disabled = true;
            fetch("/api/prompts/polish-workflow", {
              method: "POST",
              headers: { "Content-Type": "application/json" },
              credentials: "same-origin",
              body: JSON.stringify({ draft: draft })
            })
              .then(function (r) { return r.json().then(function (j) { return { ok: r.ok, j: j }; }); })
              .then(function (o) {
                wfPolish.disabled = false;
                if (o.ok && o.j && o.j.text) {
                  if (wfRes) wfRes.value = o.j.text;
                  if (wfMsg) { wfMsg.textContent = "다듬기 완료. 필요하면 수동으로 수정하세요."; wfMsg.hidden = false; wfMsg.style.color = "#047857"; }
                } else {
                  if (wfMsg) { wfMsg.textContent = (o.j && o.j.error) || "실패"; wfMsg.hidden = false; wfMsg.style.color = "#b91c1c"; }
                }
              })
              .catch(function () {
                wfPolish.disabled = false;
                if (wfMsg) { wfMsg.textContent = "요청에 실패했습니다."; wfMsg.hidden = false; wfMsg.style.color = "#b91c1c"; }
              });
          });
        }
      })();
    </script>
    <script>
      (function () {
        var pcBody = document.getElementById("pcBody");
        var pcBodyMdPreview = document.getElementById("pcBodyMdPreview");
        var pcBodyMdInner = document.getElementById("pcBodyMdInner");
        var btnRaw = document.getElementById("pcBodyModeRaw");
        var btnMd = document.getElementById("pcBodyModeMd");
        if (!pcBody || !pcBodyMdPreview || !pcBodyMdInner || !btnRaw || !btnMd) return;

        function renderMd() {
          var text = (pcBody && pcBody.value) || "";
          if (typeof marked === "undefined" || typeof DOMPurify === "undefined") {
            pcBodyMdInner.innerHTML =
              '<p class="prompt-lib-md-fallback">마크다운 뷰어를 불러오지 못했습니다. 원문 보기로 편집하세요.</p>';
            return;
          }
          if (!String(text).trim()) {
            pcBodyMdInner.innerHTML =
              '<p class="prompt-lib-md-empty">입력한 내용이 없습니다. 원문 보기에서 지시문을 입력하세요.</p>';
            return;
          }
          try {
            if (typeof marked.setOptions === "function") {
              marked.setOptions({ breaks: true, gfm: true, async: false });
            }
            var rawHtml = marked.parse(String(text), { async: false });
            pcBodyMdInner.innerHTML = DOMPurify.sanitize(rawHtml, { USE_PROFILES: { html: true } });
          } catch (e) {
            pcBodyMdInner.innerHTML = '<p class="prompt-lib-md-fallback">렌더링에 실패했습니다.</p>';
          }
        }

        function setMode(isRaw) {
          if (isRaw) {
            btnRaw.classList.add("is-active");
            btnMd.classList.remove("is-active");
            btnRaw.setAttribute("aria-selected", "true");
            btnMd.setAttribute("aria-selected", "false");
            pcBody.removeAttribute("hidden");
            pcBody.removeAttribute("aria-hidden");
            pcBodyMdPreview.setAttribute("hidden", "");
            pcBodyMdPreview.setAttribute("aria-hidden", "true");
          } else {
            renderMd();
            btnRaw.classList.remove("is-active");
            btnMd.classList.add("is-active");
            btnRaw.setAttribute("aria-selected", "false");
            btnMd.setAttribute("aria-selected", "true");
            pcBody.setAttribute("hidden", "");
            pcBody.setAttribute("aria-hidden", "true");
            pcBodyMdPreview.removeAttribute("hidden");
            pcBodyMdPreview.removeAttribute("aria-hidden");
          }
        }

        btnRaw.addEventListener("click", function () {
          setMode(true);
        });
        btnMd.addEventListener("click", function () {
          setMode(false);
        });

        window.promptLibResetPcBodyView = function () {
          if (pcBodyMdInner) pcBodyMdInner.innerHTML = "";
          setMode(true);
        };
      })();
    </script>
    <style>
      .visually-hidden {
        position: absolute;
        width: 1px;
        height: 1px;
        padding: 0;
        margin: -1px;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
        white-space: nowrap;
        border: 0;
      }
    </style>
  </body>
</html>
