#!/usr/bin/env python3
from __future__ import annotations
from pathlib import Path
from html.parser import HTMLParser
from collections import defaultdict
import html, re, shutil

ROOT = Path('/opt/data/vercel-project')
WEB = Path('/opt/data/web')
MONTH = ROOT / 'index.html'
DISTRICTS = ROOT / 'districts.html'
OUT_DIRS = [ROOT / 'daily', WEB / 'daily']
DAYS = range(16, 32)  # 15.05 is maintained from the uploaded Word source; regenerate 16-31 from monthly+dільниці.

WEEKDAYS = {1:'пʼятниця',2:'субота',3:'неділя',4:'понеділок',5:'вівторок',6:'середа',7:'четвер',8:'пʼятниця',9:'субота',10:'неділя',11:'понеділок',12:'вівторок',13:'середа',14:'четвер',15:'пʼятниця',16:'субота',17:'неділя',18:'понеділок',19:'вівторок',20:'середа',21:'четвер',22:'пʼятниця',23:'субота',24:'неділя',25:'понеділок',26:'вівторок',27:'середа',28:'четвер',29:'пʼятниця',30:'субота',31:'неділя'}

def norm(s:str)->str:
    return re.sub(r'[^A-Za-zА-Яа-яІіЇїЄєҐґЁё]+','',s).upper()

class TableParser(HTMLParser):
    def __init__(self):
        super().__init__(); self.rows=[]; self.row=[]; self.intr=False; self.incell=False; self.cell=''
    def handle_starttag(self, tag, attrs):
        if tag=='tr': self.intr=True; self.row=[]
        if tag in ('td','th') and self.intr: self.incell=True; self.cell=''
    def handle_data(self, data):
        if self.incell: self.cell += data
    def handle_endtag(self, tag):
        if tag in ('td','th') and self.incell:
            self.row.append(' '.join(self.cell.split())); self.incell=False
        if tag=='tr' and self.intr:
            if self.row: self.rows.append(self.row)
            self.intr=False

def parse_tables(path:Path):
    p=TableParser(); p.feed(path.read_text(encoding='utf-8'))
    return p.rows

monthly = {}
people_order = []
for r in parse_tables(MONTH):
    if len(r) >= 34 and r[0].isdigit():
        name = r[1].strip()
        vals = {d: r[1+d].strip() for d in range(1,32)}
        monthly[norm(name)] = {'name': name, 'vals': vals}
        people_order.append(norm(name))

# Districts: carry pair number down through blank pair cells.
district_rows = []
current_pair = ''
for r in parse_tables(DISTRICTS):
    if len(r) >= 33 and r[1] and r[1] != 'Працівник':
        if r[0].strip(): current_pair = r[0].strip()
        name = r[1].strip()
        if not name or name in ('Пт','Сб','Нд','Пн','Вт','Ср','Чт'): continue
        district_rows.append({'pair': current_pair, 'name': name, 'key': norm(name), 'days': {d: r[1+d].strip() for d in range(1,32)}})

def district_label(v:str)->str:
    v = str(v).strip()
    return f'{v} дільниця' if v in {'1','2','3'} else ''

def classify(day:int):
    cats = {'1': [], '2': [], 'Вихідний': [], 'Лікарняний': [], 'Без збереження': [], 'Відпустка': [], 'Інше / дон': []}
    for key in people_order:
        info = monthly[key]
        val = str(info['vals'][day]).strip()
        name = info['name']
        if val == '1': cats['1'].append(key)
        elif val == '2': cats['2'].append(key)
        elif val == 'В': cats['Вихідний'].append(name)
        elif val == 'ТН': cats['Лікарняний'].append(name)
        elif val == 'БЗ': cats['Без збереження'].append(name)
        elif val in ('ВП','ОТ'): cats['Відпустка'].append(name)
        elif val: cats['Інше / дон'].append(f'{name} — {val}')
    return cats

def working_rows(day:int, shift:str, cats):
    need = set(cats[shift])
    rows=[]
    for dr in district_rows:
        if dr['key'] in need:
            name = monthly[dr['key']]['name'] if dr['key'] in monthly else dr['name']
            rows.append({'pair': dr['pair'], 'name': name, 'district': district_label(dr['days'].get(day,''))})
    # If someone is working but absent from districts, append at end.
    seen={norm(r['name']) for r in rows}
    for key in cats[shift]:
        if key not in seen:
            rows.append({'pair':'', 'name': monthly[key]['name'], 'district':''})
    return rows

