# -*- coding: utf-8 -*-
"""104호기 수리 타임라인 + 조립순서 추출.

출처(우선순위 = CEO 지시):
  1) 작업기록 폴더명 (E:\\VLA시스템\\...\\불량\\캐나다\\작업기록_날짜_부위_작업내용) — 타임라인 뼈대
  2) E500PRO-R_프레임_1차가접_공정서.md 【6단계】6-2 '놓는 순서 ①~⑪' — 조립 애니메이션 확정 소스
  3) 104호기_프레임파손_20260829 사진 14장 파일명 — 불량 발견 시점의 부위

원칙: 없는 내용을 지어내지 않는다. 호기(104/102)가 폴더명에 없으면 '불명'으로 두고
      [CEO확인] 을 붙인다. 도번이 없는 단계는 부위 매칭 없이 타임라인에만 남긴다.
"""
import io, os, re

CANADA_ROOT = os.path.join('E:' + os.sep, 'VLA시스템', '에버다임', '광산기계', '락드릴',
                           '프레임', 'E500PRO-R_프레임', '불량', '캐나다')
SPEC_MD = os.path.join('E:' + os.sep, 'VLA시스템', '에버다임', '광산기계', '락드릴',
                       '프레임', 'E500PRO-R_프레임', 'E500PRO-R_프레임_1차가접_공정서.md')
DEFECT_DIR = os.path.join(CANADA_ROOT, '104호기_프레임파손_20260829')


def _rd(p):
    with io.open(p, encoding='utf-8', errors='replace') as f:
        return f.read()


# ── 1. 작업기록 폴더 → 날짜별 단계 ────────────────────────────────────
def scan_worklog():
    out = []
    if not os.path.isdir(CANADA_ROOT):
        return out, '경로 없음: ' + CANADA_ROOT
    for n in sorted(os.listdir(CANADA_ROOT)):
        if not n.startswith('작업기록_'):
            continue
        p = os.path.join(CANADA_ROOT, n)
        if not os.path.isdir(p):
            continue
        body = n[len('작업기록_'):]
        m = re.match(r'(\d{8}(?:-\d{4})?)_(.*)$', body)
        if not m:
            out.append({'date': '', 'raw': n, 'desc': body, 'machine': '불명', 'confirm': True})
            continue
        date, rest = m.group(1), m.group(2)
        # 호기 판별 — 폴더명에 적힌 것만 인정한다(추측 금지)
        if '104호기' in rest:
            machine, confirm = '104호기', False
        elif '102호기' in rest:
            machine, confirm = '102호기', False
        else:
            machine, confirm = '불명', True
        nfile = 0
        try:
            nfile = len([x for x in os.listdir(p) if os.path.isfile(os.path.join(p, x))])
        except Exception:
            pass
        out.append({'date': date, 'raw': n, 'desc': rest.replace('_', ' '),
                    'machine': machine, 'confirm': confirm, 'files': nfile})
    out.sort(key=lambda x: (x['date'], x['raw']))
    return out, None


# ── 2. 불량 사진 파일명 → 부위 ────────────────────────────────────────
#   번호_104호기_<부위>_<상태>.jpg 에서 부위 구간만 뽑는다.
#   '전경' 은 '부위를 어느 방향에서 찍었나'(측면전경·상부전경)로도 쓰여서
#   단순 포함검사로 거르면 진짜 불량 부위(전방프레임 붐마운트)까지 날아간다.
#   → '공장입고' 를 떼어낸 뒤 남는 첫 토큰이 일반 전경어뿐인 사진만 참고용으로 본다.
GENERIC = ('전경', '원경', '트랙', '후면전경', '점검중')


def scan_defects():
    out = []
    if not os.path.isdir(DEFECT_DIR):
        return out
    for n in sorted(os.listdir(DEFECT_DIR)):
        if not n.lower().endswith(('.jpg', '.jpeg', '.png')):
            continue
        stem = os.path.splitext(n)[0]
        toks = stem.split('_')
        if len(toks) >= 3 and toks[1] == '104호기':
            toks = toks[2:]
        body = [t for t in toks if t != '공장입고']
        is_ctx = ('시리얼각인' in stem) or (not body) or (body[0] in GENERIC)
        out.append({'file': n, 'label': ' '.join(body) if body else stem,
                    'context_only': is_ctx})
    return out


