from __future__ import annotations

from pathlib import Path
import textwrap

from PIL import Image, ImageDraw, ImageFont


OUT_DIR = Path("docs/gambar")
PNG_PATH = OUT_DIR / "dfd_confivoice.png"
SVG_PATH = OUT_DIR / "dfd_confivoice.svg"


def load_font(size: int, bold: bool = False) -> ImageFont.FreeTypeFont:
    candidates = [
        "/System/Library/Fonts/Supplemental/Arial Bold.ttf" if bold else "/System/Library/Fonts/Supplemental/Arial.ttf",
        "/System/Library/Fonts/Supplemental/Times New Roman Bold.ttf" if bold else "/System/Library/Fonts/Supplemental/Times New Roman.ttf",
        "/Library/Fonts/Arial Bold.ttf" if bold else "/Library/Fonts/Arial.ttf",
    ]
    for path in candidates:
        if path and Path(path).exists():
            return ImageFont.truetype(path, size)
    return ImageFont.load_default()


FONT_TITLE = load_font(42, bold=True)
FONT_HEAD = load_font(28, bold=True)
FONT_BODY = load_font(24)
FONT_SMALL = load_font(20)


def draw_wrapped(
    draw: ImageDraw.ImageDraw,
    text: str,
    box: tuple[int, int, int, int],
    font: ImageFont.ImageFont,
    fill: str = "#0f172a",
    align: str = "center",
    line_spacing: int = 6,
) -> None:
    x1, y1, x2, y2 = box
    max_width = x2 - x1 - 28
    words = text.split()
    lines: list[str] = []
    current = ""
    for word in words:
        trial = f"{current} {word}".strip()
        if draw.textbbox((0, 0), trial, font=font)[2] <= max_width:
            current = trial
        else:
            if current:
                lines.append(current)
            current = word
    if current:
        lines.append(current)

    line_heights = [draw.textbbox((0, 0), line, font=font)[3] for line in lines]
    total_h = sum(line_heights) + line_spacing * max(0, len(lines) - 1)
    y = y1 + ((y2 - y1) - total_h) / 2
    for line, lh in zip(lines, line_heights):
        bbox = draw.textbbox((0, 0), line, font=font)
        if align == "center":
            x = x1 + ((x2 - x1) - (bbox[2] - bbox[0])) / 2
        else:
            x = x1 + 14
        draw.text((x, y), line, font=font, fill=fill)
        y += lh + line_spacing


def round_rect(
    draw: ImageDraw.ImageDraw,
    box: tuple[int, int, int, int],
    fill: str,
    outline: str,
    width: int = 3,
    radius: int = 22,
) -> None:
    draw.rounded_rectangle(box, radius=radius, fill=fill, outline=outline, width=width)


def process(draw: ImageDraw.ImageDraw, box: tuple[int, int, int, int], title: str, desc: str) -> None:
    round_rect(draw, box, "#e0f2fe", "#0369a1", width=4, radius=28)
    x1, y1, x2, y2 = box
    draw_wrapped(draw, title, (x1 + 10, y1 + 12, x2 - 10, y1 + 58), FONT_HEAD, "#082f49")
    draw.line((x1 + 20, y1 + 70, x2 - 20, y1 + 70), fill="#7dd3fc", width=2)
    draw_wrapped(draw, desc, (x1 + 10, y1 + 78, x2 - 10, y2 - 10), FONT_SMALL, "#0f172a")


def entity(draw: ImageDraw.ImageDraw, box: tuple[int, int, int, int], title: str, desc: str) -> None:
    round_rect(draw, box, "#fef3c7", "#b45309", width=4, radius=16)
    x1, y1, x2, y2 = box
    draw_wrapped(draw, title, (x1 + 8, y1 + 8, x2 - 8, y1 + 50), FONT_HEAD, "#78350f")
    draw_wrapped(draw, desc, (x1 + 12, y1 + 56, x2 - 12, y2 - 8), FONT_SMALL, "#0f172a")


def datastore(draw: ImageDraw.ImageDraw, box: tuple[int, int, int, int], title: str, desc: str) -> None:
    x1, y1, x2, y2 = box
    draw.rectangle(box, fill="#dcfce7", outline="#15803d", width=4)
    draw.line((x1 + 24, y1, x1 + 24, y2), fill="#15803d", width=4)
    draw_wrapped(draw, title, (x1 + 32, y1 + 6, x2 - 8, y1 + 44), FONT_HEAD, "#14532d")
    draw_wrapped(draw, desc, (x1 + 32, y1 + 46, x2 - 8, y2 - 8), FONT_SMALL, "#0f172a")


