/**
 * 회의록 generated_minutes(Markdown)에서 체크리스트·액션 섹션 추출
 * LLM 출력은 ##/### 혼용, 번호 접두, 굵게 표기 등이 섞이므로 규칙을 넓게 둔다.
 */

const CHECKLIST_HEADINGS = [
  /^##\s+후속\s*확인\s*체크리스트\s*$/i,
  /^##\s+체크리스트\s*\([^)]*\)\s*$/i,
  /^##\s+체크리스트\s*$/i,
  /^##\s+후속\s*확인\s*$/i,
];

const ACTION_HEADINGS = [
  /^##\s+Action\s+Items\s*$/i,
  /^##\s+Action\s+Item\s*$/i,
  /^##\s+액션\s*아이템\s*$/i,
  /^##\s+액션\s*$/i,
];

function stripMd(s) {
  if (!s) return "";
  return s
    .replace(/\*\*([^*]+)\*\*/g, "$1")
    .replace(/__([^_]+)__/g, "$1")
    .trim();
}

/**
 * 제목 줄에서 비교용 텍스트 (굵게·번호 접두 제거)
 * @param {string} line
 */
function normalizeHeadingText(line) {
  return line
    .trim()
    .replace(/^#{1,6}\s+/, "")
    .replace(/^\d+[.)]\s*/, "")
    .replace(/\*\*/g, "")
    .replace(/\s+/g, " ")
    .trim()
    .toLowerCase();
}

/**
 * @param {string} trimmed — 한 줄 (마크다운 제목 가능)
 */
function isChecklistHeadingLine(trimmed) {
  const t = normalizeHeadingText(trimmed);
  if (!t) return false;
  if (/액션\s*아이템/.test(t) && /체크리스트/.test(t)) return true;
  if (/^액션\s*아이템\s*$/.test(t) || /^action\s+items\s*$/.test(t)) return false;
  if (t.includes("체크리스트")) return true;
  if (t.includes("후속") && (t.includes("확인") || t.includes("체크"))) return true;
  if (t.includes("follow") && t.includes("check")) return true;
  if (t.includes("follow-up") || t.includes("follow up")) return true;
  return false;
}

/**
 * @param {string} trimmed
 */
function isActionHeadingLine(trimmed) {
  const t = normalizeHeadingText(trimmed);
  if (!t) return false;
  if (/^액션\s*아이템\s*$/.test(t) || /^action\s+items\s*$/i.test(t) || /^action\s+item\s*$/i.test(t)) return true;
  if (t.includes("액션") && (t.includes("아이템") || t.includes("항목"))) return true;
  if (t.includes("action") && (t.includes("item") || t.includes("items"))) return true;
  return false;
}

/**
 * @param {string} text
 * @param {(trimmedLine: string) => boolean} predicate
 * @returns {string|null}
 */