def defect_areas(photos):
    """전경·식별 사진을 뺀 실제 불량 부위를 묶는다(같은 '상판들림'은 한 부위)."""
    areas, seen = [], {}
    for ph in photos:
        if ph['context_only']:
            continue
        key = ph['label'].split()[0]              # 첫 토큰 = 부위 대분류
        if key not in seen:
            seen[key] = {'area': key, 'label': ph['label'], 'photos': []}
            areas.append(seen[key])
        seen[key]['photos'].append(ph['file'])
    return areas


# ── 3. 공정서 6-2 '놓는 순서 ①~⑪' → 조립 단계 ────────────────────────
CIRCLED = '①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮'
DOBUN = re.compile(r'2110DN[-_]\d{4}(?:[-_]\d{3,4})?')


def parse_assembly_order():
    if not os.path.isfile(SPEC_MD):
        return [], '공정서 없음: ' + SPEC_MD
    s = _rd(SPEC_MD)
    i = s.find('6-2')
    if i < 0:
        return [], '6-2 놓는 순서 절을 못 찾음'
    j = s.find('### 6-3', i)
    seg = s[i:j if j > 0 else i + 6000]
    steps = []
    for line in seg.split('\n'):
        t = line.strip().lstrip('*').strip()      # 원문은 '**① 단품 8번 …**' 꼴
        if not t or t[0] not in CIRCLED:
            continue
        order = CIRCLED.index(t[0]) + 1
        text = t[1:].strip()
        clean = re.sub(r'[*`>]', '', text)
        clean = re.sub(r'\[\d{1,2}:\d{2}\]', '', clean).strip(' —-').strip()
        dobun = [d.replace('_', '-') for d in DOBUN.findall(text)]
        # ⑩ 처럼 '단품 23번 `-013` / 24번 `-014`' 로 뒤 3자리만 적힌 경우가 있다.
        #   6-2 절은 전부 2110DN-0004 계열이라 그 접두사를 붙여 후보를 만든다.
        #   다만 이것은 '추론'이므로 따로 표시해 검증문서에서 [CEO확인] 으로 뽑는다.
        guessed = []
        if not dobun:
            for suf in re.findall(r'`-(\d{3})`', text):
                guessed.append('2110DN-0004-' + suf)
        if guessed:
            dobun = guessed
        # '단품 N번' 표기 보존 — 도번이 없는 단계도 순서는 살린다
        pm = re.search(r'단품\s*([\d·\s→]+)\s*번', text)
        piece = pm.group(1).strip() if pm else ''
        steps.append({'order': order, 'piece': piece, 'dobun': dobun,
                      'guessed': bool(guessed), 'text': clean[:160]})
    steps.sort(key=lambda x: x['order'])
    return steps, None


# ── 4. 도번 → CANADA 모델 부위 매칭 ───────────────────────────────────
def match_parts(dobun_list, part_names):
    """도번으로 부위를 찾는다. 정확히 같으면 확정, 접두사만 같으면 [CEO확인]."""
    exact, prefix = [], []
    for d in dobun_list:
        for n in part_names:
            if n == d:
                exact.append(n)
            elif n.startswith(d + '-') or n.startswith(d):
                prefix.append(n)
    # 중복 제거(순서 유지)
    def uniq(a):
        o, s = [], set()
        for x in a:
            if x not in s:
                s.add(x); o.append(x)
        return o
    return uniq(exact), uniq([p for p in prefix if p not in exact])


def build(part_names):
    """빌드에 넘길 타임라인·조립순서 묶음을 만든다."""
    work, werr = scan_worklog()
    photos = scan_defects()
    areas = defect_areas(photos)
    order, oerr = parse_assembly_order()

    steps, unmatched = [], []
    for st in order:
        ex, pf = match_parts(st['dobun'], part_names)
        chosen = ex + pf
        label = '단품 ' + st['piece'] + '번' if st['piece'] else ('순서 %d' % st['order'])
        if chosen:
            for nm in chosen:
                steps.append({
                    'part': nm, 'action': '취부',
                    'note': '%s. %s' % (st['order'], st['text']),
                    '_order': st['order'],
                    '_exact': (nm in ex) and not st.get('guessed'),
                    '_guessed': bool(st.get('guessed'))
                })
        else:
            unmatched.append({'order': st['order'], 'label': label, 'text': st['text'],
                              'dobun': st['dobun']})
    return {
        'work': work, 'work_error': werr,
        'photos': photos, 'areas': areas,
        'order': order, 'order_error': oerr,
        'steps': steps, 'unmatched': unmatched
    }
