/**
 * `/meet` 슬래시 명령의 후처리 — 회의록에서 action items 를 뽑아 캘린더 task 일정을
 * 계산하는 stateless helpers. slashRouter 의 inline 블록을 분리.
 *
 *   - addBusinessDays(base, n) — 토·일 제외 영업일 n 일 후 날짜
 *   - toYmd(d) — Date → 'YYYY-MM-DD'
 *   - extractMeetingDate(report, fallback) — 회의록에서 회의 일자 추출 (없으면 fallback)
 *   - resolveTaskDate(due, meetingDate, today) — 'D+3' / 'EOW' 같은 due 문구를 절대 날짜로 변환
 *   - parseActionItems(report) — 회의록 마크다운 표에서 action items 파싱
 */

// ─── /meet 캘린더 등록 헬퍼 ───

/** 토·일을 제외하고 영업일 n일을 더한 날짜 (공휴일은 고려하지 않음). */
export function addBusinessDays(base: Date, n: number): Date {
    const r = new Date(base);
    let added = 0;
    while (added < n) {
        r.setDate(r.getDate() + 1);
        const day = r.getDay();
        if (day !== 0 && day !== 6) added++;
    }
    return r;
}

/** Date → 'YYYY-MM-DD' (로컬 기준). */
export function toYmd(d: Date): string {
    const y = d.getFullYear();
    const m = String(d.getMonth() + 1).padStart(2, '0');
    const day = String(d.getDate()).padStart(2, '0');
    return `${y}-${m}-${day}`;
}

/**
 * 녹취록 본문(첫 화자 발언) 앞의 헤더 블록을 추출한다.
 * STT 다이어라이제이션 줄("참석자 3 00:46" / "화자 1 0:12") 직전까지를 헤더로 본다.
 * 참석자 명단·일시·장소·녹취 길이가 보통 여기 있다. 헤더가 없거나 비정상적으로
 * 길면(다이어라이제이션 없는 일반 문서) 빈 문자열을 반환한다.
 */
export function extractMeetingHeader(transcript: string): string {
    const lines = transcript.split('\n');
    let cut = -1;
    for (let i = 0; i < lines.length; i++) {
        if (/^\s*(?:참석자|화자|발언자)\s*\d+\s+\d{1,2}:\d{2}/.test(lines[i])) { cut = i; break; }
    }
    if (cut <= 0) return '';
    const header = lines.slice(0, cut).join('\n').trim();
    return header.length > 0 && header.length <= 1200 ? `[회의 헤더]\n${header}` : '';
}

/** 회의록 본문의 "**일시**: 2026년 05월 08일"(구 형식 "**날짜**:" 도 호환)에서 회의 날짜 추출. 없으면 fallback. */
export function extractMeetingDate(report: string, fallback: Date): Date {
    const m = report.match(/(?:일시|날짜)\*{0,2}\s*[:：]\s*\*{0,2}\s*(\d{4})\s*년\s*(\d{1,2})\s*월\s*(\d{1,2})\s*일/);
    if (m) {
        const d = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
        if (!isNaN(d.getTime())) return d;
    }
    return fallback;
}

/**
 * 액션 아이템 '기한' 텍스트 → 캘린더 등록 날짜. 사용자 정의 규칙:
 *  - 명시 날짜(YYYY-MM-DD / YYYY년 M월 D일) → 그 날짜
 *  - "차주 / 다음 주 / 내주" → 회의일 +6일
 *  - "즉시 / 당일 / 금일 / 바로 / 오늘" → 등록일(오늘)
 *  - 변환 불가 / 빈 값 → 등록일 +영업일 5일, tentative=true (제목에 "(미확정)")
 */
export function resolveTaskDate(due: string, meetingDate: Date, today: Date): { date: string; tentative: boolean } {
    const t = (due || '').trim();
    const iso = t.match(/(\d{4})-(\d{1,2})-(\d{1,2})/);
    if (iso) {
        return { date: `${iso[1]}-${iso[2].padStart(2, '0')}-${iso[3].padStart(2, '0')}`, tentative: false };
    }
    const kor = t.match(/(\d{4})\s*년\s*(\d{1,2})\s*월\s*(\d{1,2})\s*일/);
    if (kor) {
        return { date: toYmd(new Date(Number(kor[1]), Number(kor[2]) - 1, Number(kor[3]))), tentative: false };
    }
    if (/차주|다음\s*주|내주/.test(t)) {
        const d = new Date(meetingDate);
        d.setDate(d.getDate() + 6);
        return { date: toYmd(d), tentative: false };
    }
    if (/즉시|당일|금일|바로|오늘/.test(t)) {
        return { date: toYmd(today), tentative: false };
    }
    // 변환 불가 — 등록일 + 영업일 5일, "(미확정)" 꼬리표.
    return { date: toYmd(addBusinessDays(today, 5)), tentative: true };
}

