import os
import re
import datetime
try:
    import pdfplumber
except ImportError:
    pdfplumber = None
try:
    import docx
except ImportError:
    docx = None
try:
    import fitz
except ImportError:
    fitz = None

class ResumeParser:
    """Utility class to extract text from resumes (PDF/DOCX) and parse candidate details using heuristics & regex."""

    SKILLS_DICTIONARY = [
        "Python", "Java", "React", "Node", "SQL", "HTML", "CSS", "JavaScript", "TypeScript",
        "Django", "FastAPI", "Next.js", "Vite", "Angular", "Vue", "Express", "PostgreSQL",
        "MySQL", "MongoDB", "SQLite", "AWS", "Azure", "GCP", "Docker", "Kubernetes", "Git",
        "GitHub", "C++", "C#", "C", "Go", "Rust", "Ruby", "Rails", "PHP", "Laravel", "Swift",
        "Kotlin", "Flutter", "React Native", "Tailwind", "Bootstrap", "jQuery", "Redux",
        "GraphQL", "REST API", "Flask", "Spring Boot", "Hibernate", "Oracle", "Redis",
        "Elasticsearch", "PyTorch", "TensorFlow", "Keras", "Pandas", "NumPy", "Scikit-Learn",
        "Machine Learning", "Deep Learning", "Data Science", "Jira", "Linux", "Bash", "Shell"
    ]

    DEGREES_LIST = [
        "B.Tech", "B.E.", "B.Sc", "BCA", "B.Com", "B.A.", "B.B.A", "M.Tech", "M.E.", "M.Sc",
        "MCA", "M.Com", "M.A.", "M.B.A", "Ph.D", "Diploma", "Intermediate", "High School",
        "Bachelor of Technology", "Bachelor of Engineering", "Bachelor of Science",
        "Bachelor of Computer Applications", "Master of Technology", "Master of Engineering",
        "Master of Science", "Master of Computer Applications", "Doctor of Philosophy"
    ]

    DESIGNATIONS_LIST = [
        "Software Engineer", "Frontend Developer", "Backend Developer", "Full Stack Developer",
        "Web Developer", "Software Developer", "Project Manager", "Product Manager",
        "Data Analyst", "Data Scientist", "System Administrator", "DevOps Engineer",
        "QA Engineer", "Test Engineer", "UI/UX Designer", "Technical Lead", "Architect",
        "Intern", "Associate", "Consultant", "Network Engineer", "Database Administrator"
    ]

    @classmethod
    def extract_text(cls, file_path: str) -> str:
        """Extract text from PDF or DOCX file."""
        if not os.path.exists(file_path):
            raise FileNotFoundError(f"File not found: {file_path}")

        ext = os.path.splitext(file_path)[1].lower()
        if ext == ".pdf":
            return cls._extract_pdf_text(file_path)
        elif ext in [".docx", ".doc"]:
            return cls._extract_docx_text(file_path)
        else:
            raise ValueError(f"Unsupported file format: {ext}")

    @staticmethod
    def _extract_pdf_text(file_path: str) -> str:
        text = ""
        # Try PyMuPDF (fitz) first as it is extremely fast and accurate
        if fitz is not None:
            try:
                doc = fitz.open(file_path)
                for page in doc:
                    text += page.get_text() + "\n"
                if text.strip():
                    return text
            except Exception:
                pass

        # Fallback to pdfplumber if available
        if pdfplumber is not None:
            try:
                with pdfplumber.open(file_path) as pdf:
                    for page in pdf.pages:
                        page_text = page.extract_text()
                        if page_text:
                            text += page_text + "\n"
            except Exception:
                pass

        return text

    @staticmethod
    def _extract_docx_text(file_path: str) -> str:
        if docx is None:
            return ""
        try:
            doc = docx.Document(file_path)
            return "\n".join([p.text for p in doc.paragraphs])
        except Exception as e:
            return ""

    @classmethod
    def parse(cls, file_path: str) -> dict:
        """Parse structured details from the resume file."""
        text = cls.extract_text(file_path)
        lines = [line.strip() for line in text.split("\n") if line.strip()]

        parsed_data = {
            "first_name": "",
            "last_name": "",
            "email": "",
            "phone_number": "",
            "alternate_phone_number": "",
            "date_of_birth": None,
            "gender": "",
            "city": "",
            "state": "",
            "country": "",
            "current_address": "",
            "permanent_address": "",
            "linkedin": "",
            "github": "",
            "portfolio": "",
            "personal_website": "",
            "skills": "",
            "languages": "",
            "professional_summary": "",
            "highest_qualification": "",
            "university": "",
            "college": "",
            "passing_year": None,
            "percentage_cgpa": "",
            "fresher": True,
            "total_experience": None,
            "current_company": "",
            "current_role": "",
            "educations": [],
            "experiences": [],
            "projects": [],
            "references": []
        }

        if not lines:
            return parsed_data

        # 1. Contact Details & Socials (Regex-based)
        # Email
        email_match = re.search(r'[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+', text)
        if email_match:
            parsed_data["email"] = email_match.group(0)

        # Phone numbers
        # Matches formats like +91 99999 99999, +91-9999999999, 9999999999, 09999999999 etc.
        phone_pattern = r'(?:\+?\d{1,3}[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}|\+?\d{9,15}'
        phone_matches = re.findall(phone_pattern, text)
        cleaned_phones = []
        for match in phone_matches:
            # Clean non-digits, keep '+'
            cleaned = re.sub(r'[^\d+]', '', match)
            # Ensure it fits django phone validation: optional +, followed by 9 to 15 digits
            if re.match(r'^\+?1?\d{9,15}$', cleaned):
                if cleaned not in cleaned_phones:
                    cleaned_phones.append(cleaned)

        if len(cleaned_phones) > 0:
            parsed_data["phone_number"] = cleaned_phones[0]
        if len(cleaned_phones) > 1:
            parsed_data["alternate_phone_number"] = cleaned_phones[1]

        # Social links
        linkedin_match = re.search(r'https?://(?:www\.)?linkedin\.com/in/[a-zA-Z0-9_-]+', text, re.IGNORECASE)
        if linkedin_match:
            parsed_data["linkedin"] = linkedin_match.group(0)

        github_match = re.search(r'https?://(?:www\.)?github\.com/[a-zA-Z0-9_-]+', text, re.IGNORECASE)
        if github_match:
            parsed_data["github"] = github_match.group(0)

        # 2. First/Last Name Heuristic
        # Usually name is on the first 1-3 lines, and is the first line without email, URLs, or long numbers.
        # Section headings that are NOT names (avoid picking "CAREER OBJECTIVE" etc.)
        HEADING_WORDS = {
            "CAREER", "OBJECTIVE", "RESUME", "CURRICULUM", "VITAE", "PROFILE",
            "SUMMARY", "PROFESSIONAL", "CONTACT", "PERSONAL", "DETAILS", "ABOUT",
            "EDUCATION", "EXPERIENCE", "SKILLS", "PROJECTS", "DECLARATION",
            "ADDRESS", "PHONE", "EMAIL", "MOBILE",
        }
        for line in lines[:5]:
            if parsed_data["first_name"]:
                break
            # Skip if contains contact keywords or details
            if "@" in line or "http" in line or "/" in line or "\\" in line or len(re.findall(r'\d', line)) > 3:
                continue
            words = [w for w in line.split() if w.isalpha()]
            # Skip lines that are section headings, not a person's name
            if any(w.upper() in HEADING_WORDS for w in words):
                continue
            if 1 <= len(words) <= 3:
                parsed_data["first_name"] = words[0]
                if len(words) > 1:
                    parsed_data["last_name"] = " ".join(words[1:])
                break

        # 3. Gender
        gender_match = re.search(r'\b(?:Gender|Sex)\b\s*:\s*\b(Male|Female|Other)\b', text, re.IGNORECASE)
        if gender_match:
            parsed_data["gender"] = gender_match.group(1).capitalize()
        else:
            # Simple keyword search
            for g in ["Male", "Female", "Other"]:
                if re.search(rf'\b{g}\b', text, re.IGNORECASE):
                    parsed_data["gender"] = g
                    break

        # 4. Date of Birth
        # Look for labels like DOB, Date of Birth, Birth Date, Born
        dob_label_match = re.search(r'\b(?:DOB|Date of Birth|Born|Birth Date)\b\s*:\s*([^\n]+)', text, re.IGNORECASE)
        dob_str = dob_label_match.group(1) if dob_label_match else text
        # Match common date patterns: DD/MM/YYYY, DD-MM-YYYY, YYYY-MM-DD, Month DD, YYYY
        date_patterns = [
            r'\b(\d{4})[-/.](\d{2})[-/.](\d{2})\b', # YYYY-MM-DD
            r'\b(\d{2})[-/.](\d{2})[-/.](\d{4})\b', # DD-MM-YYYY or MM-DD-YYYY
            r'\b(\d{1,2})\s+(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]*\s+(\d{4})\b' # DD Month YYYY
        ]
        dob_found = None
        for pattern in date_patterns:
            match = re.search(pattern, dob_str, re.IGNORECASE)
            if match:
                groups = match.groups()
                try:
                    if len(groups) == 3:
                        # Guess format based on positions
                        if len(groups[0]) == 4:
                            dob_found = datetime.date(int(groups[0]), int(groups[1]), int(groups[2]))
                        else:
                            # Assume DD/MM/YYYY. If group[1] (month) > 12, swap DD and MM
                            val1, val2, val3 = int(groups[0]), int(groups[1]), int(groups[2])
                            if val2 > 12:
                                dob_found = datetime.date(val3, val1, val2)
                            else:
                                dob_found = datetime.date(val3, val2, val1)
                    elif len(groups) == 2:
                        # E.g. DD Month YYYY
                        pass
                except Exception:
                    pass
            if dob_found:
                break
        if dob_found:
            parsed_data["date_of_birth"] = dob_found.strftime("%Y-%m-%d")

        # 5. PIN Code & Location heuristics
        pin_match = re.search(r'\b(\d{6})\b', text)
        pin_code = pin_match.group(1) if pin_match else ""

        # City / State heuristics from common Indian cities/states
        indian_cities = ["Mumbai", "Delhi", "Bangalore", "Bengaluru", "Hyderabad", "Ahmedabad", "Chennai", "Kolkata", "Surat", "Pune", "Jaipur", "Lucknow", "Kanpur", "Nagpur", "Indore", "Thane", "Bhopal", "Visakhapatnam", "Pimpri-Chinchwad", "Patna", "Vadodara", "Ghaziabad", "Ludhiana", "Agra", "Nashik", "Ranchi", "Faridabad", "Meerut", "Rajkot", "Kalyan-Dombivli", "Vasai-Virar", "Varanasi", "Srinagar", "Aurangabad", "Dhanbad", "Amritsar", "Navi Mumbai", "Allahabad", "Howrah", "Gwalior", "Jabalpur", "Coimbatore", "Vijayawada", "Jodhpur", "Madurai", "Raipur", "Kota", "Chandigarh", "Guwahati", "Solapur", "Hubli-Dharwad", "Bareilly", "Moradabad", "Mysore", "Gurgaon", "Aligarh", "Jalandhar", "Tiruchirappalli", "Bhubaneswar", "Salem", "Mira-Bhayandar", "Warangal", "Guntur", "Bhiwandi", "Saharanpur", "Gorakhpur", "Bikaner", "Amravati", "Noida", "Jamshedpur", "Bhilai", "Cuttack", "Firozabad", "Kochi", "Nellore", "Bhavnagar", "Dehradun", "Durgapur", "Asansol", "Rourkela", "Nanded", "Kolhapur", "Ajmer", "Akola", "Gulbarga", "Jamnagar", "Ujjain", "Loni", "Siliguri", "Jhansi", "Ulhasnagar", "Jammu", "Sangli-Miraj & Kupwad", "Mangalore", "Erode", "Belgaum", "Ambattur", "Tirunelveli", "Malegaon", "Gaya", "Jalgaon", "Udaipur", "Maheshtala"]
        indian_states = ["Andhra Pradesh", "Arunachal Pradesh", "Assam", "Bihar", "Chhattisgarh", "Goa", "Gujarat", "Haryana", "Himachal Pradesh", "Jharkhand", "Karnataka", "Kerala", "Madhya Pradesh", "Maharashtra", "Manipur", "Meghalaya", "Mizoram", "Nagaland", "Odisha", "Punjab", "Rajasthan", "Sikkim", "Tamil Nadu", "Telangana", "Tripura", "Uttar Pradesh", "Uttarakhand", "West Bengal"]

        for city in indian_cities:
            if re.search(rf'\b{city}\b', text, re.IGNORECASE):
                parsed_data["city"] = city
                break
        for state in indian_states:
            if re.search(rf'\b{state}\b', text, re.IGNORECASE):
                parsed_data["state"] = state
                break
        if parsed_data["city"] or parsed_data["state"]:
            parsed_data["country"] = "India"
            loc_parts = [p for p in [parsed_data["city"], parsed_data["state"]] if p]
            parsed_data["current_location"] = ", ".join(loc_parts)

        # Addresses heuristics
        address_match = re.search(r'\b(?:Address|Location|Residence)\b\s*[:\-]?\s*([^\n]+(?:\n[^\n]+){0,2})', text, re.IGNORECASE)
        if address_match:
            addr = address_match.group(1).strip()
            parsed_data["current_address"] = addr
            parsed_data["permanent_address"] = addr

        # 6. Skills Matching
        skills_matched = []
        for skill in cls.SKILLS_DICTIONARY:
            # Match with boundary check
            pattern = rf'\b{re.escape(skill)}\b'
            # Custom edge cases like C++ / C#
            if skill in ["C++", "C#"]:
                pattern = rf'{re.escape(skill)}'
            if re.search(pattern, text, re.IGNORECASE):
                skills_matched.append(skill)
        if skills_matched:
            parsed_data["skills"] = ", ".join(skills_matched)

        # 7. Segmenting Sections for Education, Experience, Projects, References
        sections = cls._segment_sections(lines)

        # Parse Education
        educations_text = sections.get("education", [])
        if educations_text:
            parsed_data["educations"] = cls._parse_education_section(educations_text)
            if parsed_data["educations"]:
                # Set highest academic field as summary
                first_edu = parsed_data["educations"][0]
                parsed_data["highest_qualification"] = first_edu.get("degree_name", "")
                parsed_data["college"] = first_edu.get("institution_name", "")
                parsed_data["passing_year"] = first_edu.get("passing_year")
                parsed_data["percentage_cgpa"] = first_edu.get("percentage_cgpa", "")
                # Find university if any
                for edu in parsed_data["educations"]:
                    if "university" in edu.get("institution_name", "").lower():
                        parsed_data["university"] = edu.get("institution_name", "")
                        break

        # Parse Experience
        experiences_text = sections.get("experience", [])
        if experiences_text:
            parsed_data["experiences"] = cls._parse_experience_section(experiences_text)
            if parsed_data["experiences"]:
                parsed_data["fresher"] = False
                # Deduce current company, current role, total experience
                current_exp = None
                total_months = 0
                for exp in parsed_data["experiences"]:
                    if exp.get("is_current_company"):
                        current_exp = exp
                    # Attempt to calculate duration
                    dur = cls._parse_duration_months(exp.get("joining_date"), exp.get("last_working_date"))
                    total_months += dur

                # Strip leading bullets / stray symbols that sometimes leak in
                def _clean_company(v):
                    return re.sub(r'^[^A-Za-z0-9]+', '', (v or "")).strip()

                src = current_exp or parsed_data["experiences"][0]
                parsed_data["current_company"] = _clean_company(src.get("company_name", ""))
                parsed_data["current_role"] = _clean_company(src.get("role", ""))

                parsed_data["total_experience"] = round(total_months / 12.0, 1) if total_months > 0 else 1.0
                if len(parsed_data["experiences"]) > 1:
                    parsed_data["previous_company"] = _clean_company(parsed_data["experiences"][1].get("company_name", ""))

        # Parse Projects
        projects_text = sections.get("projects", [])
        if projects_text:
            parsed_data["projects"] = cls._parse_projects_section(projects_text)

        # Parse References
        references_text = sections.get("references", [])
        if references_text:
            parsed_data["references"] = cls._parse_references_section(references_text)

        # Professional Summary heuristic
        summary_text = sections.get("summary", [])
        if summary_text:
            parsed_data["professional_summary"] = " ".join(summary_text)

        return parsed_data

    @classmethod
    def _segment_sections(cls, lines: list) -> dict:
        """Partition resume lines by matching headings."""
        sections = {
            "summary": [],
            "education": [],
            "experience": [],
            "projects": [],
            "references": []
        }

        headings = {
            "summary": ["summary", "profile", "professional summary", "about me", "objective", "career objective"],
            "education": ["education", "academic profile", "academic background", "qualifications", "academic credentials", "educational background"],
            "experience": ["experience", "work experience", "professional experience", "employment history", "work history", "career details"],
            "projects": ["projects", "key projects", "academic projects", "personal projects", "technical projects"],
            "references": ["references", "referees", "recommendations"]
        }

        current_section = None

        for line in lines:
            line_lower = line.lower().strip(":- ")
            # Check if this line is a heading
            found_heading = False
            for sec_name, keywords in headings.items():
                if line_lower in keywords or any(line_lower == kw for kw in keywords):
                    current_section = sec_name
                    found_heading = True
                    break
                # Relaxed matching for headings (e.g. "Work Experience (3 Years)")
                if any(line_lower.startswith(kw) and len(line_lower) < len(kw) + 15 for kw in keywords):
                    current_section = sec_name
                    found_heading = True
                    break

            if found_heading:
                continue

            if current_section:
                sections[current_section].append(line)
            else:
                # Top section before any heading is summary
                sections["summary"].append(line)

        return sections

    @classmethod
    def _parse_education_section(cls, lines: list) -> list:
        """Parse list of education history."""
        educations = []
        current_edu = None

        for line in lines:
            # Check if line contains a degree
            matched_degree = None
            for degree in cls.DEGREES_LIST:
                if re.search(rf'\b{re.escape(degree)}\b', line, re.IGNORECASE):
                    matched_degree = degree
                    break

            if matched_degree:
                if current_edu:
                    educations.append(current_edu)
                current_edu = {
                    "degree_name": matched_degree,
                    "field_of_study": "",
                    "institution_name": "",
                    "passing_year": None,
                    "percentage_cgpa": ""
                }
                # Look for field of study in same line
                field_match = re.search(rf'\b(?:in|of|specialization\s+in)\s+([a-zA-Z\s]{3,30})', line, re.IGNORECASE)
                if field_match:
                    current_edu["field_of_study"] = field_match.group(1).strip()

            if current_edu:
                # Look for year
                year_match = re.search(r'\b(20\d{2}|19\d{2})\b', line)
                if year_match and not current_edu["passing_year"]:
                    current_edu["passing_year"] = int(year_match.group(1))

                # Look for CGPA or Percentage
                gpa_match = re.search(r'\b(\d{1,2}(?:\.\d{1,2})?\s*%|\b\d\.\d{1,2}(?:\s*/\s*10)?\b|\b\d{1,2}\.\d{1,2}\s*cgpa)\b', line, re.IGNORECASE)
                if gpa_match and not current_edu["percentage_cgpa"]:
                    current_edu["percentage_cgpa"] = gpa_match.group(1).strip()

                # Look for institution if not set
                if not current_edu["institution_name"]:
                    inst_match = re.search(r'([^,.\n]*(?:College|University|School|Institute|Academy)[^,.\n]*)', line, re.IGNORECASE)
                    if inst_match:
                        current_edu["institution_name"] = inst_match.group(1).strip()
            else:
                # Start an adhoc edu if we see a college/university and no degree yet
                inst_match = re.search(r'([^,.\n]*(?:College|University|School|Institute|Academy)[^,.\n]*)', line, re.IGNORECASE)
                if inst_match:
                    current_edu = {
                        "degree_name": "",
                        "field_of_study": "",
                        "institution_name": inst_match.group(1).strip(),
                        "passing_year": None,
                        "percentage_cgpa": ""
                    }

        if current_edu:
            educations.append(current_edu)

        # Remove incomplete entries and keep only valid ones
        valid_educations = []
        for edu in educations:
            if edu["degree_name"] or edu["institution_name"]:
                valid_educations.append(edu)

        return valid_educations

    @classmethod
    def _parse_experience_section(cls, lines: list) -> list:
        """Parse list of experience history."""
        experiences = []
        current_exp = None

        for line in lines:
            # Check if line contains a designation
            matched_desig = None
            for desig in cls.DESIGNATIONS_LIST:
                if re.search(rf'\b{re.escape(desig)}\b', line, re.IGNORECASE):
                    matched_desig = desig
                    break

            if matched_desig:
                if current_exp:
                    experiences.append(current_exp)
                current_exp = {
                    "company_name": "",
                    "role": matched_desig,
                    "joining_date": "",
                    "last_working_date": None,
                    "is_current_company": False,
                    "responsibilities": ""
                }

            if current_exp:
                # Search for dates
                # Format: Month YYYY - Month YYYY or YYYY - YYYY
                date_range_match = re.search(r'\b((?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]*\s+\d{4}|\d{4})\s*[-–to]+\s*((?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]*\s+\d{4}|\d{4}|Present|Current)\b', line, re.IGNORECASE)
                if date_range_match:
                    start_str, end_str = date_range_match.group(1), date_range_match.group(2)
                    parsed_start = cls._parse_date_str(start_str, default=None)
                    if parsed_start:
                        current_exp["joining_date"] = parsed_start.strftime("%Y-%m-%d")
                    
                    if "present" in end_str.lower() or "current" in end_str.lower():
                        current_exp["is_current_company"] = True
                        current_exp["last_working_date"] = None
                    else:
                        parsed_end = cls._parse_date_str(end_str, default=None)
                        if parsed_end:
                            current_exp["last_working_date"] = parsed_end.strftime("%Y-%m-%d")
                        current_exp["is_current_company"] = False

                # Search for company name in line
                if not current_exp["company_name"]:
                    comp_match = re.search(r'([^,.\n]*(?:Pvt\.?\s*Ltd\.?|Ltd\.?|Inc\.?|Co\.?|Technologies|Solutions|Corporation)[^,.\n]*)', line, re.IGNORECASE)
                    if comp_match:
                        current_exp["company_name"] = comp_match.group(1).strip()

                # Add responsibilities if line starts with bullet
                if line.startswith(("•", "-", "*")) or len(line) > 30:
                    clean_resp = line.strip("•-* ")
                    if current_exp["responsibilities"]:
                        current_exp["responsibilities"] += "\n" + clean_resp
                    else:
                        current_exp["responsibilities"] = clean_resp

        if current_exp:
            experiences.append(current_exp)

        # Filter out invalid entries where company_name or joining_date is missing
        valid_experiences = []
        for exp in experiences:
            if exp["company_name"] and exp["joining_date"]:
                valid_experiences.append(exp)

        return valid_experiences

    @classmethod
    def _parse_projects_section(cls, lines: list) -> list:
        """Parse list of projects strictly without placeholder assumptions."""
        projects = []
        current_proj = None

        for idx, line in enumerate(lines):
            # Check for explicit project markers
            proj_name_match = re.match(r'^(?:project\s*\d*|title|project\s*name)\s*[:\-]\s*(.*)$', line, re.IGNORECASE)
            starts_with_num = re.match(r'^(?:\d+[\.\)\-]|\[\d+\])\s*([A-Za-z].*)$', line)
            
            is_title = False
            extracted_name = ""

            if proj_name_match:
                is_title = True
                extracted_name = proj_name_match.group(1).strip()
            elif starts_with_num:
                # E.g. "1. E-Commerce App"
                pot_name = starts_with_num.group(1).strip()
                words = pot_name.split()
                if 1 <= len(words) <= 5 and not any(kw in pot_name.lower() for kw in ["education", "experience", "reference", "skill"]):
                    is_title = True
                    extracted_name = pot_name
            else:
                # Fallback: short line (1-5 words), starts with capital letter, and not matching common keywords,
                # and followed by a line starting with technologies/GitHub link or bullets
                words = line.split()
                if 1 <= len(words) <= 5 and not line.startswith(("•", "-", "*")) and all(w[0].isupper() for w in words if w.isalpha()):
                    # Look ahead to verify if it's a project
                    if idx + 1 < len(lines):
                        next_line = lines[idx + 1].lower()
                        if next_line.startswith(("•", "-", "*")) or "github" in next_line or "tech" in next_line or "built" in next_line:
                            is_title = True
                            extracted_name = line.strip(":- ")

            if is_title and extracted_name:
                if current_proj and current_proj["project_name"]:
                    projects.append(current_proj)
                current_proj = {
                    "project_name": extracted_name,
                    "description": "",
                    "technologies_used": "",
                    "duration": "",
                    "role": ""
                }
            elif current_proj:
                # Tech stack matching
                tech_match = re.search(r'\b(?:Technologies|Tech Stack|Tools|Built with)\b\s*[:\-]\s*([^\n]+)', line, re.IGNORECASE)
                if tech_match:
                    current_proj["technologies_used"] = tech_match.group(1).strip()
                else:
                    # Append to description
                    clean_line = line.strip("•-* ")
                    if current_proj["description"]:
                        current_proj["description"] += " " + clean_line
                    else:
                        current_proj["description"] = clean_line

        if current_proj and current_proj["project_name"]:
            projects.append(current_proj)

        # Filter out projects without a valid name
        valid_projects = []
        for proj in projects:
            if proj["project_name"] and len(proj["project_name"]) > 2:
                valid_projects.append(proj)

        return valid_projects

    @classmethod
    def _parse_references_section(cls, lines: list) -> list:
        """Parse references section without guessing contact relationships or designations."""
        references = []
        current_ref = None

        for line in lines:
            email_match = re.search(r'[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+', line)
            phone_pattern = r'(?:\+?\d{1,3}[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}|\+?\d{9,15}'
            phone_match = re.search(phone_pattern, line)

            # Check if name is on this line (word starting with capital letter)
            is_name = False
            words = line.split()
            if 1 <= len(words) <= 3 and not email_match and not phone_match and all(w[0].isupper() for w in words if w.isalpha()):
                is_name = True

            if is_name:
                if current_ref:
                    references.append(current_ref)
                current_ref = {
                    "name": line,
                    "company": "",
                    "designation": "",
                    "email": "",
                    "phone": "",
                    "relationship": ""
                }
            elif current_ref:
                if email_match:
                    current_ref["email"] = email_match.group(0)
                if phone_match:
                    cleaned = re.sub(r'[^\d+]', '', phone_match.group(0))
                    current_ref["phone"] = cleaned
                # Designation/Company heuristic
                desig_match = re.search(r'\b(Manager|Lead|Director|Supervisor|Professor|Engineer)\b', line, re.IGNORECASE)
                if desig_match:
                    current_ref["designation"] = line.strip()

        if current_ref:
            references.append(current_ref)

        # Filter out empty references
        valid_refs = []
        for ref in references:
            if ref["name"]:
                valid_refs.append(ref)

        return valid_refs


    @staticmethod
    def _parse_date_str(date_str: str, default: datetime.date) -> datetime.date:
        """Helper to parse a date string like 'June 2022' or '2020' into a date object."""
        date_str = date_str.strip().lower()
        months_map = {
            "jan": 1, "feb": 2, "mar": 3, "apr": 4, "may": 5, "jun": 6,
            "jul": 7, "aug": 8, "sep": 9, "oct": 10, "nov": 11, "dec": 12
        }

        # Try matching Month YYYY
        match = re.search(r'([a-z]{3})[a-z]*\s*(\d{4})', date_str)
        if match:
            mon_str, year_str = match.group(1), match.group(2)
            month = months_map.get(mon_str, 1)
            return datetime.date(int(year_str), month, 1)

        # Try matching YYYY
        match_yr = re.search(r'\b(\d{4})\b', date_str)
        if match_yr:
            return datetime.date(int(match_yr.group(1)), 1, 1)

        return default

    @staticmethod
    def _parse_duration_months(start_date_str: str, end_date_str: str) -> int:
        """Calculate month difference between two dates."""
        try:
            if not start_date_str:
                return 0
            start = datetime.datetime.strptime(start_date_str, "%Y-%m-%d").date()
            if not end_date_str:
                end = datetime.date.today()
            else:
                end = datetime.datetime.strptime(end_date_str, "%Y-%m-%d").date()
            return (end.year - start.year) * 12 + (end.month - start.month)
        except Exception:
            return 0