function extractSectionByHeadingPredicate(text, predicate) {
  const lines = text.split(/\r?\n/);
  for (let i = 0; i < lines.length; i++) {
    const trimmed = lines[i].trim();
    const hm = trimmed.match(/^(#{1,6})(\s+.+)$/);
    if (!hm) continue;
    const level = hm[1].length;
    if (!predicate(trimmed)) continue;
    const body = [];
    for (let j = i + 1; j < lines.length; j++) {
      const tj = lines[j].trim();
      const nextHm = tj.match(/^(#{1,6})\s+/);
      if (nextHm) {
        const nextLevel = nextHm[1].length;
        if (nextLevel <= level) break;
      }
      body.push(lines[j]);
    }
    const joined = body.join("\n").trim();
    if (joined.length) return joined;
  }
  return null;
}

/**
 * 다음 ## 제목 전까지 본문 추출 (레거시: ## 만 구분)
 * @param {string} text
 * @param {RegExp[]} headingMatchers
 * @returns {string|null}
 */
function extractSectionAfterHeading(text, headingMatchers) {
  const lines = text.split(/\r?\n/);
  for (let i = 0; i < lines.length; i++) {
    const trimmed = lines[i].trim();
    for (const re of headingMatchers) {
      if (re.test(trimmed)) {
        const body = [];
        for (let j = i + 1; j < lines.length; j++) {
          if (/^##\s+/.test(lines[j].trim())) break;
          body.push(lines[j]);
        }
        const joined = body.join("\n").trim();
        return joined.length ? joined : null;
      }
    }
  }
  return null;
}

/**
 * @typedef {{ title: string, detail: string, assignee: string|null, due_note: string|null, completed: boolean }} ParsedItem
 */

/**
 * @param {string} body
 * @returns {ParsedItem[]}
 */
function parseBulletItems(body) {
  if (!body || !body.trim()) return [];
  const lines = body.split(/\r?\n/);
  /** @type {ParsedItem[]} */
  const items = [];
  /** @type {ParsedItem|null} */
  let cur = null;
  const flush = () => {
    if (cur) {
      items.push(cur);
      cur = null;
    }
  };
  let inFootnote = false;
  for (const raw of lines) {
    const t = raw.trim();
    if (!t) continue;
    if (/^※/.test(t)) {
      inFootnote = true;
      flush();
      continue;
    }
    if (inFootnote) continue;
    let m = t.match(/^\s*[-*•]\s+\[([ xX✓])\]\s*(.+)$/);
    if (m) {
      flush();
      items.push({
        title: stripMd(m[2].trim()),
        detail: "",
        assignee: null,
        due_note: null,
        completed: /[xX✓]/.test(m[1]),
      });
      continue;
    }
    m = t.match(/^\s*\[\s*([ xX✓])\s*\]\s+(.+)$/);
    if (m) {
      flush();
      items.push({
        title: stripMd(m[2].trim()),
        detail: "",
        assignee: null,
        due_note: null,
        completed: /[xX✓]/.test(m[1]),
      });
      continue;
    }
    m = t.match(/^\s*[☐☑✓✔]\s*(.+)$/);
    if (m) {
      flush();
      const done = /^[☑✓✔]/.test(t.trim());
      items.push({
        title: stripMd(m[1].trim()),
        detail: "",
        assignee: null,
        due_note: null,
        completed: done,
      });
      continue;
    }
    m = t.match(/^\s*[-*•]\s+(.+)$/);
    if (m) {
      flush();
      cur = { title: stripMd(m[1].trim()), detail: "", assignee: null, due_note: null, completed: false };
      continue;
    }
    m = t.match(/^\s*\d+\.\s+(.+)$/);
    if (m) {
      flush();
      cur = { title: stripMd(m[1].trim()), detail: "", assignee: null, due_note: null, completed: false };
      continue;
    }
    if (cur) {
      cur.detail += (cur.detail ? "\n" : "") + t;
    } else if (items.length) {
      const last = items[items.length - 1];
      last.detail += (last.detail ? "\n" : "") + t;
    }
  }
  flush();
  return items.filter((x) => x.title.length > 0);
}

/**
 * 마크다운 표 행에서 셀 배열 추출 (`| a | b | c |` → ["a","b","c"])
 * @param {string} line
 * @returns {string[]}
 */
function parseTableCells(line) {
  let s = String(line || "").trim();
  if (/^\d+\s*\|/.test(s) && !s.startsWith("|")) s = "| " + s;
  return s
    .replace(/^\s*\|\s*/, "")
    .replace(/\s*\|\s*$/, "")
    .split("|")
    .map((cell) => cell.trim());
}

/**
 * 마크다운 표 구분선 행인지 판별 (각 셀이 `-`, `:`, 공백만 포함)
 * @param {string} t - trim된 행
 */
function isTableSeparatorRow(t) {
  const cells = t.replace(/^\s*\|\s*/, "").replace(/\s*\|\s*$/, "").split("|");
  return cells.length > 0 && cells.every((c) => /^[\s:]*-+[\s:-]*$/.test(c.trim()) || c.trim() === "");
}

/**
 * 마크다운 표 형식 액션 아이템 파싱 (# | 담당 | 내용 | 기한 구조)
 * @param {string} body
 * @returns {ParsedItem[]}
 */
function parseTableActionRows(body) {
  if (!body || !body.trim()) return [];
  const lines = body.split(/\r?\n/);
  /** @type {string[]|null} */
  let headers = null;
  /** @type {ParsedItem[]} */
  const out = [];
  for (const raw of lines) {
    const t = raw.trim();
    if (!t) continue;
    const isPipeRow = /^\|/.test(t) || /^\d+\s*\|/.test(t);
    if (!isPipeRow) continue;
    const tSep = /^\d+\s*\|/.test(t) && !t.startsWith("|") ? "| " + t : t;
    if (isTableSeparatorRow(tSep)) continue;
    const cells = parseTableCells(t);
    if (!cells.length) continue;
    if (headers === null) {
      headers = cells.map((h) => h.toLowerCase());
      continue;
    }
    const get = (keywords) => {
      for (const kw of keywords) {
        const idx = headers.findIndex((h) => h.includes(kw));
        if (idx !== -1 && idx < cells.length) return cells[idx].trim();
      }
      return "";
    };
    let title = get(["내용", "항목", "task", "할 일"]);
    let assignee = get(["담당", "assignee", "담당자"]);
    let due_note = get(["기한", "due", "마감"]);

    const numFirstCell =
      cells.length >= 4 && /^\d+$/.test(String(cells[0] || "").trim());
    const headerHasOrdinalCol = headers.some((h) => {
      const x = String(h || "").trim().toLowerCase();
      return x === "#" || x === "no" || /^no\.?$/.test(x) || x.includes("순번") || x.includes("번호");
    });
    /** 헤더에 '#'/순번 열이 빠져 있으나 데이터 줄은 순번 포함 4열 — 담당·내용·기한 순으로 해석 */
    if (numFirstCell && !headerHasOrdinalCol && headers.length >= 3 && headers.length <= cells.length - 1) {
      assignee = String(cells[1] || "").trim();
      due_note = String(cells[cells.length - 1] || "").trim();
      title = cells
        .slice(2, cells.length - 1)
        .join(" ")
        .replace(/\s*\|\s*/g, " ")
        .trim();
    }

    if (!title) continue;
    out.push({
      title: stripMd(title),
      detail: "",
      assignee: assignee || null,
      due_note: due_note || null,
      completed: false,
    });
  }
  return out.filter((x) => x.title.length > 0);
}

/**
 * 액션 아이템 번호 목록 블록 (담당/기한/할 일)
 * @param {string} body
 * @returns {ParsedItem[]}
 */
function refineActionLines(detailText) {
  let assignee = null;
  let due_note = null;
  const rest = [];
  for (const line of (detailText || "").split(/\r?\n/)) {
    const r = line.trim();
    if (!r) continue;
    const t = r.replace(/^\*\s+/, "").trim();
    if (/담당\s*:/i.test(t)) {
      const m = t.match(/담당\s*:\s*(.+)$/i);
      if (m) assignee = stripMd(m[1].replace(/\*\*/g, "").replace(/^\*+|\*+$/g, "").trim());
      continue;
    }
    if (/기한\s*:/i.test(t)) {
      const m = t.match(/기한\s*:\s*(.+)$/i);
      if (m) due_note = stripMd(m[1].replace(/\*\*/g, "").replace(/^\*+|\*+$/g, "").trim());
      continue;
    }
    if (/할\s*일\s*:/i.test(t)) {
      const m = t.match(/할\s*일\s*:\s*(.+)$/i);
      if (m) rest.push(stripMd(m[1]));
      continue;
    }
    rest.push(r);
  }
  return { assignee, due_note, detail: rest.join("\n").trim() };
}

function parseNumberedActionBlocks(body) {
  if (!body || !body.trim()) return [];
  const lines = body.split(/\r?\n/);
  /** @type {ParsedItem[]} */
  const out = [];
  let i = 0;
  while (i < lines.length) {
    const line = lines[i].trim();
    /** `1. 제목` 또는 `1) 제목` */
    const nm = line.match(/^(\d+)[.)]\s+(.+)$/);
    if (nm) {
      const title = stripMd(nm[2].trim());
      const rest = [];
      i++;
      while (i < lines.length) {
        const lt = lines[i].trim();
        if (/^\d+[.)]\s+/.test(lt)) break;
        if (lt) rest.push(lines[i]);
        i++;
      }
      const rawDetail = rest.join("\n").trim();
      const refined = refineActionLines(rawDetail);
      out.push({
        title,
        detail: refined.detail,
        assignee: refined.assignee,
        due_note: refined.due_note,
        completed: false,
      });
      continue;
    }
    i++;
  }
  return out.filter((x) => x.title.length > 0);
}

/**
 * 업무 체크리스트 자동 동기화용: 규칙 기반으로 액션(번호 목록) → 체크리스트 순으로 항목 수집(제목 기준 중복 제거)
 * @param {string} markdown
 * @returns {ParsedItem[]}
 */
function parseAllRuleBasedWorkItems(markdown) {
  const text = (markdown || "").trim();
  if (!text) return [];
  const actions = parseItemsFromMinutes(text, "actions");
  const checklist = parseItemsFromMinutes(text, "checklist");
  const seen = new Set();
  /** @type {ParsedItem[]} */
  const out = [];
  for (const it of [...actions, ...checklist]) {
    const t = (it.title || "").trim().toLowerCase();
    if (!t) continue;
    if (seen.has(t)) continue;
    seen.add(t);
    out.push(it);
  }
  return out;
}

/**
 * 체크리스트 섹션 본문 찾기: 유연 매칭 → 레거시 정규식
 * @param {string} text
 * @returns {string|null}
 */
function extractChecklistSectionBody(text) {
  const flexible = extractSectionByHeadingPredicate(text, isChecklistHeadingLine);
  if (flexible) return flexible;
  return extractSectionAfterHeading(text, CHECKLIST_HEADINGS);
}

/**
 * @param {string} text
 * @returns {string|null}
 */
function extractActionSectionBody(text) {
  const flexible = extractSectionByHeadingPredicate(text, isActionHeadingLine);
  if (flexible) return flexible;
  return extractSectionAfterHeading(text, ACTION_HEADINGS);
}

/**
 * @param {string} generatedMinutes
 * @param {'checklist'|'actions'} mode
 * @returns {ParsedItem[]}
 */
function parseItemsFromMinutes(generatedMinutes, mode = "checklist") {
  const text = (generatedMinutes || "").trim();
  if (!text) return [];
  if (mode === "actions") {
    const section = extractActionSectionBody(text);
    if (!section) return [];
    const tableRows = parseTableActionRows(section);
    if (tableRows.length) return tableRows;
    const numbered = parseNumberedActionBlocks(section);
    if (numbered.length) return numbered;
    return parseBulletItems(section);
  }
  const section = extractChecklistSectionBody(text);
  if (!section) return [];
  return parseBulletItems(section);
}

module.exports = {
  extractSectionAfterHeading,
  extractSectionByHeadingPredicate,
  extractChecklistSectionBody,
  extractActionSectionBody,
  parseBulletItems,
  parseNumberedActionBlocks,
  parseTableCells,
  parseTableActionRows,
  isTableSeparatorRow,
  parseItemsFromMinutes,
  parseAllRuleBasedWorkItems,
  CHECKLIST_HEADINGS,
  ACTION_HEADINGS,
  isChecklistHeadingLine,
  isActionHeadingLine,
};