export interface ParsedActionRow { owner: string; work: string; detail: string; deliverable: string; due: string; status: string; source: string }

/** 표 머리글 셀 → 표준 필드 키. 컬럼 순서가 바뀌어도 이름으로 안전하게 매핑한다. */
function headerKey(cell: string): keyof ParsedActionRow | null {
    const c = cell.replace(/\s+/g, '');
    if (c === '담당') return 'owner';
    if (c === '액션' || c === '작업내용') return 'work';
    if (c === '작업상세') return 'detail';
    if (c === '산출물') return 'deliverable';
    if (c === '기한') return 'due';
    if (c === '상태') return 'status';
    if (c === '출처' || c === '근거') return 'source';
    return null;
}

/**
 * 회의록 본문의 "## 액션 아이템" 마크다운 표에서 행을 파싱.
 * **머리글 이름 기반 매핑**이라 컬럼 순서·개수가 달라도 안전하다:
 *  - 신 형식(v2.2.258): 담당 | 액션 | 기한 | 상태 | 출처
 *  - 구 형식: 담당 | 작업 내용 | 작업 상세 | (산출물) | 기한 | 상태
 * 머리글을 못 찾으면 위치 기반으로 폴백(구형 호환). 누락 컬럼은 빈 문자열.
 */
export function parseActionItems(report: string): ParsedActionRow[] {
    const rows: ParsedActionRow[] = [];
    const empty = (): ParsedActionRow => ({ owner: '', work: '', detail: '', deliverable: '', due: '', status: '', source: '' });
    let inSection = false;
    let colMap: Partial<Record<keyof ParsedActionRow, number>> | null = null;
    for (const line of report.split('\n')) {
        if (/^#{1,6}\s*(?:\d+\.\s*)?액션\s*아이템/.test(line)) { inSection = true; colMap = null; continue; }
        if (!inSection) continue;
        if (/^#{1,6}\s/.test(line)) break;  // 다음 섹션 시작 → 종료
        if (!/^\s*\|/.test(line)) continue;
        const cells = line.split('|').slice(1, -1).map((c) => c.trim());
        if (cells.length < 3) continue;
        if (/^:?-+:?$/.test(cells[0])) continue;            // 표 구분선
        // 머리글 행 감지 → 컬럼 인덱스 매핑 기록
        const isHeader = cells.some((c) => c === '담당') && cells.some((c) => headerKey(c) === 'work');
        if (isHeader) {
            colMap = {};
            cells.forEach((c, i) => { const k = headerKey(c); if (k && colMap![k] === undefined) colMap![k] = i; });
            continue;
        }
        if (colMap) {
            // '—'/빈 칸은 미정 신호 → 빈 문자열로 정규화(다운스트림 게이트가 보류 처리).
            const get = (k: keyof ParsedActionRow): string => {
                const i = colMap![k];
                if (i === undefined) return '';
                const v = (cells[i] ?? '').trim();
                return v === '—' || v === '-' ? '' : v;
            };
            const row = empty();
            row.owner = get('owner'); row.work = get('work'); row.detail = get('detail');
            row.deliverable = get('deliverable'); row.due = get('due'); row.status = get('status'); row.source = get('source');
            if (row.work) rows.push(row);
            continue;
        }
        // 머리글 없음 — 위치 기반 폴백(구형 회의록 호환)
        const row = empty();
        if (cells.length >= 6) {
            [row.owner, row.work, row.detail, row.deliverable, row.due, row.status] = [cells[0], cells[1], cells[2], cells[3], cells[4], cells[5]];
        } else if (cells.length === 5) {
            [row.owner, row.work, row.detail, row.due, row.status] = [cells[0], cells[1], cells[2], cells[3], cells[4]];
        } else if (cells.length === 4) {
            [row.owner, row.work, row.detail, row.due] = [cells[0], cells[1], cells[2], cells[3]];
        } else {
            [row.owner, row.work, row.due] = [cells[0], cells[1], cells[2]];
        }
        if (row.work) rows.push(row);
    }
    return rows;
}
