"""Generate SHEP Results Report PDF for Cuong's Torts essay (May 29, 2026).

This report captures all feedback from the SHEP grading engine exactly as
delivered: band score, overall feedback, structural notes (From SHEP's Eyes),
issue-level feedback, and the grader's per-criterion findings.

Source: upload session 26d50216-a239-4ed0-9799-74a278fdaf44 (MEE / TORTS).
"""

from reportlab.lib.pagesizes import letter
from reportlab.lib.units import inch
from reportlab.lib.colors import HexColor
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.enums import TA_LEFT, TA_JUSTIFY, TA_CENTER
from reportlab.platypus import (
    SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, HRFlowable,
)
import os

NAVY       = HexColor("#1a2744")
GOLD       = HexColor("#c9a227")
MED_TEXT   = HexColor("#2d3748")
LIGHT_TEXT = HexColor("#4a5568")
BORDER     = HexColor("#d4cfc5")
WHITE      = HexColor("#ffffff")
ACTION_BG  = HexColor("#f0f4ff")
STRENGTH_BG = HexColor("#f0faf0")
WARN_BG    = HexColor("#fef3e6")
RED_ACCENT = HexColor("#c53030")
AMBER_ACCENT = HexColor("#b7791f")
GREEN_ACCENT = HexColor("#276749")
LIGHT_GRAY = HexColor("#f7f7f5")

def make_styles():
    s = {}
    s["title"] = ParagraphStyle("title", fontName="Helvetica-Bold", fontSize=16,
        textColor=NAVY, alignment=TA_LEFT, spaceAfter=1, leading=19)
    s["subtitle"] = ParagraphStyle("subtitle", fontName="Helvetica", fontSize=9,
        textColor=LIGHT_TEXT, alignment=TA_LEFT, spaceAfter=4, leading=11)
    s["band_big"] = ParagraphStyle("band_big", fontName="Helvetica-Bold", fontSize=28,
        textColor=RED_ACCENT, alignment=TA_CENTER, leading=32)
    s["band_label"] = ParagraphStyle("band_label", fontName="Helvetica", fontSize=8,
        textColor=LIGHT_TEXT, alignment=TA_CENTER, leading=10)
    s["section_label"] = ParagraphStyle("section_label", fontName="Helvetica-Bold",
        fontSize=8.5, textColor=GOLD, spaceBefore=10, spaceAfter=3, leading=11)
    s["h1"] = ParagraphStyle("h1", fontName="Helvetica-Bold", fontSize=11,
        textColor=NAVY, spaceBefore=8, spaceAfter=3, leading=13)
    s["h2"] = ParagraphStyle("h2", fontName="Helvetica-Bold", fontSize=9,
        textColor=MED_TEXT, spaceBefore=5, spaceAfter=2, leading=11)
    s["badge_ok"] = ParagraphStyle("badge_ok", fontName="Helvetica-Bold",
        fontSize=7.5, textColor=AMBER_ACCENT, spaceBefore=0, spaceAfter=1, leading=10)
    s["body"] = ParagraphStyle("body", fontName="Helvetica", fontSize=8.5,
        textColor=MED_TEXT, alignment=TA_JUSTIFY, leading=11.5,
        spaceBefore=1, spaceAfter=3)
    s["grader_item"] = ParagraphStyle("grader_item", fontName="Helvetica", fontSize=8,
        textColor=HexColor("#4a5568"), alignment=TA_LEFT, leading=10.5,
        leftIndent=12, spaceBefore=1, spaceAfter=1)
    s["strength"] = ParagraphStyle("strength", fontName="Helvetica", fontSize=8.5,
        textColor=GREEN_ACCENT, alignment=TA_LEFT, leading=11.5,
        spaceBefore=1, spaceAfter=1)
    s["footer"] = ParagraphStyle("footer", fontName="Helvetica", fontSize=7,
        textColor=LIGHT_TEXT, alignment=TA_LEFT, leading=9)
    return s

ST = make_styles()

def thin_hr():
    return HRFlowable(width="100%", thickness=0.4, color=BORDER, spaceAfter=3, spaceBefore=4)

def gold_hr():
    return HRFlowable(width="25%", thickness=1, color=GOLD, spaceAfter=4, spaceBefore=2)