def render_working(day:int, title:str, rows:list[dict]):
    body=[]; last_pair=None
    for r in rows:
        pair = r['pair']
        sep = ' class="pair-separator"' if last_pair is not None and pair != last_pair else ''
        show_pair = pair if pair != last_pair else ''
        show_district = r['district'] if pair != last_pair else ''
        body.append(f'<tr{sep}><td class="pair-cell">{html.escape(show_pair)}</td><td class="name-cell">{html.escape(r["name"])}</td><td class="area-cell {('area-' + {'І дільниця':'1','ІІ дільниця':'2','ІІІ дільниця':'3'}.get(show_district,'')) if show_district else ''}">{html.escape(show_district)}</td></tr>')
        last_pair = pair
    if not body:
        body.append('<tr><td colspan="3" class="empty">Немає працівників</td></tr>')
    return f'<section class="card"><h2>{title} — {len(rows)}</h2><table class="people-table"><thead><tr><th>Пара</th><th>Прізвище</th><th>Дільниця</th></tr></thead><tbody>{"".join(body)}</tbody></table></section>'

def render_card(label, names, cls):
    lis = ''.join(f'<li>{html.escape(n)}</li>' for n in names) or '<li class="muted">Немає</li>'
    return f'<section class="status-card {cls}"><h3>{label} — {len(names)}</h3><ul class="status-list">{lis}</ul></section>'

NAV = '<div class="nav"><a href="/" data-nav="month">Графік на місяць</a><a href="/daily.html" data-nav="daily" class="active">Щоденний графік</a><a href="/timesheet.html" data-nav="timesheet">Квиток контролера</a><a href="/stt.html" data-nav="stt">Звіт СТТ</a><a href="/vacations.html" data-nav="vacations">Відпустки</a><a href="/districts.html" data-nav="districts">Дільниця</a><a href="/problems.html" data-nav="problems">Проблеми</a><a href="/needs.html" data-nav="needs">Потреби</a><a href="/archive.html" data-nav="archive">Архів</a><a href="/editor.html" data-nav="editor">Редактор</a></div>'
LOGOS = '<div class="site-logos"><img class="logo-left" src="/assets/header-left.jpg" alt="Черкаси"><img class="logo-center" src="/assets/trolleybus-center.jpg" alt="Тролейбус"><img class="logo-right" src="/assets/header-right.jpg" alt="Черкаси — найкраще місто"></div>'
CSS = '''*{box-sizing:border-box;margin:0;padding:0}body{font-family:Arial,sans-serif;background:#f0f2f5;padding:10px;color:#222}.container{max-width:1120px;margin:0 auto;background:#fff;border-radius:10px;box-shadow:0 2px 12px rgba(0,0,0,.12);padding:14px}.nav{display:flex;flex-wrap:nowrap;overflow-x:auto;justify-content:flex-start;gap:6px;position:sticky;top:0;z-index:999;background:rgba(255,255,255,.97);padding:8px;border-bottom:1px solid #e8e8e8;scrollbar-width:thin}.nav a{background:#4472C4;color:#fff;text-decoration:none;padding:8px 9px;border-radius:8px;font-size:12px;font-weight:800;white-space:nowrap;box-shadow:0 2px 6px rgba(0,0,0,.12)}.nav a.active{background:#1f4e9e;box-shadow:inset 0 0 0 2px rgba(255,255,255,.55),0 2px 6px rgba(0,0,0,.12)}@media(min-width:900px){.nav{justify-content:center}.nav a{font-size:12px;padding:8px 10px}}.site-logos{display:grid;grid-template-columns:minmax(120px,1fr) auto minmax(120px,1fr);align-items:center;gap:14px;margin:0 0 6px;padding:2px 4px;min-height:50px;pointer-events:none}.site-logos img{display:block;object-fit:contain;opacity:.94;filter:grayscale(100%);width:auto}.site-logos .logo-left{justify-self:start;max-width:300px;max-height:44px}.site-logos .logo-center{justify-self:center;max-width:150px;max-height:62px}.site-logos .logo-right{justify-self:end;max-width:340px;max-height:44px}.day-title-main{text-align:center;font-size:24px;margin:7px 0 2px;color:#111}.day-line{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:10px;margin:4px 0 8px}.day-prev{justify-self:start}.day-next{justify-self:end}.day-line a{background:#eef3ff;color:#1f4e9e;text-decoration:none;padding:7px 10px;border-radius:7px;font-weight:800;border:1px solid #c8d6f4}.date-pill{justify-self:center;text-align:center;background:#f8fafc;border:1px solid #dbe3ef;border-radius:999px;padding:7px 14px;font-weight:900;color:#111;box-shadow:0 1px 4px rgba(0,0,0,.06)}.date-pill span{display:block;font-size:12px;color:#64748b;font-weight:700;margin-top:1px}.grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:12px}.card{border:1px solid #d0d0d0;border-radius:8px;overflow:hidden;background:#fff}.card h2{background:#4472C4;color:white;padding:9px;text-align:center;font-size:17px}.people-table{width:100%;border-collapse:collapse}.people-table th{background:#f2f5fb;color:#333;text-align:left;font-size:12px;padding:7px 9px;border-bottom:1px solid #d0d0d0}.people-table td{border-bottom:1px solid #eee;padding:7px 9px;font-size:15px}.name-cell{font-weight:800}.pair-cell{width:54px;text-align:center;font-weight:900;color:#111}.area-cell{width:28%;min-width:100px;background:#fbfbfb;font-weight:700}.area-1{background:#e8f5e9!important;color:#166534!important}.area-2{background:#fff7dc!important;color:#854d0e!important}.area-3{background:#eef2ff!important;color:#3730a3!important}.people-table tr.pair-separator td{border-top:3px solid #1f2937!important}.status-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}.status-card{border:1px solid #ccc;border-radius:8px;overflow:hidden;min-height:112px;background:#fff}.status-card h3{font-size:14px;text-align:center;padding:8px;border-bottom:1px solid #ccc;background:#eee}.yellow h3{background:#FFFF00;color:#000}.blue h3{background:#00B0F0;color:white}.black h3{background:#333;color:white}.gray h3{background:#E8E8E8;color:#333}.orange h3{background:#FFA500;color:#111}.status-list{list-style:none;padding:8px 12px}.status-list li{border-bottom:1px solid #eee;padding:6px 0;font-weight:800;font-size:13px;text-align:center}.status-list li:last-child{border-bottom:0}.muted,.empty{color:#999;text-align:center;font-weight:700}.print-controls{margin-top:14px;text-align:center}@media(max-width:760px){.container{padding:10px}.grid{grid-template-columns:1fr}.status-grid{grid-template-columns:1fr 1fr}.day-title-main{font-size:21px}.day-line{grid-template-columns:1fr;gap:6px}.day-prev,.day-next,.date-pill{justify-self:center}.site-logos{grid-template-columns:1fr auto 1fr;min-height:36px;gap:6px}.site-logos .logo-left{max-width:115px;max-height:30px}.site-logos .logo-center{max-width:82px;max-height:44px}.site-logos .logo-right{max-width:125px;max-height:30px}}@media print{body{background:#fff;padding:0}.container{box-shadow:none}.nav,.print-controls{display:none}}'''

