<!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>회의록 AI - XAVIS</title>
    <link rel="stylesheet" href="/public/styles.css" />
  </head>
  <body class="meeting-minutes-page">
    <% var mmDefaultCustomInstructions = `아래 회의 내용(또는 녹취/메모)을 바탕으로 다음 형식으로 정리해 주세요.

원문·전사 전체를 회의록에 다시 붙여 넣지 마세요. ‘스크립트’·‘스크랩트’(오타)·‘원문 전사’ 같은 섹션은 만들지 말고, 요약·결정·액션·체크리스트만 작성하세요. 회의 제목·참석자·요약 등은 ## 마크다운 제목으로 구분하세요.

1) 회의 개요: 일시, 참석자(알 수 있는 경우), 목적
2) 논의 안건별 요약
3) 결정 사항 (명확한 문장으로)
4) 액션 아이템: 별도 섹션. 각 항목에 What(할 일)·Who(담당자)·When(기한)을 구체적으로
5) 회의 체크리스트: 전·중·후 준비·검토 항목을 [ ]로 (완료 여부 표시 가능하게)
체크리스트·액션 아이템 다음에 "추가 메모 / 확인 필요 사항" 같은 말미 섹션이나 CSV·템플릿 제안은 넣지 마세요.
체크리스트를 마지막 섹션으로 두고, 시연·피드백 회신 방식, 우선순위 재정렬·담당·기한 확정, DRM·후보군 추가 작성 제안 등 운영 안내 문단은 붙이지 마세요.
‘추가 권고’, ‘회의록 작성자의 제안’, 엑셀 템플릿·추적 체크리스트 제안 등은 넣지 마세요.`; %>
    <% var mmNow = new Date(); var mmTodayIso = mmNow.getFullYear() + '-' + ('0' + (mmNow.getMonth() + 1)).slice(-2) + '-' + ('0' + mmNow.getDate()).slice(-2); %>
    <div class="app-shell">
      <%- include('partials/nav', { activeMenu: 'ai-explore', adminMode: typeof adminMode !== 'undefined' ? adminMode : false }) %>
      <div class="content-area">
        <header class="topbar">
          <h1>회의록 AI</h1>
          <a class="top-action-link" href="/ai-explore">AI 목록</a>
        </header>
        <main class="container meeting-minutes-main">
          <a href="/ai-explore" class="prompts-back" aria-label="AI 탐색으로 돌아가기">← AI</a>
          <% var hasEmail = typeof meetingUserEmail !== 'undefined' && meetingUserEmail; %>
          <% if (!hasEmail) { %>
          <p class="chat-api-warning" style="margin-bottom: 16px">
            이메일 인증(OPS <code>PROD</code>) 또는 DEV에서 <strong>관리자 모드</strong>, SUPER(데모)에서는 별도 로그인 없이 이용할 수 있습니다. (DEV 테스트: <code>MEETING_DEV_EMAIL</code>)
          </p>
          <% } %>
          <div class="mm-layout">
            <aside class="mm-sidebar panel">
              <h2 class="mm-sidebar-title">내 회의록</h2>
              <button type="button" class="btn-ghost mm-refresh" id="mmListRefresh" <%= hasEmail ? '' : 'disabled' %>>새로고침</button>
              <ul class="mm-meeting-list" id="mmMeetingList" role="list"></ul>
              <p class="mm-list-empty" id="mmListEmpty" hidden>저장된 회의록이 없습니다.</p>
            </aside>
            <div class="mm-workspace">
              <section class="panel mm-prompt-panel mm-prompt-collapsed" id="mmPromptPanel">
                <div class="mm-prompt-panel-head">
                  <h2 id="mmPromptHeading">출력 형식 (프롬프트)</h2>
                  <button type="button" class="mm-prompt-toggle" id="mmPromptToggle" aria-expanded="false" aria-controls="mmPromptBody" aria-labelledby="mmPromptHeading" title="펼치기·접기">
                    <span class="mm-prompt-toggle-icon" aria-hidden="true">▼</span>
                  </button>
                </div>
                <div class="mm-prompt-body" id="mmPromptBody" hidden>
                  <p class="subtitle">회의록에 포함할 항목을 선택하고, 추가 지시를 입력할 수 있습니다. 시스템 프롬프트에 액션 아이템(What·Who·When)과 회의 체크리스트(전·중·후) 정의가 포함되며, 체크리스트는 업무 체크리스트 AI 연동을 위해 항상 포함됩니다.</p>
                  <div class="mm-prompt-form" id="mmPromptForm">
                    <div class="mm-checkbox-row" role="group" aria-label="회의록에 포함할 항목">
                      <label class="mm-checkbox-item"><input type="checkbox" id="mmIncTitle" checked /> <span>제목 한 줄</span></label>
                      <label class="mm-checkbox-item"><input type="checkbox" id="mmIncAtt" checked /> <span>참석자</span></label>
                      <label class="mm-checkbox-item"><input type="checkbox" id="mmIncSum" checked /> <span>요약</span></label>
                      <label class="mm-checkbox-item"><input type="checkbox" id="mmIncAct" checked /> <span>Action Item</span></label>
                      <label class="mm-checkbox-item mm-checkbox-item-locked" title="업무 체크리스트 AI에서 활용되므로 항상 켜져 있습니다.">
                        <input type="checkbox" id="mmIncChk" checked disabled aria-checked="true" aria-disabled="true" />
                        <span>체크리스트 <span class="mm-checkbox-badge">필수</span></span>
                      </label>
                    </div>
                    <div class="mm-custom-block">
                      <label class="mm-field-label" for="mmCustomInstr">추가 지시</label>
                      <textarea id="mmCustomInstr" class="mm-textarea mm-custom-instr-textarea" rows="9" placeholder=""><%= mmDefaultCustomInstructions %></textarea>
                    </div>
                    <div class="mm-prompt-actions">
                      <button type="button" class="top-action" id="mmSavePrompt" <%= hasEmail ? '' : 'disabled' %>>프롬프트 저장</button>
                    </div>
                  </div>
                </div>
              </section>

              <section class="panel">
                <div class="mm-tabs" role="tablist">
                  <button type="button" class="mm-tab is-active" id="mmTabText" data-tab="text" role="tab" aria-selected="true" aria-controls="mmPanelText">텍스트 입력</button>
                  <button type="button" class="mm-tab" id="mmTabAudio" data-tab="audio" role="tab" aria-selected="false" aria-controls="mmPanelAudio">음성 파일</button>
                </div>

                <div class="mm-tab-panel mm-field-stack" id="mmPanelText" role="tabpanel" aria-labelledby="mmTabText" aria-hidden="false">
                  <div class="mm-field mm-title-date-field">
                    <div class="mm-title-date-labels">
                      <span class="mm-field-label">제목</span>
                      <span class="mm-field-label">날짜</span>
                    </div>
                    <div class="mm-title-date-box">
                      <input type="text" id="mmTitleText" class="mm-input mm-title-input" placeholder="회의 제목" maxlength="500" <%= hasEmail ? '' : 'disabled' %> />
                      <div class="mm-date-wrap">
                        <input type="date" id="mmDateText" class="mm-date-native" value="<%= mmTodayIso %>" <%= hasEmail ? '' : 'disabled' %> />
                        <button type="button" class="mm-date-trigger" id="mmDateTextBtn" <%= hasEmail ? '' : 'disabled' %> aria-label="달력 열기">▼</button>
                      </div>
                    </div>
                  </div>
                  <label class="mm-field">
                    <span class="mm-field-label">회의 원문</span>
                    <textarea id="mmSourceText" class="mm-textarea" rows="12" placeholder="회의 내용을 붙여 넣거나 입력하세요." <%= hasEmail ? '' : 'disabled' %>></textarea>
                  </label>
                  <label class="mm-field mm-field-narrow">
                    <span class="mm-field-label">회의록 생성 모델</span>
                    <select id="mmModelText" class="mm-select" <%= hasEmail ? '' : 'disabled' %>>
                      <option value="gpt-5-mini" selected>gpt-5-mini</option>
                      <option value="gpt-5.4">gpt-5.4</option>
                    </select>
                  </label>
                  <div class="form-actions mm-form-actions">
                    <button type="button" class="top-action" id="mmGenText" <%= hasEmail ? '' : 'disabled' %>>회의록 생성</button>
                  </div>
                </div>

                <div class="mm-tab-panel mm-field-stack mm-audio-panel" id="mmPanelAudio" role="tabpanel" aria-labelledby="mmTabAudio" aria-hidden="true" hidden>
                  <div class="mm-audio-phase">
                    <h3 class="mm-section-heading">1. 음성 전사</h3>
                    <label class="mm-field">
                      <span class="mm-field-label">음성 파일</span>
                      <input type="file" id="mmAudioFile" class="mm-file-input" accept=".mp3,.mp4,.mpeg,.mpga,.m4a,.wav,.webm,.ogg,.flac" <%= hasEmail ? '' : 'disabled' %> />
                    </label>
                    <p class="mm-audio-hint">지원 포맷: mp3, m4a, wav 등. 파일당 최대 300MB입니다.</p>
                    <label class="mm-field mm-transcribe-model-only">
                      <span class="mm-field-label">전사 모델 (OpenAI)</span>
                      <select id="mmWhisperModel" class="mm-select" <%= hasEmail ? '' : 'disabled' %> title="음성→텍스트 API 모델">
                        <option value="gpt-4o-mini-transcribe" selected>gpt-4o-mini-transcribe (기본)</option>
                        <option value="gpt-4o-transcribe">gpt-4o-transcribe (고성능)</option>
                      </select>
                      <span class="mm-field-help">OpenAI 전사 API 공식 모델 ID와 동일합니다. 기본은 mini, 더 높은 인식 품질이 필요하면 gpt-4o-transcribe를 선택하세요.</span>
                    </label>
                  </div>
                  <div class="mm-audio-phase">
                    <h3 class="mm-section-heading">2. 회의록 작성</h3>
                    <div class="mm-field mm-title-date-field">
                      <div class="mm-title-date-labels">
                        <span class="mm-field-label">제목</span>
                        <span class="mm-field-label">날짜</span>
                      </div>
                      <div class="mm-title-date-box">
                        <input type="text" id="mmTitleAudio" class="mm-input mm-title-input" placeholder="회의 제목" maxlength="500" <%= hasEmail ? '' : 'disabled' %> />
                        <div class="mm-date-wrap">
                          <input type="date" id="mmDateAudio" class="mm-date-native" value="<%= mmTodayIso %>" <%= hasEmail ? '' : 'disabled' %> />
                          <button type="button" class="mm-date-trigger" id="mmDateAudioBtn" <%= hasEmail ? '' : 'disabled' %> aria-label="달력 열기">▼</button>
                        </div>
                      </div>
                    </div>
                    <label class="mm-field mm-field-narrow">
                      <span class="mm-field-label">회의록 생성 모델</span>
                      <select id="mmModelAudio" class="mm-select" <%= hasEmail ? '' : 'disabled' %>>
                        <option value="gpt-5-mini" selected>gpt-5-mini</option>
                        <option value="gpt-5.4">gpt-5.4</option>
                      </select>
                    </label>
                    <p class="mm-audio-summary-hint">전사된 텍스트를 아래 프롬프트(출력 형식)에 맞게 요약·정리합니다.</p>
                    <div class="form-actions mm-form-actions">
                      <button type="button" class="top-action" id="mmGenAudio" <%= hasEmail ? '' : 'disabled' %>>전사 및 회의록 생성</button>
                    </div>
                  </div>
                </div>
              </section>

              <div id="mmGenProgress" class="mm-gen-progress" hidden role="status" aria-live="polite" aria-busy="false">
                <div class="mm-gen-progress-track" aria-hidden="true">
                  <div class="mm-gen-progress-bar"></div>
                </div>
                <p class="mm-gen-progress-msg" id="mmGenProgressMsg">처리 중…</p>
              </div>

              <section class="panel mm-result-panel" id="mmResultSection" hidden>
                <div class="mm-result-head">
                  <h2 class="mm-result-title">생성 결과</h2>
                  <button type="button" class="top-action" id="mmSaveResult" disabled>저장</button>
                </div>
                <p class="mm-result-hint">
                  생성 결과에서 <strong>회의록</strong>을 수정한 뒤 <strong>저장</strong>하면 DB에 반영되며, 회의록 기준으로 액션 아이템·체크리스트가 업무 체크리스트 AI에 다시 연동됩니다. 음성 전사가 있는 회의는 <strong>음성 파일</strong> 탭을 선택하면 아래 <strong>전사 기록</strong>도 함께 수정할 수 있습니다. 텍스트 입력으로 만든 회의는 위쪽 <strong>회의 원문</strong>에서 원문을 고칩니다.
                </p>
                <div class="mm-result-split">
                  <div id="mmTranscriptPane" class="mm-result-transcript-pane" hidden aria-hidden="true">
                    <label class="mm-result-field">
                      <span class="mm-result-field-label" id="mmTranscriptLabel">전사 기록</span>
                      <textarea
                        id="mmTranscriptBody"
                        class="mm-result-body mm-result-textarea mm-result-textarea-half mm-transcript-textarea"
                        rows="14"
                        spellcheck="false"
                        placeholder="음성 전사 텍스트가 여기에 표시됩니다."
                      ></textarea>
                    </label>
                  </div>
                  <div class="mm-result-field">
                    <div class="mm-minutes-header">
                      <span class="mm-result-field-label">회의록</span>
                      <div class="mm-minutes-actions" id="mmMinutesActionsView" role="toolbar" aria-label="회의록 보기">
                        <button type="button" class="btn-ghost mm-minutes-edit" id="mmMinutesEdit">마크다운 편집</button>
                      </div>
                      <div class="mm-minutes-actions" id="mmMinutesActionsEdit" role="toolbar" aria-label="회의록 편집" hidden>
                        <button type="button" class="top-action mm-minutes-apply" id="mmMinutesApply">저장</button>
                        <button type="button" class="btn-ghost mm-minutes-cancel" id="mmMinutesCancel">취소</button>
                      </div>
                    </div>
                    <div class="mm-minutes-editor-wrap">
                      <div
                        id="mmMinutesRendered"
                        class="mm-minutes-rendered mm-minutes-rendered-empty"
                        role="region"
                        aria-label="회의록 (마크다운 렌더링)"
                        tabindex="0"
                      >
                        회의록이 없습니다. 마크다운 편집으로 내용을 입력하세요.
                      </div>
                      <textarea
                        id="mmResultBody"
                        class="mm-result-body mm-result-textarea mm-result-textarea-half mm-minutes-source"
                        rows="14"
                        spellcheck="false"
                        hidden
                        placeholder="마크다운으로 회의록을 편집합니다. 「저장」으로 뷰로 돌아가거나 「취소」로 편집 전 내용으로 되돌립니다."
                      ></textarea>
                    </div>
                  </div>
                </div>
              </section>
            </div>
          </div>
        </main>
      </div>
    </div>
    <script src="/vendor/marked/marked.umd.js"></script>
    <script src="/vendor/dompurify/purify.min.js"></script>
    <script>
      (function () {
        var hasEmail = <%= hasEmail ? 'true' : 'false' %>;
        if (!hasEmail) return;

        var listEl = document.getElementById('mmMeetingList');
        var emptyEl = document.getElementById('mmListEmpty');
        var resultSection = document.getElementById('mmResultSection');
        var resultBody = document.getElementById('mmResultBody');
        var minutesRenderedEl = document.getElementById('mmMinutesRendered');
        var minutesActionsView = document.getElementById('mmMinutesActionsView');
        var minutesActionsEdit = document.getElementById('mmMinutesActionsEdit');
        var minutesEditBtn = document.getElementById('mmMinutesEdit');
        var minutesApplyBtn = document.getElementById('mmMinutesApply');
        var minutesCancelBtn = document.getElementById('mmMinutesCancel');
        /** 편집 모드 진입 시점의 회의록 원문(취소 시 복원) */
        var minutesEditSnapshot = '';
        var transcriptBody = document.getElementById('mmTranscriptBody');
        var transcriptLabel = document.getElementById('mmTranscriptLabel');
        /** true: 음성 전사(transcript_text) 편집, false: 텍스트 회의 원문(source_text) 편집 */
        var lastMeetingTranscriptIsAudio = false;
        var saveResultBtn = document.getElementById('mmSaveResult');
        var genProgressEl = document.getElementById('mmGenProgress');
        var genProgressMsg = document.getElementById('mmGenProgressMsg');
        var currentMeetingId = null;

        function escapeHtmlMm(s) {
          var div = document.createElement('div');
          div.textContent = s;
          return div.innerHTML;
        }
        var minutesMarkdownConfigured = false;
        function configureMinutesMarkdown() {
          if (minutesMarkdownConfigured) return;
          minutesMarkdownConfigured = true;
          if (typeof marked !== 'undefined') {
            marked.setOptions({ async: false, breaks: true, gfm: true });
          }
          var domPurify = typeof window !== 'undefined' && window.DOMPurify ? window.DOMPurify : typeof DOMPurify !== 'undefined' ? DOMPurify : null;
          if (domPurify) {
            domPurify.addHook('afterSanitizeAttributes', function (node) {
              if (node.tagName === 'A' && node.hasAttribute('href')) {
                var href = node.getAttribute('href');
                try {
                  var u = new URL(href, window.location.href);
                  if (u.protocol !== 'http:' && u.protocol !== 'https:') {
                    node.removeAttribute('href');
                    return;
                  }
                } catch (e) {
                  node.removeAttribute('href');
                  return;
                }
                node.setAttribute('target', '_blank');
                node.setAttribute('rel', 'noopener noreferrer');
              }
            });
          }
        }
        function getDomPurify() {
          return typeof window !== 'undefined' && window.DOMPurify
            ? window.DOMPurify
            : typeof DOMPurify !== 'undefined'
              ? DOMPurify
              : null;
        }
        function getMarkedParseFn() {
          if (typeof marked === 'undefined') return null;
          if (typeof marked.parse === 'function') return marked.parse.bind(marked);
          if (typeof marked === 'function') return marked;
          return null;
        }
        function renderMinutesMarkdown(text) {
          configureMinutesMarkdown();
          var src = String(text || '');
          var parseFn = getMarkedParseFn();
          var purify = getDomPurify();
          if (!parseFn || !purify) {
            return escapeHtmlMm(src).replace(/\n/g, '<br>');
          }
          try {
            var raw = parseFn(src, { async: false });
            if (raw != null && typeof raw.then === 'function') {
              return escapeHtmlMm(src).replace(/\n/g, '<br>');
            }
            return purify.sanitize(String(raw || ''), { USE_PROFILES: { html: true } });
          } catch (err) {
            return escapeHtmlMm(src).replace(/\n/g, '<br>');
          }
        }
        function refreshMinutesRendered() {
          if (!minutesRenderedEl) return;
          var raw = resultBody ? resultBody.value : '';
          if (!String(raw).trim()) {
            minutesRenderedEl.classList.add('mm-minutes-rendered-empty');
            minutesRenderedEl.innerHTML = '회의록이 없습니다. 마크다운 편집으로 내용을 입력하세요.';
            return;
          }
          minutesRenderedEl.classList.remove('mm-minutes-rendered-empty');
          minutesRenderedEl.innerHTML = renderMinutesMarkdown(raw);
        }
        function setMinutesToolbarMode(editing) {
          if (minutesActionsView) minutesActionsView.hidden = !!editing;
          if (minutesActionsEdit) minutesActionsEdit.hidden = !editing;
        }
        function setMinutesViewMode(showSource) {
          if (!resultBody || !minutesRenderedEl) return;
          if (!showSource) {
            refreshMinutesRendered();
            resultBody.hidden = true;
            minutesRenderedEl.hidden = false;
            setMinutesToolbarMode(false);
          } else {
            minutesRenderedEl.hidden = true;
            resultBody.hidden = false;
            setMinutesToolbarMode(true);
            try {
              resultBody.focus();
            } catch (e) {}
          }
        }
        if (minutesEditBtn) {
          minutesEditBtn.addEventListener('click', function () {
            if (!resultBody) return;
            minutesEditSnapshot = resultBody.value;
            setMinutesViewMode(true);
          });
        }
        if (minutesApplyBtn) {
          minutesApplyBtn.addEventListener('click', function () {
            setMinutesViewMode(false);
          });
        }
        if (minutesCancelBtn) {
          minutesCancelBtn.addEventListener('click', function () {
            if (resultBody) resultBody.value = minutesEditSnapshot;
            setMinutesViewMode(false);
          });
        }

        /** 음성 파일 탭일 때만 생성 결과의 전사 기록 영역 표시(텍스트 입력은 회의 원문과 중복이므로 숨김) */
        function applyTranscriptPaneVisibility() {
          var pane = document.getElementById('mmTranscriptPane');
          var audioTab = document.getElementById('mmTabAudio');
          if (!pane || !audioTab) return;
          var show = audioTab.classList.contains('is-active');
          pane.hidden = !show;
          pane.setAttribute('aria-hidden', show ? 'false' : 'true');
        }

        function setCurrentMeetingId(id) {
          currentMeetingId = id || null;
          if (saveResultBtn) saveResultBtn.disabled = !currentMeetingId;
        }

        function setMeetingGenerating(on, msg) {
          if (genProgressEl) {
            genProgressEl.hidden = !on;
            genProgressEl.setAttribute('aria-busy', on ? 'true' : 'false');
            if (on) genProgressEl.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
          }
          if (genProgressMsg && msg) genProgressMsg.textContent = msg;
          var gText = document.getElementById('mmGenText');
          var gAudio = document.getElementById('mmGenAudio');
          if (gText) gText.disabled = !!on;
          if (gAudio) gAudio.disabled = !!on;
          if (saveResultBtn) saveResultBtn.disabled = !!on || !currentMeetingId;
          if (resultBody) resultBody.disabled = !!on;
          if (transcriptBody) transcriptBody.disabled = !!on;
          if (minutesEditBtn) minutesEditBtn.disabled = !!on;
          if (minutesApplyBtn) minutesApplyBtn.disabled = !!on;
          if (minutesCancelBtn) minutesCancelBtn.disabled = !!on;
          if (on && resultBody && minutesRenderedEl) {
            if (!resultBody.hidden) {
              refreshMinutesRendered();
              setMinutesViewMode(false);
            }
          }
        }

        function api(path, opts) {
          return fetch(path, Object.assign({ credentials: 'same-origin' }, opts || {})).then(function (r) {
            var ct = (r.headers.get('content-type') || '').toLowerCase();
            if (ct.indexOf('application/json') === -1) {
              return r.text().then(function (t) {
                var msg = (t || '').replace(/\s+/g, ' ').trim().slice(0, 200);
                throw new Error(msg || 'HTTP ' + r.status + ' ' + (r.statusText || ''));
              });
            }
            return r.json().then(function (j) {
              if (!r.ok) throw new Error(j.error || r.statusText);
              return j;
            });
          });
        }

        var MM_DEFAULT_CUSTOM_INSTRUCTIONS = <%- JSON.stringify(mmDefaultCustomInstructions) %>;

        function loadPrompt() {
          return api('/api/meeting-minutes/prompt').then(function (d) {
            var p = d.prompt || {};
            document.getElementById('mmIncTitle').checked = p.includeTitleLine !== false;
            document.getElementById('mmIncAtt').checked = p.includeAttendees !== false;
            document.getElementById('mmIncSum').checked = p.includeSummary !== false;
            document.getElementById('mmIncAct').checked = p.includeActionItems !== false;
            document.getElementById('mmIncChk').checked = true;
            var saved = (p.customInstructions && String(p.customInstructions).trim()) || '';
            document.getElementById('mmCustomInstr').value = saved || MM_DEFAULT_CUSTOM_INSTRUCTIONS;
          });
        }

        function loadMeetings() {
          return api('/api/meeting-minutes/meetings').then(function (d) {
            var list = d.meetings || [];
            listEl.innerHTML = '';
            if (!list.length) {
              emptyEl.hidden = false;
              return;
            }
            emptyEl.hidden = true;
            list.forEach(function (m) {
              var li = document.createElement('li');
              li.className = 'mm-meeting-item';
              var t = document.createElement('button');
              t.type = 'button';
              t.className = 'mm-meeting-item-btn';
              var title = (m.title || '제목 없음').slice(0, 60);
              var md = m.meetingDate ? String(m.meetingDate).slice(0, 10) : '';
              var mdKo = '';
              if (md && md.length >= 10) {
                var p = md.split('-');
                if (p.length === 3) mdKo = p[0] + '. ' + p[1] + '. ' + p[2] + '.';
              }
              var dt = m.createdAt ? new Date(m.createdAt).toLocaleString('ko-KR') : '';
              t.textContent = title + (mdKo ? ' · ' + mdKo : '') + (dt ? ' · ' + dt : '');
              t.addEventListener('click', function () {
                showMeeting(m.id);
              });
              var del = document.createElement('button');
              del.type = 'button';
              del.className = 'mm-meeting-del';
              del.setAttribute('aria-label', '삭제');
              del.textContent = '×';
              del.addEventListener('click', function (e) {
                e.stopPropagation();
                if (!confirm('이 회의록을 삭제할까요?')) return;
                api('/api/meeting-minutes/meetings/' + encodeURIComponent(m.id), { method: 'DELETE' }).then(loadMeetings);
              });
              li.appendChild(t);
              li.appendChild(del);
              listEl.appendChild(li);
            });
          });
        }

        function showMeeting(id) {
          api('/api/meeting-minutes/meetings/' + encodeURIComponent(id)).then(function (d) {
            var m = d.meeting;
            resultSection.hidden = false;
            setCurrentMeetingId(m.id);
            if (transcriptLabel) {
              transcriptLabel.textContent =
                m.transcriptText && String(m.transcriptText).trim() ? '전사 기록' : '회의 원문';
            }
            lastMeetingTranscriptIsAudio = !!(m.transcriptText && String(m.transcriptText).trim());
            if (transcriptBody) {
              if (lastMeetingTranscriptIsAudio) {
                transcriptBody.value = m.transcriptText != null ? String(m.transcriptText) : '';
              } else {
                transcriptBody.value = m.sourceText != null ? String(m.sourceText) : '';
              }
            }
            resultBody.value = m.generatedMinutes != null ? String(m.generatedMinutes) : '';
            setMinutesViewMode(false);
            applyTranscriptPaneVisibility();
            var title = m.title || '';
            document.getElementById('mmTitleText').value = title;
            document.getElementById('mmTitleAudio').value = title;
            var md = m.meetingDate ? String(m.meetingDate).slice(0, 10) : '';
            document.getElementById('mmDateText').value = md && md.length >= 10 ? md : '';
            document.getElementById('mmDateAudio').value = md && md.length >= 10 ? md : '';
            var src = '';
            if (m.sourceText && String(m.sourceText).trim()) src = m.sourceText;
            else if (m.transcriptText && String(m.transcriptText).trim()) src = m.transcriptText;
            document.getElementById('mmSourceText').value = src;
            if (m.chatModel) {
              var mt = document.getElementById('mmModelText');
              var ma = document.getElementById('mmModelAudio');
              if (mt) {
                for (var i = 0; i < mt.options.length; i++) {
                  if (mt.options[i].value === m.chatModel) {
                    mt.selectedIndex = i;
                    break;
                  }
                }
              }
              if (ma) {
                for (var j = 0; j < ma.options.length; j++) {
                  if (ma.options[j].value === m.chatModel) {
                    ma.selectedIndex = j;
                    break;
                  }
                }
              }
            }
            if (m.transcriptionModel) {
              var wEl = document.getElementById('mmWhisperModel');
              if (wEl) {
                for (var k = 0; k < wEl.options.length; k++) {
                  if (wEl.options[k].value === m.transcriptionModel) {
                    wEl.selectedIndex = k;
                    break;
                  }
                }
              }
            }
          });
        }

        document.getElementById('mmSavePrompt').addEventListener('click', function () {
          var body = {
            includeTitleLine: document.getElementById('mmIncTitle').checked,
            includeAttendees: document.getElementById('mmIncAtt').checked,
            includeSummary: document.getElementById('mmIncSum').checked,
            includeActionItems: document.getElementById('mmIncAct').checked,
            includeChecklist: true,
            customInstructions: document.getElementById('mmCustomInstr').value
          };
          api('/api/meeting-minutes/prompt', {
            method: 'PUT',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(body)
          }).then(function () {
            alert('저장되었습니다.');
          }).catch(function (e) {
            alert(e.message || '저장 실패');
          });
        });

        document.getElementById('mmSaveResult').addEventListener('click', function () {
          if (!currentMeetingId) return;
          setMeetingGenerating(true, '저장 중…');
          var savePayload = { generatedMinutes: resultBody.value };
          if (lastMeetingTranscriptIsAudio) {
            savePayload.transcriptText = transcriptBody ? transcriptBody.value : '';
          } else {
            savePayload.sourceText = transcriptBody ? transcriptBody.value : '';
          }
          api('/api/meeting-minutes/meetings/' + encodeURIComponent(currentMeetingId) + '/save', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(savePayload)
          })
            .then(function (d) {
              if (d.meeting) {
                if (d.meeting.generatedMinutes != null) resultBody.value = d.meeting.generatedMinutes;
                setMinutesViewMode(false);
                if (transcriptBody) {
                  if (lastMeetingTranscriptIsAudio && d.meeting.transcriptText != null) {
                    transcriptBody.value = String(d.meeting.transcriptText);
                  }
                  if (!lastMeetingTranscriptIsAudio && d.meeting.sourceText != null) {
                    transcriptBody.value = String(d.meeting.sourceText);
                    var stEl = document.getElementById('mmSourceText');
                    if (stEl) stEl.value = String(d.meeting.sourceText);
                  }
                }
              }
              loadMeetings();
              var cs = d.checklistSync;
              if (cs && cs.imported > 0) {
                alert('저장되었습니다. 업무 체크리스트에 ' + cs.imported + '건이 반영되었습니다.');
              } else if (cs && cs.extractError && !cs.disabled) {
                alert('저장되었습니다. (체크리스트 자동 연동: ' + cs.extractError + ')');
              } else {
                alert('저장되었습니다.');
              }
            })
            .catch(function (e) {
              alert(e.message || '저장 실패');
            })
            .finally(function () {
              setMeetingGenerating(false);
            });
        });

        document.getElementById('mmListRefresh').addEventListener('click', loadMeetings);

        function wireMeetingDatePicker(btnId, inputId) {
          var btn = document.getElementById(btnId);
          var inp = document.getElementById(inputId);
          if (!btn || !inp) return;
          btn.addEventListener('click', function () {
            if (typeof inp.showPicker === 'function') inp.showPicker();
            else inp.focus();
          });
        }
        wireMeetingDatePicker('mmDateTextBtn', 'mmDateText');
        wireMeetingDatePicker('mmDateAudioBtn', 'mmDateAudio');

        document.querySelectorAll('.mm-tab').forEach(function (tab) {
          tab.addEventListener('click', function () {
            var name = tab.getAttribute('data-tab');
            document.querySelectorAll('.mm-tab').forEach(function (t) {
              t.classList.toggle('is-active', t === tab);
              t.setAttribute('aria-selected', t === tab ? 'true' : 'false');
            });
            var panelText = document.getElementById('mmPanelText');
            var panelAudio = document.getElementById('mmPanelAudio');
            panelText.hidden = name !== 'text';
            panelAudio.hidden = name !== 'audio';
            panelText.setAttribute('aria-hidden', name !== 'text' ? 'true' : 'false');
            panelAudio.setAttribute('aria-hidden', name !== 'audio' ? 'true' : 'false');
            applyTranscriptPaneVisibility();
          });
        });

        document.getElementById('mmGenText').addEventListener('click', function () {
          var title = (document.getElementById('mmTitleText').value || '').trim();
          if (!title) {
            alert('제목을 입력해 주세요.');
            return;
          }
          var sourceText = (document.getElementById('mmSourceText').value || '').trim();
          if (!sourceText) {
            alert('회의 원문을 입력해 주세요.');
            return;
          }
          setMeetingGenerating(true, '회의 원문을 보내고 있습니다…');
          api('/api/meeting-minutes/generate-text', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
              title: title,
              meetingDate: document.getElementById('mmDateText').value,
              sourceText: sourceText,
              model: document.getElementById('mmModelText').value
            })
          })
            .then(function (d) {
              resultSection.hidden = false;
              setCurrentMeetingId(d.meeting && d.meeting.id);
              lastMeetingTranscriptIsAudio = false;
              if (transcriptLabel) transcriptLabel.textContent = '회의 원문';
              if (transcriptBody) transcriptBody.value = (d.meeting && d.meeting.sourceText) || '';
              resultBody.value = (d.meeting && d.meeting.generatedMinutes) || '';
              setMinutesViewMode(false);
              applyTranscriptPaneVisibility();
              loadMeetings();
              var cs = d.checklistSync;
              if (cs && cs.imported > 0) {
                alert('업무 체크리스트에 ' + cs.imported + '건이 자동 반영되었습니다. (업무 체크리스트 AI에서 확인)');
              } else if (cs && cs.extractError && !cs.disabled) {
                console.warn('체크리스트 자동 추출:', cs.extractError);
              }
            })
            .catch(function (e) {
              alert(e.message || '생성 실패');
            })
            .finally(function () {
              setMeetingGenerating(false);
            });
        });

        document.getElementById('mmGenAudio').addEventListener('click', function () {
          var fileInput = document.getElementById('mmAudioFile');
          if (!fileInput.files || !fileInput.files.length) {
            alert('음성 파일을 선택해 주세요.');
            return;
          }
          var audioTitle = (document.getElementById('mmTitleAudio').value || '').trim();
          if (!audioTitle) {
            alert('제목을 입력해 주세요.');
            return;
          }
          var fd = new FormData();
          fd.append('audio', fileInput.files[0]);
          fd.append('title', audioTitle);
          fd.append('meetingDate', document.getElementById('mmDateAudio').value);
          fd.append('model', document.getElementById('mmModelAudio').value);
          fd.append('whisperModel', document.getElementById('mmWhisperModel').value);
          setMeetingGenerating(true, '음성 파일을 업로드하고 전사합니다. 길이에 따라 1분 이상 걸릴 수 있습니다…');
          fetch('/api/meeting-minutes/generate-audio', { method: 'POST', body: fd, credentials: 'same-origin' })
            .then(function (r) {
              return r.json().then(function (j) {
                if (!r.ok) throw new Error(j.error || r.statusText);
                return j;
              });
            })
            .then(function (d) {
              resultSection.hidden = false;
              setCurrentMeetingId(d.meeting && d.meeting.id);
              lastMeetingTranscriptIsAudio = true;
              if (transcriptLabel) transcriptLabel.textContent = '전사 기록';
              if (transcriptBody) transcriptBody.value = (d.meeting && d.meeting.transcriptText) || '';
              resultBody.value = (d.meeting && d.meeting.generatedMinutes) || '';
              setMinutesViewMode(false);
              applyTranscriptPaneVisibility();
              fileInput.value = '';
              loadMeetings();
              var cs = d.checklistSync;
              if (cs && cs.imported > 0) {
                alert('업무 체크리스트에 ' + cs.imported + '건이 자동 반영되었습니다. (업무 체크리스트 AI에서 확인)');
              } else if (cs && cs.extractError && !cs.disabled) {
                console.warn('체크리스트 자동 추출:', cs.extractError);
              }
            })
            .catch(function (e) {
              alert(e.message || '생성 실패');
            })
            .finally(function () {
              setMeetingGenerating(false);
            });
        });

        applyTranscriptPaneVisibility();
        loadPrompt().then(loadMeetings).catch(function () {});
      })();
    </script>
    <script>
      (function () {
        var panel = document.getElementById('mmPromptPanel');
        var btn = document.getElementById('mmPromptToggle');
        var body = document.getElementById('mmPromptBody');
        if (!panel || !btn || !body) return;
        function applyOpen(open) {
          if (open) {
            panel.classList.remove('mm-prompt-collapsed');
            body.hidden = false;
            btn.setAttribute('aria-expanded', 'true');
          } else {
            panel.classList.add('mm-prompt-collapsed');
            body.hidden = true;
            btn.setAttribute('aria-expanded', 'false');
          }
        }
        try {
          if (localStorage.getItem('meetingMinutesPromptOpen') === '1') {
            applyOpen(true);
          }
        } catch (e) {}
        btn.addEventListener('click', function () {
          var nowCollapsed = panel.classList.toggle('mm-prompt-collapsed');
          body.hidden = nowCollapsed;
          btn.setAttribute('aria-expanded', String(!nowCollapsed));
          try {
            localStorage.setItem('meetingMinutesPromptOpen', nowCollapsed ? '0' : '1');
          } catch (e) {}
        });
      })();
    </script>
  </body>
</html>
