@extends('admin.layouts.app')

@section('title', '관리자 SMS 발송')
@section('page_title', '관리자 SMS 발송')
@section('page_desc', '단건 / 대량(붙여넣기) / CSV 업로드 발송')

@push('head')
    <style>
        /* 이 페이지에서만 쓰는 로컬 스타일 */
        .sms-grid{display:grid;grid-template-columns:1fr 1.15fr;gap:16px;align-items:start;}
        @media (max-width: 980px){.sms-grid{grid-template-columns:1fr;}}

        .sms-toolbar{display:flex;gap:12px;flex-wrap:wrap;align-items:end;}
        .sms-badges{margin-left:auto;display:flex;gap:10px;align-items:center;}

        .sms-seg{display:inline-flex;gap:4px;padding:4px;border-radius:999px;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.08);}
        .sms-tab{padding:8px 12px;font-size:13px;border-radius:999px;line-height:1;border:0;background:transparent;color:inherit;cursor:pointer;white-space:nowrap;}
        .sms-tab.is-active{background:rgba(255,255,255,.14);}

        .sms-btn{padding:8px 12px;font-size:13px;border-radius:12px;line-height:1.1;text-decoration:none;display:inline-flex;align-items:center;justify-content:center;gap:6px;
            border:1px solid rgba(255,255,255,.10);background:rgba(255,255,255,.06);color:inherit;cursor:pointer;white-space:nowrap;}
        .sms-btn:hover{background:rgba(255,255,255,.10);}
        .sms-btn--primary{background:rgba(59,130,246,.88);border-color:rgba(59,130,246,.95);color:#fff;}
        .sms-btn--primary:hover{background:rgba(59,130,246,.98);}
        .sms-btn--ghost{background:transparent;}

        .sms-badge{padding:6px 10px;border-radius:999px;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.08);font-size:12px;}
        .sms-badge--type{font-weight:800;}

        .sms-actions{display:flex;gap:10px;justify-content:flex-end;flex-wrap:wrap;}
        .sms-row{display:flex;gap:10px;flex-wrap:wrap;align-items:center;}
        .sms-select{min-width:280px;}

        .warn{color:#ff6b6b;font-weight:800;}
        .hintBox{
            margin-top:10px;padding:10px 12px;border-radius:12px;
            background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.08);
            font-size:12px;line-height:1.6;
        }
        .hintBox code{background:rgba(0,0,0,.25);padding:2px 6px;border-radius:8px;}
        .mmono{font-family:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;}

        /* ===== 예약발송 Picker(메일과 동일 패턴) ===== */
        .smsModal{display:none; position:fixed; inset:0; z-index:9999;}
        .smsModal__back{position:absolute; inset:0; background:rgba(0,0,0,.55);}
        .smsModal__box{
            position:relative;
            width:520px; max-width:calc(100vw - 24px);
            height:auto; max-height:calc(100vh - 24px);
            margin:12px auto;
            top:50%; transform:translateY(-50%);
            border:1px solid rgba(255,255,255,.12);
            background:rgba(20,20,20,.94);
            border-radius:16px;
            overflow:hidden;
            box-shadow:0 18px 60px rgba(0,0,0,.45);
        }
        .smsModal__head{
            display:flex; justify-content:space-between; align-items:center;
            padding:10px 12px;
            border-bottom:1px solid rgba(255,255,255,.10);
        }
        .smsModal__body{padding:12px;}
        .smsModal__hint{font-size:12px; opacity:.75; margin-top:6px;}
    </style>
@endpush

@section('content')
    <form method="POST" action="{{ route('admin.sms.send.store') }}" enctype="multipart/form-data" id="smsSendForm">
        @csrf

        {{-- ✅ send_mode: one | many | template(CSV 업로드) --}}
        <input type="hidden" name="send_mode" id="sendMode" value="one">
        <input type="hidden" name="sms_type_hint" id="smsTypeHint" value="auto">

        {{-- 상단 바 --}}
        <div class="a-card" style="padding:16px; margin-bottom:16px;">
            <div class="sms-toolbar">
                <div style="min-width:280px;">
                    <div class="a-muted" style="margin-bottom:6px;">발신번호</div>
                    <input class="a-input" name="from_number" value="{{ config('services.sms.from','1833-4856') }}" readonly>
                </div>

                <div style="min-width:340px;">
                    <div class="a-muted" style="margin-bottom:6px;">발송 시점</div>
                    <div class="sms-row">
                        <label class="a-pill"><input type="radio" name="schedule_type" value="now" checked> 즉시</label>
                        <label class="a-pill"><input type="radio" name="schedule_type" value="schedule"> 예약</label>

                        {{-- ✅ 직접 입력 불가(실수 방지): readonly + 선택 버튼 --}}
                        <input class="a-input" type="text" name="scheduled_at" id="scheduledAt"
                               placeholder="YYYY-MM-DD HH:mm" style="width:180px" disabled readonly>
                        <button type="button" class="sms-btn" id="openSchedulePicker" disabled>선택</button>
                    </div>
                </div>

                <div class="sms-badges">
                    <div class="sms-badge sms-badge--type" id="smsTypeBadge">SMS</div>
                    <div class="sms-badge" id="byteBadge">0 Bytes</div>
                </div>
            </div>
        </div>

        <div class="sms-grid">
            {{-- 좌측: 수신/모드 --}}
            <div class="a-card" style="padding:16px;">
                <div class="a-muted" style="margin-bottom:8px;">발송 유형</div>

                <div class="sms-seg" style="margin-bottom:12px;">
                    <button type="button" class="sms-tab is-active" data-tab="one">단건</button>
                    <button type="button" class="sms-tab" data-tab="many">대량(붙여넣기)</button>
                    <button type="button" class="sms-tab" data-tab="template">CSV 업로드</button>
                </div>

                {{-- 단건 --}}
                <section data-panel="one">
                    <div class="a-muted" style="margin-bottom:6px;">수신번호 (1건)</div>
                    <input class="a-input" name="to_number" id="toNumber"
                           placeholder="01012345678 또는 01012345678,이름,금지어,18000,생존템,발송"
                           value="{{ old('to_number') }}">

                    <div class="a-muted" style="margin-top:8px; line-height:1.6;">
                        • <b>전화번호만</b> 입력하면 그대로 1건 발송합니다.<br>
                        • <b>콤마(,)</b>로 뒤에 값을 붙이면 토큰이 치환됩니다.<br>
                        &nbsp;&nbsp;예) <span class="mmono">010...,홍길동,쿠폰,18000,다날,발송</span><br>
                        &nbsp;&nbsp;→ <span class="mmono">{_text_02_}=홍길동</span>, <span class="mmono">{_text_03_}=쿠폰</span>, <span class="mmono">{_text_04_}=18000</span>, <span class="mmono">{_text_05_}=다날</span>, <span class="mmono">{_text_06_}=발송</span>
                    </div>
                </section>

                {{-- 대량(붙여넣기) --}}
                <section data-panel="many" style="display:none;">
                    <div style="display:flex; justify-content:space-between; align-items:center; gap:8px;">
                        <div class="a-muted">수신번호 (여러건 · 붙여넣기)</div>
                        <div class="a-muted" id="manyCountText">0건</div>
                    </div>

                    <textarea class="a-input" name="to_numbers_text" id="toNumbersText" rows="10"
                              placeholder="전화번호만 1줄에 1개씩 입력하세요.