def shaded_box(text, style, bg, border_color=None):
    bc = border_color or BORDER
    t = Table([[Paragraph(text, style)]], colWidths=["100%"])
    t.setStyle(TableStyle([
        ("BACKGROUND", (0,0), (-1,-1), bg),
        ("LEFTPADDING", (0,0), (-1,-1), 8), ("RIGHTPADDING", (0,0), (-1,-1), 8),
        ("TOPPADDING", (0,0), (-1,-1), 6), ("BOTTOMPADDING", (0,0), (-1,-1), 6),
        ("BOX", (0,0), (-1,-1), 0.3, bc),
    ]))
    return t

def grader_bullets(items):
    """items: list of (passed: bool, text: str)."""
    elements = []
    for passed, text in items:
        if passed:
            mark = '<font color="#276749"><b>&#x2713;</b></font>  '
        else:
            mark = '<font color="#c53030"><b>&#x2717;</b></font>  '
        elements.append(Paragraph(mark + text, ST["grader_item"]))
    return elements


def build():
    output_dir = r"C:\Users\sallen\Desktop\SHEP\BAR PREP by SHEP\STUDENT ANSWERS\Cuong"
    output_path = os.path.join(output_dir, "Cuong_Torts_Results.pdf")
    doc = SimpleDocTemplate(output_path, pagesize=letter,
        leftMargin=0.65*inch, rightMargin=0.65*inch,
        topMargin=0.55*inch, bottomMargin=0.5*inch)
    story = []

    # ================================================================
    # HEADER
    # ================================================================
    story.append(Paragraph("Essay Evaluation Report", ST["title"]))
    story.append(gold_hr())
    story.append(Paragraph("TORTS  |  Uploaded Practice  |  May 29, 2026  |  922 words", ST["subtitle"]))

    # Band score box
    band_data = [
        [Paragraph("3 / 6", ST["band_big"])],
        [Paragraph("SHEP PRACTICE BAND (modeled on MEE 1-6)", ST["band_label"])],
        [Paragraph("Below Passing Expectations", ST["band_label"])],
    ]
    band_table = Table(band_data, colWidths=[2.4*inch])
    band_table.setStyle(TableStyle([
        ("BACKGROUND", (0,0), (-1,-1), LIGHT_GRAY),
        ("ALIGN", (0,0), (-1,-1), "CENTER"),
        ("TOPPADDING", (0,0), (0,0), 8),
        ("BOTTOMPADDING", (0,-1), (0,-1), 8),
        ("BOX", (0,0), (-1,-1), 0.5, BORDER),
    ]))
    story.append(band_table)
    story.append(Spacer(1, 6))

    # ================================================================
    # OVERALL FEEDBACK
    # ================================================================
    story.append(Paragraph("OVERALL FEEDBACK", ST["section_label"]))
    story.append(Paragraph(
        "This is a passing-level answer, but it falls short of a top-tier performance because your "
        "rule statements are generalized rather than element-specific. To improve, you must break "
        "down each legal test into its constituent parts and map those parts directly to the facts "
        "provided in the prompt. Avoid conclusory statements; instead, use the "
        "<i>'Because [fact], [element] is satisfied'</i> structure to ensure your analysis is rigorous.",
        ST["body"]))
    story.append(Paragraph(
        "Furthermore, ensure you are using precise legal terminology. Your framing of the "
        "'Samaritan rule' and the psychiatrist's duty to warn should be grounded in the standard "
        "elements of negligence and foreseeability rather than colloquial descriptions. By focusing "
        "on element-by-element application, you will move from merely identifying the issues to "
        "providing the thorough analysis required for a higher score.",
        ST["body"]))

    # ================================================================
    # FROM SHEP'S EYES (Structural Feedback)
    # ================================================================
    story.append(Paragraph("FROM SHEP'S EYES", ST["section_label"]))
    story.append(shaded_box(
        "You have a great grasp of how to organize your thoughts. Your use of distinct paragraphs "
        "for each call makes your essay very readable. You should focus on making your rule "
        "statements more precise. For example, in your analysis of the psychiatrist, you state the "
        "rule as needing a named person. A stronger version would be: <i>Under the Tarasoff "
        "doctrine, a psychiatrist has a duty to warn when a patient makes a specific threat against "
        "an identifiable victim. Here, Ann made a vague threat against former classmates generally, "
        "rather than naming Susan specifically. Because Susan was not an identifiable victim, the "
        "psychiatrist had no duty to warn her.</i> This connects your rule directly to the facts of "
        "the case.",
        ST["strength"], STRENGTH_BG, HexColor("#9ae6b4")))
    story.append(Spacer(1, 2))
    story.append(Paragraph(
        "<b>Structure:</b> Good. IRAC separation -- strong. Organization by call -- strong. "
        "Conclusion quality -- adequate (some conclusions repeat the analysis rather than "
        "synthesizing the legal outcome).",
        ST["body"]))
    story.append(Spacer(1, 4))

    # ================================================================
    # ISSUE 1: University Premises Liability
    # ================================================================
    story.append(thin_hr())
    story.append(Paragraph("RECOGNIZED  |  SATISFACTORY", ST["badge_ok"]))
    story.append(Paragraph("University's Duty to Protect (Premises Liability)", ST["h1"]))
    story.append(Paragraph(
        "Your analysis of premises liability is too generalized. While you correctly identify the "
        "duty to protect students, you fail to articulate the specific elements required to "
        "establish a breach in this context, such as the requirement of notice (actual or "
        "constructive) regarding the broken deadbolt. A stronger answer would explicitly state that "
        "a landowner breaches its duty when it fails to remedy a known or foreseeable dangerous "
        "condition on the premises. For example: <i>'Because the University had notice of the broken "
        "deadbolt on the rear entrance, its failure to repair the lock created an unreasonable risk "
        "of harm, thereby breaching its duty of care to Susan.'</i>",
        ST["body"]))
    story.append(Paragraph("What the grader looked for:", ST["h2"]))
    story.extend(grader_bullets([
        (True,  "<b>Issue spotting:</b> Identifies the issue of whether the University had a duty "
                "to protect the student from an attack on its premises."),
        (True,  "<b>Rule:</b> Provides a general definition of negligence and asserts a duty to "
                "protect students from outside parties, but lacks the specific elements of premises "
                "liability (such as notice or foreseeability)."),
        (True,  "<b>Facts:</b> Identifies the specific facts regarding the broken deadbolt lock and "
                "the rear entrance."),
        (True,  "<b>Analysis:</b> Attempts to connect the failure to fix the lock to the breach of "
                "duty, though the analysis is somewhat conclusory regarding the causation element."),
        (True,  "<b>Conclusion:</b> Concludes that the University is liable and provides the "
                "reasoning that the breach of duty regarding the entrance led to the injuries."),
    ]))

    # ================================================================
    # ISSUE 2: Jim's Failure to Rescue
    # ================================================================
    story.append(thin_hr())
    story.append(Paragraph("RECOGNIZED  |  SATISFACTORY", ST["badge_ok"]))
    story.append(Paragraph("Jim's Duty to Rescue / Voluntary Undertaking", ST["h1"]))
    story.append(Paragraph(
        "Your rule statement regarding the 'Samaritan rule' is imprecise. You should clarify that "
        "generally, there is no duty to rescue, but a duty may arise if the defendant's own conduct "
        "created the peril or if the defendant voluntarily undertakes to assist and does so "
        "negligently. Your analysis is conclusory; you state that Jim's actions did not rise to a "
        "duty without explaining why his specific conduct -- going to get help -- did not constitute "
        "a voluntary undertaking that increased the risk of harm or induced detrimental reliance. A "
        "stronger answer would analyze whether Jim's promise to get help caused Susan to forgo other "
        "potential avenues of assistance.",
        ST["body"]))
    story.append(Paragraph("What the grader looked for:", ST["h2"]))
    story.extend(grader_bullets([
        (True,  "<b>Issue spotting:</b> Identifies the issue of whether Susan can recover damages "
                "from Jim based on his comments and actions regarding assistance."),
        (True,  "<b>Rule:</b> Articulates a rule regarding the duty to assist, noting that mere "
                "talking does not create a duty, while intentional actions to assist can create "
                "liability."),
        (True,  "<b>Facts:</b> Identifies relevant facts, specifically that Jim made comments to "
                "assist and that he went to get help."),
        (True,  "<b>Analysis:</b> Attempts to connect the facts to the rule by arguing that Jim's "
                "actions did not rise to the level of a duty because he did not take further steps "
                "or cause detrimental reliance."),
        (True,  "<b>Conclusion:</b> Concludes that Susan cannot recover damages because Jim did not "
                "have a duty to assist her."),
    ]))

    # ================================================================
    # ISSUE 3: Psychiatrist's Duty to Warn
    # ================================================================
    story.append(thin_hr())
    story.append(Paragraph("RECOGNIZED  |  SATISFACTORY", ST["badge_ok"]))
    story.append(Paragraph("Psychiatrist's Duty to Warn (Tarasoff)", ST["h1"]))
    story.append(Paragraph(
        "While you reach the correct conclusion, your framing of the duty to warn is incomplete. You "
        "correctly identify the need for a 'named person,' but you should frame this within the "
        "broader requirement of foreseeability: a psychiatrist's duty to protect is triggered when a "
        "patient makes a specific, credible threat against an identifiable victim. Your analysis is "
        "better here, but ensure you explicitly connect the psychiatrist's lack of belief in Ann's "
        "intent to the legal standard of 'foreseeability' rather than just stating the psychiatrist's "
        "subjective belief.",
        ST["body"]))
    story.append(Paragraph("What the grader looked for:", ST["h2"]))
    story.extend(grader_bullets([
        (False, "<b>Issue spotting:</b> Identifies the issue as whether Susan can recover damages "
                "from the psychiatrist, but fails to explicitly name or clearly reference the "
                "specific legal theory of a psychiatrist's duty to warn or protect third parties."),
        (True,  "<b>Rule:</b> Articulates a rule regarding the duty to warn a named person if a "
                "patient shares an intent to attack, which captures the essence of the Tarasoff "
                "duty, though it is phrased somewhat awkwardly."),
        (True,  "<b>Facts:</b> Identifies the relevant facts that there was no named person to warn "
                "and that the psychiatrist did not believe the patient would attack a specific "
                "person."),
        (True,  "<b>Analysis:</b> Connects the absence of a named victim and the lack of belief in "
                "the patient's intent to the conclusion that no duty existed."),
        (True,  "<b>Conclusion:</b> Reaches a clear conclusion that the psychiatrist is not liable, "
                "supported by the reasoning that the duty was not triggered."),
    ]))

    # ================================================================
    # ISSUE 4: PTSD / Eggshell Plaintiff
    # ================================================================
    story.append(thin_hr())
    story.append(Paragraph("RECOGNIZED  |  SATISFACTORY", ST["badge_ok"]))
    story.append(Paragraph("PTSD Damages (Eggshell Plaintiff)", ST["h1"]))
    story.append(Paragraph(
        "Your treatment of the eggshell plaintiff rule is insufficient because you fail to connect "
        "the rule to the facts of the case. You state the rule generally but do not explain how it "
        "applies to Susan's specific PTSD symptoms. A stronger answer would explicitly state: "
        "<i>'Under the eggshell plaintiff rule, a defendant is liable for the full extent of the "
        "plaintiff's injuries, even if those injuries are exacerbated by a pre-existing condition. "
        "Because the University's negligence caused the attack, it is liable for the resulting PTSD, "
        "regardless of Susan's prior vulnerability.'</i>",
        ST["body"]))
    story.append(Paragraph("What the grader looked for:", ST["h2"]))
    story.extend(grader_bullets([
        (True,  "<b>Issue spotting:</b> Explicitly identifies the issue of whether Susan can recover "
                "damages for PTSD symptoms."),
        (True,  "<b>Rule:</b> Articulates the eggshell plaintiff rule, noting that a defendant is "
                "responsible for injuries even if they existed prior to the negligence."),
        (False, "<b>Facts:</b> Mentions PTSD symptoms but fails to cite specific facts from the "
                "prompt regarding the nature of the injury or the pre-existing condition."),
        (False, "<b>Analysis:</b> Provides a conclusory statement that the University breached a "
                "duty, but fails to connect the eggshell plaintiff rule to the specific facts of "
                "Susan's PTSD."),
        (True,  "<b>Conclusion:</b> Reaches a conclusion that Susan can recover damages, but the "
                "reasoning is bare and lacks a strong connection to the rule."),
    ]))

    # ================================================================
    # FOOTER
    # ================================================================
    story.append(Spacer(1, 12))
    story.append(HRFlowable(width="100%", thickness=0.4, color=BORDER, spaceAfter=4, spaceBefore=4))
    story.append(Paragraph("Bar Prep by SHEP  |  Essay Evaluation Report  |  May 29, 2026", ST["footer"]))
    doc.build(story)
    return output_path


if __name__ == "__main__":
    path = build()
    print(f"Results report: {path}")
