# -*- coding: utf-8 -*-
"""335~443행 일반 다단계 탭 분해. _split_plan.json 의 29개 잡을 적용.
- 각 잡: 소스행의 가장 깊은 카테고리열(base)을 찾아, 자식을 base+1, 손자를 base+2에 배치
- 잡은 행번호 내림차순(아래→위)으로 처리해 미처리 잡 인덱스 불변
- 검증된 시프트 로직(값+_style+hyperlink, 병합 +delta 시프트, base열 신규 병합)
- 순번(B)은 마지막에 305~끝 일괄 재번호
--write 로 적용.
"""
import sys, json, shutil
from copy import copy
import openpyxl
from openpyxl.utils.cell import range_boundaries

XLSX = r'D:\01.프로젝트\DB수집\작업파일\광역_사이트맵\충청남도\2.공주시\충청남도_공주시.xlsx'
PLAN = r'D:\01.프로젝트\DB수집\_스크립트\_split_plan.json'
B_START_ROW = 305
B_START_VAL = 311
CATCOLS = [4, 5, 6, 7, 8, 9, 10]  # D..J


def resolve_path(ws, row):
    """소스행의 카테고리 경로(병합 반영). {col: value}"""
    path = {}
    for c in CATCOLS:
        v = ws.cell(row, c).value
        if v is None:
            # 병합 top-left 찾기
            for mr in ws.merged_cells.ranges:
                if mr.min_col <= c <= mr.max_col and mr.min_row <= row <= mr.max_row:
                    v = ws.cell(mr.min_row, mr.min_col).value
                    break
        if v is not None:
            path[c] = v
    return path


def flatten_leaves(job):
    """잡 children 트리 -> 잎 리스트 [(lvl1name, lvl2name|None, url, L, M)]"""
    out = []
    for ch in job['children']:
        if ch.get('children'):
            for gc in ch['children']:
                out.append((ch['name'], gc['name'], gc['url'], gc.get('L', '페이지'), gc.get('M', 1)))
        else:
            out.append((ch['name'], None, ch['url'], ch.get('L', '페이지'), ch.get('M', 1)))
    return out


def apply_job(ws, source, leaves, tmpl, last_data):
    N = len(leaves)
    delta = N - 1
    base = max(resolve_path(ws, source).keys())  # 가장 깊은 카테고리열
    base_val = ws.cell(source, base).value
    if base_val is None:
        p = resolve_path(ws, source); base_val = p[base]

    # 1) 병합 해제(전체) — 시프트 위해
    old_merges = [str(mr) for mr in list(ws.merged_cells.ranges)]
    for mr in old_merges:
        ws.unmerge_cells(mr)

    # 2) 시프트 source+1..last_data -> +delta (아래에서 위로)
    if delta > 0:
        for sr in range(last_data, source, -1):
            dr = sr + delta
            for c in range(1, 28):
                s = ws.cell(sr, c); d = ws.cell(dr, c)
                d.value = s.value
                if s.has_style:
                    d._style = copy(s._style)
                if s.hyperlink is not None:
                    d.hyperlink = copy(s.hyperlink); d.hyperlink.ref = d.coordinate
                    s.hyperlink = None
                else:
                    d.hyperlink = None

    # 3) source..source+N-1 클리어 + 템플릿 스타일
    for r in range(source, source + N):
        for c in range(1, 28):
            cell = ws.cell(r, c)
            cell.value = None; cell.hyperlink = None
            if c in tmpl:
                cell._style = copy(tmpl[c])

    # 4) 잎 기입
    l1col, l2col = base + 1, base + 2
    prev_l1 = None
    for i, (l1, l2, url, L, M) in enumerate(leaves):
        r = source + i
        if l1 != prev_l1:
            ws.cell(r, l1col).value = l1
            prev_l1 = l1
        if l2:
            ws.cell(r, l2col).value = l2
        kc = ws.cell(r, 11); kc.value = url; kc.hyperlink = url
        ws.cell(r, 12).value = L
        ws.cell(r, 13).value = M
        ws.cell(r, 14).value = '어문'
        ws.cell(r, 15).value = '미부착'
    # base 값 최상단
    ws.cell(source, base).value = base_val

    # 5) 병합 재생성: 기존(>=source+1행 +delta) + 신규
    def shift(rr):
        return rr + delta if rr >= source + 1 else rr
    for rng in old_merges:
        c1, r1, c2, r2 = range_boundaries(rng)
        ws.merge_cells(start_row=shift(r1), start_column=c1, end_row=shift(r2), end_column=c2)
    # base열 신규 병합(잡 전체)
    if N > 1:
        ws.merge_cells(start_row=source, start_column=base, end_row=source + N - 1, end_column=base)
    # l1col 손자그룹 병합(같은 l1 연속 run)
    i = 0
    while i < N:
        j = i
        while j + 1 < N and leaves[j + 1][0] == leaves[i][0]:
            j += 1
        if j > i:  # 2개 이상 -> 병합
            ws.merge_cells(start_row=source + i, start_column=l1col, end_row=source + j, end_column=l1col)
        i = j + 1

    return last_data + delta


def main():
    write = '--write' in sys.argv
    jobs = json.load(open(PLAN, encoding='utf-8'))
    jobs.sort(key=lambda j: j['row'], reverse=True)  # 내림차순(아래→위)

    wb = openpyxl.load_workbook(XLSX)
    ws = wb.active
    # 템플릿 스타일(클린 데이터행 305)
    tmpl = {c: copy(ws.cell(305, c)._style) for c in range(1, 28) if ws.cell(305, c).has_style}

    # 현재 마지막 데이터행
    last_data = 3
    for r in range(3, ws.max_row + 1):
        if ws.cell(r, 11).value:
            last_data = r

    print('잡 %d개, 시작 last_data=%d' % (len(jobs), last_data))
    if not write:
        for j in jobs:
            print('  r%d %s -> 잎 %d' % (j['row'], j['label'], len(flatten_leaves(j))))
        print('(DRY)')
        return

    bak = XLSX.replace('.xlsx', '_backup_분야별분해전.xlsx')
    shutil.copy(XLSX, bak)
    for j in jobs:
        leaves = flatten_leaves(j)
        last_data = apply_job(ws, j['row'], leaves, tmpl, last_data)
        print('  적용 r%d %s (+%d) last_data=%d' % (j['row'], j['label'][:24], len(leaves) - 1, last_data))

    # 순번 B 305~last 일괄 재번호
    for r in range(B_START_ROW, last_data + 1):
        ws.cell(r, 2).value = B_START_VAL + (r - B_START_ROW)

    wb.save(XLSX)
    print('저장 완료. 백업:', bak, '| 최종 데이터행', last_data)


if __name__ == '__main__':
    main()