예)
01011112222
01033366660

• 최대 100건(중복 제거 후)
• 100건 초과 또는 개인화(토큰) 발송은 CSV 업로드를 사용하세요.">{{ old('to_numbers_text') }}</textarea>

                    <div class="sms-row" style="margin-top:10px; justify-content:space-between;">
                        <div class="a-muted" id="manyStats"></div>
                        <button type="button" class="sms-btn sms-btn--ghost" id="clearMany">비우기</button>
                    </div>

                    <div class="a-muted" style="margin-top:8px; line-height:1.6;">
                        • 붙여넣기 대량 발송은 <b>전화번호만</b> 지원합니다. (<b>토큰 치환 불가</b>)<br>
                        • 토큰(<span class="mmono">{_text_02_}</span> 등)을 쓰려면 <b>단건(콤마)</b> 또는 <b>CSV 업로드</b>를 이용하세요.
                    </div>
                </section>

                {{-- CSV 업로드(내부 send_mode=template) --}}
                <section data-panel="template" style="display:none;">
                    <div style="display:flex; gap:10px; align-items:center; flex-wrap:wrap;">
                        <label class="sms-btn">
                            CSV 업로드
                            <input type="file" name="template_csv" id="templateCsv" accept=".csv,.txt" style="display:none;">
                        </label>
                        <span class="a-muted" style="font-size:12px;">
                            • 1열: <b>수신번호</b> / 2열~: <b>치환값(선택)</b>
                        </span>
                    </div>

                    <div class="hintBox">
                        • CSV가 <b>전화번호만</b> 있어도 발송 가능합니다 (<b>문구에 토큰이 없을 때</b>).<br>
                        • 문구에 <code>{_text_02_}</code> 같은 토큰을 넣으면 CSV에 2열 이상(치환값)이 필요합니다.<br>
                        • 토큰은 <b>{_text_02_}부터 연속</b>으로 사용하세요. (예: {_text_02_}, {_text_03_}, {_text_04_} …)
                    </div>

                    {{-- CSV 미리보기 --}}
                    <div id="tplCsvPreviewWrap"
                         style="display:none; margin-top:12px; padding:12px; border-radius:14px; background:rgba(255,255,255,.03); border:1px solid rgba(255,255,255,.08);">
                        <div style="display:flex; justify-content:space-between; align-items:center; gap:10px; flex-wrap:wrap;">
                            <div style="font-weight:800;">CSV 미리보기 (상위 5줄)</div>
                            <button type="button" class="sms-btn" id="tplCsvClearBtn">파일 비우기</button>
                        </div>

                        <div class="a-muted" id="tplCsvMeta" style="margin-top:8px; line-height:1.6;"></div>

                        <div style="overflow:auto; margin-top:10px;">
                            <table class="a-table" style="width:100%; min-width:720px;">
                                <thead>
                                <tr id="tplCsvHeadRow"></tr>
                                </thead>
                                <tbody id="tplCsvBodyRows"></tbody>
                            </table>
                        </div>

                        <div class="a-muted" id="tplCsvHint" style="margin-top:10px; line-height:1.7;"></div>
                    </div>

                    <div class="a-muted" id="tplCsvError" style="display:none; margin-top:10px; line-height:1.6; color:#ff6b6b;"></div>

                    {{-- 이용 안내 --}}
                    <div style="margin-top:12px; padding:12px; border-radius:14px; background:rgba(255,255,255,.03); border:1px solid rgba(255,255,255,.08);">
                        <div style="font-weight:800; margin-bottom:8px;">CSV 이용 안내</div>

                        <div class="a-muted" style="line-height:1.7;">
                            <div style="margin-bottom:10px;">
                                <b>1) CSV 파일 작성 요령</b><br>
                                <span class="a-muted">각 줄이 “1명”이며, 쉼표(<code>,</code>)로 컬럼을 구분합니다.</span>

                                <div style="margin-top:8px;">
                                    <div class="a-muted" style="margin-bottom:6px;">예시</div>
                                    <div style="
                                        padding:8px 10px;
                                        border-radius:12px;
                                        background:rgba(0,0,0,.25);
                                        border:1px solid rgba(255,255,255,.06);
                                        font-family:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
                                        font-size:12px; line-height:1.45; text-align:left;">