def arrow(draw: ImageDraw.ImageDraw, start: tuple[int, int], end: tuple[int, int], label: str, bend: int = 0) -> None:
    sx, sy = start
    ex, ey = end
    if bend:
        mid = ((sx + ex) // 2, (sy + ey) // 2 + bend)
        draw.line((sx, sy, mid[0], mid[1], ex, ey), fill="#334155", width=4, joint="curve")
        lx, ly = mid
    else:
        draw.line((sx, sy, ex, ey), fill="#334155", width=4)
        lx, ly = (sx + ex) // 2, (sy + ey) // 2
    # arrow head
    import math

    angle = math.atan2(ey - sy, ex - sx)
    size = 16
    left = (ex - size * math.cos(angle - math.pi / 6), ey - size * math.sin(angle - math.pi / 6))
    right = (ex - size * math.cos(angle + math.pi / 6), ey - size * math.sin(angle + math.pi / 6))
    draw.polygon([end, left, right], fill="#334155")

    lines = textwrap.wrap(label, width=24)
    text = "\n".join(lines)
    bbox = draw.multiline_textbbox((0, 0), text, font=FONT_SMALL, spacing=3)
    pad = 8
    label_box = (lx - (bbox[2] - bbox[0]) / 2 - pad, ly - (bbox[3] - bbox[1]) / 2 - pad,
                 lx + (bbox[2] - bbox[0]) / 2 + pad, ly + (bbox[3] - bbox[1]) / 2 + pad)
    draw.rounded_rectangle(label_box, radius=8, fill="#ffffff", outline="#cbd5e1", width=2)
    draw.multiline_text((label_box[0] + pad, label_box[1] + pad), text, font=FONT_SMALL, fill="#0f172a", spacing=3, align="center")


def numbered_arrow(
    draw: ImageDraw.ImageDraw,
    start: tuple[int, int],
    end: tuple[int, int],
    number: str,
    bend: int = 0,
) -> None:
    sx, sy = start
    ex, ey = end
    if bend:
        mid = ((sx + ex) // 2, (sy + ey) // 2 + bend)
        draw.line((sx, sy, mid[0], mid[1], ex, ey), fill="#334155", width=4, joint="curve")
        lx, ly = mid
    else:
        draw.line((sx, sy, ex, ey), fill="#334155", width=4)
        lx, ly = (sx + ex) // 2, (sy + ey) // 2

    import math

    angle = math.atan2(ey - sy, ex - sx)
    size = 16
    left = (ex - size * math.cos(angle - math.pi / 6), ey - size * math.sin(angle - math.pi / 6))
    right = (ex - size * math.cos(angle + math.pi / 6), ey - size * math.sin(angle + math.pi / 6))
    draw.polygon([end, left, right], fill="#334155")

    r = 18
    draw.ellipse((lx - r, ly - r, lx + r, ly + r), fill="#ffffff", outline="#0f172a", width=3)
    draw.text((lx, ly - 1), number, font=FONT_SMALL, fill="#0f172a", anchor="mm")


def create_png() -> None:
    width, height = 2000, 1420
    img = Image.new("RGB", (width, height), "#f8fafc")
    draw = ImageDraw.Draw(img)
    draw.text((width / 2, 36), "Data Flow Diagram Level 1 Sistem ConfiVoice", font=FONT_TITLE, fill="#0f172a", anchor="ma")
    draw.text((width / 2, 86), "Klasifikasi tingkat percaya diri berdasarkan analisis suara", font=FONT_BODY, fill="#475569", anchor="ma")

    user = (70, 245, 390, 435)
    admin = (70, 800, 390, 980)
    auth = (560, 155, 930, 335)
    predict = (560, 405, 930, 610)
    history = (560, 675, 930, 875)
    admin_proc = (560, 940, 930, 1135)
    users = (1220, 155, 1675, 315)
    model = (1220, 390, 1675, 575)
    results = (1220, 650, 1725, 835)

    entity(draw, user, "Pengguna Mobile", "Guru/user melakukan login, input siswa, rekam/pilih audio, prediksi, simpan, dan lihat analisis")
    entity(draw, admin, "Admin Web", "Admin memantau dashboard, mengelola user, dan mengelola hasil analisis")
    process(draw, auth, "1.0 Autentikasi", "Registrasi dan login pengguna melalui endpoint /register dan /login")
    process(draw, predict, "2.0 Prediksi Audio", "Validasi audio, preprocessing, ekstraksi fitur, dan klasifikasi PD/TPD")
    process(draw, history, "3.0 Simpan & Riwayat", "Menyimpan hasil prediksi dan menampilkan riwayat analisis per siswa")
    process(draw, admin_proc, "4.0 Kelola Admin", "Menampilkan dashboard, tambah/edit/hapus data prediksi, dan kelola user")
    datastore(draw, users, "D1 users", "Akun user/admin: id, full_name, username, password_hash")
    datastore(draw, results, "D2 prediction_results", "Hasil analisis: siswa, gender, label, confidence, probabilitas, indikator suara")
    datastore(draw, model, "D3 Model SVM", "svm_voice_confidence_model.joblib + fitur suara dari librosa")

    numbered_arrow(draw, (390, 300), (560, 235), "1")
    numbered_arrow(draw, (560, 285), (390, 360), "2")
    numbered_arrow(draw, (930, 230), (1220, 230), "3")
    numbered_arrow(draw, (1220, 280), (930, 285), "4")

    numbered_arrow(draw, (390, 365), (560, 505), "5")
    numbered_arrow(draw, (930, 500), (1220, 485), "6")
    numbered_arrow(draw, (1220, 545), (930, 575), "7")
    numbered_arrow(draw, (560, 455), (390, 425), "8")

    numbered_arrow(draw, (390, 410), (560, 760), "9", bend=45)
    numbered_arrow(draw, (930, 765), (1220, 735), "10")
    numbered_arrow(draw, (1220, 800), (930, 835), "11")
    numbered_arrow(draw, (560, 835), (390, 875), "12")

    numbered_arrow(draw, (390, 890), (560, 1020), "13")
    numbered_arrow(draw, (930, 1030), (1220, 795), "14")
    numbered_arrow(draw, (560, 1080), (390, 930), "15")

    legend_x, legend_y = 70, 1165
    draw.text((legend_x, legend_y - 30), "Keterangan alur data:", font=FONT_HEAD, fill="#0f172a")
    legend_items = [
        "1 login/registrasi",
        "2 status akun",
        "3 cek/simpan akun",
        "4 data user",
        "5 data siswa dan audio",
        "6 fitur audio",
        "7 label PD/TPD + probabilitas",
        "8 hasil prediksi",
        "9 simpan/lihat riwayat",
        "10 simpan hasil",
        "11 riwayat analisis",
        "12 data riwayat",
        "13 login admin",
        "14 kelola hasil",
        "15 dashboard",
    ]
    col1 = legend_items[:8]
    col2 = legend_items[8:]
    for idx, item in enumerate(col1):
        draw.text((legend_x, legend_y + idx * 25), item, font=FONT_SMALL, fill="#334155")
    for idx, item in enumerate(col2):
        draw.text((legend_x + 500, legend_y + idx * 25), item, font=FONT_SMALL, fill="#334155")
    draw.text((1220, 1210), "PD = Percaya Diri, TPD = Tidak Percaya Diri.", font=FONT_SMALL, fill="#475569")
    draw.text((1220, 1240), "Database aktif: MySQL/MariaDB confivoice.", font=FONT_SMALL, fill="#475569")
    OUT_DIR.mkdir(parents=True, exist_ok=True)
    img.save(PNG_PATH)


def create_svg() -> None:
    # The PNG is the polished deliverable. This SVG is a lightweight fallback/reference.
    svg = """<svg xmlns="http://www.w3.org/2000/svg" width="1900" height="1200" viewBox="0 0 1900 1200">
<rect width="1900" height="1200" fill="#f8fafc"/>
<text x="950" y="60" text-anchor="middle" font-family="Arial" font-size="42" font-weight="700" fill="#0f172a">Data Flow Diagram Level 1 Sistem ConfiVoice</text>
<text x="950" y="105" text-anchor="middle" font-family="Arial" font-size="24" fill="#475569">Klasifikasi tingkat percaya diri berdasarkan analisis suara</text>
<text x="950" y="580" text-anchor="middle" font-family="Arial" font-size="26" fill="#334155">Versi PNG berisi diagram lengkap dengan alur data mobile, API, model SVM, database, dan admin web.</text>
</svg>
"""
    SVG_PATH.write_text(svg, encoding="utf-8")


def main() -> None:
    create_png()
    create_svg()
    print(PNG_PATH)
    print(SVG_PATH)


if __name__ == "__main__":
    main()