def render_page(day:int):
    cats = classify(day)
    prev_day = day-1 if day > 15 else None
    next_day = day+1 if day < 31 else None
    prev_link = f'<a href="/{"daily/%02d.05.26.html"%prev_day}">← {prev_day}</a>' if prev_day else '<span></span>'
    next_link = f'<a href="/{"daily/%02d.05.26.html"%next_day}">{next_day} →</a>' if next_day else '<span></span>'
    shift1 = working_rows(day, '1', cats)
    shift2 = working_rows(day, '2', cats)
    status = [
        render_card('Вихідний', cats['Вихідний'], 'gray'),
        render_card('Лікарняний', cats['Лікарняний'], 'yellow'),
        render_card('Без збереження', cats['Без збереження'], 'blue'),
        render_card('Відпустка', cats['Відпустка'], 'black'),
    ]
    if cats['Інше / дон']:
        status.append(render_card('Інше / дон', cats['Інше / дон'], 'orange'))
    return f'''<!DOCTYPE html><html lang="uk"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Щоденний графік контролера — {day:02d}.05.2026</title><style>{CSS}</style><link rel="manifest" href="/manifest.json"><link rel="icon" href="/assets/app-icon.svg" type="image/svg+xml"><link rel="apple-touch-icon" href="/assets/trolleybus-center.jpg"><meta name="theme-color" content="#111827"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-title" content="КП ЧЕТС"></head><body><div class="container">{NAV}{LOGOS}<h1 class="day-title-main">Щоденний графік контролера</h1><div class="day-line"><div class="day-prev">{prev_link}</div><div class="date-pill">{day} травня 2026 року<span>{WEEKDAYS[day]}</span></div><div class="day-next">{next_link}</div></div><main><div class="grid">{render_working(day,'І зміна',shift1)}{render_working(day,'ІІ зміна',shift2)}</div><div class="status-grid">{''.join(status)}</div></main><div class="print-controls" data-save-controls></div></div><script>if('serviceWorker' in navigator){{navigator.serviceWorker.register('/service-worker.js').catch(()=>{{}});}}</script><script src="/assets/save-page.js" defer></script></body></html>'''

def main():
    for out in OUT_DIRS:
        out.mkdir(parents=True, exist_ok=True)
    for day in DAYS:
        page = render_page(day)
        for out in OUT_DIRS:
            (out / f'{day:02d}.05.26.html').write_text(page, encoding='utf-8')
        print(f'wrote {day:02d}.05.26.html')

if __name__ == '__main__':
    main()