<pre style="margin:0; white-space:pre;">01036828958,홍길동,10000,쿠폰
01036828901,이순신,20000,상품권
01036828902,김개똥,30000,쿠폰</pre>
                                    </div>
                                </div>

                                <ul style="margin:10px 0 0 18px;">
                                    <li><b>1번째 컬럼</b>: 수신 전화번호(고정)</li>
                                    <li><b>2번째 컬럼</b>: 치환값 1 (→ <code>{_text_02_}</code>)</li>
                                    <li><b>3번째 컬럼</b>: 치환값 2 (→ <code>{_text_03_}</code>)</li>
                                    <li><b>4번째 컬럼</b>: 치환값 3 (→ <code>{_text_04_}</code>)</li>
                                </ul>
                            </div>

                            <div>
                                <b>2) 토큰을 쓰지 않으면?</b><br>
                                <span class="a-muted">문구에 토큰이 없으면 CSV는 “전화번호만(1열)” 있어도 정상 발송됩니다.</span>
                            </div>
                        </div>
                    </div>

                </section>

            </div>

            {{-- 우측: 문구/프리셋/액션 --}}
            <div style="display:flex; flex-direction:column; gap:16px;">
                <div class="a-card" style="padding:16px;">
                    <div class="a-muted" style="margin-bottom:6px;">문구 템플릿(프리셋) 선택</div>

                    <div class="sms-row">
                        <select class="a-input sms-select" id="tplSelect">
                            <option value="">선택하세요</option>
                            @foreach(($templates ?? []) as $t)
                                <option value="{{ $t->id ?? $t['id'] }}">
                                    {{ ($t->title ?? $t['title']) }} ({{ $t->code ?? $t['code'] }})
                                </option>
                            @endforeach
                        </select>

                        <button type="button" class="sms-btn" id="tplApplyReplace">덮어쓰기</button>
                        <button type="button" class="sms-btn sms-btn--ghost" id="tplApplyAppend">뒤에붙이기</button>
                    </div>

                    <div class="a-muted" style="margin-top:8px;">* “덮어쓰기”는 현재 문구를 교체합니다.</div>
                </div>

                <div class="a-card" style="padding:16px;">
                    <div class="a-muted" style="margin-bottom:6px;">발송 문구</div>
                    <textarea class="a-input" name="message" id="messageText" rows="10" placeholder="메시지를 입력하세요">{{ old('message') }}</textarea>

                    <div class="sms-row" style="margin-top:10px;">
                        <button type="button" class="sms-btn sms-btn--ghost" data-insert="{_text_02_}">{_text_02_}</button>
                        <button type="button" class="sms-btn sms-btn--ghost" data-insert="{_text_03_}">{_text_03_}</button>
                        <button type="button" class="sms-btn sms-btn--ghost" data-insert="{_text_04_}">{_text_04_}</button>
                        <span class="a-muted">토큰 빠른 삽입</span>
                    </div>

                    <div class="a-muted" style="margin-top:8px; line-height:1.6;">
                        • 토큰 치환은 <b>단건(콤마 입력)</b> 또는 <b>CSV 업로드</b>에서만 적용됩니다. (붙여넣기 대량은 미적용)
                    </div>

                    <hr style="margin:16px 0; opacity:.15;">

                    <div class="sms-actions">
                        <button type="submit" class="sms-btn sms-btn--primary">발송</button>
                        <a class="sms-btn" href="{{ route('admin.sms.logs') }}">발송 이력</a>
                    </div>

                    <div class="a-muted" style="margin-top:8px;">* 90 bytes 초과는 MMS 표시(최종은 서버 기준)</div>
                </div>
            </div>
        </div>
    </form>

    {{-- ===== 예약발송 날짜/시간/분(5분단위) 선택 모달 (추가) ===== --}}
    <div id="schedulePickerModal" class="smsModal" aria-hidden="true">
        <div id="schedulePickerBackdrop" class="smsModal__back"></div>

        <div class="smsModal__box" role="dialog" aria-modal="true" aria-label="예약 발송 시간 선택">
            <div class="smsModal__head">
                <div>
                    <div style="font-weight:900;">예약 발송 시간 선택</div>
                    <div class="smsModal__hint">날짜 · 시간 · 분(5분 단위)</div>
                </div>
                <div style="display:flex; gap:8px; align-items:center;">
                    <button type="button" class="sms-btn" id="closeSchedulePicker">닫기</button>
                </div>
            </div>

            <div class="smsModal__body">
                <div class="a-card" style="padding:14px;">
                    <div class="sms-row" style="align-items:end;">
                        <div>
                            <div class="a-muted" style="margin-bottom:6px;">날짜</div>
                            <input class="a-input" type="date" id="schDate" style="width:180px;">
                        </div>

                        <div>
                            <div class="a-muted" style="margin-bottom:6px;">시간</div>
                            <select class="a-input" id="schHour" style="width:120px;"></select>
                        </div>

                        <div>
                            <div class="a-muted" style="margin-bottom:6px;">분</div>
                            <select class="a-input" id="schMin" style="width:120px;"></select>
                        </div>
                    </div>

                    <div class="sms-row" style="margin-top:12px; justify-content:flex-end;">
                        <button type="button" class="sms-btn sms-btn--ghost" id="resetSchedulePicker">초기화</button>
                        <button type="button" class="sms-btn sms-btn--primary" id="applySchedulePicker">적용</button>
                    </div>

                    <div class="a-muted" style="font-size:12px; margin-top:10px; opacity:.85;">
                        선택 결과는 <span class="mmono">YYYY-MM-DD HH:mm</span> 형식으로 저장됩니다.
                        (서비스에서 <span class="mmono">:00</span> 붙여 처리)
                    </div>
                </div>
            </div>
        </div>
    </div>

    @push('scripts')
        <script>
            (() => {
                const form = document.getElementById('smsSendForm');
                if (!form) return;

                const templates = @json($templates ?? []);

                // tabs
                const tabBtns = Array.from(form.querySelectorAll('[data-tab]'));
                const panels  = Array.from(form.querySelectorAll('[data-panel]'));
                const sendModeEl = document.getElementById('sendMode');

                function setTab(tab){
                    tabBtns.forEach(b => b.classList.toggle('is-active', b.dataset.tab === tab));
                    panels.forEach(p => p.style.display = (p.dataset.panel === tab) ? '' : 'none');
                    sendModeEl.value = tab; // one | many | template
                }
                tabBtns.forEach(b => b.addEventListener('click', () => setTab(b.dataset.tab)));

                // ===== schedule (기존 로직 유지 + 선택 버튼/모달만 추가)
                const scheduledAt = document.getElementById('scheduledAt');
                const openSchBtn  = document.getElementById('openSchedulePicker');

                // 직접 입력/붙여넣기 방지(실수 차단)
                if (scheduledAt){
                    scheduledAt.readOnly = true;
                    scheduledAt.addEventListener('keydown', (e) => { if (e.key === 'Tab') return; e.preventDefault(); });
                    scheduledAt.addEventListener('paste', (e) => e.preventDefault());
                    scheduledAt.addEventListener('drop',  (e) => e.preventDefault());
                }

                form.querySelectorAll('input[name="schedule_type"]').forEach(r => {
                    r.addEventListener('change', () => {
                        const isSch = form.querySelector('input[name="schedule_type"][value="schedule"]').checked;
                        scheduledAt.disabled = !isSch;
                        if (openSchBtn) openSchBtn.disabled = !isSch;
                        if(!isSch) scheduledAt.value = '';
                    });
                });

                // ===== 예약발송 Picker modal (추가)
                const schModalEl  = document.getElementById('schedulePickerModal');
                const schBackEl   = document.getElementById('schedulePickerBackdrop');
                const schCloseBtn = document.getElementById('closeSchedulePicker');

                const schDateEl   = document.getElementById('schDate');
                const schHourEl   = document.getElementById('schHour');
                const schMinEl    = document.getElementById('schMin');

                const schApplyBtn = document.getElementById('applySchedulePicker');
                const schResetBtn = document.getElementById('resetSchedulePicker');

                function pad2(n){ return String(n).padStart(2,'0'); }
                function formatScheduledAt(dateStr, hh, mm){
                    return `${dateStr} ${pad2(hh)}:${pad2(mm)}`;
                }
                function parseScheduledAt(v){
                    const s = String(v || '').trim();
                    const m = s.match(/^(\d{4}-\d{2}-\d{2})\s+(\d{2}):(\d{2})$/);
                    if(!m) return null;
                    return { date: m[1], hh: Number(m[2]), mm: Number(m[3]) };
                }
                function next5min(){
                    const d = new Date();
                    d.setSeconds(0,0);
                    const m = d.getMinutes();
                    const add = (5 - (m % 5)) % 5;
                    d.setMinutes(m + add);
                    return d;
                }
                function toDateInputValue(d){
                    const y = d.getFullYear();
                    const m = pad2(d.getMonth()+1);
                    const day = pad2(d.getDate());
                    return `${y}-${m}-${day}`;
                }

                function buildSelectOptions(){
                    if (schHourEl && schHourEl.options.length === 0){
                        for(let h=0; h<24; h++){
                            const opt = document.createElement('option');
                            opt.value = String(h);
                            opt.textContent = pad2(h);
                            schHourEl.appendChild(opt);
                        }
                    }
                    if (schMinEl && schMinEl.options.length === 0){
                        for(let m=0; m<60; m+=5){
                            const opt = document.createElement('option');
                            opt.value = String(m);
                            opt.textContent = pad2(m);
                            schMinEl.appendChild(opt);
                        }
                    }
                }

                function fillPickerFromCurrent(){
                    buildSelectOptions();

                    const cur = parseScheduledAt(scheduledAt?.value || '');
                    if (cur){
                        if (schDateEl) schDateEl.value = cur.date;
                        if (schHourEl) schHourEl.value = String(cur.hh);
                        if (schMinEl)  schMinEl.value  = String(cur.mm - (cur.mm % 5));
                        return;
                    }

                    const d = next5min();
                    if (schDateEl) schDateEl.value = toDateInputValue(d);
                    if (schHourEl) schHourEl.value = String(d.getHours());
                    if (schMinEl)  schMinEl.value  = String(d.getMinutes() - (d.getMinutes()%5));
                }

                let schModalOpen = false;

                function openScheduleModal(){
                    if(!schModalEl) return;
                    fillPickerFromCurrent();
                    schModalEl.style.display = 'block';
                    document.documentElement.style.overflow = 'hidden';
                    schModalOpen = true;
                }
                function closeScheduleModal(){
                    if(!schModalEl) return;
                    schModalEl.style.display = 'none';
                    document.documentElement.style.overflow = '';
                    schModalOpen = false;
                }

                openSchBtn?.addEventListener('click', () => {
                    const isSch = form.querySelector('input[name="schedule_type"][value="schedule"]')?.checked;
                    if (!isSch) return;
                    openScheduleModal();
                });

                schCloseBtn?.addEventListener('click', closeScheduleModal);
                schBackEl?.addEventListener('click', closeScheduleModal);

                schResetBtn?.addEventListener('click', () => {
                    if (scheduledAt) scheduledAt.value = '';
                    fillPickerFromCurrent();
                });

                schApplyBtn?.addEventListener('click', () => {
                    const dateStr = (schDateEl?.value || '').trim();
                    const hh = Number(schHourEl?.value || 0);
                    const mm = Number(schMinEl?.value || 0);

                    if (!dateStr){
                        alert('날짜를 선택하세요.');
                        return;
                    }
                    if (scheduledAt){
                        scheduledAt.value = formatScheduledAt(dateStr, hh, mm); // "YYYY-MM-DD HH:mm"
                    }
                    closeScheduleModal();
                });

                // 초기 schedule 상태 반영
                (() => {
                    const isSch = form.querySelector('input[name="schedule_type"][value="schedule"]')?.checked;
                    if (openSchBtn) openSchBtn.disabled = !isSch;
                })();

                // byte count
                const msg = document.getElementById('messageText');
                const byteBadge = document.getElementById('byteBadge');
                const typeBadge = document.getElementById('smsTypeBadge');
                const smsTypeHint = document.getElementById('smsTypeHint');

                function calcBytes(str){
                    let byte = 0;
                    for (let i=0;i<str.length;i++){
                        const c = str.charCodeAt(i);
                        byte += (c > 127) ? 2 : 1;
                    }
                    return byte;
                }
                function refreshBytes(){
                    const b = calcBytes(msg?.value || '');
                    if (byteBadge) byteBadge.textContent = b + ' Bytes';
                    const type = (b > 90) ? 'mms' : 'sms';
                    if (typeBadge) typeBadge.textContent = type.toUpperCase();
                    if (smsTypeHint) smsTypeHint.value = 'auto';
                }
                msg?.addEventListener('input', refreshBytes);
                refreshBytes();

                // token insert
                form.querySelectorAll('[data-insert]').forEach(btn => {
                    btn.addEventListener('click', () => {
                        if(!msg) return;
                        const token = btn.getAttribute('data-insert') || '';
                        const start = msg.selectionStart ?? msg.value.length;
                        const end   = msg.selectionEnd ?? msg.value.length;
                        msg.value = msg.value.slice(0,start) + token + msg.value.slice(end);
                        msg.focus();
                        msg.selectionStart = msg.selectionEnd = start + token.length;
                        msg.dispatchEvent(new Event('input', { bubbles: true }));
                    });
                });

                // many stats + clear
                const toNumbersText = document.getElementById('toNumbersText');
                const manyCountText = document.getElementById('manyCountText');
                const manyStats     = document.getElementById('manyStats');
                const clearMany     = document.getElementById('clearMany');

                function parsePhonesRough(text){
                    const lines = (text || '')
                        .split(/\r\n|\n|\r/)
                        .map(l => l.trim())
                        .filter(Boolean);

                    const digits = [];
                    for (const line of lines) {
                        // ✅ 콤마가 있으면 "첫 토큰(수신번호)"만 사용
                        const first = line.split(',')[0].trim();
                        const d = first.replace(/\D+/g, '');
                        if (d) digits.push(d);
                    }

                    const valid = digits.filter(v => /^01\d{8,9}$/.test(v));
                    const invalid = digits.length - valid.length;
                    const uniq = Array.from(new Set(valid));

                    return { total: digits.length, valid: valid.length, uniq: uniq.length, invalid };
                }

                function parseManyLines(text){
                    const lines = (text || '')
                        .split(/\r\n|\n|\r/)
                        .map(s => s.trim())
                        .filter(Boolean);

                    let total = lines.length;
                    let invalid = 0;
                    const validPhones = [];

                    for (const line of lines){
                        const first = line.split(',')[0].trim();     // ✅ 콤마 앞만 phone
                        const digits = first.replace(/\D+/g,'');
                        if(/^01\d{8,9}$/.test(digits)) validPhones.push(digits);
                        else invalid++;
                    }

                    const uniq = Array.from(new Set(validPhones));
                    const dup = validPhones.length - uniq.length;

                    return { total, valid: validPhones.length, uniq: uniq.length, invalid, dup };
                }

                function refreshMany(){
                    if(!toNumbersText) return;
                    const r = parseManyLines(toNumbersText.value);
                    if (manyCountText) manyCountText.textContent = r.uniq + '건';
                    if (manyStats) {
                        const over = r.uniq > 100;
                        manyStats.innerHTML =
                            `입력 ${r.total} / 유효 ${r.valid} / 중복제거 ${r.uniq} / 오류 ${r.invalid}` +
                            (over ? ` <span class="warn">→ 100건 초과 (CSV 업로드 사용)</span>` : '');
                    }
                }
                toNumbersText?.addEventListener('input', refreshMany);
                clearMany?.addEventListener('click', () => {
                    if(toNumbersText){ toNumbersText.value=''; refreshMany(); }
                });
                refreshMany();

                // template apply (프리셋)
                const sel  = document.getElementById('tplSelect');
                const btnR = document.getElementById('tplApplyReplace');
                const btnA = document.getElementById('tplApplyAppend');

                function getSelectedBody(){
                    const id = Number(sel?.value || 0);
                    if (!id) return '';
                    const t = templates.find(x => Number(x.id) === id);
                    if (!t) return '';
                    return String(t.body ?? t.content ?? t.message ?? '');
                }

                function apply(mode){
                    if (!msg) return;
                    const body = getSelectedBody();
                    if (!body) { alert('템플릿을 선택하세요.'); return; }

                    if (mode === 'replace') msg.value = body;
                    else msg.value = msg.value ? (msg.value + "\n" + body) : body;

                    msg.dispatchEvent(new Event('input', { bubbles: true }));
                    msg.focus();
                }

                btnR?.addEventListener('click', () => apply('replace'));
                btnA?.addEventListener('click', () => apply('append'));

                // helper: extract tokens indexes [2..9]
                function extractTokenIdxs(text){
                    const s = String(text || '');
                    const m = s.match(/\{_text_0([2-9])_\}/g) || [];
                    const idxs = Array.from(new Set(m.map(t => Number((t.match(/\{_text_0([2-9])_\}/)||[])[1]))))
                        .filter(n => n >= 2 && n <= 9)
                        .sort((a,b)=>a-b);
                    return idxs;
                }
                function isContiguousFrom2(idxs){
                    if (!idxs.length) return true;
                    if (idxs[0] !== 2) return false;
                    for (let i=0;i<idxs.length;i++){
                        if (idxs[i] !== 2 + i) return false;
                    }
                    return true;
                }

                // CSV preview 상태(제출 전 검증용)
                window.__CSV_MAX_COLS__ = 0;

                // submit check (최종 검증은 서버)
                form.addEventListener('submit', (e) => {
                    const mode = sendModeEl?.value || 'one';
                    const m = (msg?.value || '').trim();
                    if(!m){ e.preventDefault(); alert('발송 문구를 입력하세요.'); return; }

                    // ✅ many도 토큰 치환 지원 (1줄=1명: "전화번호,치환값...")
                    if(mode === 'many'){
                        const idxs = extractTokenIdxs(m);

                        // 토큰 연속성(02부터)만 강제
                        if (idxs.length && !isContiguousFrom2(idxs)) {
                            e.preventDefault();
                            alert('토큰은 {_text_02_}부터 연속으로 사용해야 합니다. (예: {_text_02_}, {_text_03_}, {_text_04_}...)');
                            return;
                        }

                        // 줄 단위로 파싱(콤마로 전체 분해하면 안됨)
                        const lines = (toNumbersText?.value || '')
                            .split(/\r\n|\n|\r/)
                            .map(s => s.trim())
                            .filter(Boolean);

                        if (lines.length < 1) {
                            e.preventDefault();
                            alert('대량 수신번호를 입력하세요.');
                            return;
                        }

                        // 유효번호 + 중복제거
                        const validPhones = [];
                        let invalid = 0;

                        for (const line of lines){
                            const first = (line.split(',')[0] || '').trim();
                            const digits = first.replace(/\D+/g,'');
                            if(/^01\d{8,9}$/.test(digits)) validPhones.push(digits);
                            else invalid++;
                        }

                        const uniqPhones = Array.from(new Set(validPhones));

                        if (uniqPhones.length > 100) {
                            e.preventDefault();
                            alert('대량(붙여넣기)은 최대 100건입니다. 100건 초과는 CSV 업로드를 이용해 주세요.');
                            return;
                        }

                        // 토큰이 있으면, 각 줄마다 치환값 개수 체크
                        if (idxs.length){
                            const needArgs = idxs.length; // {_text_02_}부터 연속 개수

                            for (let i=0;i<lines.length;i++){
                                const parts = lines[i].split(',').map(s=>s.trim());
                                const phone = (parts[0] || '').replace(/\D+/g,'');
                                if(!/^01\d{8,9}$/.test(phone)) continue; // invalid 줄은 서버에서 카운트

                                const argsCnt = Math.max(0, parts.length - 1);
                                if (argsCnt < needArgs){
                                    e.preventDefault();
                                    alert(`대량 ${i+1}번째 줄: 토큰 ${needArgs}개({_text_02_}~)를 쓰고 있어 치환값이 ${needArgs}개 필요합니다.\n형식: 전화번호,값,값,...`);
                                    return;
                                }
                            }
                        }
                    }

                    if(mode === 'one'){
                        const raw = (document.getElementById('toNumber')?.value || '').trim();
                        const first = raw.split(',')[0].trim();
                        const v = first.replace(/\D+/g,'');

                        if(!/^01\d{8,9}$/.test(v)){
                            e.preventDefault();
                            alert('수신번호(단건)를 올바르게 입력하세요. (예: 01012345678 또는 01012345678,이름,...)');
                            return;
                        }
                    }

                    if(mode === 'many'){
                        const r = parsePhonesRough(toNumbersText?.value || '');
                        if(r.uniq < 1){ e.preventDefault(); alert('대량 수신번호를 입력하세요.'); return; }
                        if(r.uniq > 100){ e.preventDefault(); alert('대량(붙여넣기)은 최대 100건입니다. 100건 초과는 CSV 업로드를 이용해 주세요.'); return; }
                    }

                    if(mode === 'template'){
                        const hasT = (document.getElementById('templateCsv')?.files || []).length > 0;
                        if(!hasT){ e.preventDefault(); alert('CSV 파일을 업로드하세요.'); return; }

                        const idxs = extractTokenIdxs(m);
                        if (idxs.length){
                            if (!isContiguousFrom2(idxs)){
                                e.preventDefault();
                                alert('토큰은 {_text_02_}부터 연속으로 사용해야 합니다. (예: {_text_02_}, {_text_03_}, {_text_04_}...)');
                                return;
                            }
                            const needArgs = idxs.length;
                            const maxCols = Number(window.__CSV_MAX_COLS__ || 0);
                            if (maxCols > 0 && maxCols < (1 + needArgs)){
                                e.preventDefault();
                                alert(`현재 문구에 토큰 ${needArgs}개가 있습니다. CSV는 최소 ${1+needArgs}개 컬럼(전화번호 + 치환값 ${needArgs}개)이 필요합니다.`);
                                return;
                            }
                        }
                    }

                    const isSch = form.querySelector('input[name="schedule_type"][value="schedule"]')?.checked;
                    if(isSch && !(scheduledAt?.value || '').trim()){
                        e.preventDefault();
                        alert('예약 시간을 선택하세요.');
                        return;
                    }
                });

                // Esc 닫기
                document.addEventListener('keydown', (e) => {
                    if(e.key !== 'Escape') return;
                    if (schModalOpen) closeScheduleModal();
                });

            })();
        </script>

        <script>
            (() => {
                const input = document.getElementById('templateCsv');
                if (!input) return;

                const wrap   = document.getElementById('tplCsvPreviewWrap');
                const meta   = document.getElementById('tplCsvMeta');
                const headTr = document.getElementById('tplCsvHeadRow');
                const bodyTb = document.getElementById('tplCsvBodyRows');
                const hint   = document.getElementById('tplCsvHint');
                const errBox = document.getElementById('tplCsvError');
                const clearBtn = document.getElementById('tplCsvClearBtn');
                const msgEl = document.getElementById('messageText');

                function showErr(msg){
                    if (!errBox) return;
                    errBox.style.display = msg ? '' : 'none';
                    errBox.textContent = msg || '';
                }

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

                function parseCsvLoose(text){
                    const lines = (text || '')
                        .replace(/^\uFEFF/, '') // BOM 제거
                        .split(/\r\n|\n|\r/)
                        .map(l => l.trim())
                        .filter(Boolean);

                    const rows = [];
                    for (const line of lines){
                        const cells = [];
                        let cur = '';
                        let inQ = false;

                        for (let i=0;i<line.length;i++){
                            const ch = line[i];
                            if (ch === '"'){
                                if (inQ && line[i+1] === '"'){ cur += '"'; i++; continue; }
                                inQ = !inQ;
                                continue;
                            }
                            if (ch === ',' && !inQ){
                                cells.push(cur.trim());
                                cur = '';
                            } else {
                                cur += ch;
                            }
                        }
                        cells.push(cur.trim());
                        rows.push(cells);
                    }
                    return rows;
                }

                function normalizePhone(v){
                    return String(v||'').replace(/\D+/g,'');
                }

                function extractTokenIdxs(text){
                    const s = String(text || '');
                    const m = s.match(/\{_text_0([2-9])_\}/g) || [];
                    const idxs = Array.from(new Set(m.map(t => Number((t.match(/\{_text_0([2-9])_\}/)||[])[1]))))
                        .filter(n => n >= 2 && n <= 9)
                        .sort((a,b)=>a-b);
                    return idxs;
                }

                function isContiguousFrom2(idxs){
                    if (!idxs.length) return true;
                    if (idxs[0] !== 2) return false;
                    for (let i=0;i<idxs.length;i++){
                        if (idxs[i] !== 2 + i) return false;
                    }
                    return true;
                }

                function buildPreview(rows){
                    const sample = rows.slice(0, 5);

                    // 최대 컬럼수(파일 전체 기준으로는 무거우니 500줄 정도만 훑음)
                    const scan = rows.slice(0, 500);
                    const maxCols = scan.reduce((m, r) => Math.max(m, (r||[]).length), 0);

                    // 제출 전 UX 검증용
                    window.__CSV_MAX_COLS__ = maxCols;

                    // 헤더
                    headTr.innerHTML = '';
                    for (let c=0;c<Math.max(1,maxCols);c++){
                        const th = document.createElement('th');
                        th.textContent = (c===0) ? 'phone' : `val${c+1}`;
                        headTr.appendChild(th);
                    }

                    // 바디
                    bodyTb.innerHTML = '';
                    sample.forEach((r) => {
                        const tr = document.createElement('tr');
                        for (let c=0;c<Math.max(1,maxCols);c++){
                            const td = document.createElement('td');
                            const val = (r[c] ?? '');
                            td.innerHTML = esc(val);

                            if (c===0){
                                const p = normalizePhone(val);
                                if (!/^01\d{8,9}$/.test(p)) {
                                    td.style.color = '#ff6b6b';
                                    td.style.fontWeight = '800';
                                    td.title = '휴대폰 형식이 올바르지 않습니다 (01X 10~11자리 숫자)';
                                }
                            }
                            tr.appendChild(td);
                        }
                        bodyTb.appendChild(tr);
                    });

                    // 메타/힌트
                    const totalRows = rows.length;
                    const tokenIdxs = extractTokenIdxs(msgEl?.value || '');
                    const hasTokens = tokenIdxs.length > 0;
                    const needArgs = tokenIdxs.length;

                    let tokenInfo = '';
                    if (maxCols <= 1){
                        tokenInfo = '치환값 컬럼이 없습니다. (phone만 존재)';
                    } else {
                        const tokenTo = Math.min(9, 1 + (maxCols - 1));
                        tokenInfo = `치환 가능 토큰(컬럼 기준): {_text_02_} ~ {_text_0${tokenTo}_}`;
                    }

                    meta.innerHTML = `
                        <div>총 <b>${totalRows}</b>줄 · 컬럼수 <b>${maxCols}</b>개</div>
                        <div>${tokenInfo}</div>
                    `;

                    let hintHtml = '';
                    if (maxCols <= 1){
                        hintHtml += `<div>• CSV는 수신번호만 있습니다. 토큰 치환은 불가능합니다.</div>`;
                    } else {
                        hintHtml += `<div>• 1열(phone) → 수신번호</div>`;
                        hintHtml += `<div>• 2열(val2) → <code>{_text_02_}</code></div>`;
                        if (maxCols >= 3) hintHtml += `<div>• 3열(val3) → <code>{_text_03_}</code></div>`;
                        if (maxCols >= 4) hintHtml += `<div>• 4열(val4) → <code>{_text_04_}</code></div>`;
                        if (maxCols > 4)  hintHtml += `<div>• 이후 컬럼은 순서대로 <code>{_text_05_}</code> …</div>`;
                    }
                    hintHtml += `<div class="a-muted" style="margin-top:6px;">* 최종 유효성/중복 제거/치환은 서버에서 한 번 더 처리합니다.</div>`;
                    hint.innerHTML = hintHtml;

                    // 토큰 사용중인데 CSV에 치환값이 부족하면 경고
                    if (hasTokens){
                        if (!isContiguousFrom2(tokenIdxs)){
                            showErr('문구의 토큰은 {_text_02_}부터 연속으로 사용해야 합니다. (예: {_text_02_}, {_text_03_}, {_text_04_}...)');
                            return;
                        }
                        if (maxCols < (1 + needArgs)) {
                            showErr(`현재 문구에 토큰 ${needArgs}개가 있습니다. CSV는 최소 ${1+needArgs}개 컬럼(전화번호 + 치환값 ${needArgs}개)이 필요합니다.`);
                            return;
                        }
                    }
                }

                function handleFile(file){
                    showErr('');
                    window.__CSV_MAX_COLS__ = 0;

                    if (!file){
                        wrap.style.display = 'none';
                        return;
                    }

                    const name = (file.name || '').toLowerCase();
                    if (!name.endsWith('.csv') && !name.endsWith('.txt')){
                        showErr('CSV/TXT 파일만 업로드 가능합니다.');
                        input.value = '';
                        wrap.style.display = 'none';
                        return;
                    }
                    if (file.size > 5 * 1024 * 1024){
                        showErr('파일 용량은 5MB 이하만 가능합니다.');
                        input.value = '';
                        wrap.style.display = 'none';
                        return;
                    }

                    const reader = new FileReader();
                    reader.onload = () => {
                        try{
                            const text = String(reader.result || '');
                            const rows = parseCsvLoose(text);

                            if (!rows.length){
                                showErr('CSV 내용이 비어있습니다.');
                                wrap.style.display = 'none';
                                return;
                            }

                            const badPhone = rows.slice(0, 200).some(r => !/^01\d{8,9}$/.test(normalizePhone(r[0] ?? '')));

                            buildPreview(rows);
                            wrap.style.display = '';

                            if (badPhone && !(errBox?.textContent || '').trim()){
                                showErr('미리보기 범위 내에 휴대폰 형식이 올바르지 않은 줄이 있습니다. (빨간색 표시)');
                            } else if (!badPhone && !(errBox?.textContent || '').trim()){
                                showErr('');
                            }
                        }catch(e){
                            showErr('CSV 파싱 중 오류가 발생했습니다. 파일 형식을 확인해 주세요.');
                            wrap.style.display = 'none';
                        }
                    };
                    reader.onerror = () => {
                        showErr('파일을 읽을 수 없습니다. 다시 시도해 주세요.');
                        wrap.style.display = 'none';
                    };
                    reader.readAsText(file);
                }

                input.addEventListener('change', () => {
                    const file = (input.files || [])[0];
                    handleFile(file);
                });

                clearBtn?.addEventListener('click', () => {
                    input.value = '';
                    wrap.style.display = 'none';
                    showErr('');
                    window.__CSV_MAX_COLS__ = 0;
                });

                // 문구 변경 시 토큰 경고만 갱신
                msgEl?.addEventListener('input', () => {
                    if (!input.files?.length) return;

                    function extractTokenIdxs(text){
                        const s = String(text || '');
                        const m = s.match(/\{_text_0([2-9])_\}/g) || [];
                        const idxs = Array.from(new Set(m.map(t => Number((t.match(/\{_text_0([2-9])_\}/)||[])[1]))))
                            .filter(n => n >= 2 && n <= 9)
                            .sort((a,b)=>a-b);
                        return idxs;
                    }
                    function isContiguousFrom2(idxs){
                        if (!idxs.length) return true;
                        if (idxs[0] !== 2) return false;
                        for (let i=0;i<idxs.length;i++){
                            if (idxs[i] !== 2 + i) return false;
                        }
                        return true;
                    }

                    const idxs = extractTokenIdxs(msgEl.value || '');
                    if (!idxs.length) {
                        if ((errBox?.textContent || '').includes('토큰')) showErr('');
                        return;
                    }
                    if (!isContiguousFrom2(idxs)){
                        showErr('문구의 토큰은 {_text_02_}부터 연속으로 사용해야 합니다. (예: {_text_02_}, {_text_03_}, {_text_04_}...)');
                        return;
                    }
                    const needArgs = idxs.length;
                    const maxCols = Number(window.__CSV_MAX_COLS__ || 0);
                    if (maxCols > 0 && maxCols < (1 + needArgs)){
                        showErr(`현재 문구에 토큰 ${needArgs}개가 있습니다. CSV는 최소 ${1+needArgs}개 컬럼(전화번호 + 치환값 ${needArgs}개)이 필요합니다.`);
                        return;
                    }
                    if ((errBox?.textContent || '').includes('토큰')) showErr('');
                });

            })();
        </script>
    @endpush
@endsection
